#!/bin/bash
#-------------------------------------------------------------------------
#    Author:		Ross Mark (rossm@controllingedge.com.au)
#    Date:		Tue Mar 11 10:02:57 EST 2003
#
#    Copyright (C) 2003-2004 Ross Mark
#
#-------------------------------------------------------------------------
#
#    Description:
#    Archive SVN (asvn) will allow the recording of file types not
#    normally handled by svn. Currently this includes devices,
#    symlinks and file ownership/permissions.
#
#    Every file and directory has a 'file:permissions' property set and
#    every directory has a 'dir:devices' and 'dir:symlinks' for
#    recording the extra information.
#	
#    Run this script instead of svn with the normal svn arguments.
#
#
#    Licensing:
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
#-------------------------------------------------------------------------

# $HeadURL: http://svn.collab.net/repos/svn/branches/1.6.x/contrib/client-side/asvn $
# $LastChangedDate: 2008-01-28 00:24:25 +0000 (Mon, 28 Jan 2008) $
# $LastChangedBy: blair $
# $LastChangedRevision: 29065 $

SVN=/usr/bin/svn
ACTION=""
DEV_PROP="dir:devices"
SYM_PROP="dir:symlinks"
FILE_PROP="file:permissions"
TMPFILE=/tmp/asvn.tmp.$$
TMPFILE1=/tmp/asvn.tmp1.$$
TMPFILE2=/tmp/asvn.tmp2.$$
PCWD=`/bin/pwd`
SKIPSVN='\( -name .svn -prune -false \)'
PRINTDETAILS="-printf \"file='%p' mode=%m user=%u(%U) group=%g(%G)\n\""

trap cleanup 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

function cleanup()
{
    rm -f $TMPFILE $TMPFILE1 $TMPFILE2
}

function basedirname()
{
    refname="$1"
    dir="`dirname \"$2\"`"
    ref="`expr \"$dir\" : \"$refname/\(.*\)\"`"
    if [ -z "$ref" ]
    then
        echo .
    else
        echo $ref
    fi
}

#
# Modifies TMPFILE2
#
function addignorefile()
{
    file="`basename \"$1\"`"
    dir="`dirname \"$1\"`"

    efile="`echo $file |sed -e 's!\([\[\(\$]\)!\\\\\1!g'`"
    gefile="`echo $efile |sed -e 's!\(\\\\\)!\\\\\\\\\1!g'`"
    if ! ($SVN propget svn:ignore "$dir" | grep -q "^$gefile\$")
    then
        $SVN propget svn:ignore "$dir"  |sed -e '/^$/d' >$TMPFILE2
        echo "$efile" >>$TMPFILE2 
        $SVN propset svn:ignore -F $TMPFILE2 "$dir"
        echo setting ignore
        #cat $TMPFILE2 >&2
    fi
}

function deleteignorefile()
{
    file="`basename \"$1\"`"
    dir="`dirname \"$1\"`"
    efile="`echo $file |sed -e 's!\([\[\(\$]\)!\\\\\1!g'`"
    gefile="`echo $efile |sed -e 's!\(\\\\\)!\\\\\\\\\1!g'`"
    echo "deleting ignore setting for '$file'"
    if ($SVN propget svn:ignore "$dir" | grep -q "^$gefile\$")
    then
        $SVN propget svn:ignore "$dir" |sed -e '/^$/d'  |grep -v "^$gefile\$" >$TMPFILE2
        $SVN propset svn:ignore -F $TMPFILE2 "$dir"
        #cat $TMPFILE2 >&2
    fi
}

function recorddirinfo
{
    eval "find $PCWD $SKIPSVN -o \( -type d ! -name .svn  -print \)" |while read dirlist
    do
        updatedirsymlinks $1 "$dirlist"
        updatedirdevices $1 "$dirlist"
    done
}

function updatedirdevices()
{
    CHECKIN=false
    if [ "$1" = "-ci" ]
    then
        CHECKIN=true
        shift
    fi
    dir="$1"

    echo checking $dir for devices
    #
    # Obtain the list of devices in this directory
    #
    find "$dir" \( \( -type b -o -type c -o -type p \) -print \)  -o  -type d ! -name "`basename \"$dir\"`" -prune | while read file
    do
        echo -n `find "$file" -printf "file='%f' mode=%m user=%u(%U) group=%g(%G)"`
        [ -b "$file" ] && echo -n ' type=b'
        [ -c "$file" ] && echo -n ' type=c'
        [ -p "$file" ] && echo ' type=p'
        if [ -b "$file" -o -c "$file" ] 
        then
            ls -l "$file" |
                sed -e 's/^[-lcpbrdwxXstugoTS]* *[0-9] [^ ]* *[^ ]* *\([0-9]*\), *\([0-9]*\) .*/ major=\1 minor=\2/'
        fi
        # In this case file is the full path.
        addignorefile "$file"

    done | sort >$TMPFILE

    #
    # Obtain the currently defined devices
    #
    $SVN propget $DEV_PROP "$dir" >$TMPFILE1

    #
    # If the two list are the same then there is nothing to do.
    #
    if /usr/bin/cmp $TMPFILE1 $TMPFILE >/dev/null
    then
        return
    fi

    if [ -s $TMPFILE ]
    then
        # There are devices in this directory
        if [ "$CHECKIN" = "true" ]
        then
            # Add the current devices to the property
            $SVN propset $DEV_PROP "$dir" -F $TMPFILE
        else
            # Delete all the unwanted devices ie not in TMPFILE1
            cat $TMPFILE |while read line
            do
                file="`expr \"$line\" : \"file='\(.*\)' mode\"`"
                if ! grep -q "file='$file'" $TMPFILE1
                then
                    rm "$file"
                    deleteignorefile "$file"
                fi
            done
        fi
    else
        # There are no devices in this directory
        if [ "$CHECKIN" = "true" ]
        then
            $SVN propdel $DEV_PROP "$dir"
        fi
    fi

    #
    # If we are not a checkin then make sure all the devices are defined
    #
    if [ "$CHECKIN" != "true" ]
    then
        cat $TMPFILE1 |while read info
        do
            #echo info = $info
            [ -z "$info" ] && continue
            grep -q "$info" $TMPFILE && continue # This line still matches
            file="`expr \"$info\" : \"file='\(.*\)' \"`"
            mode=`expr "$info" : ".*' mode=\([0-9]*\) "`
            user=`expr "$info" : ".* user=\([^(]*\)("`
            uid=`expr "$info" : ".* user=[^(]*(\([0-9]*\) "`
            group=`expr "$info" : ".* group=\([^(]*\)("`
            gid=`expr "$info" : ".* group=[^(]*(\([0-9]*\) "`
            type=`expr "$info" : ".* type=\(.\)"`
            major=`expr "$info" : ".* major=\([0-9]*\)"`
            minor=`expr "$info" : ".* minor=\([0-9]*\)"`
            #
            # This file is either missing or wrong
            # Delete the old and create it anew.
            #
            rm -f "$dir/$file"
            mknod --mode=$mode "$dir/$file" $type $major $minor
            chown $user:$group "$dir/$file"
            addignorefile "$dir/$file"
        done
    fi
}

function updatedirsymlinks()
{
    CHECKIN=false
    if [ "$1" = "-ci" ]
    then
        CHECKIN=true
        shift
    fi
    dir="$1"

    echo checking $dir for symlinks
    cp /dev/null $TMPFILE
    #
    # Obtain the list of symlinks in this directory
    #
    find "$dir" \( -type l -printf "file='%f' dest='%l'\n" \)  -o  -type d ! -name "`basename \"$dir\"`" -prune |
        sort >$TMPFILE
    
    #
    # Make sure all the symlinks are being ignored.
    #
    cat $TMPFILE |while read line
    do
        file="`expr \"$line\" : \"file='\(.*\)' dest\"`"
        addignorefile "$dir/$file"
    done
    
    #
    # Obtain the currently defined symlinks
    #
    $SVN propget $SYM_PROP "$dir" >$TMPFILE1

    #
    # If the two list are the same then there is nothing to do.
    #
    if cmp $TMPFILE1 $TMPFILE >/dev/null
    then
        return
    fi

    if [ -s $TMPFILE ]
    then
        # There are symlinks in this directory
        if [ "$CHECKIN" = "true" ]
        then
            # Add the current symlinks to the property
            $SVN propset $SYM_PROP "$dir" -F $TMPFILE
        else
            # Delete all the unwanted symlinks
            cat $TMPFILE |while read line
            do
                file="`expr \"$line\" : \"file='\(.*\)' dest\"`"
                efile="`echo \"$file\" |sed -e 's!\([\[\(\$]\)!\\\\\1!g'`"
                if ! grep -q "file='$efile'" $TMPFILE1
                then
                    rm "$dir/$file"
                    deleteignorefile "$dir/$file"
                fi
            done
        fi
    else
        # There are no symlinks in this directory
        if [ "$CHECKIN" = "true" ]
        then
            $SVN propdel $SYM_PROP "$dir"
        fi
    fi

    #
    # If we are not a checkin then make sure all the symlinks are defined
    #
    if [ "$CHECKIN" != "true" ]
    then
        cat $TMPFILE1 |while read info
        do
            [ -z "$info" ] && continue
            file="`expr \"$info\" : \"file='\(.*\)' dest\"`"
            dest="`expr \"$info\" : \".*' dest='\(.*\)'$\"`"

            if [ -L "$dir/$file" ]
            then
                [ "`find \"$dir/$file\" -printf '%l'`" = "$dest" ] && continue
            fi 
            rm -f "$dir/$file"
            ln -s "$dest" "$dir/$file"
        done
    fi
}

function recordpermissions()
{
    CHECKIN=false
    if [ "$1" = "-ci" ]
    then
        CHECKIN=true
        shift
    fi

    # Find all the directories and files
    cp /dev/null $TMPFILE

    eval "find $PCWD $SKIPSVN -o \( \( -type d ! -name .svn  \) -o -type f \) $PRINTDETAILS" | while read info
    do
        device=`expr "$info" : "file='\(.*\)' mode"`
        info=`expr "$info" : "file='.*' \(mode.*\)"`

        if [ "$PCWD" = "$device" ]
        then
            dir="."
            file=""
        else
            dir="`basedirname \"$PCWD\" \"$device\"`"
            file="`basename \"$device\"`"
        fi

        # see if the properties have changed.
        if [ "`$SVN propget $FILE_PROP \"$dir/$file\"`" != "$info" ]
        then
            if [ "$CHECKIN" = "true" ]
            then
                $SVN propset $FILE_PROP  "$info" "$dir/$file"
            else
                info=`$SVN propget $FILE_PROP "$dir/$file"`
                mode=`expr "$info" : "mode=\([0-9]*\) "`
                user=`expr "$info" : ".* user=\([^(]*\)("`
                uid=`expr "$info" : ".* user=[^(]*(\([0-9]*\) "`
                group=`expr "$info" : ".* group=\([^(]*\)("`
                gid=`expr "$info" : ".* group=[^(]*(\([0-9]*\) "`
                if  [ "$user" = "" -o "$group" = ""  -o "$mode" = "" ]
                then
                    echo "property $FILE_PROP not set for $dir/$file"
                else
                    chown $user:$group  "$dir/$file"
                    chmod $mode "$dir/$file"
                fi
            fi
        fi
    done
}


function pre_checkin()
{
    echo this is the pre checkin process
    recorddirinfo -ci
    recordpermissions -ci
}

function post_checkout()
{
    echo this is the post checkout process
    if [ "$CHDIR" = "true" ]
    then
        shift $(($# -1))
        cd "`basename \"$1\"`"
        PCWD="$PCWD/`basename \"$1\"`"
    fi
    recorddirinfo 
    recordpermissions 
}

CHDIR=false
case "$1" in
  checkout|co)      CHDIR=true; ACTION="post";;
  commit|ci)        ACTION="pre";;
  switch|sw)        ACTION="post";;
  update|up)        ACTION="post";;
  *);;
esac

[ "$ACTION" =  "pre" ] && pre_checkin $@

$SVN $@

[ $? = 0 -a "$ACTION" = "post" ] && post_checkout $@

cleanup
#
# vim: set ai ts=8 sw=4
#
