【问题标题】:Merging two completely different repositories合并两个完全不同的存储库
【发布时间】:2016-06-28 14:27:10
【问题描述】:

我有一个 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

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

repo A 合并后,提交 34:

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

附加: Repository A 是一个 github 托管的 git repo,而 B 只存在于我的开发机器中。

【问题讨论】:

    标签: git github version-control merge


    【解决方案1】:

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

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

    Git 的提交 DAG

    Git 与大多数其他版本控制系统完全不同。它在一个提交图上运行(并使用),它只是任何D指向A循环G图(或 DAG)。

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

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

    (这看起来有点像汉堡包,所以我们称它为“汉堡包仓库”——稍后我会解释为什么会有一个标记为 X 的提交),或者:

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

    (我们称其为“AB repo”,Y 的原因将在后面解释)。

    然而,git 允许完全断开(“不相交”)的子图:

    o - o - o               <-- A
           \
            o - o - Y       <-- B
    
            o - o - o
          /           \
    o - o - o - o - o - X   <-- master
          \
            o - o - o       <-- topic
    

    Git“远程”

    要获取现有的存储库(如 AB 存储库)并向其图形添加另一个不同的存储库,只需将不同的存储库添加为远程存储库并使用 git fetch。例如,从 AB 存储库作为当前存储库开始,您可以 git remote add hamburger &lt;url&gt; 将汉堡存储库添加为“远程”。此时,运行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

    您现在可以“合并”此图中的任何提交,方法是进入指向所需提交的本地分支。例如,假设您要创建一个名为master 的新本地分支,它将hamburger/master 分支(即提交X)和B 分支(即提交Y)联系在一起,忽略所有其他提交片刻。

    首先,我们需要创建分支,指向XY。我们必须选择两者之一。为了进行合并本身,我们选择哪一个并不重要,但为了以后跟踪历史,它确实很重要。哪个是正确的?答案取决于你以后想看到什么

    在查看分支的历史记录时,Git 具有遵循“第一父”的概念(使用拼写为 --first-parent 的标志)。虽然 git 本身并不关心哪个是第一个,哪个不是,但我们人类往往想知道哪个是“主”分支,哪个是被合并的“侧”分支。 --first-parent 旨在让我们看到“主”分支,而像 gitk 这样的图形日志查看器会将“主”分支绘制为连续的直线,同时具有“边”分支,分支(参见,例如,this SO question 中的this image)。

    如果您希望B 和提交Y 看起来像“主”分支,我们应该检查一个指向提交Y 的分支。如果您希望master 和提交X 看起来像“主”分支,我们应该检查一个指向提交X 的分支。 (现在你知道为什么我们将这些提交标记为XY!)我们已经有一个提交Y 的分支——它是本地分支B——但我们还没有一个用于X 的分支;它只有名称hamburger/master 指向它,并且该名称是“远程分支”,而不是常规的本地分支。

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

    在任何一种情况下,我们都可以——如果你是 git 新手并且不熟悉从错误中恢复的所有方法,应该——使用 new 本地分支来做这个合并。所以让我们新建一个本地分支,指向提交X

    git checkout -b for-merge hamburger/master
    

    或提交Y:

    git checkout -b for-merge B
    

    (请记住,远程分支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 分支上,我们可以进行合并,但根据您的问题,我们根本不想要 正常 合并。事实上,正常的合并主要只是妨碍,因为没有merge base。在这种情况下 git 所做的是使用空树作为合并基础,因此您往往会遇到很多创建/创建冲突。所以我们最终可能想要做的是使用一个内部(不是日常使用)git 命令git commit-tree,来进行我们的新提交。

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

    首先,以防万一它确实有效,我们不希望 git 提交合并,所以让我们使用--no-commit。然后,我们唯一需要做的就是将git merge 指向要合并的提交。这很可能是提交 X,我们可以通过它的实际 SHA-1 来命名,或者通过名称 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)
    

    现在我们从提交Y 重新填充工作树(和索引/暂存区域),名称B 和当前分支for-merge 都指向它,因此@987654390 也指向它@:

    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 分支重命名为您喜欢的任何名称(例如,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}')
    

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

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

    请注意,只有在您真正了解这里发生的所有事情并且您确实希望在合并后使用与以前完全相同的工作树时才应该这样做。

    【讨论】:

    • 这是一个绝妙的答案。虽然我的问题其实不一样,但我从中学到了很多。
    【解决方案2】:

    我相信您所说的是一个完整的存储库替换,因此存储库 B 及其所有历史等都反映在存储库 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 有效,虽然有点大的 hack,但认为 B“应该”有效,因为强制应该忽略并断开 Repo A 和 Repo B 的状态之间的连接并替换东西。

    【讨论】:

      猜你喜欢
      • 2023-03-22
      • 1970-01-01
      • 1970-01-01
      • 2012-04-29
      • 2018-02-07
      • 1970-01-01
      • 1970-01-01
      • 2010-09-19
      • 1970-01-01
      相关资源
      最近更新 更多