合并两个完全不同的存储库

时间:2016-03-13 16:03:19

标签: git github version-control merge

我有一个git存储库(让我们称之为A),其中包含很多提交和标记。

我最近创建了一个新的存储库(让我们调用这个B)并在那里做了一些提交(没有标记,除了master之外没有分支)。经过一些工作,我意识到B中的工作可以完全覆盖A

有没有办法合并"这两个存储库的方式是在合并提交之后不会保留A的文件(但它们在该提交之前仍然存在),并且B的整个历史记录将被保留?

图形(种类)示例(为了这个例子,考虑git提交就像它们是svn提交/数字一样):

在提交20处回复A

foo.txt <-- 4 bytes
bar.txt <-- 2 bytes

在提交14处回复B

foo.txt <-- 3 bytes
cat.txt <-- 1 byte

----合并操作----

合并后

回购A,提交34:

foo.txt <-- 3 bytes
cat.txt <-- 1 byte

附加功能: 存储库A是github托管的git仓库,而B仅存在于我的开发机器中。

2 个答案:

答案 0 :(得分:9)

[编辑,2016年10月28日:自2016年6月中旬发布的Git 2.9版以来,您必须在合并命令中添加标记--allow-unrelated-histories,让Git尝试这种合并在一起。否则其余部分仍适用。]

如果我理解你想要什么,它不仅可能,而且非常微不足道。但我可能听不懂,所以请仔细阅读以下内容。有很多解释和缓慢的设置,以艰难的方式(这让你随时检查一切)。然后,最后,有一个命令可以同时执行所有操作(假设您已经设置了遥控器并首先完成了git fetch,那就是。)

Git提交DAG

Git与大多数其他版本控制系统完全不同。它运行(并使用)提交图,这只是 D 竖立的 A 循环 G raph(或DAG)。

典型的DAG以单根开头,并具有分支和合并,例如:

        o - o - o
      /           \
o - o - o - o - o - X   <-- master
      \
        o - o - o       <-- topic

(这看起来有点像汉堡包,所以让我们称之为&#34;汉堡包回购&#34; - 我将解释为什么有一个提交标记为X以后) ,或者:

o - o - o               <-- A
       \
        o - o - Y       <-- B

(让我们称之为&#34; AB repo&#34;以及稍后解释Y的原因。)

然而,git允许完全断开连接(&#34;不相交&#34;)子图:

o - o - o               <-- A
       \
        o - o - Y       <-- B

        o - o - o
      /           \
o - o - o - o - o - X   <-- master
      \
        o - o - o       <-- topic

Git&#34;遥控器&#34;

要获取类似AB repo的现有存储库,并在其图形中添加另一个不同的存储库,只需将不同的存储库添加为远程存储库并使用git fetch。例如,从AB repo开始作为当前存储库,您可以git remote add hamburger <url>将汉堡包存储库添加为&#34;远程&#34;。此时,运行git fetch hamburger将带来所有汉堡包提交。由于它们与AB-repo提交无关,因此它们将作为不相交的子图插入。 Git还会以通常的方式重命名分支标签,以便master变为hamburger/master,依此类推。换句话说,此时的实际存储库如下所示:

o - o - o               <-- A
       \
        o - o - Y       <-- B

        o - o - o
      /           \
o - o - o - o - o - X   <-- hamburger/master
      \
        o - o - o       <-- hamburger/topic

识别合并的提交,以及--first-parent

您现在可以&#34;合并&#34;通过访问指向所需提交的本地分支,此图中的任何提交。例如,让我们假设您要创建一个名为master的新本地分支,它将hamburger/master分支(即提交X - 和{{1}连接在一起分支,即提交B,暂时忽略所有其他提交。

首先,我们需要创建指向YX的分支。我们必须选择其中一个。为了进行合并本身,我们选择哪一个并不重要,但为了以后跟踪历史,确实很重要。哪个是正确的?答案取决于您希望稍后看到的内容

Git的概念是遵循&#34;第一个父母&#34; (在查看分支的历史时使用标记拼写为Y)。虽然git本身并不关心哪个是第一个,哪个不是,但我们人类往往想知道哪一个是&#34;主要&#34;分支,这是&#34; side&#34;分支被合并。 --first-parent旨在让我们只看到 &#34; main&#34;分支和像--first-parent这样的图形日志查看器将绘制&#34; main&#34;分支作为一条连续的直线,同时具有&#34; side&#34; branch,branch off(参见this image中的this SO question)。

如果你想要gitk,并提交B,看起来像&#34; main&#34;我们应该检查一个指向提交Y的分支。如果您想要Y,并提交master,则看起来像&#34; main&#34;我们应该检查一个指向提交X的分支。 (现在你知道为什么我们标记了这些提交XX!)我们已经有了一个提交Y的分支 - 它的本地分支Y - 但是我们还没有B的;它只有名称X指向它,并且该名称是&#34;远程分支&#34;而不是常规本地分支。

新提交(合并或常规)继续(本地)分支

在任何一种情况下,我们都可以 - 如果您是git的新手,并且不熟悉从错误中恢复的所有方法,那么 - 使用 new 本地分支做这个合并。因此,让我们获得一个新的本地分支,指向提交hamburger/master

X

或提交git checkout -b for-merge hamburger/master

Y

(请记住,远程分支git checkout -b for-merge B 指向提交hamburger/master,本地分支X指向提交B:我们在绘制图表时看到了这些。如果您愿意,可以为提交添加实际的SHA-1哈希。 Git只是将名称Yhamburger/master转换为适当的SHA-1哈希值。

最有可能的是,您希望主(第一父)分支遵循分支B的历史记录,因此我们需要B。 (事实上​​,在您的存储库中,它可能没有命名为git checkout -b for-merge B,它可能是B。请注意,同时使用{{1}可以很好} 不相关的master:这是为什么 master重命名分支。)

进行(特殊)合并

现在我们已经在这个hamburger/master分支上,我们可以进行合并,但根据您的问题,我们根本不想要正常合并。事实上,正常的合并主要只是妨碍,因为没有merge base。在这种情况下,git做的是使用空树作为合并基础,因此您往往会遇到很多创建/创建冲突。所以我们最终想要做的是使用内部(不是正常的日常使用)git命令git fetch来进行新的提交。

在我们到达那里之前,让我们看看我们如何使用普通的合并命令来做到这一点。

首先,为了防止它真正起作用,我们不希望git提交合并,所以让我们使用for-merge。然后,我们需要做的唯一其他事情是在要合并的提交处指向git commit-tree。这很可能是提交--no-commit,我们可以通过其实际的SHA-1或名称git merge来命名:

X

此时你很可能会遇到一堆冲突。要解决这些问题,因为你想要的是commit hamburger/master(来自分支git merge --no-commit hamburger/master )的内容,让我们首先删除合并混乱中的所有内容:

Y

现在我们从提交B重新填充工作树(和索引/登台区域),这是名称git rm -rf . # (note: this assumes you're at the top of your work tree) 和当前分支{{1}所指向的因此Y

B

此时所有内容都已正确解析(您可以查看for-merge),以便继续HEAD。结果是在新分支上将所有内容绑定在一起的合并提交:

git checkout HEAD -- .  # (still assumes top of work tree)

您现在可以查看各种提交并检查它们以确保您喜欢结果。如果您 喜欢结果,请将git status分支重命名为您喜欢的任何名称(例如git commit),然后您就可以开始了。 (您可能需要先将旧的o - o - o <-- A \ o - o - Y <-- B \ ----- M <-- for-merge / o - o - o / / \ / o - o - o - o - o - X <-- hamburger/master \ o - o - o <-- hamburger/topic 重命名,然后执行此操作。还有许多其他选项,例如快速转发for-merge到新的合并提交,或使用{{1移动到它,但他们最终都做了大致相同的事情,除了他们如何将他们的痕迹留在reflogs中。)

如果您喜欢结果,请查看其他分支 - 任何分支 - 并使用master删除您刚刚进行的合并。您将回到您的一个存储库中的两个单独的图形,准备尝试不同的东西。 (这就是我们制作master分支的原因。)

完成所有快捷方式(简单)

除了上述大部分内容之外,一旦您获取了汉堡包回购,您就可以使用所需的树和正确的父提交进行合并提交,然后将所需的任何分支标签设置为新的在一个命令中提交。从您想要指向合并提交的任何分支开始(master,或者更可能是git reset --hard):

git branch -D for-merge

for-merge命令将树ID(在本例中为B)写入新的提交,其父项由(有序)master参数给出。这里的两个父项是当前提交git merge --ff-only $(git commit-tree -p HEAD -p hamburger/master 'HEAD^{tree}') ,以及git commit-tree标识的提交。通过使用当前提交树,我们使新提交树完全匹配当前提交(根据您的问题,我认为您对这些内容的要求是这样)。

'HEAD^{tree}'的输出是新提交的哈希值,因此我们将当前分支标签以快进方式移动到新提交。

请注意,只有在您真正了解此处发生的所有事情时才应执行此操作,并且您确实希望在合并之后使用完全相同的工作树。

答案 1 :(得分:1)

我相信你所说的是一个完整的存储库替换,这样Repo B及其所有历史记录等都反映在repo A中。一些想法:

创意1: 1)回购A:删除所有内容并提交 2)回购B合并为回购A. 3)Repo A被提交并推送

创意2: 1)在Repo B上添加一个新的遥控器,指向与Repo a相同的遥控器 2)做一个git push -force来更新Repo A,绝对是Repo B的状态

非常确定1有效,虽然有点大,但认为B“应该”起作用,因为力量应该忽略并断开Repo A和Repo B的状态并且只是替换东西。