Wednesday, September 01, 2010

Using Mercurial Subrepositories

If you have any class libraries whose usage spans multiple projects, you will want to include them as subrepositories in your Mercurial repository. This allows you to have the library stored in its own repository rather than copying it into each individual project that requires it. This way, changes to the library can be reflected in all projects using the library. This article shows how to accomplish that task.

Let's say you have a class library named ClassLibrary1 which contains methods useful to several projects. Here is the code for ClassLibrary1
    public class Class1
    {
        public static string MyFunction()
        {
            return "ClassLibrary1 Version 1.0";
        }
    }

The library resides in a mercurial repository located at
\\hgStore\c\hgMain\Libraries\ClassLibrary1

After creating a new project, we set up the mercurial repository on it.
F:\My Projects\CoolApp>hg init

F:\My Projects\CoolApp>hg add
adding src\CoolApp.sln
adding src\CoolApp\CoolApp.csproj
adding src\CoolApp\Form1.Designer.cs
adding src\CoolApp\Form1.cs
adding src\CoolApp\Program.cs
adding src\CoolApp\Properties\AssemblyInfo.cs
adding src\CoolApp\Properties\Resources.Designer.cs
adding src\CoolApp\Properties\Resources.resx
adding src\CoolApp\Properties\Settings.Designer.cs
adding src\CoolApp\Properties\Settings.settings

F:\My Projects\CoolApp>

Now we clone the library into our CoolApp project repository.
F:\My Projects\CoolApp>hg clone \\hgStore\c\hgMain\Libraries\ClassLibrary1 src\ClassLibrary1
updating to branch default
9 files updated, 0 files merged, 0 files removed, 0 files unresolved

The class library is now located in the project directory structure where we want it. So now we can mark that directory as being a subrepository. We do that by creating a special file called ".hgsub".
F:\My Projects\CoolApp>echo src\ClassLibrary1 = \\hgStore\c\hgMain\Libraries\ClassLibrary1> .hgsub

In this case "src\ClassLibrary" is the path in our working directory, and of course "\\hgStore\c\hgMain\Libraries\ClassLibrary1" is the path to pull from.

Now add our special .hgsub file to version control:
F:\My Projects\CoolApp>hg add .hgsub

And commit:
F:\My Projects\CoolApp>hg commit --message="Set up subrepository for ClassLibrary1"
committing subrepository src\ClassLibrary1

F:\My Projects\CoolApp>

We're all set. Now if we clone our CoolApp project, it will include the ClassLibrary1 dependency.
F:\My Projects\>hg clone CoolApp CoolApp2
updating to branch default
pulling subrepo src\ClassLibrary1 from \\hgStore\c\hgMain\Libraries\ClassLibrary1
requesting all changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 14 changes to 9 files
12 files updated, 0 files merged, 0 files removed, 0 files unresolved

An important consideration when working with subrepositories is whether reverting to a specified revision of the project will also revert the library to the revision at the specified time.

Let's change the output of ClassLibrary1 and commit our changes.

    public class Class1

    {

        public static string MyFunction()

        {

            return "ClassLibrary1 Version 1.1"; //updated version

        }

    }


F:\My Projects\CoolApp2>hg commit --message="Changed output of ClassLibrary1"
committing subrepository src\ClassLibrary1

F:\My Projects\CoolApp2>hg push
pushing to f:\My Projects\CoolApp
pushing subrepo src\ClassLibrary1 to \\hgStore\c\hgMain\Libraries\ClassLibrary1
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files

Uh-oh. Someone thinks they found a bug in Version 1.0 of CoolApp. In order to duplicate the bug so we can understand it, we need to revert to the reported version. If our subrepository doesn't also revert to the state of ClassLibrary1 at the time of Version 1.0 we're going to have a mess on our hands. Let's try it and see what happens:
F:\My Projects\CoolApp2>hg log
changeset: 2:ef31b83178d1
tag: tip
user: Joel
date: Wed Sep 01 12:30:07 2010 -0600
summary: Changed output of ClassLibrary1

changeset: 1:a577f539790e
user: Joel
date: Wed Sep 01 12:21:30 2010 -0600
summary: Set up subrepository for ClassLibrary1

changeset: 0:d12383009bd2
user: Joel
date: Wed Sep 01 12:19:09 2010 -0600
summary: Initial revision


a577f539790e is the revision we want to work with.
F:\My Projects\CoolApp2>hg update a577f539790e
1 files updated, 0 files merged, 0 files removed, 0 files unresolved


Let's take a look at the code for ClassLibrary1

    public class Class1

    {

        public static string MyFunction()

        {

            return "ClassLibrary1 Version 1.0";

        }

    }


Perfect! It's just as we hoped.

Learn more about subrepositories in Mercurial
Have I missed something? Feedback is greatly appreciated.