26

Starting with

    hack---F1----M1----F2  (feature)
   /            /
  C1-----C2----C3  (master)

I would like to end up with

    hack---F1----M1----F2  (feature)
   /            /
  C1-----C2----C3---F1'---F2'  (master)

So far the best I have is

git checkout feature  
git checkout -b temp  
git rebase -i --onto master hack temp
   * Big drawback: manually remove the merged-in C2 and C3 from list of commits *
git checkout master  
git merge temp  
git branch -d temp  

I hope someone can answer even though this is a dubious workflow.

Grastveit
  • 15,770
  • 3
  • 27
  • 36
  • 1
    `git checkout master`, then `git cherry-pick F1` and `git cherry-pick F2` is what you want to do, here. – jub0bs Sep 17 '14 at 09:08
  • 1
    yes, in this case with two commits cherry-pick would be good, but with several I would prefer another way. Maybe [Cherry-pick range](http://stackoverflow.com/a/15688940/470022) with some sort of exclude-option? – Grastveit Sep 17 '14 at 10:36
  • I'm trying to figure out what you actually want to do in the general case... Could you formulate what you want to do as *cherrypick all the non-merge commits between `hack` (exclusive) and the tip of `feature` (inclusive) on top of `master`*? – jub0bs Sep 17 '14 at 14:04

2 Answers2

25

Simple case

If the state of your repo is

  hack---F1----M1----F2 [feature]
 /            /
C1-----C2----C3 [master]

and you want to arrive at

  hack---F1----M1----F2 [feature]
 /            /
C1-----C2----C3----F1'----F2' [HEAD=master]

you should use git cherry-pick, not git rebase -i (no need to juggle with interactive rebase, here):

git checkout master
git cherry-pick <commit-ID-of-F1> <commit-ID-of-F2>

General case

Correct me if I'm wrong, but I understand what you mean by general case as

cherry-pick, on top of master, all the non-merge commits between hack (exclusive) and the tip of feature (inclusive).

In the following, I'm assuming that is indeed what you mean.

As you've rightfully noted in your comment, the approach outlined above doesn't scale very gracefully as the number of commits to manually cherry-pick increases:

  hack---F1---F2--- .... --- F68--M1---F67---...---F99 [feature]
 /                               /
C1-------------C2---------------C3 [master]

However, you can get git rev-list to automatically generate the list of revisions of interest, using

git rev-list --reverse --no-merges --first-parent <commit-ID-of-hack>..feature

Edit: you also need the --first-parent flag to avoid collecting commits such as C1 and C2, and --reverse flag, so that commits get cherry-picked in the desired order.

You can pass the output of that command to git cherry-pick:

git checkout master
git cherry-pick `git rev-list --reverse --no-merges --first-parent <commit-ID-of-hack>..feature`

which would yield

  hack---F1---F2--- .... --- F68--M1---F67---...---F99 [feature]
 /                               /
C1-------------C2---------------C3---F1'---F2'---...---F99' [HEAD=master]
kowsky
  • 12,647
  • 2
  • 28
  • 41
jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • Thanks! That is exactly the general case. But I ran into problems. Standing at F2 the rev-list-command lists the IDs of F2, C2, C1 and F1. Even so I tried it with cherry-pick. Unfortunately it stopped after a conflict on F2. In case I overlooked some detail in the question I put [the test-repo on github](https://github.com/Grastveit/cherrypicktest/commits/feature). – Grastveit Sep 17 '14 at 20:23
  • @Grastveit You're right; there is a problem with my revision range. Let me think about it... – jub0bs Sep 17 '14 at 20:26
  • @Grastveit See my edit: if you use the `--first-parent` flag, it should work. – jub0bs Sep 17 '14 at 20:35
  • 2
    Great, that did the trick. I also added --reverse to get the rev-list back to chronological order. With the hack tagged as "hack" the command run on master became: ``git cherry-pick `git rev-list --no-merges --first-parent --reverse hack..feature` `` – Grastveit Sep 17 '14 at 21:09
  • @Grastveit You're right. I'll add that flag to my answer. – jub0bs Sep 17 '14 at 21:24
2

Looking at your original workflow, it seems like you want to ignore merges, the only problem is using -i for an interactive rebase, which preserves merges.

git checkout -b temp  
git rebase --onto master hack temp
   * Big drawback is gone!
git checkout master  
git merge temp  
git branch -d temp

Should work exactly how you want. This probably doesn't exactly solve your "general case," though.