Main menu

Automatically backport commits using git

22 June 2012
Fri, 06/22/2012 - 03:14

For those who just want the result please scroll to the bottom, otherwise you can read the story.

I started looking into how to backport commits since I know how easily git forward ports commits and figured there had to be a better way then the tricks and manual methods recommended in documentation and various results from google. My original plan was to make a plugin that would re-roll patches that need forward porting automatically on drupal.org and if possible backport as well. Over the course of an hour and half I found a variety of snippets that got me part of the way there, developed a basic idea of how to accomplish backporting in git, and slowly refined it to built-in commands. Huge thanks to cbreak and FauxFaux in #git on freenode!

The basic idea I started from was to simply remove all the commits between the commit you want to patch on top of and the last commit in the tree. To test my approach I started by creating a simple situation representing 7.x -> 8.x using the following.

The next step was to figure out how to remove all the commits between the one to be backported and the last point at which 7.x and 8.x "last overlap" or at the point 8.x was branched (those are not necessarily the same and in Drupal's case they are not). I first envisioned dumping git rebase -i to a file, editing with script to remove commits and then rebasing. To that end it was suggested to change the GIT_EDITOR environment variable to a grep command and then use git rebase -i which would work, but the better approach is to use --onto which does exactly that.

$ git rebase --onto=A B

In simplest terms: remove all commits between A and B non-inclusively.

Now I simply needed a way to find the best point to merge onto. There are several snippets in stackexchange and elsewhere that find the "oldest ancestor" which would work, but a) are not optimal, and b) require long snippets that are not easy to understand. When I dug around in #git I was pointed to git merge-base which finds the best point for such an operation.

$ git merge-base 7.x 8.x

Finds the last common commit between the 7.x and 8.x branches. Using this we get the following.

$ git rebase --onto=`git merge-base 7.x 8.x` HEAD~1

Backport the latest commit onto the merge-base for 7.x and 8.x. Finally, we just need to forward port the patch to the end of the 7.x branch which is easy.

$ git rebase 7.x

Result

So putting it all together we get the following powerful one-liner that will backport the last commit in the 8.x branch to the 7.x branch.

$ git rebase --onto=`git merge-base 7.x 8.x` HEAD~1 && git rebase 7.x

If you want to backport something other than the last commit simply checkout a branch with that commit as the HEAD.

$ git checkout -b backport-branch commit-to-backport

This can also be extended to backport a patch straight on drupal.org by downloading and committing the patch first.

The cherry-pick command is intended to be used to grab a commit from two feature branches that are very similar. If you try to cherry-pick the "edit" commit onto a vanilla 8.x branch it will fail because the file was moved. In any instance where cherry-pick will work, you can straight up apply a patch from the issue queue to both branches. This approach handles the file structure changes that took place in 8.x versus 7.x, which is the major annoyance in our case for back-porting 8.x changes to 7.x.