Search code examples
gitgit-rebasegit-cherry-pick

How git cherry-pick a range of commits?


I saw that there is a connection between rebase and cherry-pick a range of commits.

I have failed to find any article/toturial which explain what exactly happands when one try to cherry-pick multiple commits.

Some questions (which I can think of) are:

  1. What CHERRY_PICK_HEAD ref is?
  2. By running git cherry-pick 2^..4, what is the sequence of actions git does and exactly between which commits git use diff?

enter image description here

  1. By running git cherry-pick 1..8, what git will do?

enter image description here


Solution

  • Cherry picking n commits is the same as cherry picking one of them after the other (with distinct git calls). No branching or whatever else is involved, simply new commits created for you on the current branch.

    More details

    The help page https://www.git-scm.com/docs/git-cherry-pick.html says:

    Given one or more existing commits, apply the change each one introduces, recording a new commit for each.

    Let's pick that statement apart:

    the change

    This is what sometimes is also called "the diff". It is what git diff HEAD somecommit would output. Sometimes it is also called a "patch" (in fact, that same output from git diff could be applied with the usual patch utility - and of course with git apply but that's not the point here).

    So, the "change" is something that instructs a tool like git or patch how to modify a text file to end up with a new, changed text file.

    You could create a similar textual representation of the differences between two files by running the standard diff utility on two files. In fact, that is what git does with a cherry-pick, internally (with its own diff implementation, of course); i.e. this is only a 2-way diff, not a 3-way diff like in the git merge operation.

    each one introduces

    When you have this state:

    ...----+----+----...
       abc  def          
    

    Then the git cherry-pick def change is the 2-way diff between commits abc and def (for all the files which differ, of course, on a file-by-file basis), because this is what def "introduces".

    apply the change

    This means to take HEAD and "the change" (i.e., the diff, the patch, etc.) and create a new set of text files. You can treat this, in principle as a 2-way merge (just like the patch utility would do) except when it isn't, i.e. if the context information in the diff output does not match what's in HEAD right now. In that case, git cheats to find a common ancestor to be able to do a 3-way merge, and you can read up the gory details in What are the three files in a 3-way merge for interactive rebasing using git and meld? . But it is still, from the user's point of view, not really comparable to a git merge insofar as structurally it will end up with a single-parent commit, not a 2-parent commit like git merge.

    recording a new commit

    git applies the changes to the index and to the working directory, and commits. Unless the 2-way merge and the 3-way merge did not work out without a conflict, in which case, straight from the help page with some comments of mine:

    1. The current branch and HEAD pointer stay at the last commit successfully made. [I.e., just plain simple HEAD.]
    2. The CHERRY_PICK_HEAD ref is set to point at the commit that introduced the change that is difficult to apply. [That is def in my picture above.]
    3. Paths in which the change applied cleanly are updated both in the index file and in your working tree. [I.e., if many files are changed in the commit, those that can cleanly be applied, are.]
    4. For conflicting paths, the index file records up to three versions, as described in the "TRUE MERGE" section of git-merge[1]. The working tree files will include a description of the conflict bracketed by the usual conflict markers <<<<<<< and >>>>>>>. [I.e., the same as a merge conflict, with some "conjured out of thin air" common ancestor.]
    5. No other modifications are made.

    And finally, the rest of the sentence:

    Given one or more existing commits, apply ... recording a new commit for each.

    If you give it more than one commits, maybe explicitly as in git cherry-pick sha1 sha2 sha3... or implicitly git cherry-pick sha1..sha2, then the above just runs in a simple loop, stopping after the last pick, or when there occurs a merge conflict.

    Your Questions

    What CHERRY_PICK_HEAD ref is?

    2. The CHERRY_PICK_HEAD ref is set to point at the commit that introduced the change that is difficult to apply.
    

    If it tries to pick commit def, and a merge conflict occurs, then CHERRY_PICK_HEAD will point at def.

    By running git cherry-pick 2^..4, what is the sequence of actions git does and exactly between which commits git use diff?

    As described above:

    1. Commit 2 is picked, i.e.
      • Git calculates the diff between 1 and 2.
      • If possible, that diff is applied as a 2-way merge to HEAD and committed.
      • If not possible, that diff is applied as a 3-way merge to HEAD and committed.
      • If that is not possible (i.e., merge conflict), then you are left to resolve the conflict manually as usual, and it will wait for you issuing git cherry-pick --continue.
    2. Commit 3 is picked, i.e. ... the same.
    3. Commit 4 is picked, i.e. ... the same.

    By running git cherry-pick 1..8, what git will do?

    The same, but this time it will pick commit 2, 3, 4, 8.

    (The fact that the "first" commit in the range is not picked is the usual behaviour, for example git log 2^..4 or git log 1..8 would output the same commits - in fact the same that would be picked. This is described in the cherry-pick help page under <commits> including links to how git walks revisions, for all the details. This is not a property of git cherry-pick but of how these .. ranges work.)