Git Push --force vs --force-with-lease: Key Differences Explained for Developers
Git has revolutionized collaboration in software development, enabling teams to track changes, experiment, and merge code seamlessly. A common task in Git workflows is pushing local changes to a remote repository. But what happens when your local branch history diverges from the remote—for example, after rewriting commits with git commit --amend or rebasing? This is where force pushing enters the picture.
Force pushing (git push --force) is a powerful but risky command that overwrites the remote branch with your local history. While it’s occasionally necessary, it can easily erase teammates’ work if misused. Enter git push --force-with-lease: a safer alternative that adds a critical safety check before overwriting the remote.
In this blog, we’ll demystify both commands, explore their inner workings, highlight key differences, and guide you on when to use each. By the end, you’ll understand how to avoid catastrophic mistakes and push changes confidently—even when rewriting history.
Table of Contents#
- Understanding Git Push Basics
- What is
git push --force? - The Risks of
git push --force - What is
git push --force-with-lease? - How
--force-with-leaseWorks Under the Hood - Key Differences:
--forcevs--force-with-lease - When to Use Each Command
- Best Practices for Safe Force Pushing
- Real-World Scenarios and Examples
- Conclusion
- References
Understanding Git Push Basics#
Before diving into force pushing, let’s recap how normal git push works. When you run git push <remote> <branch>, Git attempts to update the remote branch with your local commits. For this to succeed without force, your local branch must be a fast-forward of the remote branch. In other words:
- The remote branch has no new commits that your local branch lacks.
- Your local branch has new commits added on top of the remote’s history.
If the remote branch has diverged (e.g., a teammate pushed commits while you were working), Git rejects the push with a "non-fast-forward" error:
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'https://github.com/your/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again. To resolve this, you’d typically git pull to fetch and merge the remote changes, then push again. But what if you intentionally rewrote your local history (e.g., to clean up commits with git rebase -i or git commit --amend)? In that case, your local history no longer aligns with the remote, and a normal push will fail. This is where force pushing comes into play.
What is git push --force?#
git push --force (or -f for short) is a command that unconditionally overwrites the remote branch with your local branch’s history. It ignores the "non-fast-forward" check and replaces the remote’s version of the branch with yours.
How It Works:#
When you run git push --force <remote> <branch>, Git tells the remote: "Discard your current history for this branch and replace it with mine."
Example:#
Suppose you commit a typo, amend it locally, and try to push:
# Make a commit
git add .
git commit -m "Add login feature"
# Realize there's a typo; amend the commit
git commit --amend -m "Add login feature (fix typo)"
# Normal push fails (history diverged)
git push origin main
# ! [rejected] main -> main (non-fast-forward)
# Force push to overwrite remote
git push --force origin main
# Enumerating objects: 5, done.
# Counting objects: 100% (5/5), done.
# ...
# To https://github.com/your/repo.git
# + a1b2c3d...e4f5g6h main -> main (forced update) When It’s Tempting to Use:#
- Rewriting commits in a personal branch (no collaborators).
- Cleaning up a messy commit history before merging to
main. - Undoing a previous push (e.g., accidental sensitive data exposure).
The Risks of Using git push --force#
While --force is powerful, it’s also dangerous. Here’s why:
1. Overwriting Teammates’ Work#
If a teammate pushed commits to the remote branch after you last pulled, --force will delete their commits. For example:
- Alice pushes commit
Atomain. - Bob pulls
A, works, and pushes commitB(remote now hasA → B). - Alice, unaware of
B, amendsAtoA'and runsgit push --force. - Remote
mainis nowA'—Bob’s commitBis erased!
2. Losing Critical History#
Force pushing replaces the remote’s history, making it hard to recover lost commits unless someone else has a local copy. Even with git reflog, recovery is time-consuming and error-prone.
3. Breaking CI/CD Pipelines#
If the remote branch is linked to CI/CD, overwriting history can invalidate builds, rollbacks, or deployments tied to old commit hashes.
What is git push --force-with-lease?#
git push --force-with-lease is a safer alternative to --force that prevents accidental overwrites of remote changes you haven’t fetched. It acts as a "conditional force push": it will only overwrite the remote branch if no one else has pushed new commits since you last fetched.
How It Works at a High Level:#
Before overwriting the remote, --force-with-lease checks: "Does the remote branch have any commits I don’t have locally?" If yes, it aborts the push. If no, it proceeds with the force push.
Example:#
Using the earlier typo scenario, but with --force-with-lease:
# Amend the commit (same as before)
git commit --amend -m "Add login feature (fix typo)"
# Safely force push with lease check
git push --force-with-lease origin main
# Enumerating objects: 5, done.
# ...
# To https://github.com/your/repo.git
# + a1b2c3d...e4f5g6h main -> main (forced update) If a teammate had pushed new commits in the meantime:
git push --force-with-lease origin main
# To https://github.com/your/repo.git
# ! [rejected] main -> main (stale info)
# error: failed to push some refs to 'https://github.com/your/repo.git'
# hint: Updates were rejected because the remote contains work that you do
# hint: not have locally. This is usually caused by another repository pushing
# hint: to the same ref. You may want to first integrate the remote changes
# hint: (e.g., 'git pull ...') before pushing again. Git detects the remote has new commits and blocks the push—saving you from overwriting your teammate’s work!
How --force-with-lease Works Under the Hood#
To understand --force-with-lease, we need to peek at how Git tracks remote branches.
Git’s "Lease" Check:#
When you clone or fetch a repository, Git stores the last known state of remote branches in your local .git directory (e.g., refs/remotes/origin/main). This is your "lease" on the remote branch’s history.
--force-with-lease compares your local tracking branch (e.g., origin/main) with the actual remote branch. If they match (no new commits on the remote), Git allows the force push. If they don’t (remote has new commits), it aborts.
Technical Deep Dive:#
- FETCH_HEAD: When you run
git fetch, Git records the remote’s latest commit hash inFETCH_HEAD. - Lease Validation:
--force-with-leaseusesFETCH_HEAD(or your local tracking branch) to check if the remote has changed. If the remote’s current commit hash matchesFETCH_HEAD, the lease is valid; otherwise, it’s "stale."
You can even customize the lease check (e.g., specify a specific expected commit hash), but the default behavior works for most cases.
Key Differences: --force vs --force-with-lease#
| Feature | git push --force | git push --force-with-lease |
|---|---|---|
| Safety | Unsafe: Overwrites remote unconditionally. | Safe: Overwrites only if remote is unchanged. |
| Remote Change Check | None. Ignores new remote commits. | Checks for new remote commits (via lease). |
| Behavior on Diverged Remote | Overwrites remote, deleting new commits. | Aborts push with an error. |
| Risk of Data Loss | High (especially in shared repos). | Low (prevents accidental overwrites). |
| Use Case | Personal repos with no collaborators. | Shared repos; any scenario with team members. |
When to Use Each Command#
Use git push --force Only If:#
- You’re the sole contributor to the repository/branch (no teammates will push to it).
- You’re absolutely certain no one has pushed changes since you last fetched (e.g., a private side project).
- You’ve explicitly coordinated with your team to overwrite the remote (e.g., rolling back a production incident).
Use git push --force-with-lease (Almost) Always:#
- Shared repositories: Even if you "think" no one else pushed,
--force-with-leaseadds a safety net. - Rewriting history in team branches: After rebasing or amending commits in a shared feature branch.
- As a default: Many developers alias
git push --force-with-leasetogit pushffor convenience (see Best Practices below).
Best Practices for Safe Force Pushing#
-
Always Fetch Before Force Pushing
Rungit fetch(orgit pull --rebase) to ensure you have the latest remote changes before using--forceor--force-with-lease. This minimizes the chance of divergence. -
Alias
--force-with-leasefor Convenience
Make--force-with-leaseyour go-to by setting a global alias:git config --global alias.pushf 'push --force-with-lease'Now you can run
git pushf origin maininstead of typing the full command. -
Communicate with Your Team
If you need to force push to a shared branch, announce it in your team chat (e.g., "Force pushing tofeature/loginto clean up commits—pull after 5 minutes!"). -
Avoid Force Pushing to Protected Branches
Most Git hosting platforms (GitHub, GitLab) let you mark branches likemainas "protected," blocking force pushes entirely. Use this to enforce safety. -
Consider Alternatives to Force Pushing
For shared branches,git revert(creates a new commit undoing changes) is often safer than rewriting history. Reserve force pushing for cases where history cleanup is critical.
Real-World Scenarios and Examples#
Scenario 1: Solo Project (Safe with --force, but --force-with-lease Still Better)#
You’re working alone on a personal blog repo. You amend a commit and want to push:
# Amend the commit
git commit --amend -m "Fix typo in about page"
# Push with --force (tempting, but unnecessary risk)
git push -f origin main
# Better: Use --force-with-lease (no downside here)
git push --force-with-lease origin main Even solo, --force-with-lease protects against accidental pushes if you forgot you fetched changes from another device (e.g., your laptop and desktop).
Scenario 2: Team Collaboration (Disaster with --force, Saved by --force-with-lease)#
- Team Setup: You and your teammate, Priya, are working on
feature/payment. - Timeline:
- Priya pushes commit
Atofeature/payment. - You pull
A, work, and push commitB(remote:A → B). - Priya, unaware of
B, amendsAtoA'(local history:A'). - Priya runs
git push --force: Remote becomesA', deleting your commitB. Disaster! - If Priya used
git push --force-with-lease: Git detectsB(which Priya doesn’t have), aborts the push, and Priya is prompted to pull first.
- Priya pushes commit
Scenario 3: Recovering from a Failed --force-with-lease#
If --force-with-lease aborts, here’s how to fix it:
# 1. Fetch the remote changes
git fetch origin feature/payment
# 2. Rebase your changes on top of the remote (to integrate new commits)
git rebase origin/feature/payment
# 3. Now push with --force-with-lease (safe, since you’ve integrated remote changes)
git push --force-with-lease origin feature/payment Conclusion#
Force pushing is a powerful tool for managing Git history, but it comes with significant risks—especially in collaborative environments. git push --force is an unconditional overwrite that can erase teammates’ work, while git push --force-with-lease adds a critical safety check to prevent accidental data loss.
The golden rule: Use --force-with-lease by default, reserve --force for solo projects, and always communicate with your team before force pushing to shared branches. With these practices, you’ll keep your repository history clean and your teammates happy.