Git & CI/CD Intermediate

Git Error: ‘rejected non-fast-forward’ Push Conflicts on CentOS Stream / Rocky Linux

Resolve Git 'rejected non-fast-forward' errors on CentOS Stream & Rocky Linux. Understand push conflicts, rebase, merge, and force push strategies for clean Git workflows.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Resolve Git 'rejected non-fast-forward' errors on CentOS Stream & Rocky Linux. Understand push conflicts, rebase, merge, and force push strategies for clean Git workflows.

When working with Git repositories on CentOS Stream or Rocky Linux, encountering a rejected non-fast-forward error during a git push operation is a common scenario for developers and system administrators. This error indicates a divergence in your local and remote branch histories, preventing your changes from being pushed upstream. Understanding its root cause and applying the correct resolution strategy is crucial for maintaining a clean, consistent repository history and efficient team collaboration.

Symptom & Error Signature

You attempt to push your local commits to a remote repository, typically using git push origin <branch-name>, and the command fails with output similar to this:

[user@server project-repo]$ git push origin main
To github.com:your-org/your-project.git
 ! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'github.com:your-org/your-project.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 message here is (non-fast-forward) which explicitly tells you why your push was rejected.

Root Cause Analysis

The "rejected non-fast-forward" error signifies that the remote branch contains commits that are not present in your local branch's history, and your local branch also has new commits that are not yet on the remote. In simpler terms:

  • Divergent Histories: The commit history of your local branch has diverged from that of its remote counterpart. Someone else (or another process like a CI/CD pipeline) has pushed new commits to the remote branch since you last pulled, and you've also made new commits locally.
  • Non-Fast-Forward Push: Git's default behavior is to only allow "fast-forward" pushes. A fast-forward push occurs when the remote branch can simply move its pointer forward to your new commit, without needing to integrate any new commits of its own. When histories diverge, Git detects that a simple pointer move isn't possible because it would overwrite history that exists on the remote but not locally, and thus rejects the push to prevent accidental data loss or disruption for other collaborators.
  • Common Scenarios:
    1. Concurrent Development: Another developer pushed changes to the same branch (e.g., main or develop) after you pulled but before you pushed your own work.
    2. Local History Rewriting: You performed an operation like git rebase or git commit --amend on your local branch, effectively rewriting its history. When you try to push this rewritten history to a remote branch that still holds the "old" history, Git detects the non-fast-forward condition.
    3. CI/CD Updates: An automated process or webhook pushed new commits (e.g., version bumps, build artifacts) to the remote branch.

Step-by-Step Resolution

The primary goal is to integrate the remote changes into your local branch before pushing, thus creating a unified history. There are several ways to achieve this, each with implications for your branch's commit history.

1. Fetch Remote Changes and Review History

Before attempting any resolution, it's good practice to fetch the latest remote state and inspect the commit history. This helps you understand the divergence.

# Ensure you are on the correct branch (e.g., main)
git checkout main

# Fetch all remote branches and their commits
git fetch origin

# Visualize the divergent history (your local 'main' vs. remote 'origin/main')
# This command shows commits unique to 'main' and 'origin/main'
git log --oneline --graph --decorate main origin/main

Look for branching points and unique commits. You'll likely see commits on origin/main that are not in your main branch, and vice-versa.

2. Safest Method: Merge Remote Changes (Recommended for shared branches)

This is the most common and generally safest approach for integrating changes, as it preserves all history and creates a merge commit.

# Make sure your working directory is clean or stash your changes
git status
# If you have uncommitted changes:
# git stash

# Pull the remote changes into your current branch.
# This is equivalent to `git fetch origin main` followed by `git merge origin/main`.
git pull origin main

# At this point, Git might automatically merge if there are no conflicts.
# If there are conflicts, Git will pause and prompt you to resolve them.
# 1. Open the conflicted files in your editor.
# 2. Resolve the conflict markers (<<<<<<<, =======, >>>>>>>).
# 3. Add the resolved files to the staging area.
git add .

# 4. Commit the merge. Git usually provides a default merge commit message.
# You can accept it or customize it.
git commit -m "Merge remote-tracking branch 'origin/main' into main"

# If you stashed changes earlier, reapply them now
# git stash pop

# Now, push your combined local history (your commits + merged remote commits)
git push origin main

If you encounter merge conflicts, resolve them carefully. Using a good diff/merge tool (like meld or VS Code's built-in tool) can be invaluable. Ensure you're not accidentally discarding necessary changes from either side.

3. Cleaner History Method: Rebase Local Changes (Recommended for personal feature branches or before merging to main)

Rebasing rewrites your local history by moving your commits "on top" of the remote branch's latest commits. This results in a linear history without merge commits, which many teams prefer for feature branches before they are squashed or merged into main.

# Make sure your working directory is clean or stash your changes
git status
# If you have uncommitted changes:
# git stash

# Checkout your working branch (e.g., a feature branch or 'main')
git checkout your-feature-branch

# Pull remote changes and rebase your local commits on top of them.
# This is equivalent to `git fetch origin` followed by `git rebase origin/your-feature-branch`.
git pull --rebase origin your-feature-branch

# Git will now apply your local commits one-by-one on top of the remote's latest.
# If conflicts occur during any of these "replayed" commits:
# 1. Resolve the conflict in the file(s).
# 2. Add the resolved file(s).
git add .
# 3. Continue the rebase process.
git rebase --continue

# If you need to stop the rebase at any point due to issues:
# git rebase --abort

# If you stashed changes earlier, reapply them now
# git stash pop

# Once the rebase is complete and successful, push your updated branch.
# This should now be a fast-forward push.
git push origin your-feature-branch

Do not rebase branches that have already been pushed to a public remote and shared with others. Rebasing rewrites history, and if others have based their work on the "old" history, it will cause significant headaches for them, requiring them to force pull and potentially rebase their own work. Use rebase primarily on local branches or branches you haven't pushed yet, or on private feature branches before merging into shared mainlines.

4. Force Push (Use with extreme caution!)

Force pushing overwrites the remote branch with your local branch's history, regardless of divergence. This effectively discards any commits on the remote that are not in your local history.

# ONLY USE THIS IF YOU ARE ABSOLUTELY CERTAIN YOU WANT TO OVERWRITE THE REMOTE HISTORY.
# This command is safer than plain `--force` as it first checks if the remote branch
# is at the expected commit before overwriting.
git push --force-with-lease origin main

# This command unconditionally overwrites the remote. Use with even greater care.
# git push --force origin main

Force pushing (--force or --force-with-lease) is a dangerous operation, especially on shared branches like main or develop. It can erase work from other collaborators if they have pushed commits that you are unaware of. Always communicate with your team and ensure no one else has pushed to the branch before considering a force push. Typically, force pushes are only acceptable for:

  • Cleaning up a feature branch you exclusively own and haven't merged yet.
  • Recovering from a catastrophic, known-bad commit, with prior team coordination.
  • Pushing after a local rebase on a branch that is not shared.

By understanding the nature of the "rejected non-fast-forward" error and choosing the appropriate resolution method (merge for integration, rebase for cleaner history, and force push for explicit overwrites with caution), you can effectively manage your Git workflow on CentOS Stream and Rocky Linux environments.

👨‍💻

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.