旧主题:如何将分支切片移动到不相关的提交?
新主题:将树(提交/分支及其所有子项)重新绑定到不相关的分支?
示例:
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
这真的是一个乌托邦的要求吗?
答案 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
。如果有许多分支,如果需要在重写的历史记录中包含合并,或者如果有标记,那么这可能是更容易的方法。但A
和O
之间存在的差异越大,正确执行此操作就越困难。
另一种选择是改变。在某种程度上,这是更直接的,因为它为每个提交计算一个补丁,然后将该补丁应用于新的基础;因此,A
和O
之间的差异会自动得到处理。 (实际上,我应该说&#34;尽可能自动&#34 ;;根据A
和O
之间的差异,在应用每个补丁时可能会出现冲突。)但是你必须要改变每个分支,你必须手动移动标签,rebase
不能很好地处理合并。
如果您决定尝试filter-branch
你最需要的是--parent-filter
。 git filter-branch
文档有几个如何重新提交提交的示例;在这种情况下,您将B
的{{1}}重新加入A
。
但是父过滤器还不够。您还必须转换提交内容(O
)以说明TREE
和A
之间的差异。例如,如果要点是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;更改被计算在内),然后在合并后最终重新提交提交。