Git & CI/CD Intermediate

Git Push Rejected Non-Fast-Forward: Resolving Branch Conflicts on Ubuntu 22.04 LTS

Resolve 'rejected non-fast-forward' Git push errors on Ubuntu 22.04 LTS. This guide covers root causes and step-by-step solutions for divergent branch histories.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Resolve 'rejected non-fast-forward' Git push errors on Ubuntu 22.04 LTS. This guide covers root causes and step-by-step solutions for divergent branch histories.

When working with Git, encountering the "rejected non-fast-forward" error during a git push operation is a common scenario, especially in collaborative environments or after performing local history rewrites. This error indicates that your local branch history has diverged from its remote counterpart, meaning the remote repository contains commits that are not present in your local branch. Git, by default, prevents such a push to avoid inadvertently overwriting or losing remote work.

This guide, crafted by an experienced SysAdmin and DevOps engineer, will walk you through the precise symptoms, delve into the underlying causes, and provide robust, step-by-step resolutions to get your codebase synchronized efficiently and safely on an Ubuntu 22.04 LTS system.

Symptom & Error Signature

The most common manifestation of this issue is a specific error message displayed in your terminal after attempting to push your local changes to a remote repository.

$ git push origin main
To github.com/your-org/your-repo.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'github.com/your-org/your-repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

The key phrase here is ! [rejected] ... (non-fast-forward). This explicitly tells you that Git cannot simply "fast-forward" the remote branch to your local branch's state because your local branch lacks some history present on the remote.

Root Cause Analysis

The "rejected non-fast-forward" error fundamentally occurs when your local branch's commit history has diverged from the remote branch's history, preventing a straightforward fast-forward update. Here are the primary underlying reasons:

  1. Remote Updates Not Reflected Locally: This is the most common cause. Another developer (or an automated process like a CI/CD pipeline) has pushed new commits to the same remote branch while you were working. Your local branch is now "behind" the remote, and your push would erase their changes if allowed to fast-forward.
  2. Local History Rewrites: You have performed operations that rewrite your local branch's history after it was last synchronized with the remote. Examples include:
    • git rebase: Re-applies your commits on top of another base, changing their SHA-1 hashes.
    • git commit --amend: Modifies the last commit, creating a new commit object.
    • git reset --hard or git reset --soft: Moves your branch pointer to an earlier commit, potentially discarding commits that were pushed or locally rebased.
  3. Pushing to Protected Branches with Restricted Policies: Many Git hosting platforms (GitHub, GitLab, Bitbucket) allow branch protection rules. These rules can prevent pushes that are not fast-forward merges, often requiring changes to come through pull requests or merge requests. While the non-fast-forward error is generic, it often arises in this context.
  4. Misuse of git revert: If you git revert a commit that was already pushed to the remote, and then later try to push a branch that does not contain this revert commit but has other new changes, you might encounter issues if the history diverged in a way that Git can't fast-forward.

Git's default behavior is to protect the remote history. It will only allow a push if the remote branch can be directly moved forward to include your new commits without losing any existing remote commits. When this isn't possible, it rejects the push, prompting you to integrate the remote changes first.

Step-by-Step Resolution

Resolving the "rejected non-fast-forward" error typically involves integrating the remote changes into your local branch before attempting to push again. There are several strategies, each with its own implications for your branch's commit history.

1. Understand the State of Your Repository

Before attempting any resolution, it's crucial to understand where your local and remote branches stand.

  • Check local status:

    git status
    

    This shows uncommitted changes, staged changes, and if your branch is ahead/behind the remote.

  • Fetch latest remote changes without merging:

    git fetch origin
    

    This updates your local view of the remote branches (origin/main, origin/develop, etc.) without altering your local working branch.

  • Compare local and remote branches:

    git log --oneline --graph --decorate origin/main..HEAD # Shows commits only on your local 'main'
    git log --oneline --graph --decorate HEAD..origin/main # Shows commits only on 'origin/main'
    

    These commands help visualize the divergent paths.

  • View a graphical history (if gitk or a GUI is installed):

    git log --oneline --graph --all
    

    This can often visually clarify the branching structure.

2. Option A: Integrate Remote Changes with git pull --rebase (Recommended for Clean History)

This is often the preferred method for feature branches or when you want to maintain a linear commit history without merge commits. git pull --rebase fetches the remote changes and then "re-applies" your local commits on top of the updated remote branch.

  1. Ensure a clean working directory:

    git status
    

    Make sure your working directory is clean (git status shows no uncommitted changes). If you have uncommitted changes, either commit them temporarily (git commit -m "WIP: stash before rebase") or stash them (git stash save "WIP for rebase"). You can git stash pop or git reset HEAD~1 later.

  2. Pull with rebase:

    git pull --rebase origin main
    

    (Replace main with your branch name if different).

    • Git will fetch changes from origin/main.
    • It will then temporarily revert your local commits.
    • It will update your main branch to match origin/main.
    • Finally, it will re-apply your local commits one by one on top of the new origin/main history.
  3. Resolve conflicts (if any): If conflicts occur during the rebase, Git will pause and notify you.

    # Example conflict message:
    # Applying: My new feature
    # Using index info to reconstruct a base tree...
    # M       src/app.js
    # Auto-merging src/app.js
    # CONFLICT (content): Merge conflict in src/app.js
    # error: could not apply <commit-sha>... My new feature
    # Resolve all conflicts manually, then mark them as resolved with
    # "git add/rm <conflicted_files>"
    # Then run "git rebase --continue"
    
    • Manually edit the conflicted files to resolve the differences.
    • Mark conflicts as resolved:
      git add <conflicted_file_1> <conflicted_file_2>
      
    • Continue the rebase process:
      git rebase --continue
      
    • If you decide to abandon the rebase:
      git rebase --abort
      
  4. Push your updated branch: Once the rebase is complete (and conflicts resolved), your local branch is now ahead of the remote and can be fast-forwarded.

    git push origin main
    

    Rebasing Shared Branches: Avoid rebasing branches that have already been pushed to a public remote and are being worked on by others. Rebasing rewrites history, and this can cause significant issues for collaborators who have based their work on the original history. Only rebase your private feature branches or when you are absolutely sure no one else is affected.

3. Option B: Integrate Remote Changes with git pull (Merge Strategy)

This is the simplest and safest option for integrating remote changes, especially on shared branches. git pull is a shorthand for git fetch followed by git merge. It fetches the remote changes and then merges them into your current local branch, creating a new "merge commit" if your histories have diverged.

  1. Ensure a clean working directory:

    git status
    

    Commit or stash any uncommitted changes before pulling to avoid accidentally merging them with remote changes.

  2. Pull remote changes:

    git pull origin main
    

    (Replace main with your branch name).

    • Git will fetch the changes from origin/main.
    • It will then perform a merge of origin/main into your local main branch. If histories have diverged, a new merge commit will be created.
  3. Resolve conflicts (if any): If conflicts occur during the merge:

    • Manually edit the conflicted files.
    • Mark conflicts as resolved:
      git add <conflicted_file_1> <conflicted_file_2>
      
    • Commit the merge:
      git commit -m "Merge remote-tracking branch 'origin/main' into main"
      
      Git usually provides a default merge commit message, which you can accept or modify.
  4. Push your updated branch: After a successful pull (and conflict resolution), your local branch is updated and can be pushed.

    git push origin main
    

4. Option C: Force Push (Use with Extreme Caution!)

A force push (git push --force or git push --force-with-lease) will overwrite the remote branch with your local branch's history, regardless of whether it results in a non-fast-forward update. This effectively discards any commits on the remote branch that are not present in your local history.

Use of git push --force can lead to permanent data loss and seriously disrupt the workflow of other developers. Only use this if you are absolutely certain you want to replace the remote history with your local history, and ideally, only on branches where you are the sole contributor or after explicit coordination with your team.

  1. Understand the implications: Be 100% sure you want to discard the remote history and replace it with your local version. There is no undo for lost remote commits without significant effort.

  2. Prefer git push --force-with-lease: This command is a safer alternative to git push --force. It performs the force push only if the remote branch you're pushing to hasn't been updated since you last fetched from it. This prevents you from accidentally overwriting someone else's new work that you haven't seen yet.

    git push --force-with-lease origin main
    

    (Replace main with your branch name).

  3. When to use force push:

    • You have just rebased your private feature branch and want to update the remote.
    • You are recovering from a mistaken commit or revert on a private or temporary branch.
    • You have explicitly coordinated with your team, and everyone understands and agrees to a history rewrite.

5. Recovering from Accidental Force Push (Advanced)

If you or a teammate accidentally force-pushed and lost commits, it might be possible to recover them from the remote's reflog (if the hosting provider exposes it) or from another developer's local repository that still has the "lost" history.

  • Consult remote logs: Check your Git hosting platform's audit logs or UI for recent branch updates.

  • Check another developer's machine: If a teammate hasn't fetched the force-pushed state, they might still have the original history.

  • Use git reflog (on a local copy): On a local repository that still contains the "lost" commits, git reflog can show past states of your branches. You can then use git reset --hard <SHA> to bring a local branch back to a previous state and potentially force-push that correct history back up (again, with extreme caution and coordination).

    git reflog
    # Look for the commit you want to restore
    git reset --hard HEAD@{<index_from_reflog>}
    # Or to a specific SHA if you know it
    git reset --hard <original_commit_SHA>
    # Then, if absolutely necessary and coordinated, force push
    # git push --force-with-lease origin main
    

    Recovery from a destructive force push is complex and requires deep understanding of Git. Always backup critical repositories and coordinate with your team when performing such operations.

By understanding the nature of non-fast-forward pushes and employing the appropriate resolution strategy, you can efficiently manage your Git workflow and maintain a clean, collaborative development environment.

👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn →

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.