鉴于您想保留与原始A--B--C--D 系列提交相关的树,毕竟您真的不想rebase。变基意味着将提交转换为差异(变更集),然后将这些变更集一次一个地应用到某个现有的起点——但您要做的就是将附加到 A 的树复制到您的新提交 A'其父级为H,然后将附加到B 的树复制到其父级为A' 的新提交B',依此类推。
这是git filter-branch 运作良好的地方。运行时:
git filter-branch <filter-list> <branch-name>
Git 会从给定的<branch-name> 中找到每个可访问的提交,然后复制这些提交中的每一个。无论如何,从逻辑上讲,复制是通过按原样提取整个提交,运行<filter-list> 中的每个过滤器,然后使用生成的树和消息进行新的提交来完成的。它以与 Git 正常顺序相反的顺序运行复制过程,即“向前遍历历史”,而不是向后。
如果 新的提交(带有可能更改的可能不是树、可能更改的可能不是父级、可能更改的可能不是消息等)为 100%与原始提交逐位相同,新提交的哈希 ID 不变。在这种情况下,next 提交的默认“新父级”与原始父级相同。否则下一次提交的默认“新父级”就是我们刚刚创建的那个。
(在实践中,因为提交图可以发散和再次合并,并且因为您可以跳过提交或添加新提交,所以 filter-branch 真正做的是将旧提交哈希 映射 到新的提交哈希。每次复制时,它都会在这个映射中输入一对:。不过,对于一个简单的线性链,你可以认为这只是记住最近提交的新哈希 ID。)
现在,您遇到的问题是您想要更改一个特定提交的父哈希 ID,即根提交。有一个专门用于此的过滤器,--parent-filter。还有两种方法可以做到这一点,但让我们先描述--parent-filter。这是来自the git filter-branch documentation:
--父过滤器
这是重写提交的父列表的过滤器。它会
在标准输入上接收父字符串并输出新的父字符串
标准输出上的字符串。父字符串采用中描述的格式
git-commit-tree(1):初始提交为空,“-p parent”为
正常提交和“-p parent1 -p parent2 -p parent3 ...”
合并提交。
因此,您可以测试标准输入是否为空,如果是,则输出-p <hash-of-H>。结果是:
E--F--G--H--A'-B'-C'-D' <-- master
(不完全符合您的要求,但可能更好)。
(要复制E-F-G-H 链,您还必须将master-old 作为正向引用传递,并且由于任何逐位相同的提交都必须具有与原始相同的哈希ID,您将必须至少进行一项更改才能提交E,例如将提交者 tiemstamp 更改一秒。)
这里值得一提的是其他两种方法。一种是使用--commit-filter:这是实际进行新提交的命令。你可以在这里做任何事情,包括完全省略一些提交;但是所有 other 过滤器的原因是为了让事情更容易,所以在这种情况下根本没有理由使用提交过滤器。
使用git replace
最后是the git replace command。 git replace 所做的是创建留在存储库中的新对象,由 refs/replace/ 命名空间中的特殊名称引用。每当 Git 通过其哈希 ID 查看某个对象时,Git 通常首先检查refs/replace/<hash-id> 是否存在。如果是这样,Git 会查看该引用指向的对象。
这意味着你可以构造一个新的 Git 对象,它与 commit A 非常相似,但略有不同。细微的差别是新提交对象中存储了一个父哈希 ID。父哈希 ID 是提交 H 的 ID。 (请注意,它与A 具有相同的树。)
现在你有了这个新对象——我们称之为A'——你将它粘贴到存储库中并让refs/replace/<big-ugly-hash>指向它:
A--B--C--D <-- master
E--F--G--H <-- master-old
\
A' <-- refs/replace/deadcabf001...
(基于A 的实际哈希,可能不是真正的deadcabf001...,因此请在此处使用正确的ID)。
当git log去查看从commit D开始的历史,它会查看commit D,然后得到D的父IDC,查看提交C,获取B 的ID,然后继续提交B,获取A 的ID 和...哇,嘿,这个有refs/replace/!毕竟我们不要看A!一起来看看A'!它会将A' 显示为B 的父级,然后转到A' 的父级并显示H,然后是G,依此类推。
当您使用 git replace 时,您不必复制任何其他提交。您拥有的是一个提交历史,其中新的“更好”提交取代了旧的“不太好”的一个,但两者实际上并存。 Git 在这些情况下使用替换:
- 当然,它必须有替换对象;
- 它一定是要查看一个带有一些散列散列的对象,但在引用中找到
refs/replace/<em>hash</em>;和
- 它必须以正常方式运行,而不是
git --no-replace-objects。
要求 3 允许您查看原始(未替换)历史记录(如果您愿意)。第 2 项表示在 git clone 上,默认情况下您不会得到替换。您必须明确要求它们(这并不难,但也没有任何好的简单前端)。
使用过滤器分支替换
由于上述第 2 项,您可能想要进行替换,确保一切正常,然后然后运行git filter-branch。由于您没有运行git --no-replace-objects filter-branch,Git 将看到替换 提交A',而不是原始提交A。因此它将复制A' 而不是A。您不需要--parent-filter。当它复制E 到H 时,新副本将与原始副本逐位相同,因此它们将保持不变。最终结果将与您使用正确的父过滤器运行 git filter-branch 相同。