I work at a large company which uses TFS for all of its source control and task management needs. In most cases this works great, but sometimes I work on a project that involves a lot of experimenting with unconventional ninja-style techniques. I'm used to a “branch per experiment” kind of workflow with Mercurial, but it's really hard to implement this workflow with TFS, especially when the project is large and creating new branches gets really painful. So I decided to use a Mercurial clone for my experiments and then push more or less stable code to TFS. I was really surprised to find a total absense of tools which would help me to accomplish this task. And yes, I know about Git, but still prefer Mercurial. So naturally, I set out to write my very own Mercurial-Tfs bridge.
It's important to understand the workflow for which this tool was created.
First of all, we take our TFS working copy and clone a new Mercurial repository from it. Then we work in our new sandbox completely disconnected from TFS. When we are ready, we just push all our changes to TFS in a single changeset. We can also pull changes from TFS into our Mercurial repo.
This workflow implies that default is your main branch to which you pull changes from TFS and from which you push your code to the outside world. When you want to experiment, you create a new branch and crunch some crazy code there. When you feel confident enough, you merge that branch into default and then push your changes to TFS. If the experiment goes wrong, you can just abandon that branch. The picture at the beginning of this post illustrates this kind of workflow.
There are almost none. You can use whatever workflow you like, keeping in mind that hgtfs always works with your current revision (the one you are updated to). It doesn't perform much validation and generally does what you ask it to do. So if you ask it to shoot you with a railgun, it will do just that.
Working with hgtfs
The main reason behind creating this tool was aspiration for simplicity. I could've used Git along with git-tfs, but these tools don't seem intuitive at all. HgTfs has three commands. The idea is that if you need more, you are probably doing it wrong.
Creating Mercurial repository from TFS (clone)
First of all, you need to get a local copy of your TFS project. That will be your master repository. Then you just choose a folder for your new Mercurial repository and issue a clone command:
hgtfs clone -master:"C:\TFSProject" -repo:"C:\HgProject"
And that's about it. HgTfs will do the following:
- Connect to your configured TFS server and get the list of files under source control (so we don't copy binaries, Resharper caches and other junk).
- Create a directory for your Mercurial repo if it doesn't exist.
- Copy files.
- Commit the first revision.
Note that Mercurial directory you specify may not exist, but if it does, it should be empty.
Pulling changes from TFS
If you are working with a team, you will probably end up with your teammates making changes to TFS code while you are working with your local Mercurial repo. So naturally you want to be in sync with those guys. To pull changes into your local repository, execute the pull command:
hgtfs pull -repo:"C:\HgProject"
Here we have two modes of operation. If you haven't committed any changes after your las pull, hgtfs will just update your working copy with all the changes from TFS. But if you made some local changes, hgtfs will have to merge these changes with changes from TFS. Here is how it does that:
- Update to the last pulled revision.
- Create a new branch.
- Rebase all your local changes to this brach.
- Now we have no revisions in default branch after the last pull. So we pull changes from TFS into default branch.
- Merge the branch with local changes into default branch.
We do rebase because we need to keep track of your local changes to be able to push them to TFS correctly. So even if you developed in default branch, it would seem that you actually commited to a parallel branch and then merged your changes into default. It may not be really straightforward, but it's the best solution that I came up with for this kind of task.
Pushing changes to TFS
To push your local changes to TFS, execute the push command:
hgtfs push -repo:"C:\HgProject" -comment:"Pushing changes" -workitems:42,655
You can specify an optional comment for TFS changeset. You can also associate your changeset with some workitems. Here is how hgtfs pushes changes:
- Make sure that there are no uncommitted changes.
- Make sure that we have nothing to pull from TFS. Otherwise, perform a pull.
- Scan all the changesets from last push or pull up to current changeset.
- Provision changes to TFS.
- Check in.
Bear in mind that you can actually update to a revision which is an ancestor to the last synced revision. That way all changes will be performed backwards and will have a rollback effect. However, this feature is not tested and can have some gruesome consequences.
Source code and binaries
Feel free to post some feedback, suggestions or pull requests :)