Automated workspace updates with Mercurial

06 Sep 2007, 15:34 by Giorgos Keramidas

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 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 the 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.

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 :-)


Comments