Troubleshooting Git Error: rejected non-fast-forward push (Conflicts Branch) in WSL2 Ubuntu

Resolve the common Git 'rejected non-fast-forward' push error when working with WSL2 Ubuntu. This guide details causes and step-by-step solutions to safely synchronize your local and remote repositories.


Resolve the common Git 'rejected non-fast-forward' push error when working with WSL2 Ubuntu. This guide details causes and step-by-step solutions to safely synchronize your local and remote repositories.

When working with Git repositories within a Windows Subsystem for Linux 2 (WSL2) Ubuntu environment, it's common to encounter the "rejected non-fast-forward" error during a git push operation. This error indicates that the remote branch has diverged from your local branch, preventing a direct update without explicitly resolving the differing histories. This guide will walk you through understanding, diagnosing, and resolving this prevalent Git issue.

Symptom & Error Signature

You attempt to push your local changes to a remote Git repository, and instead of a successful update, your terminal displays an error similar to this:

$ git push origin main
To https://github.com/your-org/your-repo.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'https://github.com/your-org/your-repo.git'
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. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

This output clearly states the push was rejected because the remote main branch (or whatever your branch name is) has commits that are not present in your local main branch, making it a non-fast-forward update.

Root Cause Analysis

The "non-fast-forward" error signifies that your local branch's history has diverged from its corresponding remote branch. Git is designed to prevent unintentional overwrites of work, so it rejects pushes that would "lose" remote history. Here are the primary reasons for this divergence:

  1. Remote Updates: The most common reason is that another developer (or an automated process like a CI/CD pipeline) has pushed new commits to the same branch on the remote repository since you last pulled. Your local branch is now "behind" the remote.
  2. Local History Rewrite: You have rewritten your local branch's history using commands like git rebase, git commit --amend, git reset --hard followed by new commits, or git filter-branch. If these changes affect commits that have already been pushed to the remote, your local history no longer aligns linearly with the remote, making a fast-forward push impossible.
  3. Multiple Sources: You might be working on the same branch from different machines (e.g., your WSL2 environment and a separate Windows Git client) and pushed from one without pulling on the other.
  4. Local Branch Deletion and Recreation: In rare cases, if you deleted your local branch and recreated it from a different point, or if the remote branch was reset, this can cause a non-fast-forward situation.

In essence, Git sees that your local branch A and the remote branch B both share a common ancestor C, but A has new commits X and B has new commits Y, and X is not a direct descendant of Y, nor is Y a direct descendant of X.

      A -- X  (Your local branch)
     /
C --
     
      B -- Y  (Remote branch)

A fast-forward push would only be possible if your local branch X was a direct descendant of Y, meaning your local branch contained Y plus new commits on top.

Step-by-Step Resolution

The resolution strategy depends on whether you want to integrate the remote changes into your local history (merge/rebase) or if you are intentionally overwriting the remote (force push).

1. Fetch Latest Remote Changes

Before doing anything, always fetch the latest state of the remote repository to see what you're dealing with. This updates your remote-tracking branches (e.g., origin/main).

git fetch origin

Now you can see the difference:

git log --oneline --graph --all

This command will show you the commit history for all branches, including remote-tracking branches, helping you visualize the divergence. Alternatively, you can compare specific branches:

git log HEAD..origin/main

This shows commits on origin/main that are not on your HEAD (local branch).

2. Option A: Integrate Remote Changes Using git merge (Recommended for Shared Branches)

This is the safest and most common approach. It integrates the remote changes into your local branch by creating a new merge commit.

a. Ensure Your Working Directory is Clean
git status

If you have uncommitted changes, either commit them (git add . && git commit -m "WIP: My current work") or stash them (git stash save "My unsaved work").

Merging with uncommitted changes can lead to conflicts that are harder to resolve. Always commit or stash your work first.

b. Pull the Remote Changes
git pull origin main
# Or simply:
# git pull

The git pull command is a shorthand for git fetch followed by git merge FETCH_HEAD. It will fetch the remote main branch and then merge it into your local main branch.

c. Resolve Merge Conflicts (If Any)

If there are conflicts (i.e., the same lines of code were modified differently in both your local branch and the remote branch), Git will pause the merge and prompt you to resolve them.

# Example output during a merge conflict
Auto-merging src/App.js
CONFLICT (content): Merge conflict in src/App.js
Automatic merge failed; fix conflicts and then commit the result.
  • Open the conflicting files in your editor (VS Code works well with WSL2).
  • Look for conflict markers (<<<<<<<, =======, >>>>>>>).
  • Edit the files to combine the changes as desired.
  • Once resolved, stage the changes:
git add .
  • Commit the merge:
git commit -m "Merge remote-tracking branch 'origin/main'"
# Git usually pre-populates a sensible message for you.
d. Push Your Integrated Changes

After resolving conflicts and committing the merge, you can now push your local branch, which now contains the remote's history, in a fast-forward manner:

git push origin main

3. Option B: Integrate Remote Changes Using git rebase (For Cleaner History)

Rebasing reapplies your local commits on top of the remote branch's history, creating a linear history without merge commits. This is often preferred for feature branches before merging them back into main.

Do NOT rebase branches that have already been pushed to a shared remote, unless you are absolutely certain no one else has pulled those commits. Rebasing rewrites history, which can cause significant problems for collaborators. This option is generally best for personal feature branches before they are shared widely, or if you are the sole contributor to the branch.

a. Ensure Your Working Directory is Clean
git status

Commit or stash any uncommitted changes.

b. Rebase Your Local Branch
git pull --rebase origin main
# Or equivalently:
# git fetch origin
# git rebase origin/main

This command fetches the latest remote main and then attempts to reapply your local commits on top of it.

c. Resolve Rebase Conflicts (If Any)

If conflicts occur during the rebase, Git will pause and tell you which commit introduced the conflict.

  • Resolve the conflicts in the specified files.
  • Stage the changes:
git add .
  • Continue the rebase:
git rebase --continue

Repeat this process for any subsequent conflicts until the rebase is complete.

  • If you get stuck or change your mind:
git rebase --abort

This will return your branch to its state before the rebase started.

d. Push Your Rebased Changes

After a successful rebase, your local history is now a direct descendant of the remote. You can push normally:

git push origin main

If you rebased a branch that was already pushed to the remote (which is generally discouraged), you might need to force push:

> [!IMPORTANT]
> If you have truly rebased a branch that had commits previously pushed, you will need to use `git push --force-with-lease`. Use this with extreme caution. See Option C below.

4. Option C: Force Push (git push --force-with-lease) (Use with Extreme Caution!)

This option overwrites the remote branch with your local branch's history, effectively deleting any commits on the remote that are not present locally.

Never use git push --force on shared branches like main or develop unless you are absolutely certain about what you are doing and have communicated with your team. This can destroy other people's work and break repositories.

Use git push --force-with-lease instead of git push --force. force-with-lease is a safer alternative because it only forces the push if the remote branch hasn't been updated since you last fetched it. This prevents you from accidentally overwriting changes made by others in the interim.

When to Use Force Push:
  • You are working on a private branch that no one else has pulled.
  • You are the sole maintainer of a repository and intentionally want to reset a branch.
  • You've rebased a personal feature branch and want to update its remote counterpart without creating a merge commit.
Steps for Force Push:
git push --force-with-lease origin main

5. Prevention: Configure git pull for Rebasing by Default

To avoid merge commits and maintain a cleaner, linear history on your local feature branches, you can configure Git to always rebase when you git pull.

git config --global pull.rebase true

With this setting, git pull will automatically act as git pull --rebase. If you later need to perform a merge instead, you can explicitly run git pull --no-rebase.

Conclusion

The "rejected non-fast-forward" error is a fundamental Git safety mechanism. By understanding its root causes and applying the appropriate resolution strategy—whether merging, rebasing, or in rare cases, force-pushing—you can maintain a clean, synchronized, and collaborative workflow within your WSL2 Ubuntu development environment. Always prioritize integrating remote changes (git pull or git pull --rebase) to ensure you're building on the latest shared history.