git push --force-with-lease can still overwrite a teammate's commit: background fetch and --force-if-includes

What --force-with-lease compares when you force-push after a rebase, why a background fetch defeats it, and how --force-if-includes (Git 2.30) or an explicit expected value fixes that.

DevelopmentGitVersion Control

After a rebase, getting the cleaned-up branch back to the remote takes a force push. The usual advice is to use --force-with-lease instead of --force, but switching the flag doesn’t always keep a teammate’s commits safe. To see when the protection disappears, you need to know what the option actually compares.

This post follows the git-push and git-config manuals on git-scm.com for Git 2.56.0 (September 28, 2026). The git-push page was last changed in 2.55.0.

Why a rebase needs a force push

git push normally won’t update a remote branch in a non-fast-forward way: the branch’s current tip has to be an ancestor of the commit replacing it. A rebase swaps commits you already pushed for new ones, so the old tip still on the remote is no longer an ancestor of your new history. --force, or a + in front of the refspec, turns that check off.

The manual describes --force as a method reserved for when you do mean to lose history, and warns that if someone built on your original history while you were rebasing, blindly pushing with --force loses their work. --force also applies to every ref being pushed, so with push.default set to matching it can overwrite refs other than the current branch. To force a single branch, put + in front of its refspec: git push origin +feature.

What –force-with-lease compares

--force-with-lease updates the remote ref only if its current value matches an expected value, and fails the push otherwise. Where that expected value comes from depends on the form:

Form Expected value
--force-with-lease For each ref being updated, its remote-tracking branch (e.g. origin/feature)
--force-with-lease=<ref> The remote-tracking branch of that one ref
--force-with-lease=<ref>:<expect> The <expect> you wrote. An empty string means the ref must not exist on the remote yet

The manual notes that every form except the last, where you state the expected value yourself, is still experimental and may change meaning.

How a background fetch removes the protection

The forms without an explicit value treat your remote-tracking branch as “the remote state I have seen”. That assumption breaks when your editor or a cron job runs git fetch origin in the background.

  1. While you rebase feature, a teammate pushes commit X to the remote feature.
  2. Your editor fetches in the background, and origin/feature now points at X. You have never looked at X, and it isn’t in your rebase.
  3. You run git push --force-with-lease. The remote’s current value and origin/feature are both X, so the check passes, and X disappears from the remote branch.

The manual says this combination interacts very badly: Git has nothing but the remote-tracking info to go on, so a process updating refs in the background removes the advantage over --force.

Two ways to close the gap

First, state the expected value. Following the manual’s example, tag the point you saw before rebasing. Even if a background fetch moves origin/master, the push only overwrites the remote if it is still at that commit.

git fetch                  # update master from the remote
git tag base master        # mark the point you have seen
git rebase -i master       # rewrite commits
git push --force-with-lease=master:base master:master

Second, add --force-if-includes, available since Git 2.30.0. It checks whether the tip of the remote-tracking ref is reachable from one of the reflog entries of your local branch, and rejects the forced update if it isn’t. In the scenario above, X is not reachable from any reflog entry of your feature, so the push is rejected.

git push --force-with-lease --force-if-includes
# or set it once
git config --global push.useForceIfIncludes true

Setting push.useForceIfIncludes to true is the same as passing --force-if-includes, and --no-force-if-includes overrides it for a single push. Two caveats: --force-if-includes does nothing when used without --force-with-lease or together with the <ref>:<expect> form, and according to the manual, --force disables the --force-with-lease checks too.

If --force-if-includes rejects the push because the remote-tracking tip isn’t reachable from your branch’s reflog, the remote has work you haven’t incorporated. Fetch and look at it before you rewrite or push again. The ... notation for seeing where your local and remote-tracking branches diverged is covered in Two dots or three in Git.

Sources

Links and contents checked on October 6, 2026.

Back to the blog