KhueApps
Home/DevOps/Fixing Git non-fast-forward push rejections

Fixing Git non-fast-forward push rejections

Last updated: October 06, 2025

Overview

A non-fast-forward push rejection happens when your local branch is behind or diverged from the remote branch. Git refuses to update the remote because it would drop or overwrite remote commits.

Typical error:

! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
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.

Quickstart (most common fix)

  • You have local commits and remote is ahead. Rebase onto the remote, then push:
# Update tracking info
git fetch origin

# Rebase local work onto the latest remote commits
git rebase origin/main  # use your branch name

# If conflicts appear: resolve, then
git add <resolved-files>
git rebase --continue

# Push the linearized history
git push

If you previously rewrote history (e.g., git commit --amend or rebase) and need to update the remote, use a guarded force:

git push --force-with-lease

Minimal working example (reproduce and fix)

# Create a bare central repo
mkdir -p /tmp/git-nff && cd /tmp/git-nff
git init --bare central.git

# Clone it twice (simulate two developers)
git clone central.git alice && git clone central.git bob

# Alice adds a commit and pushes
cd alice
echo A > file.txt
git add file.txt
git commit -m "Alice: add file"
git push -u origin main 2>/dev/null || git push -u origin master  # pick default branch
BR=$(git symbolic-ref --short HEAD)
cd ..

# Bob makes a conflicting commit without fetching Alice's change
cd bob
echo B > file.txt
git add file.txt
git commit -m "Bob: add file"
# This push will be rejected as non-fast-forward
git push || true

# Fix: rebase Bob onto remote, then push
git fetch origin
git rebase origin/$BR
# Resolve conflicts if prompted, then continue
# Finally
git push

Choose a resolution strategy

SituationGoalCommand(s)Safe for shared branches
You want a linear historyReapply your commits on top of remotegit fetchgit rebase origin/<branch>git pushYes
You want a merge commitKeep both histories with a mergegit pull --no-rebase or git merge origin/<branch>git pushYes
Your local commits are disposableAlign local to remote, discard localgit fetchgit reset --hard origin/<branch>git push (no-op)Yes
You rewrote history locally (amend/rebase) and need remote to matchOverwrite remote safelygit push --force-with-leaseUsually; check policies
Branch is protected (no force, require up-to-date)Satisfy policiesgit fetchgit rebase or git mergegit pushYes

Step-by-step: rebase-based fix

  1. Ensure your working tree is clean.
  • Commit or stash changes.
  1. Fetch the latest remote state.
  • git fetch origin
  1. Rebase your local branch onto the remote.
  • git rebase origin/<branch>
  1. Resolve conflicts if any.
  • Open conflicted files, edit to keep desired changes.
  • git add <file> for each resolved file.
  • Continue: git rebase --continue.
  • To abort: git rebase --abort.
  1. Push.
  • git push
  • If you rewrote history earlier and the push still rejects, use git push --force-with-lease.

Step-by-step: merge-based fix

  1. git fetch origin
  2. git merge origin/<branch>
  3. Resolve conflicts → git add ...git commit
  4. git push

Use this when merges are acceptable or required by policy.

When to use --force-with-lease

Use only when your local history intentionally rewrites commits and you need the remote to match (e.g., after git rebase -i, git commit --amend). --force-with-lease protects against clobbering others’ new remote commits by ensuring your push only proceeds if the remote tip matches what you last fetched.

Examples:

# After interactive rebase on a feature branch
git push --force-with-lease origin feature/login

# Never use plain --force on shared branches

Handling special cases

  • Out-of-date branch protection: Some servers require “update branch before merging.” Do git pull --rebase before opening or updating PRs.
  • Protected main branch: Force pushes may be blocked. Use rebase or merge, not force.
  • Dropped remote commits (history rewrite upstream): You may need git fetch --prune then git rebase --rebase-merges or reset to the new tip after review.
  • Tags: Non-fast-forward tag updates are often blocked. Create a new tag or delete+recreate intentionally: git tag -d v1.2 && git push origin :refs/tags/v1.2 && git tag v1.2 <commit> && git push --tags (coordinated with policy).
# Prefer rebase for pull (linear history)
git config --global pull.rebase true

# Auto-stash before rebase to avoid dirty tree issues
git config --global rebase.autoStash true

# Prune deleted remote branches on fetch
git config --global fetch.prune true

Pitfalls to avoid

  • Using --force instead of --force-with-lease on shared branches.
  • Rebasing public branches others already based work on, causing duplicate/conflicting histories.
  • Ignoring conflicts during rebase/merge; always inspect and test before pushing.
  • Working with an unclean tree; stash or commit first to avoid losing changes.
  • Mixing merge and rebase strategies haphazardly; agree on a team policy per branch.

Performance notes

  • Fetch depth and partial clone: For large repos, git fetch --depth=50 or git clone --filter=blob:none can speed up syncs, but be cautious when rebasing deep histories.
  • Prune and GC: Periodically run git fetch --prune and git gc --aggressive locally to reduce object store size; on servers, admins may tune GC schedules.
  • Pack/delta tuning: Pushing many small commits is fine, but squashing (when allowed) can reduce push size and CI load.
  • Large files: Use Git LFS for binaries to avoid heavy pushes that exacerbate non-FF conflicts on large objects.

Troubleshooting checklist

  • Did you run git fetch origin and target the correct branch?
  • Do you actually want a merge (git merge) instead of rebase?
  • Are there branch protections blocking force pushes?
  • Did someone rewrite remote history? Compare: git log --oneline --graph --decorate --all.
  • Do you need --force-with-lease due to local history edits?

FAQ

  • What is a fast-forward? When the remote tip is an ancestor of your local tip; Git can move the pointer without creating a merge.
  • What causes non-fast-forward? Divergent histories: both sides have commits the other does not.
  • Rebase or merge? Rebase for linear history; merge to preserve original commit topology.
  • How do I undo a bad rebase? Use git reflog to find the previous HEAD, then git reset --hard <hash>.
  • How do I avoid this long-term? Regularly git fetch and rebase small batches before pushing; align with branch protection policies.

Series: Git

DevOps