Pages by Andreas B. M. Hofmeier
An-H
email Impressum/Note Privacy
http[s]://www.abmh.de/en/papers/linux/asvn-svn-including-file-permissions.html

Like to be free? ... then: Fight for Freedom

asvn - svn including file permissions and ownership

If you want to control somethink like /etc with svn you definitely need svn to take care of file permissions. I found this asvn script which works good after I spend some time to sort out all this patch-chaos. I found nowhere an up-to-date version. So I decided to put it up here.

This script was written by Ross Mark (rossm at controllingedge.com.au), I downloaded it from http://svn.collab.net/repos/svn/trunk/contrib/client-side/asvn. That I patched it with http://markmail.org/message/rqitgigeh2ep5dsw and made it working. So I hope its going to help you.

download

download
#!/bin/bash
#-------------------------------------------------------------------------
#    Author:		Ross Mark (rossm@controllingedge.com.au)
#    Date:		Tue Mar 11 10:02:57 EST 2003
#
#    Copyright (C) 2003-2004 Ross Mark
#
#-------------------------------------------------------------------------

#    This script was written by Ross Mark (rossm at
#    controllingedge.com.au), I (Andreas Hofmeier, http://www.abmh.de/)
#    downloaded it from
#    http://svn.collab.net/repos/svn/trunk/contrib/client-side/asvn. Than
#    I patched it with http://markmail.org/message/rqitgigeh2ep5dsw and
#    made it working. So I hope its going to help you.

#    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
#
# http://svn.collab.net/repos/svn/trunk/contrib/client-side/asvn
#-------------------------------------------------------------------------

# $HeadURL$
# $LastChangedDate$
# $LastChangedBy$
# $LastChangedRevision$

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
}

# Escapes all "special" filename characters.  SED magic was modified from
# the original found at the website:
#   http://elonen.iki.fi/code/misc-notes/remove-duplicate-files/index.html
#
function escapename()
{
       echo "`echo $1 | sed -r 's/([^a-zA-Z0-9./_-])/\\\\\1/g'`"
}

#
# 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'`"
    efile="`escapename \"$file\"`"
    gefile="`echo $efile |sed -e 's!\([\\]\)!\1\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" > /dev/null 2>&1
#        $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'`"

      file=`basename "$1"`
      dir=`dirname "$1"`
      efile="`escapename \"$file\"`"
      gefile="`echo $efile |sed -e 's!\([\\]\)!\1\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"
	$SVN propset svn:ignore -F $TMPFILE2 "$dir" > /dev/null 2>&1
        #cat $TMPFILE2 >&2
    fi
}

function recorddirinfo
{
#    eval "find $PCWD $SKIPSVN -o \( -type d ! -name .svn  -print \)" |while read dirlist
    find "$PCWD" \( -type d -name .svn -print \) |  sed -e 's/\/\.svn//' | while read dirlist
    do
#        updatedirsymlinks $1 "$dirlist"
#        updatedirdevices $1 "$dirlist"
 	recordpermissions $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

	find "$dir" -maxdepth 1 \( -type b -o -type c -o -type p \) -printf "file='%p' mode=%m user=%u(%U) group=%g(%G)\n" |
	sed -r 's#\\#\\\\#g' | sort | while read devinfo
    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" ] 
       file=`expr "$devinfo" : "file='\(.*\)' mode"`
       echo -n "$devinfo"
       [ -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
}


# No longer needed.
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

    dir="$1"

    echo checking $dir for permissions

    cp /dev/null $TMPFILE

#    eval "find $PCWD $SKIPSVN -o \( \( -type d ! -name .svn  \) -o -type f \) $PRINTDETAILS" | while read info
       #
       # Obtain the list of files and subdirectories in this directory
       #   We also need to fix the backslash characters before the 'while read' or
       #   they will get interepreted within the read.
       #
       find "$dir" -maxdepth 1 \( -type f -o \( -type d ! -name \.svn \) \) -printf "file='%p' mode=%m user=%u(%U) group=%g(%G)\n" |
       sed -r 's#\\#\\\\#g' | sort | while read info
    do
        device=`expr "$info" : "file='\(.*\)' mode"`
	file=`expr "$info" : "file='\(.*\)' mode"`
	info=`expr "$info" : "file='.*' \(mode.*\)"`

#        if [ "$PCWD" = "$device" ]
	if [ "$PCWD" = "$file" ]
        then
            dir="."
            file=""
        else
#            dir="`basedirname \"$PCWD\" \"$device\"`"
#            file="`basename \"$device\"`"
             dir="`basedirname \"$PCWD\" \"$file\"`"
             file="`basename \"$file\"`"
             # All chars except spaces and tabs seem to be handled correctly
             # within the double quotes, so we just fix the spaces and tabs
# this leads to an error: sed: -e expression #1, char 26: invalid reference \1 on `s' command's RHS

#             file="`echo \"$file\" | sed -r 's#\([\ \        ]\)#\\\\\1#g'`"
        fi

       props="`$SVN propget $FILE_PROP \"$dir/$file\"`"
       if [ $? -eq 0 ]
       then
               #file is under svn control

        # see if the properties have changed.
#        if [ "`$SVN propget $FILE_PROP \"$dir/$file\"`" != "$info" ]
        if [ "$props" != "$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"
		    echo "property $FILE_PROP not set for \"$dir/$file\""
                else
#                    chown $user:$group  "$dir/$file"
#                    chmod $mode "$dir/$file"
                     echo "changing owner/group mode on \"$dir/$file\""
                     chown $user:$group  "$dir/$file"
                     chmod $mode "$dir/$file"
                fi
            fi
        fi

       else
               #file is not under svn control
               # echo -n "" is just a no-op allowing this else block to exist
               echo -n ""
       fi

    done
}


function pre_checkin()
{
    echo this is the pre checkin process
    recorddirinfo -ci
# recordpermissions is called from recorddirinfo now
#   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 
# recordpermissions is called from recorddirinfo now

}

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

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

#$SVN $@
if [ "$DOSVN" = "true" ]
then
       echo $SVN "$@"
       $SVN "$@"
fi


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

cleanup
#
# vim: set ai ts=8 sw=4
#
Last modified: Sun Jan 25 14:36:54 CET 2009
Pages by Andreas B. M. Hofmeier
An-H
email Impressum/Note Privacy
http[s]://www.abmh.de/en/papers/linux/asvn-svn-including-file-permissions.html
Creative Commons License
(c) Andreas B. M. Hofmeier
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Germany License