关于git:如何从不相关的存储库合并历史记录

How to merge history from unrelated repositories

我有一个关于SVN的老项目,组织方式如下:

  • /一些/sub目录/a/trunk/foo
  • /部分/子目录/b/trunk/foo
  • /部分/子目录/c/trunk/foo

我使用Github工具git-import-svn-raw迁移了三个Git存储库下的这三个存储库:

  • A.GIT
  • B.GIT
  • 小精灵

我用这个命令来清除路径

1
2
git filter-branch --prune-empty --subdirectory-filter \
/some/sub-directories/a/trunk/foo master

现在,我想将这三个项目的历史导入到同一个存储库中,其中有三个目录:abc

1
2
3
4
bundle.git/
   a/
   b/
   c/

其思想不是合并子目录树,而是将每个子目录的历史交错在一起。

有可能吗?

注意:将整个SVN存储库转换为单个存储库会更容易,但它不起作用。我对git-import-svn-raw工具有很多问题。同一个问题出现在git svn clone --stdlayout中,它需要一个单独的存储库,而事实并非如此。我也试过svn2git,但也不管用。


另请参阅https://stackoverflow.com/a/34861819/933106,了解基于提交时间戳"zipper up"两个存储库的另一种方法。


我建议不要使用"bundle"作为回购的名称,因为bundle对git来说意味着其他东西。为了避免混淆这个问题,我将在整个答案中使用您的术语,但我将使用不同的名称。

因此,很容易将所有承诺都纳入同一回购协议。

1
2
3
4
5
cd bundle.git
git remote add a ../a.git
git remote add b ../b.git
git remote add c ../c.git
git fetch --all

现在你有了像remote/a/masterremote/b/masterremote/c/master这样的引用。

1
2
3
4
5
6
7
A1 -- A2 -- A3 -- A4 <--(a/master)

B1 -- B2 -- B3 -- B4 <--(b/master)

C1 -- C2 -- C3 -- C4 <--(c/master)

(master)

请注意,我假设您从master开始,没有承诺("未出生"分支),它仍处于这种状态。

接下来,使用组合内容进行单一提交也不难。例如,您可以这样做:

1
2
3
git reset --hard a/master
get merge --allow-unrelated-histories b/master
git merge --allow-unrelated-histories c/master

那会给你

1
2
3
4
5
6
7
               (a/master)
                   v
A1 -- A2 -- A3 -- A4 -- M1 ------------- M2 <--(master)
                       /                /
B1 --- B2 --- B3 --- B4 <--(b/master)  /
                                      /
C1 -------- C2 -------- C3 -------- C4 <--(c/master)

这段历史是完整和正确的,但是历史承诺没有交错,也没有组合的内容(TREE对象)。例如,如果您查看B3,您将看不到相应时间的ac

这也许足够好了。来自log的输出将默认为逆时间顺序,您可以提供排序选项来精确地影响此历史记录的排序方式(请参阅git log文档)。但这不是你要的…

所以下一步你可以

1
git rebase -i --root master

这将显示一个显示所有提交的"待办事项"列表;但是您必须手动确定希望它们交错的顺序。(默认顺序跟随每个分支,一次一个。)所以这可能相当乏味。您可以使用git log输出来确定正确的顺序,然后相应地重新安排TODO列表。

更新:我突然想到,我应该增加一些关于rebase步骤的注意事项。

首先,上面只明确地提到了master分支。当然,每个回购都可能有额外的分支,您必须决定如何反映这些分支。也许您会决定分支点(从分支可以到达的master的第一次提交)应该是不变的,并且分支不会简单地从交错进入它的另一个repos中进行更改(假设这不会导致分支名称冲突)。或者您可能会决定以某种方式组合来自每个源回购的相应分支。

其次,如果原始历史中有合并,那么您必须决定如何在REBASE中处理它们。如果合并包含冲突解决方案,或者是"邪恶合并"(即引入相对于默认合并结果的更改的合并),则这是一个特别的问题。上面的过程将尝试生成一个单一的线性历史。如果这不是你想要的,那么很难做到这一点,因为如果你告诉rebase保持合并,那么这三个历史也将保持分离。(另外,--preserve-merges--interactive-i混合不好。)

因此,上面的研究适用于相对简单的历史,而对于更复杂的历史来说,这可能不是一件实际的事情。