Mercurial: Autopushing changes from a central repository

When you have hundreds of applications managed via a central repository which are checked out to multiple production and staging servers, it can become burdensome to manage and/or remember where every working copy of that code resides. We recently ran into this issue at my work so we needed a solution that was going to be developer friendly and easy to manage on a per-repository basis.

Not only did we want to automatically push new changesets to working copies, but we wanted to automatically update those working copies based upon their nature; staging working copies needed to be updated to the ‘qa’ branch, and production copies needed to be updated to the latest tag (all of our tags reside on the stable branch).

I eventually accomplished this task with a small bash script that runs on the ‘changegroup’ hook and reads the contents of a specific version controlled file in that repository, .hgautopush. The contents of the bash file are below. I simply added this hook to the Rhodecode admin screen, created the file in any repository I wanted to automatically push changes from, and we were set.

[code lang=”bash”]#!/bin/bash

mktemp_bin=`which mktemp`;

# create a random temp file
temp_file=`$mktemp_bin -t autopushXXX`;

hg cat -r default .hgautopush 1>"$temp_file" 2>/dev/null

# Add an extra newline to the file, just in case
echo -e "\n" >> $temp_file

cat $temp_file | while read URL; do
IFS=':’ read -ra PARTS <<< "$URL"
if [ -z "${PARTS[1]}" ] || [ -z "${PARTS[0]}" ]; then
continue
fi

# If the destination doesn’t exist, attempt to create it
if [ ! -d "${PARTS[1]}" ]; then
echo "Remote repository does not exist; Attempting to create…" >/dev/stdout
hg init "${PARTS[1]}" 2>/dev/null

# If it still doesn’t exist, error
if [ ! -d "${PARTS[1]}" ]; then
echo "Could not create remote repository at ${PARTS[1]}" > /dev/stderr
continue
fi
fi

# Push to the path, this could take a small amount of time due to automounting
hg push "${PARTS[1]}" –force

if [ "${PARTS[0]}" = "staging" ]; then
echo "Updating to latest QA revision…" >/dev/stdout
hg update qa –repository "${PARTS[1]}" 2>/dev/null

elif [ "${PARTS[0]}" = "production" ]; then
echo "Updating to latest tag on \"stable\" branch…" >/dev/stdout
hg update –rev "max(branch(stable) and tagged())" –repository "${PARTS[1]}" 2>/dev/null
fi
done

rm $temp_file
[/code]

To prepare for this to actually work, I installed autofs on our server, obtained an active directory user for our Windows servers, and created a local user on all of the Linux servers with SSH authentication, assigned the proper permissions, and setup autofs for both sshfs and cifs access to the appropriate servers. The .hgautopush file has a format similar to this:

[code]staging:/windows/path/to/windows/share
production:/linux/path/to/linux/share[/code]

This accomplished all of our goals with minimal effort. Our developers are now able to create a simple file in any repository they want autopushed to another location (very handy for repositories that live on our public servers which have no reverse connection back inside the network).