#!/bin/bash : ${SVN_LOG:=~/.fli4l/svn.log} # if non-empty, the target branch is not cleaned up after a failed merge cleanup_after_merge=1 # get the first revision after branch creation svn_get_first_branch_rev() { case $1 in branches/3.10/*) echo 24750;; branches/4.0/trunk/src) echo 27237;; branches/4.0/testing/src) echo 34792;; branches/4.0/stable/src) echo 34793;; *) do_svn log --stop-on-copy -v --xml "$local_root/$1" | xmllint --xpath 'string(/log/logentry[last()]/@revision)' - 2>/dev/null ;; esac } do_svn() { echo svn "$(pack_args "$@")" >> $SVN_LOG eval LC_ALL=C svn --non-interactive "$(pack_args "$@")" 2>&1 | tee -a $SVN_LOG } do_svn_quiet() { echo svn "$(pack_args "$@")" >> $SVN_LOG eval LC_ALL=C svn --non-interactive "$(pack_args "$@")" >> $SVN_LOG 2>&1 } # $1 = remote URL # $2 = branch (need not exist) checkout_branch() { do_svn_quiet checkout "$1" "$local_root/$2" } # $1 = branch to update update_branch() { do_svn_quiet update "$local_root/$1" } # $1 = branch to clean (by reverting all local changes, additions, and removals) clean_branch() { local src=$1 do_svn_quiet revert -R "$local_root/$src" rm -rf $(do_svn status "$local_root/$src" | grep "^?" | $SED -e "s/^.//") } # $1 = branch to clean (by reverting all local changes, additions, and removals) clean_branch_if_enabled() { [ -n "$cleanup_after_merge" ] && clean_branch "$@" } # $1 = branch get_repo_root() { do_svn info "$local_root/$1" | grep "^Repository Root:" | $SED -e "s/^Repository Root:[[:space:]]*//" } # $1 = branch get_repo_url() { do_svn info "$local_root/$1" | grep "^URL:" | $SED -e "s/^URL:[[:space:]]*//" } # $1 = branch # $2 = (optional) target branch for merging get_revs() { local src_firstrev=$(svn_get_first_branch_rev "$1") if [ -n "$2" ] then local tgt_firstrev=$(svn_get_first_branch_rev "$2") fi if [ -z "$src_firstrev" ] then local firstrev=$tgt_firstrev elif [ -z "$tgt_firstrev" ] then local firstrev=$src_firstrev elif [ $src_firstrev -gt $tgt_firstrev ] then local firstrev=$src_firstrev else local firstrev=$tgt_firstrev fi if [ -n "$firstrev" ] then do_svn log -q -r$firstrev:HEAD "$local_root/$1" | grep "^r[0-9]\+[[:space:]]" | $SED -e 's/^r\([0-9]\+\).*/\1/' fi } # $1 = branch # $2 = merged branch get_merged_revs() { local src=$2 # backwards compatibility... case $2 in branches/3.10/trunk/src) src="$src trunk/src";; branches/4.0/trunk/src) src="$src branches/feature/FFL-506/src";; branches/*/testing/src) src="$src branches/testing/src";; branches/*/stable/src) src="$src branches/stable/src";; esac local dir for dir in $src do do_svn propget svn:mergeinfo "$local_root/$1" | grep "^/${dir%/src}\(/src\)\?:" | $SED -e "s|^/${dir%/src}\(/src\)\?:||;s/,/ /g" done } # $1 = branch get_base_rev() { get_revs "$1" | tail -n 1 } # $1 = branch path _get_head_rev() { do_svn info "$1" | grep "^Revision:" | $SED -e "s/^Revision:[[:space:]]*//" } # $1 = branch get_head_rev() { _get_head_rev "$local_root/$1" } # $1 = branch # $2 = revision get_log_message() { if [ "$1" = "-r" ] then shift local branch="$remote_root/$1@$2" else local branch="$local_root/$1" fi do_svn propget --revprop -r $2 svn:log "$branch" } # $1 = variable containing log message shorten_log_message() { eval local _msg=\$$1 if [ $(wc -l <<< "$_msg") -gt 1 ] then _msg="$(head -n 1 <<< "$_msg") [...]" fi eval $1=\$_msg } # $1 = branch # $2 = revision # $3 = analyse merge commits if non-empty # $4 = don't use colours if non-empty print_log_messages() { local msg=$(get_log_message "$1" $2) shorten_log_message msg if [ -z "$4" ] then message " [$2] " -n message "$msg" yellow else echo " [$2] $msg" fi if [ -n "$3" ] then local merge_source= merged_revs= get_merged_revs_in_changeset "$1" $2 if [ -n "$merged_revs" ] then if [ -z "$4" ] then message " (from $merge_source)" standout else echo " (from $merge_source)" fi local merged_rev for merged_rev in $(decompress_revs $merged_revs) do msg=$(get_log_message -r "${merge_source#/}" $merged_rev) shorten_log_message msg if [ -z "$4" ] then message " [$merged_rev] " -n message "$msg" yellow else echo " [$merged_rev] $msg" fi done fi fi } # $1 = branch # $2 = revision get_merged_revs_in_changeset() { set -- $(do_svn diff --properties-only -c $2 --depth empty "$local_root/$1" | sed '1,/^Modified: svn:mergeinfo/d' | head -n 1 | sed -n 's/^[[:space:]]*Merged \(.*\):r\([-0-9,]\+\)$/\1 \2/p') merge_source=$1 merged_revs=$2 } # $1 = revision list, using commas and hyphens decompress_revs() { local oldifs="$IFS" IFS=, set -- $1 IFS="$oldifs" local revrange for revrange do case $revrange in *-*) local first=${revrange%-*} local second=${revrange#*-} seq $first $second ;; *) echo $revrange ;; esac done } # $1 = branch to check get_merge_conflicts() { do_svn status "$local_root/$1" | grep "^\(C......\|.C.....\|......[C>]\) " } # $1 = branch to check # $2 = revision # $3 = filter expression filter_files() { local branch_sed=$(echo "$1" | $SED 's/\//\\\//g') do_svn log -v -r $2 "$local_root/$1" | $SED '1,3d' | while read line do [ -n "$line" ] || break echo "$line" | grep "^[$3]" | $SED "s/^. \/${branch_sed}\(\/\|$\)//;s/ (from .*)$//" done } # $1 = branch # $2... = list of revisions partition_revs() { local branch=$1 local tmpfile=$(mktemp /tmp/fli4l-merge.XXXXXXXXXX) shift local result= local sep= local rev for rev do local entry local split= while read entry do if grep -q "^$entry\(/\|$\)" "$tmpfile" then # deleting a file previously added or modified split=1 fi entry=$(echo "$entry" | $SED 's/\//\\\//g') $SED -i "/^$entry\(\/\|$\)/d" "$tmpfile" done < <(filter_files "$branch" $rev "D") filter_files "$branch" $rev "^D" >> "$tmpfile" if [ -n "$split" ] then result+=" $rev" split= else result+="$sep$rev" fi sep="," done echo "$result" rm "$tmpfile" } svn_error() { msg=$(tail -n 20 $SVN_LOG | $SED -n '/^svn:/,$p') message "$msg" red error "$@" } > $SVN_LOG SVNVER=$(svn -q --version) SVNMAJOR=$(echo "$SVNVER" | cut -d . -f 1) SVNMINOR=$(echo "$SVNVER" | cut -d . -f 2) if [ $SVNMAJOR -lt 1 -o $SVNMINOR -lt 7 ] then error "At least SVN 1.7 is required for using this script" fi