The Problem: The Nightmare Named git push –force
Over two years ago, our team was in a sprint to release a payment gateway. After rebasing my feature branch to clean up commit history, I casually ran: git push origin feature/checkout --force. Everything seemed fine. Exactly 10 minutes later, the teammate responsible for VNPay integration rushed over to my desk, looking pale: four bug-fix commits he had just pushed to the shared branch had completely vanished.
The feeling was pure dread. The -f (or --force) flag operates completely blind. It forces the server to accept your local branch, regardless of whether someone else has pushed new code to the remote. After that unforgettable incident kept the whole team up until 2 AM recovering code, we changed the rules: ban git push --force entirely and switch permanently to --force-with-lease. Over the past six months, this command has saved our team from at least 10 close calls.
Core Concept: How –force-with-lease Works
At its core, --force-with-lease still allows rewriting commit history. The key difference lies in one crucial detail: it checks before it overwrites.
This mechanism is similar to optimistic locking in databases. Git compares the current ref of the remote branch against the remote-tracking branch (such as origin/feature-branch) cached on your local machine:
- Safe scenario: The commit SHA on the remote server matches your local remote-tracking branch. This means nobody has pushed any new commits since your last
git fetch. Git immediately permits the overwrite. - Conflict scenario: A teammate pushed new code to the remote while you were rebasing. The SHA on the server no longer matches. Git immediately blocks the push and raises an error.
Practical Comparison: –force vs. –force-with-lease
The comparison table below highlights the key differences between the two flags:
| Criteria | git push --force |
git push --force-with-lease |
|---|---|---|
| Overwrite behavior | Unconditionally overwrites, ignoring remote state | Overwrites only if no unseen commits exist on remote |
| Teammate code protection | No (easily obliterates others’ commits) | Yes (blocks push as soon as new changes are detected) |
| Accident risk | Very high when multiple people share a branch | Extremely low, tightly controlled risk |
Hands-On Guide: Step-by-Step Practical Usage
1. Standard Syntax
Whenever you squash commits, rebase a branch, or run git commit --amend locally, replace the -f flag with the following command:
# Safely push code to your current working branch
git push --force-with-lease origin feature/payment-gateway
2. Simulating a Blocked Push When New Code Exists
Suppose you and your teammate Alex are collaborating on feature/login. You just finished a local rebase squashing 3 commits into 1. Meanwhile, Alex has just pushed a commit fixing some CSS to GitHub:
# You attempt a safe force push
$ git push --force-with-lease origin feature/login
# Git output:
To github.com:org/repo.git
! [rejected] feature/login -> feature/login (stale info)
error: failed to push some refs to 'github.com:org/repo.git'
The stale info error message confirms the safety catch is doing its job. Git detected that the remote branch has moved ahead of your local remote-tracking branch, so it refused to overwrite it.
3. The 4-Step Workflow to Resolve a stale info Error
Don’t panic when encountering this error. Follow these four steps sequentially:
# Step 1: Fetch the latest changes from the remote
git fetch origin
# Step 2: Quickly inspect what your teammate recently pushed
git log --oneline feature/login..origin/feature/login
# Step 3: Rebase your local branch on top of the remote's new commits
git rebase origin/feature/login
# Step 4: Safely push again
git push --force-with-lease origin feature/login
All changes from both sides are seamlessly merged. Not a single line of code is lost.
4. A Critical Blind Spot: Beware of Background Auto-Fetch
Although significantly safer, --force-with-lease has one technical caveat. It only compares against the remote-tracking branch cached on your local machine. If IDEs like VS Code or JetBrains have background auto-fetch enabled, Git will automatically pull new refs behind the scenes. When that happens, --force-with-lease assumes you are already aware of your teammate’s new commits, and the push will overwrite remote changes anyway!
For mission-critical branches, explicitly specify the exact expected remote SHA ref as an argument:
# Only permit overwrite if the remote commit matches SHA a1b2c3d
git push --force-with-lease=feature/login:a1b2c3d origin feature/login
5. Setting Up a Git Alias for Faster Typing
Typing out --force-with-lease every time can be tedious. To stick to best practices without the extra keystrokes, add an alias to your ~/.gitconfig file:
# Set up a quick pf alias
git config --global alias.pf "push --force-with-lease"
From now on, whenever you need to force push, just run:
git pf origin feature/my-branch
Conclusion: Small Habit, Huge Impact
Switching from -f to --force-with-lease takes just seconds to set up. In return, you get complete peace of mind whenever you rebase or rework commit history. Don’t let a careless keystroke wipe out hours of your teammate’s hard work. Set up your alias today and share this practice with your team.
