假设我在仅限本地的分支上有以下版本历史记录:
A -- B -- C
如何在A和B之间插入新版本X,以便版本历史记录如下所示:
A -- X -- B -- C
注意,在how to insert a commit in the past上存在类似的问题,但在我的情况下它有点不同:X引入的任何更改都不会传播到B,在A之间插入版本X之后应保持不变和B.
答案 0 :(得分:3)
如果你想添加提交,但是它对源没有影响,那么是的,你需要一个不同于交互式rebase的路径。
没有专门针对此设计,但使用git replace
相对容易:
A
(作为分离的HEAD,或在新分支上,但新分支很快就会无用):git checkout <hash-of-A>
。X
(但您希望它出现)。git replace --graft <hash-of-B> HEAD
。您现在拥有这段实际历史:
X--B' <-- refs/replace/<hash-of-B>
/
A--B--C <-- whatever-branch-this-is
这些&#34;替换&#34;有什么特别之处?对象是每当Git几乎可以对任何对象做任何事情时 - 包括&#34;使用提交用于任何目的&#34;,例如-Git将查看是否存在refs/replace/
名称对象的哈希ID。由于 是B
的替代品,因此Git现在将改为B'
。因此,git log
和其他Git命令将表现为历史记录:
A--X--B'--C
但请注意,此存储库中仍存在真实历史记录。当且仅当<{em> refs/replace/
名称位于存储库中时才使用替换历史记录 - 当然它位于此存储库中 - 并且已启用替换(这是默认情况下)。但是,如果您将此存储库克隆或推送到其他位置,则提取或推送过程通常不会传输任何refs/replace/
名称;因此,此存储库的克隆将切换回旧历史记录。 (您也可以通过禁用替换来查看原始历史记录,例如使用git --no-replace-objects log ...
。)
如果您愿意,现在可以运行git filter-branch
,它只是复制提交。默认情况下,git filter-branch
遵守替换规则。这意味着当它复制分支上的提交时,它将以A
开头,然后复制X
,然后复制B'
,然后复制C
指向{{ 1}}。 B'
,A
和X
的副本将与原始文件一点一点地相同,因此实际上会重复使用原件;但B'
的副本会略有不同:它会使用C
作为其真正的父级,而不是替代的移植父级。因此,提交B'
将被复制到新提交C
,并且分支名称(无论它是什么)将被指向新副本。
此已过滤的分支包含实际(不仅仅是嫁接)历史记录,该历史记录会遍历C'
到B'
到X
,所以现在如果您克隆已过滤存储库,克隆中看到的历史记录同样从A
开始,然后从C'
返回到B'
到X
。