git push 命令与主和主命令冲突

  • 本文关键字:命令 冲突 push git git
  • 更新时间 :
  • 英文 :


当我在终端上运行git push -u origin main时,它说error: src refspec main does not match any.

但是当我运行git push -u origin master时,它工作正常,它会在存储库中创建另一个master分支以及默认的main分支,总分支将为 2。

此时,我无法执行 PR 以将master合并到main,并且错误消息there's no diff between master and main branches.

谁能解释这种情况并给我一个可行的解决方案?您的支持将不胜感激。

我很确定你想要一个简单的公式,先做X然后做Y,一切都会起作用。 不幸的是,如果没有您提供更多信息,我们无法为您提供此信息。 最后,我会建议一些可能性,但你必须检查你的提交,以及在 GitHub 上的存储库中找到的提交。

出了什么问题

这里的最终问题是 Git 与分支无关。 Git 是关于提交的。 分支名称确实很重要,因为分支名称可以帮助我们和 Git找到提交。 但真正重要的是提交。 分支名称仅对查找提交很重要;在那之后,名称不再重要——这就是为什么您可以随时更改它们的原因。

提交本身已编号。 这些提交编号或哈希 ID是 Git真正找到提交的方式。 如果你有这个数字,这就是你真正需要的:把它交给 Git,如果你有的话,Git 会找到提交。 这些数字的问题在于它们是人类无法理解和不可能处理的。 例如,2283e0e9af55689215afa39c03beb2315ce18e83是 Git 的 Git 存储库中提交的哈希 ID。 没有人愿意记住这一点! 所以我们用mastermain这样的名字来为我们记住它。

当你使用git push时,你告诉你Git 调用其他一些 Git,在这种情况下是在 GitHub 上(origin=https://github.com/randiltennakoon/my-bmi.git,根据你的评论)。 你的 Git 和他们的 Git 有对话。 你的 Git 列出了你的提交哈希 ID,他们的列出了他们的(有很多花哨的算法工作来最小化这种通信),这样你的两个 Git 就可以同意你有哪些提交,他们没有,你的 Git 想要发送给他们。

然后,你的 Git 将这些提交发送给他们。 他们把他们放在一个临时隔离区,同时检查你想做的其余事情。 您的 Git 现在要求他们的 Git 设置他们的一些分支名称,以记住这些新提交。

您的 Git 可以要求他们的 Git 设置他们的master。 如果你告诉你的Git 这样做,而他们没有master,他们通常会说"OK!"然后去做。 之后,他们将以他们的名义提交您的提交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 索引(又名暂存区域中)中这些文件的版本,而不是您可以查看和使用的版本,但我们将跳过此详细信息。

  • 提交的另一部分是 Git 所说的元数据,或者有关提交本身的信息。 在这里,Git 存储您的姓名(作为提交的作者)和您的电子邮件地址。 Git 添加了日期和时间戳。 Git 包含您的日志消息,您可以在其中解释为什么要进行此提交(不是其中的内容,我们可以从提交的快照部分看到,而是您为什么做了您所做的一切,我们无法从快照中看到)。 而且,对于 Git 本身至关重要的是,Git 添加了上一次提交的哈希 ID。

这些哈希 ID 最终形成了一个向后看的链。 让我们画一个,使用单个大写字母来代替令人费解的哈希 ID:

A <-B <-C

这是一个由三个提交组成的简单链,正如您在几乎新的 Git 存储库中找到的那样。 提交A是第一次提交。 它没有任何早期提交 - 它不能;这是第一次提交,因此它根本不会向后指向。 然而,提交B第二次提交,因此它向后指向A,第一次提交。 提交C,当前是最后一个提交,向后指向B

实际上,提交C实际上包含了早期提交B的实际提交编号——哈希ID,作为C元数据的一部分。 同样,B包含早期提交A的哈希 ID。

为了找到所有三个提交,Git 只需要这三个提交中最后一个的哈希 ID。 我们让 Git 将此哈希 ID 粘贴到名称中,例如master

A--B--C   <-- master

如果我们将名称从master更改为main,则所有提交都不会更改:

A--B--C   <-- main

所以这是微不足道的:我们可以随心所欲地更改我们的分支名称。 我们选择的任何分支名称的git checkout实际上都会得到提交C- 无论它的真实哈希ID是什么 - 因为我们拥有的一个名称中具有提交C的哈希ID。

如果我们进行一个新的提交,我们的新提交——我们称之为D,尽管实际上它得到了一些丑陋的哈希ID——将包含提交C的哈希ID,作为其向后链接:

A--B--C--D   <-- main

这就是分支的成长方式。 从DC的向后连接表示提交D父级是提交CC的父级是B,依此类推,只是一旦我们A,就根本没有父级,一切都停止了。

GitHub 也有一个 Git 存储库

同时,在GitHub上,正如URLhttps://github.com/randiltennakoon/my-bmi.git找到的那样,还有另一个Git存储库。 这个其他 Git 存储库有一些提交。 这些提交有一些哈希 ID。

如果这些提交与您拥有的提交相同(例如,如果它们具有相同的A-B-C),那么您将处于良好状态。 你会让你的 Git 调用他们的 Git。 您的 Git 会列出哈希 IDD。 他们会说:嘿,我没有D,你为什么不给我那个?您的 Git 会将D添加到要发送的提交列表中。 然后你的 Git 会说D的父级是C. 他们会检查并查看他们确实C. 这意味着他们也有AB,因此您的 Git 必须发送的唯一提交是D

然后,你的 Git 只会发送D,并要求他们设置main以记住D而不是C。 那很好,因为D本身记得C. 你只是在增加他们的提交

唉,这不是正在发生的事情。 相反,他们有,例如:

G--H   <-- main

他们的存储库中。 因此,如果您向他们发送整个系列的提交,他们最终会得到:

A--B--C--D
G--H   <-- main

然后你的 Git 要求他们将main设置为指向D。 这将丢失他们的提交。 名称main只能指向最后一个提交,例如HD。 切换到D不会增加其提交。 所以他们拒绝了。

你如何修复它

您需要做的是:

  1. 获取他们的提交。
  2. 决定如何组合提交和提交

第 2 步是一个重大决定。 这是我们无法为您制作的。

第 1 步很简单:只需运行git fetch. 完成后,您将有一个origin/main。 这个名字不是一个分支名称,但它在大多数方面几乎一样好,而且在一个特定方面更好:这是你的 Git记住他们Git 的分支名称的方式。 您运行的每个git fetch都会从它们那里获得任何新提交,然后更新其 Gitmain分支名称(以及任何其他分支)的内存。1

无论如何,这个origin/main让你看到他们的提交——现在在你的仓库中,通过他们的Git 使用的相同哈希 ID 找到,因为每个 Git 都同意这些提交获得这些ID。您现在有了他们的提交提交。 这些不会相互连接,因为您通过不从他们的提交开始而进行您的提交,从而使您的提交完全独立于他们的提交。

跑:

git log origin/main

以查看所有提交。 他们有什么提交? 这些提交有什么? 它们重要吗? 你应该保留这些提交吗? 如果是这样,您希望如何将您的提交与他们的承诺结合起来


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 称之为合并库。 从此图中可以看出,提交H最佳的共享提交。 我们从两个分支端开始——提交JL——然后开始向后工作,就像 Git 总是做的那样。 从J开始,我们回到I,然后是HG,等等。 从L开始,我们回到K,然后HG等等。

这两个遍历在提交H。 他们从那里开始不断见面,所以提交H自动是两个分支相遇的最佳(即最后一个)提交。 然后,合并操作会找出自提交H以来我们更改了哪些内容以及它们更改了哪些内容,并合并了这些更改。

我不知道你的 Git 和他们的 Git 中到底有什么,但我知道,从你制作这两个 Git 存储库的方式来看,你拥有的和他们从未见过的。 你有这样的东西:

A--B--C--D   <-- main
G--H   <-- origin/main

如果我们从两端开始,然后向后工作,我们永远不会见面。

从 Git 版本 2.9 开始,git merge默认情况下不会合并这些内容。 它只是抱怨历史无关,然后停止。 你可以告诉 Git 继续合并,使用--allow-unrelated-histories.

这是否有效取决于DH快照中的内容(或您上次提交的任何内容)。 如果出现合并冲突,则必须自行解决合并冲突。 一旦完成所有操作(所有冲突都已解决,或者没有冲突),Git 可以进行新的合并提交,我将Merge称为M

A--B--C--D

M   <-- main
/
G--------H   <-- origin/main

提交M的特别之处在于,它不仅仅是一个父级,例如DH,而是将这两个父级作为其两个父级。 因此,它结合了两个独立的提交链。

假设你想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或等效项按A-B-C-D顺序复制每个提交(转发!Git 必须将它们向后列出,然后反向向后列表才能执行此操作),以便在提交H之后出现。 如果我们调用A的副本A'BB'的副本,依此类推,我们可以得出这样的结果:

A--B--C--D   <-- main
G--H   <-- origin/main

A'-B'-C'-D'   <-- ???

问号表明这里有一个问题:你的 Git 将如何找到副本D'? 答案是:它将名称main旧的提交系列中删除,并将其粘贴到新系列的末尾。 结果是:

A--B--C--D   ???
G--H   <-- origin/main

A'-B'-C'-D'   <-- main

现在很难找到旧的提交。 如果您将他们的哈希 ID 保存在某个地方(在纸上、白板上、文件中等等),您可以使用它们。 Git 还有其他技巧可以帮助您在一段时间内找到这些提交 - 默认情况下至少再过 30 天 - 但在大多数情况下,提交似乎A-B-C-D消失了。因此,当您使用git log查看提交时,您现在只有6次提交,而不是 10 次: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。

组合方法#3:粗暴地对待它们

假设在查看它们的提交时,您决定这些提交完全没有价值。 完全扔掉它们并没有什么坏处。

在这种情况下,您可以使用 Git 所谓的强制推动git push -fgit 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   ???

提交GH似乎消失了。 它们实际上会在未来的某个时候从 GitHub 存储库中真正消失——很难说什么时候取决于 GitHub,但在 Git中有效的 30 天规则并不适用。 它们可能会立即消失,或者在一天左右的时间内消失。

因为你确实把它们放进了你自己的(本地)Git 仓库,所以如果你认为它们毕竟有价值,你的 Git 将保留至少 30 天的G-H。不过,找到它们可能很棘手! 我们(人类)使用名称,而不是哈希ID,是有原因的。

听起来您的本地分支称为master,而远程分支称为main。您有两种选择:

  • 重命名本地分支:

    git checkout master
    git branch -m main
    git push -u origin main
    
  • 将本地分支推送到不同名称的远程分支:

    git push -u origin master:main
    

实际上,执行此操作并不难。我发现当我们初始化一个新的 GitHub 项目时,它会自动创建main违规作为默认分支。

然后,我们可以使用以下命令将现有项目推送到 GitHub。

git init
git add <file-name>
git commit -m "msg"
git remote add origin <remote-URL>

接下来,在转到git push命令之前,让我们运行git branch命令来检查可用的分支。git branch显示本地分支,git branch -r显示远程分支。

这是我的输出。

randiltennakoon@Randils-MacBook-Pro test_bmi_app % git branch
* master
randiltennakoon@Randils-MacBook-Pro test_bmi_app % git branch -r

您可以看到git branch -r命令还没有输出。但它将master显示为本地分支。

然后我们需要运行以下命令将master移动到main

git branch -m master main

然后,您可以运行git push命令以及-f标志,如下所示。

git push -u -f origin main

然后,它会将更改推送到远程main分支,你可以通过转到 GitHub 中的存储库来验证它。

然后,您可以发出以下命令进行验证,这效果很好。

git branch
git branch -r

git branch -a

也许你只需要提交。请运行以下命令;

git add .(不要忘记最后的点)

git commit -m "initial commit">

git push origin main

最新更新