将树(提交/分支及其所有子项)重新绑定到不相关的分支?

时间:2018-01-09 13:38:43

标签: git

旧主题:如何将分支切片移动到不相关的提交?

新主题:将树(提交/分支及其所有子项)重新绑定到不相关的分支?

示例:

A - B - C - F - G - J - K
          \       \
           D - E   H - I

O - 

我想将B,C,D,E,F,G,H,I,J,K提交到O分支,同时保留分支树。

结果应该是:

A -

O - B - C - F - G - J - K
          \       \
           D - E   H - I

怎么做?

谢谢!

EDIT1: 我需要一个完整的解决方案,无论是否有10个分支或100或1000,都可以自动进行精确的树复制。

EDIT2: 解决方案应该适用于Linux(我使用Debian服务器)和Windows(MSYS2可以接受,就像我使用https://github.com/git-for-windows/git-sdk-64上的工作站一样)

EDIT3: 变基/移动树应该是git的基本部分。 提议,A& A之间的冲突得到解决。 O,剩下的树应该是一个简单的复制树,从B到O&删除A上方的树切片。

我想象"移动树"可以像这样工作:

1) create O orphaned commit
2) Make diff between A & O. 
3) Commit diff between A & O onto O, named AO commit.
4) Rebase A and all child commits (B, C, etc.) of all branches onto AO
5) Delete child commits & branches of A

这真的是一个乌托邦的要求吗?

1 个答案:

答案 0 :(得分:0)

从您的图片中不清楚的一件事是分支(或其他参考)存在并需要移动。你指的是" B,C,D,E,F,G,H,I,J,K分支"和" O分支",但在图表中,您已经绘制了那些似乎是提交。假设

之类的东西,我会给出指示
A - B - C - F - G - J - K <--(master)
          \       \
           \       H - I <--(branch_X)
            \
             D - E <--(branch_Y)

O <--(new_root)

所以这里只是每个提交都有一个没有子节点的分支。

现在有几种方法可以继续,但在我们开始之前,有一件重要的事情要理解。你不能&#34;移动&#34;提交。您可以创建一个新提交,以便通过旧提交在O上应用与A最初进行的更改相同的更改。这称为变基。你得到的是一个带有新ID的新提交。

我想如果您有关注提交ID值的工具或文档,这可能很重要,但更一般地说,这意味着您最终会重写&#34;重写&#34;分支的历史,所以如果你以后想要推到遥远的地方,你可能不得不强行推动&#34; (push -f),这将需要清理遥控器的任何其他克隆(请参阅&#34;从git rebase文档中的上游rebase&#34;中恢复。)

所以你可以得到你所描述的最接近的东西就像是

A <--(old_root)

O - B' - C' - F' - G' - J' - K' <--(master)
           \         \
            \         H' - I' <--(branch_X)
             \
              D' - E' <--(branch_Y)

实际上,实际摆脱原始提交可能需要相当多的额外步骤;默认情况下,您可以获得更像

的内容
A - B - C - F - G - J - K
          \       \
           \       H - I
            \
             D - E

O - B' - C' - F' - G' - J' - K' <--(master)
           \         \
            \         H' - I' <--(branch_X)
             \
              D' - E' <--(branch_Y)

A树中的所有提交都无法访问,因此不会在默认输出中显示,但它们仍然存在(至少一段时间)。如果这是一个问题 - 例如,如果所有这些的原因是删除A中的一些敏感信息或二进制膨胀 - 那么在重写提交和引用后需要额外的步骤。

但首先,如何进行重写?基本上有两种选择。

您可以使用git filter-branch。如果有许多分支,如果需要在重写的历史记录中包含合并,或者如果有标记,那么这可能是更容易的方法。但AO之间存在的差异越大,正确执行此操作就越困难。

另一种选择是改变。在某种程度上,这是更直接的,因为它为每个提交计算一个补丁,然后将该补丁应用于新的基础;因此,AO之间的差异会自动得到处理。 (实际上,我应该说&#34;尽可能自动&#34 ;;根据AO之间的差异,在应用每个补丁时可能会出现冲突。)但是你必须要改变每个分支,你必须手动移动标签,rebase不能很好地处理合并。

如果您决定尝试filter-branch

你最需要的是--parent-filtergit filter-branch文档有几个如何重新提交提交的示例;在这种情况下,您将B的{​​{1}}重新加入A

但是父过滤器还不够。您还必须转换提交内容(O)以说明TREEA之间的差异。例如,如果要点是O省略了文件,则可以使用O之类的

index-filter

(这假定其余的提交后来不会引入您希望保留在该路径上的新内容。)

对于更复杂的转换(如添加文件),使用git filter-branch --parent-filter \ 'test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>" || cat' \ --index-filter \ 'git rm --cached --ignore-unmatch path/to/unwanted/file` \ -- --all 代替tree-filter可能更容易,但速度较慢。

如果有标签,您只想移动它们,则可以使用index-filter

有关详细信息,请参阅--tag-name-filter cat文档。 https://git-scm.com/docs/git-filter-branch

如果您决定尝试git filter-branch

最简单的情况是rebase单个分支,无法进行合并提交。例如,从我们的例子中你可以说

rebase

git rebase --onto O A master O中的每一个替换为相应提交的SHA ID或解析为SHA ID的其他名称或表达式。 (例如,您可以为A说明分支名称new_root。您可以标记O以便更容易引用,或使用A之类的内容。

然后你有

master~6

请注意,A - B - C - F - G - J - K \ \ \ H - I <--(branch_X) \ D - E <--(branch_Y) O <--(new_root) \ A` - B` - C` - F` - G` - J` - K` <--(master) J无法访问,因此默认情况下不会在日志输出等中显示。您可以将K称为K(使用reflog查看以前master@{1}的位置。

然后你想要移动master。因此,您需要获取branch_Y的标识符,因为它将成为重写分支的新基础。在此示例中,可以是C'

之类的表达式
master~4

给我们

git rebase --onto master~4 master@{1} branch_Y

并继续每个额外的分支。

如果有标签,您可以手动移动它们

A - B - C - F - G - J - K
          \       \
           \       H - I <--(branch_X)
            \
             D - E

O <--(new_root)
 \
  A` - B` - C` - F` - G` - J` - K` <--(master)
              \
               D' - E' <--(branch_Y)

如果此历史记录包含提交,则这是一个更大的问题。默认情况下,rebase将尝试创建一个线性历史记录,您可以使用git checkout new-commit-to-tag git tag -f tag-name 覆盖它,但是--preserve-merges将假设每个合并都是其父项的自然产物 - 即它将假设没有&#34;邪恶合并&#34;。出于这个原因,如果您通过合并进行了重组,或者您希望在细分市场中进行细分,则应验证结果。例如,给定

rebase

您可以在... A -- B -- C \ M -- G <--(master) / ... D .. E .. F C创建临时分支;然后重新定义F,rebase C,手动重新创建合并(确保任何&#34; evil&#34;更改被计算在内),然后在合并后最终重新提交提交。