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.

#!/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

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:

staging:/windows/path/to/windows/share
production:/linux/path/to/linux/share

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