如果您的历史非常线性(即仅master
在 A 和 B 中),这不会太糟糕。如果有很多分支要与之抗衡,它将涉及更多(请参阅下面的更新),但这仍然可以提供一个起点:
首先,创建您的 C 存储库。
git clone /url/for/repo/A C
cd C
现在从 B 获取所有对象
git remote add B /url/for/repo/B
git fetch B
现在我们应该在新的 C 存储库中拥有这两个历史记录。将 B 存储库提交重新定位到 A 历史记录
git rebase --onto master --root B/master
现在你需要更新master
ref 并且可能清理一下
git branch -f master
git remote remove B
现在 A 被列为您的origin
遥控器;您可能想要推送它或将其作为原点删除,这取决于您是否打算让 A 包含前进的所有内容。
- 更新:好的,这里有更多信息,以防您的历史不是线性的,因为它可能永远不会......首先让我们关注拓扑,然后在参考上说一两个词......
在拓扑方面,实际上存在三种总体情况:
1)B中的分支和合并
因此,假设您在 B 中有一个分支尖端(如果没有,请通读此内容,然后查看场景 2),它可以追溯到单个根,您可以嫁接到 A 中的单个分支尖端(如果没有,请参见下面的场景 3)回到这个)。
A1 ---- A2 ---- A3 <--- (master)
\ /
A4 -- A5
---------------------------
B1 ---- B2 ---- B3 <--- (master)
\ /
B4 -- B5
A 中可能有分支,但最终它们已被合并回来,您不必担心它们。B 中也可能有分支,虽然它们被合并回来,但如果 rebase 试图使它们成为线性,它们可能会造成麻烦。
首先创建 C 并导入两个历史记录,就像我们上面所做的那样。(最后,我会建议对这个过程稍作改动,与 refs 相关......但让我们回到那个。)
现在,您有两个选择。到目前为止最简单的是使用filter-branch
(但可能很耗时)。找到您将要移植到的提交的哈希值(上面标记为 A3)并运行
git filter-branch --parent-filter 'sed "s/^\$/-p xxxxxxxxxx/"' B/master
(其中 xxxxxxxxxx 是哈希值)。
那确实假设您已经使用了 sed;它可以在 Windows 上的 git bash 环境中使用,也可以在几乎任何 *nix 系统上使用。如果没有,你可以想出一个等效的过滤器。(它所做的只是说“如果输入是空行,写出'-p',后跟我要嫁接的哈希;否则将输入作为我的输出传递”。)
如果由于某种原因你不能这样做,或者它似乎确实是一个性能问题,那么你可以尝试计划 b:给...--preserve-merges
选项rebase
,这将在很多时候做你想要的。 但是有一些重要的警告。
基本上,如果合并引入了手动更改——要么是因为需要手动解决冲突,要么是因为合并完成--no-commit
并以这种方式引入了手动更改——那么即使使用此选项,rebase 也不会正确复制合并。
在发生冲突的情况下,rebase 应该停止并让您重新应用手动解决方案(您可以使用checkout ... -- .
原始合并提交的路径 checkout ( ) 来完成。但在有人使用--no-commit
rebase 的情况下甚至不会意识到任何事情都是错的。
如果您知道这将是一个问题,但可以识别一两个问题合并,那么一种选择是重新设置问题合并的每个父级,然后手动重做合并,然后从该点继续。
如果您不知道是否/在哪里会出现问题,您可以尝试变基,然后运行验证以比较提交。在做rebase之前
git checkout master
git tag old-B-master
然后尝试变基
git rebase --preserve-branches --onto master --root B/master
git tag new-B-master
然后做任何对你来说安全的验证级别。(显然,至少有差异old-B-master
。new-B-master
当我做这样的事情时,我编写了一个脚本来递归遍历提交祖先,比较提交。偏执狂?也许吧。)
除非这非常非常顺利,否则您最好还是退回到该filter-branch
方法。
2)B中的多个分支提示
A1 ---- A2 ---- A3 <--- (master)
\ /
A4 -- A5
---------------------------
B1 ---- B2 <--- (master)
\
B4 -- B5 <--- (branch1)
也许你的 B 回购没有完全合并。这可能会使事情复杂化,也可能不会。如果您正在使用filter-branch
,它可以同时处理多个 refs。您可能不能只说--all
(因为这可能会捕获已经在A
树中的 refs 并且可能操作最终会失败),但是您可以列出B
树中的分支提示。
git filter-branch --parent-filter 'sed "s/^\$/-p xxxxxxxxxx/"' B/master B/branch1
如果您尝试使用 rebase(或者如果您只想使用单个提示 ref),您可以创建一个临时章鱼合并。
git checkout B/master
git checkout -b b-entry-point
git merge -s ours B/branch1 B/branch2 ...
生成的合并提交是临时的。(您可以在移植后删除 b-entry-point 分支。)它只是提供了一个进入B
提交树的“入口点”。
3)A中的多个分支提示
A --- Am <-- (master)
\
Ab1 <-- (branch1)
---------------------------------------
B1 ---- B2 <-- (master)
B3 ---- B4 <-- (branch1)
那么如果 A 一开始没有完全合并呢?当您创建 repo B 时,您是否只创建了一个新提交B === Am
?我猜是这样,因为你不得不做一些奇怪的事情,比如多个历史树来包含 的表示Ab1
,如果你想重新合并,你以后会有点头疼......
如果您确实要嫁接多棵树,那么我认为您只需要分别处理每一棵即可。没有太多的事情会改善这一点。
如果您有多个嫁接点,但它们已经在 重新合并M
,那么您可能必须M
单独嫁接每个父级,然后重新创建合并为M'
,然后继续嫁接 上 的子M
级M'
。
好的,但是裁判呢?
master
现在上面的内容很好,但是您可能有除分支之外的您关心的参考(在 A 和/或 B 中) 。
filter-branch
这是由;处理得更好的事情之一。事实上,如果我没记错的话rebase
,除了分支提示它的变基之外,不会重写任何引用(即使它是远程分支引用也不会重写)。
特别是如果使用filter-branch
,您可能会发现通过克隆 B 并从 A 导入远程 refs 来创建 C 很方便(而不是相反,如上所示),这样您就可以为您filter-branch
重写本地 refs。
即便如此,您可能会发现一些需要重新定位的远程引用组合。可以根据需要使用带有选项的分支和标记命令-f
,将本地引用与最适合您的最终状态的远程引用对齐。