我有一个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
仅存在于我的开发机器中。
答案 0 :(得分:9)
[编辑,2016年10月28日:自2016年6月中旬发布的Git 2.9版以来,您必须在合并命令中添加标记--allow-unrelated-histories
,让Git尝试这种合并在一起。否则其余部分仍适用。]
如果我理解你想要什么,它不仅可能,而且非常微不足道。但我可能听不懂,所以请仔细阅读以下内容。有很多解释和缓慢的设置,以艰难的方式(这让你随时检查一切)。然后,最后,有一个命令可以同时执行所有操作(假设您已经设置了遥控器并首先完成了git fetch
,那就是。)
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
要获取类似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
,暂时忽略所有其他提交。
首先,我们需要创建指向Y
或X
的分支。我们必须选择其中一个。为了进行合并本身,我们选择哪一个并不重要,但为了以后跟踪历史,确实很重要。哪个是正确的?答案取决于您希望稍后看到的内容。
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
的分支。 (现在你知道为什么我们标记了这些提交X
和X
!)我们已经有了一个提交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只是将名称Y
或hamburger/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的状态并且只是替换东西。