这是多年来多次被问过的问题。我找到了很多答案,特别是这个答案:
Git - how to force merge conflict and manual merge on selected file(@Dan Molding)
此页面包含详细指南,介绍如何设置始终返回失败的合并驱动程序,从而实现手动合并。我试图为Windows修改该解决方案:
我在%homepath%\.gitconfig
添加了以下内容:
[merge "verify"]
name = merge and verify driver
driver = %homepath%\\merge-and-verify-driver.bat %A %O %B
我将驱动程序更改为
cmd /K "echo Working > merge.log & git merge-file %1% %2% %3% & exit 1"
(添加echo Working > merge.log
以检查是否已调用驱动程序。)
并在repo的根目录下创建了一个文件.gitattributes
,其中包含以下行:
*.txt merge=verify
不幸的是,它不起作用。我试图合并一个文件feature.txt
,唉,合并成功完成。似乎根本没有调用驱动程序,因为没有创建merge.log文件。
我做错了吗?任何强制手动合并问题的解决方案都是最受欢迎的。
答案 0 :(得分:1)
问题分为两部分。相对容易的是编写自定义合并驱动程序,正如您在步骤1和步骤2中所做的那样。困难的是,如果在Git&#39中,Git实际上并不打扰运行自定义驱动程序的意见,没有必要。这是您在步骤3中观察到的内容。
那么,当 时,Git运行你的合并驱动程序?答案相当复杂,为了实现这一目标,我们必须定义术语 merge base ,我们将在稍后介绍。您还需要知道Git识别文件 - 事实上,几乎所有内容:提交,文件,补丁等等 - 通过哈希ID 。如果你已经知道所有这些,你可以直接跳到最后一节。
哈希ID(有时是对象ID 或OID)是您在提交时看到的那些丑陋的名字:
$ git rev-parse HEAD
7f453578c70960158569e63d90374eee06104adc
$ git log
commit 7f453578c70960158569e63d90374eee06104adc
Author: ...
所有Git商店都有一个唯一的哈希ID,根据对象的内容(文件或提交或其他)计算。
如果将相同的文件存储两次(或更多),则会获得两次(或更多)相同的哈希ID。由于每次提交最终都会存储截至提交时每个文件的快照,因此每个提交都有一个每个文件的副本,由其哈希ID列出。你实际上可以看到这些:
$ git ls-tree HEAD
100644 blob b22d69ec6378de44eacb9be8b61fdc59c4651453 README
100644 blob b92abd58c398714eb74cbe66671c7c3d5c030e2e integer.txt
100644 blob 27dfc5306fbd27883ca227f08f06ee037cdcb9e2 lorem.txt
中间的三个丑陋的ID是三个哈希ID。这三个文件位于这些ID下的HEAD
提交中。我在几个提交中有相同的三个文件,通常内容略有不同。
DAG 或 D 竖立 A 循环 G raph,是一种绘制关系的方法提交。要真正正确地使用Git,您至少需要对DAG的含义有一个模糊的概念。它也被称为提交图,这在某些方面是一个更好的术语,因为它避免了专门的信息学术语。
在Git中,当我们制作分支时,我们可以用各种方式绘制它们。我喜欢在这里使用的方法(在文本中,在StackOverflow上)是在左边提交早期提交,在右边提交稍后提交,并用单个大写字母标记每个提交。理想情况下,我们按照Git保留它们的方式绘制这些,这是相反的:
A <- B <- C <-- master
这里我们只有三次提交,都在master
上。分支名称 master
&#34;指向&#34;三个提交中的最后一个。这就是Git实际上通过从分支名称C
读取其哈希ID来实现提交master
的方式,实际上名称master
有效地存储只是这个ID。
Git通过读取提交B
找到提交C
。提交C
在其中包含提交B
的哈希ID。我们说C
&#34;指向&#34; B
,因此是向后箭头。同样,B
&#34;指向&#34; A
。由于A
是第一次提交,因此它没有先前的提交,因此它没有后向指针。
这些内部箭头告诉Git每个提交的父提交。大多数情况下,我们并不关心它们都是倒退的,所以我们可以更简单地将其绘制为:
A--B--C <-- master
这让我们假装明显C
来自B
,尽管其实在Git中相当困难。 (与{34} B
C
之前的B
进行比较,这在Git中很容易:它很容易倒退,因为内部箭头都是向后的。)
现在让我们画一个实际的分支。假设我们从提交D
开始创建一个新分支,然后进行第四次提交A--B--C <-- master
\
D <-- sidebr
(当我们我们制作它时,它并不完全清楚但最终无论如何都不重要):
sidebr
名称D
现在指向提交master
,而名称C
指向提交B
。
这里的一个关键Git概念是提交master
在两个分支上。它位于sidebr
和 A
上。对于提交C
也是如此。在Git中,任何给定的提交可以同时在许多分支上进行。
这里隐藏在Git中的另一个关键概念与大多数其他版本控制系统完全不同,我将在其中提及。这是实际的分支实际由提交本身形成,而分支名称在这里几乎没有任何意义或贡献。这些名称仅用于查找分支提示:在这种情况下提交D
和C
。分支本身就是我们通过绘制连接线获得的,从较新的(子)提交回到较旧的(父)提交。
作为一个侧面点,值得注意的是,这种奇怪的向后链接允许Git永远不会改变任何有关任何提交的内容。请注意,D
和B
都是B
的子项,但我们并非必须知道,当我们创建C
时,我们要同时制作D
和 C
。但是,因为父母不知道&#34;对于它的孩子,Git根本不必在D
内存储B
和B
的ID。它只会存储C
的ID - 当D
和C
创建每个D
时,其中 确实存在<和A--B--C <-- master
\
D <-- sidebr
。
我们制作的这些图纸显示(部分)提交图。
合并基础的正确定义太长了,不能在这里讨论,但是现在我们已经绘制了图形,非正式定义非常简单,并且视觉上很明显。当我们像Git那样向后工作时,两个分支的合并基础是他们第一次聚集在一起的点。也就是说,它是两个分支上的第一个这样的提交。
因此,在:
B
合并基础是提交A--B--C--F <-- master
\
D--E--G <-- sidebr
。如果我们提交更多提交:
B
合并基础仍为提交A--B--C--F---H <-- master
\ /
D--E--G <-- sidebr
。如果我们实际上成功合并,则新的合并提交有两个父提交而不是一个:
H
此处,提交master
是合并,我们在git merge sidebr
上通过运行F
进行合并,其两个父项为master
(曾经是G
)和sidebr
提示的提交(仍然的提交 G
的提示)。
如果我们现在继续提交,后来决定进行另一个合并,A--B--C--F---H--I <-- master
\ /
D--E--G--J <-- sidebr
将成为新的合并基础:
H
G
有两个父母,我们(和Git)同时跟随父母和#34;同时&#34;当我们向后看时因此,如果我们运行另一个合并,则提交F
是两个分支上的第一个。
请注意,在这种情况下,sidebr
不在J
上:我们在遇到父链接时必须遵循这些链接,因此G
会返回E
返回到F
等,以便从sidebr
开始时我们永远不会到达master
。但是,如果我们将下一个合并从 sidebr
合并到 A--B--C--F---H--I <-- master
\ / \
D--E--G--J---K <-- sidebr
:
F
现在提交I
在两个分支上。但事实上,提交A--B--C--E-G--I <-- br1
\ X
D---F-H--J <-- br2
也在两个分支上,所以即使这使得合并双向进行,我们也可以在这里。我们可以在所谓的&#34; criss cross merges&#34;中遇到麻烦,我会画一个只是为了说明问题,但不在这里讨论:
E
我们从分别前往F
和git checkout br1; git merge br2; git checkout br2; git merge br1
的两个分支开始,然后执行G
以E
(F
的合并}和br1
,已添加到H
),然后立即生成F
(E
和br2
合并,添加到E
) 。我们可以继续提交两个分支,但最终,当我们再次合并时,我们在选择合并基础时遇到问题,因为F
和 merge.conflictstyle = diff3
都是&# 34;最佳候选人&#34;。
通常,即使这个&#34;只是工作&#34;,但有时纵横交错合并会产生Git试图以一种奇特的方式处理的问题,使用其默认的&#34;递归&#34;合并策略。在这些(罕见的)情况下,您可以看到一些奇怪的合并冲突,特别是如果您设置git merge branch-name
(我通常建议:它显示冲突合并中的合并基础版本)。
既然我们已经定义了合并基础并且看到了哈希识别对象(包括文件)的方式,我们现在可以回答原始问题了。
当你运行HEAD
时,Git:
--ours
。这也称为本地或branch-name
提交。--theirs
提交的提交。这是另一个分支的提示提交,并且被不同地称为另一个,B
,或者有时远程提交(&#34;远程&#34;是非常差的名称,因为Git也将该术语用于其他目的。)%A
也不错,但合并驱动程序%B
和--ours
分别引用--theirs
和%O
版本git diff
指基地。git diff base ours
命令:git diff base theirs
和git diffs
。这两个差异告诉Git&#34;发生了什么&#34;。 Git的目标,请记住,是要结合两组更改:&#34;我们在我们的工作中做了什么&#34;和他们在他们的作品中做了什么&#34;。这就是两个-s
显示的内容:&#34; base vs our&#34;是我们做了什么,&#34; base vs theirs&#34;是他们做了什么。 (这也是Git如果发现任何文件被添加,删除和/或重命名的方式,从基础到我们和/或基础到他们的 - 但这是一个不必要的复杂现在,我们将忽略。)
组合这些调用合并驱动程序的更改的实际机制,或者 - 在我们的问题案例中 - 并没有。
请记住,Git的每个对象都由其哈希ID编目。根据对象的内容,每个ID都是唯一的。这意味着它可以立即判断任何两个文件是否100%相同:当且仅当它们具有相同的散列时,它们才完全相同。
这意味着如果在base-vs-ours或base-vs-theirs中,这两个文件具有相同的哈希值,那么我们没有做任何更改,或他们> em>没有做任何改变。如果我们没有进行任何更改并且他们进行了更改,那么为什么显然组合这些更改的结果是他们的文件。或者,如果他们没有做任何更改并且我们进行了更改,则结果是我们的文件。
同样,如果我们和他们的相同的哈希,那么我们都进行了相同的更改。在这种情况下,组合更改的结果是文件 - 它们相同,因此即使Git选择了哪一个也不会。
因此,对于所有这些情况,Git只会从基本版本中选择 new 文件具有不同的哈希值(如果有)。这是合并结果,没有合并冲突,Git完成合并该文件。 它永远不会运行您的合并驱动程序,因为显然没有必要。
只有当所有三个文件都有三个不同的哈希值时,Git才能进行真正的三向合并。这是它运行自定义合并驱动程序的时候,如果你已经定义了一个。
有一种解决方法,,但它不适合胆小的人。 Git不仅提供自定义合并驱动程序,还提供custom merge strategies。有四种内置合并策略,全部通过-s ours
选项选择:-s recursive
,-s resolve
,-s octopus
和{{1} }。但是,您可以使用-s custom-strategy
来调用自己的。
问题是要编写合并策略,你必须识别合并基础,在模糊的情况下做任何你想要的递归合并(la -s recursive
)合并基础,运行两个git diff
,找出文件添加/删除/重命名操作,然后运行各种驱动程序。因为这会占用whole megillah,所以可以做任何你想做的事情 - 但你必须做很多事情。据我所知,没有使用这种技术的固定解决方案。
答案 1 :(得分:1)
tl;博士:我试图重复你描述的内容,似乎有效。与你的版本相比有2个变化,但没有它们我合并也失败了(因为驱动程序基本上无法运行)
我试过这个:
创建合并驱动程序$HOME/bin/errorout.bat
:
exit 1
为合并类型
创建一个部分[merge "errorout"]
name = errorout
driver = ~/bin/errorout.bat %A %O %B
创建.gitattributes文件:
*.txt merge=errorout
之后,报告错误,因为我认为您希望报告错误:
$ git merge a
C:\...>exit 1
Auto-merging f.txt
CONFLICT (content): Merge conflict in f.txt
Automatic merge failed; fix conflicts and then commit the result.
我有git版本2.11.0.rc1.windows.1。我无法按照您指定的运行成功执行复杂的命令,它报告了一些语法错误。