该分支提前 11 次提交,落后 1 次提交
This branch is 11 commits ahead, 1 commit behind master
正在使用 Git 作为我的 VCS 并使用 GitHub 作为远程仓库的项目开发分支。
我想做一个 PR,但我注意到以下消息,“这个分支提前 11 次提交,落后 1 次提交。”
经过一些调查,我注意到 main 和 dev 分支都由 2 个不同的用户进行了不同的初始提交。
总支:
commit 49038002fbb1d6cd1434f30809bcexxxxxxx (HEAD -> main, upstream/main, origin/main, origin/HEAD)
Author: John doeh
Date: Wed Jul 28 14:41:30 2021 -0400
Initial commit
(END)
开发分支
commit 5b39f1b9cd01d858b30bd5fd6770694xxxxxx
Author: Jane Doe
Date: Fri Jul 23 18:00:00 2021 -0400
Initial commit from Create Next App
(END)
我应该如何处理这个问题来解决它,并给出解释,以便我以后不会犯同样的错误?
提前致谢
这通常根本不会发生。但最近 暂时 经常发生。这种混乱可能会持续一段时间,然后可能最终停止,或者变得更糟。这是怎么回事。对于背景,请参阅我较长的“提交和分支名称如何在 Git 中工作”的答案,例如 .
Git 分支名称 需要 包含一些现有的有效提交的哈希 ID。1 但是当我们首先创建一个新的 totally-empty 存储库,那里 没有提交 。如果分支名称必须包含提交哈希 ID,并且没有提交,那么分支名称中应该 Git 存储什么哈希 ID?
Git 对此的回答是,在新的 totally-empty 存储库中, 不能有任何分支 。这是一个简单而巧妙的答案,但它有一个很大的缺陷,因为 Git 还有一个要求:特殊名称 HEAD
应该包含一个分支的名称(如果你在一个分支上),或一些现有的有效提交的原始哈希 ID(如果您处于“分离的 HEAD”模式)。我们刚刚说过没有提交,所以你不能处于分离的 HEAD 模式。因此 HEAD
需要包含一个分支的名称——然而,我们刚刚说过不能有任何 分支 。
幸运的是,这里没有实际的矛盾,没有需要某种魔法状态或其他东西的悖论。要求是 HEAD
包含分支的 名称 ,而不是 现有 分支的 名称 。 Git 只允许 HEAD
包含一个不存在的分支的名称,巧妙地回避了悖论。
当你处于这种状态——在一个不存在的分支上——Git有时会说你在未出生的分支,有时会说你是在 孤儿分支 上。 Git 关于它使用的短语并不一致,但它通常使用这两个短语之一,或者它们的一些变体。
此外,当您处于此状态时,您所做的下一次提交 将自动成为root 提交。根提交只是没有 parents 的提交:初始提交。现在已经创建了根提交,Git 立即将生成的哈希 ID 存储到分支名称中,从而也创建了分支,并且解决了奇怪的状态。您的存储库有一个初始提交,现在可以有无限数量的分支名称,所有分支名称都必须 select 这一个初始提交。
1“现有”和“有效”是多余的;我在这里使用它们作为一种强调。这个想法是 Git 只允许任何给定的分支名称包含某些提交的哈希 ID,git cat-file -t <hash>
会说 commit
并且 git cat-file -p <hash>
会向您显示内部提交数据。
最近怎么样了?
为什么我们现在看到大量存储库有两个,有时甚至更多,root 提交?答案来自于回答另一个问题。
当我们创建一个新的空存储库时,HEAD
应该包含什么不存在的分支名称?这取决于 git init
命令。过去,git init
总是 创建一个新的空存储库,其中存储库位于名为 master
.
的不存在的分支上
假设我们使用像 GitHub 或 Bitbucket 这样的 Git-Repository-Storage-Site 来存储存储库。这些站点通常要求我们使用某种 clicky-button 网络界面来创建新的存储库。2 进一步假设我们也在本地使用 git init
,在我们的笔记本电脑上,创建新的空存储库——或 运行 git clone
将空存储库 从 GitHub 或任何地方 复制到 我们的笔记本电脑。
我们现在必须检查多个案例。
2GitHub,至少,终于,终于,提供了一个命令行脚本,gh
,可以做这样的事情无需单击烦人的 Web 界面按钮。当然,如果您喜欢点击 Web 界面按钮,也许它们并不烦人。
案例 1:克隆一个空存储库
git clone
的一般形式是,或者至少可以描述为:3
git clone [ -b <branch> ] [ -c config-options ] <url> [ <path> ]
这实际上是 shorthand 以下六个操作,其中五个是 Git 命令;五个 Git 命令在新目录中是 运行:
mkdir <em>path</em>
: 这将创建一个新的 totally-empty 目录,其中 Git 可以创建一个存储库。如果省略 path
参数,Git 将根据 URL.
计算出一个
git init
:因为这是 运行 在一个新的、完全空的目录中,它创建了一个空的存储库:没有提交,因此必然没有分支。我们到底在哪个分支?我第 6 步顺利,没关系,但让我们继续。
git远程添加源<em>url</em>
。这会将您提供的 URL 保存在标准名称 origin
.
下
保存您指定的配置所需的 git config
数量。 (列出这一步很重要,因为它可能会影响下一步。特别是,single-branch 克隆,此处未正确涵盖,请进行一些特殊配置。)
git fetch origin
:这会调用一些其他 Git软件,在存储的URL。我们现在从另一个 Git 存储库中获取所有 他们的 提交。 fetch 命令导致另一个 Git 也列出它们所有的 分支名称 ,我们的 Git renames将它们变成 remote-tracking names.
最后,我们的 Git 将我们的 -b
参数用于 运行 git checkout
或 git switch
。如果我们提供 -b
参数,它必须是存在于另一个存储库中的分支的名称。4 这就是我们的 Git 将 在本地创建,然后使用通过查看 their Git 的分支名称找到的提交哈希 ID 进行检出。也就是说,我们的 Git 查看我们的 remote-tracking 名称,这些名称基于它们的分支名称,并选择正确的重命名分支名称以获得正确的提交哈希 ID 来检查我们的分支。
如果我们省略 -b
标志,我们的 Git 应该询问他们的 Git 他们推荐哪个分支名称。这部分在 Git 的当前版本中被巧妙地破坏了。 推荐的名称很简单,就是存储在 HEAD
中的名称。如果您使用 Web 界面更改某些 GitHub 存储库中的 默认分支 ,您真正要做的是让 GitHub 填充不同的名称该克隆中的 HEAD
。
在我们查看“克隆一个空存储库”案例之前,让我们考虑克隆一个更典型的 非 空存储库。这里有两种可能:
我们提供了一个-b
的说法,他们有这样一个分支;那是我们所在的分支,也是我们检出的提交。 (如果他们没有我们命名的分支,我们的 git clone
会给我们一个错误并删除它创建的目录,这样它看起来就像什么都没有启动。)
我们未能提供 -b
论据,但他们推荐了他们的标准分支名称——不管是什么——这就是我们登陆的分支。
因此,由于第 6 步,我们在 HEAD
中获得了一些有效的分支名称,一切都很好。我们正在处理一些现有的提交;已经有一个初始(root)提交,我们将像往常一样添加新提交。
但请稍等:我们正在克隆一个完全空的 存储库。它没有分支名称。他们推荐什么分支名称? 他们能推荐一个分支名称吗?
最后一个问题的答案——他们可以他们推荐一个分支名称,即使他们没有任何分支名称——是并且一直是“是的,他们可以”大约十年。这意味着仍有一些人使用 Git 的旧版本 不能 ,因此不使用。幸运的是,托管网站并没有那么陈旧和破旧,所以他们可以推荐一个名字。但现在我们遇到了我提到的那个“微妙地破坏”的部分。
这正在修复,但 目前,即使使用现代 Git 版本,我们的 Git 也不会从他们的 Git. 因此,如果 他们的 Git 选择 main
,而 我们的 Git 选择 master
,在创建空存储库时,克隆过程会发生以下情况:
- 我们(本地)执行步骤 1-5 就好了。
- 我们然后(本地)失败执行第 6 步。我们的本地存储库保留在 我们 使用的任何初始分支名称上,而 他们的(GitHub 的,或 Bitbucket 的,或任何人的)存储库保留在 他们 使用的任何初始分支名称上。他们也继续推荐这个名字,因为它还在他们的
HEAD
.
如果“他们”是 GitHub,他们现在使用 main
作为他们的标准第一个分支名称,但我们使用的 Git 版本使用 master
作为我们标准的第一个分支名称,我们和他们都有不同的“未出生分支”。
我们现在进行第一次提交。这 创建了我们的分支 master
,我们将其与 git push
一起使用。这 在网站上的存储库中创建了名称 master
— GitHub 或其他 — 但 仍然让他们推荐 main
.
这是一种混淆的方法。它不启动ly 导致 两个单独的根提交,但他们推荐了一个我们不使用的分支名称,而我们使用的是他们不推荐的分支名称,此时。
3命令提供了很多其他的选项,比如-o
、--no-checkout
等等,可以稍微乱一点,但是总体计划保持不变。对我们来说最大的一个是 --no-checkout
,它完全省略了第 6 步。
4或者,-b
的参数可以是一个 标签名 ,如果成功的话,结果将是在 detached-HEAD 克隆中,但我们将再次忽略这种情况。
案例 2:两个 non-empty 初始存储库
我不太确定如何称呼案例 2,但我看到有人这样做:
- 他们 运行
git init
在他们的笔记本电脑上,在分支 master
. 上创建了一个空存储库
- 然后他们在此笔记本电脑存储库中创建初始提交。
- 与此同时,他们使用 GitHub(或任何站点)Web 界面创建新存储库,但选择创建 non-empty 存储库: 一次提交包含 template-sourced LICENSE、README、and/or 其他初始文件。这将创建一个名为
main
. 的分支
他们现在使用 git 远程添加来源 <em>url</em> 将笔记本电脑存储库连接到 GitHub 存储库
和 运行 git push origin master
或类似的。他们还 运行 git fetch origin
(在 git push origin master
之前或之后)。
他们现在在他们的笔记本电脑和 GitHub 存储库中,两个初始提交,彼此不相关,在两个不同的分支名称上。
他们不明白他们刚刚做了这件事。 Git 没有抱怨:这实际上是一个完全有效的情况。存储库中的一组提交不需要只有 one 根提交。存储库中的提交图可以包含多个不相交的子图。
你的情况应该是后者
假设我们使用这种方法创建两个 non-empty 存储库:一个在 GitHub 上 main
,另一个在笔记本电脑上 master
。
如果我们从来没有抽出时间推动 master
,但确实创建了一个 dev
分支 使用名称 master
,我们的新 dev
笔记本电脑上的分支将使用相同的初始提交。请注意,我们还可以 运行:
git checkout -b dev
当我们处于未出生-master
-分支状态时。我们所做的 下一个 提交将是根提交。
有赠品。当我们进行新的提交时,Git 打印消息:
$ mkdir empty && cd empty
$ git init
Initialized empty Git repository in ...
$ echo root > README
$ git add README
$ git commit -m initial
[master (root-commit) ea0681d] initial
1 file changed, 1 insertion(+)
create mode 100644 README
注意输出中的 (root-commit)
。这表明这个git commit
命令创建了当前分支名称:我们刚才处于orphan/unborn状态。
后续提交省略此处的 (root-commit)
部分。我们知道这些提交 添加 到我们目前的任何提交。
不过,唯一确定提交图的方法是使用显示提交图的东西。请参阅 Pretty Git branch graphs(并注意 --oneline
有一个微妙的缺陷,它看起来像是两个单独的图形是连在一起的,而实际上它们不是)。
固定
正如 torek 所解释的,我的问题属于情况 2。
克隆原始存储库后忘记正确同步本地和远程存储库,导致 2 个不同的“初始”提交,使两个分支的历史记录无关。
我使用 git rebase 解决了它,按照说明 。
正在使用 Git 作为我的 VCS 并使用 GitHub 作为远程仓库的项目开发分支。 我想做一个 PR,但我注意到以下消息,“这个分支提前 11 次提交,落后 1 次提交。”
经过一些调查,我注意到 main 和 dev 分支都由 2 个不同的用户进行了不同的初始提交。
总支:
commit 49038002fbb1d6cd1434f30809bcexxxxxxx (HEAD -> main, upstream/main, origin/main, origin/HEAD)
Author: John doeh
Date: Wed Jul 28 14:41:30 2021 -0400
Initial commit
(END)
开发分支
commit 5b39f1b9cd01d858b30bd5fd6770694xxxxxx
Author: Jane Doe
Date: Fri Jul 23 18:00:00 2021 -0400
Initial commit from Create Next App
(END)
我应该如何处理这个问题来解决它,并给出解释,以便我以后不会犯同样的错误?
提前致谢
这通常根本不会发生。但最近 暂时 经常发生。这种混乱可能会持续一段时间,然后可能最终停止,或者变得更糟。这是怎么回事。对于背景,请参阅我较长的“提交和分支名称如何在 Git 中工作”的答案,例如
Git 分支名称 需要 包含一些现有的有效提交的哈希 ID。1 但是当我们首先创建一个新的 totally-empty 存储库,那里 没有提交 。如果分支名称必须包含提交哈希 ID,并且没有提交,那么分支名称中应该 Git 存储什么哈希 ID?
Git 对此的回答是,在新的 totally-empty 存储库中, 不能有任何分支 。这是一个简单而巧妙的答案,但它有一个很大的缺陷,因为 Git 还有一个要求:特殊名称 HEAD
应该包含一个分支的名称(如果你在一个分支上),或一些现有的有效提交的原始哈希 ID(如果您处于“分离的 HEAD”模式)。我们刚刚说过没有提交,所以你不能处于分离的 HEAD 模式。因此 HEAD
需要包含一个分支的名称——然而,我们刚刚说过不能有任何 分支 。
幸运的是,这里没有实际的矛盾,没有需要某种魔法状态或其他东西的悖论。要求是 HEAD
包含分支的 名称 ,而不是 现有 分支的 名称 。 Git 只允许 HEAD
包含一个不存在的分支的名称,巧妙地回避了悖论。
当你处于这种状态——在一个不存在的分支上——Git有时会说你在未出生的分支,有时会说你是在 孤儿分支 上。 Git 关于它使用的短语并不一致,但它通常使用这两个短语之一,或者它们的一些变体。
此外,当您处于此状态时,您所做的下一次提交 将自动成为root 提交。根提交只是没有 parents 的提交:初始提交。现在已经创建了根提交,Git 立即将生成的哈希 ID 存储到分支名称中,从而也创建了分支,并且解决了奇怪的状态。您的存储库有一个初始提交,现在可以有无限数量的分支名称,所有分支名称都必须 select 这一个初始提交。
1“现有”和“有效”是多余的;我在这里使用它们作为一种强调。这个想法是 Git 只允许任何给定的分支名称包含某些提交的哈希 ID,git cat-file -t <hash>
会说 commit
并且 git cat-file -p <hash>
会向您显示内部提交数据。
最近怎么样了?
为什么我们现在看到大量存储库有两个,有时甚至更多,root 提交?答案来自于回答另一个问题。
当我们创建一个新的空存储库时,HEAD
应该包含什么不存在的分支名称?这取决于 git init
命令。过去,git init
总是 创建一个新的空存储库,其中存储库位于名为 master
.
假设我们使用像 GitHub 或 Bitbucket 这样的 Git-Repository-Storage-Site 来存储存储库。这些站点通常要求我们使用某种 clicky-button 网络界面来创建新的存储库。2 进一步假设我们也在本地使用 git init
,在我们的笔记本电脑上,创建新的空存储库——或 运行 git clone
将空存储库 从 GitHub 或任何地方 复制到 我们的笔记本电脑。
我们现在必须检查多个案例。
2GitHub,至少,终于,终于,提供了一个命令行脚本,gh
,可以做这样的事情无需单击烦人的 Web 界面按钮。当然,如果您喜欢点击 Web 界面按钮,也许它们并不烦人。
案例 1:克隆一个空存储库
git clone
的一般形式是,或者至少可以描述为:3
git clone [ -b <branch> ] [ -c config-options ] <url> [ <path> ]
这实际上是 shorthand 以下六个操作,其中五个是 Git 命令;五个 Git 命令在新目录中是 运行:
计算出一个mkdir <em>path</em>
: 这将创建一个新的 totally-empty 目录,其中 Git 可以创建一个存储库。如果省略path
参数,Git 将根据 URL.git init
:因为这是 运行 在一个新的、完全空的目录中,它创建了一个空的存储库:没有提交,因此必然没有分支。我们到底在哪个分支?我第 6 步顺利,没关系,但让我们继续。
下git远程添加源<em>url</em>
。这会将您提供的 URL 保存在标准名称origin
.保存您指定的配置所需的
git config
数量。 (列出这一步很重要,因为它可能会影响下一步。特别是,single-branch 克隆,此处未正确涵盖,请进行一些特殊配置。)git fetch origin
:这会调用一些其他 Git软件,在存储的URL。我们现在从另一个 Git 存储库中获取所有 他们的 提交。 fetch 命令导致另一个 Git 也列出它们所有的 分支名称 ,我们的 Git renames将它们变成 remote-tracking names.最后,我们的 Git 将我们的
-b
参数用于 运行git checkout
或git switch
。如果我们提供-b
参数,它必须是存在于另一个存储库中的分支的名称。4 这就是我们的 Git 将 在本地创建,然后使用通过查看 their Git 的分支名称找到的提交哈希 ID 进行检出。也就是说,我们的 Git 查看我们的 remote-tracking 名称,这些名称基于它们的分支名称,并选择正确的重命名分支名称以获得正确的提交哈希 ID 来检查我们的分支。如果我们省略
-b
标志,我们的 Git 应该询问他们的 Git 他们推荐哪个分支名称。这部分在 Git 的当前版本中被巧妙地破坏了。 推荐的名称很简单,就是存储在HEAD
中的名称。如果您使用 Web 界面更改某些 GitHub 存储库中的 默认分支 ,您真正要做的是让 GitHub 填充不同的名称该克隆中的HEAD
。
在我们查看“克隆一个空存储库”案例之前,让我们考虑克隆一个更典型的 非 空存储库。这里有两种可能:
我们提供了一个
-b
的说法,他们有这样一个分支;那是我们所在的分支,也是我们检出的提交。 (如果他们没有我们命名的分支,我们的git clone
会给我们一个错误并删除它创建的目录,这样它看起来就像什么都没有启动。)我们未能提供
-b
论据,但他们推荐了他们的标准分支名称——不管是什么——这就是我们登陆的分支。
因此,由于第 6 步,我们在 HEAD
中获得了一些有效的分支名称,一切都很好。我们正在处理一些现有的提交;已经有一个初始(root)提交,我们将像往常一样添加新提交。
但请稍等:我们正在克隆一个完全空的 存储库。它没有分支名称。他们推荐什么分支名称? 他们能推荐一个分支名称吗?
最后一个问题的答案——他们可以他们推荐一个分支名称,即使他们没有任何分支名称——是并且一直是“是的,他们可以”大约十年。这意味着仍有一些人使用 Git 的旧版本 不能 ,因此不使用。幸运的是,托管网站并没有那么陈旧和破旧,所以他们可以推荐一个名字。但现在我们遇到了我提到的那个“微妙地破坏”的部分。
这正在修复,但 目前,即使使用现代 Git 版本,我们的 Git 也不会从他们的 Git. 因此,如果 他们的 Git 选择 main
,而 我们的 Git 选择 master
,在创建空存储库时,克隆过程会发生以下情况:
- 我们(本地)执行步骤 1-5 就好了。
- 我们然后(本地)失败执行第 6 步。我们的本地存储库保留在 我们 使用的任何初始分支名称上,而 他们的(GitHub 的,或 Bitbucket 的,或任何人的)存储库保留在 他们 使用的任何初始分支名称上。他们也继续推荐这个名字,因为它还在他们的
HEAD
.
如果“他们”是 GitHub,他们现在使用 main
作为他们的标准第一个分支名称,但我们使用的 Git 版本使用 master
作为我们标准的第一个分支名称,我们和他们都有不同的“未出生分支”。
我们现在进行第一次提交。这 创建了我们的分支 master
,我们将其与 git push
一起使用。这 在网站上的存储库中创建了名称 master
— GitHub 或其他 — 但 仍然让他们推荐 main
.
这是一种混淆的方法。它不启动ly 导致 两个单独的根提交,但他们推荐了一个我们不使用的分支名称,而我们使用的是他们不推荐的分支名称,此时。
3命令提供了很多其他的选项,比如-o
、--no-checkout
等等,可以稍微乱一点,但是总体计划保持不变。对我们来说最大的一个是 --no-checkout
,它完全省略了第 6 步。
4或者,-b
的参数可以是一个 标签名 ,如果成功的话,结果将是在 detached-HEAD 克隆中,但我们将再次忽略这种情况。
案例 2:两个 non-empty 初始存储库
我不太确定如何称呼案例 2,但我看到有人这样做:
- 他们 运行
git init
在他们的笔记本电脑上,在分支master
. 上创建了一个空存储库
- 然后他们在此笔记本电脑存储库中创建初始提交。
- 与此同时,他们使用 GitHub(或任何站点)Web 界面创建新存储库,但选择创建 non-empty 存储库: 一次提交包含 template-sourced LICENSE、README、and/or 其他初始文件。这将创建一个名为
main
. 的分支
他们现在使用 git 远程添加来源 <em>url</em> 将笔记本电脑存储库连接到 GitHub 存储库
和 运行 git push origin master
或类似的。他们还 运行 git fetch origin
(在 git push origin master
之前或之后)。
他们现在在他们的笔记本电脑和 GitHub 存储库中,两个初始提交,彼此不相关,在两个不同的分支名称上。
他们不明白他们刚刚做了这件事。 Git 没有抱怨:这实际上是一个完全有效的情况。存储库中的一组提交不需要只有 one 根提交。存储库中的提交图可以包含多个不相交的子图。
你的情况应该是后者
假设我们使用这种方法创建两个 non-empty 存储库:一个在 GitHub 上 main
,另一个在笔记本电脑上 master
。
如果我们从来没有抽出时间推动 master
,但确实创建了一个 dev
分支 使用名称 master
,我们的新 dev
笔记本电脑上的分支将使用相同的初始提交。请注意,我们还可以 运行:
git checkout -b dev
当我们处于未出生-master
-分支状态时。我们所做的 下一个 提交将是根提交。
有赠品。当我们进行新的提交时,Git 打印消息:
$ mkdir empty && cd empty
$ git init
Initialized empty Git repository in ...
$ echo root > README
$ git add README
$ git commit -m initial
[master (root-commit) ea0681d] initial
1 file changed, 1 insertion(+)
create mode 100644 README
注意输出中的 (root-commit)
。这表明这个git commit
命令创建了当前分支名称:我们刚才处于orphan/unborn状态。
后续提交省略此处的 (root-commit)
部分。我们知道这些提交 添加 到我们目前的任何提交。
不过,唯一确定提交图的方法是使用显示提交图的东西。请参阅 Pretty Git branch graphs(并注意 --oneline
有一个微妙的缺陷,它看起来像是两个单独的图形是连在一起的,而实际上它们不是)。
固定
正如 torek 所解释的,我的问题属于情况 2。
克隆原始存储库后忘记正确同步本地和远程存储库,导致 2 个不同的“初始”提交,使两个分支的历史记录无关。
我使用 git rebase 解决了它,按照说明