TL;DR
I think you just want git merge -s ours
.
Description
Cherry picking some existing commit makes a new commit that does not link back to the original commit (the new commit has a single parent; this parent is the commit that was HEAD
when you ran git cherry-pick
). The drawing you made shows commit #2 with two parents, as if your cherry-pick was a merge. Hence I'm not sure I understand the situation itself correctly.
The phrase empty commit is one that Git itself uses, but I find very misleading: commits do not normally have an empty tree object (see Is git's semi-secret empty tree object reliable, and why is there not a symbolic name for it?); what's empty about a commit made with git commit --allow-empty
is the difference between that commit and its parent.
There are several ways to make such a commit, obviously including git commit --allow-empty
, but that first method won't make a merge commit. The only user-facing command that makes merge commits is git merge
. This leaves you with the problem of "merging without merging", perhaps, if that phrase even means anything. Sometimes it does: git merge -s ours
.
(Note that since a merge commit has two parents, using the phrase "empty commit" to talk about this merge makes even less sense than it did when talking about a single-parent commit, because there are two diffs to make against the merge commit's two parents. It's likely that if one is empty, the other is not.)
When you run git merge
, you may supply a merge strategy name. Git has four built in strategies, named recursive
, resolve
, octopus
, and ours
. The normal one that people encounter during normal merges is recursive
. The resolve
strategy is a simplified variant of -s recursive
that is almost the same thing (the two differ only in the relatively rare case of a merge with multiple merge bases). The octopus
strategy is the one Git uses when you make a true merge with more than two parents, which we won't discuss at all here. That leaves -s ours
. Before we get to it, let's take a brief moment to review the normal (recursive/resolve) method:
- You check out some particular branch:
git checkout mainline
.
- You run
git merge sidebranch
.
At this point, Git computes a merge base between the tip commit of mainline
—the commit to which the branch name points, which I will call L
for left/local/--ours
—and the tip commit of sidebranch
, which I will call R
for right/remote/--theirs
:
...--o--*--o--L <-- mainline
\
o----R <-- sidebranch
The merge base is a point where the two branches join up: here, it's commit *
. Git then, in essence, computes two git diff
s:
git diff --find-renames <hash-of-base> <hash-of-L> # what we did
git diff --find-renames <hash-of-base> <hash-of-R> # what they did
Git then tries to combine the two sets of changes, making those combined changes apply to the contents of the merge base *
, and if all goes well, Git makes a new merge commit M
, with L
as its first parent and R
as its second parent:
...--o--*--o--L--M <-- mainline
\ /
o----R <-- sidebranch
Comparing (diffing) L
vs M
will show the changes that this merge obtained from *
-vs-R
that were not already in *
-vs-L
. That is, we picked up their work. Comparing R
vs M
will show the changes that this merge obtained from *
-vs-L
that were not already in *
-vs-R
. That is, we also kept our work.
The -s ours
strategy
What -s ours
does is quite simple. Instead of bothering with all the diff
-ing, Git just keeps the tree from L
. Git makes the new merge commit with L
as its first parent and R
as its second parent, so the graph looks exactly the same:
...--o--*--o--L--M <-- mainline
\ /
o----R <-- sidebranch
But now if we run git diff mainline^1 mainline
to compare L
and M
, the output is empty. If we run git diff sidebranch mainline
to compare R
and M
, they can be completely different. Git has completely ignored the contents of commit R
: it has made M
using the contents of L
.
If you call an empty diff an "empty commit", then L
vs M
is just such an empty commit. But there's still R
vs M
as part of this merge commit, so as we noted above, the phrase "empty commit" does not really make sense here.
A long aside
The whole thing would make much more sense if there were an existing merge commit from the old tvfc branch to the new branches:
...--o--M--o--<arbitrarily complex work>--I <-- integration
/
...--o--* <-- tvfc
which over time becomes:
...--o--M--o--<arbitrarily complex work>--I <-- integration
/
...--o--*--o--<arbitrarily complex work>-----X <-- tvfc
In this case, any and all new commits added to tvfc
itself can simply be merged to integration
with git checkout integration; git merge tvfc
as the merge base of integration
and tvfc
is now commit *
. Git will diff *
vs X
to see what they changed, and combine this with *
vs I
to see what you changed. After combining these changes, Git will make a new merge commit N
and the new merge base in the future will be X
itself:
...--o--M--o--<arbitrarily complex work>--I--N <-- integration
/ /
...--o--*--o--<arbitrarily complex work>-----X <-- tvfc
but that seems not to be what you are doing. I wonder a bit here if you are simply setting up this situation now. If so, the git merge -s ours
command above is probably what you want.