我有一个存储库,其中有两个分支,master和master-old,它是作为孤儿分支创建的。
现在我想对整个masteron进行变基master-old,但每个提交的树应该保持不变,即每个提交的工作副本在变基之前master和master-old之后看起来应该完全相同。
Current state
-------------
A - B - C - D <--- master
E - F - G - H <--- master-old
Desired state
-------------
E'- F'- G'- H'- A'- B'- C'- D' <--- master
Run Code Online (Sandbox Code Playgroud)
我尝试使用 来完成此操作git rebase --onto master-old --root。问题是,在 的初始提交master和整个提交历史记录中master-old,创建了许多相同的文件,因此我需要解决大量冲突。
有没有办法以保持每次提交树完整的方式重写历史记录?
考虑到您想要保留与原始提交系列相关的树A--B--C--D,您根本就不想进行变基。变基意味着将提交转换为差异(变更集),然后一次一个地应用这些变更集到某个现有的起点\xe2\x80\x94,但您所需要做的就是将附加到新的树的树A复制提交A\'其父级为H,然后将附加到的树复制到其父级为 的B新提交,依此类推。B\'A\'
这是git filter-branch运作良好的地方。当你跑步时:
git filter-branch <filter-list> <branch-name>\nRun Code Online (Sandbox Code Playgroud)\n\nGit 找到从给定的可到达的每个提交<branch-name>,然后复制每个提交。无论如何,从逻辑上讲,复制已完成,方法是按原样提取整个提交,运行 中的每个过滤器<filter-list>,然后使用生成的树和消息进行新的提交。它以与 Git 正常顺序相反的方式运行复制过程,即“向前历史记录”,而不是向后。
如果新提交(及其 Maybe-altered-maybe-not 树、maybe-altered-maybe-not 父级、maybe-altered-maybe-not 消息等)与原始提交 100% 逐位相同,新提交的哈希 ID 不变。在这种情况下,下一个的默认“新父级”与原始父级相同。否则,下一次提交的默认“新父级”就是我们刚刚所做的提交。
\n\n(实际上,因为提交图可以再次发散和合并,并且因为您可以跳过提交或添加新提交,所以过滤器分支真正所做的是将旧提交哈希映射到新提交哈希。每次创建副本时,它会在该映射中输入一对:<old-hash, new-hash>。不过,对于简单的线性链,您可以将其视为仅记住最近提交的新哈希 ID。)
\n\n现在,您遇到的问题是您想要更改一个特定提交(即根提交)的父哈希 ID。有一个专门用于此目的的过滤器,即--parent-filter. 还有两种方法可以做到这一点,但让我们描述第--parent-filter一种。这是来自文档git filter-branch:
\n\n\n--parent-filter <命令>
\n\n这是用于重写提交的父列表的过滤器。它将在 stdin 上接收父字符串,并在 stdout 上输出新的父字符串。父字符串采用 git-commit-tree(1)中描述的格式:初始提交为空,“-pparent”表示正常提交,“-pparent1 -pparent2 -pparent3..” ." 用于\n 合并提交。
\n
因此,您可以测试 stdin 是否为空,如果是,则输出-p <hash-of-H>。结果将是:
E--F--G--H--A\'-B\'-C\'-D\' <-- master\nRun Code Online (Sandbox Code Playgroud)\n\n(不完全是你所要求的,但也许更好)。
\n\n(要E-F-G-H复制链,您还必须master-old作为正引用传递,并且由于任何逐位相同的提交都必须具有与原始相同的哈希 ID,因此您必须至少对犯罪E,例如将提交者 tiemstamp 更改一秒。)
这里值得一提的是另外两种方法。一种是使用--commit-filter:这是实际进行新提交的命令。您可以在这里做任何事情,包括完全省略一些提交;但所有其他过滤器的原因是为了让事情变得更容易,所以在这种情况下根本没有理由使用提交过滤器。
git replace最后是命令git replace。所做git replace的就是创建保留在存储库中的新对象,并通过refs/replace/名称空间中的特殊名称进行引用。每当 Git 通过哈希 ID 查看某个对象时,Git 通常首先检查是否refs/replace/<hash-id>存在。如果是这样,Git 会查看该引用所指向的对象。
这意味着您可以构造一个与 commit 非常相似A但略有不同的新 Git 对象。细微的差别是新的提交对象存储了一个父哈希 ID。父哈希 ID 是 commit 的哈希 ID H。(请注意,它与 具有相同的树。A)
现在您有了这个新对象\xe2\x80\x94,我们将其称为A\'\xe2\x80\x94,您将其粘贴到存储库中并指向refs/replace/<big-ugly-hash>它:
A--B--C--D <-- master\n\nE--F--G--H <-- master-old\n \\\n A\' <-- refs/replace/deadcabf001...\nRun Code Online (Sandbox Code Playgroud)\n\n(基于A实际的哈希值,这可能不是真正的哈希值deadcabf001...,因此请在此处使用正确的 ID)。
当git log查看从 commit 开始的历史记录D时,它将查看 commit D,然后获取D父 ID C,查看 commit C,获取BID,然后继续提交B,获取AID ......哇,嘿,有refs/replace/这个!咱们还是别看了A!我们来看看吧A\'!它将您显示A\'为B\ 的父级,然后移动到A\'\ 的父级并显示您H,然后显示G,依此类推。
当您使用时,git replace您不必复制任何其他提交。 您拥有的是一份提交历史记录,其中新的“更好”提交取代了旧的“不太好”提交,但两者实际上是共存的。Git 在以下条件下使用替换:
refs/replace/hashgit --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最终结果将与使用正确的父过滤器