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与大多数其他版本控制系统完全不同.它在(并使用)提交图上运行,该提交图只是任何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)
要获取像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
.(现在你知道为什么我们这些标记提交X
和Y
!)我们已经有了这样一个分行提交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/master
或B
到相应的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
新提交的哈希,因此我们以快进方式将当前分支标签移动到新提交.
请注意,只有在您真正了解此处发生的所有事情时才应该执行此操作,并且您确实希望在合并之后使用与之前完全相同的工作树.