您已经使用--allow-unrelated-histories 修复了(?)这个问题,没有真正的理由不离开它。但如果你仍然想知道......
发生了什么,以及您(可能)是如何来到这里的
使用 Git 的第一个关键是要了解 Git 是关于 commits 的。当然,这也需要你以一种相当深入的方式理解什么是提交。它是什么,非常简短:它是一个永久(大部分)和不可变(完全)快照加上一些元数据。快照包含你的所有文件——嗯,所有这些文件都是你提交时的——并且元数据有:
- 您的姓名和电子邮件地址,以及您提交的时间;
- 你的日志信息,即,为什么你做了这个提交;而且,至关重要的是,
- 在此提交之前的提交的哈希 ID,定义为该提交的 父。
每个提交都是唯一的——出于多种原因,包括上面提到的时间戳——并且每个唯一的提交都有一个唯一的哈希 ID。哈希 ID 是一些丑陋的十六进制字符串,看起来是随机的,但实际上是提交的 内容 的加密校验和。它也是该提交的真实名称:该 ID 表示该提交,并且仅表示该提交。没有其他提交将拥有该哈希 ID。该哈希 ID 将始终意味着 那个 提交。
Git 实际上找到哈希 ID 的提交。所以哈希ID是至关重要的。当然,人类也无法记住。所以 Git 为我们提供了一种方法来记住 latest 哈希 ID,这种方法是 branch name,例如 master 或 dev。
名称只需要记住last提交因为每个提交都自己记住其父级的哈希。也就是说,给定一个只有三个提交的小型存储库,我们将实际的哈希 ID 替换为一个大写字母,我们可以得出这样的结论:
A <-B <-C <-- master
名称master 记住C 的哈希ID。 C 本身——实际提交,通过哈希 ID 检索——记住 B 的哈希 ID,B 本身记住 A 的哈希 ID。
当某个东西记住了某个其他提交的哈希 ID 时,我们说这个东西 指向该提交。所以名字master指向C,C指向B,B指向A。这三个提交——C,然后是B,然后是A——是存储库中的历史记录。
请注意,A 并不指向任何地方。它实际上不能,因为它是第一次提交。没有更早的提交。所以它只是没有,Git 称之为 root 提交。所有非空存储库都必须至少有一个根提交。大多数人可能只有一个……但你的有两个。
正常的分支和合并
让我们快速看一下更正常的制作分支的方法。假设我们只有这三个提交,由master 指向。我们要求 Git 创建一个 new 分支名称,指向与 master 相同的提交:
A--B--C <-- dev (HEAD), master
这两个名称都标识了提交C,因此提交C 处于开启状态,并且是两个分支的提示提交,并且所有三个提交都在两个分支上。但是现在我们进行了新的提交。进行新提交的过程——使用通常的编辑和git add 和git commit——创建一个新快照,添加我们的姓名、电子邮件和时间戳等,使用 current 提交 @987654348 @ 作为保存的哈希,并构建新的提交。新提交获得了一些丑陋的哈希 ID,但我们将其命名为 D:
A--B--C <-- dev (HEAD), master
\
D
由于D 的父级是C,D 指向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 master 和git merge dev。 Git 会为我们找到 merge base 提交,dev 和 master 存在分歧。这显然是提交C,因为这是两个分支过去重新加入的地方。 Git 将比较 C 与 L 以查看我们在 master 上所做的更改,比较 C 与 I 以查看我们在 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 可以区分 0 与 A 以查看您在 master 上所做的更改,以及 0 与 L 以查看他们在 dev 上所做的更改。在master,您添加了每个文件。在dev 上,您还添加了每个文件。只要这些是不同文件,组合这两个更改的方法是将提交A中的master文件添加到提交L中的dev文件中,应用这些更改到空的 null 提交,并提交结果,父母同时返回 A 和 L:
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 表示 master 但 master 不存在。这就是触发 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
和瞧,你在泡菜中。