【问题标题】:Git cannot merge unrelated branchesGit 无法合并不相关的分支
【发布时间】:2018-08-20 23:33:32
【问题描述】:

我在某个软件项目中,我们将代码存储在 GitHub 上。有一个带有 master 和 development 分支的仓库。我在没有互联网连接的笔记本电脑上,我想开始开发另一个功能,所以我做了以下事情:

git init
git remote add origin git://repo_adress
git checkout -b webapp <- created a new branch

我添加了一些文件、文件夹等

然后我做了:

git add .
git commit -m "something"
git push origin webapp

我在 GitHub 上查了一下,现在有 3 个分支。我想将 webapp 合并到开发中(不会有冲突,我在单独的文件夹中工作)。不幸的是,当我尝试git pull 时,发生了这种情况:

 * [new branch]      development -> origin/development
 * [new branch]      master      -> origin/master
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.

现在当我使用 git pull origin development 时,会显示:

From github.com:repo_address
 * branch            development -> FETCH_HEAD
fatal: refusing to merge unrelated histories

我现在能做什么?我应该变基吗?

【问题讨论】:

  • git merge --allow-unrelated-histories branch1 branch2

标签: git github merge


【解决方案1】:

简单的rebase 也可能不起作用,因为它还试图找到“最后一次共同提交”。

最好的机会是使用树运算符形式

rebase --onto target_branch start_commit_exclusive end_commit_inclusive

target_branch 是远程主控,start_commit_exclusive 是不相关分支中的第一个提交,end_commit_inclusive 是不相关分支的 HEAD。

问题是你不能将你的“初始”提交包含在你的无关分支中,所以你可以*cherry-pick”它首先进入远程主控:

git checkout -b master origin/master
git cerry-pick SHA_OF_YOUR_FIRST_COMMIT
git rebase --onto master  SHA_OF_YOUR_FIRST_COMMIT webapp

【讨论】:

    【解决方案2】:

    TL;DR

    您可能应该创建新分支并将您的提交挑选到其中。您可以与--allow-unrelated-histories 合并。无论哪种方式,您都需要至少一个新的提交来绑定到现有的分支。

    这部分是问题的关键:

    我在没有互联网连接的情况下使用笔记本电脑...

    您在笔记本电脑上创建了一个与其他存储库没有关系的存储库。

    git init
    

    我假设您在一个空目录中执行此操作,因此它实际上创建了一个新的空存储库。您的新存储库没有提交,因此没有分支,即使您(相反地)在分支 master 上。也就是说,你在master,但master 不存在!

    git remote add origin git://repo_address
    

    这会添加一个遥控器,但不会联系遥控器(这当然是不可能的)。因此,您的存储库仍然没有提交。

    git checkout -b webapp   <- created a new branch
    

    其实这还没有创建分支。它所做的只是将您从不存在的master 分支更改为不存在的webapp 分支。

    然后我做了:

    git add .
    git commit -m "something"
    

    这是实际创建分支的地方,当您在这个空的存储库中创建它的第一次提交时。而且,由于这个 存储库中的第一个提交,它具有它没有 parent 提交的特殊属性。 Git 将此称为 root 提交

    git push origin webapp
    

    这当然需要访问 Internet,因为它会通过您提供的 URL 调用 Git。但是,只要您有权访问,它所做的就是将您所做的提交(单个根提交)及其所有父级(它没有)及其所有文件完整地传输,并在 repo_address 处询问另一个 Git , 更改或创建其分支名称 webapp 以匹配您自己的最后一次提交 webapp

    到目前为止一切正常,但现在您有一个包含两个根的存储库

    repo_address 的 Git 存储库现在有一个提交图,看起来像是一个更复杂的版本:

    A--...--M   <-- master
         \
          N   <-- development
    
    O   <-- webapp
    

    我假设只有几个提交(这样我就可以用单个字母而不是大而丑陋的哈希 ID 对它们进行编号)。提交 AO 是根提交。所有其他提交都源自AO(但实际上没有任何东西源自O,除非您在笔记本电脑上进行了多次提交)。

    不幸的是,当我尝试git pull 时,发生了这种情况:

     * [new branch]      development -> origin/development
     * [new branch]      master      -> origin/master
    There is no tracking information for the current branch.
    Please specify which branch you want to merge with.
    See git-pull(1) for details.
    

    git pull 命令只运行git fetch,然后是第二个 Git 命令。在这种情况下,它运行 git fetch origin 没有额外的约束(因为在您的存储库中没有为您的 webapp 设置 upstream),因此您的 Git 调用了另一个 Git,即 @987654350 的那个@,并下载了他们拥有的所有你没有的提交和分支——此时,是 masterdevelopment 工作和那些提交。现在您有了与他们相同的图表,只是您的 Git 使用 远程跟踪名称 而不是分支名称:

    A--...--M   <-- origin/master
         \
          N   <-- origin/development
    
    O   <-- webapp (HEAD), origin/webapp
    

    请注意,您仍然拥有自己的分支名称,并且这两个名称——您的分支名称 webapp,以及记住其分支名称 webapp 的远程跟踪名称 origin/webapp——指向提交 O,您的新的根提交。

    git pull 运行的第二个命令通常是git merge。如果您没有指定要合并的内容,则此合并需要上游设置,并且由于您没有上游并且没有指定要合并的内容,因此这部分失败了。

    然后你跑了:

    git pull origin development
    

    其中指定了要合并的内容:由他们的development 标识的提交,您自己的Git 通过远程跟踪名称origin/development 记住该提交。 (在我的猜测图中,我将其绘制为提交 N。)

    git merge 命令通过查找 shared 提交来工作。这是 both 分支上的“最近”(无论它的确切含义:从图表中可以直观地看出)提交。两个分支之一是您当前的分支,即您的 HEAD 所附加的分支。另一个分支是从您命名的提交中找到的,即提交N。所以 Git 从提交 N 回到它的根,提交 A,以找到合适的共享提交。它还从提交 O 回到其根目录,以找到共享提交。但是O 一个根,所以从O向后走O处停止。没有共享提交!

    这就是 为什么 Git 抱怨的原因。附加到提交 O 的历史记录由一个提交组成,即提交 O 本身。提交N 背后有更多的历史,但它永远不会导致提交O。没有共享历史记录,合并没有意义。

    樱桃采摘

    Cherry-picking 包括将提交(即快照)转换为更改集以查看该提交中“发生了什么”,然后将相同的更改集应用于其他提交。变更集只是一个差异列表:将一个提交转换为另一个提交的说明。例如,git diff 就是这样显示的。要将提交转换为差异,Git 会将提交与其父级进行比较。

    显然,挑选根提交有点棘手,但 Git 可以做到这一点。它所做的是将根提交与空提交(嗯,更准确地说,empty tree)进行比较。产生的差异由以下命令组成: 添加这个新文件;这是内容。

    所以您现在可以检查您现有的分支名称development,除了一个问题:您没有拥有一个名为development 的现有分支。记住,你有这个:

    A--...--M   <-- origin/master
         \
          N   <-- origin/development
    
    O   <-- webapp (HEAD), origin/webapp
    

    但是git checkout 会为你创建一个名为development 的分支,如果你要求它检查development,因为它会发现origin/development 看起来很像@ 987654385@,它将继续创建本地名称,指向相同的提交,然后将其设为您的HEAD

    A--...--M   <-- origin/master
         \
          N   <-- development (HEAD), origin/development
    
    O   <-- webapp, origin/webapp
    

    您现在可以创建另一个新分支,例如webapp2,它也指向提交N,使用git checkout -b webapp2

    A--...--M   <-- origin/master
         \
          N   <-- development, origin/development, webapp2 (HEAD)
    
    O   <-- webapp, origin/webapp
    

    现在你可以合并或挑选提交O

    分支名称与分支

    我将把本节的大部分内容外包给另一个 StackOverflow 问题:What exactly do we mean by "branch"? 当我们在这里谈论“分支”时,如果我们不是指分支名称,我们指的是一系列提交以MN 之类的分支提示提交结尾。您将希望您的工作(以分支图结构或分支祖先术语)与这些现有分支相关联。这意味着您希望您的新提交链接回提交N,即development 的当前提示。

    如果你挑剔

    假设您如上所述创建webapp2,然后运行git cherry-pick webapp。 Git 会将O复制 提交到适用于当前提交N 的新提交O'。 Git 将通过执行git diff 通过将提交O 与一棵空树进行比较而产生的所有“使用这些内容创建新文件”操作来做到这一点。由于新文件不会干扰任何现有文件,因此这一切都应该正常工作。

    当 Git 进行新的提交时,它会将当前分支 webapp2 向前拖动以适应它,您将拥有:

    A--...--M   <-- origin/master
         \
          N   <-- development, origin/development, webapp2 (HEAD)
           \
            O'   <-- webapp2 (HEAD)
    
    O   <-- webapp, origin/webapp
    

    这可能是你一直想要的。现在你可以删除你自己的webapp,然后在另一个Git 中的repo_address 中删除webapp,然后在任何地方都使用webapp2,就好像你已经按照你的预期那样做了,如果你这样做了d 早点上网。

    如果你合并

    您可以改为运行git merge --allow-unrelated-histories webapp。这将告诉 Git 创建 两个 差异,使用相同的空树作为公共起点。一个与提交 N 的内容进行比较:添加每个文件。 一个与提交 O 的内容进行比较:添加每个文件。 Git 然后将这些操作结合起来——它们都在不同的文件名上——并进行 merge 提交, 有两个父母:

    A--...--M   <-- origin/master
         \
          N   <-- development, origin/development, webapp2 (HEAD)
           \
            P   <-- webapp2 (HEAD)
      _____/
     /
    O   <-- webapp, origin/webapp
    

    再一次,您可以在任何地方删除webapp。奇怪的是,在您的历史记录(您的提交集)中,您现在拥有这个额外的根提交 O

    练习:git merge --squash

    查看git merge --squash 做了什么并预测它将如何离开您的存储库。这与采摘樱桃相比如何? (如果您愿意,可以在克隆中尝试。)

    【讨论】:

    • 很棒的答案。谢谢!我试图弄清楚为什么我的 repo 有多个根,这正好解释了它。
    • 是否可以运行git merge --allow-unrelated-histories foo 但不会在合并中带来 foo 的任何更改?合并将以我们正在合并的分支结束,保持不变,具有相同的文件状态。这可能吗?
    • @trusktr:如果您希望新合并的快照与您所在分支中的快照完全匹配,请使用 git merge -s ours,即“我们的”策略 (不是“我们的”扩展策略选项)。这可能甚至不需要--allow-unlreated-histories 标志,因为它是策略本身必须确定合并基础。
    猜你喜欢
    • 2021-11-04
    • 2020-08-28
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 2014-01-07
    • 2021-01-12
    • 2018-08-06
    • 1970-01-01
    相关资源
    最近更新 更多