Skip to content

Understanding Git Divergent Branches: A Real-World Example

The Scenario

While working on a feature branch, I encountered a common Git problem that many developers face. Let me share my experience and how to resolve it.

What I Was Doing

I was working on a feature branch called feature/25.10.25 in my project. Here's what happened step by step:

  1. First, I successfully pushed some changes to GitHub
  2. Then, I made a local commit with the message "changes" (commit ID: 396de08)
  3. I tried to push again, but Git rejected my push!

The First Error: Push Rejected

Here's what Git told me when I tried to push:

$ git push
To https://github.com/dwdas9/home.git
 ! [rejected]        feature/25.10.25 -> feature/25.10.25 (fetch first)
error: failed to push some refs to 'https://github.com/dwdas9/home.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.

Git's message was clear: "use git pull before pushing again." So that's exactly what I did.

The Second Error: Git Asks Me to Choose

Following Git's advice, I ran git pull, but instead of solving the problem, I got another error:

$ git pull
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint:   git config pull.rebase false  # merge
hint:   git config pull.rebase true   # rebase
hint:   git config pull.ff only       # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.

This is the critical moment! Git is essentially saying:

"I can see your branches have diverged. I can help you reconcile them, but YOU need to tell me HOW. Do you want to merge? Rebase? Or only fast-forward?"

This is where many developers get confused. Git is giving us three options, but which one should we choose? That's what this article is about!

What Actually Happened

The error message gives us a crucial clue: "the remote contains work that you do not have locally."

This means: - While I was working on my local branch and making commits - Someone else (or I from another machine, or maybe a CI/CD system) pushed different commits to the same branch on GitHub - Now my local version and the remote version have diverged - they've gone in different directions from a common starting point

Think of it like two people editing the same document simultaneously but in different ways. Eventually, you need to reconcile the changes.


Understanding the Problem Visually

Let me show you what "divergent branches" actually means:

Your commits
Remote commits
Merge commit
THE PROBLEM

What Happened? 🤔

A B C E F D ← Remote (GitHub) ← Local (You) Common history
What happened:
• You both started with A → B → C
• Someone else pushed E and F to remote 🔴
• You created commit D locally 🟡
• Now: DIVERGED! Two different versions exist!

Breaking Down the Divergence

Looking at the diagram above:

  • Commits A, B, C: These are the commits that both my local branch and the remote branch shared originally
  • Commit C: This is where our histories diverged (the "common ancestor")
  • Commits E and F (in red): Someone pushed these to the remote while I was working
  • Commit D (in yellow): This is my local commit that hasn't been pushed yet

The problem? Git doesn't know which version is "correct" - both have valid new work!


Why This Happens

There are several common scenarios that lead to divergent branches:

1. Multiple Developers Working on the Same Branch

  • You and a teammate are both working on feature/25.10.25
  • Your teammate pushes their changes first
  • When you try to push, your branch has diverged

2. Working from Multiple Machines

  • You commit and push from your work computer
  • Later, you commit from your laptop (forgetting to pull first)
  • The branches diverge

3. CI/CD Automation

  • Your CI/CD pipeline makes automated commits (version bumps, generated files, etc.)
  • You're working locally at the same time
  • When you try to push, there's a conflict

4. Force Push by Someone Else

  • Someone force-pushed to the branch, rewriting history
  • Your local branch is now based on old commits

The Three Solutions

When Git detects divergent branches and you try to pull, it stops and asks you to choose how to reconcile them. This is exactly what happened when I ran git pull - Git showed me three options and said "fatal: Need to specify how to reconcile divergent branches."

Let's explore each option Git offered:

Solution 1: Merge Strategy

OPTION 1

Merge Strategy 🔀

A B C E F D M ← Merge!
What happens:
• Combines both histories
• Creates new merge commit M 🔵
• Keeps all commits intact
git config pull.rebase false
git pull
git push
✅ Pros:
• Preserves complete history
• Safest option
• Easy to understand
❌ Cons:
• Extra merge commit
• History can get messy
• Graph looks complex

When to Use Merge

  • Best for: Main branches, shared team branches
  • Use when: You want to preserve the complete history of both branches
  • Team setting: When multiple people are collaborating and you want transparency

How It Works

The merge strategy creates a new "merge commit" that has two parents - your local changes and the remote changes. This preserves both lines of development in the history.

Example scenario: You're working on the main branch with your team. You want to see exactly when and how different features were integrated.


Solution 2: Rebase Strategy ⭐

When to Use Rebase

  • Best for: Feature branches, personal branches
  • Use when: You want a clean, linear history
  • Team setting: When you're the primary developer on a feature branch

How It Works

Rebase "replays" your commits on top of the latest remote commits. It's like saying "pretend I made my changes AFTER the remote changes were made." Your commit gets a new ID because it's technically a new commit in a new position.

Example scenario: You're working on a feature branch alone. You want the Git history to look like you made your changes in a nice, orderly sequence.

Important: This is my recommended approach for the situation in the screenshot!


Solution 3: Fast-Forward Only

When to Use Fast-Forward Only

  • Best for: When you want to be extra cautious
  • Use when: You only want to pull when there's no divergence
  • Team setting: When you want to ensure you never accidentally merge or rebase

Why It Fails

Fast-forward only works when your local branch is simply "behind" the remote - meaning the remote has new commits, but you haven't made any new commits locally. In our case, we HAVE made new commits (commit D), so this option fails.

Example scenario: You cloned a repo, haven't made any changes, and just want to update to the latest version. Fast-forward works perfectly here.


Resolving the Conflict: Step-by-Step

Now that we understand the three options Git gave us, let's solve the actual problem from my screenshot.

The Journey So Far

  1. ✅ Tried to push → Rejected ("fetch first")
  2. ✅ Ran git pullError ("Need to specify how to reconcile divergent branches")
  3. ❓ Now what?

Since this is a feature branch (feature/25.10.25), rebase is the best choice:

# Step 1: Configure Git to use rebase
git config pull.rebase true

# Step 2: Pull with rebase
git pull

# Step 3: If there are no conflicts, push
git push

What If There Are Conflicts?

Sometimes, your changes and the remote changes modify the same lines of code. Git will pause and ask you to resolve conflicts:

# After git pull, if conflicts occur:
$ git pull
Auto-merging myfile.py
CONFLICT (content): Merge conflict in myfile.py
error: could not apply 396de08... changes

How to resolve:

  1. Open the conflicting files - Git marks conflicts like this:

    <<<<<<< HEAD
    # Remote version (what's on GitHub)
    print("Hello from remote")
    =======
    # Your version (your local commit)
    print("Hello from local")
    >>>>>>> 396de08 (changes)
    

  2. Edit the file to keep what you want:

    # Resolved version
    print("Hello from both versions")
    

  3. Stage the resolved files:

    git add myfile.py
    

  4. Continue the rebase:

    git rebase --continue
    

  5. Push your changes:

    git push
    

Need to abort? If things go wrong:

git rebase --abort  # Returns to state before rebase


Preventing This Issue

While divergent branches are normal, here are some practices to minimize them:

1. Pull Before You Push

# Good habit: Always pull first
git pull
git push

2. Communicate with Your Team

  • If multiple people work on the same branch, coordinate who's pushing when
  • Consider using separate feature branches for each developer

3. Commit and Push Frequently

  • Don't let your local branch get too far ahead
  • Smaller, more frequent pushes reduce the chance of conflicts

4. Use Branch Protection

  • For main branches, use pull requests instead of direct pushes
  • This prevents accidental divergence

5. Set Your Default Strategy

# For all repositories:
git config --global pull.rebase true

# Or for merge:
git config --global pull.rebase false

Quick Reference Guide


Key Takeaways

  1. Divergent branches are normal - They happen when local and remote histories differ
  2. Three solutions exist: Merge, Rebase, or Fast-Forward (when possible)
  3. For feature branches: Use rebase for clean history
  4. For shared branches: Use merge to preserve history
  5. Always pull before pushing to minimize divergence
  6. Conflicts are okay - Git helps you resolve them step by step

Conclusion

The divergent branches error might seem scary at first, but it's actually Git protecting your work. By understanding what happened (branches diverged), why it happened (concurrent changes), and how to fix it (merge or rebase), you can confidently handle this situation.

For the scenario in my screenshot, the solution was simple:

git config pull.rebase true
git pull
git push

And that's it! Clean, linear history maintained, and my changes successfully pushed to GitHub.

Remember: Git's complexity comes from its power. Once you understand these concepts, you'll appreciate having the flexibility to choose how to integrate changes.


Additional Resources


Have questions or suggestions? Feel free to reach out!