Mainline parent number when cherry picking merge commits

40,671

Solution 1

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 <[email protected]>" --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

Solution 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.

Share:
40,671

Related videos on Youtube

rink.attendant.6
Author by

rink.attendant.6

Software engineer in Ottawa. I'm most familiar with web technologies, particularly HTML, CSS, JavaScript/TypeScript, PHP, and SQL. As a member of the Stack Exchange network, I strive to learn something new every day. #SOreadytohelp I currently work as a security analyst at the University of Ottawa on the IT Services and Infrastructure team, specializing in web application security. In the IT field, I've also worked for: University of Ottawa, as a Programmer Analyst on the Student Life Solutions team under IT Solutions Esri Inc., as a Software Engineer Intern on the Esri Maps for Cognos team Canadian Cyber Incident Response Centre (Public Safety Canada), as a Web Application Developer (CO-OP job) Statistics Canada, as a Systems Tester (CO-OP job) Kenora District Services Board, as a Web Application Developer (officially EMS Resource Assistant)

Updated on October 21, 2020

Comments

  • rink.attendant.6
    rink.attendant.6 over 3 years

    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?

    • torek
      torek over 7 years
      This seems to be mostly a duplicate of stackoverflow.com/questions/9229301/… ... 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).
    • Scott Weldon
      Scott Weldon over 7 years
      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?
    • rink.attendant.6
      rink.attendant.6 over 7 years
      @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
      rink.attendant.6 over 7 years
      @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.
  • torek
    torek over 7 years
    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.)
  • Scott Weldon
    Scott Weldon over 7 years
    @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.
  • boweeb
    boweeb about 5 years
    Note that the parent order also appears to be the order displayed on the Merge: line with git show <commit>.
  • Brandon
    Brandon about 3 years
    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 ...?
  • torek
    torek about 3 years
    @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 <hash>^@ prints them in order as well (and is more suitable for script-writing, if that's what you're doing).
  • torek
    torek about 3 years
    There's also the hat-and-digits suffix method, for parsing them one at a time: git rev-parse <hash>^1, git rev-parse <hash>^2, and so on. The numbers here are in decimal so if you have a 66-parent Cthulhu merge, you'd use ^9 and then ^10 and ^11 and so on all the way up to ^66.