Git Alias To Migrate Commits To A Branch

github git comments edit

Show of hands if this ever happens to you. After a long day of fighting fires at work, you settle into your favorite chair to unwind and write code. Your fingers fly over the keyboard punctuating your code with semi-colons or parentheses or whatever is appropriate.

But after a few commits, it dawns on you that you’re in the wrong branch. Yeah? Me too. This happens to me all the time because I lack impulse control. You can put your hands down now.

GitHub Flow

As you may know, a key component of the GitHub Flow lightweight workflow is to do all new feature work in a branch. Fixing a bug? Create a branch! Adding a new feature? Create a branch! Need to climb a tree? Well, you get the picture.

So what happens when you run into the situation I just described? Are you stuck? Heavens no! The thing about Git is that its very design supports fixing up mistakes after the fact. It’s very forgiving in this regard. For example, a recent blog post on the GitHub blog highlights all the different ways you can undo mistakes in Git.

The Easy Case - Fixing master

This is the simple case. I made commits on master that were intended for a branch off of master. Let’s walk through this scenario step by step with some visual aids.

The following diagram shows the state of my repository before I got all itchy trigger finger on it.

Initial state

As you can see, I have two commits to the master branch. HEAD points to the tip of my current branch. You can also see a remote tracking branch named origin/master (this is a special branch that tracks the master branch on the remote server). So at this point, my local master matches the master on the server.

This is the state of my repository when I am struck by inspiration and I start to code.

First

I make one commit. Then two.

Second Commit - fixing time

Each time I make a commit, the local master branch is updated to the new commit. Uh oh! As in the scenario in the opening paragraph, I meant to create these two commits on a new branch creatively named new-branch. I better fix this up.

The first step is to create the new branch. We can create it and check it out all in one step.

git checkout -b new-branch

checkout a new branch

At this point, both the new-branch and master point to the same commit. Now I can force the master branch back to its original position.

git branch --force master origin/master

force branch master

Here’s the set of commands that I ran all together.

git checkout -b new-branch
git branch --force master origin/master

Fixing up a non-master branch

The wrong branch

This case is a bit more complicated. Here I have a branch named wrong-branch that is my current branch. But I thought I was working in the master branch. I make two commits in this branch by mistake which causes this fine mess.

A fine mess

What I want here is to migrate commits E and F to a new branch off of master. Here’s the set of commands.

Let’s walk through these steps one by one. Not to worry, as before, I create a new branch.

git checkout -b new-branch

Always a new branch

Again, just like before, I force wrong-branch to its state on the server.

git branch --force wrong-branch origin/wrong-branch

force branch

But now, I need to move the commits from the branch new-branch onto master.

git rebase --onto master wrong-branch

Note that git rebase --onto works on the current branch (HEAD). So git rebase --onto master wrong-branch is saying migrate the commits between wrong-branch and HEAD onto master.

Final result

The git rebase command is a great way to move (well, actually you replay commits, but that’s a story for another day) commits onto other branches. The handy --onto flag makes it possible to specify a range of commits to move elsewhere. Pivotal Labs has a helpful post that describes this option in more detail.

So in this case, I moved commits E and F because they are the ones since wrong-branch on the current branch, new-branch.

Here’s the set of command I ran all together.

git checkout -b new-branch
git branch --force wrong-branch origin/wrong-branch
git rebase --onto master wrong-branch

Migrate commit ranges - great for local only branches

The assumption I made in the past two examples is that I’m working with branches that I’ve pushed to a remote. When you push a branch to a remote, you can create a local “remote tracking branch” that tracks the state of the branch on the remote server using the -u option.

For example, when I pushed the wrong-branch, I ran the command git push -u origin wrong-branch which not only pushes the branch to the remote (named origin), but creates the branch named origin/wrong-branch which corresponds to the state of wrong-branch on the server.

I can use a remote tracking branch as a convenient “Save Point” that I can reset to if I accidentally make commits on the corresponding local branch. It makes it easy to find the range of commits that are only on my machine and move just those.

But I could be in the situation where I don’t have a remote branch. Or maybe the branch I started muddying up already had a local commit that I don’t want to move.

That’s fine, I can just specify a commit range. For example, if I only wanted to move the last commit on wrong-branch into a new branch, I might do this.

git checkout -b new-branch
git branch --force wrong-branch HEAD~1
git rebase --onto master wrong-branch

Alias was a fine TV show, but a better Git technique

When you see the set of commands I ran, I hope you’re thinking “Hey, that looks like a rote series of steps and you should automate that!” This is why I like you. You’re very clever and very correct!

Automating a series of git commands sounds like a job for a Git Alias! Aliases are a powerful way of automating or extending Git with your own Git commands.

In a blog post I wrote last year, GitHub Flow Like a Pro with these 13 Git aliases, I wrote about some aliases I use to support my workflow.

Well now I have one more to add to this list. I decided to call this alias, migrate. Here’s the definition for the alias. Notice that it uses git rebase --onto which we used for the second scenario I described. It turns out that this happens to work for the first scenario too.

    migrate = "!f(){ CURRENT=$(git symbolic-ref --short HEAD); git checkout -b $1 && git branch --force $CURRENT ${3-'$CURRENT@{u}'} && git rebase --onto ${2-master} $CURRENT; }; f"

There’s a lot going on here and I could probably write a whole blog post unpacking it, but for now I’ll try and focus on the usage pattern.

This alias has one required parameter, the new branch name, and two optional parameters.

parameter type Description
branch-name required Name of the new branch.
target-branch optional Defaults to “master”. The branch that the new branch is created off of.
commit-range optional The commits to migrate. Defaults to the current remote tracking branch.

This command always migrates the current branch.

If I’m on a branch and want to migrate the local only commits over to master, I can just run git migrate new-branch-name. This works whether I’m on master or some other wrong branch.

I can also migrate the commits to a branch created off of something other than master using this command: git migrate new-branch other-branch

And finally, if I want to just migrate the last commit to a new branch created off of master, I can do this.

git migrate new-branch master HEAD~1

And there you go. A nice alias that automates a set of steps to fix a common mistake. Let me know if you find it useful!

Also, I want to give a special thanks to @mhagger for his help with this post. The original draft pull request had the grace of a two-year-old neurosurgeon with a mallet. The straightforward Git commands I proposed would rewrite the working tree twice. With his proposed changes, this alias never rewrites the working tree. Like math, there’s often a more elegant solution with Git once you understand the available tools.

Comments