我的假设是解决方案很简单,但是我找不到任何清楚地解决我的问题的示例。我在以下方面寻求指导:
我目前的历史如下:
A - - - C - - - - - F Master
B - - - D - E - - - G - H Topic
D
, E
, G
, H
应该都发生在Master
上。在这一点上唯一的区别应该是commit B
。所以它应该是这样的:
A - - - C - D - E - F - G - H Master
B Topic
或者更好:
A - C - D - E - F - G - H Master
B Topic
…但我也不知道如何做到这一点。
我似乎不能同时合并D
、E
、G
和H
。我总是在Master
中出现B
。
我相信我可以挑选他们,但这似乎会带来未来的问题。最好的解决方案是什么?
如果您已经将这些分支推送到任何远程repo,那么重写过去的历史记录不是一个好主意。如果你已经推了,我会这样做来避免重写:
- 从
Master
的F
开始。 - 在
Master
上选择D
,E
,G
和H
。 - 检出
Topic
并从Master
合并。
如果你这样做,你将有:
A - - - C - - - - - F - - - - - D' - E' - G' - H'- Master
B - - - D - E - - - G - H - - - - - - - - - - - - Topic
历史记录看起来不太整洁,但是你可以在分支中拥有你想要的所有代码。
如果你真的想重写历史记录,因为这只是你本地的,或者你不想打扰其他可能从你的repo中提取的人,那么:
- 回滚到
C
(回滚前记下F
的哈希值) - 在
Master
上选择D
,E
,F
,G
和H
。 - 从
Master
的H
头分支Topic2
。 - 在
Topic2
上选择B
- 删除
Topic
,将Topic2
重命名为Topic
得到:
A - C - D - E - F - G - H Master
B Topic
假设图纸是准确的,"你不能从这里到达那里"。(不过,你或许可以靠得足够近。)具体来说,绊脚石是commit F
。
当前在Master
分支上的F
的提交有C
作为父提交。
在新的期望图中,标记为F
的提交将E
作为其父提交。
由于提交是其所有内容(其树,消息,作者/提交者和时间戳,以及问题所在的所有父id)的加密校验和,因此任何以E
为父的新的"F-like"提交都必然是一个不同的提交。
"better yet"图显示提交B
时,H
是它的父节点,所以这再次不同于"existing"图。因此,这与F
具有相同的问题(具有相同的潜在解决方案)。
分支标签Master
和Topic
也必须更改。这两项更改都不会是"快进"(git术语)。
这里你需要回答的问题是:除了你自己之外,还有谁可以访问这个存储库或这个存储库的副本,他们(所有这样的访问器)是否愿意做一些事情来处理一个非快进的分支标签更改?
如果答案是"没有其他人有这样的权限",或者"他们都愿意做需要做的事情",你就可以了。如果不是,你需要另一种方法。现在让我们做最好的假设。
你仍然不能从这里到达那里,因为我们需要对你所绘制的commit F
进行更改,而你永远不能更改提交。你也不能改变D
和E
,虽然你不需要直接改变H
,但你需要改变G
——它目前有两个父级——并且改变"冒进":我们马上会看到为什么,随着父级id的改变。
您需要做的是将复制提交到稍微不同的新版本。我们将复制提交D
到一个版本,该版本省略了B
的任何更改,并且只有C
作为其父版本。让我们把这个副本命名为D'
,以表明它"很像D
,但不完全相同"。
然后,我们将E
复制到与旧D
到E
具有相同更改的版本,但将D'
列为其父。
接下来,我们将发现C
和F
之间发生了什么,并对E'
中的内容进行相同的更改,并将其提交为F'
,并带有与原始F
相同的消息等。
F
和G
之间的更改(而不是从E
到G
的),应用于F'
,并作为G'
提交,具有相同的消息,等等,就像G
一样;然后我们将G
到H
的更改应用到G'
,并作为H'
提交。
结果看起来像这样(我们还没有完全选择您不太喜欢的替代方案):
A---C---D'--E'--F'--G'--H' <-- Master
-----F [old Master was here]
B----D--E--G--H <-- Topic
现在,为了使Topic
结束于B
,我们只需要将其重新指向提交B
。其余的提交仍然在存储库中,但是没有标签指向它们(除了refflogs):
A---C---D'--E'--F'--G'--H' <-- Master
-----F
B----D--E--G--H
`.......................<-- Topic
我们现在有你想要的作为你不太喜欢的替代品。
为了获得您喜欢的替代方案,我们现在只需要将B
重置到Master
上。这将B
复制到一个新的提交B'
,该提交具有与旧的A
到B
相同的更改(省略任何由于在C
到H
中而不再需要的更改),但将H'
作为其父。重新绘制图,去掉所有不再标记的提交,得到:
A---C---D'--E'--F'--G'--H' <-- Master
B' <-- Topic
至于实际复制提交的机制,做这件事的主力是git cherry-pick
。使用git rebase -i
(没有-p
),您可以让git做一系列的选择。但是,合并提交有一个小障碍。交互式重基可以省略一些简单的合并,但是对于更复杂的合并,您需要向git cherry-pick
提供-m
参数,以告诉它从合并的哪一边选择。
那么,从现有的起点开始,我将这样做(有许多可能的方法):
git checkout -b temp Master^ # make new "temp" branch pointing to commit C
git cherry-pick -m 2 Topic~3 # get C->D changes, make D'
git cherry-pick Topic~2 # get D->E changes, make E'
git cherry-pick Master # get C->F changes, make F'
git cherry-pick -m 2 Topic~1 # get F->G changes, make G'
git cherry-pick Topic # get G->H changes, make H'
在此过程中,或者在最后,在每次提交时使用git show
,以确保您得到了您想要的(并且我在这里有正确的-m
参数)。
如果一切顺利,移动分支Master
以匹配新的temp
:
git checkout master; git reset --hard temp # reset Master to equal temp
git branch -d temp # and delete temp, no longer needed
现在你可以重置和重置Topic
:
git checkout Topic; git reset --hard HEAD~4 # reset to point to B
git rebase Master # copy B to B' and reset to B'
(稍微高级一点的技巧:不是git checkout -b temp
,而是git checkout
,并使用分离的HEAD模式来构建所需的新Master
,然后手动移动它。交互式rebase shell脚本就是这样做的。但它更令人困惑,所以最好是显式的,如上所述。
如果你有其他人使用这些分支,他们必须通过自己的重置和/或重置来从你的"非快进"更改中恢复。该方法在git rebase文档中被描述为"从上游rebase中恢复"。