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

ale*_*nst 6 git version-control merge github

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

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

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

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

A提交20的回购:

foo.txt <-- 4 bytes
bar.txt <-- 2 bytes
Run Code Online (Sandbox Code Playgroud)

B提交14的回购:

foo.txt <-- 3 bytes
cat.txt <-- 1 byte
Run Code Online (Sandbox Code Playgroud)

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

A合并后的回购,提交34:

foo.txt <-- 3 bytes
cat.txt <-- 1 byte
Run Code Online (Sandbox Code Playgroud)

额外:存储库A是一个github托管的git repo,B只存在于我的开发机器中.

tor*_*rek 10

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

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

Git的提交DAG

Git与大多数其他版本控制系统完全不同.它在(并使用)提交图上运行,该提交图只是任何D irected A cyclic G raph (或DAG).

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

        o - o - o
      /           \
o - o - o - o - o - X   <-- master
      \
        o - o - o       <-- topic
Run Code Online (Sandbox Code Playgroud)

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

o - o - o               <-- A
       \
        o - o - Y       <-- B
Run Code Online (Sandbox Code Playgroud)

(让我们称之为"AB回购",并再次Y解释其原因).

但是,git允许完全断开连接("不相交")子图:

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

        o - o - o
      /           \
o - o - o - o - o - X   <-- master
      \
        o - o - o       <-- topic
Run Code Online (Sandbox Code Playgroud)

Git"遥控器"

要获取像AB repo这样的现有存储库,并在其图形中添加另一个不同的存储库,只需将不同的存储库添加为远程并使用即可git fetch.例如,从AB repo开始作为当前存储库,您可以git remote add hamburger <url>将汉堡包存储库添加为"远程".此时,跑步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
Run Code Online (Sandbox Code Playgroud)

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

您现在可以通过进入指向所需提交的本地分支来"合并"此图中的任何提交.例如,假设您要创建一个名为的新的本地分支master,它将hamburger/master分支 - 即提交X- 和B分支(即提交)联系在一起,Y暂时忽略所有其他提交.

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

--first-parent当查看分支的历史时,Git具有遵循"第一父"(使用拼写的标志)的概念.虽然git本身并不关心哪个是第一个,哪个不是,但我们人类往往想知道哪个是"主要"分支,哪个是"侧"分支被合并.本--first-parent是为了让我们看到只有 "主"分支,图形日志观众喜欢gitk将以此为"主"分支作为一个连续的直线,而具有"侧面"的分支,分支(见,例如,这一形象这所以问题).

如果你想要B并提交Y看起来像"主"分支,我们应该检查一个指向commit的分支Y.如果你想要master并提交X看起来像"主"分支,我们应该检查一个指向commit的分支X.(现在你知道为什么我们这些标记提交XY!)我们已经有了这样一个分行提交Y-它的本地分支B-但我们没有一个X尚未; 它只有hamburger/master指向它的名称,该名称是"远程分支",而不是常规的本地分支.

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

在任何一种情况下,我们都可以 - 如果您是git的新手,并且不熟悉从错误中恢复的所有方法,那么应该使用新的本地分支来执行此合并.所以让我们得到一个新的本地分支,指向任一提交X:

git checkout -b for-merge hamburger/master
Run Code Online (Sandbox Code Playgroud)

或提交Y:

git checkout -b for-merge B
Run Code Online (Sandbox Code Playgroud)

(记住,远程分支hamburger/master指向提交X,本地分支B指向提交Y:我们在绘制图形时看到了这些).如果您愿意,可以为提交添加实际的SHA-1哈希.Git的只是要转名hamburger/masterB到相应的SHA-1散列反正.

最有可能的是,您希望主(第一父)分支遵循分支B的历史记录,因此我们需要git checkout -b for-merge B.(事实上,在你的资料库,它可能没有命名B,它可能master需要注意的是这是相当确定兼得.master 无关hamburger/master:这是为什么 git fetch.重命名分支机构)

做(特殊)合并

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

然而,在我们到达那里之前,让我们看看我们如何使用普通的merge命令执行此操作.

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

git merge --no-commit hamburger/master
Run Code Online (Sandbox Code Playgroud)

在这一点上,你很可能会遇到一堆冲突.要解决它们,因为你想要的是commit Y(来自分支B)的内容,让我们首先删除合并混乱中的所有内容:

git rm -rf .    # (note: this assumes you're at the top of your work tree)
Run Code Online (Sandbox Code Playgroud)

现在我们从commit重新填充工作树(和index/staging-area)Y,这是由名称B和当前分支指向的for-merge,因此通过HEAD:

git checkout HEAD -- .  # (still assumes top of work tree)
Run Code Online (Sandbox Code Playgroud)

在这一点上,一切都得到了妥善解决(你可以查看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
Run Code Online (Sandbox Code Playgroud)

您现在可以查看任何各种提交并检查它们以确保您喜欢结果.如果你喜欢的结果,重命名for-merge分支你喜欢的任何名称(例如,master),你准备好去.(你可能需要首先重命名旧master的,这样做.还有很多其他选项,比如快速转发master到新的合并提交,或者使用git reset --hard移动到它,但是它们最终都是同样的事情,除了他们如何在reflogs中留下痕迹.)

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

这样做的所有快捷方式(简单)

一旦你获取了汉堡包回购,你可以使用所需的树和正确的父提交进行合并提交,然后将你想要的任何分支标签设置为新的提交,而不是上面的大部分内容.命令.从您想要指向合并提交的任何分支开始(B或者更可能master):

git merge --ff-only $(git commit-tree -p HEAD -p hamburger/master 'HEAD^{tree}')
Run Code Online (Sandbox Code Playgroud)

git commit-tree命令将树ID(在这种情况下)'HEAD^{tree}'写入一个新的提交,其父项由(ordered)-p参数给出.这里的两个父项是当前的提交HEAD,以及由此确定的提交hamburger/master.通过使用当前提交的树,我们使新提交的树与当前提交完全匹配(根据您的问题,这是我认为您想要的这些内容).

输出来自git commit-tree新提交的哈希,因此我们以快进方式将当前分支标签移动到新提交.

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