The error Updates were rejected because the remote contains work that you do not have locally (a non-fast-forward error) means someone pushed commits to the remote branch that you don’t have. Git blocks your push to prevent overwriting their work. Here’s how to resolve it safely.
📋 Table of Contents
What This Error Means
$ git push origin main
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the remote contains work that
hint: you do not have locally. This is usually caused by another
hint: repository pushing to the same ref.
Someone (or you from another machine) pushed commits to the remote branch after you last pulled. Your local branch and the remote have diverged. Git won’t let you push because it would discard their commits.
Fix 1: Pull, Then Push (Merge)
# Fetch and merge the remote changes into your local branch
git pull origin main
# This creates a merge commit combining their work and yours.
# Resolve any conflicts if they occur, then:
git push origin main
This is the safe default. It integrates the remote commits with yours via a merge commit, then your push succeeds.
Fix 2: Pull with Rebase (Cleaner History)
# Replay YOUR commits on top of the remote's - linear history, no merge commit
git pull --rebase origin main
# If conflicts occur during rebase, resolve them, then:
git add
git rebase --continue
# Then push
git push origin main
Rebasing gives a cleaner, linear history without a merge commit. Many teams prefer this. Your commits are replayed on top of the latest remote work.
Fix 3: Fetch and Review First (Careful Approach)
# See what's on the remote before integrating
git fetch origin
# Compare your branch with the remote
git log HEAD..origin/main # commits on remote you don't have
git log origin/main..HEAD # your commits not on remote
# Then choose to merge or rebase
git merge origin/main # or: git rebase origin/main
git push origin main
Set a Default Pull Strategy
# Configure how git pull behaves (avoids the "divergent branches" warning)
# Merge (default) - creates merge commits
git config --global pull.rebase false
# Rebase - linear history
git config --global pull.rebase true
# Fast-forward only - fail if not possible (forces you to decide)
git config --global pull.ff only
When You’re SURE You Want to Overwrite (Dangerous)
# ⚠️ ONLY if you're certain the remote commits should be discarded
# (e.g., your own feature branch nobody else uses)
# --force-with-lease is safer than --force:
# it fails if the remote changed since your last fetch
git push --force-with-lease origin my-feature-branch
# ❌ NEVER force push to shared branches (main, develop)
# It permanently deletes other people's commits
git push --force origin main # DON'T do this on shared branches
Warning: Force pushing to a shared branch destroys other people’s commits. Only force push to your own feature branches that nobody else works on, and prefer --force-with-lease.
Understanding the Difference
# Before: your local and remote have diverged
# A---B---C (your local main)
# # D---E (remote main - someone else's commits)
# git pull (merge) creates:
# A---B---C---F (merge commit)
# \ /
# D---E
# git pull --rebase creates (cleaner):
# A---B---D---E---C' (your C replayed on top)
Frequently Asked Questions
Q: Should I merge or rebase to fix this?
A: Merge (git pull) is the safe default and preserves exact history. Rebase (git pull –rebase) gives cleaner linear history. For shared branches, either works; for your own feature branches before a PR, rebase keeps history tidy. Follow your team’s convention.
Q: Why can’t I just force push?
A: Force pushing to a shared branch permanently deletes the commits others pushed — you’d lose their work. Only force push (with –force-with-lease) to your own feature branches that nobody else uses. Never force push main or develop.
Q: What does –force-with-lease do that –force doesn’t?
A: --force-with-lease checks that the remote hasn’t changed since your last fetch — if someone pushed in the meantime, it fails instead of overwriting. It’s a safety net that prevents accidentally destroying commits you haven’t seen. Always prefer it over plain –force.
Q: I got merge conflicts after pulling. What now?
A: Open the conflicted files, resolve the conflicts (choose which changes to keep), then git add the resolved files. For a merge, git commit. For a rebase, git rebase --continue. Then push.
Q: How do I avoid this error in the future?
A: Pull before you start working and before you push. Communicate with your team about who’s working on what. On active shared branches, pull frequently to stay current and minimize divergence.
Conclusion
The non-fast-forward push rejection means the remote has commits you don’t have locally — Git protects those commits by blocking your push. The fix is simple: pull the remote changes first (git pull to merge, or git pull --rebase for cleaner history), resolve any conflicts, then push. Never force push to shared branches — it destroys others’ work. If you must overwrite your own feature branch, use --force-with-lease (safer than --force). To avoid this error, pull frequently and communicate with your team. Understanding that Git is protecting commits you haven’t seen makes the fix intuitive: integrate the remote work, then push.
📚 You might also like
🔗 Share this article




✍️ Leave a Comment