First, a quick comment regarding cherry-pick: cherry-pick and merge are very different (more on that in a bit).
Merging a specific commit
Now, on to merge. Typically we describe the merge sequence like this:
$ git checkout master
$ git merge sidebranch
If the automatic merge goes well, git will create a new merge commit on the current branch—in this case, on master
—whose everyday "first" parent is the previous tip of the current branch, and whose second parent is the commit we told git to merge.
The fact that there is a second parent is what makes this a merge commit. Diagrammatically we get:
A <----------- M <-- master
\ /
B <- C <- D <-- sidebranch
where M
has as its two parents A
and D
. The name master
now points to the new merge commit M
, while the name sidebranch
is untouched and still points to D
.
What git merge
does with the sidebranch
argument is two things, only one of which really matters:
- it translates it to a specific commit and merges using that commit; and
- it puts the argument name into the default commit message (
merge branch sidebranch
or merge branch sidebranch into foo
where into foo
appears only if the current branch is not named master
).
The second item does not really matter, at least not to git: git only shows you that commit message, it never depends on the message. If you choose to edit the message you can make it say something else.
The point of all this is that you can identify any commit, not just the tip of a branch, and git can merge that. So if sidebranch
keeps going, as in your case, but you specify commit D
specifically, you get:
A <----------- M <-- master
\ /
B <- C <- D <- E <- F <-- sidebranch
which is what you want. Git's auto-generated merge commit message text, however, says merge commit <hash>
, which is probably not what you want. That's easy to fix: just edit it to read merge branch sidebranch
.
(To accomplish the merge itself, git does the usual three-way merge, finding the merge base and diffing that commit against the two commits that are to be merged.)
The rest of the side note on cherry-pick
The key difference between merge and cherry-pick is not so much the merging itself (you can cherry-pick a range of commits, and even do so without committing each one by adding -n
to the cherry-pick command: the result is what git calls a "squash merge", except much less efficient and you might have to resolve the same conflicts many times this way). Rather, it's that the final commit is not a merge commit, i.e., does not have a second parent.
This means that the resulting commit graph is different, even if the source code changes are the same (as they are for a squash "merge"). This has no immediate effect, but it means a future merge won't know that the cherry-pick already happened, and will have to work hard to skip it (this may or may not succeed). Recording the second parent gives git all the information it needs to locate the correct new merge base for the later merge.
As an example, consider the squash-"merge" you get by copying the effect of B+C+D
onto master
:
A <-------- S <-- master
\
B <- C <- D <- E <- F <-- sidebranch
Here S
is produced by taking "what we did in B
" (by diffing A
vs B
) and applying that to the source tree for A
, then taking "what we did in C
" (diff B
vs C
) and applying that, and last, taking "what we did in D
" and applying that. Everything is fine so far—what we did in B
clearly must apply cleanly to A
, and so on.
The problem occurs when (or maybe "if") we later go to merge sidebranch
into master
again. Git will search for the merge-base and find that it is commit A
, so it will compare A
vs S
, and A
vs F
. It may then be able to automatically construct the right tree, depending on the changes in E
and F
vs the changes we cherry-picked earlier. If we had merged, though, git would see D
as the merge base, and compare D
-vs-F
to get new items to bring into master
.