git 状态仍然显示“您的分支领先 <tag1> N 次提交。” git 变基后 <tag2>
git status still says “Your branch is ahead of <tag1> by N commits.” after git rebase <tag2>
假设我有一个 my-topic-branch
b运行ch,它是 b运行ched off my local master
b运行ch,并且 master
b运行ch 绑定到遥控器 master
b运行ch.
my-topic-branch
b运行ch 最初是根据名为 tag1
的标签创建的。 tag1
是放置在远程主机 b运行ch 上的标签,我看到该标签是 git fetch
.
的结果
一段时间过去了,允许其他人将他们的更改推送到远程主机 b运行ch。再后来,一个新的 tag2
被其他人放下(请参阅下面的 更新 6 了解这样做的原因)。
然后我再次使用 git fetch
以确保我将所有这些远程标签放入我的本地存储库以进行进一步操作。
我像这样 tag2
变基:
$ git rebase tag2
First, rewinding head to replay your work on top of it...
Applying: CENSORED_LOG_MESSAGE1
Applying: CENSORED_LOG_MESSAGE2
Auto packing the repository in background for optimum performance.
See "git help gc" for manual housekeeping.
但后来我 运行 git status
看到了这个:
$ git status
On branch my-topic-branch
Your branch is ahead of 'tag1' by 195 commits.
(use "git push" to publish your local commits)
我的期望是上面的消息应该说一些关于 tag2
的事情,当然不是说我比旧标签领先 195 次提交。
为什么 git status
报告 my-topic-branch
b运行 的提交,而不是我最近重新定位的新提交?
如果这是预期的行为,那么很好,我只需要忽略它,但奇怪的是我仍然落后 master
195 次提交,而这肯定不是真的 (如果 git rebase
实际上做了我认为应该做的事情)。
更新 1
如果 HEAD 还在那个 b运行ch 上,我仍然可以找到 my-topic-branch
的基点,来自:
$ git show -s $(git merge-base master @)
commit CENSORED_SHA1_TAG2 (tag: tag2)
Author: CENSORED_AUTHOR
Date: CENSORED_DATE
CENSORED_LOG_MESSAGE_TAG2
$
但这仍然引出了关于 git status
输出的问题。
更新 2:
更新以响应 Pesho_T's comment:
我 运行 git branch -vv
得到了以下内容。这是经过审查的,但添加了“2”和“4”之类的数字以将它们与上面的其他数字区分开来:
$ git branch -vv | grep my-topic-branch
* my-topic-branch CENSORED_SHA1_TAG1 [tag1: ahead 195] MDFCOR-420 CENSORED_LOG_MESSAGE_TAG1
更新 3:
重播 中的一些命令,我看到:
$ git rev-parse --abbrev-ref @{u}
tag1
$ git rev-parse --symbolic-full-name @{u}
refs/tags/tag1
我目前从 torek 非常好的文章中得出结论,tag1 确实是一个标签,而不是 b运行ch。
更新 4:
我编辑了之前的 CENSORED_SHA1 以保持一致:
- CENSORED_SHA1_TAG1是tag1对应的commit SHA1
- CENSORED_SHA1_TAG2是tag2
对应的commit SHA1
更新 5:
响应 的另一个更新:
The upstream of a branch is always another branch name or remote-tracking name, as tag names are forbidden
由于这个实验,我不确定:
$ git rev-list --count --left-right my-topic-branch...my-topic-branch@{upstream}
195 0
$ git show -s $(git rev-parse my-topic-branch@{u})
commit CENSORED_SHA1_TAG1 (tag: tag1)
Author: CENSORED_AUTHOR
Date: CENSORED_DATE
CENSORED_LOG_MESSAGE_TAG1
以上表示上游指向一个标签
为了确认这些 tag1 和 tag2 标签都在主 b运行ch 上,我按照 :
中的提示进行了此操作
$ git branch -a --contains $(git rev-parse tag1^{commit}) | grep -E 'my-topic-branch|master'
* my-topic-branch
master
$
更新 6:
我的计划实际上并不是要git push
回到我运行git rebase
上的那个标签(tag2
)。我只是将该标记用作 master
b运行ch 上的一个点,以将 my-topic-branch
变基为。 tag2
恰好是 master
b运行ch 上的一个已知点,该应用程序正确地构建在该点上(出于保密原因,我无法进一步详细说明)。我希望继续 git rebase
到后续标签,tag3
、tag4
等等,直到 my-topic-branch
准备好生产,届时我会将其合并回我的local master
b运行ch 并从那里执行 git push
。
更新 7
我发布了一个 答案来说明一旦在主题 b运行ch,从而支持其中显示的 torek 评论。我仍在考虑将 作为 的答案,因为如果没有他的意见,我不会想出来。
您似乎1不小心创建了一个分支名称和一个标签名称 缩短时两者打印相同。 [编辑:根据更新 3,这不是问题所在。相反,不知何故,您将标签名称设置为分支的上游。我不确定您是如何进入该状态的 - 请参阅下面我自己尝试这样做的结果。] 一旦您拥有两个这样的名称,它们就可以拥有不同的哈希 ID。 解析这样的名字时得到的哈希ID有点棘手(有规则,在the gitrevisions documentation中概述,但规则也有例外) .最好从这种情况中恢复过来,通常是通过重命名不合适的分支名称。
记住像master
这样的分支名称才是真正的名称refs/heads/master
;像 v2.25.0
这样的 标签名称 实际上是 refs/tags/v2.25.0
。因此,即使标签存在,也可以创建名为 v2.25.0
的 branch,因为分支的 full 名称是 refs/heads/v2.25.0
,而不是 refs/tags/v2.25.0
。这两个名称不同,但如果您查看每个名称的缩写版本,都将是 v2.25.0
.
来自 git status
的 ahead
或 behind
计数消息是 运行:
的结果
git rev-list --count --left-right <name1>...<name2>
注意两个名字之间有三个点。2两个名字是:
name1
是您的当前分支;
name2
是您当前分支的上游。
git rev-list
命令以这种形式(使用三个点)找到 可从 左侧名称而不是右侧名称到达的提交,并提交 可从 右侧名称而不是左侧名称到达的提交,然后是 counts (--count
)它们,但分开 (--left-right
) 而不是合并。
这意味着这些计数取决于您当前的分支(当然——这就是它说 "your branch ... is ahead of" 的原因)和 upstream 设置。您可以使用 git branch --set-upstream-to
控制上游设置,您可以使用以下方式读取当前分支的上游:
$ git rev-parse --abbrev-ref @{u}
origin/master
$ git rev-parse --symbolic-full-name @{u}
refs/remotes/origin/master
如果您不小心使 分支名称 和 标签名称 缩写时看起来相同,使用 --symbolic-full-name
变体。
一个分支的上游是[编辑:或者应该是]总是另一个分支名称或远程跟踪名称,因为禁止使用标签名称:
git branch --set-upstream-to=v2.25.0
fatal: Cannot setup tracking information; starting point 'v2.25.0' is not a branch.
像origin/master
这样的远程跟踪名称更典型,但是您可以将任何分支的上游设置为任何其他分支名称。
如果 ahead
计数不为零,这通常就是您所看到的,也是您在这里看到的。但是,如果两个计数都不为零,git status
将使用单词 diverged
。如果 ahead
计数为零且 behind
计数不为零,则 git status
打印 behind
计数。如果两个计数都为零——这样分支与其上游同步——git status
表示 Your branch is up to date with ...
.
有关三点语法的更多信息,请参阅 the gitrevisions documentation。要了解 可达性 ,请参阅 类似思考 (a) Git。对于简短的图形说明,请考虑这张图:
I--J <-- branch1
/
...--G--H <-- master
\
K--L <-- branch2, origin/branch1
名称 branch1
是 master
的 "ahead 2" 因为从提交 J
我们回到 I
然后 H
,这意味着 I-J
提交可以从 branch1
访问,但不能从 master
访问。同样,branch2
比 master
早 2 个,但它的两个是提交 K-L
。这意味着 master
比 branch1
或 branch2
落后 ,这两个提交是 I-J
或 K-L
分别。同时,branch1
与 origin/branch1
有 分歧 因为它领先 2 (I-J
) 和 2后面 (K-L
).
1如果您 移动 标签,您可能会遇到类似的棘手情况,因为标签在所有 Git 存储库,但标签也意味着永远不会 移动 。一旦一个 Git 存储库有一个标签,它将倾向于假设 它 有的副本是 正确的,即使有人有强行移动了此 Git 存储库要匹配的其他 Git 存储库中的标签。但这会表现出不同的症状,因为您不能将分支的上游设置为标签名称。
2三点句法以集合论的形式产生 symmetric difference。因为这是对称的,所以您可以交换这两个名称,只要您记得 git rev-list --count --left-right
将打印的两个 counts 现在也交换了。
总结
我现在认为 是关键方面。下面我
通过在本地主机上使用 git push -u
来证明这一点
在此过程中早些时候在该分支上设置上游。
MCVE
我发现显示 git 状态的根本原因是:
Your branch is ahead of 'tag1' by N commits
是本地master分支没有upstream因为
-u
没有传递给最初的 git push
。如下所示
与 MCVE。它仍然是最小的,因为我无法避免使用
工作树在这里(我在真实场景中确实有工作树)。我没有
在我的 OP 中包含有关使用工作树的详细信息,因为它是
不认为是相关的。我仍然不认为它是,但我不能
在不使用工作树的情况下重现问题。因此,或许
工作树是一个重要因素。也许有人可以启发我们
进一步说明为什么会这样,或者进一步最小化我的演示脚本
因此。
演示脚本
下面的Bash(Linux)脚本是执行一个函数两次
以两种方式重现我的场景:一种使用 git push -u
并再次使用
使用 git push
(没有 -u
):
#/!/bin/bash
change () {
local file=""
local change_text=""
echo "$change_text" > $file
git add $file
git commit -m "$change_text"
}
log () {
(
set +x
echo
echo "Current branch: $(git symbolic-ref --short HEAD)"
echo "Current upstream: $(git rev-parse --symbolic-full-name @{u})"
git log --oneline --graph --decorate --parents
echo
)
}
replay () {
local scratch_dir=""
local master_push_args=""
test -z "" && { echo "USAGE: replay scratch_dir"; exit 1; }
(
echo
echo "Generating Git scenario into ${scratch_dir} ..."
set -e -x
# Create a scratch area:
rm -rf $scratch_dir
mkdir $scratch_dir
cd $scratch_dir
# Create the bare repo for some scrubber product:
mkdir scrubber
cd scrubber
git init --bare
main_git_repo=$(pwd)
cd ..
git clone $main_git_repo bootstrap
cd bootstrap
# Add some changes to it:
change file1 "change 1.1"
change file1 "change 1.2"
git tag tag1 -m "tag1"
git push $master_push_args
git push --tags
log
cd ..
# Create a mirror:
git clone --mirror $main_git_repo my_mirror
mirror_dir=$(readlink -f my_mirror)
# Create a worktree
GIT_DIR=$mirror_dir git worktree add scrubber1
cd scrubber1
# Add some changes to a new branch:
#
# Avoid conflicts during subsequent rebase by changing a different file.
#
git checkout -b my-topic-branch tag1
change file2 "change 2.1"
change file2 "change 2.2"
change file2 "change 2.3"
log
# Add some changes to master:
git checkout master
change file1 "change 3.1"
change file1 "change 3.2"
change file1 "change 3.3"
change file1 "change 3.4"
git tag tag2 -m "tag2"
change file1 "change 4.1"
change file1 "change 4.2"
git push $master_push_args
log
git checkout my-topic-branch
git rebase tag2
git status
log
) 2>&1 # for subsequent filtration (e.g., ... | grep/sed/awk/etc.)
}
filter () {
grep -A2 'git status'
}
# Show the version I'm using:
echo "Git Version: $(git --version)"
echo
echo "Using git push on master without -u ..."
replay /tmp/scenario1 | filter
echo
echo "Using git push on master with -u ..."
replay /tmp/scenario2 -u | filter
执行该脚本会得到来自 git 状态的两个结果:
Git Version: git version 2.20.1
Using git push on master without -u ...
+ git status
On branch my-topic-branch
Your branch is ahead of 'tag1' by 7 commits.
Using git push on master with -u ...
+ git status
On branch my-topic-branch
Your branch is up to date with 'my-topic-branch'.
假设我有一个 my-topic-branch
b运行ch,它是 b运行ched off my local master
b运行ch,并且 master
b运行ch 绑定到遥控器 master
b运行ch.
my-topic-branch
b运行ch 最初是根据名为 tag1
的标签创建的。 tag1
是放置在远程主机 b运行ch 上的标签,我看到该标签是 git fetch
.
一段时间过去了,允许其他人将他们的更改推送到远程主机 b运行ch。再后来,一个新的 tag2
被其他人放下(请参阅下面的 更新 6 了解这样做的原因)。
然后我再次使用 git fetch
以确保我将所有这些远程标签放入我的本地存储库以进行进一步操作。
我像这样 tag2
变基:
$ git rebase tag2
First, rewinding head to replay your work on top of it...
Applying: CENSORED_LOG_MESSAGE1
Applying: CENSORED_LOG_MESSAGE2
Auto packing the repository in background for optimum performance.
See "git help gc" for manual housekeeping.
但后来我 运行 git status
看到了这个:
$ git status
On branch my-topic-branch
Your branch is ahead of 'tag1' by 195 commits.
(use "git push" to publish your local commits)
我的期望是上面的消息应该说一些关于 tag2
的事情,当然不是说我比旧标签领先 195 次提交。
为什么 git status
报告 my-topic-branch
b运行 的提交,而不是我最近重新定位的新提交?
如果这是预期的行为,那么很好,我只需要忽略它,但奇怪的是我仍然落后 master
195 次提交,而这肯定不是真的 (如果 git rebase
实际上做了我认为应该做的事情)。
更新 1
如果 HEAD 还在那个 b运行ch 上,我仍然可以找到 my-topic-branch
的基点,来自:
$ git show -s $(git merge-base master @)
commit CENSORED_SHA1_TAG2 (tag: tag2)
Author: CENSORED_AUTHOR
Date: CENSORED_DATE
CENSORED_LOG_MESSAGE_TAG2
$
但这仍然引出了关于 git status
输出的问题。
更新 2:
更新以响应 Pesho_T's comment:
我 运行 git branch -vv
得到了以下内容。这是经过审查的,但添加了“2”和“4”之类的数字以将它们与上面的其他数字区分开来:
$ git branch -vv | grep my-topic-branch
* my-topic-branch CENSORED_SHA1_TAG1 [tag1: ahead 195] MDFCOR-420 CENSORED_LOG_MESSAGE_TAG1
更新 3:
重播
$ git rev-parse --abbrev-ref @{u}
tag1
$ git rev-parse --symbolic-full-name @{u}
refs/tags/tag1
我目前从 torek 非常好的文章中得出结论,tag1 确实是一个标签,而不是 b运行ch。
更新 4:
我编辑了之前的 CENSORED_SHA1 以保持一致:
- CENSORED_SHA1_TAG1是tag1对应的commit SHA1
- CENSORED_SHA1_TAG2是tag2 对应的commit SHA1
更新 5:
响应
The upstream of a branch is always another branch name or remote-tracking name, as tag names are forbidden
由于这个实验,我不确定:
$ git rev-list --count --left-right my-topic-branch...my-topic-branch@{upstream}
195 0
$ git show -s $(git rev-parse my-topic-branch@{u})
commit CENSORED_SHA1_TAG1 (tag: tag1)
Author: CENSORED_AUTHOR
Date: CENSORED_DATE
CENSORED_LOG_MESSAGE_TAG1
以上表示上游指向一个标签
为了确认这些 tag1 和 tag2 标签都在主 b运行ch 上,我按照
$ git branch -a --contains $(git rev-parse tag1^{commit}) | grep -E 'my-topic-branch|master'
* my-topic-branch
master
$
更新 6:
我的计划实际上并不是要git push
回到我运行git rebase
上的那个标签(tag2
)。我只是将该标记用作 master
b运行ch 上的一个点,以将 my-topic-branch
变基为。 tag2
恰好是 master
b运行ch 上的一个已知点,该应用程序正确地构建在该点上(出于保密原因,我无法进一步详细说明)。我希望继续 git rebase
到后续标签,tag3
、tag4
等等,直到 my-topic-branch
准备好生产,届时我会将其合并回我的local master
b运行ch 并从那里执行 git push
。
更新 7
我发布了一个
您似乎1不小心创建了一个分支名称和一个标签名称 缩短时两者打印相同。 [编辑:根据更新 3,这不是问题所在。相反,不知何故,您将标签名称设置为分支的上游。我不确定您是如何进入该状态的 - 请参阅下面我自己尝试这样做的结果。] 一旦您拥有两个这样的名称,它们就可以拥有不同的哈希 ID。 解析这样的名字时得到的哈希ID有点棘手(有规则,在the gitrevisions documentation中概述,但规则也有例外) .最好从这种情况中恢复过来,通常是通过重命名不合适的分支名称。
记住像master
这样的分支名称才是真正的名称refs/heads/master
;像 v2.25.0
这样的 标签名称 实际上是 refs/tags/v2.25.0
。因此,即使标签存在,也可以创建名为 v2.25.0
的 branch,因为分支的 full 名称是 refs/heads/v2.25.0
,而不是 refs/tags/v2.25.0
。这两个名称不同,但如果您查看每个名称的缩写版本,都将是 v2.25.0
.
来自 git status
的 ahead
或 behind
计数消息是 运行:
git rev-list --count --left-right <name1>...<name2>
注意两个名字之间有三个点。2两个名字是:
name1
是您的当前分支;name2
是您当前分支的上游。
git rev-list
命令以这种形式(使用三个点)找到 可从 左侧名称而不是右侧名称到达的提交,并提交 可从 右侧名称而不是左侧名称到达的提交,然后是 counts (--count
)它们,但分开 (--left-right
) 而不是合并。
这意味着这些计数取决于您当前的分支(当然——这就是它说 "your branch ... is ahead of" 的原因)和 upstream 设置。您可以使用 git branch --set-upstream-to
控制上游设置,您可以使用以下方式读取当前分支的上游:
$ git rev-parse --abbrev-ref @{u}
origin/master
$ git rev-parse --symbolic-full-name @{u}
refs/remotes/origin/master
如果您不小心使 分支名称 和 标签名称 缩写时看起来相同,使用 --symbolic-full-name
变体。
一个分支的上游是[编辑:或者应该是]总是另一个分支名称或远程跟踪名称,因为禁止使用标签名称:
git branch --set-upstream-to=v2.25.0
fatal: Cannot setup tracking information; starting point 'v2.25.0' is not a branch.
像origin/master
这样的远程跟踪名称更典型,但是您可以将任何分支的上游设置为任何其他分支名称。
如果 ahead
计数不为零,这通常就是您所看到的,也是您在这里看到的。但是,如果两个计数都不为零,git status
将使用单词 diverged
。如果 ahead
计数为零且 behind
计数不为零,则 git status
打印 behind
计数。如果两个计数都为零——这样分支与其上游同步——git status
表示 Your branch is up to date with ...
.
有关三点语法的更多信息,请参阅 the gitrevisions documentation。要了解 可达性 ,请参阅 类似思考 (a) Git。对于简短的图形说明,请考虑这张图:
I--J <-- branch1
/
...--G--H <-- master
\
K--L <-- branch2, origin/branch1
名称 branch1
是 master
的 "ahead 2" 因为从提交 J
我们回到 I
然后 H
,这意味着 I-J
提交可以从 branch1
访问,但不能从 master
访问。同样,branch2
比 master
早 2 个,但它的两个是提交 K-L
。这意味着 master
比 branch1
或 branch2
落后 ,这两个提交是 I-J
或 K-L
分别。同时,branch1
与 origin/branch1
有 分歧 因为它领先 2 (I-J
) 和 2后面 (K-L
).
1如果您 移动 标签,您可能会遇到类似的棘手情况,因为标签在所有 Git 存储库,但标签也意味着永远不会 移动 。一旦一个 Git 存储库有一个标签,它将倾向于假设 它 有的副本是 正确的,即使有人有强行移动了此 Git 存储库要匹配的其他 Git 存储库中的标签。但这会表现出不同的症状,因为您不能将分支的上游设置为标签名称。
2三点句法以集合论的形式产生 symmetric difference。因为这是对称的,所以您可以交换这两个名称,只要您记得 git rev-list --count --left-right
将打印的两个 counts 现在也交换了。
总结
我现在认为 git push -u
来证明这一点
在此过程中早些时候在该分支上设置上游。
MCVE
我发现显示 git 状态的根本原因是:
Your branch is ahead of 'tag1' by N commits
是本地master分支没有upstream因为
-u
没有传递给最初的 git push
。如下所示
与 MCVE。它仍然是最小的,因为我无法避免使用
工作树在这里(我在真实场景中确实有工作树)。我没有
在我的 OP 中包含有关使用工作树的详细信息,因为它是
不认为是相关的。我仍然不认为它是,但我不能
在不使用工作树的情况下重现问题。因此,或许
工作树是一个重要因素。也许有人可以启发我们
进一步说明为什么会这样,或者进一步最小化我的演示脚本
因此。
演示脚本
下面的Bash(Linux)脚本是执行一个函数两次
以两种方式重现我的场景:一种使用 git push -u
并再次使用
使用 git push
(没有 -u
):
#/!/bin/bash
change () {
local file=""
local change_text=""
echo "$change_text" > $file
git add $file
git commit -m "$change_text"
}
log () {
(
set +x
echo
echo "Current branch: $(git symbolic-ref --short HEAD)"
echo "Current upstream: $(git rev-parse --symbolic-full-name @{u})"
git log --oneline --graph --decorate --parents
echo
)
}
replay () {
local scratch_dir=""
local master_push_args=""
test -z "" && { echo "USAGE: replay scratch_dir"; exit 1; }
(
echo
echo "Generating Git scenario into ${scratch_dir} ..."
set -e -x
# Create a scratch area:
rm -rf $scratch_dir
mkdir $scratch_dir
cd $scratch_dir
# Create the bare repo for some scrubber product:
mkdir scrubber
cd scrubber
git init --bare
main_git_repo=$(pwd)
cd ..
git clone $main_git_repo bootstrap
cd bootstrap
# Add some changes to it:
change file1 "change 1.1"
change file1 "change 1.2"
git tag tag1 -m "tag1"
git push $master_push_args
git push --tags
log
cd ..
# Create a mirror:
git clone --mirror $main_git_repo my_mirror
mirror_dir=$(readlink -f my_mirror)
# Create a worktree
GIT_DIR=$mirror_dir git worktree add scrubber1
cd scrubber1
# Add some changes to a new branch:
#
# Avoid conflicts during subsequent rebase by changing a different file.
#
git checkout -b my-topic-branch tag1
change file2 "change 2.1"
change file2 "change 2.2"
change file2 "change 2.3"
log
# Add some changes to master:
git checkout master
change file1 "change 3.1"
change file1 "change 3.2"
change file1 "change 3.3"
change file1 "change 3.4"
git tag tag2 -m "tag2"
change file1 "change 4.1"
change file1 "change 4.2"
git push $master_push_args
log
git checkout my-topic-branch
git rebase tag2
git status
log
) 2>&1 # for subsequent filtration (e.g., ... | grep/sed/awk/etc.)
}
filter () {
grep -A2 'git status'
}
# Show the version I'm using:
echo "Git Version: $(git --version)"
echo
echo "Using git push on master without -u ..."
replay /tmp/scenario1 | filter
echo
echo "Using git push on master with -u ..."
replay /tmp/scenario2 -u | filter
执行该脚本会得到来自 git 状态的两个结果:
Git Version: git version 2.20.1
Using git push on master without -u ...
+ git status
On branch my-topic-branch
Your branch is ahead of 'tag1' by 7 commits.
Using git push on master with -u ...
+ git status
On branch my-topic-branch
Your branch is up to date with 'my-topic-branch'.