2

I am working on a branch, foo. I have no unstaged changes, no working changes, perfectly clean state, where HEAD == foo == origin/foo according to my box.

$ git status
# On branch foo
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   some_irrelevant_file_here

$ git log --pretty=...
* 456520c 2015-02-13 (HEAD, origin/foo, foo) Commit A
* 23bfcd1 2015-02-11 Commit B
* b0bdd18 2015-02-12 Commit C

I am then asked to look at some changes that a colleague pushed, so I do:

$ git pull --rebase origin foo
remote: Counting objects: 47, done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 36 (delta 22), reused 0 (delta 0)
Unpacking objects: 100% (36/36), done.
From ...
 * branch            foo       -> FETCH_HEAD
First, rewinding head to replay your work on top of it...
Fast-forwarded foo to 43dad88c737762e0f1e84fdcd135155080bdce2a.

At this point, my graph looks like:

$ git log --pretty=...
* 43dad88 2015-02-13 (HEAD, foo) Commit D
* 40039f9 2015-02-13 Commit E
* 456520c 2015-02-13 (origin/foo) Commit A
* 23bfcd1 2015-02-11 Commit B
* b0bdd18 2015-02-12 Commit C

Why does it look like my local foo is ahead of origin/foo? Neither D nor E are my commits, I just pulled both of those from origin - I'd expect at this point to still have HEAD == foo == origin/foo.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    "according to my box". Your box might be outdated. What happenes if you run `git fetch origin foo` first? – Jørgen R Feb 13 '15 at 14:51
  • 1
    @jurgemaister the pull --rebase does a fetch implicitly. – Robert Bain Feb 13 '15 at 14:54
  • I'd have expected `* (HEAD, foo) commit C * (origin/foo, origin/HEAD) commit A * commit B * commit D` – Robert Bain Feb 13 '15 at 14:55
  • @RobertBain I think jurgemaister is referring to the "diff preview" when you checkout a certain branch and it gets compared to the origin _based on the last fetch_. So that information can be misleading sometimes. – Cody Stott Feb 13 '15 at 15:00
  • 1
    @CodyStott we're looking at the commit history, not the diff preview - we're passed that stage. There's been a `git pull --rebase origin foo`, so effectively a `git fetch origin foo` and a `git rebase origin/foo`. The fetch has already happened. – Robert Bain Feb 13 '15 at 15:12
  • 1
    @RobertBain I know what `git pull` does. My comment however is based on the fact that the original question mentions the fact that he "knows" that his local is even with the remote _before_ he mentions doing the `git pull`. Just consider the sequence of what he says. – Cody Stott Feb 13 '15 at 15:23
  • @CodyStott that doesn't explain the order of the commits. If locally there are no unstaged changes, no working changes and a perfectly clean state where HEAD == foo == origin/foo, then doing a fetch and rebase should replay the commit C on top of remote's HEAD. – Robert Bain Feb 13 '15 at 15:54
  • @RobertBain "Neither `A` nor `B` are my commits, I just pulled both of those from `origin`" So what he's saying is that he did a (default?) `git pull` which merges on top of his local `foo`, then he rebases off of `origin/foo` which I'm assuming doesn't have `A` or `B` and you are wondering why `A` and `B` are at the top? – Cody Stott Feb 13 '15 at 16:42
  • 1
    @CodyStott in my mind that's not what he's saying. I think he's saying he did a `git pull --rebase origin foo`, which is the same as `git fetch origin foo` and a `git rebase origin/foo`. I'd be happy to chat about this further in discussion or perhaps just leave it for others to offer their thoughts. – Robert Bain Feb 13 '15 at 16:48
  • 1
    @CodyStott I did no `git pull`, just `git pull --rebase origin foo`. Is there anything I can clarify? – Barry Feb 13 '15 at 17:07
  • @RobertBain I added more detail into the question, with some SHAs and the output of all my commands and stuff (I ran exactly these commands in order). – Barry Feb 17 '15 at 16:45

3 Answers3

3

What you had before the pull --rebase was:

x--x--x (foo, HEAD, origin/foo)
       \
        y--y (actual origin/foo)

When you pull --rebase, you are asking to replay your local commits on top of origin/foo:
foo is reset/checked out at origin/foo (which is fetched in FETCH_HEAD), and since your commits 'x' are already part of the new updated FETCH_HEAD, foo is simply fast-forwarded:

x--x--x--y--y (foo, HEAD, FETCH_HEAD)
      |
 (origin/foo)

The fact that origin/foo has not changed is typical of a git older than 1.8.4 (found for instance in Ubuntu Precise 12.04, which comes with and old git-core 1.7.9.5)

A simple git fetch should be enough to update origin/foo, and not just FETCH_HEAD.

A more recent git (1.8.4+) would have updated both FETCH_HEAD and origin/foo, making the additional git fetch redundant.


Note: with Git 2.12 (Q1 2017), this case (pull --rebase) will be a simple fast-forward merge.

See commit 33b842a (29 Jun 2016) by Junio C Hamano (gitster). (Merged by Junio C Hamano -- gitster -- in commit 2fb11ec, 19 Dec 2016)

pull: fast-forward "pull --rebase=true"

"git pull --rebase" always runs "git rebase" after fetching the commit to serve as the new base, even when the new base is a descendant of the current HEAD, i.e. we haven't done any work.

In such a case, we can instead fast-forward to the new base without invoking the rebase process.

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
0

Since it is not pushed to the repo that is a normal behavior. How come you can have your local commits before origin? They will always be on top.

Gaskoin
  • 2,469
  • 13
  • 22
0

Why does it look like my local foo is ahead of origin/foo?

That foo is ahead of remote/foo is expected, because rebase plays back your current branch on top of the upstream branch. You rebased foo on top of the remote/foo, so foo should be ahead of remote/foo.

HEAD == foo == origin/foo according to my box.

As a result, the order of commits is also as expected. The following is how your branching would look before a rebase or merge where A is the actual state of the remote and C is your stale representation of the remote.

D <------C (HEAD, foo, remote/foo) <------B <------A (remote/foo)

You're doing the equivalent of a "fast-forward" rebase. I.e. it doesn't do anything to the commits; there is nothing to play back onto remote/foo! The result should only change ref locations:

D <------C <------B <------A (HEAD, foo, remote/foo)

Why does it look like my local foo is ahead of origin/foo?

That's the question I haven't been able to answer. After the rebase, I would expect again that HEAD == foo == remote/foo but it doesn't. Why after the rebase of foo onto remote/foo does the latter appear to be behind?

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • 2
    Yes, that is both what I would expect, and my question. – Barry Feb 13 '15 at 18:56
  • @Barry Where's Linus when we need him? – Shaun Luttin Feb 14 '15 at 03:53
  • 1
    It seems so bizarre that I'm inclined to think that something's gone wrong and somehow the local repo is corrupted. I have no doubt that the purpose of the question is to clear up any possible misunderstanding but in light of any decent answer, I'd be tempted to diff HEAD HEAD~1 into a file, patch, push and hope it doesn't happen again. – Robert Bain Feb 15 '15 at 01:06
  • @RobertBain no, it is just an old git (pre 1.8.4): See [my answer above](http://stackoverflow.com/a/29081368/6309). – VonC Mar 17 '15 at 07:07
  • @VonC, what a subtle one, good catch. Can I assume you've run into the same problem before? – Robert Bain Mar 17 '15 at 11:28
  • @RobertBain yes, indeed. Plus I had quite a few questions here on Stack Overflow from users on Ubuntu 12.04 (the previous LTS version), using the default old git 1.7.9.5. And I documented that change before (http://stackoverflow.com/a/20967347/6309). – VonC Mar 17 '15 at 11:32