What's Actually Happening

When you try to push and get a "non-fast-forward" rejection, Git is protecting you from losing work. The remote branch has commits that your local branch doesn't have, and a simple push would overwrite them.

The Error You'll See

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

Why This Happens

  1. 1.Someone else pushed commits - A teammate pushed to the same branch
  2. 2.You force pushed from another machine - Your other workstation overwrote history
  3. 3.Rebase rewritten history - You rebased locally but haven't force pushed yet
  4. 4.Branch protection rules - Server rejects non-fast-forward pushes

Step 1: Check What's Different

See what commits the remote has that you don't:

```bash # Fetch the latest from remote git fetch origin

# See commits on remote that aren't on your branch git log HEAD..origin/main --oneline ```

Output shows commits you're missing:

bash
a1b2c3d Fix authentication bug by teammate
d4e5f6g Add new feature from PR merge

Step 2: Understand the Options

You have two safe choices:

Option A: Merge - Combines remote commits with yours, preserves history

bash
git pull --merge origin main

Creates a merge commit. History shows both branches.

Option B: Rebase - Replays your commits on top of remote

bash
git pull --rebase origin main

Your commits appear after remote commits. Cleaner history, but rewrites your local commits.

Step 3: Merge Approach (Safer for Beginners)

```bash # Pull and merge remote changes git pull origin main

# If there are conflicts, Git will pause # Resolve conflicts in the files, then: git add resolved-file.txt git commit

# Now push should work git push origin main ```

Step 4: Rebase Approach (Cleaner History)

```bash # Pull and rebase your work on top of remote git pull --rebase origin main

# If conflicts occur during rebase: # Fix the file, then: git add resolved-file.txt git rebase --continue

# After rebase completes: git push origin main ```

Step 5: Handling Merge Conflicts

When pulling shows conflicts:

bash
$ git pull origin main
CONFLICT (content): Merge conflict in src/app.js
Automatic merge failed; fix conflicts and then commit the result.

Check which files have conflicts:

bash
git status

Look for files marked "both modified". Open them and find conflict markers:

bash
<<<<<<< HEAD
your local changes
=======
remote changes
>>>>>>> origin/main

Edit to resolve - keep the correct version or combine both:

bash
# After resolving all conflicts
git add .
git commit -m "Merge remote changes and resolve conflicts"
git push origin main

Step 6: When You Know Force Push Is Needed

If you intentionally rewrote history (rebase, amend) and need to force push:

bash
# WARNING: This overwrites remote history
# Only use when you're certain it's safe
git push --force-with-lease origin main

--force-with-lease is safer than --force - it verifies the remote state before overwriting.

Step 7: Branch Protection Blocking Push

If the remote has branch protection rules:

bash
$ git push --force origin main
remote: error: GH006: Protected branch update failed for main
remote: error: Cannot force-push to a protected branch
  1. 1.Solution options:
  2. 2.Create a pull request instead of direct push
  3. 3.Ask admin to temporarily disable protection
  4. 4.Push to a different branch and PR
bash
# Create feature branch and PR
git checkout -b feature/my-changes
git push origin feature/my-changes
# Then create PR via GitHub/GitLab UI

Verify the Fix

After successfully pushing:

```bash git log --oneline -5

# Check remote state git fetch origin git log origin/main --oneline -5 ```

Both should show your commits.

Prevention Tips

Before starting work, always:

```bash # Pull latest before starting new work git pull origin main

# Or use fetch to check without merging git fetch origin git status ```

This reduces the chance of push conflicts.