我怎样才能有不同的提交来进行合并和冲突解决

2024-04-12

我将开发分支合并到我的功能分支中,这导致在解决我提交和推送的分支后出现合并冲突,现在的问题是合并和冲突解决更改位于一次提交中,很难找到解决冲突的方法。当存在合并冲突时,如何才能有两个单独的提交,一个用于合并,另一个用于冲突修复?


如果你真的想这样做,你可以——好吧,mostly。 Git 让它变得相当困难,我认为这不是一个好主意。有些冲突无法通过这种方式捕获。

我将提供如何捕获您可以捕获的内容的概要,但不会提供其实际代码。相反,我将描述设置是什么以及会出现什么问题。

Long

这里的问题是这样的:

  • Git 构建new从 Git 中出现的文件提交index (aka 暂存区).
  • 合并冲突,带有冲突标记,仅出现在你的工作树.

使所有这一切都有意义的部分 - 因为上面的内容没有意义,除非并且直到你知道这另一部分 - 是当你没有处于冲突合并的中间时,有three每个文件的活动副本。

请记住,提交充当快照:它们拥有每个文件的完整副本。但是提交中任何给定文件的快照副本都以特殊的、只读的、仅限 Git 的格式存储。从字面上看can't更改后,除 Git 之外的任何程序都无法使用它。因此,当您使用git checkout or git switch要选择某个特定的提交来查看和处理/使用,Git 必须将文件从提交复制到工作区:您的工作树 or 工作树。这些文件是普通的日常文件。提交的文件仍然存在,在当前提交,因此这提供了每个文件的两个副本:

  • 当前提交中存在冻结的:HEAD:README.md例如。跑步git show HEAD:path看见了。

  • 并且,里面有正常的日常文件README.md:使用您喜欢的任何查看器来查看它,以及您喜欢的任何编辑器来更改它。

But in between these two, Git keeps a third copy1 of the file. This copy is in Git's index, which Git also calls its staging area. This copy is in the frozen format—but unlike a committed copy, you can replace it, wholesale, with a new copy. That's what git add does: it takes the work-tree copy, compresses it into the special Git format, and puts that copy into Git's index, ready to be committed.

  • 要查看索引副本,请运行git show :path, e.g., git show :README.md.

通常索引副本将匹配HEAD副本(因为您刚刚签出了提交,或者刚刚提交)或工作树副本(因为您刚刚git add-ed 文件),或者将匹配both其他副本(git status says nothing to commit, working tree clean)。但可以:

  • 检查一些提交(所有三个都匹配)
  • 修改工作树文件(HEAD和索引匹配,工作树不匹配)
  • git add修改后的文件(HEAD 不匹配,索引和工作树匹配)
  • 再修改一下文件

现在三个副本都不同了。这里没有什么根本性的错误:这就是 Git 的工作方式,并且git add -p and git reset -p旨在让您故意操纵这种情况。它们的工作原理是将文件的索引副本复制到临时文件,然后让您修补此临时文件,一次一个差异块,然后将其复制回索引副本。

无论如何,这就是normal设置,当您没有处于冲突合并中时:

  • HEAD代表当前提交,当前提交拥有您无法更改的每个文件的副本。您可以更改哪个提交是当前提交(通过检查一些othercommit),但您无法更改存储在这些提交中的文件。可以轻松访问HEAD已提交文件的副本,以及git status and git diff等等就会看这些副本。

  • 索引存储每个文件的副本。你can更改这些副本。通常,您可以通过将工作树文件复制到索引副本中来实现这一点,就像现在一样,使用git add。或者,您可以通过复制来更改它HEAD复制回索引,使用git reset.

  • 工作树存储每个文件的副本。这个副本是yours:只有当您告诉 Git 覆盖它时,Git 才会覆盖它。当你git commit:Git 使用 Git 中的副本index.

但是,当进入冲突合并状态时,索引已被扩展。而不是仅仅包含one冲突文件的副本,它现在包含three。现在,事情变得棘手了。


1Technically, the index holds references, rather than actual copies, but the effect is the same, unless you start using git ls-files --stage and git update-index to delve into the low level details.


与冲突合并

正如您所发现的,当您运行时:

git checkout somebranch
git merge other

有时 Git 能够自行合并并完成,有时它会some合并完成,但吐出一些CONFLICT消息并在合并过程中停止。

实际上有两种不同的kinds冲突,我喜欢称之为高水平 and 低级。大多数人首先遇到的冲突是低级别的冲突,因为它们是最常见的。它们是在 Git 中生成的ll-merge.c代码,其中ll代表“低级”,因此得名。

Git 中的合并使用了相当标准的三路合并算法 https://en.wikipedia.org/wiki/Merge_(version_control)#Three-way_merge。 Git 实际上默认使用递归变体;你可以使用禁用它git merge -s resolve,但很少有任何理由这样做。任何三向合并都需要三个输入文件:公共(共享)合并基础版本、左侧或本地或--ours版本,以及右侧或远程或--theirs版本。合并只是将基数与左侧和右侧进行比较。这会产生一系列需要进行的更改。合并combines更改:如果左侧修正了第 42 行某个单词的拼写,则接受该更改;如果右侧删除了第 79 行,则也进行该更改。

冲突——或者更具体地说,低级冲突——当左侧和右侧尝试对单个文件的同一区域进行不同的更改时就会发生冲突。这里 Git 根本不知道是采取左侧改变、右侧改变、两者都改变​​还是都不改变。因此,它会因冲突而停止合并(在继续合并它可以自行合并的任何其他内容之后)。

高水平当整个文件发生更改时,就会发生冲突。也就是说,左侧的变化可能包括方向:rename README.md to README.rst。如果右侧没有重命名README.md,或者确实将其重命名为README.rst也没关系。但如果右边说怎么办rename README.md to README.html:Git 应该如何结合these变化?

同样,Git 只是放弃并声明冲突。不过,这一次,是一个高水平冲突。

在这两种情况下,Git 在 Git 中所做的事情index很简单:它只是保留所有副本。能够区分出三种不同的README.md文件分开——假设没有复杂的重命名冲突——它只是对文件进行编号在索引中:

  • git show :1:README.md显示合并基础版本;
  • git show :2:README.md向您展示--ours版本;和
  • git show :3:README.md显示 --theirs` 版本。

Git 写出一个新的README.md带有冲突标记的工作树副本,但原始的三个输入仍然存在于索引中。作为完成合并的人,您的工作不一定是修复问题工作树复制。 Git 不需要那个副本:那个是为了you。 Git 需要最终版本,在 Git 的索引中。

上面的索引号是插槽编号,最终副本进入插槽 0,这会擦除其他三个插槽。你的工作就是想出正确的方案README.md并将其放入零号槽中。

一种简单的方法是编辑工作树README.md– 完成其冲突标记 – 直到获得正确的合并结果。然后,将此文件写回工作树并运行git add README.md。那个复制README.md像往常一样从工作树进入索引:副本进入插槽零,擦除其他三个插槽。

其他三个槽条目的存在——:1:README.md, :2:README.md,和/或:3:README.md— 是将文件标记为矛盾的。现在它们都消失了,文件不再存在冲突。

您可以使用任何您喜欢的程序将正确的文件放入插槽 0。这就是 Git 真正关心的一切:正确的文件进入插槽 0,而其他三个插槽被删除。一个奇特的工具,由git mergetool,可能对您来说很方便,但最终,它的工作原理是将最终结果复制到插槽零并擦除其他插槽。 Git 根本不关心你的工作树文件; Git 只需要修复它的索引。

当你得到一个高水平冲突,例如重命名/重命名冲突或修改/删除冲突,Git 也会将其记录在 Git 的索引中 - 但这次,记录是通过以下事实来记录的:有一些槽位aren't占据。请记住,插槽与文件源一致:合并基础 = 插槽 1,我们的 = 2,他们的 = 3。因此,如果合并基础有README.md, 我们有README.rst,并且他们有README.html,你最终得到的是:

  • :1:README.md存在,但 :2: 和 :3: 不存在
  • :2:README.rst存在,但 :1: 和 :3: 不存在
  • :3:README.html存在,但 :1: 和 :2: 不存在

你的工作是删除所有这三个并放入一些东西some插槽零。它不必被命名README.md or README.rst或其他什么:也许您可以创建一个名为的零槽文件README.who-knows.

当您进行新的合并提交时,它将包含插槽零中的所有文件。你cannot进行提交直到all编号较高的暂存槽已被清除。那么你must自己解决每个冲突的文件:只有这样你才能运行git merge --continue or git commit做出最终的合并提交结果。

You can只需运行git add在所有冲突的文件上。如果工作树有低级冲突README.md其中,使用冲突标记,将工作树版本复制到索引槽零并擦除其他三个槽。如果这是唯一的冲突,那么您现在就可以承诺了。问题是您丢失了所有三个输入文件:您必须稍后重新合并并解决冲突。但是你can只需使用git add在每个文件上,然后提交。

这对于高级冲突来说效果不太好:如果存在重命名/重命名冲突,您应该使用哪个名称?如果出现修改/删除冲突,是保留修改的文件,还是保留删除的文件?

无论您在这里选择什么,您have解决了那个冲突。合并提交将存储您放入槽零索引条目中的任何内容作为其新快照。

如果您已经存储了冲突的文件,并且希望恢复冲突,那么唯一的方法就是重新执行合并,或者等效地,保存合并冲突数据(输入文件和/或索引)。目前还不清楚哪一个更容易:两者都有很多潜在的问题。我认为陷阱最少的一个是使用git merge-file,它对三个输入文件运行低级合并。

结论

因此,对于每个低级冲突文件,您可以:

  1. 将文件的三个副本提取到某处。 (笔记:git checkout-index有选项可以做到这一点。就是这样git mergetool向您的合并工具提供三个副本。)
  2. git add工作树中的冲突文件,以解决冲突,将标记版本作为正确的解决方案。
  3. Run git merge --continue提交合并。
  4. Use git merge-file在步骤 1 中保存的文件上,重新创建冲突。
  5. 手动解决冲突。
  6. git add生成的文件,将它们复制到 Git 的索引。
  7. 进行新的提交。

这是一个需要做大量工作的事情,而 Git 无法做到这一点,而且它不能很好地处理高层冲突。其他 Git 工具会假设提交包含正确的解决方案,因此您为其他假设该工具知道正确内容的人设置了陷阱。至少我不清楚why你想这样做——为什么有人会想这样做——当你稍后可以通过运行发现相同的冲突时:

git checkout <hash>
git merge <hash>

哪里两个hashvalue 是两次提交的哈希 IDsomebranch and otherbranch在您运行原始版本时确定git merge命令。这两个哈希值很容易从合并提交本身找到:它们分别是它的第一个和第二个父级。因此如果$M包含合并哈希 ID:

git rev-parse $M^1 $M^2

显示重复合并以重新获取冲突所需的两个哈希 ID。这里唯一缺少的是您提供给的任何选项git merge命令。 Git 不会保存它们(我认为应该保存)——但如果没有别的办法,您可以手动将它们保存在日志消息中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

我怎样才能有不同的提交来进行合并和冲突解决 的相关文章

随机推荐