[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Submission for /contrib for a script that can version control filesystems

From: Ross Mark <rossm_at_controllingedge.com.au>
Date: 2004-03-19 03:20:12 CET

Last year I wrote a script for using subversion to version control
entire filesystems including symlinks, devices, ownership and
permissions of files and directories. Originally had posted it to the
mailing list and asked to do so again yesterday on the users@ list where
Sander pointed out that I was missing the licensing and also preferred
it come on the dev@ list. So here it is. I'm releasing it under GPL as a
number of the files in /contrib were already using this.

For a quick explanation this script is used as a replacement for svn.
All arguments will be passed directly to the svn program. Depending on
what the command is, commit, checkout, switch or update, the script will
perform some pre or post processing of the working copy. SVN properties
a used to record symlinks, devices and permissions with ownership. On
checkin the script scans the working copy or any changes and updates the
appropriate property prior to calling svn. On a checkout, switch or
update, after calling svn the script checks the property entries on each
file and directory and updates the working copy as appropriate. As
symlinks and devices are identified they are automatically added into
the svn:ignore property.

The names of properties used can be easily changed by updating the
variables in the start of the asvn script. I'm not aware of any standard
being proposed for how properties should be named. Currently I use
"dir:symlinks", dir:devices" and "file:permissions". I am happy for the
script to be changed to use the asvn name space so they are associated
with the script or a different name space that is more generic if other
tools want to access them.

 I have only run the script on Red Hat 7 and 8 systems so there may be
some portability issues as I do parse the output of many system commands
when inspecting the working copy.

I'll be the first to admit that the script is not the most polished and
possibly a perl script would do the job better but it appears to work so
I'm happy.

If there is something else that is required to do in order to get this
added to the contrib section please let me know and I'll endeavor to do
so. Since originally posting the script to the mailing list last year I
have had a few inquiries about it so I believe including it as part of
the archive would be of benefit to others.

Cheers

Ross

#!/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
#
#
#-------------------------------------------------------------------------
SVN=/usr/local/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 $1
    PCWD="$PCWD/$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
#

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Fri Mar 19 03:20:22 2004

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.