Git Cherry Picking: Move small code patches across branches
I just performed my first 'git cherry-pick'. And it was great. A cherry-pick, in Git parlance, is an operation which moves a specific glob of changed code from one branch of the VCS to another. Not sure what that means? Read on for an explanation and an example. Git is a distributed version control system, and I use it to manage files on my local workstation as well as several of my Open Source projects hosted at GitHub. Git makes working with branches of code trivially easy -- far more so than CVS and Subversion. In fact, it is so easy to work with branches that often times I create a new branch just to test out a feature or two. When I wrote my last performance test blog, I was simply testing against two different Git branches -- one with the performance optimizations and one without.
After verifying that the performance improvements were having a positive impact, I simply merged that branch back into the mainline. You can see this visualized at QueryPath's GitHub. Just look for newiterator.
When Branches Diverge
But what happens when branches diverge substantially? With the latest Quasar build of QueryPath, much of the library has been re-factored to take advantage of PHP 5.3's features. Meanwhile, the 2.x branch continues to be the home of the stable PHP 5.2 version of the library. These two branches were plodding along independently when along came two issues that had an impact on both branches.
I quickly fixed both issues in the quasar branch, and then realized that I needed to do basically the same thing in the master branch. But there is a little problem: I've refactored the new version so that the changes aren't even in the same files anymore. Quasar's files are organized differently. Oh, and I didn't want any of the several hundred other changes to be copied. Just the fixes for those two issues.
I tried my old CVS-style patching procedure, but that failed miserably (just like it usually does in CVS). I was going to just do the whole thing by hand, but then I ran across a nice little article at GitReady that explained picking out individual commits and pushing them across branches.
And that's how I learned about something GREATER than sliced bread: Cherry picking. I can move one commit -- just one! -- from one branch to another without doing anything else. No sophisticated merges. No careful patching. Just a single-time clone of one change set from branch to branch.
And what's better, since Git has the entire history, it can apparently determine how the chunks of code have been reorganized during my protracted refactoring. With no work on my part, it correctly took the hunk from a part of a new file in a new location and moved it to the correct place in the old branch.
If I didn't know better, I would have thought it was magic.
Using Git Cherry-Pick
How was this elaborate magic accomplished? I began on the quasar branch, and I grabbed the log to find out the ID of the desired checkin:
$ git checkout quasar $ git log -1 commit e458a9b309acb5d937b360aa3140631dc7af0b1e Author: M Butcher <...@aleph-null.tv> Date: Fri Dec 4 17:30:19 2009 -0600 Bug fixes for :contains() pseudo-class. Strip backslashes from processed content, strip quotes from :contains() contents.
That's the commit record I needed, so I copied the first few characters of the commit ID: .
From there, I had the information I needed to execute the cherry pick. I simply switched back to the other branch and ran the appropriate command:
$ git checkout master $ git cherry-pick 5d3e1b6 Finished one cherry-pick. [master e458a9b] Bug fixes for :contains() pseudo-class. 3 files changed, 36 insertions(+), 3 deletions(-)
That was it! A quick scan of the source code confirmed that all of the changes from the patch had been correctly applied to both the original source file and the appropriate unit tests. And a quick run of the unit tests confirmed that the changes were in place and were working.
Once again, I find myself impressed with Git's capabilities. The learning curve for Git is steep -- perhaps the steepest of any VCS I've used. But the reason should be clear: It's not a hammer; it's a hardware store.