【问题标题】:Rebasing a tree (a commit/branch and all its children) to an unrelated branch?将树(提交/分支及其所有子项)重新定位到不相关的分支?
【发布时间】:2018-01-09 13:38:42
【问题描述】:

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

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

例子:

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 和 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

真的是空想吗?

【问题讨论】:

  • 你想更改分支中所有提交的父提交,还是只想更改分支的名称?根据您的图表,您已经多次使用“分支”这个词来指代看似提交的内容。
  • AO 有共同的历史吗?
  • 没有。 A和O没有关系,没有共同的历史。当然,由于 rebase 冲突必须解决。
  • 在 Rebasing a tree (a commit/branch and its all its children) - stackoverflow.com/questions/17315285/… 问题中 A & H 是相关的。因此它与我的问题不同。

标签: git


【解决方案1】:

您的图片中不清楚的一件事是存在哪些分支(或其他参考)并且需要移动。您指的是“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)

所以这里只是为每个没有孩子的提交有一个分支。

现在有几种方法可以继续,但在我们开始之前,有一件重要的事情需要了解。你不能“移动”一个提交。您可以创建一个新的提交以将旧提交对A 所做的更改应用于O。这称为变基。你得到的是一个带有新 ID 的新提交。

我想如果你有关心提交 ID 值的工具或文档,这可能很重要,但更一般地说,这意味着你最终会“重写”分支的历史,所以如果你之后想要推送到远程您可能必须“强制推送”(push -f),然后这将需要清理远程的任何其他克隆(请参阅git rebase 文档中的“从上游 rebase 恢复”)。

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

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 之间的差异会被自动处理。 (实际上,我应该说“尽可能自动”;根据AO 之间的差异,应用每个补丁时可能会出现冲突。)但是您必须重新设置每个分支,并且您必须移动标签手动,rebase 不能很好地处理合并。

如果你决定试试filter-branch

您最需要的是--parent-filtergit filter-branch 文档有几个关于如何重新提交提交的示例;在这种情况下,您需要将 BA 重新设置为 O

但是父过滤器是不够的。您还必须转换提交内容 (TREE) 以解决 AO 之间的差异。例如,如果重点是 O 省略了一个文件,您可以使用 index-filter like

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

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

如果你决定试试rebase

最简单的情况是rebaseing 一个无法到达合并提交的分支。例如,在我们的示例中,您可以说

git rebase --onto O A master

OA 替换为相应提交的 SHA ID 或解析为 SHA ID 的其他名称或表达式。 (例如,您可以将O 的分支名称称为new_root。您可以标记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)

请注意,JK 无法访问,因此默认情况下不会显示在日志输出等中。您可以将 K 引用为 master@{1}(使用 reflog 查看 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)

并为每个额外的分支继续。

如果有标签可以手动移动

git checkout new-commit-to-tag
git tag -f tag-name

如果这个历史记录包含提交,这是一个更大的问题。默认情况下,rebase 将尝试创建线性历史记录,您可以使用 --preserve-merges 覆盖它,但随后 rebase 将假定每个合并都是其父项的自然产物 - 即它将假定没有“邪恶合并”。出于这个原因,如果您已经通过合并重新定位,或者可能想要“分段重新定位”,您应该验证结果。例如,给定

... A -- B -- C
                \
                 M -- G <--(master)
                /
... D .. E .. F

您可以在CF 创建临时分支;然后 rebase C,rebase F,手动重新创建合并(确保考虑任何“邪恶”更改),最后在合并后 rebase 提交。

【讨论】:

  • 真的很详细的答案!谢谢!我对变基的问题是,我必须为每个分支重复变基。我从你的回答中学到了这一点。但我需要一个完整的解决方案,无论有 10 个分支还是 100 个或 1000 个分支,它都能自动生成精确的树副本。
  • @klor - 是的,在许多分支/引用和任意复杂的历史记录中实现自动化的愿望是 filter-branch 选项的用武之地,但问题在于确保 AO 在整个重写的树中保持原位。真的没有很好的、简单的解决方案。您可以通过大量自定义编程来接近,但即便如此(可能很多)需要手动干预的冲突也可能是可能的(取决于很多事情)。这需要一个重要的问题才能值得尝试......
  • @klor - ... 在重写历史可能非常重要的特定情况下(A 中的敏感信息和/或二进制膨胀),有像 BFG Repo Cleaner 这样的工具旨在解决这些情况,这可能会有所帮助。在大多数其他情况下,最好只在新分支中应用从 AO 的更改,然后将其合并到每个现有分支。结果历史不会那么“整洁”,但它的麻烦要少得多。
  • 变基/移动树应该是 git 的基本部分。建议,在 A 和 O 之间解决冲突,剩余的树应该是从 B 到 O 的简单复制树并删除 A 上方的树切片。我想“移动树”可以像这样工作:1)创建 O 孤立提交 2)使 A 和 O 之间的差异。 3) 将 A 和 O 之间的差异提交到 O 上,命名为 AO 提交。 4) Rebase A 和所有分支的所有子提交(B、C 等)到 AO 上。这真的是空想的要求吗?
  • 我会尝试向 git 开发者发送功能请求。
猜你喜欢
  • 2013-06-23
  • 1970-01-01
  • 2011-08-01
  • 2023-02-07
  • 2023-01-11
  • 2020-09-19
  • 2015-11-07
  • 2014-02-22
  • 2023-03-16
相关资源
最近更新 更多