git push --force: rewriting history on the server

Updated July 31, 2026

git push --force replaces the branch on the server with your local version, even if the commits that were sitting there become unreachable as a result.

A plain git push can only build on top of the history the server already has. The moment your version and the server's diverge, the push is refused:

$ git push
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:username/my-project.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart.

There is one case where force is the right answer: you rewrote your own branch after you had already pushed it. After git commit --amend or git rebase the commits are new ones with new hashes, and an ordinary push has nothing to attach them to. Then the push looks like this:

$ git push --force-with-lease
To github.com:username/my-project.git
 + 6d6699c...4e39190 main -> main (forced update)

What the other person sees

Whoever pulled the old version is left holding a history that no longer exists on the server. Their next git pull does not update the branch, it stitches two diverging ones together:

$ git pull
From github.com:team/proj
 + 6d6699c...4e39190 main       -> origin/main  (forced update)
Merge made by the 'ort' strategy.

$ git log --oneline --graph
*   d784901 Merge branch 'main' of github.com:team/proj
|\
| * 4e39190 add the hiring section
* | 6d6699c add the hiring sectoin
|/
* 470601c first version of the report

The typo you fixed is back in the history, sitting next to the fix. With one commit it is funny; with a rewritten branch of twenty commits it costs half a day of untangling for everyone who had pulled it. Hence the rule: a force push goes into your own branch that nobody pulled. Into main and other shared branches, never.

--force-with-lease

This form checks first that the server is where you think it is. Git remembers where the server's branch stood at your last exchange with it: that is the origin/main pointer. Before pushing, --force-with-lease compares the server's branch against that pointer. If they match, nobody has written there since, and the push goes through. If they do not, it refuses:

$ git push --force-with-lease
 ! [rejected]        main -> main (stale info)
error: failed to push some refs to 'github.com:team/proj.git'

stale info means "what you know about the server is out of date". Run git fetch, look at what turned up, and decide with your eyes open. That is exactly why you should not run a blind fetch right before a force push: it refreshes the pointer and removes the whole protection.

Common mistakes

Answering with --force when the refusal is marked (fetch first). Git says that when the server holds commits of someone else that you do not have, and the cure is git pull followed by git push. Force here simply erases their work.

If a force push into a shared branch has already happened, the commits are not gone. Anyone who has not pulled yet still holds the old history: they find the hash in git reflog and put the branch back.

$ git reflog
6d6699c HEAD@{0}: clone: from github.com:team/proj.git

$ git push --force origin 6d6699c:main
To github.com:team/proj.git
 + 4e39190...6d6699c 6d6699c -> main (forced update)

Where this is in the book

Chapter 7. How teams actually use git.