The modifier "dominant" is not defined by Git. The way you use the word appears to me to make an incorrect assumption, which I think makes the question un-answerable as is. With one small change, though, the answer becomes simple: it's neither. No branch is "dominant" here; both branches are equal partners, and the result of the merge is the same, whether you merge A into B, or B into A—but you can change this, in several ways.
There are quite a few useful points underneath, which this question exposes, so let's explore them. At the end, we'll see how to properly phrase the question, and what the possible answers are.
Reminder: Git stores snapshots with parent linkage
Each commit stores a complete copy of all the files in that commit, intact (albeit compressed). Some other version control systems start with an initial snapshot, then, for each commit, store a set of changes since a previous commit or changes since a previous snapshot. These other VCSes can therefore show you the changes easily (since that's what they have), but have a hard time getting the actual files (because they have to assemble lots of changes). Git takes the opposite approach, storing the files each time, and computing the changes only when you ask for them.
This doesn't make much difference in terms of usage, since given two snapshots, we can find a change, and given one snapshot and one change, we can apply the change to get a new snapshot. But it does matter somewhat, and I refer to these snapshots below. For (much) more on this, see How does git store files? and Are Git's pack files deltas rather than snapshots?
Meanwhile, each commit, in Git, also records a parent commit. These parent linkages form a backwards chain of commits, which we need since the commit IDs seem quite random:
4e93cf3 <- 2abedd2 <- 1f0c91a <- 3431a0f
The fourth commit "points back" to the third, which points back to the second, which points back to the first. (The first commit has no parent, of course, because it's the first commit.) This is how Git finds previous commits, given the latest or tip commits. The word tip does appear in the Git glossary, but only under the definition of branch.
The goal of a merge
The goal of any merge is to combine work. In Git, as in any other modern version control system, we can have different authors working on different branches, or "we" can be one person (one author, the royal "we" :-) ) working on different branches. In those various branches, we—whether "we" means one person or many—can make different changes to our files, with different intents and different outcomes.
Eventually, though, we decide we'd like to combine some of these in some way. Combining these different sets of changes, to achieve some particular result and—at least normally—record the fact that we did combine them, is a merge. In Git, this verb version of merge—to merge several branches—is done with git merge
, and the outcome is a merge, the noun form of merge.1 The noun can become an adjective: a merge commit is any commit with two or more parents. This is all defined properly in the Git glossary.
Each parent of a merge commit is a previous head (see below). The first such parent is the head that was HEAD
(see below as well). This makes the first parent of merge commits special, and is why git log
and git rev-list
have a --first-parent
option: this allows you to look at just the "main" branch, into which all "side" branches are merged. For this to work as desired, it's crucial that all merges (verb form) be performed carefully and with proper intent, which requires that none of them be performed via git pull
.
(This is one of several reasons that people new to Git should avoid the git pull
command. The importance, or lack thereof, of this --first-parent
property depends on how you are going to use Git. But if you are new to Git, you probably don't know yet how you are going to use Git, so you don't know whether this property will be important to you. Using git pull
casually screws it up, so you should avoid git pull
.)
1Confusingly, git merge
can also implement the action verb, but produce an ordinary, non-merge commit, using --squash
. The --squash
option actually suppresses the commit itself, but so does --no-commit
. In either case it's the eventual git commit
you run that makes the commit, and this is a merge commit unless you used --squash
when you ran git merge
. Why --squash
implies --no-commit
, when you can in fact run git merge --squash --no-commit
if you wanted it to skip the automatic commit step, is a bit of a mystery.
Git merge strategies
The git merge
documentation notes that there are five built-in strategies, named resolve
, recursive
, octopus
, ours
, and subtree
. I will note here that subtree
is just a minor tweak to recursive
, so perhaps it might be better to claim just four strategies. Moreover, resolve
and recursive
are actually pretty similar, in that recursive is simply a recursive variant of resolve
, which gets us down to three.
All three strategies work with what Git calls heads. Git does define the word head:
A named reference to the commit at the tip of a branch.
but the way Git uses this with git merge
does not quite match this definition either. In particular, you can run git merge 1234567
to merge commit 1234567
, even if it has no named reference. It is simply treated as if it were the tip of a branch. This works because the word branch itself is rather weakly defined in Git (see What exactly do we mean by "branch"?): in effect, Git creates an anonymous branch, so that you have an un-named reference to the commit that is the tip of this unnamed branch.
One head is always HEAD
The name HEAD
—which can also be spelled @
—is reserved in Git, and it always refers to the current commit (there is always a current commit).2 Your HEAD
may be either detached (pointing to a specific commit) or attached (containing the name of a branch, with the branch name in turn naming the specific commit that is therefore the current commit).
For all merge strategies, HEAD
is one of the heads to be merged.
The octopus
strategy is truly a bit different, but when it comes to resolving merge-able items, it works a lot like resolve
except that it cannot tolerate conflicts. That allows it to avoid stopping with a merge conflict in the first place, which thus allows it to resolve more than two heads. Except for its intolerance of conflicts and ability to resolve three or more heads, you can think of it as a regular resolve merge, which we'll get to in a moment.
The ours
strategy is wholly different: it completely ignores all other heads. There are never any merge conflicts because there are no other inputs: the result of the merge, the snapshot in the new HEAD
, is the same as whatever was in the previous HEAD
. This, too, allows this strategy to resolve more than two heads—and gives us a way to define "dominant head" or "dominant branch", as well, although now the definition is not particularly useful. For the ours
strategy, the "dominant branch" is the current branch—but the goal of an ours
merge is to record, in history, that there was a merge, without actually taking any of the work from the other heads. That is, this kind of merge is trivial: the verb form of "to merge" does nothing at all, and then the resulting noun form of "a merge" is a new commit whose first parent has the same snapshot, with the remaining parents recording the other heads.
2There is one exception to this rule, when you are on what Git calls variously an "unborn branch" or an "orphan branch". The example most people encounter most often is the state a newly created repository has: you are on branch master
, but there are no commits at all. The name HEAD
still exists, but the branch name master
does not exist yet, as there is no commit it can point-to. Git resolves this sticky situation by creating the branch name as soon as you create the first commit.
You can get yourself into it again at any time using git checkout --orphan
to create a new branch that does not actually exist yet. The details are beyond the scope of this answer.
How resolve/recursive merge works
The remaining (non-ours
) kinds of merge are the ones we usually think of when we talk about merging. Here, we really are combining changes. We have our changes, on our branch; and they have their changes, on their branch. But since Git stores snapshots, first we have to find the changes. What, precisely, are the changes?
The only way Git can produce a list of our changes and a list of their changes is to first find a common starting point. It must find a commit—a snapshot—that we both had and both used. This requires looking through the history, which Git reconstructs by looking at the parents. As Git walks back through the history of HEAD
—our work—and of the other head, it eventually finds a merge base: a commit we both started from.3 These are often visually obvious (depending on how carefully we draw the commit graph):
o--o--o <-- HEAD (ours)
/
...--o--*
o--o <-- theirs
Here, the merge base is commit *
: we both started from that commit, and we made three commits and they made two.
Since Git stores snapshots, it finds the changes by running, in essence, git diff base HEAD
and git diff base theirs
, with base
being the ID of the merge base commit *
. Git then combines these changes.
3The merge base is technically defined as the Lowest Common Ancestor, or LCA, of the Directed Acyclic Graph or DAG, form