59

Suppose this is my git history

  Z
 /
A -- C -- D
 \  /      
  B

My HEAD is currently at Z. I want to cherry-pick B and C. If my understanding is correct, I should do this:

git cherry-pick B
git cherry-pick C -m 1
git commit --allow-empty

It worked in my case because C is a no-op (hence the empty commit afterwards, I needed the commit for other reasons), but I am wondering what the parameter after -m does. Here is what I read from the docs:

-m parent-number

--mainline parent-number

Usually you cannot cherry-pick a merge because you do not know which side of the merge should be considered the mainline. This option specifies the parent number (starting from 1) of the mainline and allows cherry-pick to replay the change relative to the specified parent.

In my case, C has two parents but how do I know which one is 1 and 2, and more importantly when does it matter when I pick 1 or 2?

Community
  • 1
  • 1
rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156
  • 2
    This seems to be mostly a duplicate of http://stackoverflow.com/questions/9229301/git-cherry-pick-says-38c74d-is-a-merge-but-no-m-option-was-given/ ... or is your question really about whether you can determine the parent number from this graph drawing? (answer: no), or: is there some determinism about 1st vs 2nd parent when merging? (answer: yes, not enough room in comment for more). – torek Oct 20 '16 at 08:50
  • What do you want your resulting history to look like? Do you want `C'` to have parents `Z` and `B'`? Why do you need to cherry-pick the `C` if you already cherry-picked `B`? – Scott Weldon Oct 20 '16 at 18:35
  • 1
    @ScottWeldon I want my resulting history on that branch to be `A -- Z -- B' -- C'` (if it's easier for `C'` to have multiple parents that works too). I need to cherry-pick that commit for the author information despite it being "empty" – rink.attendant.6 Oct 21 '16 at 00:14
  • 2
    @torek I actually read that question prior to posting this one and the answer states that the merge commit represents multiple different diffs and has multiple parents with an example of `-m 1`, but doesn't really expand on *what the difference is*. Expanding on your second question and perhaps showing the differences between 1st vs 2nd parent would be really helpful I think. – rink.attendant.6 Oct 21 '16 at 00:19

2 Answers2

63

My understanding based off this answer is that parent 1 is the branch being merged into, and parent 2 is the branch being merged from. So in your case, parent 1 is A, and parent 2 is B. Since a cherry-pick is really applying the diff between two commits, you use -m 1 to apply only the changes from B (because the diff between A and C contains the changes from B). In your case, it probably doesn't matter, since you have no commits between A and C.

So yes, -m 1 is what you want, and that is true even if there were extra commits between A and C.

If you want to make the new history look a little more like the original history, there's another way to do this:

git cherry-pick B
git checkout Z
git merge --no-ff --no-commit B
git commit --author="Some Dev <someone@example.com>" --date="<commit C author date>"

(If needed, you can create a new branch for B before cherry-picking.)

This will retain the author information, should give you a history that looks like this:

    B'
   /  \
  Z -- C'
 /
A -- C -- D
 \  /      
  B
Scott Weldon
  • 9,673
  • 6
  • 48
  • 67
  • 7
    This is correct: the *first parent* of any normal merge is the branch you are on at the time you make the merge, so that `-m 1` always gives "the changes brought in via the merge". There's no way to tell just from a graph drawing though, since this information gets lost in the two-dimensional-ization process. (That is, we can *draw* the graph however we like: the graph topology has no parent ordering. Parent ordering is something Git added. We'd need the ability to stick little counting numbers on each outbound arc from each vertex, to represent Git's parent-number concept.) – torek Oct 21 '16 at 01:10
  • 1
    @torek Good point about the graph. I generally assume that when it's drawn as a triangle (rather than a diamond as in your answer), the author is indicating which branch was merged into which. – Scott Weldon Oct 21 '16 at 16:38
  • Note that the parent order also appears to be the order displayed on the `Merge:` line with `git show `. – boweeb Apr 08 '19 at 18:25
2

I've upvoted Scott Weldon's answer, which is correct, but I just want to add an attempt at ASCII art that includes parent numbering. Given a graph that looks like this:

       B
      / \
...--A   D--...
      \ /
       C

we can tell that node D is a merge commit, but we cannot tell whether B or C is the first parent of D. One of the two is necessarily the first parent, and the other is the second. So if we need to know, we must label the drawing, which takes more room. Here is one such attempt.

         B
       /   \²
...--A       D--...
       \   /¹
         C

We now see that, for some reason,1 I have drawn the graph "upside down": that commit C is in fact the first parent of D, while commit B is the second parent.

It's possible to create arbitrary merges using lower level ("plumbing") commands. In particular, git commit-tree just takes however many -p arguments you wish to give it, in the order you give them, and makes a new commit with the given commits as its parents. Give it one -p and it makes an ordinary commit with one parent. Give it no -p arguments and it makes a root commit. Give it 155 distinct -p arguments (all must of course resolve to valid commit IDs) and it makes one massive octopus merge commit.

The git merge command, however, always makes its new commit with the first parent being the current HEAD (hence the current branch, if on a branch). The second parent, for a standard two-parent merge, comes from .git/MERGE_HEAD, into which git merge writes the other commit ID. If the merge is conflicted, or the final merge commit is delayed with --no-commit, this MERGE_HEAD file is in fact the only place that commit ID is available.

(When git merge makes an octopus merge, using the -s octopus strategy—this strategy is forced on for such merges—and multiple additional parents, it aborts and leaves no trace at all if there are merge conflicts, so the conflict case never occurs. I have not tried combining --no-commit with an octopus merge, but that would, logically, leave the 2nd through N'th parents in MERGE_HEAD, if Git allows this at all.)


1Obstinacy.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • So if I wanted to know what numbers belonged to which parents, would I use `git cat-file -p D-sha1-hash` and the first parent in the file is 1 and the next is 2 and ...? – Brandon Feb 12 '21 at 18:10
  • 1
    @Brandon: That certainly will work (and will show you exactly how commits are encoded, which is kind of nice too). Git does preserve the parent order in other output as well though, so `git rev-parse ^@` prints them in order as well (and is more suitable for script-writing, if that's what you're doing). – torek Feb 12 '21 at 18:14
  • 1
    There's also the hat-and-digits suffix method, for parsing them one at a time: `git rev-parse ^1`, `git rev-parse ^2`, and so on. The numbers here are in decimal so if you have a 66-parent [Cthulhu merge](https://www.destroyallsoftware.com/blog/2017/the-biggest-and-weirdest-commits-in-linux-kernel-git-history), you'd use `^9` and then `^10` and `^11` and so on all the way up to `^66`. – torek Feb 12 '21 at 18:18