Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
451 lines (418 sloc) 12.4 KB
#!/bin/bash
#-------------------------------------------------------------------------
# Author: Adam Borowski <kilobyte@angband.pl>
#
# 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
#