Moving Unpushed Changes to a New Branch with Mercurial

So you’ve been studiously committing your changes early and often only to discover that, for whatever reason, you really wished you’d been committing your changes to a different branch. Now what do you do? Is there a way we can just move unpushed changes onto a new branch? Actually, Yes!

1. Make a Backup

First of all, in order to do this we’re going to need to use the mercurial rebase extension to essentially re-write (unpushed) commit history. Rebasing is one of the few mercurial commands that can irreversibly destroy changes. Fortunately, we can easily create a convenient backup by just creating a local clone of the repository.

Make sure you don’t have any uncommitted local changes, and then do:

hg clone path/to/original path/to/backup/

Now that we have a good backup, lets get started.

2. Create the Branch

First we need to create the new branch. For the rebase, we’re going to need the branch to point to the parent of the oldest commit you want to move. I’m going to call the branch your commits are currently on original_branch and the branch you are wanting to move them to new_branch.

To find this commit do:

hg outgoing -b original_branch -l 1

Which will print out info on your oldest outgoing (unpushed) commit. Make note of the commit hash, and then do:

hg parent -r _commit_hash_of_oldest_outgoing_commit_on_original_branch_

To find that commit’s parent. Make note of that hash as well.

Now we can make our branch by doing:

    hg update _commit_hash_of_parent_of_oldest_outgoing_commit_
    hg branch new_branch
    hg commit -m "I made a new branch"

3. Rebase!

Now we want to take all the changes that are descendants of that commit and move them to the new branch. We’re going to need the commit hash of the oldest unpushed commit again.

hg rebase -s _commit_hash_of_oldest_outgoing_commit_on_original_branch_ -d new_branch

And that’s it!

If you don’t get much output, then it probably worked! :D

Since you’re only moving commits, there should be no merge conflicts. If you had merge conflicts you probably did something wrong.

4. Check it Twice

Make sure that all the commits you wanted moved made it to your new branch and that none got left on your original branch. You could also do a regular diff between what you have in on your original_branch in your backup clone, and what’s in your newly rebased new_branch. If everything went well, they should match modulo untracked files.

Also, once you’re sure things worked out okay, make sure you get rid of your backup or put it in a place where you won’t actually confuse it with your original. It now contains a different history from your original.

Oops, I messed it all up. Now what do I do?

What happens if something goes wrong? If your rebase hasn’t finished yet, you can do a:

hg rebase --abort

to undo the rebase.

If the rebase finished, but you don’t like the results, you’re going to have switch to the backup you just made and try again. Make sure you make a backup of your backup first though :)

There is one catch. Your local clone will have the “phase” of all those outgoing commits marked as “public” since it knows at least one other repository (the one we just cloned) knows about them (see Phases). The rebase command won’t let rewrite “public” history so we’re going to have to fix that.

First copy the .hg/hgrc file from your original working directory into the .hg/ directory in your clone. Otherwise your clone will think that your original working directory is the remote repository (since that’s where you cloned it from). It also avoids a lot of other little gotcha’s.
Then all you should need to do is:

hg phase -fd 'outgoing()'

Which will un-publicify all your outgoing changes so you can try again. Good luck!

  • Greg says:

    Thanks, great tutorial!

  • Martin says:

    “Rebasing … can irreversibly destroy changes”? It certainly cannot – whenever you rebase, Mercurial creates a backup bundle in .hg/strip-backup.
    When you want to restore, simply look for the latest backup bundle in that directory and type “hg unbundle “.
    Done hundreds of times, never failed on me. Still if you don’t trust, you may use hg rebase’s “–keep” option, which keeps the old commits instead of deleting them.
    No need to backup the whole repository just for rebasing, which might take very long …

  • David EI says:

    What about if I have already pushed some of the commits to our remote hg server? Can I still rebase the changesets locally to a new branch then push the new head and branch?

  • Comments are closed.