It's hard to be sure without access to the actual commits and repositories, but this is the sort of thing that happens if your upstream—the people in control of the repository from which you git clone
d your own repository—have done a massive history rewrite.
The heart of the issue here is that "ahead" and "behind" don't mean what people want to think they mean at first, because people like to think of history as linear: thing X happened, then thing Y happened later, then thing Z. History in Git is not linear: thing X happened while thing Y was also happening. Thing Z tied X and Y together:
...--X--Y--Z <-- present-day
is wrong and:
...--X
\
Z <-- present-day
/
...--Y
is right. Neither X nor Y are "ahead of" or "behind" each other, even though both are quite clearly "behind" Z.
(Don't take this particular analogy too far: it's just meant as an illustration. Starting from Z and working backwards, Git might show you Y first, then X, when Git shows you one commit at a time—but that doesn't mean there's some strong ordering between X and Y. You might see Z then X then Y instead, just as easily.)
With the above in mind, suppose that you clone a repository that has some string of commits in it:
...--F--G--H <-- master
Because your clone copies the actual commits, you get exactly-identical commits in your own (separate) repository:
...--F--G--H <-- master, origin/master
The origin/master
is your Git's memory of their Git's master
: their Git's master
pointed to commit H
.
You now make your own commits, in your own repository, on your own branch you call feature
:
...--F--G--H <-- master, origin/master
\
I--J <-- feature (HEAD)
Meanwhile, for some reason, the people in control of the repository you cloned decide they hate commits G
and H
and they replace them with improved commits G'
and H'
. You now run git fetch origin
to update your repository with their new commits, giving you this:
G'-H' <-- origin/master
/
...--F--G--H <-- master
\
I--J <-- feature
Your branch is now four commits "ahead of" origin/master
, and also two commits "behind" origin/master
. The four that you are ahead of them are G
, H
, I
, and J
—which as far as your Git is concerned, you must have written yourself, because origin/master
goes from H'
back to G'
back to F
, skipping over G
and H
.
(The two that you are "behind" are of course G'
and H'
.)
In this particular example you wind up just two extra ahead. If they did a massive copy-and-substitute operation, using git filter-branch
or the BFG or some such, though, you'll be hundreds or thousands of commits "ahead" of the ones they threw away, which your Git now thinks are your own work. You fix this by, as Git puts it, "recovering from an upstream rebase".