Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
158 views
in Technique[技术] by (71.8m points)

What is the dominant branch when doing a git merge

Given my current branch is Branch A

Branch A contains:

line 1 -- "i love you Foo"

Branch B contains:

line 1 -- "i love you Bar"

if i do a:

git merge Branch B 

what would i get?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...