#!/bin/bash export SCRIPT=$(basename $0) usage() { cat < This creates a new feature branch for . $SCRIPT [--full] checkout This checks out a feature branch for . The option --full also checks out the binaries. Without that option, the "bin" directory is linked to that one of the original branch. $SCRIPT sync This performs a original-->clone merge. $SCRIPT [--close] merge This performs a clone-->original merge. If --close is passed, the clone branch is deleted after a successful merge. Example: # create a feature branch for FFL-2345 src/_TOOLS/merge/feature create FFL-2345 # check it out src/_TOOLS/merge/feature checkout FFL-2345 # go ahead and make some changes cd ../feature/FFL-2345; ...; svn ci -m "..." ...; ... # synchronize with trunk (i.e. original-->clone merge) src/_TOOLS/merge/feature sync # make more changes ...; svn ci -m "..." ...; ... # merge current state of feature to trunk (i.e. clone-->original merge) src/_TOOLS/merge/feature merge # make even more changes ...; svn ci -m "..." ...; ... # synchronize with trunk, merge current state of feature to trunk and # close feature src/_TOOLS/merge/feature sync src/_TOOLS/merge/feature --close merge # afterwards, remove checkout cd ..; rm -rf FFL-2345 EOF exit 1 } export -f usage allowed_options="--review-merge --full --close" rootdir=${0%/*} . "$rootdir/include/config.inc" . "$rootdir/include/options.inc" . "$rootdir/include/display.inc" . "$rootdir/include/utility.inc" . "$rootdir/include/cleanup.inc" . "$rootdir/include/svn.inc" . "$rootdir/include/repo-info.inc" . "$rootdir/include/branching.inc" . "$rootdir/include/ticket.inc" case $1 in -*) error "Unknown option: $1" usage ;; esac # # determine readlink and sed # type -p gsed >/dev/null && SED=gsed || SED=sed type -p greadlink > /dev/null && READLINK=greadlink || READLINK=readlink # full checkout including bin/ has_option "--full" && full_checkout=1 || full_checkout= # close branch after merge has_option "--close" && close_after_merge=1 || close_after_merge= do_create() { local ticket=$1 [ -n "$ticket" ] || usage local_this=$local_root$rel_path remote_this=$remote_root$rel_path remote_feature=$remote_root$rel_path_base/feature/$ticket do_svn_quiet copy -m "$ticket: feature branch opened" "$local_this" "$remote_feature" || error "Branch creation failed: $(tail -n 1 "$SVN_LOG")" } do_checkout() { local ticket=$1 [ -n "$ticket" ] || usage local_this=$local_root$rel_path local_feature=$local_root$rel_path_base/feature/$ticket remote_feature=$remote_root$rel_path_base/feature/$ticket do_svn_quiet checkout --depth=immediates "$remote_feature" "$local_feature" || error "Checkout failed: $(tail -n 1 "$SVN_LOG")" do_svn_quiet update --set-depth infinity "$local_feature/src" || error "Checkout failed: $(tail -n 1 "$SVN_LOG")" if [ -n "$full_checkout" ] then do_svn_quiet update --set-depth infinity "$local_feature/bin" || error "Checkout failed: $(tail -n 1 "$SVN_LOG")" else for arch in "$local_this/bin"/* do ln -s "$arch" "$local_feature/bin" done fi } do_sync() { local ticket=${rel_path##*/} local_feature=$local_root$rel_path_base/feature/$ticket # find original branch this one has been cloned from local copy_from=$(svn log --stop-on-copy -v --xml "$local_feature" | xmllint --xpath 'string(/log/logentry[last()]/paths/path/@copyfrom-path)' - 2>/dev/null) if [ -z "$copy_from" ] then error "Cannot determine the branch the feature branch has been cloned from" fi $rootdir/merge-changesets --review-merge ${copy_from#/}/src ${rel_path#/}/src FFL "" "$@" || return $? } do_merge() { local ticket=${rel_path##*/} local_feature=$local_root$rel_path_base/feature/$ticket remote_feature=$remote_root$rel_path_base/feature/$ticket # find original branch this one has been cloned from local copy_from=$(svn log --stop-on-copy -v --xml "$local_feature" | xmllint --xpath 'string(/log/logentry[last()]/paths/path/@copyfrom-path)' - 2>/dev/null) if [ -z "$copy_from" ] then error "Cannot determine the branch the feature branch has been cloned from" fi $rootdir/merge-changesets --all-commits --review-merge ${rel_path#/}/src ${copy_from#/}/src FFL || return $? if [ -n "$close_after_merge" ] then do_svn_quiet delete -m "$ticket: feature branch closed" "$remote_feature" || error "Branch deletion failed: $(tail -n 1 "$SVN_LOG")" fi } load_repo_info repo_uri=$(LC_ALL=C svn info $(dirname $($READLINK -f $0))/../.. | $SED -n 's/^URL: \(.*\)\/src$/\1/p' 2>/dev/null) if [ -z "$repo_uri" ] then error "Could not determine source branch -- do you call this script from a SVN repository checkout?" exit 2 fi case $repo_uri in $remote_root/*) rel_path=${repo_uri#$remote_root} ;; *) error "Incompatible branch URI \"$repo_uri\", it does not start with \"$remote_root\"" ;; esac rel_path_base=$(echo "$rel_path" | $SED -n 's,^\(/branches/[^/]\+\).*,\1,p') if [ -z "$rel_path_base" ] then error "Cannot determine relative path base for \"$rel_path\"" fi cmd=$1 shift [ -n "$cmd" ] || usage case $cmd in create|checkout|sync|merge) do_$cmd "$@" ;; *) error "Unknown command $cmd" ;; esac