Skip to main content
DevOps Insightsguides & answers

Git · troubleshooting

Push rejected as non-fast-forward

The remote branch has commits yours is not built on, so accepting your push would drop them. Git refuses by default. The fix is almost never --force — it is to incorporate what is already there, and where history genuinely was rewritten, --force-with-lease --force-if-includes refuses to overwrite work that arrived while you were not looking.

Reproduced on Git 2.50, . Where the text says “reproduced”, the behaviour was run on that version, not taken from documentation.

First published

New to this? Read what is actually happening first, then come back to the steps.

On this page · 10 sections

Before you start

  • Read access to the remote, so you can run git fetch before deciding anything.
  • Push access to the branch. A forced push also has to be allowed by the server: one with receive.denyNonFastForwards set refuses forced non-fast-forward updates, and a protected branch on your Git host may refuse them too.
  • A clean working tree before git pull --rebase. With uncommitted changes, Git stops with error: cannot pull with rebase: You have unstaged changes.
  • Git 2.30 or later if you need --force-if-includes, which arrived in that release.
  • Agreement from everyone else who uses the branch before you overwrite its history.

Run this first

step 1 of 4
git fetch origin

Look for Updated refs, or nothing — both are informative

Everything below compares against remote-tracking refs, and those are only as current as your last fetch. Doing this first stops you diagnosing a stale picture.

Work out which cause you have

A few questions to narrow the list. Every answer ends in a command that confirms or rules the cause out — this cannot see your cluster, so nothing here is a certainty until you have checked.

Narrow it down

0 answered · nothing is sent anywhere

After fetching, whose commits are on the remote that you do not have?

git fetch origin && git log --oneline --graph --left-right HEAD...origin/<branch>

Cause space

3 of 3 still possible

  • Somebody pushed while you were workingCommon
  • You rewrote commits that were already pushedCommon
  • You are pushing from a stale clone or at the wrong branchOccasional

Nothing ruled out yet. Answer the question above and the branches your answer eliminates will strike through here.

Check it with a tool

Or diagnose it manually

In this order. The first command usually contains the whole answer.

  1. Step 1

    git fetch origin

    Everything below compares against remote-tracking refs, and those are only as current as your last fetch. Doing this first stops you diagnosing a stale picture.

    Look for Updated refs, or nothing — both are informative

  2. Step 2

    git log --oneline --graph --left-right HEAD...origin/<branch>

    Shows both sides of the divergence at once. This single command separates the two main causes.

    Look for Different subjects on the remote side means somebody else pushed; the same subjects with different hashes means you rewrote history

  3. Step 3

    git rev-parse --abbrev-ref HEAD && git rev-parse --abbrev-ref --symbolic-full-name @{u}

    Confirms which branch you are on and which upstream it tracks, before any history is changed. @{push} shows where a plain push goes, if that differs.

    Look for The branch you expected, tracking the remote branch you expected

  4. Step 4

    git reflog --date=relative | head -20

    If a rebase or amend happened, it is recorded here with timestamps — including rewrites you did not mean to perform.

    Look for rebase, amend or reset entries since your last successful push

Every cause, and how to fix it

Ordered by how often each one turns out to be the answer.

Somebody pushed while you were working

Common

The remote branch has commits yours does not contain. Your own history is untouched and correct — it is simply based on an older tip. This is the ordinary case on any shared branch and needs no rewriting of anything.

Confirm

git fetch origin && git log --oneline HEAD..origin/<branch>

Commits listed here are on the remote and not in your branch. If this list is non-empty and the reverse is too, the branches have diverged

Fix

  • Fetch, then bring their work into yours — rebase for a linear history, merge if your project prefers to preserve the shape of what happened.
  • Resolve any conflicts, then push normally. No force is involved or needed.
  • If you rebase a branch you have already pushed and others may have pulled, you are converting this into the second cause for them — which is a reason to prefer merge on shared branches.
git pull --rebase origin <branch> && git push origin <branch>

Replays your commits on top of theirs. --no-rebase merges instead, which is equally valid and leaves a merge commit. With neither the option nor pull.rebase set, current Git refuses to pull diverged branches and asks you to choose.

You rewrote commits that were already pushed

Common

git commit --amend, an interactive rebase, or a squash gives commits new hashes. The old ones are still what the remote points at, so your branch is no longer a descendant of it even though the content may be nearly identical. The giveaway is that the remote's extra commits are yours, under different hashes.

Confirm

git log --oneline --graph --left-right HEAD...origin/<branch>

Commits on both sides with the same subjects but different hashes — that is a rewrite, not somebody else's work

Fix

  • This is the case where overwriting is correct, because the remote commits are superseded versions of your own.
  • Use --force-with-lease rather than --force, so the push still fails if anything arrived since your last fetch.
  • Add --force-if-includes. A lease only compares against your last fetch, so a fetch that brings in a teammate's commit, yours or one your editor ran in the background, quietly turns the lease into a plain force. --force-if-includes refuses unless the remote tip is reachable from an entry in your local branch's reflog, that is, unless your branch has contained it at some point. Your own amended or rebased commits pass; a teammate's commit you only fetched does not. Reproduced on Git 2.50: after a fetch, --force-with-lease alone overwrote a teammate's commit; with --force-if-includes the push was rejected.
  • On a shared branch, tell whoever else has it — their clone now has the old commits and they will hit this same error from the other direction.
git fetch origin
git log --oneline HEAD..origin/<branch>
git push --force-with-lease --force-if-includes origin <branch>

The log should list only your own superseded commits; anything else is someone's work to integrate first. Plain --force would overwrite it without checking, and a lease alone stops checking once a fetch has brought it in.

You are pushing from a stale clone or at the wrong branch

Occasional

A long-lived working copy, a CI checkout that fetched once, or a push whose refspec names a different branch than you think. The ancestry check is doing its job against a ref you did not mean to move, and the diverged history is real but irrelevant.

Confirm

git rev-parse --abbrev-ref HEAD && git rev-parse --abbrev-ref --symbolic-full-name @{u}

The local branch and the upstream it tracks. @{u} is where git pull reads from; add git rev-parse --abbrev-ref @{push} to see where a plain git push sends it. A surprise on any line is the answer

Fix

  • Fetch so the remote-tracking refs are current — a clone that has not fetched is comparing against a snapshot.
  • Push explicitly with git push origin <branch>:<branch> when the tracking configuration is in doubt.
  • In CI, check the checkout depth. A shallow clone can be missing the common ancestor. With a --depth=1 clone and a --depth=1 fetch, git merge-base found nothing and git pull --no-rebase stopped with fatal: refusing to merge unrelated histories.

Understanding it properly

Skip this if you are mid-incident — the working part of the page is above. Worth reading afterwards, because understanding the mechanism is what stops the next one.

In plain terms

Git refused to update the branch on the server because the server has commits that your branch does not include. Accepting your push would throw those commits away, so Git stops instead. Usually the fix is to fetch the new commits, combine them with yours, and push again.

Two clones, one branch

Reproduced with Git 2.50.1 (Apple Git-155) on macOS, so Git 2.50: a local bare repository and two clones of it, with no network and global and system config switched off. One clone pushed a commit; the other, which had not fetched, committed and pushed, then fetched and pushed again. The repository path is replaced with <path>.

Command

git push origin main
git fetch -q origin
git push origin main

Output

$ git push origin main
To <path>/origin.git
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to '<path>/origin.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

$ git fetch -q origin
$ git push origin main
To <path>/origin.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to '<path>/origin.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

How to read it

  • (fetch first) means your clone has never seen the remote's new commit. After git fetch the same push says (non-fast-forward): your clone now has the commit, and your branch still does not contain it. The hint text changes with it.
  • ! and [rejected] mean Git did not try to send the ref, so nothing changed on the remote and there is nothing to undo.
  • After the fetch, git status said Your branch and 'origin/main' have diverged, and have 1 and 1 different commits each, respectively. and git log --oneline --left-right HEAD...origin/main marked the teammate's commit > and yours <.
  • A plain git pull then stopped with fatal: Need to specify how to reconcile divergent branches. git pull --rebase origin main and git push origin main worked, and the push printed cb196f3..6bf43f6 main -> main.
  • In the same setup, after you amend a pushed commit and a teammate pushes, --force-with-lease without a fetch refused with (stale info). After a fetch, the lease alone overwrote the teammate's commit; adding --force-if-includes refused with (remote ref updated since checkout).

Words used on this page

Remote
The other copy of the repository that you push to and fetch from. origin is the name git clone gives the one you cloned.
Remote-tracking branch
Your local record of where a remote branch was at your last fetch, such as origin/main. It only moves when you fetch or pull.
Fast-forward
Moving a branch to a commit that already contains its current tip. Nothing is lost, so Git allows it without force.
Diverged
Both sides have commits the other lacks. git status reports how many on each side.
Rebase
Replaying your commits on top of another tip. The commits get new hashes, which is why rebasing pushed work causes this error for others.
--force-with-lease
A forced push that goes ahead only if the remote branch is still where your remote-tracking branch says it is.
--force-if-includes
An extra check for --force-with-lease: the remote tip must be reachable from an entry in your local branch's reflog, meaning your branch has actually contained it.
Reflog
Git's local record of every position a branch has had, including commits you later amended or rebased away.
What is actually happening

Git will only move a remote branch forward along its own history. The documentation states the rule directly: push "refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it". If the remote tip is not reachable from your commit, moving the branch to yours would make those commits unreferenced — so the push is refused rather than silently losing them.

The refusal is reported per ref. In push output, ! marks "a ref that was rejected or failed to push", and the status word rejected means Git did not even try to send it. Nothing was sent for that ref and it did not move on the remote (other refs in the same push may still have been updated, unless you pushed with --atomic), which is worth knowing before you start trying to undo things.

There are only two ways to get here, and they need different responses. Either somebody else pushed while you were working — your history is fine, it is just behind — or you rewrote commits you had already published, with amend, rebase or squash, so your branch is no longer a descendant of what the remote holds. The error is identical; the fix is not.

For the first case, the answer is to incorporate the remote work and push the result. For the second, you genuinely do need to overwrite, and that is the only situation where forcing is correct. Reaching for --force in the first case is how a teammate's commits disappear: the documentation is blunt that if somebody built on your original history, "blindly pushing with --force will lose their work".

--force-with-lease is the version worth having in your fingers. It overrides the ancestry rule "if the current value of the remote ref is the expected value" and fails otherwise — described in the docs as taking a lease on the ref without locking it. If anything arrived since your last fetch, the lease is stale and the push is refused, which is exactly the protection plain --force discards. The catch is the fetch itself: one that brings in a teammate's commit refreshes the lease, so pair it with --force-if-includes.

How to tell this is your problem
WhereWhat you see
git push output! [rejected] <branch> -> <branch> (non-fast-forward), or (fetch first)
git push outputerror: failed to push some refs to '<remote>'
git statusYour branch and 'origin/<branch>' have diverged, with counts on both sides
git log --oneline --graph HEAD origin/<branch>Two lines of history that split at a common ancestor rather than one line

How to know it is actually fixed

  • git push completes with no ! line and the ref shown moving from the old to the new value.
  • git status reports the branch up to date with its upstream, rather than diverged.
  • The commits you expected are visible on the remote, and — in the rewrite case — the ones you replaced are gone rather than both sets being present.
  • If a teammate shares the branch, they can fetch and see a sensible history rather than a second copy of your commits.

Stopping it happening again

  • Fetch before you start work on a shared branch, not only when a push fails. Most of these are a push against a tip that moved hours ago.
  • Make --force-with-lease --force-if-includes the default force in your fingers and your aliases, or set push.useForceIfIncludes. It costs nothing when you are right and refuses when you are about to overwrite somebody, even after a background fetch.
  • Avoid rewriting commits that have already been pushed to a shared branch. Rewrite freely on your own branch before it is published, which is where rebase is genuinely useful.
  • If the branch is protected on the host, expect a different rejection with its own message. A branch-protection refusal is not an ancestry problem and no amount of fetching or forcing will clear it.

Security considerations

  • A forced push removes commits from the remote branch for everyone, and nobody is told. In the reproduction the teammate's commit simply vanished from the remote's log; only their own clone still had it.
  • Leave server-side protection on. receive.denyNonFastForwards and branch protection on your Git host exist to stop exactly this overwrite, and switching them off to get one push through removes the protection for everybody.
  • --force applies to every ref a push sends. With push.default set to matching, or several push refspecs configured, it can overwrite branches other than the one you meant. git-push(1) advises forcing one branch with +<branch> in the refspec instead.
  • Read what you are about to integrate with git log -p HEAD..origin/<branch> before you rebase or merge. Those commits become part of the code you build and run next.

Sources

Behaviour described here is drawn from official documentation. Where a figure could not be confirmed on an official page it is attributed in the text rather than stated as canonical.

Did this get you to an answer?

No text box on purpose — please do not paste production logs anywhere

Something wrong or out of date? Report it