Mercurial: Merging changes from an untracked copy.

One of our project that is tracked using Mercurial and hosted remotely on BitBucket.org presented us with a version control conundrum we had not faced. This post is dedicated to describing the problem and one particular solution of it.

The project in question is being tracked using Mercurial and has a history to it. By history, I refer to a set of change-sets (or commits, if you are a Git user not familiar with the use of change-sets to refer to commits). We had a user, let’s call them User A for the sake of clarity, who downloaded the source of the project at a particular change-set. Let’s call that change-set ‘Change-set X’. Note that User A didn’t clone the project repository and go back to Change-set X. They used the ‘get source’ convenience feature on BitBucket which makes possible downloading only the source of the project as it looks at a particular change-set or the tip of the repository (by tip, I refer to the last change-set recorded by the project). So, this User A got the source on their local system, and made some changes to the source. They unfortunately did all this without actually tracking the source under Mercurial, or any other version contorl system, for that matter.

Let’s introduce User B. User B is another contributor on the project. However, they have a clone of the repository on their local machine, and contribute changes and additions through their tracked working copy. User B committed and pushed several changes since Change-set X. All these changes were visible on the remote repository on BitBucket for the project. Now is when the problem came to be about. Both User A and User B now wanted the changes User A made to their private, untracked copy to be merged and pushed to the remote repository, so that they were available to User B. How do they go about doing it?

The biggest hurdle here was that the copy User A had wasn’t tracked under Mercurial at all. That meant that it had no history, nothing of the sort. It just wasn’t being tracked.

I thought about this. After much thinking, I came to a plan that I thought would be worth giving a go at. The plan was like this. First of all, User A had to initialize a Mercurial repository on the local copy that they had made changes to. This would create a new repository. Next, they had to push the changes upstream to the remote repository on BitBucket. However, when they did that, Mercurial aborted, and complained about the local repository being unrelated to the remote repository. Having looked around for what that really meant, I found out that Mercurial complained in that tone whenever the root or base change-set on two repositories were missing or different. This was indeed the case here. However, reading the help page for the “hg push” command, I noticed the “–force, -f” switch, with a note that said that this could be used to force push to an unrelated repository. For what it’s worth, the forced push worked, in that, it was able to push the changes on to the remote repository. It did mess the history and the change-set time-line a bit, because the change-sets from User’s A copy had a different base and parent than those that were on the tip on the remote repository. As far as User B was concerned, when they pulled the latest changes from the repository, they had two dangling heads to deal with and had to merge. The merge resulted in a lot of unresolved files with marked conflicts. Since I wasn’t familiar with the changes in the project, I didn’t pursue User B (and User A) after this point.

While wondering what could be a better way to handle this situation, I posed the question on the #mercurial IRC channel on FreeNode. User “krigstask” was kind enough to provide a solution. His solution went like this. User A would clone the remote repository on their local machine.

$ hg clone URL cloned-repo

They would then jump back to Change-set X.

$ cd cloned-repo
$ hg update -C change-setX

They would copy recursively the changes from their local, untracked copy of the repo into their working copy.

$ cp -r /path/to/local/untracked/repo/* .

They would go through the diffs to ensure their changes are there.

$ hg diff

They would commit their changes.

$ hg commit

Finally, they would merge their commit with the tip of the repo.

$ hg merge

In hindsight, this is a cleaner approach that actually works. I wondered why it didn’t cross my mind when I was thinking about a solution. Many thanks to “krigstask” for taking the time out to explain this approach to me.

I hope I was able to provide something helpful for my version control readers in particular and everyone else in general.