恢复 git 合并冲突标志

Restoring git merge conflict flags

我正在尝试找出一种与团队其他成员共享合并冲突的方法。我们有一些非常大的分支机构,合并它们会产生很多冲突。我尝试了几种不同的方法,我目前的尝试包括将处于冲突状态的文件推送到远程仓库(将合并标记留在文件中),然后 运行 一个烦人的长别名来 grep 遍历文件和手动重新创建合并文件(本地、基础、远程)。

我最近发现

git checkout --conflict=merge -- (file)

命令,在本地分支上运行良好,但一旦推送到远程,此命令就不再适用于恢复合并标志。

有没有办法强制 git 将文件重新标记为冲突,以便人们可以使用正常的合并工具来解决它们?

Is there a way to force git to re-flag a file as conflicted so people can use the normal merge tools to resolve them?

不,无论如何都不能不编写自己的代码。 (应该有人写一些代码,也许应该是——是时候为此准备一个工具了。不过,有很多极端情况很难解决。)

这里的问题是 Git 中的冲突合并由存储在 Git 的 索引.[=51= 中的状态表示]

退后一步,让我们定义索引,以及当前提交HEAD,以及工作树(或工作树、工作树和一堆类似的变体):

  • 当前提交,也称为 HEAD 或 HEAD,非常简单。 (我喜欢在这样的计算机文本布局中使用 HEAD,具体表示 Git 名称 HEAD。您也可以在 Git 中使用 @ version 1.8.5 or later. 这个特殊名称指的是current branch,如果有current branch,则current branch定位到tip commit 该分支的当前提交。或者,在 "detached HEAD" 模式下,HEAD 直接包含当前提交的哈希 ID。无论哪种方式,这都会命名当前提交。)

  • 工作树很简单,就是您工作的地方。 Git 保存提交和文件版本副本的内部数据结构不适合其他任何东西,因此 Git 将版本提取到普通文件中,然后您可以像往常一样读取和操作这些文件。

    工作树还可以保存您尚未提交且不想提交的文件。这些是 未跟踪的 文件。 (从技术上讲,未跟踪文件是工作树中尚未在 索引 中的任何文件,但我们尚未定义索引。:-) )

    Git 倾向于抱怨未跟踪的文件未被跟踪;您可以通过在 .gitignore 文件中列出文件或其路径名模式来关闭这些投诉。请注意,将文件名添加到 .gitignore 不会 使文件无法跟踪。如果文件被跟踪,它将保持跟踪状态。 .gitignore 条目主要只是关闭投诉,并且当您说 "add all files".[=51= 时,Git 不会自动 添加 这些未跟踪的文件]

  • index 位于这两者之间。通常,当您第一次签出提交或分支时,索引内容与 HEAD 提交内容相匹配,Git 也会将其提取到工作树中。然后,您可以随心所欲地修改工作树,但索引会继续与 HEAD 匹配。您必须 git add 个文件才能将它们从工作树复制回索引。

    因此,索引实质上代表了您将进行的下一次提交。当您 运行 git commit 时,Git 将索引变成新的提交(自动成为新的 HEAD 提交)。只有您复制回 索引中的内容才会被提交,这意味着您可以通过一次 git add 几个文件将更改拆分为多个提交。 (并且,您可以使用 git add -p 仅添加文件的 部分 ,而不是整个文件,以便索引版本本身介于 HEAD 提交版本和工作树版本。)

如果您从不进行任何合并,或者从不发生任何合并冲突,我们可以到此为止并完成索引。当然,你在做合并,他们遇到了冲突,所以我们需要仔细观察。

索引有四个 "stage slots" 每个索引条目

索引按工作树中的路径名记录文件。如果您修改其中一些文件,并且 git add 它们为下一次提交做好准备,这将更新文件的索引版本。但是如果您在冲突合并期间 运行 git ls-files --stage 会出现一个秘密。没有其他时间通常会出现这种情况——只有在发生冲突的合并期间才会出现。秘诀在于每个文件最多可以在索引中三次,在编号的阶段槽中。插槽零是正常的日常插槽:

$ git ls-files --stage
[snip]
100644 d8d18736e74c7a5f61d794770a2dd94786501d12 0   Makefile
100644 046dcab7645305cbf4b94adef54a859234ac3caa 0   README
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0   lib/__init__.py

全零值列表示这些文件中的每一个都在槽零中。

但是,在冲突合并期间,README 或其他名称之一可能在插槽 1、2 和 3 中有最多三个条目。在这种情况下,Git 知道存在合并冲突。插槽 1 包含文件的合并基础版本。插槽 2 保存 HEAD--ours 版本,插槽 3 保存同一文件的 MERGE_HEAD--theirs 版本。 (根据定义,插槽零此时未被占用。)

这些插槽中有几个可以是空的。 (我曾经说过最多有一个可以为空,但我错了:在 rename/rename 或 rename/delete 冲突中,我们实际上可以看到多个空槽。)空槽或插槽表示 no 文件在合并的三个输入中的一个或多个中具有该名称。但是,存在 any 个更高编号的条目,表明正在进行合并冲突。

如您所见,解决这些冲突是您的工作。指定文件的工作树版本通常包含 Git 解决合并的最佳尝试,但由于 Git 无法解决冲突,工作树版本有冲突标记在里面。解决冲突后,您应该像往常一样在路径上 运行 git add(如果解决方案是删除文件,则 git rm)。这将清除更高阶段的槽,同时还将工作树文件复制到槽零,除非该文件确实被删除。现在冲突已经解决了。

如果您正在进行合并,尚未提交结果,并且已经编辑甚至解析了文件但希望将其恢复到原始未合并状态,您可以如您所述使用:

git checkout -m -- <path>

(或与--conflict相同)。您可以添加 =<style>,它允许您指定冲突样式:mergediff3(我更喜欢 diff3,它包括文件的合并基础版本中的文本) .这将删除阶段零条目(如果您创建了一个条目),并恢复更高阶段的冲突条目。不过,git checkout 的这种特殊形式 要求 原始未合并的条目在索引中可用。

在任何情况下,您不能 进行新的提交,直到所有更高阶段的索引条目都已解析。也就是说,如果 git ls-files --stage 显示任何 不是 阶段零的条目,您可能不会进行新的提交。

... as soon as it gets pushed to a remote, this [git checkout -m] command no longer works to restore the merge flags.

事实上,那是很久以前的事情了。一旦你承诺,恢复冲突的能力就会消失。这将永久清除所有更高阶段的索引条目。但是您不能推送索引,也不能推送文件:您只能推送提交。这意味着要推送部分合并(让其他人处理它),您必须解析合并并提交。现在它不再进行中,也不能再进行中了。

我们需要的是一个可以保存完整索引状态、工作树文件、合并状态(包括两个提交 HEADMERGE_HEAD 的 ID 的工具——这意味着合并基础的 ID——甚至可能是未跟踪的 and/or 被忽略的文件(a la git stash)到一个特殊的提交或一组存储在非分支引用上的提交。然后可以将此提交或这些提交从一个存储库转移到另一个存储库。同一工具的反向版本可以恢复合并状态、索引状态和工作树。构建这样一个工具所需的所有组件都存在(因为 git ls-files --stagegit update-index 都存在)。但是编写这个工具对会很复杂,可能至少和 git stash 脚本一样困难。