270

I had a repository that had some bad commits on it (D, E and F for this example).

A-B-C-D-E-F master and origin/master

I've modified the local repository specifically with a git reset --hard. I took a branch before the reset so now I have a repo that looks like:

A-B-C master  
     \ D-E-F old_master

A-B-C-D-E-F origin/master

Now I needed some parts of those bad commits so I cherry picked the bits I needed and made some new commits so now I have the following locally:

A-B-C-G-H master
     \ D-E-F old_master

Now I want to push this state of affairs to the remote repo. However, when I try to do a git push Git politely gives me the brush off:

$ git push origin +master:master --force  
Total 0 (delta 0), reused 0 (delta 0)  
error: denying non-fast forward refs/heads/master (you should pull first)  
To git@git.example.com:myrepo.git  
! [remote rejected] master -> master (non-fast forward)  
error: failed to push some refs to 'git@git.example.com:myrepo.git'  

How do I get the remote repo to take the current state of the local repo?

Black
  • 18,150
  • 39
  • 158
  • 271
robertpostill
  • 3,820
  • 3
  • 29
  • 38
  • 2
    The is an 'almost' duplicate of several "how do I push amended history questions", e.g. see the answer here http://stackoverflow.com/questions/253055/how-do-i-push-amended-commit-to-the-remote-git-repo/255080#255080 – CB Bailey Sep 04 '09 at 08:26
  • 2
    That's true and I had searched StackOverflow for an answer before posting. However my search had only turned up answers in which a git push --force fixed the issue. Thanks for linking to your post :) – robertpostill Sep 04 '09 at 08:52
  • 2
    You will soon (git1.8.5, Q4 2013) be able to [do a `git push -force` more carefully](http://stackoverflow.com/a/18505634/6309). – VonC Sep 10 '13 at 08:42

5 Answers5

347

If forcing a push doesn't help (git push --force origin or git push --force origin master should be enough), it might mean that the remote server is refusing non fast-forward pushes, via either receive.denyNonFastForwards config variable (see git config manpage for description), or via an update/pre-receive hook.

With older Git you can work around that restriction by deleting git push origin :master (note the : before branch name) and then re-creating git push origin master given branch.

If you can't change this, then the only solution would be instead of rewriting history to create a commit reverting changes in D-E-F:

A-B-C-D-E-F-[(D-E-F)^-1]   master

A-B-C-D-E-F                origin/master
Jakub Narębski
  • 309,089
  • 65
  • 217
  • 230
38

For users of GitHub, this worked for me:

  1. In any branch protection rules where you wish to make the change, make sure Allow force pushes is enabled
  2. git reset --hard <full_hash_of_commit_to_reset_to>
  3. git push --force

This will "correct" the branch history on your local machine and the GitHub server, but anyone who has sync'ed this branch with the server since the bad commit will have the history on their local machine. If they have permission to push to the branch directly then these commits will show right back up when they sync.

All everyone else needs to do is the git reset command from above to "correct" the branch on their local machine. Of course they would need to be wary of any local commits made to this branch after the target hash. Cherry pick/backup and reapply those as necessary, but if you are in a protected branch then the number of people who can commit directly to it is likely limited.

Jason Faulkner
  • 6,378
  • 2
  • 28
  • 33
  • Ditto for Gitlab: if you have a nasty message like "error: failed to push some refs to `http://www.gitlab.com/me/myproject`" the problem *may* be that you need to "allow forced push": Settings --> Repository --> Protected branches (at time of writing). No need to actually unprotect the branch, but do allow forced push. I actually did a pull on the branch in question, then a hard reset, then a forced push. – mike rodent May 24 '22 at 20:06
28

To complement Jakub's answer, if you have access to the remote git server in ssh, you can go into the git remote directory and set:

user@remote$ git config receive.denyNonFastforwards false

Then go back to your local repo, try again to do your commit with --force:

user@local$ git push origin +master:master --force

And finally revert the server's setting in the original protected state:

user@remote$ git config receive.denyNonFastforwards true
Jealie
  • 6,157
  • 2
  • 33
  • 36
  • See also http://pete.akeo.ie/2011/02/denying-non-fast-forward-and.html for sourceforge tailored information about this. – hlovdal Mar 07 '14 at 22:41
  • Detailed instructions on how to disable denyNonFastForwards using `vi` are provided on this SO post: stackoverflow.com/a/43721579/2073804 – ron190 May 01 '17 at 23:18
3

Instead of fixing your "master" branch, it's way easier to swap it with your "desired-master" by renaming the branches. See https://stackoverflow.com/a/2862606/2321594. This way you wouldn't even leave any trace of multiple revert logs.

Community
  • 1
  • 1
Aidin
  • 25,146
  • 8
  • 76
  • 67
2

The whole git resetting business looked far to complicating for me.

So I did something along the lines to get my src folder in the state i had a few commits ago

# reset the local state
git reset <somecommit> --hard 
# copy the relevant part e.g. src (exclude is only needed if you specify .)
tar cvfz /tmp/current.tgz --exclude .git  src
# get the current state of git
git pull
# remove what you don't like anymore
rm -rf src
# restore from the tar file
tar xvfz /tmp/current.tgz
# commit everything back to git
git commit -a
# now you can properly push
git push

This way the state of affairs in the src is kept in a tar file and git is forced to accept this state without too much fiddling basically the src directory is replaced with the state it had several commits ago.

Wolfgang Fahl
  • 15,016
  • 11
  • 93
  • 186