Mercurial Push/Pull and Update scripts#

I like Mercurial[1] as a version control system. It has a number of advantages over more traditional systems such as Subversion[2]. I won’t go into details, they are easy to find on the internet. What I have found with mercurial is that I organize all of my repos under a root directory. I also use TortoiseHG[3] as a graphical client that manages the commits and push/pull cycles. It works well for a single repository. Unfortunately it doesn’t work as well for a large number of repositories, that is it can’t do batch push/pull or updates.

I put together a couple of python scripts (conversion of windows batch files that I was using) to batch pull/push and update the repositories. The scripts all share a common module that holds common methods. It is called shared.py and it contains a method that iterates through all of the directories in a given root directory and returns the directories that match the dir_to_find criteria. In our case we are looking for directories that contain the mercurial ‘.hg’ directory. The shared.py module also contains a method that executes a shell statement within a particular directory.

shared.py:

#! /usr/bin/env python
import os
"""Contains shared methods used between the python mercurial scripts"""

def findDirectories(root, dir_to_find):
    """Walks through the directory structure from the root and finds the directories that match dir_to_find"""
    foundDirs = []
    for root, dirs, files in os.walk(root):
        for d in dirs:
            if d.upper() == dir_to_find.upper():
                foundDirs.append(os.path.join(root, d))
        return foundDirs

def executeHGStatement(statement, dir):
    """Takes a string and executes it in the passed in directory."""
    os.chdir(dir)
    os.system(statement)

In order to pull changes from the remote repositories you use the pull.py script. Starting from the directory the script is located in it searches recursively for all the ‘.hg’ directories. From there it takes the parents of the ‘.hg’ directories and executes the ‘hg pull’ command in them.

pull.py:

#! /usr/bin/env python
import os
import sys
import shared

"""This module will recurse through the current folder and discover the mercurial repositories. It then executes the hg pull command on each repository pulling the changes from the remote repository to the local one."""

if __name__ == '__main__':
    for item in shared.findDirectories(sys.path[0], '.hg'):
        repo = os.path.dirname(item)
        print 'Repository: ' + repo
        shared.executeHGStatement('hg pull',repo)

In addition to pulling changes, changes can also be pushed to remote repositories using the push.py. It works identical to the pull.py except it issues the ‘hg push’ command.

push.py:


#! /usr/bin/env python
import os
import sys
import shared

"""This module will recurse through the current folder and discover the mercurial repositories. It then executes the hg pull command on each repository pushing the changes to the remote repository."""

if __name__ == '__main__':
    for item in shared.findDirectories(sys.path[0], '.hg'):
        repo = os.path.dirname(item)
        print 'Repository: ' + repo
        shared.executeHGStatement('hg push',repo)

Typically after pulling changes using the pull.py script, the changes need to be applied to the repository. This is where the update.py script comes into play. It works identically to the other two scripts except it executes ‘hg update’.

update.py:

#! /usr/bin/env python
import os
import sys
import shared

"""This module will recurse through the current folder and discover the mercurial repositories. It then executes the hg pull command on each repository updating to the changes from the remote repository."""

if __name__ == '__main__':
    for item in shared.findDirectories(sys.path[0], '.hg'):
        repo = os.path.dirname(item)
        print 'Repository: ' + repo
        shared.executeHGStatement('hg update',repo)

The nice thing about these scripts is that they can be placed at the root of any repository folder and they will automatically find any new repositories that are added at a later time. That is also the drawback. They need to be copied to every root folder. The scripts could be modified to read the repositories paths from a file. That way the scripts could be moved to a more standard area such as /usr/local/scripts and be shared among the users of the computer.

In addition the scripts could probably be combined into one file and a command line option passed to indicate what action to perform. Personally, I think the separate scripts make it easier to work with in nautilus (or what ever graphical file display you may be working on). You would have to create some sort of shortcut or launcher with the proper command line. Executing that single script from the command line would be much easier with the command line switches in place. I may create a new script that in the near future that does just that.

Cheers, Troy