进行git合并时,支配分支是什么? [英] What is the dominant branch when doing a git merge

查看:137
本文介绍了进行git合并时,支配分支是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

鉴于我目前的分行是分行A



分行A包含:

 第1行 - 我爱你Foo

分行B包含:

 第1行 - 我爱你吧

如果我做了:

  git合并分支B 

我会得到什么?

解决方案

修饰语主导不是由Git定义的。你用这个词的方式在我看来做出了一个不正确的假设,我认为这个问题是不负责任的。然而,只要做一点小小的改变,答案就会变得简单:既不是 。没有分支在这里是主导的;两个分支都是平等的合作伙伴,并且合并的结果是相同的,无论您将A合并为B还是将B合并为A - 但您可以通过多种方式更改此项。



下面有很多有用的观点,这个问题暴露出来,所以我们来探讨一下。最后,我们将看到如何正确地描述问题以及可能的答案。

提醒:Git使用父链接存储快照



每次提交都会保存该提交中所有文件的完整副本,完整(尽管是压缩的)。一些其他版本控制系统以初始快照开始,然后对于每次提交,自上次提交自上一次快照以来更改以后存储一组更改。因此,这些其他VCS可以很容易地向您显示更改(因为这就是他们所拥有的),但很难获取实际文件(因为他们必须组装大量更改)。 Git采取相反的方法,每次都存储文件,并且只在你请求时才计算更改。



这在使用方面没有太大的区别,由于给定两个快照,我们可以找到一个更改,并给出一个快照和一个更改,我们可以应用更改以获取新快照。但是它有些重要,我参考下面的这些快照。有关(更多)更多信息,请参阅 git存储文件? Git的包文件增量而不是快照?



同时,Git中的每个提交也记录<父母提交。这些父链接组成了一个后向提交链,我们需要这样做,因为提交ID看起来非常随机:

  4e93cf3 < -  2abedd2 <  -  1f0c91a<  -  3431a0f 

第四个提交点回到第三个,回到第二个,回到第一个。 (当然,第一次提交没有父项,因为它是第一次提交。)这就是Git如何查找先前的提交,给定最新或提示 em>提交。 这个词 tip 确实出现在 Git词汇表,但仅限于分支



合并目标



任何合并的目标都是合并工作。在Git中,与其他任何现代版本控制系统一样,我们可以让不同的作者在不同的分支上工作,或者我们可以成为一个人(一个作者,皇家我们:-))在不同的分支上工作。在这些不同的分支机构中,我们 - 无论我们是指一个人还是多个 - 都可以对我们的文件做出不同的改变,但意图不同,结果也不一样。最后,尽管,我们决定以某种方式将其中的一些结合起来。结合这些不同的变化集合,以获得某些特定的结果,并且至少通常记录我们将它们组合在一起的事实是合并。在Git中,这种动词版本的merge-to merge几个分支 - 用 git merge 完成,结果是合并,名词形式合并。 1 名词可以变成形容词:合并提交是任何提交两个或多个父母。这一切都在 Git词汇表中正确定义。

合并提交的每个父代都是前一个(见下文)。 first 这样的父项是 HEAD 的头(也见下文)。这使合并的第一个父提交特别,这就是为什么 git log git rev-list 有一个 - first-parent 选项:这允许您查看所有side分支合并到的main分支。 为了达到这个目的,所有所有合并(动词形式)都必须小心谨慎地执行,并且要有合适的意图,这要求通过 none 执行 git pull



(这是Git新手应该避免的几个原因之一 git pull 命令。 - 第一父母属性的重要性或缺乏取决于您的方式如果你是Git的新手,你可能还不知道你将如何使用Git,所以你不知道这个属性对你是否重要。使用> git pull 随便把它拧紧,所以你应该避免 git pull )






1 令人困惑的是, git merge 也可以实现动作动词,但使用 - squash 产生一个普通的非合并提交。 - squash 选项实际上禁止了提交本身,但 - no-commit 也是如此。无论哪种情况,最终的 git commit 你都会执行提交,而这个合并提交,除非你使用 --squash 当你运行 git merge 时。为什么 - squash 意味着 - no-commit ,当你实际上可以运行 git合并--squash --no-commit 如果你想让它跳过自动提交步骤,有点神秘。






Git合并策略



git merge 文档注意到五种内置策略 ,名为 resolve 递归, octopus 我们的子树。我会在这里注意到子树只是对递归的小调整,所以也许最好声明只有四个策略。此外, resolve 递归实际上非常相似,因为递归只是一个递归变体 resolve ,这会让我们减少到三个。



这三种策略都可以用Git调用的方式工作。 Git确实定义了这个词:
$ b


一个分支顶端的提交的命名引用。




,但Git在 git merge 中使用它的方式也不完全符合此定义。特别是,你可以运行 git merge 1234567 来合并commit 1234567 ,即使它没有命名引用。它被简单地视为,就好像它是分支的尖端。这是有效的,因为在Git中分支本身是非常弱的定义(请参阅我们的意思是什么分支?):实际上,Git会创建一个匿名分支,以便您有一个名为 un 的提交的引用,它是此未命名分支的提示。



一个头总是 HEAD



名称 HEAD - 它也可以拼写为 @ - 在Git中保留,它始终指向当前提交(总是有一个当前提交) 2 您的 HEAD 可以是分离的(指向特定的提交)或附加的(包含名称对于所有的合并策略, HEAD <分支名称,分支名称依次命名特定的提交,因此它是当前的提交)。 $ b

/ code>是要合并的头之一。



octopus 策略真的有点儿differen t,但是在解决可合并项目时,它的工作原理与 resolve 很相似,只是它不能容忍冲突。这样可以避免因合并冲突而停下来,因此可以解决两个以上的头部问题。除了不容忍冲突和解决三个或更多头的能力之外,您可以将其视为常规解决方案合并,我们稍后会解决。



我们的策略完全不同:它完全忽略了所有其他的头像。从来没有任何合并冲突,因为没有其他输入:合并结果,新 HEAD 中的快照与前一个 HEAD 。这也使得这一策略能够解决两个以上的负责人问题,并为我们提供了一种定义显性负责人或主要负责人的方法,尽管现在这个定义并不是特别有用。对于我们的策略,主导分支是当前分支 - 但是我们合并的目标是在历史上记录了合并,而实际上并没有从其他主管那里获得任何工作。也就是说,这种合并是微不足道的:合并的动词形式根本不做任何事情,然后由此产生的合并的名词形式是一个新的提交,其第一个父代具有相同的快照,其余的父代记录其他人的头像。




2 这个规则有一个例外,当你在Git所谓的未出生的分支或孤儿分支上。大多数人经常遇到的例子是新创建的存储库具有的状态:您位于分支 master 上,但根本没有提交。名称 HEAD 仍然存在,但分支名称 master 尚不存在,因为没有可以指向的承诺。 Git通过创建第一次提交来创建分支名称来解决这种棘手的情况。



您可以随时再次使用 git checkout --orphan 来创建一个实际上还不存在的新分支。详细信息超出了本答案的范围。






合并解析/递归如何工作



其余的(非我们的)类型的合并是我们在谈论合并时通常会想到的。在这里,我们确实在组合更改。我们在我们的分支上有我们的改变;他们在他们的分支上有他们的改变。但是,由于Git存储快照,首先我们必须找到所做的更改。什么,确切地说是变化



Git可以生成我们所做的更改列表及其更改列表的唯一方法是首先找到一个共同的起点。它必须找到一个提交 - 快照 - 我们都有并且都使用了。这需要查看历史,Git通过查看父母来重构。当Git回顾 HEAD - 我们的工作 - 和另一个头的历史时,它最终找到了一个 merge base :一个提交,我们都从这里开始。 3 这些通常在视觉上很明显(取决于我们仔细画出提交图表):

 <$ c $ (我们的)
/
...-- o - *
\
o - o< o ; - 他们的

这里,合并基础是commit * :我们都是从那个提交开始的,我们做了三次提交,他们做了两次。



由于Git存储快照,它发现 / em>通过运行,本质上, git diff base HEAD git diff base theirs ,其中 base 是合并基础提交的标识 * 。 Git然后结合了这些变化。






3 合并基础在技术上定义为最低公共祖先或定向非循环图或DAG的LCA,由提交将每个提交链接到它的父代的单向弧构成。提交是图中的顶点/节点。 LCA在树上很容易,但DAG比树更普遍,所以有时候没有单一的LCA。这是递归合并不同于解析合并:解析通过基本上任意选择这些最好的祖先节点之一来工作,而递归挑选其中的所有,合并它们以形成一种假装 - commit:一个虚拟合并基。细节超出了这个答案的范围,但我已经在别处展示过它们。






对于解析/递归,现在我们终于得到了你的问题的答案:




假设我目前的分支是Branch A

分支A包含[in file file.txt ]:

 第1行 - 我爱你Foo

B分部包含:

 第1行 - 我爱你吧 

如果我做了:

  git merge BranchB 

我会得到什么?


为了回答这个问题,我们需要更多的信息:什么在合并基础中?在 BranchA c $ c>?两个分支中的不是什么,而是每个人都改变了

假设我们找到了合并基地的标识符, 4 和它(以某种方式) ba5eba5 。然后我们运行:

  git diff ba5eba5 HEAD 

找出我们发生了什么变化,以及:

  git diff ba5eba5 BranchB 

来了解它们 的变化。 (或者,类似地,我们使用 git show ba5eba5:file.txt 并查看第1行,尽管只是执行了两个 git diff
显然,至少我们中的一个人改变了 ,否则第1行在两个文件中都是相同的。如果我们改变了第1行,而他们没有,则合并结果是我们的版本。



如果我们没有改变第1行,和他们做了,则合并结果是他们的版本。 b
$ b

如果我们两者都改变​​了第1行,则合并结果是合并失败,合并冲突。 Git将两行写入文件并停止合并,并使得我们清理混乱:

 自动合并file.txt 
CONFLICT(内容):在file.txt中合并冲突

使用默认样式,我们看到:

  $ cat file.txt 
我爱你Foo
=======
我爱你Bar
>>>>>>> BranchB

如果我们将 merge.conflictStyle 设置为 diff3 git config merge.conflictStyle diff3 git -c merge.conflictStyle = diff3 merge BranchB 或类似的), 5 Git不仅写我们的两行,而且还写了原来的内容:

  $ cat file.txt 
<<<<<<<<<<<<<<<<<< HEAD
我爱你Foo
|||||||合并共同祖先
原始行1
=======
我爱你Bar
>>>>>>> BranchB

请注意,顺便说一下,Git 不会查看任何中间提交。它只是简单地将合并基础与两个头部进行比较,并使用两个 git diff 命令。




4 这假设有一个合并基础。通常情况下,除了其中一些脚注外,我不想进入虚拟合并基地。我们可以用 git merge-base --all HEAD BranchB 找到所有的合并基地,它通常只打印一个提交ID;这是 (单个)合并基础。

5 我用这个 diff3 code> style,将其设置在我的 - global Git配置中,因为我发现它在解决冲突时很好,看看合并基础中的内容。我不喜欢必须找到合并基础并检查它;而对于一个真正的递归合并,当Git构建了一个虚拟合并库时,没有什么要检出。无可否认,当虚拟合并库时,这会变得非常复杂,因为虚拟合并库中可能存在合并冲突!查看 Git - diff3冲突风格 - 临时合并分支作为示例。






您如何设定优势



让我们定义显性头处理合并冲突或潜在合并冲突的目的,因为自动更改其优先级的版本。在递归和解析策略中,有一种简单的方法来设置它。



当然,Git给我们 - 我们的 code> merge strategy ,它甚至可以消除潜在的合并冲突。但是如果我们没有改变line 1 并且他们做了,那么它会使用我们的第1行,所以这不是我们想要的。如果只有我们或只有他们改变了我们,我们希望将他们的他们的第1行取出;我们只是希望Git在我们 改变它的情况下更喜欢我们的头部或者他们的头部。



这些是 -X我们 -X他们 strategy-option 我们仍然像以前一样使用递归或解析策略,但是我们使用 -X我们的运行来告诉Git,在如果发生冲突,它应该更喜欢我们的更改。或者,我们使用 -X他们来运行以选择他们的更改。无论哪种情况,Git 不会停止冲突:它会采用首选(或主导)更改并按下。



这些选项有点危险,因为他们依赖Git获取正确的背景。 Git对我们的代码或文本文件一无所知:它只是逐行比较,试图找到一组最少的指令:在这里删除这一行,在那里添加一行,并且这会将你从基础承诺中的版本转换为其中一个版本。无可否认,不优先/占主导地位的头也是如此,但在这些情况下,我们不会采取他们的任何变化,然后用我们的另一个他们 ,这是我们可能遇到麻烦的地方,尤其是在我使用的文件类型中。



无论如何,如果您运行常规 git merge without 其中一个 -X 参数,您将照常发生冲突。然后,您可以在任何一个文件上运行 git checkout --ours 来挑选文件的我们的版本(根本不存在冲突,忽略<所有的变化)或 git checkout - 他们的来挑选他们的 版本的文件(再用no冲突并忽略所有我们的更改)或 git checkout -m 来重新创建合并冲突。如果有一个针对 git merge-file 的面向用户的包装会从索引 8 中提取所有三个版本并让您使用 - 我们的 - 他们的表现得像 -X我们 -X他们 仅用于那个文件(这是这些标志的含义,在 git merge-文件)。请注意,这也应该让你使用 - union 。请参阅 git merge -file 文档来获得这个功能的描述。像 - 我们的 - 他们的这有点危险,应该小心使用和观察;例如,当合并XML文件时,它并不真正起作用。






6 名称我认为,战略选择是一个不好的选择,因为它听起来就像 -s 策略 参数,但实际上完全不同。我使用的助记符是 -s 需要一个策略,而 -X 需要一个 eXtended 策略选项,传递给我们已经选择的任何策略。

7 您可以通过 .gitattributes 或选项到 git diff ,但我不确定前者如何影响内置合并策略,因为

8 在冲突合并期间,每个冲突文件的全部三个版本都是在索引中,尽管 git checkout 对它们中的两个只有特殊的语法。您可以使用 gitrevisions 语法来提取基本版本。


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?

解决方案

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, formed by the commits' one-way arcs linking each commit to its parent(s). The commits are the vertices / nodes in the graph. LCAs are easy in trees, but DAGs are more general than trees, so sometimes there is no single LCA. This is where recursive merge differs from resolve merge: resolve works by picking one of these "best" ancestor nodes essentially arbitrarily, while recursive picks all of them, merging them to form a sort of pretend-commit: a virtual merge base. The details are beyond the scope of this answer, but I have shown them elsewhere.


For resolve/recursive, there is no dominant branch by default

Now we finally get to the answer to your question:

Given my current branch is Branch A

Branch A contains [in file file.txt]:

line 1 -- "i love you Foo"

Branch B contains:

line 1 -- "i love you Bar"

if i do a:

git merge BranchB 

what would i get?

To answer this, we need one more piece of information: What's in the merge base? What did you change on BranchA, and what did they change on BranchB? Not what's in the two branches, but rather, what did each of you change since the base?

Let's suppose we find the ID of the merge base,4 and it's (somehow) ba5eba5. We then run:

git diff ba5eba5 HEAD

to find out what we changed, and:

git diff ba5eba5 BranchB

to find out what they changed. (Or, similarly, we use git show ba5eba5:file.txt and look at line 1, although just doing the two git diffs is easier.) Obviously at least one of us changed something, otherwise line 1 would be the same in both files.

If we changed line 1 and they didn't, the merge result is our version.

If we didn't change line 1, and they did, the merge result is their version.

If we both changed line 1, the merge result is that the merge fails, with a merge conflict. Git writes both lines into the file and stops the merge with an error, and makes us clean up the mess:

Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt

With the default style, we see:

$ cat file.txt
<<<<<<< HEAD
i love you Foo
=======
i love you Bar
>>>>>>> BranchB

If we set merge.conflictStyle to diff3 (git config merge.conflictStyle diff3 or git -c merge.conflictStyle=diff3 merge BranchB or similar),5 Git writes not only our two lines, but also what was there originally:

$ cat file.txt
<<<<<<< HEAD
i love you Foo
||||||| merged common ancestors
original line 1
=======
i love you Bar
>>>>>>> BranchB

Note, by the way, that Git doesn't look at any of the intermediate commits. It simply compares the merge base to the two heads, with two git diff commands.


4This presupposes that there is a single merge base. That's usually the case, and I don't want to get into virtual merge bases here except in some of these footnotes. We can find all the merge bases with git merge-base --all HEAD BranchB, and it usually prints just one commit ID; that's the (single) merge base.

5I use this diff3 style, setting it in my --global Git configuration, because I find it good, when resolving conflicts, to see what was in the merge base. I don't like having to find the merge base and check it out; and for a truly recursive merge, when Git constructed a virtual merge base, there's nothing to check out. Admittedly, when there is a virtual merge base, this can get quite complicated, as there can be merge conflicts in the virtual merge base! See Git - diff3 Conflict Style - Temporary merge branch for an example of this.


How you can set dominance

Let's define dominant head, for the purpose of handling merge conflicts or potential merge conflicts, as the version whose changes are preferred automatically. There is an easy way, in the recursive and resolve strategies, to set this.

Of course, Git gives us the -s ours merge strategy, which eliminates even the potential of merge conflicts. But if we didn't change line 1 and they did, this uses our line 1 anyway, so that's not what we want. We want to take either our or their line 1 if only we or only they changed it; we just want Git to prefer our head, or their head, for line 1 in the case where we both changed it.

These are the -X ours and -X theirs strategy-option arguments.6 We still use the recursive or resolve strategy, just as before, but we run with -X ours to tell Git that, in the case of a conflict, it should prefer our change. Or, we run with -X theirs to prefer their change. In either case, Git doesn't stop with a conflict: it takes the preferred (or dominant) change and presses on.

These options are somewhat dangerous, because they depend on Git getting the context right. Git knows nothing about our code or text files: it's just doing a line-by-line diff,7 trying to find a minimal set of instructions: "Delete this line here, add that one there, and that will take you from the version in the base commit to the version in one of the heads." Admittedly, this is true of "no preferred / dominant head" merges as well, but in those cases, we don't take one of their changes, and then override another nearby "their" change with ours, which is where we likely to hit trouble, especially in the kinds of files I work with.

In any case, if you run a regular git merge without one of these -X arguments, you'll get a conflict as usual. You can then run git checkout --ours on any one file to pick out our version of the file (with no conflicts at all, ignoring all of their changes), or git checkout --theirs to pick out their version of the file (again with no conflicts and ignoring all of our changes), or git checkout -m to re-create the merge conflicts. It would be nice if there were a user-oriented wrapper for git merge-file that would extract all three versions from the index8 and let you use --ours or --theirs to act like -X ours or -X theirs just for that one file (this is what those flags mean, in git merge-file). Note that this should also let you use --union. See the git merge-file documentation for a description of what this does. Like --ours and --theirs it's a bit dangerous and should be used with care and observation; it doesn't really work when merging XML files, for instance.


6The name "strategy option" is, I think, a bad choice, since it sounds just like the -s strategy argument, but is actually entirely different. The mnemonic I use is that -s takes a Strategy, while -X takes an eXtended strategy option, passed on to whatever strategy we already chose.

7You can control the diff through .gitattributes or options to git diff, but I'm not sure how the former affects the built in merge strategies, as I have not actually tried it.

8During a conflicted merge, all three versions of each conflicted file are there in the index, even though git checkout only has special syntax for two of them. You can use gitrevisions syntax to extract the base version.

这篇关于进行git合并时,支配分支是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆