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"
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](https://www.mercurial-scm.org/wiki/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!