Automatically backport commits using git

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.

$ git checkout -b test 8.x
$ git mv core/CHANGELOG.txt core/CHANGELOG2.txt
$ git commit -m "move"
 
$ echo "hello world" > core/CHANGELOG2.txt
$ git commit -am "edit"

I then proceeded to remove the 2nd to last commit ("move") to see if the last commit ("edit") would be updated to edit core/CHANGELOG.txt instead of core/CHANGELOG2.txt.

$ git rebase -i HEAD ~3 # commented out the "move" commit and saved
$ git show

To my delight the "edit" commit was indeed updated.

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.

$ your facorite download method (wget, curl, etc)
$ git apply some.patch
$ git commit -am "patch to backport" 
$ git rebase --onto=`git merge-base 7.x 8.x` HEAD~1 && git rebase 7.x
$ git show > some.patch

You can of course use a fancier git format-patch and what not, but this gets the point across. Enjoy!

Tags:

Comments

In there any reason why git-cherry-pick can't be used to backport commits from some other branches in drupal?

> In there any reason why git-cherry-pick can't be used to backport commits from some other branches in drupal?

I agree for simple things git-cherry-pick is much easier and faster approach. I am use it all the time to move individual commits from one branch to another.

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.

Sweet! Thanks for the write-up.

Add new comment

Filtered HTML

  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Type the characters you see in this picture. (verify using audio)
Type the characters you see in the picture above; if you can't read them, submit the form and a new image will be generated. Not case sensitive.