git之将整个git分支重新设置为孤立分支,同时保持提交树完整
jyk
阅读:67
2024-12-31 21:38:35
评论:0
我有一个仓库,其中有两个分支master
和master-old
,它们是作为孤立分支创建的。
现在,我想将整个master
重新设置为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
我尝试使用
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。有一个专门用于此的过滤器
--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
command。
git 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
相同。
命令>
声明
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。