Automated workspace updates with Mercurial

When using a distributed SCM, it is often very useful to be able to automatically sync a “reference” workspace with a remote, “parent” workspace. This way, even when offline, a local clone of the parent workspace is available.

Having a local clone of the “reference tree” is useful in many ways.

On my laptop I keep periodically sync’ed clones of the following Mercurial repositories, for example:

After a while, I got tired of having to type “hg pull -u” in each one of them:

% cd ~/hg/mercurial/main && hg pull
% cd ~/hg/mercurial/stable && hg pull
% cd ~/hg/mercurial/crew && hg pull
% cd ~/hg/mercurial/crew-stable && hg pull

Even with the hg -R repo ... option of Mercurial itself, it’s boring to have to type things like:

% cd ~/hg/mercurial ; \\
    for repo in main stable crew crew-stable ; do \\
        hg -R "${repo}" pull -u ; \\
    done

Instead of having to rely on bash(1) and its wonderful CTRL-R history search feature to repeat this command whenever I needed it, I started writing a shell script to make this process easier to repeat without a lot of opportunity for typos, or other errors being introduced:

#!/usr/bin/ksh -p

# Copyright (c) 2007 Giorgos Keramidas <keramida@FreeBSD.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

#
# Pull changesets in one or more Mercurial clones.
#
# usage:
#	hg-pull-mercurial repo [repo ...]
#
# The repositories where local Mercurial clones live are assumed to be
# under ${HOME}/hg/mercurial or under the ${MERCURIALBASE} path.
#

# --------------------------------------------------------------------

progname=$(basename "$0")

# If TMPDIR is not set, use /var/tmp as a safe fallback value.
TMPDIR="${TMPDIR:-/var/tmp}"
export TMPDIR

# --------------------------------------------------------------------

#
# err exitval message
#	Display message to stderr and log to the syslog, and exit with
#	exitval as the return code of the script.
#
function err
{
        exitval=$1
        shift

        log "${progname}: ERROR: $*"
        exit "${exitval:-1}"
}

#
# warn message
#	Display message to stderr and the log file, if any.
#
function warn
{
	log "${progname}: WARNING: $*"
}

#
# info message
#	Display informational message to stdout and to the logfile,
#	if one is defined.
#
function info
{
	log "${progname}: INFO: $*"
}

#
# debug message
#	Output message to stderr if debug_output_enabled is set to
#	'yes', 'true' or '1'.  Please AVOID calling any shell
#	subroutine that may recursively call debug().
#
function debug
{
	case ${debug_output_enabled} in
	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
		log "${progname}: DEBUG: $*"
		;;
	esac
}

#
# log message
#	Display time-stamped message and log it to "${LOGFILE}",
#	if one is defined.
#
function log
{
	__timestamp="`date -u '+%Y-%m-%d %H:%M:%S'`"
	print -u2 "${__timestamp} -- $*"
	if [ -n "${LOGFILE}" ]; then
		print "${__timestamp} -- $*" >> "${LOGFILE}"
	fi
}

#
# msg message
#	Display message and log it to "${LOGFILE}", if one is defined.
#
function msg
{
	print -u2 "$*"
	if [ -n "${LOGFILE}" ]; then
		print "$*" >> "${LOGFILE}"
	fi
}

# --------------------------------------------------------------------

function usage
{
	print -u2 "usage: ${progname} repo [repo ...]"
	exit 1
}

if [ $# -eq 0 ]; then
	usage
	exit 1
fi

# --------------------------------------------------------------------

#
# Local mercurial clones are assumed to be stored in ~/hg/mercurial,
# unless MERCURIALBASE points elsewhere.  The repositories under the
# $MERCURIALBASE tree should have a 'parent' path defined in their
# repository-specific .hg/hgrc configuration, pointing at the proper
# remote 'parent workspace'.
#
MERCURIALBASE="${MERCURIALBASE:-${HOME}/hg/mercurial}"

EXIT=0
for repo in "$@" ; do
	repodir="${MERCURIALBASE}/${repo}"
	debug "Checking for hg workspace at ${repodir}"
	if [ ! -d "${repodir}/.hg" ]; then
		err 1 "${repodir} is not a Mercurial workspace"
	fi
	debug "Workspace looks ok at ${repodir}"

	log "Pulling changes into ${repodir}"
	hg -R "${repodir}" pull -u parent
	if [ $? -ne 0 ]; then
		warn "${repodir}: cannot update workspace."
		EXIT=1
	fi
	log "Successfully updated workspace at ${repodir}"
	msg ""
done

exit $EXIT

With script saved in my ~/cron.d directory, I can now update one or more of my local Mercurial clones, by running:

% ~/cron.d/hg-pull.sh main stable crew crew-stable

This is something that will also look good in a cron job, but I’m sticking to the manual invocation for now, until all the warts have been cleaned in the pull script.

Running the script is now as easy as:

% /cron.d/hg-pull.sh
usage: hg-pull.ksh repo [repo ...]

Updating one repository is also easy:

% ~/cron.d/hg-pull.ksh main                        
2007-09-06 15:36:49 -- Pulling changes into /home/keramida/hg/mercurial/main
pulling from http://www.selenic.com/repo/hg/
searching for changes
no changes found
2007-09-06 15:36:51 -- Successfully updated workspace at /home/keramida/hg/mercurial/main

%

Multiple repositories seem to work too:

% ~/cron.d/hg-pull-mercurial.ksh main stable crew crew-stable
2007-09-06 15:37:43 -- Pulling changes into /home/keramida/hg/mercurial/main
pulling from http://www.selenic.com/repo/hg/
searching for changes
no changes found
2007-09-06 15:37:45 -- Successfully updated workspace at /home/keramida/hg/mercurial/main

2007-09-06 15:37:45 -- Pulling changes into /home/keramida/hg/mercurial/stable
pulling from http://www.selenic.com/repo/hg-stable
searching for changes
no changes found
2007-09-06 15:37:46 -- Successfully updated workspace at /home/keramida/hg/mercurial/stable

2007-09-06 15:37:46 -- Pulling changes into /home/keramida/hg/mercurial/crew
pulling from http://hg.intevation.org/mercurial/crew
searching for changes
no changes found
2007-09-06 15:37:47 -- Successfully updated workspace at /home/keramida/hg/mercurial/crew

2007-09-06 15:37:47 -- Pulling changes into /home/keramida/hg/mercurial/crew-stable
pulling from http://hg.intevation.org/mercurial/crew-stable
searching for changes
no changes found
2007-09-06 15:37:48 -- Successfully updated workspace at /home/keramida/hg/mercurial/crew-stable

% 

That was nice, and fairly easy to get going :-)

3 thoughts on “Automated workspace updates with Mercurial

  1. keramida Post author

    Today’s multi-pull run seems to have worked ok too :-)

    $ ~/cron.d/hg-pull-mercurial.ksh main stable crew crew-stable
    2007-09-07 14:28:34 — Pulling changes into /home/keramida/hg/mercurial/main
    pulling from http://www.selenic.com/repo/hg/
    searching for changes
    no changes found
    2007-09-07 14:28:46 — Successfully updated workspace at /home/keramida/hg/mercurial/main

    2007-09-07 14:28:46 — Pulling changes into /home/keramida/hg/mercurial/stable
    pulling from http://www.selenic.com/repo/hg-stable
    searching for changes
    no changes found
    2007-09-07 14:28:49 — Successfully updated workspace at /home/keramida/hg/mercurial/stable

    2007-09-07 14:28:49 — Pulling changes into /home/keramida/hg/mercurial/crew
    pulling from http://hg.intevation.org/mercurial/crew
    searching for changes
    adding changesets
    adding manifests
    adding file changes
    added 3 changesets with 4 changes to 4 files
    4 files updated, 0 files merged, 0 files removed, 0 files unresolved
    2007-09-07 14:29:04 — Successfully updated workspace at /home/keramida/hg/mercurial/crew

    2007-09-07 14:29:04 — Pulling changes into /home/keramida/hg/mercurial/crew-stable
    pulling from http://hg.intevation.org/mercurial/crew-stable
    searching for changes
    no changes found
    2007-09-07 14:29:07 — Successfully updated workspace at /home/keramida/hg/mercurial/crew-stable

    I can smell a cron job coming my way…

  2. -Johan

    With some small changes this script fit my purposes like a glove. Thank you very much.

Comments are closed.