这是一个多年来被问过很多次的问题。我找到了很多答案,尤其是这个:
Git - 如何在所选文件上强制合并冲突和手动合并(@Dan Moulding)
此页面包含如何设置始终返回失败的合并驱动程序的详细指南,从而使手动合并成为可能。我试图为 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 文件。
我做错了什么吗?任何强制手动合并问题的解决方案都是最受欢迎的。
问题有两个部分。相对简单的方法是编写自定义合并驱动程序,就像您在步骤 1 和 2 中所做的那样。困难的是,如果 Git 认为不适合,则 Git实际上不会费心运行自定义驱动程序。必要的。这就是您在步骤 3 中观察到的情况。
\n\n那么,Git 何时运行合并驱动程序?答案相当复杂,为了达到这个目的,我们必须定义术语“合并基础”,我们稍后会介绍它。您还需要知道 Git 通过哈希 ID来识别文件\xe2\x80\x94(事实上,几乎所有内容:提交、文件、补丁等\xe2\x80\x94)。如果您已经了解所有这些,可以直接跳到最后一节。
\n\n哈希 ID(有时是对象 ID)或 OID)是您在提交中看到的那些丑陋的大名称:
\n\n$ git rev-parse HEAD\n7f453578c70960158569e63d90374eee06104adc\n$ git log\ncommit 7f453578c70960158569e63d90374eee06104adc\nAuthor: ...\n
Run Code Online (Sandbox Code Playgroud)\n\nGit 存储的所有内容都有一个唯一的哈希 ID,该 ID 是根据对象的内容(文件或提交或其他内容)计算得出的。
\n\n如果您将同一文件存储两次(或更多次),您将获得两次(或更多次)相同的哈希 ID。由于每次提交最终都会存储截至该提交时每个文件的快照,因此每次提交都有每个文件的副本(按其哈希 ID 列出)。其实你可以查看这些:
\n\n$ git ls-tree HEAD\n100644 blob b22d69ec6378de44eacb9be8b61fdc59c4651453 README\n100644 blob b92abd58c398714eb74cbe66671c7c3d5c030e2e integer.txt\n100644 blob 27dfc5306fbd27883ca227f08f06ee037cdcb9e2 lorem.txt\n
Run Code Online (Sandbox Code Playgroud)\n\n中间的三个丑陋的大ID是三个哈希ID。这三个文件位于HEAD
这些 ID 下的提交中。我在多次提交中拥有相同的三个文件,通常内容略有不同。
DAG或有向循环图是一种绘制提交之间关系的方法。要真正正确地使用 Git,您至少需要对 DAG 是什么有一个模糊的了解。它也称为提交图,在某些方面这是一个更好的术语,因为它避免了专门的信息学术语。
\n\n在 Git 中,当我们创建分支时,我们可以用多种不同的方式绘制它们。我喜欢在这里使用的方法(在 StackOverflow 上以文本形式)是将较早的提交放在左侧,将较晚的提交放在右侧,并用单个大写字母标记每个提交。理想情况下,我们会按照 Git 保留它们的方式绘制它们,这是相当落后的:
\n\nA <- B <- C <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n这里我们只有三个提交,全部都在master
. 分支名称 master
“指向”三个提交中的最后一个。这就是 Git 实际上C
通过从分支名称读取其哈希 ID 来查找提交的方式master
,而实际上该名称实际上只master
存储了这一 ID。
B
Git通过读取 commit 来查找提交C
。CommitC
内部有 commit 的哈希 ID B
。我们说C
“指向” B
,因此是向后指向的箭头。同样,B
“指向” A
。由于A
是第一次提交,因此它没有先前的提交,因此它没有后向指针。
这些内部箭头告诉 Git每个提交的父提交。大多数时候,我们并不关心它们都是向后的,所以我们可以更简单地把它画成:
\n\nA--B--C <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\nC
这让我们假装后面的内容是显而易见的B
,尽管事实上这在 Git 中很难做到。B
(与“在之前”的声明相比C
,这在 Git 中非常容易:向后移动很容易,因为内部箭头都是向后的。)
现在让我们画一个实际的分支。假设我们创建一个新分支,从 commit 开始B
,并进行第四次提交D
(不清楚我们何时创建,但最终没关系):
A--B--C <-- master\n \\\n D <-- sidebr\n
Run Code Online (Sandbox Code Playgroud)\n\n现在名称sidebr
指向 commit D
,而名称master
又指向 commit C
。
Git 的一个关键概念是提交B
位于两个分支上。它已打开master
和 sidebr
。对于提交来说也是如此A
。在 Git 中,任何给定的提交都可以而且经常同时在多个分支上进行。
Git 中还隐藏着另一个关键概念,它与大多数其他版本控制系统有很大不同,我只是顺便提一下。这是因为实际的分支实际上是由提交本身形成的,并且分支名称在这里几乎没有任何意义或贡献。这些名称仅用于查找分支提示:在本例中C
是提交。D
分支本身是我们通过绘制连接线得到的,从较新的(子)提交回到较旧的(父)提交。
还值得注意的是,作为一个侧面,这种奇怪的向后链接允许 Git永远不会改变任何提交的任何内容。请注意, 和C
都是D
的子项,但我们在制作时B
不一定知道B
我们要制作C
和 D
。但是,由于父级并不“了解”其子级,因此 Git 根本不必在其中存储C
和的 ID 。它只存储\xe2\x80\x94的 ID ,当它创建每个和时,它在每个和中确实存在。D
B
B
C
D
C
D
我们制作的这些图显示了提交图(的一部分)。
\n\n合并基础的正确定义太长,无法在这里讨论,但现在我们已经绘制了图表,非正式的定义非常简单,而且视觉上很明显。当我们像 Git 一样向后工作时,两个分支的合并基础是它们第一次走到一起的点。也就是说,这是两个分支上的第一个此类提交。
\n\n因此,在:
\n\nA--B--C <-- master\n \\\n D <-- sidebr\n
Run Code Online (Sandbox Code Playgroud)\n\n合并基础是 commit B
。如果我们进行更多提交:
A--B--C--F <-- master\n \\\n D--E--G <-- sidebr\n
Run Code Online (Sandbox Code Playgroud)\n\n合并基础仍然是 commit B
。如果我们确实成功合并,新的合并提交将有两个父提交,而不是只有一个:
A--B--C--F---H <-- master\n \\ /\n D--E--G <-- sidebr\n
Run Code Online (Sandbox Code Playgroud)\n\n这里,commit是我们通过运行H
进行的合并,它的两个父项是(曾经是 的尖端的提交)和(仍然是 的尖端的提交)。master
git merge sidebr
F
master
G
sidebr
如果我们现在继续进行提交,然后决定进行另一次合并,G
则将成为新的合并基础:
A--B--C--F---H--I <-- master\n \\ /\n D--E--G--J <-- sidebr\n
Run Code Online (Sandbox Code Playgroud)\n\nH
有两个父母,当我们向后看时,我们(和 Git)“同时”跟随父母。因此,G
如果我们运行另一次合并,提交是两个分支上的第一个提交。
请注意F
,在本例中, 不是 on sidebr
:我们必须在遇到父链接时跟踪它们,因此J
会返回到G
,从而导致返回到E
,等等,这样我们F
从 开始时就永远不会到达sidebr
。但是,如果我们进行下一次合并from master
into sidebr
:
A--B--C--F---H--I <-- master\n \\ / \\\n D--E--G--J---K <-- sidebr\n
Run Code Online (Sandbox Code Playgroud)\n\n现在提交F
已在两个分支上进行。但事实上,提交I
也在两个分支上,所以即使这使得合并双向进行,我们在这里也没问题。我们可以会遇到所谓的“十字交叉合并”的麻烦,我将画一个只是为了说明问题,但这里不再赘述:
A--B--C--E-G--I <-- br1\n \\ X\n D---F-H--J <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n\n我们从两个分支开始分别到E
和F
,然后执行git checkout br1; git merge br2; git checkout br2; git merge br1
make (和 的G
合并,添加到),然后立即也 make (和 的合并,添加到)。我们可以继续致力于两个分支,但最终,当我们再次合并时,我们在选择合并基础时遇到问题,因为 和都是“最佳候选者”。E
F
br1
H
F
E
br2
E
F
通常,即使这样“也能工作”,但有时交叉合并会产生问题,Git 会尝试使用其默认的“递归”合并策略以一种奇特的方式来处理。在这些(罕见)情况下,您可能会看到一些看起来很奇怪的合并冲突,特别是如果您设置了merge.conflictstyle = diff3
(我通常建议:它会显示冲突合并中的合并基础版本)。
现在我们已经定义了合并基础并了解了哈希识别对象(包括文件)的方式,现在我们可以回答原来的问题了。
\n\n当你跑步时git merge branch-name
,Git:
HEAD
. 这也称为本地或--ours
提交。branch-name
。这是另一个分支的提示提交,并且被称为“other”,--theirs
有时也称为“remote”提交”(“远程”是一个非常糟糕的名称,因为 Git 也将该术语用于其他目的)。B
也不错,但是带有合并驱动程序,%A
并分别%B
参考--ours
和--theirs
版本,带有%O
版本,并参考基础。git diff
命令:git diff base ours
和git diff base theirs
。这两个差异告诉 Git“发生了什么”。请记住, Git 的目标是结合两组更改:“我们在我们的版本中做了什么”和“他们在他们的版本中做了什么”。这就是两人所git diffs
展示的:“基地与我们的”是我们所做的,“基地与他们的”是他们所做的。(这也是 Git 发现在我们的和/或他们的\xe2\x80\x94 中是否添加、删除和/或重命名任何文件的方式,但这现在是一个不必要的复杂化,我们会忽略。)
这是组合这些更改的实际机制,调用合并驱动程序,或者\xe2\x80\x94,如我们的问题案例中的\xe2\x80\x94 那样。
\n\n请记住,Git 将每个对象都按其哈希 ID 进行编目。根据对象的内容,每个 ID 都是唯一的。这意味着它可以立即判断任何两个文件是否100% 相同:当且仅当它们具有相同的哈希值时,它们才完全相同。
\n\n这意味着,如果在“base-vs-ours”或“base-vs-theirs”中,两个文件具有相同的哈希值,那么要么我们没有进行任何更改,要么他们没有进行任何更改。如果我们没有进行更改而他们进行了更改,那么为什么呢,显然组合这些更改的结果是他们的文件。或者,如果他们没有进行任何更改而我们进行了更改,则结果就是我们的文件。
\n\n同样,如果我们和他们的哈希值相同,那么我们都会进行相同的更改。在这种情况下,组合更改的结果是 file\xe2\x80\x94,它们是相同的,因此 Git 选择哪一个都无关紧要。
\n\n因此,对于所有这些情况,Git 只是选择具有与基本版本不同的哈希值(如果有)的新文件。这就是合并结果,没有合并冲突,Git 就完成了该文件的合并。 它永远不会运行您的合并驱动程序,因为显然没有必要。
\n\n只有当所有三个文件都具有三个不同的哈希值时,Git 才必须进行真正的三向合并。此时它将运行您的自定义合并驱动程序(如果您已定义)。
\n\n有一种方法可以解决这个问题,但不适合胆小的人。Git 不仅提供自定义合并驱动程序,还提供自定义合并策略。有四种内置合并策略,全部通过-s
选项选择:-s ours
、-s recursive
、-s resolve
和-s octopus
。但是,您可以用来-s custom-strategy
调用您自己的。
问题是,要编写合并策略,您必须识别合并基础,-s recursive
在合并基础不明确的情况下进行任何您想要的递归合并(a la),运行两个git diff
,找出文件添加/删除/重命名操作,然后运行各种驱动程序。因为这会接管整个megillah,所以你可以做任何你想做的\xe2\x80\x94,但你必须做很多事情。据我所知,还没有使用这种技术的固定解决方案。
tl;dr:我尝试重复你所描述的内容,它似乎有效。与你的版本相比有 2 个更改,但没有它们我合并也失败(因为驱动程序基本上无法运行)
我已经尝试过这个:
创建合并驱动程序$HOME/bin/errorout.bat
:
exit 1
Run Code Online (Sandbox Code Playgroud)
为合并类型创建一个部分
[merge "errorout"]
name = errorout
driver = ~/bin/errorout.bat %A %O %B
Run Code Online (Sandbox Code Playgroud)
创建 .gitattributes 文件:
*.txt merge=errorout
Run Code Online (Sandbox Code Playgroud)
之后,会报告错误,因为我认为您希望报告错误:
$ 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.
Run Code Online (Sandbox Code Playgroud)
我有 git 版本 2.11.0.rc1.windows.1。我无法使您指定的复杂命令成功运行,它报告了一些语法错误。
归档时间: |
|
查看次数: |
2728 次 |
最近记录: |