Raffael
Raffael

Reputation: 20045

Updates were rejected because the tip of your current branch is behind - but why?

This question has been answered from another perspective here. But my question is about trying to understand why this problem arises in the first place. I have been running into this issue at work and the suggested solution is not overly satisfying as it doesn't really address the issue itself and comes with the danger of losing commits.

Below you'll find the shortest sequence of git interaction I found to reproduce the error:

   git clone [email protected]:joyofdata/test3.git
   cd test3
   echo "1" > m
   git add .
   git commit -m "m1"
   git push origin master

   git checkout -b feature
   git push -u origin feature
   echo "1" > f
   git add .
   git commit -m "f1"
   git rebase master
   git push origin feature

   git checkout master
   echo "2" >> m
   git add .
   git commit -m "m2"
   git push origin master

   git checkout feature
   echo "2" >> f
   git add .
   git commit -m "f2"
   git rebase master
   git push origin feature (error - see next code box)

I simply version a file m in master, then a file f in feature, then I commit a change in master, then I commit a change in feature. Now before pushing my change to remote feature branch I want to rebase it on master.

This is the command and the error message of the last command in above list:

➜  test3 git:(feature) git push origin feature
To github.com:joyofdata/test3.git
 ! [rejected]        feature -> feature (non-fast-forward)
error: failed to push some refs to '[email protected]:joyofdata/test3.git'
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.

Now - I try to solve the issue by first pulling from remote feature (as suggested in the message), then I resolve the conflict, push to feature:

➜  test3 git:(feature) git pull origin feature
From github.com:joyofdata/test3
 * branch            feature    -> FETCH_HEAD
Auto-merging f
CONFLICT (add/add): Merge conflict in f
Automatic merge failed; fix conflicts and then commit the result.

➜  test3 git:(feature) ✗ vim f
➜  test3 git:(feature) ✗ git add .
➜  test3 git:(feature) git commit -m "deconflicted"
[feature 3fb647e] deconflicted

➜  test3 git:(feature) git push origin 
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 565 bytes | 565.00 KiB/s, done.
Total 5 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), done.
To github.com:joyofdata/test3.git
   0f31f60..3fb647e  feature -> feature

But - from now on - I'm going to run again and again and again into that error every time I rebase feature on master - again a merge conflict and again that error.

➜  test3 git:(feature) git rebase master
First, rewinding head to replay your work on top of it...
Applying: f1
Applying: f1
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.
Applying: f2

➜  test3 git:(feature) git push origin  
To github.com:joyofdata/test3.git
 ! [rejected]        feature -> feature (non-fast-forward)
error: failed to push some refs to '[email protected]:joyofdata/test3.git'
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.

I don't get it!

I mean - what I want to do is simply applying the following workflow:

I simple terms - before pushing a commit to remote feature I want to rebase feature on master. If I merge master into feature (git pull origin master or git merge master if local master is up to date) then I don't run into that issue.

I hope this isn't too confusing but I don't know how to put it more simple and it's really annoying me.

Upvotes: 1

Views: 1627

Answers (2)

torek
torek

Reputation: 488619

The root of the problem is that you are rebasing your feature. This is a sloppy way to phrase it: it's not the branch name that matters, it is the commits that matter. But rebase works by copying some commits, to new and (supposedly) improved different commits. This require throwing away—or abandoning—the old (and now lousy) commits.

The git push command calls up another Git and sends them your new and improved feature commits, then asks that other Git repository to throw away its old (and now lousy) commits. It says: No! If I do ask you ask, I'll lose some precious commits! This complaint comes back in this form:

 ! [rejected]        feature -> feature (non-fast-forward)

The non-fast-forward error is the other Git's way of telling you that if it obeyed your polite request to change its name feature to name the new and improved commits you've suggested it use, this would lose the old (outdated by new-and-improved) commits.

Of course, that's precisely what you want it to do. Note: whether you should do this depends on whether everyone else who uses this other Git repository—the one over on GitHub—has agreed in advance that this is going to happen.

To make the other Git, the one over on GitHub, agree to discard commits from its repository, you must set a "force flag" on this particular git push operation. There are two different kinds of force flag, though:

  • General force: I command you, other Git repository, to set your branch name this way! This is simple and effective and discards not only the old and lousy commits you've replaced with new and improved commits via rebase, but also every other commit that anyone else added that you didn't also replace with a new and improved copy. (That's one reason, of several, that everyone who uses this GitHub repository must agree in advance that rebasing will happen.)

  • Force-with-lease: I command you, other Git repository, to compare the hash ID stored in your branch name to the value I think you have. If they are equal, you should then replace that value with the new value I am supplying now. This variant of force-push is safer: if you rebased some commits, but someone else has, since then, added new commits that you failed to rebase, your force-with-lease operation will fail. The other Git, over at GitHub, will say: The hash ID I had stored does not match the one you said you thought I had. So I did not accept your command after all.

If you and all other users of this GitHub repository have agreed in advance that rebasing can happen, and you think someone might have snuck in a few commits while you weren't looking, you should use the --force-with-lease option to git push to find out whether that's actually the case.

If there are no other users of the GitHub repository (or none of them use the feature branch), none of this is necessary and you can simply use git push --force. The only person you need to agree with is yourself: do you give yourself permission to force-push the feature branch? If so, you may force-push the feature branch.

Note that if there are other users, and you and they all agree to this force push rebase process, you and they must all take care to observe any forced updates and do your own rebasing of any commits that didn't get copied to new and improved commits yet. This is a fairly complex work-flow; be sure that you and all of your friends / co-workers are ready for it.

Upvotes: 4

JhonatanSV
JhonatanSV

Reputation: 31

The quick answer is don't rebase upstream branches, this link explains the problem and the solution: https://git-scm.com/docs/git-rebase#_recovering_from_upstream_rebase

A rudimentary option could be this: Starting from your example, before the error line the branches looks like this:

$ git log --pretty=oneline --graph --all
* b5b045591ec6584e8f896d85399b7ed5b08d8098 (HEAD -> feature) f2
* 5187c95d6d91b550b9b2cc10ad673a52add620f6 f1
* cea62f3fd0349390115ee5e263730656b7a52d2d (origin/master, master) m2
| * f043b50940593c1ded4631f3681420ad57c0b190 (origin/feature) f1
|/
* 4847339d95d8f02c25538da7e51be14cbb30530d m1

when execute

$ git push origin feature

You try to include changes from branch feature to origin/feature wich is imposible because they have diferent commits.

So you can delete the remote branch origin/feature

  1. Unset upstream

    $ git checkout feature
    $ git branch --unset-upstream
    
  2. Remove remote branch

    $ git push origin --delete feature
    
  3. Push your changes adding upstream

    $ git push -u origin feature
    

Branch ends like this

$ git log --pretty=oneline --graph --all
* b5b045591ec6584e8f896d85399b7ed5b08d8098 (HEAD -> feature) f2
* 5187c95d6d91b550b9b2cc10ad673a52add620f6 f1
* cea62f3fd0349390115ee5e263730656b7a52d2d (origin/master, master) m2
* 4847339d95d8f02c25538da7e51be14cbb30530d m1

According to this example the commit b5b045 has the changes of commit f043b50 so there is not losing changes, you can check with

$ git diff f043b50 b5b045

Upvotes: 3

Related Questions