我很确定你会想要一个简单的公式,先做 X 然后做 Y,一切都会奏效。不幸的是,如果没有您提供更多信息,我们无法为您提供此信息。最后,我会提出一些可能性,但是你必须检查你的提交,以及在 GitHub 上的存储库中找到的提交。
出了什么问题
这里的最终问题是 Git 不是关于 分支。 Git 是关于提交的。分支名称确实很重要,因为分支名称可以帮助我们——以及 Git——找到提交。但真正重要的是提交。分支名称仅对查找提交很重要;在那之后,名称就不再重要了——这就是为什么您可以随时更改它们。
提交本身被编号。这些提交编号或 hash IDs 是 Git 真正 找到提交的方式。如果你有这个数字,这就是你真正需要的:把它交给 Git,如果你有,Git 会找到提交。这些数字的问题在于它们难以理解,人类无法处理。例如,2283e0e9af55689215afa39c03beb2315ce18e83 是 Git 存储库中 Git 提交的哈希 ID。没有人愿意记住这一点! ? 所以我们使用 name 像 master 或 main 为我们记住它。
当您使用 git push 时,您是在告诉 您的 Git 调用其他 Git,在这种情况下位于 GitHub(origin = https://github.com/randiltennakoon/my-bmi.git,根据 your comment)。你的 Git 和他们的 Git 进行了对话。你的 Git 列出了你的提交哈希 ID,他们列出了他们的(通过大量花哨的算法工作来最小化这种通信),这样你的两个 Git 就可以同意你有哪些提交,他们没有,你的 Git 想要发送到他们。
然后,您的 Git 将这些提交发送给他们。他们将它们放在临时隔离区,同时检查您想做的其他事情。您的 Git 现在要求他们的 Git 设置一些他们的分支名称,以记住这些新提交。
你的 Git 可以让他们的 Git 设置他们的master。如果你告诉你的 Git 这样做,而他们没有master,他们通常会说“好的!”并去做。之后,他们将以他们的名字master 提交您的提交。但要做到这一点,你必须运行一个你还没有运行过的 Git 命令。
你的 Git 可以要求他们的 Git 设置他们的main。如果你告诉你的 Git 这样做,而他们确实有一个 main,他们会查看你刚刚发送的提交。这些提交是否添加到他们的main?还是这些新提交只是清除他们现有的提交,从他们的名字main 中找到?
这是您在该评论中稍后推送失败的地方:
$ git push -u origin main
To https://github.com/randiltennakoon/my-bmi.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'https://github.com/randiltennakoon/my-bmi.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
在这里,您将您的名字 master 更改为您的名字 main,以匹配他们的名字 main。这本身就很好——你可以使用任何你喜欢的名字,而且大多数人发现在 他们的 Git 中像其他人一样使用 相同 的名字是很有帮助的(例如,GitHub)正在使用。然后你让你的 Git 调用他们的 Git,向他们发送你的 main-branch 提交,并让他们设置他们的 main 以记住你的最后一次提交。
在这里,我们需要进行一次侧面旅行。我会尽量保持简短,但这里有很多要知道的!
提交中有什么
我们已经提到,Git 是关于提交的,而且它们是有编号的。如果你运行 git log,你的 Git 会以相反的顺序显示你的提交——在你的情况下,所有提交,因为你只有一个分支——以及它们的哈希 ID(提交编号)。
这些数字看起来是随机的。它们实际上根本不是随机的:它们是使用来自提交的全部内容的加密哈希计算的。这就是 Git 确保每个提交都获得一个 唯一 编号的方式。但这也意味着任何现有提交的任何部分都不能更改。无论您现有的提交有什么编号,这些 都是 他们的编号。各地的每个 Git——你的,GitHub 上的那个,我的,Fred 的,每个 Git——都同意这些是提交的正确数字。没有其他提交可以在任何地方获得这些数字。这就是数字如此庞大和丑陋的原因。
现在,每个提交(无论其数量如何)都由两部分组成:
-
提交的一部分是 Git 知道的每个文件的完整快照,在您或任何人进行提交时。也就是说,在您的git add .(或其他)和git commit 之后,Git 制作了它当时知道的所有文件的快照:这是您添加的任何文件,加上它已经知道的任何文件关于。 Git 目前实际上使用的是 Git index a.k.a. staging area 中那些文件的版本,而不是您可以查看和使用的版本,但我们会跳过这个细节。
-
提交的另一部分是 Git 所称的 元数据,或有关提交本身的信息。在这里,Git 存储您的姓名、提交的作者以及您的电子邮件地址。 Git 添加了日期和时间戳。 Git 包含您的日志消息,您在其中解释为什么您进行此提交(不是其中的内容,我们可以从提交的快照部分看到,而是 为什么你做了你所做的一切,我们不能从快照中看到)。而且,对于 Git 本身至关重要的是,Git 添加了 previous 提交的哈希 ID。
这些哈希 ID 最终形成了一个向后看的链。让我们画一个,使用单个大写字母来代表令人费解的哈希 ID:
A <-B <-C
这是一个由三个提交组成的简单链,您可以在一个几乎全新的 Git 存储库中找到它。提交A 是第一次提交。它没有任何更早的提交——它不能;这是 first 提交——所以它根本不指向后面。然而,提交B 是第二次 提交,因此它向后指向A,即第一次提交。提交C,当前是last 提交,向后指向B。
实际上,作为C 元数据的一部分,提交C 字面上包含先前提交B 的实际提交编号(哈希ID)。同样,B 包含先前提交 A 的哈希 ID。
为了找到所有三个提交,Git 只需要这三个提交中last的哈希ID。我们让 Git 将此哈希 ID 粘贴到名称中,例如 master:
A--B--C <-- master
如果我们将名称从master 更改为main,则提交 都不会更改:
A--B--C <-- main
所以这很简单:我们可以随意更改分支名称。我们选择的任何一个分支名称的git checkout 实际上会get 提交C——无论它的真实哈希 ID 是什么——因为我们拥有的一个名称中包含提交 C 的哈希 ID .
如果我们进行 new 提交,我们的新提交——我们称之为D,尽管实际上它得到了一些丑陋的大哈希 ID——将包含,作为它的反向链接,提交C的哈希ID:
A--B--C--D <-- main
这就是分支的生长方式。从D 到C 的反向连接表示提交D 的父 是提交C。 C 的父级是B,以此类推,只是一旦我们点击A,就根本没有父级,一切都停止了。
GitHub 也有一个 Git 存储库
同时,在 GitHub 上,通过 URL https://github.com/randiltennakoon/my-bmi.git 找到,另一个 Git 存储库。这个其他 Git 存储库有一些提交。这些提交有一些哈希 ID。
如果这些提交与您的相同的提交 - 例如,如果他们有相同的A-B-C - 你会处于良好状态。你会让你的 Git 调用他们的 Git。你的 Git 会列出哈希 ID D。他们会说:嘿,我没有 D,你为什么不给我那个? 你的 Git 会将 D 添加到提交列表中以发送过来。那么你的 Git 会说 D 的父级是 C。他们会检查并查看他们确实拥有C。这意味着他们也有A 和B,所以你的Git 必须发送的唯一提交是D。
然后,您的 Git 将只发送 D,并要求他们设置 他们的 main 记住 D 而不是 C。这很好,因为D 本身记得C。您只是添加到他们的提交中。
唉,这不是正在发生的事情。相反,他们有,例如:
G--H <-- main
在他们的存储库中。因此,如果您将整个系列的提交发送给他们,他们最终会得到:
A--B--C--D
G--H <-- main
然后你的 Git 要求他们将他们的 main 设置为指向 D。这将失去他们的提交。名称main 只能指向一个最后一次提交,例如H 或D。切换到D 不会添加到他们的提交中。所以他们拒绝了。
你如何解决它
你需要做的是:
- 获得他们的提交。
- 决定如何组合你的提交和他们的提交。
第 2 步是一个重大决定。这是我们无法为您制作的。
第 1 步很简单:只需运行 git fetch。完成后,您将拥有一个origin/main。这个名字不是一个分支的名字,但它在大多数方面几乎一样好,在一个特定方面更好:这是你的 Git 记住 他们的 Git 的分支名称。您运行的每个 git fetch 都会从他们那里获得任何新的提交,然后更新您对其 Git 的 main 分支名称(以及任何其他分支)的记忆。1
无论如何,这个origin/main 可以让你看到他们的提交——现在也在 你的 存储库中,通过 他们的 Git 使用的相同哈希 ID 找到,因为每个 Git 都同意 那些 提交获得 那些 ID。你现在有了他们的提交和你的提交。这些不会相互关联,因为您的提交完全独立于他们的,而不是从他们的开始。
运行:
git log origin/main
查看所有他们的提交。他们有什么承诺?那些提交 in 是什么?它们重要吗?你应该保留这些提交吗?如果是这样,如何你想结合 你的提交与他们的?
1此处的确切细节取决于您运行的git fetch 的种类。例如,git pull 命令运行一个有限的git fetch,它不会获取所有内容。
组合方法#1:git merge
组合两个提交字符串的最简单方法是使用git merge。在你的情况下这仍然是正确的,但有一个障碍。
通常,使用git merge,我们从以下内容开始:
I--J <-- ours
/
...--G--H
\
K--L <-- theirs
也就是说,我们有一个分支——我们在这张图中称它为ours——在提交J处结束,并且他们有一个在提交L 结束的分支.我们让 Git 结合了这两个分支。 Git 通过查找提交 H 来实现这一点,Git 将其称为 merge base。从这张图中可以看出,提交H 是最佳共享提交。我们从两个分支末端开始——提交J 和L——然后开始向后工作,就像 Git 一直做的那样。从J,我们回到I,然后到H,和G,以此类推。从L,我们回到K,然后是H和G等等。
这两个遍历相遇在提交H。他们不断地从那里开会,所以提交H 自动成为两个分支相遇的最佳(即最后一个)提交。然后,合并操作会找出自提交 H 以来我们已经 更改了什么,以及他们 更改了什么,并结合这些更改。
我不确切知道你的 Git 和他们的 Git 中有什么,但我确实知道,从你创建这两个 Git 存储库的方式来看,你拥有的和他们拥有的永远不会相遇时间>。你有类似的东西:
A--B--C--D <-- main
G--H <-- origin/main
如果我们从两端开始并向后工作,我们就永远不会相遇。
从 Git 2.9 版开始,git merge 默认不会合并这些。它只是抱怨历史无关,然后停止。您可以使用--allow-unrelated-histories 告诉 Git 继续合并。
这是否有效取决于D 和H 中的快照 中的内容(或您最后一次提交的任何内容)。如果您遇到合并冲突,您必须自己解决合并冲突。一旦这一切都完成了——所有的冲突都解决了,或者没有冲突——Git 可以创建一个新的 merge commit,我将它称为 M for Merge:
A--B--C--D
\
M <-- main
/
G--------H <-- origin/main
提交M 的特别之处在于,它不仅仅是一个父级,例如D 或H,它有both 作为它的两个父母。所以它结合了两个独立的提交链。
假设您想要制作M,并且确实制作M,您现在可以运行:
git push -u origin main
您将向另一个 Git 发送提交 A-B-C-D-M。提交 M 链接回提交 H,这就是 他们的 main 现在所在的位置。因此,如果他们遵守 Git 的礼貌请求,将他们的 main 设置为指向 M,他们也会保留他们的 G-H 提交。他们会裁定这很好。
提交M 中的内容作为快照 是合并两个提交的结果。如果您必须手动进行合并,因为合并冲突,您选择提交M 的内容作为快照。如果 Git 能够自己进行合并,Git 将自己提交M,它会拥有你在D 中的所有内容,以及它们在H 中拥有的所有内容。
组合方法#2:git rebase
rebase 命令相当复杂,这里就不好好解释了。有关更多信息,请参阅其他 StackOverflow 答案。相反,我会注意到它通过 复制 提交来工作,就像通过运行 git cherry-pick 一样。
假设你确实有这个:
A--B--C--D <-- main
G--H <-- origin/main
如果您运行 git rebase origin/main,您的 Git 会将提交 A-B-C-D 的四个哈希 ID 保存在一个临时文件中。然后,您的 Git 将使用 git cherry-pick 或等效于 copy 每个提交,按照 A-B-C-D 顺序(向前!Git 必须将它们向后列出,然后反向列出来执行此操作),以在提交H 之后。如果我们调用A、A'的copy,以及BB'的副本等等,我们可以画出这样的结果:
A--B--C--D <-- main
G--H <-- origin/main
\
A'-B'-C'-D' <-- ???
问号表明这里有一个问题:你的 Git 将如何找到副本 D'?答案是:它将 name main 从旧的提交系列中拉出,并将其粘贴到新系列的末尾。结果是:
A--B--C--D ???
G--H <-- origin/main
\
A'-B'-C'-D' <-- main
现在很难找到旧的提交。如果您将他们的哈希 ID 保存在某处(在纸上、白板上、文件中等),您可以使用它们。 Git 还有其他技巧可以帮助您在一段时间内找到这些提交——默认情况下至少再找到 30 天——但在大多数情况下,提交 A-B-C-D似乎消失了强>。结果,当您使用 git log 查看您的提交时,您现在将只有 六个 提交,而不是十个:git log 将显示您提交 D',然后是 C' , 等等。当它到达A' 时,好吧,提交A' 确实 有一个父节点:它链接回提交H。所以在A' 之后,git log 显示H,然后是G,然后因为提交用完而停止。
你现在可以运行了:
git push -u origin main
您的 Git 将发送提交 A'、B'、C' 和 D',并礼貌地询问他们的 Git,他们是否会设置 他们的 main 指向到D'。由于D' 扩展了他们的分支,他们会说OK。
假设,在查看他们的提交后,您认为这些提交完全没有价值。完全扔掉它们并没有什么坏处。
在这种情况下,您可以使用 Git 所谓的 强制推送、git push -f 或 git push --force-with-lease。 --force-with-lease 版本增加了一些额外的安全性(通常是要走的路),但我不会在这里详细介绍这种安全性是什么。
这些选项的作用是告诉另一个 Git,在这种情况下,在 GitHub 上:我命令你,用我的提交哈希 ID 替换你的 main! 也就是说,与常规推送不同,这不是一个礼貌的要求。这是一个直接的命令。他们不必必须服从,但如果您拥有该存储库,他们就会这样做。
服从您的推送意味着他们会使用您的A-B-C-D 链(或其他任何东西)并将他们的main 指向该链中的最后一个提交,就像您现有的main 一样。所以如果你有:
A--B--C--D <-- main
G--H <-- origin/main
如果你强制推送到 GitHub (git push --force-with-lease -u origin main),他们会将 他们的 main 设置为指向 D。你最终得到:
A--B--C--D <-- main, origin/main
G--H ???
提交 G 和 H 似乎消失了。它们实际上会在未来某个时候从 GitHub 存储库中真正消失 - 很难说什么时候,因为这取决于 GitHub,但 30 天规则适用于你的 Git 不适用。它们可能会立即消失,或者在一天左右后消失。
因为您确实将它们放入您自己的(本地)Git 存储库,您的如果您认为它们很有价值,Git 将在至少 30 天内使用 G-H毕竟。 找到它们可能很棘手!我们(人类)使用名称而不是哈希 ID 是有原因的。