goose

Vladimir Marek Vladimir.Marek at Sun.COM
Sat Nov 24 08:13:53 UTC 2007


Hi,

I often find myself in a situation, where I do have patch against some
program, but can't or don't want to push the patch back to upstream
repository. At the same time I would like to keep my patch updated with
every upstream change, so that the updates are as small as possible. I
used mercurial queues plus this my wrapper to automate the task. It's
made to fulfill my needs, but I'm open to discussion.

Enjoy
-- 
	Vlad
-------------- next part --------------
#!/usr/bin/ksh

# goose
# =====
#
# Made by: Vladimir.Marek at Sun.COM
#
# So far this script is made to help me in may day to day work, but if there
# will be interest, I'll create it's own support alias and homepage. I do
# welcome any comments.
#
# Script used to keep your patches up to date against upstream changes.
#
# Whole thing is based on Mercurial and Mercurial queues. For the script
# operation we need two Mercurial workspaces:
#
# incoming_ws: This is where the upstream changes are converted to Mercurial
# patches_ws: Clone of incoming_ws, here we keep our patches in mq
#
# The work is done in several steps:
# 1) bring incoming_ws up to date and convert it to Mercurial
# 2) synchronize patches_ws with incoming_ws and refresh all patches
# 3) after successful refresh, optionally run some command (can be some test,
#    package build, etc.)
#
# If anything fails, you are notified about what failed. Goose can send emails,
# so you can run it from cron. The intended usage is to have goose silently
# merge your patches with upstream as long as possible, only disturbing you
# when the merge (or the optional build or test) fails. Also any potential
# change to your patches due to automatic merging is hg qsaved. This means that
# you will not loose any work because of silent mismerge and moreover you will
# be able to easilly find which change in incoming_ws provoked modification to
# your patches.
#
# ad 1)
# -----
# If incoming_ws is svn, cvs, hg or teamware, goose can itself update the
# workspace and convert the changes to Mercurial. Best is hg, as there is no
# work, just "hg up". Second is svn, where goose can convert each "svn commit"
# to single "hg commit". Cvs and TeamWare are bad, as I do something like "cvs
# up; hg commit --addremove".
# You can also take care of this step yourself, just make sure you keep
# incoming_ws up-to-date and managed by hg. In such case pass option
# '-nocontrol', which tels goose to skip on any updating of incoming_ws.
# When you pass any directory as incoming_ws, but is not managed by Mercurial,
# goose will convert it. Again, goose knows few other systems (svn, cvs,
# TeamWare) and do the right thing. If you want to do it by hand, just convert
# it before running mercurial first time.
#
# ad 2)
# -----
# Goose tries to apply your mq patches to every single incoming_ws changeset.
# This ensures that the merging is as small as possible. If automatic merging
# fails, you are notified about it with instructions what to do now. It is
# recommended to use 'nodate' style patches, since otherwise mq qrefresh
# creates different patches every time, even if there are no real changes. When
# goose detects that you are not using 'nodate' patches, it will try to use
# external utility "sterilize_qrefresh", which cleans up the unnecessary
# differences.
#
# ad 3)
# -----
# The command specified in -after has to return with code 0, otherwise it's
# treated as some sort of error and reported to you.
#
# For commandline and examples run "goose --help"


# TODO
#  - better incoming sync (done for svn)
#  - use hg convert to manage repo conversion ?
#  - proper status in .hg/goose/...
#    - don't start two processes in parallel (user/cron)
#    - easilly find out which state the repository is at (needs merging, etc.)
#  - run -after script only if patches_ws changed

set -u

alias debug=''
alias error=report_error_screen

ORIGINAL_PWD=$(pwd)
GOOSE_REFRESH_MQ=''
GOOSE_USAGE=''
GOOSE_NO_CONTROL=''
GOOSE_AFTER_COMMAND=''
VERSION=4
set -A SETUP_VARIABLE_NAME
set -A SETUP_VARIABLE_VALUE

trap '[ -n "$GLOBAL_GOOSE_TEMP" ] && rm -rf "$GLOBAL_GOOSE_TEMP"' EXIT INT TERM
GLOBAL_GOOSE_TEMP=$( mktemp -d )
export GLOBAL_GOOSE_TEMP

while [ $# -gt 0 ]; do
	case "$1" in
		-h|-help|--help)
			GOOSE_USAGE='yes'
			;;
		-m|--mail)
			alias error=report_error_mail
			;;
		-mq)
			GOOSE_REFRESH_MQ='yes'
			;;
		-after)
			GOOSE_AFTER_COMMAND="$2"
			shift
			;;
		-nocontrol)
			GOOSE_NO_CONTROL='yes'
			;;
		-d*)
			alias debug="set -x"
			file=${1#??}
			[ -n "$file" ] && exec 2>"$file"
			;;
		*)
			break;
			;;
	esac
	shift
done

debug

#-----
##
## @function add_setup_variable
##
## @desc Registers variable to goose. This complex evnironment variable
## handling is here, so that later we can display all variable values in --help
##
## @param $1 - variable name
## @param $2 - default value
##
#-----
function setup_variable
{
	debug

	SETUP_VARIABLE_NAME[${#SETUP_VARIABLE_NAME[*]}]="$1"
	SETUP_VARIABLE_VALUE[${#SETUP_VARIABLE_VALUE[*]}]="$2"
}

#-----
##
## @function environment_display
##
## @desc Display all registered variables value and default value
##
#-----
function environment_display
{
	debug

	local CNT=0
	while [ "${#SETUP_VARIABLE_NAME[*]}" -ne "$CNT" ]; do
		local NAME=${SETUP_VARIABLE_NAME[$CNT]}
		eval "echo $NAME = \$$NAME"
		let CNT=$CNT+1
	done
}

#-----
##
## @function environment_setup
##
## @desc Go thgrough all registered vriables and fill them with default if they are not set
##
#-----
function environment_setup
{
	debug

	local CNT=0
	while [ "${#SETUP_VARIABLE_NAME[*]}" -ne "$CNT" ]; do
		local NAME=${SETUP_VARIABLE_NAME[$CNT]}
		local VAL=${SETUP_VARIABLE_VALUE[$CNT]}
		eval "$NAME=\"\${$NAME-\"$VAL\"}\""
		let CNT=$CNT+1
	done
}

setup_variable GOOSE_HG hg
setup_variable GOOSE_CVS cvs
setup_variable GOOSE_SVN svn
setup_variable STERILIZE_QREFRESH sterilize_qrefresh.pl
setup_variable GOOSE_EMAIL ''

environment_setup

#-----
##
## @function report_error_mail
##
## @desc Report error to email and exit
##
## @param $1 - Short error description
## @param STDIN - error text
##
#-----
function report_error_mail
{
	debug

	local TEMP=$GLOBAL_GOOSE_TEMP/mail

	cat > "$TEMP"

	local SUBJECT="goose - empty subject"
	[ -n "$1" ] && SUBJECT="$1"
	cat "$TEMP" | mailx -r "$GOOSE_EMAIL" -s "$1" "$GOOSE_EMAIL"

	echo "Goose Error (sent by mail to $GOOSE_EMAIL): $1"
	cat "$TEMP"

	exit
}

#-----
##
## @function report_error_screen
##
## @desc Report error to screen and exit
##
## @param $1 - Short error description
## @param STDIN - error text
##
#-----
function report_error_screen
{
	(
		debug
		echo "Goose Error: $1"
		cat
	)
	exit
}

#-----
##
## @function error_short
##
## @desc Report error and exit. No error text on stdin (as oposed to error only)
##
## @param $1 - Error text
##
#-----
function error_short
{
	debug
	/bin/true | error "$1"
}

#-----
##
## Identify what repository type is at given path
##
## @param path (when not present, CWD is used)
##
#-----
function get_repo_type
{
	debug

	local REPO="${1-"$(pwd)"}/"

	if [ -d "${REPO}CVS" ]; then
		echo cvs
		return
	fi
	if [ -d "${REPO}Codemgr_wsdata" ]; then
		echo teamware
		return
	fi
	if [ -d "${REPO}.svn" ]; then
		echo svn
		return
	fi
	if [ -d "${REPO}.hg" ]; then
		echo hg
		return
	fi
	echo none
}

#-----
##
## @function teamware_update
##
## @desc Do teamware update in current directory; bringover
##
#-----
function teamware_update
{
	debug
	$GOOSE_CVS -q up -dR
}

#-----
##
## @function cvs_update
##
## @desc Do cvs update in current directory
##
#-----
function cvs_update
{
	debug
	$GOOSE_CVS -q up -dR
}

#-----
##
## @function svn_update
##
## @desc Do svn update in current directory
##
#-----
function svn_update
{
	debug
	$GOOSE_SVN up
}

#-----
##
## @function hg_update
##
## @desc Do hg update in current directory
##
#-----
function hg_update
{
	debug
	$GOOSE_HG pull -u
}

#-----
##
## @function hg_none_update
##
## @desc Update hg managed directory. Convert it to hg if it's the first time
##
#-----
function hg_none_update
{
	debug
	set -e

	init_incoming_repository
	$GOOSE_HG addremove
	$GOOSE_HG ci -m "* update $(date +'%Y-%m-%d %H-%M-%S')" >/dev/null
}

#-----
##
## @function hg_cvs_update
##
## @desc Update hg manged cvs repo in current path. Convert it to hg if it's
## the first time
##
#-----
function hg_cvs_update
{
	debug
	set -e

	init_incoming_repository
	cvs_update
	$GOOSE_HG addremove
	$GOOSE_HG ci -m "* cvs up $(date +'%Y-%m-%d %H-%M-%S')" >/dev/null
}

#-----
##
## @function hg_svn_update
##
## @desc Update hg manged svn repo in current path. Convert it to hg if it's
## the first time
##
#-----
function hg_svn_update
{
	debug
	set -e

	init_incoming_repository
	local SRC_REV=$( svn info . | grep ^Revision: | sed -e 's/.* //' )
	svn_update
	local DST_REV=$( svn info . | grep ^Revision: | sed -e 's/.* //' )
	if [ "$SRC_REV" -eq "$DST_REV" ]; then
		return
	fi

	local TEMP=$GLOBAL_GOOSE_TEMP/svn_update
	local MSG=$GLOBAL_GOOSE_TEMP/msg

	svn log --xml -r $SRC_REV:HEAD |
		perl -e '$_=join "", <>; while (m/<logentry\s+revision="(\d+)"/sg) { print "$1\n" }' |
		while read LINE; do
			set -e
			[ "$LINE" = "$SRC_REV" ] && continue

			svn up -r "$LINE"
			svn log -r "$LINE" > $TEMP
			local AUTHOR=$( cat $TEMP | tail +2 | head -1 | sed -e 's/^[^|]*| //' -e 's/ .*//' )
			[ -z "$AUTHOR" ] && error_short "hg_svn_update: no author found"
			local DATE=$( cat $TEMP | tail +2 | head -1 | sed -e 's/^[^|]*|[^|]*| //' -e 's/ (.*//' )
			[ -z "$DATE" ] && error_short "hg_svn_update: no date found"
			cat $TEMP | perl -e '
				@x=<>;
				@x=splice (@x, 3, -1); # remove everything apart message
				while ($x[-1]=~m/^\r?\n$/s) { pop @x }; # remove empty lines at the end
				print @x' > $MSG
			echo "\nOriginal svn revision: $LINE" >> $MSG
			$GOOSE_HG addremove
			$GOOSE_HG ci -u $AUTHOR -d "$DATE" -l $MSG
		done || error_short "Error in hg_svn_update"
}

#-----
##
## @function hg_teamware_update
##
## @desc Update hg manged teamware repo in current path. Convert it to hg if
## it's the first time
##
#-----
function hg_teamware_update
{
	debug
	set -e

	init_incoming_repository
	teamware_update
	$GOOSE_HG addremove
	$GOOSE_HG ci -m "* bringover $(date +'%Y-%m-%d %H-%M-%S')" >/dev/null
}

#-----
##
## @function hg_hg_update
##
## @desc Update hg repo in current path.
##
#-----
function hg_hg_update
{
	debug
	set -e
	
	if [ -n "$GOOSE_NO_CONTROL" ]; then
		$GOOSE_HG addremove
		$GOOSE_HG ci -m "* goose update $(date +'%Y-%m-%d %H-%M-%S')" >/dev/null
		return
	fi
		
	if ! $GOOSE_HG showconfig | grep paths.default > /dev/null; then
		# No parent defined
		return
	fi
	$GOOSE_HG pull -u
}

#-----
##
## @function update_incoming
##
## @desc Update incoming repository at given path
##
## @param $1 - incoming repository path
##
#-----
function update_incoming
{
	debug

	cd "$1" &&

	local TYPE=$(get_repo_type)
	if [ -n "$TYPE" ]; then
		eval hg_${TYPE}_update
	fi
}

#-----
##
## @function is_mq_clean
##
## @desc Checks that mercurial patches queue is clean. If there is no mq, it
## returns succesfully
##
## @param $1 -  temporary file
##
#-----
function is_mq_clean
{
	debug

	if [ -d ".hg/patches" ]; then
		cd .hg/patches &&
		is_workspace_clean "$1" &&
		cd ../.. &&
		return
	fi
}

#-----
##
## Checks if CWD mercurial workspace is 'clean'. That means no local changes
##
## @param $1 - temporary file
##
#-----
function is_workspace_clean
{
	debug

	if [ -z "$1" ]; then
		error_short "is_workspace_clean: Error, no directory specified"
	fi
	
	local CLEAN_TEMP="$1"

	$GOOSE_HG status >"$CLEAN_TEMP" 2>&1 &&

	# Exit if there are some conflicts
	if [ $( cat "$CLEAN_TEMP" | wc -l ) != "0" ]; then
		echo "hg status not clean"
		return 1
	fi

	if [ -e ".hg/patches.1" ]; then
		echo ".hg/patches.1 exists"
		return 1
	fi

	local SIZE=$( find .hg/patches/series -size 0 2>/dev/null )
	if [ -n "$SIZE" ]; then
		# hg qpush returns error when there are no patches
		return 0
	fi

	$GOOSE_HG qpop -a >"$CLEAN_TEMP" 2>&1

	if ! $GOOSE_HG qpush -a >"$CLEAN_TEMP" 2>&1; then
		echo "qpush -a does not work"
		return 1
	fi

	return 0
}

#-----
##
## @param $1 - incoming repository path
##
## a) Create apropriate .hgignore
## b) Do first commit
##
#-----
function init_incoming_repository
{
	debug
	set -e

	[ -d .hg ] && return

	if [ -n "$GOOSE_NO_CONTROL" ]; then
		$GOOSE_HG init .
		$GOOSE_HG addremove
		$GOOSE_HG ci -m "Initial goose Hg setup"
		return
	fi
	if [ -d CVS ]; then
		echo 'syntax: regexp' > .hgignore
		echo '(^|/)CVS($|/)' >> .hgignore
		$GOOSE_HG init .
		$GOOSE_HG addremove
		$GOOSE_HG ci -m "Initial goose Hg setup for CVS"
		return
	fi
	if [ -d .svn ]; then
		echo 'syntax: regexp' > .hgignore
		echo '(^|/).svn($|/)' >> .hgignore
		local REV=$( svn info . | grep ^Revision: | sed -e 's/.* //' )
		local DATE=$( svn log -r $REV | tail +2 | head -1 | sed -e 's/^[^|]*|[^|]*| //' -e 's/ (.*//' )
		$GOOSE_HG init .
		$GOOSE_HG addremove
		$GOOSE_HG ci -d "$DATE" -m "Initial goose Hg setup for SVN; revision = $REV"
		return
	fi
	if [ -d Codemgr_wsdata ]; then
		echo 'syntax: regexp' > .hgignore
		echo '(^|/)Codemgr_wsdata($|/)' >> .hgignore
		echo '(^|/)SCCS($|/)' >> .hgignore
		$GOOSE_HG init .
		$GOOSE_HG addremove
		$GOOSE_HG ci -m "Initial goose Hg setup for TeamWare"
		return
	fi
	error_short "Unknown repository type at $(pwd). Perhaps you need -nocontrol parameter ?"
}

#-----
##
## @function commit_patches
##
## @desc commits queue state
##
## @param $1 - patches repository path
##
#-----
function commit_patches
{
	debug

	cd "$1"
	$GOOSE_HG qpop -a
	local TIP=$( $GOOSE_HG tip --template '{rev}\n')
	$GOOSE_HG qcommit --addremove -m "$TIP $(date +'%Y-%m-%d %H-%M-%S')"
}

#-----
##
## @function refresh_patches
##
## @desc Goes through all patches in queue and does refresh only when necessary
##
## @param $1 - patches repostory path
##
#-----
function refresh_patches
{
	debug
	set -e

	local TEMP=$GLOBAL_GOOSE_TEMP/refresh_patches

	cd "$1"
	$GOOSE_HG qpop -a >> "$TEMP" 2>&1
	while $GOOSE_HG qpush >> "$TEMP" 2>&1; do
		$GOOSE_HG qrefresh >> "$TEMP" 2>&1
	done

	cd "$1/.hg/patches"
	if ! $GOOSE_HG showconfig | grep ^diff.nodates &> /dev/null
	then
		echo "diff.nodates is not set in hgrc, I'm running sterilize_qrefresh ($STERILIZE_QREFRESH)"
		$GOOSE_HG diff | $STERILIZE_QREFRESH 2>&1 >> "$TEMP"
	fi

	# Now check if we still apply cleanly
	cd "$1"
	$GOOSE_HG qpop -a 2>> "$TEMP" 2>&1
	$GOOSE_HG qpush -a >> "$TEMP" 2>&1
	is_workspace_clean "$TEMP" && return

	( echo "Refreshing patches in "`pwd`" did not work as expected.\n\n"; cat "$TEMP" ) | error "Workspace '$1' mq refresh failed"
}

#-----
##
## @function create_patches_repository
##
## @desc create patches repository if there is not already one
##
## @param $1 - incoming repository path
## @param $2 - patches repository path
##
#-----
function create_patches_repository
{
	debug
	set -e

	cd "$ORIGINAL_PWD"

	if ! [ -e "$2" ]; then
		# If we are asked to sync with non-existing clone, create it first
		$GOOSE_HG clone "$1" "$2" &&
		$GOOSE_HG --cwd "$2" qinit -c &&
		return

		error_short "Creating patches repo failed"
	fi
}

#-----
##
## @function update_patches_setup
##
## @desc update patches repository from incoming repository. Start merging process also
##
## @param $1 - patches repository path
## @param $2 - do microstep update if nonempty string
##
#-----
function update_patches_setup
{
	debug

	local TEMP=$GLOBAL_GOOSE_TEMP/update_patches_setup_1
	local TEMP2=$GLOBAL_GOOSE_TEMP/update_patches_setup_2

	cd "$1" &&

	# Exit if there are some conflicts
	local REASON
	REASON=$( is_workspace_clean "$TEMP" ) ||
		( echo "Workspace "`pwd`" is not in clean state:$REASON"; cat "$TEMP" ) | error "Workspace '$1' is not clean"

	REASON=$( is_mq_clean "$TEMP" ) ||
		( echo "Workspace's MQ at "`pwd`" is not in clean state:$REASON"; cat "$TEMP" ) | error "Workspace '$1' is not clean"

	$GOOSE_HG qpop -a >> "$TEMP" 2>&1

	if $GOOSE_HG incoming | grep "no changes found" > /dev/null; then
		# No changes
		return
	fi

	$GOOSE_HG qpush -a >> "$TEMP" 2>&1
	$GOOSE_HG qsave -e -c >> "$TEMP" 2>&1

	if [ -z "$2" ]; then
		# whole change
		$GOOSE_HG pull >> "$TEMP" 2>&1
	else
		# microstep
		local REV
		REV=$( $GOOSE_HG incoming --newest-first --template '{rev}\n' | tail -1 )
		if [ -z "$REV" ]; then
			error_short "Can't get microstep revision"
		fi
		$GOOSE_HG pull --rev "$REV" >> "$TEMP" 2>&1
	fi

	$GOOSE_HG update -C tip >> "$TEMP" 2>&1
	touch "$1/.hg/goose_in_merging"
}

#-----
##
## @function update_patches_run
##
## @desc Run the actual merging work. If merge failed, this is the point where to continue
##
## @param $1 - patches repository
##
#-----
function update_patches_run
{
	debug

	local TEMP=$GLOBAL_GOOSE_TEMP/update_patches_run_1

	cd "$1"

	HGMERGE=/bin/false $GOOSE_HG qpush -m -a >> "$TEMP" && return

	(
		echo "Workspace "`pwd`" Needs manual intervention:\n\n";
		cat "$TEMP";
		echo "\n\nTO MANUALLY MERGE:\ncd $1\n$GOOSE_HG qpush -m\n... resolve conflicts\n$0 -mq '' $1\n"
	) | error "Workspace '$1' needs merging"
}

#-----
##
## @function update_patches_finish
##
## @desc Cleanup after mq merging
##
## @param $1 - patches repository
##
#-----
function update_patches_finish
{
	debug

	local TEMP=$GLOBAL_GOOSE_TEMP/UPDATE_PATCHES_FINISH

	cd "$1"

	if [ $( $GOOSE_HG qunapplied | wc -l ) != "0" ]; then
		( echo "Workspace "`pwd`": not all patches pushed\n\n"; cat "$TEMP" ) | error "Workspace '$1': not all patches pushed"
	fi

	set -e

	$GOOSE_HG qpop -a >> "$TEMP"
	$GOOSE_HG qpop	-a -n patches.1 >> "$TEMP"
	rm -rf ".hg/patches.1"
	rm ".hg/goose_in_merging"
}

#-----
##
## @function full_path
##
## @desc Return absolute path of a directory for the given relative path
## If $1 is empty, return empty
##
## @param $1 - directory path
##
#-----
function full_path
{
	debug
	[ -z "$1" ] && return
	cd "$1" || error_short "full_path: '$1' does not exist or is not directory"
	pwd
}

#-----
##
## @function have_outstanding_changes
##
## @desc returns true if hg pull will get some changes from parent
##
## @param $1 - hg repository path to be considered updating
##
#-----
function have_outstanding_changes
{
	debug

	cd "$1"
	$GOOSE_HG incoming --template '{rev}\n' | grep "no changes found" >/dev/null && return 1
	return 0
}

#-----
##
## @function do_update_incoming
##
## @desc Update incoming repository. If patches is empty, don't convert it to hg
##
## @param $1 - incoming repository path
## @param $2 - patches repository path
##
#-----
function do_update_incoming
{
	debug

	local INCOMING="$1"

	# If INCOMING is empty, don't update from official gate
	if [ -n "$INCOMING" ]; then
		if [ $# = 1 ]; then
			# Simple update
			export TYPE=$(get_repo_type "$INCOMING")
			[ -n "$TYPE" ] && (
				cd "$INCOMING"
				eval ${TYPE}_update
			)
			return
		fi

		update_incoming "$INCOMING"
		create_patches_repository "$@"
	fi
}

#-----
##
## @function do_update_patches
##
## @desc Update patches repository. Main work is done here
##
## @param $1 - path to patches repository
##
#-----
function do_update_patches
{
	debug
	[ -z "$1" ] && return
	local PATCHES
	PATCHES=$(full_path "$1")

	if [ -e "$PATCHES/.hg/goose" ]; then
		( echo "Workspace "`pwd`" is locked\n$PATCHES/.hg/goose exists\n" ) | error "Workspace '$1' is locked"
	fi
	while [ -e "$PATCHES/.hg/goose_in_merging" ] || have_outstanding_changes "$PATCHES" || [ -n "$GOOSE_REFRESH_MQ" ]; do
		if [ -n "$PATCHES" ]; then
			if [ -z $GOOSE_REFRESH_MQ ]; then
				if ! [ -e "$PATCHES/.hg/goose_in_merging" ]; then
					# update_patches_setup has to be run only once, even that several microsteps will be processed
					# Also don't run setup if we are just merging (-mq)
					update_patches_setup "$PATCHES" 'microstep'
				fi
			fi

			update_patches_run "$PATCHES"
			update_patches_finish "$PATCHES"
			refresh_patches "$PATCHES"
			commit_patches "$PATCHES"

			if [ -n "$GOOSE_REFRESH_MQ" ]; then
				# We have been just merging, don't loop over more work and return
				return
			fi
		fi
	done
}

#-----
##
## @function do_update_after
##
## @desc Run specified command after all updating is done
##
## @param $1 - incoming_ws
## @param $2 - patches_ws
##
#-----
function do_update_after
{
	debug

	[ -z "$GOOSE_AFTER_COMMAND" ] && return

	local TEMP=$GLOBAL_GOOSE_TEMP/after
	local INCOMING="$1"
	local PATCHES=""

	if [ $# = 1 ]; then
		# Just incoming is defined
		[ -z "$INCOMING" ] && error "-after: incoming_ws has to be defined if patches_ws is not"
		cd "$INCOMING"
	else
		[ -z "$2" ] && error "-after: patches_ws can not be empty when defined"
		PATCHES="$(full_path "$2")"
		cd "$PATCHES"
		$GOOSE_HG qpush -a
	fi

	set +e
		( $GOOSE_AFTER_COMMAND "$INCOMING" "$PATCHES" 2>&1 ) 2>&1 > "$TEMP"
		RET=$?
	set -e

	if ! [ $RET = 0 ]; then
		cat "$TEMP" | error "Error in -after script for workspace $(pwd)"
	fi
}

#-----
##
## @param $1 - incoming repository
## @param $2 - patch repository (optional)
##
## When $2 is missing, we just update incoming repository (like cvs up)
##
## Otherwise we
## a) Update incoming repository
## b) Update Hg for incoming repo. (unless incoming repo. is already Hg)
## c) If patches repo is not clean, exit
## d) Update patches repository
## e) If there are conflicts, help resolve them
##
#-----
function do_update
{
	debug
	set -e

	# Replace $1 by it's full path
	local INCOMING
	INCOMING="$(full_path "$1")"
	shift
	if [ $# = 0 ]; then
		set -- "$INCOMING"
	else
		set -- "$INCOMING" "$@"
	fi

	do_update_incoming "$@"
	do_update_patches "${2-}"
	do_update_after "$@"
}

#-----
##
## @function usage
##
## @desc Usage
##
#-----
function usage
{
name=$(basename "$0")
cat <<EOT
$name [options] incoming_ws [patches_ws]
Version: $VERSION

options:
   -h|--help  - this help
	-m|--mail  - report errors by email
	-d         - produce debug output
	-dfile     - produce debug output and store it to file
	-mq        - just refresh mq
	-nocontrol - incoming repository is not under version control
	-after cmd - run following command after successfull merge
	   - the command is run from patches_ws dir
	   - if patches_ws is empty string, after command is not run
	   - if patches_ws is not defined, after command is run from incoming_ws
		  - all mq patches are applied first

$name incoming - update only incoming directory
$name incoming patches - update incoming, patches, and sync your changes

environment variables:
EOT

	environment_display

cat <<EOT

Examples:
=========

Detect versioning system and do update:
  $name incoming_ws

Detect versioning system, do update and convert to hg:
  $name incoming_ws ''

Detect versionoing system, do update, convert to hg and update patches:
  $name incoming_ws patches_ws

Don't let goose update incoming repository
  cd incoming_ws
  p4 sync            # do whatever you need to bring the incoming up to date
  convert_to_hg.sh   # do whatever you need to convert the changes to hg
                     # now ask $name to skip any management of incoming repository
  $name -nocontrol incoming_ws patches_ws

If updating patches resulted in colision, merge it by hand:
  cd patches_ws      # enter the workspace
  hg qpush -m        # initiate merge. External tool will be launched
  $name -mq '' .     # finish the merge work by commiting mq state
							# now continue in automatic patches updating to process
							#  any remaining changes
  $name incoming_ws patches_ws
EOT
}

#-----
##
## @function main
##
## @param $1 - incoming repository
## @param $2 - patches repository
##
#-----

local CNT=0
if [ -n "$GOOSE_USAGE" ] || [ $# = 0 ]; then
	usage
	exit
fi

do_update "$@"
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 185 bytes
Desc: not available
URL: <http://lists.mercurial-scm.org/pipermail/mercurial/attachments/20071124/3ab0804b/attachment-0001.asc>


More information about the Mercurial mailing list