2010-07-20

Cherry-picking a range of git commits

The cherry-pick command in git allows you to copy commits from one branch to another, one commit at a time. In order to copy more than one commit at once, you need a different approach.

Cherry-picking a single commit

Say we have the following repository composed of three branches (master, feature1 and stable):

$ git tree --all
* d9484311 (HEAD, master) Delete test file
* 4d4a0da8 Add a test file
| * 5753515c (stable) Add a license
| * 4b95278e Add readme file
|/
| * a37658bd (feature1) Add fourth file
| * a7785c10 Add lines to 3rd file
| * 7f545188 Add third file
| * 2bca593b Add line to second file
| * 0c13e436 Add second file
|/
* d3199755 Add a line
* b58d925c Initial commit

The "git tree" command is an alias I defined in my ~/.gitconfig:

[alias]
tree = log --oneline --decorate --graph

To copy the license file (commit 5753515c) to the master branch then we simply need to run:

$ git checkout master
$ git cherry-pick 5753515c
Finished one cherry-pick.
[master 08ff7d4] Add a license
1 files changed, 676 insertions(+), 0 deletions(-)
create mode 100644 COPYING

and the repository now looks like this:

$ git tree --all
* 08ff7d4a4 (HEAD, master) Add a license
* d94843113 Delete test file
* 4d4a0da88 Add a test file
| * 5753515c (stable) Add a license
| * 4b95278e Add readme file
|/
| * a37658bd (feature1) Add fourth file
| * a7785c10 Add lines to 3rd file
| * 7f545188 Add third file
| * 2bca593b Add line to second file
| * 0c13e436 Add second file
|/
* d3199755 Add a line
* b58d925c Initial commit

Cherry-picking a range of commits

In order to only take the third file (commits a7785c10 and 7f545188) from the feature1 branch and add it to the stable branch, I could cherry-pick each commit separately, but there is a faster way if you need to cherry-pick a large range of commits.

First of all, let's create a new branch which ends on the last commit we want to cherry-pick:

$ git branch tempbranch a7785c10
$ git tree --all
* 08ff7d4a (HEAD, master) Add a license
* d9484311 Delete test file
* 4d4a0da8 Add a test file
| * 5753515c (stable) Add a license
| * 4b95278e Add readme file
|/
| * a37658bd (feature1) Add fourth file
| * a7785c10 (tempbranch) Add lines to 3rd file
| * 7f545188 Add third file
| * 2bca593b Add line to second file
| * 0c13e436 Add second file
|/
* d3199755 Add a line
* b58d925c Initial commit

Now we'll rebase that temporary branch on top of the stable branch:

$ git rebase --onto stable 7f545188^ tempbranch
First, rewinding head to replay your work on top of it...
Applying: Add third file
Applying: Add lines to 3rd file
$ git tree --all
* ec488677 (HEAD, tempbranch) Add lines to 3rd file
* a85e5281 Add third file
* 5753515c (stable) Add a license
* 4b95278e Add readme file
| * 08ff7d4a (master) Add a license
| * d9484311 Delete test file
| * 4d4a0da8 Add a test file
|/
| * a37658bd (feature1) Add fourth file
| * a7785c10 Add lines to 3rd file
| * 7f545188 Add third file
| * 2bca593b Add line to second file
| * 0c13e436 Add second file
|/
* d3199755 Add a line
* b58d925c Initial commit

All that's left to do is to make stable point to the top commit of tempbranch and delete the old branch:

$ git checkout stable
Switched to branch 'stable'
$ git reset --hard tempbranch
HEAD is now at ec48867 Add lines to 3rd file
$ git tree --all
* ec488677 (HEAD, tempbranch, stable) Add lines to 3rd file
* a85e5281 Add third file
* 5753515c Add a license
* 4b95278e Add readme file
| * 08ff7d4a (master) Add a license
| * d9484311 Delete test file
| * 4d4a0da8 Add a test file
|/
| * a37658bd (feature1) Add fourth file
| * a7785c10 Add lines to 3rd file
| * 7f545188 Add third file
| * 2bca593b Add line to second file
| * 0c13e436 Add second file
|/
* d3199755 Add a line
* b58d925c Initial commit
$ git branch -d tempbranch
Deleted branch tempbranch (was ec48867).

It would be nice to be able to do it without having to use a temporary branch, but it still beats cherry-picking everything manually.

Another approach

Another way to achieve this is to use the format-patch command to output patches for the commits you are interested in copying to another branch and then using the am command to apply them all to the target branch:

$ git format-patch 7f545188^..a7785c10
0001-Add-third-file.patch
0002-Add-lines-to-3rd-file.patch
$ git am *.patch

Update: looking forward to git 1.7.2

According to a few people who were nice to point this out in a comment, version 1.7.2 of git, which is going to be released soon, will have support for this in cherry-pick:

git cherry-pick 7f545188^..a7785c10

5 comments:

rhonda said...

You have a mistake in your alias that got me confused at first: It's not "--pretty=oneline" but it's "--oneline" instead (or "--pretty=oneline --abbrev-commit"). Otherwise you'll get the full commit IDs which are rather unhandy.

Thanks for this helpful article, and even more for the useful alias. :)

Enjoy!

Simon said...

Are you looking for the "-i" option?

Björn Steinbrink said...

And with git v1.7.2 you'll be able to use:

git cherry-pick 7f545188^..a7785c10

:-)

Philipp Kern said...

Now Simon already talked about interactive rebase, but another point: there's no need to rebase stable --hardly to the other branch. In fact, as the target was rebased a "git merge" will be a fast-forward, so can be done without generating a merge commit.

Motiejus Jakštys said...

For any older git:

git rev-list ^f545188 master --reverse | git cherry-pick --stdin