Suppose I have a my-topic-branch
branch that is branched off my local master
branch, and that master
branch is tied to the remote master
branch.
The my-topic-branch
branch was originally created off of a tag called tag1
. tag1
is a tag layed down on the remote master branch, and I see that tag as a result of git fetch
.
Some time passes to allow others to pushed their changes into that remote master branch. And still later, a new tag2
is layed down by someone else (See Update 6 below for the reasons for doing this).
I then use git fetch
again to ensure that I have all of those remote tags into my local repo for further operations.
I rebase to that tag2
like this:
$ 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.
But then I run git status
and see this:
$ git status
On branch my-topic-branch
Your branch is ahead of 'tag1' by 195 commits.
(use "git push" to publish your local commits)
My expectation is that the above message should say something about tag2
, and certainly not that I'm ahead of the old tag by 195 commits.
Why would git status
report the commit that my-topic-branch
branched from, and not the new commit that I mostly recently rebased to?
If this is expected behavior, then fine, I will just have to ignore it, but it is odd to see that I'm still behind master
by 195 commits, when that is certainly not true (if the git rebase
actually did what I think it should).
I can still find the base point of my-topic-branch
if HEAD is still on that branch, via:
$ git show -s $(git merge-base master @)
commit CENSORED_SHA1_TAG2 (tag: tag2)
Author: CENSORED_AUTHOR
Date: CENSORED_DATE
CENSORED_LOG_MESSAGE_TAG2
$
But this still begs the question about the git status
output.
Update in response to Pesho_T's comment:
I ran git branch -vv
and got the following. This is censored, but adding numbers like "2" and "4" to discriminate them from the others above:
$ git branch -vv | grep my-topic-branch
* my-topic-branch CENSORED_SHA1_TAG1 [tag1: ahead 195] MDFCOR-420 CENSORED_LOG_MESSAGE_TAG1
Replaying some of the commands in torek's answer, I see:
$ git rev-parse --abbrev-ref @{u}
tag1
$ git rev-parse --symbolic-full-name @{u}
refs/tags/tag1
I currently conclude, from torek's very nice writeup, that tag1 is indeed a tag, and not a branch.
I edited prior CENSORED_SHA1's to be consistent:
Another update in response to torek's answer:
The upstream of a branch is always another branch name or remote-tracking name, as tag names are forbidden
I am not sure of that because of this experiment:
$ 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
The above shows that the upstream is pointing to a tag.
To confirm that both of these tag1 and tag2 tags are on the master branch I did this, per the tips found in answer to Git: How to find out on which branch a tag is?:
$ git branch -a --contains $(git rev-parse tag1^{commit}) | grep -E 'my-topic-branch|master'
* my-topic-branch
master
$
My plans are not actually to git push
back to that the tag (tag2
) I ran git rebase
upon. I am only using that tag as a point on the master
branch to rebase the my-topic-branch
to. The tag2
happens to be a known point, on the master
branch, that the application builds properly upon (I cannot go into further detail on that for confidential reasons). I expect to continue to git rebase
to subsequent tags, tag3
, tag4
and so on, until my-topic-branch
is ready for production, at which time I will merge it back to my local master
branch and do the git push
from there.
I posted a MCVE answer to show how the upstream shown by git status
changes once an upstream is set on the parent branch of the topic branch, thus bolstering torek's comments shown therein. I'm still considering torek's answer as the answer to this, because without his input, I would have not figured it out.
It seems likely1 that you have accidentally created a branch name and a tag name that both print the same when shortened. [Edit: per update 3, this isn't the problem. Instead, somehow, you have the tag name set as the branch's upstream. I'm not sure how you got into that state—see the result of my own attempt to do that, below.] Once you have two such names, they can hold different hash IDs. The hash ID you get when parsing such a name is a little bit tricky (there are rules, which are outlined in the gitrevisions documentation, but there are exceptions to the rules as well). It's best to get back out of the situation, usually by renaming the inappropriate branch name.
Remember that a branch name like master
is really the name refs/heads/master
; a tag name like v2.25.0
is really refs/tags/v2.25.0
. It is therefore possible to create a branch named v2.25.0
even though the tag exists, because the branch's full name is refs/heads/v2.25.0
, not refs/tags/v2.25.0
. These two names are different, but if you view the short versions of each, both will be v2.25.0
.
The ahead
or behind
count message from git status
is the result of running:
git rev-list --count --left-right <name1>...<name2>
Note that there are three dots between the two names.2 The two names are:
name1
is your current branch;name2
is the upstream of your current branch.The git rev-list
command, in this form (using three dots), finds commits that are reachable from the left-side name but not the right-side name, and commits that are reachable from the right-side name but not the left-side name, and then counts (--count
) them, but separately (--left-right
) rather than combined.
This means that these counts depend on your current branch (of course—that's why it says "your branch ... is ahead of") and the upstream setting. You control the upstream setting with git branch --set-upstream-to
, and you can read the upstream of your current branch with:
$ git rev-parse --abbrev-ref @{u}
origin/master
$ git rev-parse --symbolic-full-name @{u}
refs/remotes/origin/master
To help with the case when you have accidentally made both a branch name and a tag name that look the same when abbreviated, use the --symbolic-full-name
variant.
The upstream of a branch is [Edit: or should be] always another branch name or remote-tracking name, as tag names are forbidden:
git branch --set-upstream-to=v2.25.0
fatal: Cannot setup tracking information; starting point 'v2.25.0' is not a branch.
Remote-tracking names like origin/master
are more typical, but you can set the upstream of any branch to any other branch name.
If the ahead
count is nonzero, that's usually what you see, and is what you are seeing here. However, if both counts are nonzero, git status
will use the word diverged
. If the ahead
count is zero and the behind
count is nonzero, git status
prints the behind
count. If both counts are zero—so that the branch is in sync with its upstream—git status
says Your branch is up to date with ...
.
For more about the three-dot syntax, see the gitrevisions documentation. To understand reachability, see Think Like (a) Git. For a short graphic illustration, consider this drawing:
I--J <-- branch1
/
...--G--H <-- master
\
K--L <-- branch2, origin/branch1
The name branch1
is "ahead 2" of master
because from commit J
we walk back to I
and then H
, which means the I-J
commits are reachable from branch1
but not from master
. Similarly, branch2
is 2 ahead of master
, but its two are commits K-L
. This means that master
is 2 behind either of branch1
or branch2
, with those two commits being I-J
or K-L
respectively. Meanwhile, branch1
has diverged from origin/branch1
because it is both 2 ahead (I-J
) and 2 behind (K-L
).
1You can get into similar tricky situations if you move a tag, because tags are meant to be universal across all Git repositories, but tags are also meant never to move. Once one Git repository has a tag, it will tend to assume that the copy that it has is correct, even if someone has forcibly moved a tag in some other Git repository that this Git repository is meant to match. But that would show different symptoms, because you cannot set the upstream of a branch to a tag name.
2The three-dot syntax produces a symmetric difference, in set-theoretical terms. Because this is symmetric, you can swap the two names, as long as you remember that the two counts that git rev-list --count --left-right
will print are now swapped as well.