TechEarl

Force Push Safely in Git: --force vs --force-with-lease

When you need to force push in Git, plain --force can silently clobber a teammate's commits. Use --force-with-lease (and --force-if-includes) so the push refuses if the remote moved.

Ishan Karunaratne⏱️ 8 min readUpdated
Share thisCopied
Force push safely in Git: use --force-with-lease instead of plain --force so a push refuses when the remote branch moved since your last fetch, and never force-push a shared branch.

The safe way to force push in Git is git push --force-with-lease, not git push --force. The lease version refuses to push if the remote branch moved since you last fetched it, so you cannot silently overwrite a commit a teammate pushed while you were rebasing. Plain --force does no such check: it overwrites whatever is on the remote, full stop.

bash
# The safe default: refuses if someone else pushed since your last fetch
git push --force-with-lease origin my-branch

That one substitution is most of the lesson. The rest of this page is when you actually need to force push at all, why --force is a foot-gun on a shared branch, and the two stronger variants for when you want the check to be airtight.

When you need to force push

You only need a force push after you have rewritten history that you already pushed. Normal pushes fast-forward: the remote tip is an ancestor of your local tip, so Git just moves the pointer. Rewriting breaks that ancestry, and Git rejects the push to protect the remote. The common causes:

  • git rebase moved your commits onto a new base, so they have new SHAs.
  • git commit --amend changed the last commit (message or content), giving it a new SHA.
  • An interactive rebase to squash commits collapsed several commits into one.

In every case your local branch and the remote branch have diverged in a way that is not a fast-forward, and Git tells you so:

bash
 ! [rejected]        my-branch -> my-branch (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch is behind

That rejection is correct. The fix is not to reach for --force reflexively; it is to force push safely.

Why plain --force is dangerous

git push --force (and its short forms -f and the git push origin +my-branch plus-prefix trick) all do the same blind thing: they tell the remote to set the branch to your local commit no matter what is there now. If a teammate pushed a commit to that branch since you last fetched, you have not seen it, and --force overwrites it. Their work is gone from the branch tip. It still exists in their local reflog and possibly in the remote's reflog, but as far as the shared branch is concerned you just erased it.

bash
# Avoid: overwrites the remote unconditionally, including commits you never fetched
git push --force origin my-branch

The danger is precisely that it works even when you are wrong about the remote state. Nothing warns you.

--force-with-lease is the safe version

--force-with-lease adds the check --force is missing. It records what your local copy thinks the remote tip is (your remote-tracking ref, refs/remotes/origin/my-branch, set the last time you fetched) and refuses the push if the real remote tip no longer matches. So if a teammate pushed since your last fetch, the lease is broken and the push is rejected instead of clobbering their commit.

bash
git push --force-with-lease origin my-branch

If it is rejected, that is the system doing its job: you fetch, see the commit you would have destroyed, reconcile (rebase your work on top, or talk to whoever pushed), and try again. A rejected lease has never lost anyone's work; an accepted --force regularly has.

The airtight form: pin the expected SHA

The bare --force-with-lease trusts your remote-tracking ref. You can make it stricter by stating exactly which remote SHA you expect to overwrite. If the remote is not at that SHA, the push fails:

bash
# Refuse unless origin/my-branch is exactly this commit
git push --force-with-lease=my-branch:abc1234 origin my-branch

This is the form to use in scripts and CI, where you do not want to depend on whatever happens to be in the remote-tracking ref at push time. You read the SHA you intend to replace, pass it explicitly, and the push is a no-op-or-fail rather than a guess.

--force-if-includes closes the lease gotcha

There is one way the bare lease can betray you: a background fetch. Tools like IDEs, git maintenance, and some shell prompts run git fetch on their own. A fetch updates your remote-tracking ref without integrating the new commit into your local branch. Now origin/my-branch matches the real remote, so the lease passes, but you never actually saw or merged the teammate's commit. --force-with-lease is satisfied and you overwrite it anyway.

--force-if-includes (Git 2.30+, December 2020) plugs that hole. Combined with the lease, it requires that the remote tip was actually integrated into your local branch, not merely fetched into a tracking ref:

bash
git push --force-with-lease --force-if-includes origin my-branch

Per the git-push documentation, passing --force-if-includes without --force-with-lease is a no-op, so the two go together. This pair is the strongest practical safety you can put on a force push, and it is worth aliasing as your default. If your Git is older than 2.30, --force-with-lease alone is still a large improvement over --force.

Never force-push a shared branch

No variant of force push makes it acceptable to rewrite main, master, develop, or any branch other people build on. Rewriting a shared branch changes commit SHAs out from under everyone who has pulled it; their next pull becomes a divergent mess and they have to recover by hand. The lease does not save you here, because it only guards against unfetched remote commits, not against the social cost of rewriting history that dozens of clones already contain.

The rule is simple: force push is for your own feature or pull-request branch, after you rebased or amended it, when you are the only one working on it. To undo something on a shared branch, do not rewrite it; make a new commit with git revert instead. See undo things in Git for which tool fits which situation, and essential Git branch commands for keeping branches straight in the first place.

A good habit: set the safe form as an alias so you never type plain --force out of muscle memory.

bash
git config --global alias.fpush "push --force-with-lease --force-if-includes"
# then just: git fpush origin my-branch

FAQ

See also

Sources

Authoritative references this article was fact-checked against.

Tagsgitforce pushforce-with-leaserebasegit pushDevOpsversion control

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

How to Use .gitignore (with Examples)

A practical guide to .gitignore: pattern syntax, per-repo vs global ignore, ready-made templates, and the gotcha that trips everyone up - already-tracked files keep showing up.

find vs locate vs mlocate: Which File Search Tool to Use

find walks the live filesystem every time it runs: always current, sometimes slow. locate queries a prebuilt database: instant, but stale until the next updatedb. This breaks down the locate family (mlocate, plocate, slocate), the macOS situation, and exactly when to reach for each one.