A normal push requires fast-forward: the remote branch must be an ancestor of your local branch. This protects against accidentally losing someone else's commits.
Sometimes you need to bypass that protection. After a local rebase, amend, or interactive rebase (interactive-rebase) the commit SHAs have changed, and a normal push refuses:
! [rejected] feat -> feat (non-fast-forward)
That is where force push comes in.
--force: blunt and dangerous
git push --force origin feat
This tells the remote: "forget what you had, take exactly my history." If someone else pushed a commit between your last fetch and your push, their commit disappears. You will not notice right away: their clone still has it, but it is gone from the shared remote.
That is the classic "someone force-pushed and broke things" disaster in team work.
--force-with-lease: the safe version
git push --force-with-lease origin feat
This tells the remote: "overwrite the branch, but only if its current SHA equals what I last saw via fetch." If the SHA differs, someone pushed after you and the force is cancelled.
! [rejected] feat -> feat (stale info)
In that case: run git fetch, inspect what came in, decide what to do
(usually rebase your commits on top of the new ones), then repeat
force-with-lease.
Rule: never --force, always --force-with-lease. Set an alias
so your fingers go to the right place automatically:
git config --global alias.pushf 'push --force-with-lease'
For an even more explicit check, pass the SHA you expect:
git push --force-with-lease=feat:abc123 origin feat
When force push is appropriate
- After rebasing your feature branch. This is the standard scenario.
- After amending the last commit on a pushed feature branch.
- After interactive rebase to clean up history before a PR.
All three apply only to branches that you alone are working on. On
main, master, or develop, force push is blocked by branch
protection rules on the forge.
When force push is not the answer
- "I pushed with a typo in the commit message." Usually easier to leave it, or fix it with a new commit.
- "I want to remove a secret from history." Force push alone is not enough. See git-filter-repo, and rotate the secret regardless.
- "I could not figure out the merge." No. Do the merge or rebase properly.
Branch protection: the safety net
On GitHub/GitLab, protected branches (main, master, release/*) can have:
- Force push blocked.
- Direct push blocked (only through a PR).
- Required green CI and approvals.
This is a safeguard against human error. Enable it on main always, even in small teams.
Pitfalls
- A background fetch can fool
--force-with-lease. Without an explicit SHA, the lease compares against your remote-tracking ref (refs/remotes/origin/feat). Normally "fetched long ago" works in your favor: the lease sees an old SHA that does not match the real remote, and the push is rejected withstale info. The dangerous case is when something fetches on your behalf (an IDE, a file watcher, a parallel terminal): the remote-tracking ref silently updates to the new foreign SHA, the lease matches, the force goes through, and their commits are gone. The fix is an explicit SHA:git push --force-with-lease=feat:<sha>, where<sha>is what you personally saw viagit fetchand consciously decided to overwrite. - Some forges keep reflog for 30+ days. If someone was accidentally overwritten, the SHA can usually be recovered from the forge reflog or from a colleague's local clone.
- Force push breaks stacked PRs. If someone rebased on top of your branch, a force push forces them to rebase again. In a stacked workflow this is normal, but give a heads-up first.