Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

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.

Levels: Intermediate ~45 min read

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.

bash
git fetch origin
git rev-parse HEAD
git rev-parse refs/remotes/origin/main
git merge-base --is-ancestor refs/remotes/origin/main HEAD

Quick 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.

bash
git fetch origin
git rev-parse refs/remotes/origin/main
git log --oneline --decorate --graph --all -6

Quick 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.

bash
git fetch origin
git merge --no-edit origin/main
git log --oneline --decorate --graph -6
git push origin main

Quick 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.

bash
git fetch origin
git rebase origin/main
git log --oneline --decorate --graph -6
git push origin main

Quick 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.

bash
git rev-parse HEAD
git rev-parse refs/remotes/origin/main
git push --dry-run origin main
git rev-parse refs/remotes/origin/main

Quick 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.

bash
expected=$(git rev-parse refs/remotes/origin/main)
git push --force-with-lease=main:$expected origin main

Quick 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.

bash
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 main

Practice what you learned

Use the dedicated Git code-completion exercise for this lesson.

Was this page helpful?

Mark this learning resource complete to track your learning progress.