Topic 5.4
Force-Pushing Safely (--force-with-lease) & When Never To
In one line
A normal push is rejected after rewriting history — force-pushing overrides that protection, and --force-with-lease is the version that at least checks nobody else pushed in the meantime before doing it.
Think of it like this
Overwriting a shared document with your own local copy, without checking whether anyone else has edited the shared version since you last opened it — this is exactly what a plain force-push does, and exactly why it's dangerous on any branch other people are also using.
Key ideas
- 01
After amending, rebasing, or otherwise rewriting commits that were already pushed (Topics 5.1 and 2.3's golden rule notwithstanding — sometimes it genuinely is necessary), a normal
git pushgets rejected, since the remote's history and your local history have diverged.git push --forceoverrides this rejection, making the remote match your local branch EXACTLY, discarding whatever was there before. - 02
The real danger: if someone else pushed a new commit to that branch AFTER your last pull but BEFORE your force-push, a plain
--forcesilently OVERWRITES and permanently discards their commit, with no warning at all — they'll simply find their work missing, with no error or indication of what happened, unless they happen to still have it locally themselves. - 03
git push --force-with-leaseis the safer version: it checks that the remote branch is EXACTLY where your local copy last knew it to be, and REFUSES to force-push if the remote has moved since (meaning someone else pushed something new) — turning a silent, destructive overwrite into a safe rejection you can investigate first. - 04
The genuinely safe use case for force-pushing at all: your OWN feature branch, that only you work on, after cleaning it up with an interactive rebase or amend — force-pushing there only ever overwrites your own prior commits, which is exactly what you intend. Force-pushing to a SHARED branch (
main, or any branch others actively push to) should be exceedingly rare, and always coordinated with the team first if it's ever genuinely necessary. - 05
Many real teams configure BRANCH PROTECTION on their hosting platform (GitHub/GitLab) to outright disable force-pushes on
mainentirely — removing the possibility of this mistake at the infrastructure level, rather than relying purely on everyone remembering to be careful.
Code & diagrams
Plain --force overwrites blindly; --force-with-lease checks first.
The safe default, and why plain --force is genuinely risky by comparison.
cd ~/scratch/my-first-repo
git switch feature/my-own-branch
# Clean up your OWN branch's history before opening a pull request
git rebase -i HEAD~3
# ... squash a few messy commits ...
# The plain push is now rejected, since history diverged
git push
# ! [rejected] ... (non-fast-forward)
# --force overwrites the remote unconditionally — risky if anyone else pushed
# git push --force
# --force-with-lease checks the remote hasn't moved since you last knew about it
git push --force-with-lease
# succeeds if nobody else pushed in the meantime
# REFUSES if someone else's commit is on the remote that you don't have locally —
# investigate with "git fetch" and "git log origin/feature/my-own-branch" before deciding what to do nextExplain it without notes
Why is a plain git push --force considered genuinely dangerous on a branch other people also push to?
What does --force-with-lease check that plain --force does not, and why does that make it meaningfully safer?
Practice
On a personal test branch, rebase or amend a pushed commit, then use --force-with-lease to update the remote — confirm it succeeds when you're the only one who's touched that branch.
Simulate the dangerous scenario: have a 'teammate' (a second local clone, or ask a real collaborator) push a new commit to a branch, then attempt your own --force-with-lease push without fetching first, and observe it correctly refuse.
Trade-offs
- ↔
--force-with-leaseis a strictly safer default than plain--forcewith essentially no downside, which is exactly why many experienced developers simply never use plain--forceat all — the only genuine trade-off is needing togit fetchand investigate on the (rare) occasions it correctly refuses, rather than blindly overwriting and finding out something was lost only later.
Done when you can
I understand why plain --force can silently destroy a teammate's work with no warning.
I default to --force-with-lease instead of plain --force, and understand why it's safer.
I only force-push to branches that are genuinely mine alone, or with explicit team coordination.