Rebasing against a feature branch with Git cherry-pick
I’ve been working on a project recently where we occasionally deal with some pretty gnarly rebases. I recently learned how to recover from them much more easily.
This is our setup1, which is quite common:
- The master branch runs in production
- The team contributes to a very active and long-lived feature branch.
- We branch off the feature branch and merge contributions back into it.
Anytime there’s an update to master, we have to rebase the feature branch against it. (We rebase on Wednesdays in order not to fall too far behind.) That means any personal working branch checked out before the rebase winds up with a ton of extraneous commits and merge conflicts when making a pull request on GitHub:
This is a screenshot of a real pull request. I’ve obscured my colleagues to protect the innocent.
Sometimes I’ve slogged through and reconciled the conflicts, but it took a long time and felt like a waste of effort. Other times I’ve just given up, deleted my local working branch, recreated the work on a fresh branch, and kept going.
A few weeks ago, my colleague Tiffany Tse taught me a technique that sidesteps this mess with git cherry-pick
. Note that this really only works if you pushed up your local working branch to remote (in my case, GitHub).
The method
Here’s how it works:
- Check out the rebased feature branch and do a pull so it’s up to date and includes all the rebase changes:
$ git checkout feature/branch
Switched to branch 'feature/branch'
$ git pull
Already up to date.
- Delete your local working branch that’s been caught in the rebase:
$ git branch -D my-working-branch
Deleted branch my-working-branch (was 478fe9e).
- Checkout a new branch with the same name as the old one:
$ git checkout -b my-working-branch
Switched to a new branch 'my-working-branch'
- This is where the magic happens. On GitHub2 find the commit(s) you need to rescue from rebase hell. In this example, it’s this one. Use its hash to
git cherry-pick
just that commit for your fresh branch:
$ git cherry-pick 478fe9e68a71fd706149f95e0719cdd9bdc4197d
[my-working-branch 77d97fc] adding a feature
Date: Sat Jan 11 13:39:23 2020 -0500
1 file changed, 3 insertions(+)
- Finally, force-push the new branch to remote. Force-pushing wipes out all the rebased junk and re-applies the smaller batch of changes you were making:
$ git push origin +my-working-branch
(The plus-sign +branchname
shortcut was also new to me. It’s functionally equivalent to -f
.)
In just a few days this has definitely saved me time and hassle, and it was something I’d never seen in many months of periodically searching for how to make this process less painful. Maybe it’ll save you some time too.
Try it out on a toy repo
To help understand the problem and this particular solution, I created a GitHub repo that recreates the problem:
master
is up to date.feature/branch
was two commits ahead of master, but has been rebased against master.my-working-branch
is one commit ahead of the non-rebased feature branch. A pull request against the feature branch produces merge conflicts.
Feel free to clone the repo to sandbox the problem and test the git cherry-pick
approach to rebasing.
Notes
1. Some argue this whole scheme is a bad idea; I take the point but we deal with the world as it is.
2. ...or whatever service you use, of course. You could also comb through git reflog
if that’s your thing.