将整个 git 分支变基到孤立分支,同时保持提交树完好无损
Rebase entire git branch onto orphan branch while keeping commit tree intact
我有一个仓库,其中有两个分支,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
系列提交关联的 树 ,您并不真的想要 rebase毕竟。变基意味着将提交转换为差异(变更集),然后将这些变更集一次一个地应用到某个现有的起点——但您要做的就是将附加到 A
的树复制到您的新提交 A'
其父级为 H
,然后将附加到 B
的树复制到其父级为 A'
的新提交 B'
,依此类推。
这就是 git filter-branch
运作良好的地方。当你 运行:
git filter-branch <filter-list> <branch-name>
Git 从给定的 <branch-name>
中找到每个可访问的提交,然后 复制 每个这些提交。无论如何,从逻辑上讲,复制是通过按原样提取整个提交,运行在您的 <filter-list>
中设置每个过滤器,然后使用生成的树和消息进行新提交来完成的。它 运行 以与 Git 的正常顺序相反的方式完成复制过程,即 "forwards through history",而不是向后。
If 新提交(带有它的可能改变的可能不是树、可能改变的可能不是父节点、可能改变的可能不是消息等.) 与原始提交 100% 逐位相同,新提交的哈希 ID 未更改。在这种情况下,next 提交的默认 "new parent" 与原始父项相同。否则下一次提交的默认 "new parent" 是我们刚刚提交的。
(实际上,因为提交图可以再次发散和合并,并且因为您可以跳过提交或添加新提交,所以 filter-branch 真正做的是制作一个 映射 旧的提交哈希到新的提交哈希。每次复制时,它都会输入一对: 到这个映射中。但是对于一个简单的线性链,你可以认为这只是记住最近提交的新哈希 ID。)
现在,您遇到的问题是您想要更改 一个特定提交(即根提交)的父哈希 ID。有一个专门用于此的过滤器,--parent-filter
。还有两种方法可以做到这一点,但让我们首先描述 --parent-filter
。这是来自 the git filter-branch
documentation:
--parent-filter <command>
This is the filter for rewriting the commit's parent list. It will
receive the parent string on stdin and shall output the new parent
string on stdout. The parent string is in the format described in
git-commit-tree(1): empty for the initial commit, "-p parent" for a
normal commit and "-p parent1 -p parent2 -p parent3 ..." for a
merge commit.
因此,您可以测试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
:这是实际进行新提交的命令。您可以在这里做任何事情,包括完全省略一些提交;但是所有 other 过滤器的原因是让事情变得 更容易 ,所以在这种情况下根本没有理由使用提交过滤器。
使用git replace
最后是 the git replace
command。 git replace
所做的是创建保留在存储库中的新对象,由 refs/replace/
名称中的特殊名称引用 - space。每当 Git 通过哈希 ID 查看某个对象时,Git 通常首先检查 refs/replace/<hash-id>
是否存在。如果是这样,Git 而是查看该引用指向的对象。
这意味着您可以构造一个新的 Git 对象,该对象与提交 A
非常相似,但略有不同。细微的差别是新的提交对象中存储了一个父散列 ID。父哈希 ID 是提交 H
的哈希 ID。 (请注意,它具有与 A
相同的 tree。)
现在您有了这个新对象——让我们称它为 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
时,您不必复制任何其他提交。 您拥有的是提交历史,其中新的 "better" 提交取代了旧的 "not-so-good" 提交,但两者实际上共存。 Git 在这些条件下使用替换:
- 当然,它必须有替换对象;
- 它一定是要查看一个带有一些散列的对象 hash 但找到
refs/replace/<em>hash</em>
在参考文献中;和
- 它必须是 运行正常方式,而不是
git --no-replace-objects
。
要求 3 允许您查看原始(未替换)历史记录(如果您愿意)。第 2 项表示在 git clone
上,您 不会 默认获得替换。您必须明确要求它们(这并不难,但也没有任何漂亮的简单前端)。
使用带替换的过滤器分支
由于上述第 2 项,您可能需要进行更换,确保一切正常,然后然后 运行 git filter-branch
.由于您不是 运行ning git --no-replace-objects filter-branch
,Git 将看到 替换 提交 A'
而不是原始提交 A
.因此它将复制 A'
而不是 A
。您不需要 --parent-filter
。当它复制 E
到 H
时,新副本将与原始副本逐位相同,因此它们将保持不变。最终结果将与 运行 git filter-branch
和正确的父过滤器相同。
我有一个仓库,其中有两个分支,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
系列提交关联的 树 ,您并不真的想要 rebase毕竟。变基意味着将提交转换为差异(变更集),然后将这些变更集一次一个地应用到某个现有的起点——但您要做的就是将附加到 A
的树复制到您的新提交 A'
其父级为 H
,然后将附加到 B
的树复制到其父级为 A'
的新提交 B'
,依此类推。
这就是 git filter-branch
运作良好的地方。当你 运行:
git filter-branch <filter-list> <branch-name>
Git 从给定的 <branch-name>
中找到每个可访问的提交,然后 复制 每个这些提交。无论如何,从逻辑上讲,复制是通过按原样提取整个提交,运行在您的 <filter-list>
中设置每个过滤器,然后使用生成的树和消息进行新提交来完成的。它 运行 以与 Git 的正常顺序相反的方式完成复制过程,即 "forwards through history",而不是向后。
If 新提交(带有它的可能改变的可能不是树、可能改变的可能不是父节点、可能改变的可能不是消息等.) 与原始提交 100% 逐位相同,新提交的哈希 ID 未更改。在这种情况下,next 提交的默认 "new parent" 与原始父项相同。否则下一次提交的默认 "new parent" 是我们刚刚提交的。
(实际上,因为提交图可以再次发散和合并,并且因为您可以跳过提交或添加新提交,所以 filter-branch 真正做的是制作一个 映射 旧的提交哈希到新的提交哈希。每次复制时,它都会输入一对:
现在,您遇到的问题是您想要更改 一个特定提交(即根提交)的父哈希 ID。有一个专门用于此的过滤器,--parent-filter
。还有两种方法可以做到这一点,但让我们首先描述 --parent-filter
。这是来自 the git filter-branch
documentation:
--parent-filter <command>
This is the filter for rewriting the commit's parent list. It will receive the parent string on stdin and shall output the new parent string on stdout. The parent string is in the format described in git-commit-tree(1): empty for the initial commit, "-p parent" for a normal commit and "-p parent1 -p parent2 -p parent3 ..." for a merge commit.
因此,您可以测试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
:这是实际进行新提交的命令。您可以在这里做任何事情,包括完全省略一些提交;但是所有 other 过滤器的原因是让事情变得 更容易 ,所以在这种情况下根本没有理由使用提交过滤器。
使用git replace
最后是 the git replace
command。 git replace
所做的是创建保留在存储库中的新对象,由 refs/replace/
名称中的特殊名称引用 - space。每当 Git 通过哈希 ID 查看某个对象时,Git 通常首先检查 refs/replace/<hash-id>
是否存在。如果是这样,Git 而是查看该引用指向的对象。
这意味着您可以构造一个新的 Git 对象,该对象与提交 A
非常相似,但略有不同。细微的差别是新的提交对象中存储了一个父散列 ID。父哈希 ID 是提交 H
的哈希 ID。 (请注意,它具有与 A
相同的 tree。)
现在您有了这个新对象——让我们称它为 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
时,您不必复制任何其他提交。 您拥有的是提交历史,其中新的 "better" 提交取代了旧的 "not-so-good" 提交,但两者实际上共存。 Git 在这些条件下使用替换:
- 当然,它必须有替换对象;
- 它一定是要查看一个带有一些散列的对象 hash 但找到
refs/replace/<em>hash</em>
在参考文献中;和 - 它必须是 运行正常方式,而不是
git --no-replace-objects
。
要求 3 允许您查看原始(未替换)历史记录(如果您愿意)。第 2 项表示在 git clone
上,您 不会 默认获得替换。您必须明确要求它们(这并不难,但也没有任何漂亮的简单前端)。
使用带替换的过滤器分支
由于上述第 2 项,您可能需要进行更换,确保一切正常,然后然后 运行 git filter-branch
.由于您不是 运行ning git --no-replace-objects filter-branch
,Git 将看到 替换 提交 A'
而不是原始提交 A
.因此它将复制 A'
而不是 A
。您不需要 --parent-filter
。当它复制 E
到 H
时,新副本将与原始副本逐位相同,因此它们将保持不变。最终结果将与 运行 git filter-branch
和正确的父过滤器相同。