合并没有共同历史记录但具有共同文件的git仓库

4
我需要将两个独立的git仓库ORIG和WORK合并。WORK是从ORIG的一个子目录中派生出来的,其中包含一些实验性更改,这些更改尚未提交到ORIG。
~> mkdir WORK
~> cp -a ORIG/src/* WORK
~> cd WORK
~/WORK> # apply some experimental changes to WORK
~/WORK> git init
~/WORK> git add .
~/WORK> git commit -m "Entirely disconnected commit."

作为结果,WORK 不知道它来自 ORIG,并且缺少文件名的 src 前缀。
有没有办法:
- 确定 WORK 是由 ORIG 的哪个提交创建的; - 更改 WORK 使得完整的文件路径(即 ./src/FILE 而非 ./FILE)存在; - 合并这两个仓库而不会失去历史记录?
目前我通过什么方式解决了这个问题。
  • git mv'ing each file in WORK to a newly created ./src directory,
  • adding ORIG as a remote,
  • merging ORIG/master into WORK's master

    git merge -X theirs --allow-unrelated-histories ORIG/master
    

    with the theirs merge strategy, and reapplying changes by hand, using git diff to find the relevant parts,

但结果最多只是一份不太干净的历史记录:

  • 历史记录并不代表WORK和ORIG提交的共同祖先。
  • 在WORK的历史记录中,文件出现在根目录而不是./src中,而来自./src之外的文件在WORK的历史记录中根本不存在。

我该如何生成一个干净的合并历史记录?

1个回答

6
能否以某种方式确定WORK是基于ORIG的哪个提交创建的? 易:最简单的方法是记住它。另一种方法是查找源树与已保存子树匹配的提交记录。这很困难(但不是不可能的,如果您愿意进行确切的子树匹配,速度相对较快:使用子树的哈希ID),但打开了多个匹配的可能性:根据源存储库,很可能有许多提交符合条件。在这种情况下,有可能会有一个或多个适合的提交记录。 更改WORK,使得所有文件路径都为全路径(即./src/FILE而不是./FILE)是否可行? 是的,有点可行;或不,这要看您的意思是什么。您可以使用git filter-branch来复制存储库,并在副本中进行更改。该副本不再与原始存储库兼容,但是如果您计划有一个标志日并将所有人都转换为副本,则非常简单。 如何将两个存储库合并在一起而不丢失历史记录? 这是真正棘手的部分。Git从未真正丢失历史记录:在Git中,历史记录就是提交。提交是永久且不变的。但是,Git通过分支名称(以及其他名称,如标签)记住提交记录,因此如果您强制分支名称停止记住某些提交记录(例如,这正是git filter-branch在将所有过滤后的提交记录复制到新提交记录后所做的),那么这些提交就会被有效地遗忘。最终,如果您删除了找到这些提交的所有能力,则Git将通过垃圾回收将它们删除:git gc。
再次强调一下,这就是git filter-branch的工作原理:您告诉它将每个提交复制到一个新的提交中,新的提交与原始提交非常相似,但是每个FILE都已重命名为src/FILE。然后,您使所有分支名称指向新副本的最后一个,而不是原始副本的最后一个。您删除任何保存的原始名称(git filter-branch会复制原始引用以防万一),删除所有其他备份安全带和安全线(git reflog expire等),并强制进行垃圾收集,瞬间,您的原始提交集就消失了,只剩下替换提交。

但是:提交是快照。您拥有ORIG中的所有快照,可以从WORK(或通过git filter-branch制作的修改后的替换副本)添加所有所需的快照。结果只是提交的总和。这不是两个工作集相互交织的历史记录:它只是一段历史记录,即“在<日期>这些内容被合并在一起,在此之前,我们有这两个单独的历史记录”。例如,ORIG可能如下所示:

root--o--(history graph)---o   <-- master
       \                  /
        o--(branchy)--o--o   <-- feature

你筛选后的工作可能是这样的:

            o--o
           /    \
root2--o--o------o   <-- master

将两者放入单个存储库中,将WORKmaster更改为其他名称,您将得到以下结果:
            o--o
           /    \
root2--o--o------o   <-- workmaster

root--o--(history graph)---o   <-- master
       \                  /
        o--(branchy)--o--o   <-- feature

现在你可以运行git checkout master; git merge workmaster,解决所有合并冲突——Git会抱怨src/*中的每个文件在master顶部提交和workmaster顶部提交都有一个添加/添加冲突,因为共同的起点是“没有文件”,然后从合并结果中创建一个提交:

                       o--o
                      /    \
root2--o-------------o------o   <-- workmaster
                             \
root--o--(history graph)---o--o   <-- master
       \                  /
        o--(branchy)--o--o   <-- feature

现在你拥有一个基于ORIG的仓库,并加入了连接两个历史记录的新提交。

如果这对你来说已经足够了,那么你现在就完成了。如果不是,接下来的内容可能并没有真正帮助,但我仍然会概述一下。

使合并更容易和/或处理历史记录

一个直接的git merge很困难,因为所有文件都冲突了。但是,如果你找到了一个所有文件都匹配的点,你可以使用git replace来创建一个临时移植。然后你可以更容易地进行合并,甚至可以使替换永久化(通过另一个filter-branch,其中包含所有这意味着的内容)。

我们从与上面相同类型的图开始,但选择一个"root2"与主仓库中的某个提交X匹配的点。请注意,我在这里也标记了root2的子项:

            o--o
           /    \
root2--Y--o------o   <-- workmaster

root--o--...--X----...-----o   <-- master
       \                  /
        o--(branchy)--o--o   <-- feature

我们现在使用git replace命令告诉Git:不要查看提交记录Y,而是查看新的替换提交记录Y'。这样,大多数Git会看到以下内容:
                       o--o
                      /    \
                Y'---o------o   <-- workmaster
               /
root--o--...--X----...-----o   <-- master
       \                  /
        o--(branchy)--o--o   <-- feature

提交记录 Yroot2 仍然存在,只是Git不再“查看”它们了(除非像git gc这样的东西,或者你使用--no-replace-objects选项运行任何命令)。

为了进行替换操作,我们找到了root2后的子提交记录Y - 如果幸运的话只有一个,但如果有多个,我们可以用 git replace 替换所有的记录,并运行以下命令:

git replace --graft <hash-of-Y> <hash-of-X>

这使得替代提交Y',我们得到了上面制作的绘图,现在git merge将把X视为合并两个分支端点的公共提交。

这样我们的合并就会更容易(也许),我们得到:

                       o--o
                      /    \
                Y'---o------o   <-- workmaster
               /             \
root--o--...--X----...-----o--o   <-- master
       \                  /
        o--(branchy)--o--o   <-- feature

作为我们的结果。

如果我们运行一个没有过滤器的 git filter-branch 命令,并且确保我们不使用 git --no-replace-objects filter-branch,filter-branch 将会复制该代码库而不包含原始的 Yroot2 提交记录,相反它会使用 XY'。 换句话说,在我们新的、再次重写的代码库中,这些嫁接是永久性的(另一个标志日更改,但是通过运气或良好的规划,可能在同一天进行,以便只有一个标志日)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接