Git Non-Fast-Forward Rejection: Safe Integration Before Publishing
Understand why ordinary pushes are rejected when remote history has diverged, and verify safe fetch, merge, rebase, and force-with-lease boundaries before publishing again.
Intermediate
A non-fast-forward rejection means the remote tip is outside the local history being proposed
For an existing remote branch, an ordinary push is allowed when the remote tip is an ancestor of the value being pushed. If the remote branch has advanced along history that the local branch does not contain, Git rejects the update rather than replacing that remote history with the local tip. The rejection is a ref-update safety boundary, not evidence that the objects are missing.
git fetch origin
git rev-parse HEAD
git rev-parse refs/remotes/origin/main
git merge-base --is-ancestor refs/remotes/origin/main HEADQuick check
What does a non-fast-forward push rejection establish?
The remote and local histories have diverged.The push is rejected until the histories are reconciled or an intentional ref replacement is explicitly performed.
Do not integrate from a stale remote-tracking ref
A rejected push is a signal to obtain the current remote state before choosing an integration strategy. Run fetch and inspect the resulting remote-tracking ref. Fetch changes locally available remote state; it does not merge the remote history into the current branch.
git fetch origin
git rev-parse refs/remotes/origin/main
git log --oneline --decorate --graph --all -6Quick check
Why fetch after a rejection before deciding how to integrate?
The local origin/main value is stale.After fetch, origin/main identifies the remote state that must be considered.
Merge preserves both divergent lines in a merge commit
One reconciliation path is to merge the fetched remote-tracking branch into the current branch. If both sides contain commits not reachable from the other, the result can be a merge commit with both tips as parents. Once that merge commit contains the remote tip as an ancestor, an ordinary push can advance the remote branch fast-forward.
git fetch origin
git merge --no-edit origin/main
git log --oneline --decorate --graph -6
git push origin mainQuick check
Why can a merge make the next push fast-forwardable?
Local and remote histories have diverged.The merge result joins both histories, allowing the remote ref to advance to the merge commit.
Rebase can replay local commits on top of the fetched remote tip
Another reconciliation path is to rebase the local commits onto the fetched remote-tracking tip. Rebase changes the local commit identities by replaying them on a different base. After a successful rebase, the remote tip is an ancestor of the rewritten local tip, so the next push can be a fast-forward. Detailed rebase workflows belong to Phase 7; this lesson owns only the safe integration boundary needed to recover from a rejected push.
git fetch origin
git rebase origin/main
git log --oneline --decorate --graph -6
git push origin mainQuick check
What is the defining history effect of rebasing after a non-fast-forward rejection?
The local commits are based on an older remote tip.The local commits are replayed after the fetched remote tip, making the remote tip an ancestor of the rewritten local tip.
A successful integration is not the same as publishing it
Merge and rebase only change the local repository. The remote branch does not move until a subsequent push succeeds. This separation matters when diagnosing a rejected push: first obtain remote state, then integrate locally, then publish the resulting ref.
git rev-parse HEAD
git rev-parse refs/remotes/origin/main
git push --dry-run origin main
git rev-parse refs/remotes/origin/mainQuick check
Which operation actually changes the remote branch after local integration?
The local repository has already been reconciled.The remote branch changes only when the push successfully updates its ref.
Force-with-lease is an explicit history-replacement boundary, not the normal repair path
If history was intentionally rewritten and the remote ref must be replaced, Git provides `--force-with-lease`. The explicit `--force-with-lease=<refname>:<expect>` form requires the remote ref to still equal the expected object ID; otherwise the push fails. This is different from treating a non-fast-forward rejection as permission to overwrite remote history blindly. The normal recovery path is fetch, inspect, integrate, and push. Use an explicit lease only when replacing the remote history is itself intentional.
expected=$(git rev-parse refs/remotes/origin/main)
git push --force-with-lease=main:$expected origin mainQuick check
What safety property does `--force-with-lease=main:<expect>` add?
The expected remote tip was recorded before the intentional rewrite.The push succeeds only if the remote ref still has that expected value.
Use the decision sequence instead of guessing
Treat the rejection as information about repository state. First fetch. Second inspect the remote tip and the local tip. Third choose whether the intended result is a merge, a rebase, or an explicitly authorized history replacement. Fourth verify the resulting ancestry. Finally push. If the integration itself conflicts, enter the merge or rebase workflow instead of forcing the ref.
git fetch origin
git rev-parse HEAD
git rev-parse refs/remotes/origin/main
# choose and complete the intended local integration
git merge-base --is-ancestor refs/remotes/origin/main HEAD
git push origin mainPractice what you learned
Use the dedicated Git code-completion exercise for this lesson.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.