#!/bin/bash #------------------------------------------------------------------------- # Author: Adam Borowski # # Copyright (C) 2003-2004 Ross Mark, 2006-2007 Adam Borowski # # Changes: # 2006-06-13 Adam Borowski: # file:timestamp. It's currently stored as seconds past epoch, # losing any fractional parts; this preserves portability at the cost # of accuracy. # 2006-06-15 Adam Borowski: # storing permissions and/or timestamps can be crudely controlled # by editing this file and changing STORE_{PERMS,TIMES} # 2006-07-21 Adam Borowski: # bugfix: running 'update' or friends would overwrite the timestamps of # locally modified files, losing them and also triggering a grave data # loss bug in svn # 2007-05-22 Adam Borowski: # removed timestamping of dirs, it causes WAY too much hassle # 2007-05-22 Adam Borowski: # non-empty files without changes don't get their timestamps touched # 2007-12-09 Adam Borowski: # rename to svnt, to avoid confusion with TortoiseSVN #------------------------------------------------------------------------- # # Description: # Timestamp SVN (svnt) allows the recording of timestamps not # normally handled by svn. It also retains all of functionality # of its parent, Archive SVN (asvn) by Ross Mark, although that is # disabled by default. # # Every file has a 'file:timestamp' property, which stores the mtime # as number of seconds since 1970-01-01 GMT, with an optional fractional # part (currently ignored). # # 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/bin/svn ACTION="" DEV_PROP="dir:devices" SYM_PROP="dir:symlinks" FILE_PROP="file:permissions" TIME_PROP="file:timestamp" STORE_PERMS=false STORE_TIMES=true STORE_SPECS=false 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) time=%T@\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"` timestamp=`expr "$info" : "file='.*' mode.* time=\([0-9]\+\)"` info=`expr "$info" : "file='.*' \(mode.*\) time="` if [ "$PCWD" = "$device" ] then dir="." file="" else dir="`basedirname $PCWD $device`" file="`basename $device`" fi # see if the properties have changed. if $STORE_PERMS then 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 fi # same for the timestamp if $STORE_TIMES && [ ! -d "$dir/$file" ] then secs="`$SVN propget $TIME_PROP $dir/$file 2>/dev/null`" || continue secs=`expr "$secs" : "\([0-9]\+\)"` #forward compat: 1234.000000 is ok if [ "$secs" != "$timestamp" ] then if [ "$CHECKIN" = "true" ] then if [ -s "$dir/$file" ] && cmp -s "$dir/$file" "$dir/.svn/text-base/$file.svn-base" then echo "Non-empty file $dir/$file has no real mods, not changing the timestamp" else $SVN propset $TIME_PROP "$timestamp" $dir/$file fi else if [ -z "$secs" ] then echo "property $TIME_PROP not set for $dir/$file" else if cmp -s "$dir/$file" "$dir/.svn/text-base/$file.svn-base" then echo -n "Updating date for $dir/$file to " date -d "1970-01-01 UTC $secs seconds" touch -d "1970-01-01 UTC $secs seconds" "$dir/$file" else echo "File $dir/$file has local mods, not updating the timestamp" fi fi fi fi fi done } function pre_checkin() { echo svnt: pre-checkin process if $STORE_SPECS;then recorddirinfo -ci;fi recordpermissions -ci } function post_checkout() { echo svnt: post-checkout process if [ "$CHDIR" = "true" ] then shift $(($# -1)) cd $1 PCWD="$PCWD/$1" fi if $STORE_SPECS;then recorddirinfo;fi 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 #