【问题标题】:Why can't I merge my git dev branch to master even though master only contains an empty README?为什么即使 master 只包含一个空的 README,我也不能将我的 git dev 分支合并到 master?
【发布时间】:2019-08-16 06:11:43
【问题描述】:

我正在构建在我的dev 分支上开发的应用程序的最后阶段,现在想将其添加到master 以进行生产。

但是,当我结帐到master 并运行git merge dev 时,我收到refusing to merge unrelated histories 的致命错误。不幸的是,git pull origin master --allow-unrelated-historiesdidn't help resolve the error

这是master的状态:

  • git status 表明我的“分支与 'origin/master' 是最新的”。
  • repo 中唯一的文件是一个空的README.md
  • GitLab 已将此标记为“陈旧”分支。
  • 这是一个受保护的分支。

帮助我了解为什么我无法合并。

编辑:我还应该提到git log 产生以下内容:

commit ac22443836aeaf8abe38aa3761970a4cd3835587 (HEAD -> master, origin/master)
Author: // my info
Date:   Fri Dec 21 03:25:26 2018 -0700

    Initial commit
(END)

【问题讨论】:

  • masterdev 是否有共同的祖先(我的意思是共同的提交父母)?
  • 我经历过,它实际上似乎没有共同的祖先,这可能是问题的症结所在。但它看起来像一个已发布的解决方案(但随后被另一个用户删除......不知道为什么)成功了。基本上,git merge dev --allow-unrelated-histories.
  • 您是如何设置本地开发环境的?您是否克隆了要合并的 repo,然后 git checkout -b dev 创建您的 dev 分支?
  • @JoeMcMahon 这就是我认为我所做的,因为这是我的作案手法。但是无论出于何种原因,当我回顾我的日志时,我没有看到 devmaster 分支出来或与 master 共享相同的提交 ID。所以这让我很奇怪。尽管这是在 3 个月前完成的,但它似乎已经很久了。我上面描述的修复masterahead of 'origin/master' by 720 commits.

标签: git gitlab


【解决方案1】:

如果--allow-unrelated-hhistories 是必要的,这意味着您实际上拥有相当于两个不同的存储库,它们合并在一起。

我怀疑发生这种情况是因为您在创建开发分支并开始工作之前没有先clone 原始存储库。我从你的描述中推断出你做了一个git init,签出了一个dev 分支,然后当你想合并时将远程仓库添加为origin(这是因为你的本地仓库没有遥控器)。

此时尝试合并会得到“不相关的历史记录”,因为这两个存储库实际上是不相关的;原始文件已创建并推送到 GitLab,当您使用 git init 创建本地文件时,创建了一个完全独立的文件。克隆原始版本会创建一个与 相关的本地存储库,因为它共享了最初的 README 提交。

这确实是解决此问题的唯一方法,一旦您推送到 GitLab,本地和远程将同步 - 但您应该始终克隆以避免首先出现问题。

【讨论】:

  • 你是那个建议运行git merge dev --allow-unrelated-histories的人吗?然后有人确实删除了这个解决方案。但它奏效了。我想在信用到期的地方给予信用。
  • 不是我,抱歉 - 但这是这种情况的正确答案。我在从多个存储库中创建单一存储库时使用过它,所以它本质上并不是一个 的事情。只是如你所说,clone-branch-commit-merge/rebase 更好。
【解决方案2】:

如果,如前所述,masterdev 没有共同的(父)提交,我会尝试从 master 创建新分支并从 dev 中挑选所有提交它: git cherry-pick startHash^..endHash 例如git cherry-pick 1234^..5678。 然后你应该可以将这个新分支合并到master

【讨论】:

    【解决方案3】:

    您可能在创建存储库时选中了“[ ] Create repository with a README”。

    master真的为空时,我看到以下选项

    • 转到 gitlab,将默认分支更改为其他内容,然后在 Web 界面中删除 master 分支(在那里,可以删除受保护的分支)。或者,

    • 取消保护master

    现在你可以将你的 dev 分支强制推送到 gitlab(例如 git push -f dev:master)。

    注意:此选项不适用于分支机构。其他人使用的存储库。在这种情况下:

    git fetch gitlab master
    git merge -s ours FETCH_HEAD
    

    并推送这个分支。

    【讨论】:

    • 澄清一下git fetch gitlab mastergit merge -s ours FETCH_HEAD 是从我的master 分支执行的,对吗?
    • git fetch 可以在任何地方执行(甚至在裸存储库中)。对于git merge,您必须签出应成为master 的分支。
    【解决方案4】:

    您已经使用--allow-unrelated-histories 修复了(?)这个问题,没有真正的理由不离开它。但如果你仍然想知道......

    发生了什么,以及您(可能)是如何来到这里的

    使用 Git 的第一个关键是要了解 Git 是关于 commits 的。当然,这也需要你以一种相当深入的方式理解什么是提交。它是什么,非常简短:它是一个永久(大部分)和不可变(完全)快照加上一些元数据。快照包含你的所有文件——嗯,所有这些文件都是你提交时的——并且元数据有:

    • 您的姓名和电子邮件地址,以及您提交的时间;
    • 你的日志信息,即,为什么你做了这个提交;而且,至关重要的是,
    • 在此提交之前的提交的哈希 ID,定义为该提交的

    每个提交都是唯一的——出于多种原因,包括上面提到的时间戳——并且每个唯一的提交都有一个唯一的哈希 ID。哈希 ID 是一些丑陋的十六进制字符串,看起来是随机的,但实际上是提交的 内容 的加密校验和。它也是该提交的真实名称:该 ID 表示该提交,并且仅表示该提交。没有其他提交将拥有该哈希 ID。该哈希 ID 将始终意味着 那个 提交。

    Git 实际上找到哈希 ID 的提交。所以哈希ID是至关重要的。当然,人类也无法记住。所以 Git 为我们提供了一种方法来记住 latest 哈希 ID,这种方法是 branch name,例如 masterdev

    名称只需要记住last提交因为每个提交都自己记住其父级的哈希。也就是说,给定一个只有三个提交的小型存储库,我们将实际的哈希 ID 替换为一个大写字母,我们可以得出这样的结论:

    A <-B <-C   <-- master
    

    名称master 记住C 的哈希ID。 C 本身——实际提交,通过哈希 ID 检索——记住 B 的哈希 ID,B 本身记住 A 的哈希 ID。

    当某个东西记住了某个其他提交的哈希 ID 时,我们说这个东西 指向该提交。所以名字master指向CC指向BB指向A。这三个提交——C,然后是B,然后是A——是存储库中的历史记录。

    请注意,A 并不指向任何地方。它实际上不能,因为它是第一次提交。没有更早的提交。所以它只是没有,Git 称之为 root 提交。所有非空存储库都必须至少有一个根提交。大多数人可能只有一个……但你的有两个。

    正常的分支和合并

    让我们快速看一下更正常的制作分支的方法。假设我们只有这三个提交,由master 指向。我们要求 Git 创建一个 new 分支名称,指向与 master 相同的提交:

    A--B--C   <-- dev (HEAD), master
    

    这两个名称都标识了提交C,因此提交C 处于开启状态,并且是两个分支的提示提交,并且所有三个提交都在两个分支上。但是现在我们进行了新的提交。进行新提交的过程——使用通常的编辑和git addgit commit——创建一个新快照,添加我们的姓名、电子邮件和时间戳等,使用 current 提交 @987654348 @ 作为保存的哈希,并构建新的提交。新提交获得了一些丑陋的哈希 ID,但我们将其命名为 D

    A--B--C   <-- dev (HEAD), master
           \
            D
    

    由于D 的父级是CD 指向C。但现在奇迹发生了:Git 将 D 的哈希 ID 写入当前的分支名称——HEAD 所附加的那个——所以现在我们有了:

    A--B--C   <-- master
           \
            D   <-- dev (HEAD)
    

    瞧,我们有了一个新分支。 (嗯,我们之前有过,指向C。但是大多数人不喜欢这样想,他们想调用D分支。实际上分支是D-C-B-A!)

    随着时间的推移,我们向两个分支添加了一些提交:

    A--B--C-----J----K----L   <-- master
           \
            D--E--F--G--H--I   <-- dev
    

    我们git checkout mastergit merge dev。 Git 会为我们找到 merge base 提交,devmaster 存在分歧。这显然是提交C,因为这是两个分支过去重新加入的地方。 Git 将比较 CL 以查看我们在 master 上所做的更改,比较 CI 以查看我们在 dev 上所做的更改,然后合并更改。 Git 将 combined 更改应用于 C 中的快照(合并基础)并进行新的 merge commit M,这像往常一样继续我们当前的HEAD 分支,更新该分支名称,使master 指向M

    A--B--C-----J----K----L--M   <-- master (HEAD)
           \                /
            D--E--F--G--H--I   <-- dev
    

    M 的特别之处在于它有 两个 反向链接:它返回到 L,就像所有提交一样,但它有第二个父 I,这是分支dev 的当前提示提交。不过,除了两个父母之外,它很普通:它像往常一样有一张快照,还有我们的姓名、电子邮件、时间戳和一条日志消息。

    异常分支

    Git 中没有任何东西可以阻止您进行额外的根提交。这有点棘手。假设,不知何故,你这样做了:

    A   <-- master
    
    B--C--D--...--L   <-- dev (HEAD)
    

    一旦你遇到这种情况,git checkout master; git merge dev 只会给你一个错误。这是因为寻找合并基础的常用方法——从两个分支提示开始并向后工作——永远找不到共同的提交。

    添加--allow-unrelated-histories 告诉Git 假装两个分支之前都有一个特殊的空提交:

     A   <-- master (HEAD)
    0
     B--C--D--...--L   <-- dev
    

    现在 Git 可以区分 0A 以查看您在 master 上所做的更改,以及 0L 以查看他们在 dev 上所做的更改。在master,您添加了每个文件。在dev 上,您还添加了每个文件。只要这些是不同文件,组合这两个更改的方法是将提交A中的master文件添加到提交L中的dev文件中,应用这些更改到空的 null 提交,并提交结果,父母同时返回 AL

    A---------------M   <-- master (HEAD)
                   /
    B--C--D--...--L   <-- dev
    

    你(可能)是怎么来到这里的

    有一个git checkout 选项git checkout --orphan 可以设置此状态。但这可能不是你所做的。当您使用git init 创建一个新的空存储库时,此设置的状态是相同

    [no commits]   <-- [no branches]
    

    没有分支,但 Git 会说你是 on branch master。你不能master:它不存在。但你是,即使它不存在。 Git 管理它的方式是它把 name master 放入 HEAD(实际上是 .git/HEAD),而不首先创建一个名为 master 的分支。它无法创建分支,因为分支名称​​必须包含有效的哈希 ID,而没有。

    因此,当您运行 git commit 时,Git 会检测到这种异常状态:HEAD 表示 mastermaster 不存在。这就是触发 Git 使我们的根提交 A 的原因。然后 Git 将 A 的哈希 ID 写入分支,创建分支,现在我们有了:

    A   <-- master (HEAD)
    

    这正是我们想要的。

    但是假设,当我们处于这种奇怪的 no-commits-yet 状态时,我们运行:

    git checkout -b dev
    

    这告诉 Git:将名称 dev 放入 HEAD。即使没有master,它也会毫无怨言地做到这一点。然后我们进行第一次提交,但没有明显的原因,我们将选择 B 作为其哈希 ID 的单字母替代:

    B   <-- dev (HEAD)
    

    同时,在这里运行git init,然后运行git checkout -b dev,然后做了一些事情和git commit,我们将转到$WebHostingProvider——无论是GitHub、GitLab、Bitbucket还是其他——并使用它的让我成为一个新的存储库 clicky 按钮。这些通常有一个选项:使用 README 和/或 LICENSE 文件等创建初始提交。如果该选项被选中——或者 don't 选项未被选中——他们会继续进行第一次提交和master:

    A   <-- master (HEAD)
    

    现在您将您的存储库连接到他们的存储库,并让您的 Git 下载他们拥有的任何您没有的提交:

    A   <-- origin/master
    
    B   <-- dev (HEAD)
    

    您现在可以继续添加大量提交,永远不会注意到您的 dev 分支与其 master 分支(您的 Git 调用 origin/master)无关。

    稍后,您运行:

    git checkout master
    

    您的 Git 注意到您没有master,但您确实有origin/master。因此,您的 Git 创建一个 master 为您,指向与 origin/master 相同的提交,并将您的 HEAD 附加到您的新 master

    A   <-- master (HEAD), origin/master
    
    B--C--D--...--L   <-- dev
    

    ,你在泡菜中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-29
      • 1970-01-01
      • 1970-01-01
      • 2016-04-30
      • 2018-03-08
      • 1970-01-01
      • 1970-01-01
      • 2013-01-14
      相关资源
      最近更新 更多