合并后Git提交丢失

ren*_*iaL 18 git

我们有3个分支(A,B,C)如下:

---\--A1--\------Am------Merge1---An---Merge2---
    \      \              /             /
     \      \--C1---C2---/             /
      \                               /
       \--B1--------------Bn---------/
Run Code Online (Sandbox Code Playgroud)

问题出现在Merge2上.在Merge1和Merge2之间出现的Merge2之后,分支C上的一些提交(不是全部,但是只有一些,比如说C2)会在分支A上丢失.

在执行Merge2时,只有一个文件冲突,这与丢失的提交(C2)无关.我们解决了这个问题并成功完成了合并.

似乎C2在分支A上被Merge2反转,没有任何日志.

发生了什么?这种情况可能是什么原因?

tor*_*rek 19

你的意思不是提交本身丢失,而是在该提交中(某些文件)所做更改已被还原(未制作).

值得注意的是,提交并不真正"对文件进行更改".相反,每个提交都存储一整套完整且完整的文件.当你要求Git向你展示一些提交时(让我们说它的ID是c2c2c2...):

$ git show c2c2c2
Run Code Online (Sandbox Code Playgroud)

Git的作用是:

  1. 提取提交 c2c2c2...
  2. 提取父提交 c2c2c2...
  3. 产生从父母到孩子的差异列表

这就是Git如何设法向您展示改变的内容:它将"您提交之前的内容"与"您提交的内容"进行比较.(Git可以很快地进行优化,因为每个文件都被简化为一个独特的散列"指纹",Git可以先比较指纹(散列).如果散列相同,则文件是相同的.如果哈希值不同,它只需要提取实际文件数据.)

这种节省流程的整个文件,而不是累积更改,称为"存储快照".(其他版本控制系统倾向于积累变化,这称为"存储增量".他们这样做是因为保存对文件的更改显然比保存文件所需的空间少得多.Git巧妙地围绕这个问题偷偷摸摸地使用无论如何,磁盘空间比旧的基于delta的版本控制系统少.)

为什么"Git存储快照而不是增量"在这里非常重要

一个合并提交是特殊的,在一个特定的和明显的方式.看看你自己的图并考虑一下Merge1.在它之前发生了什么?

这里的答案是,无论AmC2来"正确之前" Merge1.也就是说,commit Merge1两个父提交.

如果您要求Git向您显示提交Merge1,哪个父级应该与之进行比较?

事情变得特别奇怪.这两个指令git log -pgit show 看起来非常相似.事实上,他们非常相似的.一个明显的区别是git log -p显示多个提交,而git show只显示您告诉它显示的提交.您可以运行git log -n 1 -p <commit>以显示一个提交,现在看起来这些完全相同.

他们不是!

当您git show在合并提交上使用时,Git会尝试通过同时对两个父项进行比较来解决"比较什么提交"问题.产生的差异称为组合差异.

但是当你git log -p在合并提交中使用时,Git只是抛出了它的隐喻之手,说"我不能显示针对两个父母的补丁",然后放弃并继续进行下一次提交. 换句话说,git log -p甚至不打算尝试合并的差异.

但等等,还有更多

现在,在这种情况下,你可能会想知道你是否可以c2c2c2...通过git show两次合并来确定你的文件发生了什么,特别是on Merge2,其中的变化被还原了.但是git show默认情况下会产生组合差异,而组合差异会故意省略很多差异输出.特别是,组合差异仅列出从所有父项修改的文件.

假设您的更改所在的文件C2是文件f2.并且,从图中,两个父母Merge2An(有f2你想要它的方式)和Bn(它没有).

这里实际发生的是,在创建的合并期间Merge2,你以某种方式告诉Git使用f2from commit 的版本Bn.也就是说,文件f2Merge2是完全一样的文件f2Bn,并从不同f2的承诺An.

如果您使用git show查看Merge2,组合差异将跳过 f2,因为它与in 相同.f2Bn

同样如此,更糟糕的是,git log -p它完全跳过合并,因为它太难以显示差异.

即使没有-p,当你要求"文件改变"时,git log最终会做同样的事情 - 完全跳过合并.这就是为什么你不能在日志输出中看到它.

(顺便说一句,原因git log master -- f2从来没有显示提交C2本身是为选项添加文件名以git log打开"历史简化".在我认为有点错误的行为,Gi​​t最终简化了太多的历史,所以它永远不会显示提交C2.--full-history-- f2恢复之前添加C2到显示的提交集.但是,合并仍然缺失,因为git log跳过它.)

如何看待变化

有一个解决方案.两者git showgit log采取额外的标志,-m"拆分"合并.也就是说,不是将其Merge2视为合并提交,而是将合并分解为两个"虚拟提交".一个将是" Merge2vs An",您将看到这两个提交之间的所有差异,另一个将是" Merge2vs Bn",您将看到两个提交之间的所有差异.这将显示文件已f2重新设置为其所处的方式Bn,C2从而显示An但不在其中的版本Bn.

(包括--full-history以及-m确保提交C2也会出现.)

这首先是怎么回事

这一部分根本不清楚.你说有一个合并冲突,这意味着git merge停止并获得人工的手动帮助.在这个过程中帮助某些时候,人类大概有更新的版本文件的索引f2Bn(或至少,一个版本f2是没有在发回的变化C2).

这可能在合并期间发生,并且它有点阴险,因为 Git显示与这些压缩(组合)差异合并,或者在git log -p默认情况下,根本不合并.这是值得注意的,特别是如果合并需要手动冲突解决.在大多数情况下,捕获此类合并错误的方法是使用自动化测试.(当然,并非所有东西都可以测试.)