Search code examples
gitgit-branchgit-rebasegit-taggit-status

git status still says “Your branch is ahead of <tag1> by N commits.” after git rebase <tag2>


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).

Update 1

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 2:

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

Update 3:

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.

Update 4:

I edited prior CENSORED_SHA1's to be consistent:

  1. CENSORED_SHA1_TAG1 is the commit SHA1 corresponding for tag1
  2. CENSORED_SHA1_TAG2 is the commit SHA1 corresponding for tag2

Update 5:

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
$

Update 6:

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.

Update 7

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.


Solution

  • 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.