如何解决 git 我想保留更改并拒绝在最新分支中删除文件的冲突
how to resolve git conflict where i want to keep a change and reject the file deletion in latest branch
CONFLICT (modify/delete): src/a.js在aa1e4d中删除,在HEAD中修改。 src/a.js 的版本 HEAD 留在树中。
我想保留 HEAD 中的内容并拒绝 aa1e4d
中的删除
[Git said]
CONFLICT (modify/delete): src/a.js deleted in aa1e4d and modified
in HEAD. Version HEAD of src/a.js left in tree.
I want to keep what is there in the HEAD and reject the deletion in aa1e4d
Git 已经做到了——或者更准确地说,在您的 工作树 中做到了。那么,您现在需要做的就是告诉 Git 这实际上是正确的分辨率:
git add src/a.js
一旦您告诉 Git 所有必要的解决方案,您就可以完成您正在执行的任何操作(git merge
、git rebase
或任何可能的操作 — 所有这些调用 Git 的合并机制)。在 Git、运行 git merge --continue
、git rebase --continue
的现代版本中,或者无论你用 --continue
做什么来告诉 Git 继续进一步暂停操作。
了解正在发生的事情
了解您在这里做什么很重要:您将需要此类信息用于将来的合并、变基等。
在 Git 中,合并操作——我喜欢称其为动词合并——是各种Git 命令采用某些输入文件的 三个 版本,并使用这三个版本为每个文件提供一个版本。这种合并的一个常见来源,也是一个易于理解的来源——至少 更容易 ——是 git merge
命令本身,通常是 运行通过 git pull
.
合并有三个输入
这三个输入是什么?好吧,请记住 Git 实际上就是 提交 。这与分支无关,尽管分支名称可以帮助您 find 提交。它甚至与文件无关,尽管提交包含文件。 Git 是关于提交的。所以这三个输入是三个特定的提交。
当我们使用 git merge
时,我们自己选择其中两个提交。让我们绘制一对分支,每个分支都有一些对该特定分支私有的提交和一些共同的提交(在两个分支之间共享)。我们将把较旧的提交放在左边,较新的提交放在右边,而不是实际的提交哈希 ID(如 aa1e4d
),我们将只使用一个大写字母代表每个提交:
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
在这里,我们在分支 branch1
上,我们将 运行 git merge branch2
合并提交 L
与提交 J
。
如果一切顺利,最终我们会产生一个新的合并提交。这使用相同的词 merge,但作为形容词修饰词 commit。 Git 还调用结果 a merge,使用单词 merge 作为名词。结果看起来像这样,假设我们得到一个结果并保留它:
I--J
/ \
...--G--H M <-- branch1 (HEAD)
\ /
K--L <-- branch2
这个新的合并提交 M
仅在分支 branch1
上,但是有两个 parents(而不是通常单个 parent) 三个输入提交中的两个,即 J
——我们开始时使用的提交——和 L
,我们告诉 Git 合并的提交。
与任何提交一样,新的合并 M
将保存 每个 文件的完整快照 Git 知道。 Git 知道的文件将是来自三个输入提交的文件。这三个输入提交每个都包含每个文件的完整快照,Git在您或任何人进行这些提交时知道这些文件。
我一直提到 三个 提交。这里的三个提交是什么?就输入而言,我们只显示了 两个 提交:J
,我们当前或来自 branch1
的 HEAD
提交,以及 L
, branch2
上的最后一次提交,因为 branch2
现在是这样。 第三次 提交是最佳共同祖先。
请注意,如果我们从提交 J
开始并向后工作 ,我们将找到提交 I
,然后提交 H
,然后提交 G
,等等。同时,如果我们从提交 L
开始并向后工作,我们将找到提交 K
,然后是提交 H
,然后是提交 G
,依此类推。我们的两条路径在提交 H
处汇合,这两个分支从一个共同的祖先分支出来。
这就是 Git 的工作原理。这就像一个大家谱或 family-tree:提交 J
有提交 I
作为它的(一个,单个)parent。提交 I
将提交 H
作为其 parent。提交 H
将提交 G
作为其 parent。我们工作向后,通过时间倒退,从最近的提交开始作为我们的当前或HEAD
提交。大多数提交只有一个 parent,但偶尔,我们会遇到一个 合并提交 ,如 M
,其中有两个 parent。 (我们现在不会担心这个,但是当我们在向后工作时确实遇到合并时,我们通常必须遵循 both parents。)
无论如何,看一下图表,很明显 G
和 H
都可以用作共享的共同祖先。在这种情况下,best 被定义为提交 H
。对于像这样的简单图表,最好的是显而易见的:它是最新的,并且 H
比 G
更新,所以这是最常见的共享提交。 技术术语因为这是合并基础.
合并的工作原理
要执行合并,git merge
将:
- 将合并基础提交中的快照与我们当前的或
HEAD
提交进行比较,以查看 我们 更改了什么;和
- 将相同的合并基础快照与 他们的 提交进行比较,看看 他们 发生了什么变化。
由于这三个提交中的每一个在其快照中都有一组文件,因此这些文件就是要比较的文件。
在您的特定情况下,合并库有一个名为 src/a.js
.1 的文件,您自己的提交也有一个 src/a.js
文件,并且您有更改了该文件,使其与 src/a.js
:
的合并基础副本不匹配
modified in HEAD
他们在他们的提交中所做的是遗漏src/a.js
完全。因此,当将合并基础 src/a.js
与他们的提交进行比较时,他们 删除了文件:
... deleted in aa1e4d and ...
git merge
的工作是将他们的更改与您的更改结合起来。如果您更改了文件的第 42 行,而他们没有,Git 会将您的更改带到第 42 行。如果他们在文件的前面添加了三行,Git 将接受他们的更改更改以保留额外的三行(这样您的更改现在就在第 45 行)。但他们不只是 修改 文件。他们完全删除了文件。
Git 不是 确定 如何正确组合这些,所以它会选择一些事情去做,然后确保停下来并从你那里得到帮助.它告诉你它选择做什么:
Version HEAD of src/a.js left in tree.
left in tree 部分在这里很重要。
1请注意,文件名中有一个嵌入的斜杠。这就是 Git 命名文件的方式:这些不是 folders-and-files,它们只是带有斜线的文件名。 Git 中正在进行一些工作以使其更好地识别重命名,并且 Git 现在确实以有限的方式理解这些 代表 folders-and-files 并且 folder-rename 在进行合并时会导致很多 file-renames。但它仍然有点乱 ad-hoc.
当事情出错时,git merge
留下很多零件
同样,合并有 三个输入。这三个输入是提交,所以每个都有很多文件。 Git 管理这些文件的方式是将 每个文件的所有三个副本 放入 Git 调用的 index 或者暂存区.
索引,或暂存区——有时有第三个名字,缓存,尽管最近这个第三个名字几乎消失了:你现在大多把它看作是一个标志,如在 git rm --cached
中一样——是 Git 如何了解文件 。我之前提到过,每个提交都有 all Git 当时知道的文件保存为永久快照。 Git 知道他们是因为他们被列在它的索引中。
索引的内容随着您从提交移动到提交而改变。检查一些提交的行为填充了 Git 的索引,来自您 checked-out 的提交。进行 new 提交的行为会根据 Git 索引中的任何内容进行新提交。所以在这两个步骤之间,您的工作是更新 Git 的索引。
这是很多混乱的根源,在 Git. 索引中包含 Git 实际上是 的文件using.2 您可以在其中查看文件和完成工作的工作树包含这些文件的 份 。这些副本供您使用。它们不是 Git 的副本!他们是你的。 Git 将要使用的副本在 Git 的索引中。当您 运行 git commit
或以其他方式进行新提交时,index 副本将被 Git 使用。
当您更改某个文件的 working-tree 副本时,您必须 运行 git 添加 <em> 文件</em>
一直。原因很简单:git add
意味着 使索引副本匹配我更新的 working-tree 副本 。如果该文件之前在索引中,那么现在它已更新。如果该文件之前根本不在索引中,那么现在是了。所以无论哪种方式,在 git 添加 <em>file</em>
之后,索引副本都会更新。
这一切归结为一个很好的简化:Git 的索引包含您的提议的下一次提交。如果你想 完全删除 一个文件,这样它就不会出现在 下一个 提交中,你只需 运行 gitrm<em>文件</em>
。这将从两个地方删除文件:您的工作树,您可以在其中查看和使用该文件作为常规普通文件,以及 Git 的索引,其中保留副本以在 下一步提交。
git merge
命令乱七八糟这张漂亮的简单图片。索引不再只包含每个文件的 一个 副本,现在索引包含每个文件的 最多三个 副本。这三个副本来自正在合并的三个提交。
2从技术上讲,索引包含文件 names——用正斜杠完成,例如 src/a.js
——以及相应的 blob 哈希 ID。它还包含大量缓存数据,可帮助 Git 运行得更快。内部 Git blob objects 都是 de-duplicated,因此文件在 re-use 相同文件内容的提交中得到 共享 。这意味着索引本身并不真正包含 files。但您可以将其视为 Git 内部格式的文件副本。只有当您开始使用 git update-index
或 git ls-files --stage
直接查看索引中的内容时,这种错觉才会被打破。
git merge
如何使用 Git 的索引
简化图——实际更复杂——你可以认为git merge
是这样工作的:
确保索引和工作树是“干净的”,即每个文件的 HEAD
副本是 Git 索引和您的索引中的副本工作树。
展开索引。通常,每个文件 in 索引都在“槽零”中。每个文件有四个插槽,但通常插槽 1、2 和 3 未使用。 Git 现在将所有文件从插槽 0 移动到插槽 2,Git 有时调用 --ours
。
将合并库中的文件复制到插槽 1,并将其他提交中的文件复制到插槽 3。索引现在包含所有三个提交的每个文件的所有三个版本。插槽 1 是合并基础插槽 - 它没有 --base
名称,但也许应该有 - 插槽 3 有时称为 --theirs
。 (插槽 2 当然是 --ours
,如步骤 2 中所述。)
当所有三个插槽中的所有文件 匹配 时,此文件的合并为 super-trivial。只需将(单个)文件放回零槽,擦除剩余的槽:反正这三个槽都是一样的。
我们没有三个 个文件都匹配,但如果其中两个匹配怎么办?这里分三种情况:
我们的和他们的匹配(但不同于 slot-1 副本):我们都对文件进行了 相同的更改,所以使用哪个这些都很方便。将那一个放到零槽并擦除其他槽,我们就完成了。
他们的和 merge-base 副本匹配:他们没有更改文件,而我们做了。使用我们的文件版本:将其从插槽 2 拖放到插槽 0。擦除其他插槽,我们就完成了。
我们的和 merge-base 的副本匹配:我们没有更改文件,而他们更改了。使用他们的文件版本:将其从插槽 3 拖放到插槽 0。也将其复制到工作树中,以便我们可以看到新文件。擦除其他插槽,我们就完成了。
剩下的案子比较难
所有剩余的案例都需要一些实际工作,当他们罢工时,必须做更多的工作。 (上述情况由 Git 中的特殊 index-only 代码处理。这实际上是一个特别烦恼的来源,与低级合并驱动程序有关:他们没有 运行在任何简单的情况下都可以。)
最常见的情况是,对于案例 6,我们和他们都对一个特定文件进行了一些更改。 Git 将尝试 合并 这两组更改,并将合并的更改应用于合并基础副本(在插槽 1 中)。如果 Git 能够自己进行合并,Git 会将合并后的文件写入我们的工作树,将合并后的文件移至槽零,并擦除三个 higher-numbered 槽。此合并冲突现已解决:Git 自行合并文件。3
但是,在某些情况下,Git不能或不会自行解决冲突。这包括我们和他们都更改了相同行但进行了不同更改的情况。在这些情况下,Git 将向文件的工作树副本写入 marked-up diff,其中 <<<<<<< HEAD
和 >>>>>>> theirs
行添加到 Git 的位置无法自行解决。但它也包括我喜欢称之为 high-level 冲突的东西:low-level 冲突的对立面。其他人称这些 树冲突 。这些 high-level 冲突包括像您这样的情况,您更改了文件,但他们 删除了 文件。
对于这些情况,Git 在 Git 的索引中保留尽可能多的副本。在这种情况下,它将在 Git 的索引中保留 src/a.js
的合并基础副本,并在 Git 的索引中保留 src/a.js
的 HEAD
副本.是我即将 阅读 Git 打印的消息 :
CONFLICT (modify/delete): src/a.js deleted in aa1e4d and modified
in HEAD. Version HEAD of src/a.js left in tree.
这会告诉您 Git 留在您的工作树中的内容:来自 HEAD
提交的副本,即来自您当前分支的副本。
3当 Git 解析了这样的文件时,它是在 line-by-line 的基础上使用简单的文本规则完成的。 Git 不了解文件的内容。这意味着即使 Git 自行解决了冲突,结果 也可能 完全是无稽之谈。实际上,这实际上适用于数量惊人的大量情况。如果没有,您有时可以通过编写自己的 low-level 合并驱动程序来帮助 Git,尽管这很重要。
事实上 Git 在没有实际 理解 文件的情况下执行此操作,这就是为什么 测试合并结果很重要。即使 Git 认为一切顺利,也可能并非如此。
你的工作是修复Git的索引
无论发生什么冲突以及 Git 在您的工作树和 Git 的索引中留下什么,您现在的工作是填写 Git的索引与正确的最终合并结果。 当然,在这种情况下,Git 的索引已经部分或什至大部分充满了正确的东西。这里,Git 的索引在两个槽中有一个 src/a.js
的副本:槽 1(合并基础)和槽 2(--ours
槽)。如果正确的结果是从新提交中省略该文件。
Git的索引rest大概已经全部正确了。如果是这样,您无需对任何这些条目执行任何操作。它只是 src/a.js
的条目 messed-up 并且存在冲突。它有一个插槽 1 条目和一个插槽 2 条目。 Git 本身并不关心您如何执行此操作,但您必须擦除编号较高的插槽并放入 slot-zero 条目,或者擦除所有插槽以使文件不存在。 Git 为此提供的两个主要工具是 git add
和 git rm
。
记住,git rm
意味着 从索引中删除并删除 working-tree 版本 。因此,如果正确答案是“完全删除 src/a.js
”,您只需 运行 git rm src/a.js
。不过,在您的情况下,这不是正确的答案:
I want to keep what is there
这意味着您希望 src/a.js
的某些版本位于槽零中。如果您的工作树中有 正确的 版本,请记住 git add
意味着 使索引副本与工作树副本匹配 。所以:
I want to keep what is there in the HEAD
这就是 Git 留在工作树中的内容。那么,您所要做的就是 运行 git add src/a.js
,获取工作树副本(现在也在插槽 2 中)并让 Git 将其写入插槽 0。填充或擦除槽零 也 会擦除所有编号较高的槽。
所以:
git add src/a.js
将 src/a.js
的 working-tree 副本复制到插槽 0 的索引,并删除插槽 1 和 2 中的条目。插槽 3 中没有条目,因此现在已解析此文件。
如果您有 more-standard 冲突,Git 会在所有三个位置中保留所有三个副本,加上它自己的合并尝试,并在您的工作树中完成冲突标记.在这种情况下,您可以编辑工作树副本以产生正确的结果,并使用 git add
擦除所有三个非零槽并在槽零中提供正确的文件。但是你不需要做那么多工作,因为工作树副本已经正确了。只是放错了位置!
CONFLICT (modify/delete): src/a.js在aa1e4d中删除,在HEAD中修改。 src/a.js 的版本 HEAD 留在树中。 我想保留 HEAD 中的内容并拒绝 aa1e4d
中的删除[Git said]
CONFLICT (modify/delete): src/a.js deleted in aa1e4d and modified in HEAD. Version HEAD of src/a.js left in tree.
I want to keep what is there in the HEAD and reject the deletion in aa1e4d
Git 已经做到了——或者更准确地说,在您的 工作树 中做到了。那么,您现在需要做的就是告诉 Git 这实际上是正确的分辨率:
git add src/a.js
一旦您告诉 Git 所有必要的解决方案,您就可以完成您正在执行的任何操作(git merge
、git rebase
或任何可能的操作 — 所有这些调用 Git 的合并机制)。在 Git、运行 git merge --continue
、git rebase --continue
的现代版本中,或者无论你用 --continue
做什么来告诉 Git 继续进一步暂停操作。
了解正在发生的事情
了解您在这里做什么很重要:您将需要此类信息用于将来的合并、变基等。
在 Git 中,合并操作——我喜欢称其为动词合并——是各种Git 命令采用某些输入文件的 三个 版本,并使用这三个版本为每个文件提供一个版本。这种合并的一个常见来源,也是一个易于理解的来源——至少 更容易 ——是 git merge
命令本身,通常是 运行通过 git pull
.
合并有三个输入
这三个输入是什么?好吧,请记住 Git 实际上就是 提交 。这与分支无关,尽管分支名称可以帮助您 find 提交。它甚至与文件无关,尽管提交包含文件。 Git 是关于提交的。所以这三个输入是三个特定的提交。
当我们使用 git merge
时,我们自己选择其中两个提交。让我们绘制一对分支,每个分支都有一些对该特定分支私有的提交和一些共同的提交(在两个分支之间共享)。我们将把较旧的提交放在左边,较新的提交放在右边,而不是实际的提交哈希 ID(如 aa1e4d
),我们将只使用一个大写字母代表每个提交:
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
在这里,我们在分支 branch1
上,我们将 运行 git merge branch2
合并提交 L
与提交 J
。
如果一切顺利,最终我们会产生一个新的合并提交。这使用相同的词 merge,但作为形容词修饰词 commit。 Git 还调用结果 a merge,使用单词 merge 作为名词。结果看起来像这样,假设我们得到一个结果并保留它:
I--J
/ \
...--G--H M <-- branch1 (HEAD)
\ /
K--L <-- branch2
这个新的合并提交 M
仅在分支 branch1
上,但是有两个 parents(而不是通常单个 parent) 三个输入提交中的两个,即 J
——我们开始时使用的提交——和 L
,我们告诉 Git 合并的提交。
与任何提交一样,新的合并 M
将保存 每个 文件的完整快照 Git 知道。 Git 知道的文件将是来自三个输入提交的文件。这三个输入提交每个都包含每个文件的完整快照,Git在您或任何人进行这些提交时知道这些文件。
我一直提到 三个 提交。这里的三个提交是什么?就输入而言,我们只显示了 两个 提交:J
,我们当前或来自 branch1
的 HEAD
提交,以及 L
, branch2
上的最后一次提交,因为 branch2
现在是这样。 第三次 提交是最佳共同祖先。
请注意,如果我们从提交 J
开始并向后工作 ,我们将找到提交 I
,然后提交 H
,然后提交 G
,等等。同时,如果我们从提交 L
开始并向后工作,我们将找到提交 K
,然后是提交 H
,然后是提交 G
,依此类推。我们的两条路径在提交 H
处汇合,这两个分支从一个共同的祖先分支出来。
这就是 Git 的工作原理。这就像一个大家谱或 family-tree:提交 J
有提交 I
作为它的(一个,单个)parent。提交 I
将提交 H
作为其 parent。提交 H
将提交 G
作为其 parent。我们工作向后,通过时间倒退,从最近的提交开始作为我们的当前或HEAD
提交。大多数提交只有一个 parent,但偶尔,我们会遇到一个 合并提交 ,如 M
,其中有两个 parent。 (我们现在不会担心这个,但是当我们在向后工作时确实遇到合并时,我们通常必须遵循 both parents。)
无论如何,看一下图表,很明显 G
和 H
都可以用作共享的共同祖先。在这种情况下,best 被定义为提交 H
。对于像这样的简单图表,最好的是显而易见的:它是最新的,并且 H
比 G
更新,所以这是最常见的共享提交。 技术术语因为这是合并基础.
合并的工作原理
要执行合并,git merge
将:
- 将合并基础提交中的快照与我们当前的或
HEAD
提交进行比较,以查看 我们 更改了什么;和 - 将相同的合并基础快照与 他们的 提交进行比较,看看 他们 发生了什么变化。
由于这三个提交中的每一个在其快照中都有一组文件,因此这些文件就是要比较的文件。
在您的特定情况下,合并库有一个名为 src/a.js
.1 的文件,您自己的提交也有一个 src/a.js
文件,并且您有更改了该文件,使其与 src/a.js
:
modified in HEAD
他们在他们的提交中所做的是遗漏src/a.js
完全。因此,当将合并基础 src/a.js
与他们的提交进行比较时,他们 删除了文件:
... deleted in aa1e4d and ...
git merge
的工作是将他们的更改与您的更改结合起来。如果您更改了文件的第 42 行,而他们没有,Git 会将您的更改带到第 42 行。如果他们在文件的前面添加了三行,Git 将接受他们的更改更改以保留额外的三行(这样您的更改现在就在第 45 行)。但他们不只是 修改 文件。他们完全删除了文件。
Git 不是 确定 如何正确组合这些,所以它会选择一些事情去做,然后确保停下来并从你那里得到帮助.它告诉你它选择做什么:
Version HEAD of src/a.js left in tree.
left in tree 部分在这里很重要。
1请注意,文件名中有一个嵌入的斜杠。这就是 Git 命名文件的方式:这些不是 folders-and-files,它们只是带有斜线的文件名。 Git 中正在进行一些工作以使其更好地识别重命名,并且 Git 现在确实以有限的方式理解这些 代表 folders-and-files 并且 folder-rename 在进行合并时会导致很多 file-renames。但它仍然有点乱 ad-hoc.
当事情出错时,git merge
留下很多零件
同样,合并有 三个输入。这三个输入是提交,所以每个都有很多文件。 Git 管理这些文件的方式是将 每个文件的所有三个副本 放入 Git 调用的 index 或者暂存区.
索引,或暂存区——有时有第三个名字,缓存,尽管最近这个第三个名字几乎消失了:你现在大多把它看作是一个标志,如在 git rm --cached
中一样——是 Git 如何了解文件 。我之前提到过,每个提交都有 all Git 当时知道的文件保存为永久快照。 Git 知道他们是因为他们被列在它的索引中。
索引的内容随着您从提交移动到提交而改变。检查一些提交的行为填充了 Git 的索引,来自您 checked-out 的提交。进行 new 提交的行为会根据 Git 索引中的任何内容进行新提交。所以在这两个步骤之间,您的工作是更新 Git 的索引。
这是很多混乱的根源,在 Git. 索引中包含 Git 实际上是 的文件using.2 您可以在其中查看文件和完成工作的工作树包含这些文件的 份 。这些副本供您使用。它们不是 Git 的副本!他们是你的。 Git 将要使用的副本在 Git 的索引中。当您 运行 git commit
或以其他方式进行新提交时,index 副本将被 Git 使用。
当您更改某个文件的 working-tree 副本时,您必须 运行 git 添加 <em> 文件</em>
一直。原因很简单:git add
意味着 使索引副本匹配我更新的 working-tree 副本 。如果该文件之前在索引中,那么现在它已更新。如果该文件之前根本不在索引中,那么现在是了。所以无论哪种方式,在 git 添加 <em>file</em>
之后,索引副本都会更新。
这一切归结为一个很好的简化:Git 的索引包含您的提议的下一次提交。如果你想 完全删除 一个文件,这样它就不会出现在 下一个 提交中,你只需 运行 gitrm<em>文件</em>
。这将从两个地方删除文件:您的工作树,您可以在其中查看和使用该文件作为常规普通文件,以及 Git 的索引,其中保留副本以在 下一步提交。
git merge
命令乱七八糟这张漂亮的简单图片。索引不再只包含每个文件的 一个 副本,现在索引包含每个文件的 最多三个 副本。这三个副本来自正在合并的三个提交。
2从技术上讲,索引包含文件 names——用正斜杠完成,例如 src/a.js
——以及相应的 blob 哈希 ID。它还包含大量缓存数据,可帮助 Git 运行得更快。内部 Git blob objects 都是 de-duplicated,因此文件在 re-use 相同文件内容的提交中得到 共享 。这意味着索引本身并不真正包含 files。但您可以将其视为 Git 内部格式的文件副本。只有当您开始使用 git update-index
或 git ls-files --stage
直接查看索引中的内容时,这种错觉才会被打破。
git merge
如何使用 Git 的索引
简化图——实际更复杂——你可以认为git merge
是这样工作的:
确保索引和工作树是“干净的”,即每个文件的
HEAD
副本是 Git 索引和您的索引中的副本工作树。展开索引。通常,每个文件 in 索引都在“槽零”中。每个文件有四个插槽,但通常插槽 1、2 和 3 未使用。 Git 现在将所有文件从插槽 0 移动到插槽 2,Git 有时调用
--ours
。将合并库中的文件复制到插槽 1,并将其他提交中的文件复制到插槽 3。索引现在包含所有三个提交的每个文件的所有三个版本。插槽 1 是合并基础插槽 - 它没有
--base
名称,但也许应该有 - 插槽 3 有时称为--theirs
。 (插槽 2 当然是--ours
,如步骤 2 中所述。)当所有三个插槽中的所有文件 匹配 时,此文件的合并为 super-trivial。只需将(单个)文件放回零槽,擦除剩余的槽:反正这三个槽都是一样的。
我们没有三个 个文件都匹配,但如果其中两个匹配怎么办?这里分三种情况:
我们的和他们的匹配(但不同于 slot-1 副本):我们都对文件进行了 相同的更改,所以使用哪个这些都很方便。将那一个放到零槽并擦除其他槽,我们就完成了。
他们的和 merge-base 副本匹配:他们没有更改文件,而我们做了。使用我们的文件版本:将其从插槽 2 拖放到插槽 0。擦除其他插槽,我们就完成了。
我们的和 merge-base 的副本匹配:我们没有更改文件,而他们更改了。使用他们的文件版本:将其从插槽 3 拖放到插槽 0。也将其复制到工作树中,以便我们可以看到新文件。擦除其他插槽,我们就完成了。
剩下的案子比较难
所有剩余的案例都需要一些实际工作,当他们罢工时,必须做更多的工作。 (上述情况由 Git 中的特殊 index-only 代码处理。这实际上是一个特别烦恼的来源,与低级合并驱动程序有关:他们没有 运行在任何简单的情况下都可以。)
最常见的情况是,对于案例 6,我们和他们都对一个特定文件进行了一些更改。 Git 将尝试 合并 这两组更改,并将合并的更改应用于合并基础副本(在插槽 1 中)。如果 Git 能够自己进行合并,Git 会将合并后的文件写入我们的工作树,将合并后的文件移至槽零,并擦除三个 higher-numbered 槽。此合并冲突现已解决:Git 自行合并文件。3
但是,在某些情况下,Git不能或不会自行解决冲突。这包括我们和他们都更改了相同行但进行了不同更改的情况。在这些情况下,Git 将向文件的工作树副本写入 marked-up diff,其中 <<<<<<< HEAD
和 >>>>>>> theirs
行添加到 Git 的位置无法自行解决。但它也包括我喜欢称之为 high-level 冲突的东西:low-level 冲突的对立面。其他人称这些 树冲突 。这些 high-level 冲突包括像您这样的情况,您更改了文件,但他们 删除了 文件。
对于这些情况,Git 在 Git 的索引中保留尽可能多的副本。在这种情况下,它将在 Git 的索引中保留 src/a.js
的合并基础副本,并在 Git 的索引中保留 src/a.js
的 HEAD
副本.是我即将 阅读 Git 打印的消息 :
CONFLICT (modify/delete): src/a.js deleted in aa1e4d and modified in HEAD. Version HEAD of src/a.js left in tree.
这会告诉您 Git 留在您的工作树中的内容:来自 HEAD
提交的副本,即来自您当前分支的副本。
3当 Git 解析了这样的文件时,它是在 line-by-line 的基础上使用简单的文本规则完成的。 Git 不了解文件的内容。这意味着即使 Git 自行解决了冲突,结果 也可能 完全是无稽之谈。实际上,这实际上适用于数量惊人的大量情况。如果没有,您有时可以通过编写自己的 low-level 合并驱动程序来帮助 Git,尽管这很重要。
事实上 Git 在没有实际 理解 文件的情况下执行此操作,这就是为什么 测试合并结果很重要。即使 Git 认为一切顺利,也可能并非如此。
你的工作是修复Git的索引
无论发生什么冲突以及 Git 在您的工作树和 Git 的索引中留下什么,您现在的工作是填写 Git的索引与正确的最终合并结果。 当然,在这种情况下,Git 的索引已经部分或什至大部分充满了正确的东西。这里,Git 的索引在两个槽中有一个 src/a.js
的副本:槽 1(合并基础)和槽 2(--ours
槽)。如果正确的结果是从新提交中省略该文件。
Git的索引rest大概已经全部正确了。如果是这样,您无需对任何这些条目执行任何操作。它只是 src/a.js
的条目 messed-up 并且存在冲突。它有一个插槽 1 条目和一个插槽 2 条目。 Git 本身并不关心您如何执行此操作,但您必须擦除编号较高的插槽并放入 slot-zero 条目,或者擦除所有插槽以使文件不存在。 Git 为此提供的两个主要工具是 git add
和 git rm
。
记住,git rm
意味着 从索引中删除并删除 working-tree 版本 。因此,如果正确答案是“完全删除 src/a.js
”,您只需 运行 git rm src/a.js
。不过,在您的情况下,这不是正确的答案:
I want to keep what is there
这意味着您希望 src/a.js
的某些版本位于槽零中。如果您的工作树中有 正确的 版本,请记住 git add
意味着 使索引副本与工作树副本匹配 。所以:
I want to keep what is there in the HEAD
这就是 Git 留在工作树中的内容。那么,您所要做的就是 运行 git add src/a.js
,获取工作树副本(现在也在插槽 2 中)并让 Git 将其写入插槽 0。填充或擦除槽零 也 会擦除所有编号较高的槽。
所以:
git add src/a.js
将 src/a.js
的 working-tree 副本复制到插槽 0 的索引,并删除插槽 1 和 2 中的条目。插槽 3 中没有条目,因此现在已解析此文件。
如果您有 more-standard 冲突,Git 会在所有三个位置中保留所有三个副本,加上它自己的合并尝试,并在您的工作树中完成冲突标记.在这种情况下,您可以编辑工作树副本以产生正确的结果,并使用 git add
擦除所有三个非零槽并在槽零中提供正确的文件。但是你不需要做那么多工作,因为工作树副本已经正确了。只是放错了位置!