几周前,我的搭档“乔恩”(Jon)开设了一个新分支,从事新功能。同时,团队的其余成员在许多新功能中进行了大量工作,我们创建了新分支并将其合并,并且始终将工作合并到branch-update
分支中。但是“ Jon”尚未完成,他从未从我们更新的工作中更新过他的分支。现在,与“乔恩”合并的时刻到了。问题是,当我尝试与乔恩合并时,他的工作优先于我的工作。
我有branch-update
和branch-jon
,如果HEAD指向branch-update
并且我说git merge branch-jon
,则结果是branch-jon优先于许多已经存在的文件更新,乔恩从未触及他的分支。如果HEAD指向branch-jon
,而我说git merge branch-update
,则结果仍然相同。
所以,我的问题是,是否有一种方法可以通知git branch-update
优先于branch-jon
,并且合并应该只为我带来jon更新的工作?
答案 0 :(得分:5)
在合并中没有“优先级”之类的东西。好吧,可能是 ,但是您为其分配了;您一会儿就会明白我的意思。
合并是关于合并的工作。但是,要合并工作,无论您是谁,无论他们是谁,都必须有一个共同的起点。由于Git是关于 commits 的全部内容-文件只是嵌入在commits中,因此,这个共同的起点是 shared commit 。
例如,尝试使用git log --graph --decorate --online branch-update branch-jon
。您将看到的可能很复杂;我要画一些简单的东西。 Git用最近的提交到顶部,下面的提交来绘制图表。出于空间原因,我倾向于水平绘制图形。
请记住,每个提交都是所有文件的完整快照,并在时间上冻结(不可变),并包含其前任或 parent 提交的哈希ID。这意味着我们(或Git)从最近提交(由某个分支名称标识)开始,可以向后进行操作,一次提交一次:
I--J <-- branch-update (HEAD)
/
...--F--G--H
\
K--L <-- branch-jon
在这个(非常简化,以至于不切实际的简单点)示例中,您和Jon自共同起点以来分别进行了两次提交。实际的共同出发点是提交H
(H
实际上是一些丑陋的哈希ID,git log --oneline
将以缩写形式打印)。通过从您的最新提交J
开始,然后向后依次到I
和H
,并从Jon的最新提交L
开始,并向后工作到K
,然后H
,Git将自动 找到该最佳共享的常见提交。 Git将此最佳共享提交称为合并操作的合并基础。 (请注意,提交G
和F
等也被共享;它们不是 best 。)
(Git可能将此图形绘制为:
* abcdef0 (HEAD -> branch-update) your commit subject
* c9f1233 your previous commit subject
| * 5bf3149 (branch-jon) his commit subject
| * 78983fc his previous commit subject
|/
* 371820d shared commit subject line
[snip]
尽管方向发生了变化,但这两张图代表的是同一张图。)
您的实际图形将更加混乱。合并基础在哪里可能不清楚。您可以让Git告诉您哪个提交是(或可能但不太可能是提交)合并基础:
git merge-base --all branch-update branch-jon
如果这会产生多个合并基数,则情况会稍微复杂一些,但是通常您只会看到一个提交哈希ID作为输出。
无论如何,在确定了合并基础后,就像我的图形中那样称其为H
,Git现在必须将合并基础H
中的快照与进行比较最新快照J
,以了解您所做的更改。它还必须将H
中的快照与其他最新快照进行比较,以查看Jon所做的更改。因此它运行两个git diff
命令:
git diff --find-renames hash-of-H hash-of-J
:您所做的更改git diff --find-renames hash-of-H hash-of-L
:乔恩做了什么现在,Git实际上合并了更改。它从共同的起点开始,从提交H
中提取所有文件。对于这些文件,它将应用您的更改。它还适用于乔恩的更改。在某些情况下,应用很容易:例如,如果您更改了文件F1
和F2
,而Jon没有触摸这些文件,则结果为您的 {{1} }和您的 F1
。如果乔恩(Jon)碰过F2
而您没有碰,那么结果就是他的F3
。 F3
中的许多其他文件在H
和J
中可能完全相同,因此您甚至无法确定Git是否使用L
的{{1} }或H
。但是对于一些文件,例如J
,两者,您和 Jon进行了一些更改。
这是L
真正必须努力的地方。在所有其他情况下,F4
仅仅从git merge
,git merge
或H
中提取了一些文件。对于这些文件,您都都接触过,Git确实必须结合更改。
如果您更改了J
的第10至15行,则Git将对这些行进行您的更改。如果Jon将第20行更改为第25行,则Git将 his 更改为这些行。不需要 作为优先级:Git只需进行 更改。
如果您都是将第30行更改为第35行怎么办?好吧,如果您都对这些行进行了相同更改,则Git只会获取这些更改的一个副本,并将该一个副本应用于L
中的文件。
但是,如果你们俩都将第30行更改为第35行,并且和进行了不同的更改,那么现在就出现了问题。 这是不存在的优先级不存在的地方。默认情况下,Git仅声明合并冲突,并放弃完成合并。这让您一团糟:您的工作树中有一个文件,其中包含冲突的一组更改周围的冲突标记。 Git还会在索引中留下所有三个输入文件。修复Git机器的麻烦,这就是您的工作。
这也是您可以选择的“优先级”出现的地方。如果您使用标准的F4
合并策略,则可以使用H
或{ {1}}自变量-我称为扩展自变量,尽管Git将其称为策略自变量-告诉Git:在发生冲突的情况下,请选择我的更改(recursive
或他的更改(-X ours
)。
大多数人在想到-X theirs
时首先犯的错误是认为只有两个输入并且正在运行:
-X ours
告诉您将会发生什么。那是完全错误的!每次合并有三个输入:当前(-X theirs
)提交及其文件,您提供的另一个提交(git merge
)和合并基础< / em>。 Git从提交图本身找到第三输入(在重要意义上实际上是第一输入)。该图在这里非常重要。该图确定了合并基础,与两个分支技巧相比,合并基础确定了合并的结果。
合并过程本身(Git到达将要进入新合并的内容的路径)是对称的。哪个git diff branch-update branch-jon
首先运行并不重要。但是,当您说HEAD
或branch-jon
时,就破坏了对称性:发生冲突时,您选择一侧或另一侧为准。
最后,当Git从合并的文件进行提交时,它也是不对称的。新提交的 first 父级基于git diff
:无论连接到哪个分支-X ours
,该分支的尖端提交都是新合并提交的第一父级。新提交的 second 父级是另一个提交。当然,新提交的哈希ID通过-X theirs
进入当前分支,因此当我们获得新的合并提交HEAD
时, current 分支就是获取它:
HEAD
请注意,此合并提交及其两个父级的存在会在以后的合并中更改图结构。现在,HEAD
和M
之间最好的共享公用提交是提交 I--J
/ \
...--F--G--H M <-- branch-update (HEAD)
\ /
K--L <-- branch-jon
:
branch-update
如果您现在运行branch-jon
,Git将比较L
与 I--J
/ \
...--F--G--H M--N--O <-- branch-update (HEAD)
\ /
K--L--------P <-- branch-jon
来找到我们所做的更改,以及git merge branch-jon
与L
来查找它们的更改