#!/bin/bash # # This script called in a CVS directory compares local files with # the repository, and prepares an update package containing all # changes and new files for submission to a CVS maintainer. If there # are only changes in text files, then a compressed unified diff is # made (foo.diff.bz2). If there are also changed binary or new files, # then an archive is made instead (foo.tar.bz2). The base name ("foo") # can be given as command line argument. Otherwise the directory name # is used. The script also leaves a diff in uncompressed/unpackaged # form. This is only for developer convenience -- for a quick check # of the diff correctness. It is not to be submitted. The script will # not overwrite any file, but rather rename conflicting files. # # Usage: fg-submit [-v] [] # # Options: # -v ... verbose output # # Example: # $ cd $FG_ROOT/Aircraft/bo105 # $ fg-submit # -> bo105.diff.bz2 or bo105.tar.bz2 # # $ fg-submit update # -> update.diff.bz2 or update.tar.bz2 # # # Spaces in the basename are replaced with "%20". People who prefer # to have the date in the archive name can conveniently achieve this # by defining a shell alias in ~/.bashrc: # # alias submit='fg-submit "${PWD##*/}-$(date +%Y-%m-%d)"' # # # # If the script finds an application named "fg-upload", then it calls # this at the end with two arguments: # # $1 ... archive or compressed diff for submission # $2 ... accessory diff, *NOT* for submission! # # $1 and $2 are guaranteed not to contain spaces, only $1 is guaranteed # to actually exist. Such a script can be used to upload the file to an # ftp-/webserver, and/or to remove one or both files. Example using # KDE's kfmclient for upload (alternatives: ncftpput, gnomevfs-copy, wput): # # $ cat ~/bin/fg-upload # #!/bin/bash # echo "uploading $1" # if kfmclient copy $1 ftp://user:password@server.com; then # echo "deleting $1 $2" # rm -f $1 $2 # # URL=ftp://server.com/$1 # # # copy URL to KDE's clipboard, so that MMB-clicking pastes it # dcop klipper klipper setClipboardContents $URL # # echo "Done. --> $URL" # else # echo "$0: uploading failed!" # fi # # # # Whether a file should be included in the archive or not, is decided # by pattern rules. There is a set of reasonable default rules predefined, # but alternative settings can be defined in a hidden configuration file # named ".fg-submit". Such a file is searched in the current directory, # in its parent directory, in its grand-parent directory and so on, # and finally in the $HOME directory. The first found file is taken. # # A file can use a list of three keywords with arguments, each on a # separate line: # # ALLOW ... accept & report matching file # DENY ... reject & report matching file # IGNORE ... silently reject matching file # # A is a space-separated list of shell pattern. # It may also be empty, in which case it has no effect. Examples: # # DENY test.blend # ALLOW *.xcf *.blend # # The list of pattern is checked in the same order in which it was # built. The first match causes a file to be accepted or rejected. # Further matches are not considered. Comments using the # hash character '#' are allowed and ignored. # # Some default rules are always added at the end. If you want to # bypass them, then just add "ALLOW *" or "DENY *" at the end of # your configuration, and no file will ever reach the default rules. # # # Example: # # DENY test.xcf # throw out the test image, but ... # ALLOW *.xcf # ... allow all other GIMP images (the default # # rules would otherwise throw them out) # # ALLOW g.old # add this silly file, but ... # IGNORE *.old # throw out all other "old" files (and don't # # report that to the terminal) # # # .fg-submit configuration files are "sourced" bash scripts, the # keywords are simple shell functions. That means that you can # also use other bash commands in that file, such as "echo", or # write several commands on one line, separated with semicolon. # You can even put all special rules in your ~/.fg-submit file, # with rules depending on the working directory: # # case "$PWD" in # */bo105*) DENY *.osg; ALLOW livery.xcf ;; # */ufo*) DENY *.tiff ;; # esac SELF=${0##*/} DIR=${PWD##*/} if [ "$1" == "-v" ]; then DBG=1 shift fi BASE=${1:-$DIR} BASE=${BASE// /%20} CVS=/usr/bin/cvs # avoid colorcvs wrapper from [ -x $CVS ] || CVS=cvs # http://www.hakubi.us/colorcvs/ UPLOAD=$(which fg-upload 2>/dev/null) CONFIG_FILE=".fg-submit" ARCHIVE=$BASE.tar.bz2 DIFF=$BASE.diff CDIFF=$DIFF.bz2 # these rules are always prepended; the first letter decides if the # rule accepts (+), rejects (-), or ignores (!) a matching file. PREFIX_RULES=" !$DIFF* !$CDIFF* !$ARCHIVE* !CVS/* !*/CVS/* " # these rules are always appended DEFAULT_RULES=" +.cvsignore +*/.cvsignore -*~ -*. -*.bak -*.orig -*.RGB -*.RGBA -*.MDL -*.xcf -*.XCF -*.tga -*.TGA -*.bmp -*.BMP -*.png -*.PNG -*.blend -*.blend[0-9] -*blend[0-9][0-9] -*.blend[0-9][0-9][0-9] -*.gz -*.tgz -*.bz2 -*.zip -*.tar.gz* -*.tar.bz2* " POSTFIX_RULES=" !.* !*/.* +* " function ERROR { echo -e "\e[31;1m$*\e[m"; } function LOG { echo -e "\e[35m$*\e[m"; } function NEW { echo -e "\e[32m\t+ $*\e[m"; } function CHANGED { echo -e "\e[36m\t+ $*\e[m"; } function REJECT { echo -e "\e[31m\t- $*\e[m"; } function DEBUG { [ $DBG ] && echo -e "$*"; } function diffstat { # output diff statistics, similar to the "diffstat" utility awk ' function line(a, r, c, f) { print "\t\033[32m"a"\033[m\t\033[31m"r"\033[m\t\033[34m"c"\033[m\t"f } function dofile() { if (!file) { return } if (bin) { print "\t. . . . binary . . . . \033[36m"file"\033[m" } else { line(a, r, c, file) at += a; rt += r; ct += c } a = r = c = 0 } BEGIN { print "\tadded---removed-changed----------------------------------------" a = r = c = at = rt = ct = n = bin = 0 } /^Index: / { dofile(); scan = bin = 0; file = $2; n++; next } /^@@/ { scan = 1; next } /^Binary/ { if (!scan) bin = 1; next } /^\+/ { if (scan) a++; next } /^-/ { if (scan) r++; next } /^!/ { if (scan) c++; next } END { dofile() print "\t----------------------------------------total------------------" line(at, rt, ct, "\033[min "n" files") } ' <$1 } function backup_filename { for ((i = 1; 1; i = i + 1)); do name=$1.$i if ! [ -a "$name" ]; then touch $name echo $name return fi done } # set up accept/reject rules function ALLOW { for i in $*; do RULES="$RULES +$i"; done } function DENY { for i in $*; do RULES="$RULES -$i"; done } function IGNORE { for i in $*; do RULES="$RULES !$i"; done } function search_config { file="$1/$CONFIG_FILE" DEBUG "checking for config file $file" if [ -f "$file" ]; then CONFIG="$file" return 0 elif [ "$1" ]; then search_config ${1%/${1##*/}} # parent dir return fi return 1 } set -f RULES= if search_config "$PWD"; then LOG "loading config file $CONFIG" source "$CONFIG" elif [ -f ~/$CONFIG_FILE ]; then DEBUG "loading config file ~/$CONFIG_FILE" source ~/$CONFIG_FILE elif [ -f ~/${CONFIG_FILE}rc ]; then DEBUG "loading config file ~/${CONFIG}rc" source ~/${CONFIG_FILE}rc fi RULES="$PREFIX_RULES $RULES $DEFAULT_RULES $POSTFIX_RULES" set +f if [ $DBG ]; then DEBUG "using these rules: " for i in $RULES; do echo -n "$i "; done echo fi # create temporary dir that's automatically removed on exit TMP=$(mktemp -d /tmp/$SELF.$BASE.XXXXXX) || (echo "$0: can't create temporary dir"; exit 1) trap "rm -rf $TMP" 0 1 2 3 13 15 # move old files out of the way adding sequential suffixes for i in $DIFF $CDIFF $ARCHIVE; do [ -f $i ] && mv $i $(backup_filename $i) done LOG "updating and checking for new files ..." $CVS -q up -dP >$TMP/up || exit 1 if grep "^C " $TMP/up &>/dev/null; then ERROR "there are conflicts with the following files:" grep "^C " $TMP/up exit 1 fi LOG "making diff ..." if ! $CVS -q diff -up >$DIFF; then LOG "diff statistics:" diffstat $DIFF echo # add diff file itself echo $DIFF >>$TMP/files # add changed binary files awk ' /^Index: / { scan = 1; file = $2; next } /^@@/ { scan = 0; next } /^Binary/ { if (scan) { print file } } ' <$DIFF >>$TMP/files else rm -f $DIFF fi LOG "checking for files to submit ..." if [ -f $TMP/files ]; then cat $TMP/files|while read i; do CHANGED "$i" done fi grep "^? " $TMP/up|while read i; do find ${i#? } -type f >>$TMP/check done # filter files according to the pattern rules if [ -f $TMP/check ]; then for i in $(cat $TMP/check); do DEBUG "checking whether file '$i' matches" for r in $RULES; do DEBUG "\t\trule $r" class=${r:0:1} rule=${r:1} case "$i" in $rule) case $class in !) DEBUG "$i\t\t\"silently\" rejected\t\t$rule" ;; -) REJECT "$i\t\t$rule" ;; +) NEW "$i\t\t$rule" && echo "$i" >>$TMP/files ;; esac break ;; esac done done fi if ! [ -f $TMP/files ]; then LOG "no changed or new files found" exit 0 fi echo numfiles=$(awk '//{n++}END{print n}' <$TMP/files) if [ -f $DIFF -a $numfiles == 1 ]; then LOG "only changed non-binary files found" LOG "creating compressed diff \e[1;37;40m$CDIFF\e[m\e[35m ..." bzip2 --keep $DIFF RESULT=$CDIFF else LOG "changed and/or new files found" LOG "creating archive \e[1;37;40m$ARCHIVE\e[m\e[35m ..." tar --create --bzip2 --file=$ARCHIVE --files-from $TMP/files RESULT=$ARCHIVE fi [ -x "$UPLOAD" -a -f $RESULT ] && $UPLOAD $RESULT $DIFF