Git rebase跳过相同的提交记录

3
根据 git rebase doc

If the upstream branch already contains a change you have made (e.g., because you mailed a patch which was applied upstream), then that commit will be skipped. For example, running git rebase master on the following history (in which A' and A introduce the same set of changes, but have different committer information):

      A---B---C topic
     /
D---E---A'---F master 

will result in:

               B'---C' topic
              /
D---E---A'---F master
我无法理解的是这种优化的必要性是什么? 如果 Git 进行了变基操作,例如:
               A''---- B'---C' topic
              /
D---E---A'---F master

可能出了什么问题?(A = A' = A'')
编辑:为了让自己更清楚,我们假设 F 撤消了 A' 的更改。现在 rebase 将不会在 F 上应用 A 补丁,只会应用 B' 和 C'。在这种情况下,git rebase 的行为可能是意外的,对我来说没有意义。
3个回答

7
可以有多个存储相同源码树的连续提交。在此示例中,由于A''对F没有任何变更,因此会得到这种情况;从E到F的差异已经包含了由E到A'的差异引起的相同变更。
换句话说,在A''中的快照与F中的快照相同。Git将这种提交成对称为空提交:如果父提交和子提交相同且子提交不是合并提交,则Git称该子提交为空(尽管它仍然具有完整的快照)。使用git commit命令创建这种提交需要标志--allow-empty。
此外,A''中的提交消息可能与A'中的提交消息相同。如果是这样,A''在所有有用的方面都是冗余的。消除它可能是最好的做法。
请注意,git rebase最初是使用git format-patch实现的,该程序将每个提交与其父提交转换为差异,并添加文本以使补丁可通过电子邮件发送。一个名为git am的单独程序读取“邮箱格式”文件(一系列电子邮件消息)并应用补丁,使用附加文本来重新生成提交作者、作者日期和日志消息。但是,format-patch和git am拒绝使用空补丁。因此,最初版本的rebase使用这两个程序来实现提交复制,必须丢弃这些“空”提交。
现代的git rebase具有多个后端,不仅限于git am。这些可以创建或复制空提交。根据您的特定Git版本,您可能需要向git rebase提供-k标志,以使rebase故意创建此类提交。如果您的Git版本使用git am作为默认后端,则git rebase -k将强制rebase使用交互式或合并后端,而不是git am后端(当然还修改了其初始设置,告诉它不要自动丢弃“空”提交)。
Git版本2.26及以后默认使用基于合并的后端,不需要-k标志来保留空提交。现在使用-k意味着保留最终成为空的提交,而不是最初为空的提交。

您还可以手动完成rebase,使用单个git cherry-pick命令或使用序列执行批量挑选。与rebase一样,默认情况下,cherry-pick拒绝复制“空”提交,需要--allow-empty和/或--keep-redundant-commits标志来保留此类提交。


1git format-patch无法格式化合并提交,因为它们具有多个父项。


1
正确:rebase 选择复制那些在对称差异中没有补丁ID等效项的提交,并且 A 在上游有一个补丁ID等效项 A',在您的示例中。这是一种机制,而不是一种策略,但它达到了一个未声明的策略目标:即上游接受后跟上游撤消意味着您的补丁将不会被包含。 - torek
1
我认为(我没有参与这个特定的历史,所以只是猜测)rebase 最初只是执行 git rev-list 来获取 format-patch 的提交记录(或直接调用 format-patch),将结果传输到 git am -3,然后有人发现上游的 cherry-pick 失败了。因此,修改了 git rebase 以省略上游的 cherry-picks。这被认为是正确和可取的,并一直保持这种方式。它失败(或曾经失败)的事实已经成为一种设计选择。但这只是猜测:它一个有效的设计选择,并符合我所描述的策略。 - torek
2
我并没有自己做出这个决定,但实践证明它还是很有效的。不过对于那些特殊、奇怪的情况,最好还是意识到一下这点。始终要检查rebase的结果是否仍然良好,例如是否通过了自动化测试。如果失败了,请记住您可以使用“ORIG_HEAD”或参考日志回滚,并手动正确地重新进行。 - torek
2
此前的对话 - jthill
1
@bdukes:旧的-k选项适用于旧的git rebase;新的git rebase仍然有一个-k,但它的工作方式有些不同,因为常规的rebase已经保留了“空”提交,而-k只是强制rebase保留一个新的提交,该提交变为空。我稍微更新了答案。 - torek
显示剩余6条评论

1
首先,它不会是A .... 而是A''.... 但真正的“问题”在于修订将是空的,因为这些更改已经应用于A'。但如果您想要保留历史记录,可以这样做。

1
您的最后一个模式是不可能的状态。(或者至少曾经是这样,但现在您编辑了问题,所以不再明显) A 以其父级 E 为根,这永远不会改变。如果改变了,那么 A 将不再是 A,因为提交是(内容+元数据)的哈希结果。
顺便说一下,这就是为什么在这里使用 A' 的原因。
但是回答标题问题,跳过这些提交是避免冗长历史记录的一种方法,有可能会有很多空提交(当然这取决于工作流程)。

为什么A''必须为空? - Number945
如果它检测到更改已经存在,它就不会应用任何内容。我只是对这种行为提出质疑。为什么它必须像这样表现? - Number945
1
由于在正常的开发环境下,为什么要添加/删除/修改已经以完全相同的方式添加/修改/删除过的代码呢?因此......在一行代码中,您有i ++,而在A中,您将其更改为i + = 2。太好了......你挑选并创建了A'。很棒吧......现在代码说i+=2。您想重新应用A的最新版本....您希望它怎么说? i+=4吗?它会注意到代码已经说i+ =2,并且Git将对此感到满意。 - eftshift0
1
没错...在这种情况下,Git会像往常一样应用更改,并且不会抱怨更改为空。 - eftshift0
1
它将被应用!Git只是不知道A'是A的类比,所以它会尝试应用A...在正常情况下,它将被跳过(git会抱怨A不必要,因为它已经应用了...不是因为A'在那里,而是因为内容会指向那个方向)...或者,在您还原的情况下,更改将被预期应用...但这是一个特殊情况。不知道为什么会有这么多麻烦,因为这两件事情都很合乎逻辑。 - eftshift0
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接