git之将整个git分支重新设置为孤立分支,同时保持提交树完整

jyk 阅读:67 2024-12-31 21:38:35 评论:0

我有一个仓库,其中有两个分支mastermaster-old,它们是作为孤立分支创建的。

现在,我想将整个master重新设置为master-old,但是每个提交的树都应保持不变,即在重新设置基础之前和之后,每个提交在mastermaster-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 

我尝试使用 git rebase --onto master-old --root完成此操作。问题在于,在最初提交给 master的整个提交历史记录和整个 master-old的提交历史记录中,都创建了许多相同的文件,因此我需要解决大量冲突。

有没有办法以使每个提交的树保持完整的方式来重写历史记录?

请您参考如下方法:

既然您想保留与原始A--B--C--D系列提交相关联的树,那么您根本就不希望重新设置基准。变基意味着将提交转换为diff(变更集),然后一次将这些变更集应用到某个现有起点上,但是您要做的就是将附加到A的树复制到父A'的新提交H ,然后将附加到B的树复制到其父级为B'的新提交A'中,依此类推。

这是git filter-branch运作良好的地方。运行时:

git filter-branch <filter-list> <branch-name> 

Git从给定的 <branch-name>中找到每个可到达的提交,然后复制每个提交。从逻辑上说,通过按原样提取整个提交,运行 <filter-list>中的每个过滤器,然后使用生成的树和消息进行新的提交来完成复制。它按照Git的正常顺序(即“向前浏览历史记录”)而不是向后的顺序进行复制过程。

如果新提交(及其可能更改的树,可能未更改,可能未更改的父级,可能已更改的消息,等等)与原始提交的比特率100%相同,新提交的哈希ID不变。在这种情况下,下一次提交的默认“新父级”与原始父级相同。否则,下一次提交的默认“新父级”就是我们刚才所做的。

(在实践中,由于提交图可以再次分开并合并,并且可以跳过提交或添加新的提交,所以filter-branch的真正作用是将旧的提交哈希映射到新的提交哈希。每次创建副本时, ,它会在该映射中输入一对: 。但是,对于简单的线性链,您可以将其视为仅记住最近提交的新哈希ID。)

现在,这里的问题是您想更改一个特定提交(即根提交)的父哈希ID。有一个专门用于此的过滤器 --parent-filter。还有两种方法可以执行此操作,但让我们首先描述 --parent-filter。这是从 the git filter-branch documentation:

--parent-filter <命令>

这是用于重写提交的父列表的过滤器。它会
在stdin上接收父字符串,并应输出新的父字符串
标准输出上的字符串。父字符串的格式如下:
git-commit-tree(1):初始提交为空,a为“-p父级”
正常提交和“-p parent1 -p parent2 -p parent3 ...”表示
合并提交。

因此,您可以测试stdin是否为空,如果是,则输出 -p <hash-of-H>。结果将是:
E--F--G--H--A'-B'-C'-D'   <-- master 

(与您要求的不完全相同,但可能更好)。

(要复制 E-F-G-H链,您还必须将 master-old传递为肯定引用,并且由于任何逐位相同的提交都必须具有与原始哈希相同的哈希ID,因此您必须至少进行一次更改提交 E,例如将提交者tiemstamp更改一秒钟。)

在此值得一提的另外两种方法。一种是使用 --commit-filter:这实际上是进行新提交的命令。您可以在这里做任何事情,包括完全省略一些提交;但是使用所有其他过滤器的原因是使事情变得更容易,因此在这种情况下根本没有理由使用提交过滤器。

使用 git replace
最后是 the git replace commandgit replace的作用是使新对象保留在存储库中,并由 refs/replace/命名空间中的特殊名称引用。每当Git通过其哈希ID查看某个对象时,Git通常通常首先检查 refs/replace/<hash-id>是否存在。如果是这样,则Git会转而查看该参考指向的对象。

这意味着您可以构造一个新的Git对象,该对象与commit A非常相似,但略有不同。稍有不同是新提交对象中存储了一个父哈希ID。父哈希ID是commit 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去查看从提交 D开始的历史记录时,它将查看提交 D,然后获取 D的父ID C,查看提交 C,获取 B的ID,然后继续提交 B,获取 A的ID。并且...哇,嘿,这是一个 refs/replace/!毕竟我们不要看 A!让我们看看 A'!它向您显示 A'作为 B的父级,然后继续到 A'的父级,向您显示 H,然后是 G,依此类推。

使用git replace时,您不必复制任何其他提交。 您所拥有的是一个提交历史,在该提交历史中,新的“更好”提交取代了旧的“不太好”提交,但实际上两者共存。 Git在以下情况下使用替代品:
  • 当然必须具有替换对象;
  • 它必须要看带有哈希哈希的对象,但要在引用中找到refs/replace/hash;和
  • 它必须以正常方式运行,而不是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相同。


    标签:git
    声明

    1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

    关注我们

    一个IT知识分享的公众号