2

I am having an odd problem with a repo. When I try to push my latest commit I can't, even though I am pretty sure that I have the latest commits and I am the only one who has been editing the repo in the last few hours.

Also my git log matches line by line the git log on GitHub.

$git push
To git@github.com:me/repo_name
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:me/repo_name'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details

This takes me to the following question:

  1. How can I see what commits specifically are colliding with the ones I am trying to push?
  2. How can my git log match line by line the one from GitHub and still have this problem?
  3. How can I force a push ?
  4. What will happen exactly if I force the push? (would history be rewritten?)

Note: I tried merging, but I run into conflicts, so I aborted since I wasn't expecting them. I was earlier testing an experimental git editor and I canceled commits halfway (could my repo be in some sort of inconsistent state?).

Also - I am using git 2.0.2

Update:

Following Jubobs answer, I did:

$ git log master ..origin/master 

and it lists one commit that I do have in git log. As I said above, the git logs match line by line, and I checked my source code and I have the exact same changes that the commit in question says I don't have. How can this be?

Community
  • 1
  • 1
Amelio Vazquez-Reina
  • 91,494
  • 132
  • 359
  • 564

2 Answers2

5

For items 1 and 2, simply use git fetch to pick up the latest origin/master (this assumes your github repo is under the remote named origin, and that you have not fiddled with the fetch = line as well), then use any log viewer (git log, gitk, etc) to show you the commits "they have" that "you don't". There are lots of ways to do this. For a complete view I personally prefer gitk --all here, but git log --graph master origin/master will also show something illustrative (see below).

For item 3, you can git push --force or git push origin +master:master (again this assumes that origin is the name of the remote that points to github).

For item 4: this is where the commit graph (as seen in item 1) is useful. Let's say that, for whatever reason, the graph you will see in item 1 looks like this (except that I want to draw it horizontally here, rather than the vertical drawings you'll get in gitk):

             --- o - o   <-- master
            /
... - o - o
            \
              o    <-- origin/master

In this particular drawing, you're "ahead 2" and "behind 1". You have two commits the remote does not, and it has one commit that you don't. Or rather, you do have "their" commit, but not on the history of commits found via your branch name master.

If you git push --force, what your git will do is contact their (github's) git and in "their" repo (i.e., your github copy of your repo), ask it to take your new commits—the two that are on your master that are not on its—and then just make their master point to the same commit. That is, their master will now point to the upper-right-most (in this drawing) commit, which is where your master points. Your git will then update its copy of their git's master—this is what your git is keeping in origin/master—so that the graph you have now looks like this:

             --- o - o   <-- master, origin/master
            /
... - o - o
            \
              o    [abandoned]

That "abandoned" commit may be removed from "their" repo (i.e., your github copy of your own repo) almost immediately.

If someone else is also copying "their" repo and using commits from it, they may still have the "abandoned" commit in their own copy. They may start depending on it, and get annoyed if you've told github to forget that commit, because now they'll have to do extra work to re-synchronize and yet also keep the contents of that commit. But if no one else is also using this github copy, or if they have not picked up that particular commit, they will never know that the commit used to be there and is now abandoned-and-maybe-really-gone.


You mention in an edit that you started a git merge. It's not clear if you ever finished or aborted the merge. If you are "in the middle" of a merge, you haven't actually created a merge commit yet, but you should either finish or abort the merge first. You can use git status to tell you if you are still in a merge:

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)
[snip]

If you already did abort the merge, and then also copied that commit to a new one (different SHA-1 hash, but same effect) ... well, let's re-draw this graph a bit, and give those commit nodes identifying letters:

             --- D - C'   <-- master
            /
... - A - B
            \
              C    <-- origin/master

Here, the "prime" single-quote in C' indicates that this commit is a copy of commit C: it has the same message and same effect, and maybe even the same time-stamps; but it's a bit different because, if nothing else, it has a different parent commit ID: it points to commit D, rather than commit B.

If you force-push this to the github copy of the repo, commit C is abandoned as before. That's no loss since everything important about it is already there in C'. But it can still make work for someone else who has cloned the github repo and has built new commits on top of C: they'll have to alter their work so that it sits atop C' now. The actual moving should be very easy (just git rebase), but it is still work.

You can go ahead and force-push, or, if you're feeling generous towards these other folks, you can attempt to re-arrange your own work so that it's a linear extension, a "fast forward" as git calls it. In this particular case, that would just require copying commit D to a D' that sits atop C, then abandoning your D - C' sequence in favor of the new sequence:

             --- D - C'   <-- your old master, to be abandoned
            /
... - A - B
            \
              C        <-- origin/master
                \
                  D'   <-- this becomes your new master

To do that, you can simply git rebase your master onto origin/master (with or without -i). Rebase is usually smart enough to notice a copied commit (C') and simply drop it; or you can use git rebase -i and explicitly drop it yourself:

$ git checkout master
Already on branch 'master'
$ git rebase origin/master

(or with -i, as above). Once all that works, you should be able to git push without --force: you're now strictly "ahead of" origin/master, which is the case git (and people using git) expect.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Sigh, the problem with direct numbering :-) – torek Aug 30 '14 at 23:07
  • Although I would be sad to lose the 15 rep points, you deserve the checkmark :) – jub0bs Aug 30 '14 at 23:22
  • @Jubobs: I dunno about that, your graphics are so much prettier... :-) – torek Aug 31 '14 at 00:49
  • If you're a LaTeX user, you could produce the same graphs. And I actually quite like the sober style of ASCII graphs :) – jub0bs Aug 31 '14 at 01:02
  • I have LaTeX, I just need to install the various additional bits. (But I have so many higher priority items... just replaced a failed GFI outlet, have home and work stuff to do, etc., before I get to play) – torek Aug 31 '14 at 01:09
4

1 - How can I see what commits specifically are colliding with the ones I am trying to push?

Do a fetch and then, assuming your local repo knows your remote repo under the name origin, run

git log master..origin/master

This will show you all the commits that are reachable from origin/master, but not from your local master branch.

2 - How can my git log match line by line the one from GitHub and still have this problem?

To see what changed between your master branch and origin/master, run

git diff master origin/master

If you get no output, compare the timestamps of the two heads (master and origin/master). Since the SHA-1 hashes are different, something must be different.

3 - How can I force a push ?

By running

git push -f origin master

but wait!

4 - What will happen exactly if I force the push? (would history be rewritten?)

If you force push your local master branch to the remote, you will force the master branch living on the remote repository to exactly mirror that which lives in your local repository. Commits recently pushed by your coworkers to the master branch on the remote repo will "disappear" from the history of that repository. That's dangerous territory.

For instance, if you do a fetch and your local repository looks as follows,

enter image description here

if you then run

git push -f origin master

the master branch in both repositories will point to commit D:

enter image description here

Commits E and F will be "lost". So, think twice before force pushing...

jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • Thanks --- I have run `git log master ..origin/master` and surprise, It lists a commit that I do have in `git log`! (and it is from yesterday). How can that be? (see my updated Q2) – Amelio Vazquez-Reina Aug 30 '14 at 22:42
  • @user815423426 Not sure what you mean by 2.? Could you clarify? – jub0bs Aug 30 '14 at 22:50
  • Interesting. I just did `git log --graph --all` and I see two commits with different hashes, but the same exact timestamp and message (from yesterday). Could it be that something went wrong with my push yesterday? – Amelio Vazquez-Reina Aug 30 '14 at 22:51
  • My question N.2 was because I thought I had the same exact commits in `origin` and locally, but it turns out that one of the commits got a different hash in GitHub, so my local git thinks I don't have that commit even though I was the one who created it and pushed it! So odd.. – Amelio Vazquez-Reina Aug 30 '14 at 22:52
  • @user815423426 What does the message of that commit tell you? Also, try running `git diff master origin/master`. – jub0bs Aug 30 '14 at 22:54
  • Your answer is perfect. The question I now have is why GitHub assigned it a different hash than my machine. It is literally the same commit, but with two different hashes. I created the commit and I pushed it, and I certainly have the changes that GitHub says I have. – Amelio Vazquez-Reina Aug 30 '14 at 22:54
  • 3
    If the hash is different, it's not the same commit `:)` If may be the exact same content, but something (the timestamp, perhaps) *must* be different. – jub0bs Aug 30 '14 at 22:55
  • I agree, but even the `timestamp` itself is the same as well (all the way to the second). So odd. – Amelio Vazquez-Reina Aug 30 '14 at 22:56
  • 3
    Note that there are actually two timestamps: author timestamp and committer timestamp. To see the raw contents of the two commits, use `git cat-file -p `. They will (necessarily) have at least one bit different somewhere, whether that's name, parent, author/committer/timestamps, tree, or message. – torek Aug 30 '14 at 23:05
  • 3
    BTW, @Jubobs, what are you using to make those nice graphics? :-) – torek Aug 30 '14 at 23:06
  • 3
    @torek A home-made tool: https://github.com/Jubobs/gitdags/wiki. Spread the word :) – jub0bs Aug 30 '14 at 23:07
  • 1
    @user815423426 Read torek's answer. It's much more complete than mine is. Consider giving *him* the checkmark instead. – jub0bs Aug 30 '14 at 23:24