Merging Mercurial repositories

A quick note because if I ever need to do this again I know I'll have forgotten how :-) I had three Mercurial repos that were parts of the same thing, a web-based browser/searcher for engineering documents at work. The three parts were all NetBeans projects that were interrelated, so having them as separate repos didn't make much sense. However, merging them into a single, non-branched repo whilst maintaining history wasn't particularly straightforward, I couldn't find any description of how to do this that didn't end up with branches in the resulting repo, and mercurial extensions such as mq didn't seem to cut it either. After some experimentation, I came up with the following sequence of steps which did what I wanted:

  1. Use the Mercurial Convert extension to move each project one directory level down so they didn't clash when merged. This uses the --filemap flag with a simple filemap of the form
    . NewProjSubDir
    
    to move each project into a subdirectory:
    $ hg convert --filemap filemap orig1 moved1
    

  2. Pull all the intermediate moved projects into a new repo:
    $ hg clone moved1 merged
    $ cd merged
    $ hg pull -f ../moved2
    $ hg pull -f ../moved3
    
    The -f flag is needed because the repos are unrelated. This will result in a repo with a branch for each source repo, with each branch sorted in date order but the repo overall won't be date sorted.

  3. Reorder the history so it is sorted in date order, again using the Convert extension to do that:
    $ hg convert --datesort merged merged1
    

  4. Reparent each changeset in the merged, sorted repo so that its parent is the immediately preceding one, in date order. The Convert extension's --splicemap feature is used to do this, along with a small perl script to create the splicemap:
    #!/bin/perl
    use strict;
    use warnings;
    my ($line, $parent);
    while (defined($line = <>)) {
            if ($line =~ m{\bchangeset:\s+\d+:([\dabcdef]+)\b}) {
                    my $child = $1;
                    if (defined($parent)) {
                            printf("%s %s\n", $child, $parent);
                    }
                    $parent = $child;
            }
    }
    
    
    Then:
    $ cd merged1
    $ hg glog --debug | perl ../splicemap.pl > ../splicemap
    $ cd ..
    $ hg convert --splicemap splicemap merged1 merged2
    
    The resulting merged2 repo will have the merged, date-sorted and branchless union of the contents of the original repos.
Categories : Tech, Work