3

我有一个存储库 A(原始存储库)具有完整的提交集。在某个时间点,决定不再使用存储库 A,并从头开始创建一个新的干净存储库 B,使用复制 + 粘贴将所有存储库 A 的内容(而不是将 A 的内容合并到干净的 B 中,以便保留提交历史)在其中。我现在想要实现的是在第三个新的干净存储库 C 中粘合存储库的提交和历史(粘合历史非常重要)。最终目标是存储库 C 应该包含来自源存储库 A 和B 按时间顺序排列,就好像根本没有创建存储库 B,例如好像在存储库 A 中进行了工作。

所以现在的情况是:

回购 A:提交 A1 > 提交 A2 > 提交 A3 > ... > 提交一个

回购 B:提交 B1 (= A1 + A2 + A3 + ... + An) > 提交 B2 (= 提交 An+1) > ... > 提交 Bk

尝试了以下方法:

git remote add -f A <repo_A_url>
git merge A/master
git remote add -f B <repo_B_url>
git merge B/master

然后解决了冲突。由于历史以某种方式搞砸了,因此部分工作。

然后尝试了我认为最干净的方法 - 仅将 repo A 合并到 repo C 中,然后从 B2 ... Bk 中挑选 repo B 的提交。它可以工作,但樱桃采摘合并提交会减慢合并过程。

我可能正在复制一个主题,如果我这样做了,请告诉我,因为爬过许多线程并且大多数时候看到如何通过将它们放置在最终存储库中的不同文件夹中来将两个存储库添加到第三个存储库,但事实并非如此。如果您可以分享最合适、语义正确且对 git 友好的方法。

非常感谢。

4

2 回答 2

3

如果您的历史非常线性(即仅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

现在你需要更新masterref 并且可能清理一下

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-commitrebase 的情况下甚至不会意识到任何事情都是错的。

如果您知道这将是一个问题,但可以识别一两个问题合并,那么一种选择是重新设置问题合并的每个父级,然后手动重做合并,然后从该点继续。

如果您不知道是否/在哪里会出现问题,您可以尝试变基,然后运行验证以比较提交。在做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-masternew-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',然后继续嫁接 上 的子MM'

好的,但是裁判呢?

master现在上面的内容很好,但是您可能有除分支之外的您关心的参考(在 A 和/或 B 中) 。

filter-branch这是由;处理得更好的事情之一。事实上,如果我没记错的话rebase,除了分支提示它的变基之外,不会重写任何引用(即使它是远程分支引用也不会重写)。

特别是如果使用filter-branch,您可能会发现通过克隆 B 并从 A 导入远程 refs 来创建 C 很方便(而不是相反,如上所示),这样您就可以为您filter-branch重写本地 refs。

即便如此,您可能会发现一些需要重新定位的远程引用组合。可以根据需要使用带有选项的分支和标记命令-f,将本地引用与最适合您的最终状态的远程引用对齐。

于 2017-02-21T15:14:37.027 回答
0

您可以通过单个提交来挑选一系列提交。

$ git merge A/master          # merge A/master into C/master
$ git cherry-pick B2^..Bk     # cherry-pick all B2 to Bk commits

或者,您可以重新设置基准。转到 C repomaster分支。您可以签出一个新的银行(例如,rebase),然后合并 B/master 并将其重新定位到A/master

$ git checkout -b rebase origin/master # checkout a new branch with clean C-remote/master history 

$ git merge B/master            # merge B/master into C/master
$ git rebase A/master           # rebase C/master onto A/master 

如果所有历史看起来都正常,则用master分支rebase替换。

$ git checkout rebase          # make sure current branch is rebase 
$ git branch -D master         # delete local master branch

$ git checkout -b master       # create & checkout a new 'master' branch      
$ git push -f orgin master     # update C-remote/master
于 2017-02-21T15:05:49.077 回答