Using Multiple Working Directories in both Mercurial and Git

I’ve been told that utilizing this functionality is terrible, bad practice, a mistake, contributes to climate change, etc. However, it fits into my workflow pretty well, and it might fit into yours as well. After all, it is implemented in at least two version control systems…so some people must find it useful, right?

I use both Mercurial and Git depending on the project, so I’ll briefly describe the usage for both, with references to more complete documentation at the end. While the implementation is different, in both cases there are essentially multiple working directories for a single repository.

Use Cases

I only use this feature as the sole committer to a local repository; any interaction with other developers is done via a normal DVCS push/pull workflow. This feature allows for working on multiple branches (or any commit/checkout) from the same repository using different working directories. There are a few situations I’ve run into where this is helpful:

  • The build process is time consuming and generates a lot of artifacts – This streamlines working on multiple branches simultaneously.
  • Items outside of the repository are referenced – Having to switch out the references between branches can be a pain, but with multiple directories it’s not necessary.
  • Comparisons between different versions of a project – This especially streamlines situations where you need to compare several files at once, or make modifications as you go.
  • Flexibility – While light branching is great, sometimes it’s nice to be able to experiment in separate directories without needing to shelve/stash those changes every time work is needed on a different branch.

There are other workarounds for these types of problems, but this method is straightforward and simple (assuming that you’re aware of the pitfalls).

The obvious question is why not just clone the repository? With multiple repositories, you need to make sure to keep them synchronized, which can be easy to overlook sometimes (e.g. a branch you only work on every once in a while).

This becomes a bigger issue when you often have to pull in upstream changes from one or more sources. With repository clones you need to make sure that all of them get the latest updates every time. On the enterprise or team level this can be easily automated, but at least in my experience, for a single user simply having multiple working directories is easier to manage.

Another workflow that I haven’t used myself but may be interesting is to have your “real” repository be “bare”, or not have a checkout. Then individual branches can be checked out in separate working directories, all referencing the real repository. This is similar to the primary model supported by the Bazaar VCS.

Mercurial (hg share)

Of the two, Mercurial’s approach is more simplistic. It utilizes the “share” extension, which is bundled with Mercurial but needs to be activated in your configuration:

[extensions]
share =

You can then create a share.

hg share <source> <destination>

When creating a share, you have the option whether or not to create an empty working directory, and whether or not to share bookmarks.

In Mercurial this functionality doesn’t provide much in the way of safety nets. It’s best to avoid any destructive operations (e.g. rollback, Mercurial Queues usage, HistEdit, etc.) as the state of the other working directories could easily become invalid. Verifying that you’re working on separate heads isn’t a bad idea either, be it just via the directories, or more explicitly via bookmarks or branches.

Git (git worktree)

In Git, this functionality offers some additional features and safeguards. It works out of the box.

From an existing Git repository you can run the following command to create a separate “work tree”:

git worktree add <directory>

You have the option to specify the branch, create a detached HEAD, and whether or not to lock the work tree. Among other things, locking a work tree prevents it from being moved/deleted. Git also provides the ability to list the current set of working directories for a repository via the git worktree list command.

By default Git will prevent you from checking out a branch that is already checked out in another directory, but that can be forced. As with Mercurial, be careful to avoid any destructive history rewriting. According to the documentation this functionality should also not be used with submodules.

References