Mid-level 9 min · March 06, 2026

Git Rebase vs Merge — Shared Branch Rebase Cost 6 Dev Hours

Git Rebase vs Merge: After a force-pushed rebase on develop cost 6 devs 3 hours, learn the production disaster, recovery steps, and the golden rule to prevent it.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Merge creates a merge commit with two parents — preserves parallel development history
  • Rebase replays commits on top of new base — linear history with rewritten SHAs
  • Interactive rebase (rebase -i): squash, reorder, reword commits before PR
  • Golden Rule: never rebase a branch others have pulled
  • Performance: linear history from rebase speeds up git bisect by ~30%
  • Production insight: rebasing a shared branch forces teammates to hard-reset; one bad rebase costs hours
Plain-English First

Imagine you're writing a group essay. Merge is like stapling everyone's drafts together at the end — you can see every version and who wrote what, but the final document has a messy paper trail. Rebase is like retyping your section from scratch onto the latest shared draft — the result looks seamless, as if only one person wrote it the whole time. Both get you to a finished essay, but they leave very different paper trails behind.

Merge and rebase both integrate changes from one branch into another. They solve the same problem in fundamentally different ways. Merge creates a merge commit that records when two branches came together. Rebase rewrites your commits so they appear to have started from the tip of the target branch.

The choice shapes your project's Git history. Merge preserves the full context of parallel development — you can see exactly what state main was in when your feature was built. Rebase produces a clean precise and git log more readable. Neither is universally better.

Common misconceptions: that rebase is always cleaner (it rewrites SHAs, which breaks shared branches), that merge always creates noise (merge commits are meaningful integration markers), and that the choice is personal preference (it is a team decision that affects debugging and collaboration).

How Git Merge Works — and What It Leaves Behind

When you run git merge, Git finds the most recent common ancestor of the two branches (called the merge base), then combines the changes from both branches into a brand-new commit. This new commit has two parents — one from each branch — which is why it's called a merge commit. It's the only commit in your repo that points backwards at two different lines of work.

This means your history is a faithful record of reality. If you and a colleague worked in parallel for a week, the graph shows that. You can run git log --graph and literally see two lanes of traffic merging into one. That's incredibly useful when you're trying to understand why a change was made in the context of what else was happening at the time.

The downside is noise. On a busy team where a dozen feature branches merge into main every day, your history fills up with merge commits that add structural information but no actual code change. Tools like git bisect and git log --oneline become harder to read. Some teams are fine with this — it's an honest record. Others find it distracting. That tension is the whole reason rebase exists.

io/thecodeforge/git/MergeFeatureBranch.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# io.thecodeforge — Merge Feature Branch

# ─────────────────────────────────────────────────────────────
# SCENARIO: You've been building a user-authentication feature.
# A colleague just merged a critical security patch into main.
# You need those changes in your feature branch before you can continue.
# ─────────────────────────────────────────────────────────────

# Step 1Check your current position
git log --oneline --graph --all
# Output before merge:
# * f3a91bc (HEAD -> feature/user-auth) Add password hashing
# * 9d44e02 Add login endpoint
# | * c7b83af (main) SECURITY: sanitize SQL inputs in user queries
# | * 4e21d09 Add rate limiting middleware
# |/
# * 8b12cde Initial project scaffold

# Step 2Switch to your feature branch (already on it, just confirming)
git checkout feature/user-auth

# Step 3Merge main INTO your feature branch
# This brings the security patch into your work
git merge main
# Git opens your editor for a commit message, or auto-creates:
# "Merge branch 'main' into feature/user-auth"

# Step 4See what the history looks like now
git log --oneline --graph --all
Output
* b9e4f01 (HEAD -> feature/user-auth) Merge branch 'main' into feature/user-auth
|\
| * c7b83af (main) SECURITY: sanitize SQL inputs in user queries
| * 4e21d09 Add rate limiting middleware
* | f3a91bc Add password hashing
* | 9d44e02 Add login endpoint
|/
* 8b12cde Initial project scaffold
Merge Preserves Parallel History
  • Merge commit has two parents — one from each branch
  • git log --graph shows the parallel lanes and the merge point
  • History is a faithful record of what happened in parallel
  • Merge commits add structural information but no code change — this is the noise trade-off
Production Insight
Merge commits are meaningful integration markers, not noise. When debugging a regression six months later, the merge commit tells you exactly what state main was in when the feature was integrated. A linear history from rebase loses this context. The trade-off: on a team with 20 merges per day, the graph becomes hard to read. The solution is not to avoid merge commits — it is to use them deliberately for significant integration points and use rebase for daily branch synchronization.
Key Takeaway
Merge creates a merge commit with two parents — a faithful record of parallel development. The history is honest but can get noisy on active repos. Merge commits are meaningful integration markers that help future debugging. Use merge for shared branches and significant integration points.

How Git Rebase Works — and Why It Rewrites History

Rebase does something more radical: it replays your commits one by one on top of a new base commit. The word 'rebase' is literal — you're changing the base commit that your branch started from. The result looks as if you had branched off at the very tip of the target branch and written all your commits from there.

Here's the key thing to internalise: your original commits are not moved. Git creates brand-new commits with the same changes but different parent hashes, timestamps, and therefore different SHA-1 identifiers. The old commits still exist temporarily, but with nothing pointing to them, they'll be cleaned up by Git's garbage collector. This is not a cosmetic rename — it is a genuine rewrite.

The payoff is a perfectly linear history. When a reviewer reads your branch after a rebase, they see a clean sequence of purposeful commits with no structural noise. git log looks like a well-written changelog. git bisect works with surgical precision because every commit in the chain is meaningful. This is why many teams require rebase before merging feature branches via pull requests — you get the readability benefits of a linear history in the shared record.

io/thecodeforge/git/RebaseOntoMain.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# io.thecodeforge — Rebase Feature Branch onto Main

# ─────────────────────────────────────────────────────────────
# Same scenario — feature branch needs the security patch from main.
# This time we'll use rebase instead of merge.
# ─────────────────────────────────────────────────────────────

# Step 1Verify starting state (same branching point as before)
git log --oneline --graph --all
# * f3a91bc (HEAD -> feature/user-auth) Add password hashing
# * 9d44e02 Add login endpoint
# | * c7b83af (main) SECURITY: sanitize SQL inputs in user queries
# | * 4e21d09 Add rate limiting middleware
# |/
# * 8b12cde Initial project scaffold

# Step 2Make sure your working tree is clean before rebasing
git status
# Should show: nothing to commit, working tree clean

# Step 3Rebase the feature branch onto the tip of main
# Git will replay 9d44e02 and f3a91bc on top of c7b83af
git rebase main
# Output during rebase:
# Successfully rebased and updated refs/heads/feature/user-auth.

# Step 4Look at the history now
git log --oneline --graph --all

# Step 5If you had already pushed the old commits, force-push the rebased ones
# ONLY if nobody else has pulled your branch
git push --force-with-lease origin feature/user-auth
# --force-with-lease is safer than --force: it fails if the remote has
# commits you don't know about (someone else pushed while you were rebasing).
Output
* e7c12d3 (HEAD -> feature/user-auth) Add password hashing
* a1f84b9 Add login endpoint
* c7b83af (main) SECURITY: sanitize SQL inputs in user queries
* 4e21d09 Add rate limiting middleware
* 8b12cde Initial project scaffold
Production Insight
Rebasing a branch that's already been pushed — even if you're the only contributor — requires force-push. Always use --force-with-lease to avoid overwriting later pushes by others. This is a common mistake: developers use --force out of habit, which can silently destroy teammates' work. Enforce --force-with-lease in team documentation.
Key Takeaway
Rebase replays commits onto a new base, creating new SHAs. This linearizes history but rewrites shared history when pushed. Only rebase branches that haven't been pushed or are solely yours. Use --force-with-lease, never --force.

Warning: Never Rebase Shared Branches

The Golden Rule of rebasing is non-negotiable: never rebase a branch that another developer has pulled from. Once a commit is shared — pushed to a remote or pulled by a teammate — its SHA must never change. Rebasing a shared branch rewrites all commits, creating diverging histories that force every collaborator to hard-reset. The most common casualty is the develop branch, where a well-intentioned cleanup costs hours of team coordination.

Rebase Rewrites Commit SHAs
  • Rebase creates new commits with new SHAs — old commits are orphaned
  • Orphaned commits are garbage-collected after 30 days (recoverable via reflog)
  • Golden Rule: never rebase a branch others have pulled
  • Use --force-with-lease instead of --force when pushing rebased branches
Production Insight
The --force-with-lease flag is a critical safety mechanism that most tutorials omit. Unlike --force, which blindly overwrites the remote, --force-with-lease checks that the remote ref matches what you expect before pushing. If someone else pushed to the branch while you were rebasing, --force-with-lease fails instead of silently overwriting their work. Every team should use --force-with-lease exclusively and never --force.
Key Takeaway
The Golden Rule is absolute: never rebase a branch others have pulled. If you must, announce and coordinate. Use branch protection to block force-pushes to shared branches.

Visual Branch History: Merge vs Rebase

Seeing the difference between merge and rebase is easier with visual diagrams. Below is a Mermaid graph showing the same initial forked history, then the result of merging, and the result of rebasing. Notice how the merge result introduces a merge commit with two parents, while the rebase result appears as a linear sequence of commits.

Before: Forked History — main has commit A, then both main and feature branches diverge. main receives commit C, feature receives commit B.

After Merge — a new merge commit D joins the two branches. Git shows both lanes of work. This is truthful parallelism but adds structural nodes.

After Rebase — feature's commit B is replayed on top of main's commit C, becoming B' with a new SHA. The history looks as if the feature was developed entirely after main's latest commit. This is easier to read but loses the fact that the feature and main commits happened concurrently.

io/thecodeforge/git/visualBranchHistory.mmdMERMAID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
graph TD
  subgraph "Before: Forked History"
    A[main: commit A] --> B[feature: commit B]
    A --> C[main: commit C]
  end
  subgraph "After Merge"
    D[main: commit A] --> E[feature: commit B]
    D --> F[main: commit C]
    E --> G[merge commit D]
    F --> G
  end
  subgraph "After Rebase"
    H[main: commit A] --> I[main: commit C]
    I --> J["feature: commit B' (rewritten)"]
  end
Readability vs. Fidelity
The rebase diagram hides the fact that the feature and main commits were developed in parallel. This is great for a clean changelog but bad for understanding the timeline of events. Choose based on what your team values more: readability or historical accuracy.
Production Insight
When onboarding new team members, the merge graph helps them understand the project's development timeline. The rebase graph is easier to bisect and revert. If your team frequently uses git bisect to track down regressions, the linear history from rebase saves significant time. If your team needs to audit 'what was happening when', merge commits are invaluable.
Key Takeaway
Visual diagrams clarify that merge preserves parallel development with a merge commit, while rebase linearizes history by rewriting commits. Use merge for historical accuracy, rebase for a clean linear log.

Rebasing onto Specific Branches with --onto

The git rebase --onto flag gives you fine-grained control over where your branch is rebased. Without --onto, rebasing moves the entire branch onto the new base (e.g., git rebase main rebases all commits in the current branch that are not in main onto the tip of main). With --onto, you can rebase only a subset of commits onto a completely different base.

Common use case: you started a feature branch from the wrong point — say you branched from main when you should have branched from release/v2.0. Instead of cherry-picking each commit, you can run:

git rebase --onto release/v2.0 main feature/checkout

This takes all commits on feature/checkout that are not in main and replays them on top of release/v2.0. It's a precise surgical operation.

Another scenario: you have a chain of branches (feature-A depends on feature-B). If feature-B has already been merged and you want to rebase feature-A directly onto main, you can use --onto to skip the intermediate base.

io/thecodeforge/git/RebaseOntoFlag.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# io.thecodeforge — Rebase onto Specific Branch Using --onto

# ─────────────────────────────────────────────────────────────
# SCENARIO: You accidentally branched feature/checkout from main,
# but it should be based on release/v2.0 which has different configs.
# ─────────────────────────────────────────────────────────────

# Step 1Check the current state
git log --oneline --graph --all
# * e7c12d3 (feature/checkout) Add checkout validation
# * a1f84b9 Add shipping cost calculation
# | * d9e0a44 (release/v2.0) Update tax rates for EU
# | * 4e21d09 Add EU-specific checkout fields
# |/  
# * c7b83af (main) SECURITY: sanitize SQL inputs

# Step 2Rebase feature/checkout onto release/v2.0, skipping main
# The three-argument form: git rebase --onto <new_base> <old_base> <branch>
# old_base is main (the current base), new_base is release/v2.0
# This replays ONLY commits that are not in main (the feature commits)
git rebase --onto release/v2.0 main feature/checkout

# Step 3Verify the new base
git log --oneline --graph --all
# * f5c12e7 (feature/checkout) Add checkout validation
# * b4f84c1 Add shipping cost calculation
# * d9e0a44 (release/v2.0) Update tax rates for EU
# * 4e21d09 Add EU-specific checkout fields
# * c7b83af (main) SECURITY: sanitize SQL inputs
Output
* f5c12e7 (HEAD -> feature/checkout) Add checkout validation
* b4f84c1 Add shipping cost calculation
* d9e0a44 (release/v2.0) Update tax rates for EU
* 4e21d09 Add EU-specific checkout fields
* c7b83af (main) SECURITY: sanitize SQL inputs
--onto is for Pivoting, Not Just Rebasement
Think of --onto as a 'rebase and retarget' command. It's especially useful when you mistakenly branch from the wrong commit or when you want to move a set of commits to a completely different part of the tree. Many developers underuse this flag — it's one of Git's most powerful tools for branch management.
Production Insight
In production repositories with multiple long-lived release branches (e.g., v1.0, v2.0, hotfix), the --onto flag is essential for moving fixes between branches without merging unrelated history. For example, a hotfix applied to v1.0 may need to be rebased onto v2.0. Using git rebase --onto v2.0 v1.0 hotfix replays only the hotfix commits onto the newer release branch.
Key Takeaway
git rebase --onto allows you to rebase a subset of commits onto a different base than the current one. Use it to pivot branches to a new starting point, such as moving a feature from main to a release branch.

Three Real-World Workflows — Which Approach Fits Each

Knowing the mechanics is half the battle. Knowing which to reach for in a given situation is what separates a senior engineer from someone who just memorised the commands.

Workflow 1 — Keeping your feature branch up to date: Use rebase. While you're developing in isolation, nobody else is working off your branch. Rebasing onto main daily keeps your branch current without cluttering the future merge commit with a tangle of catch-up merges inside your PR. Your pull request shows exactly your work, nothing else.

Workflow 2 — Landing a feature into main via pull request: Both strategies are used in industry. Teams that value linear history use 'Squash and Rebase' in GitHub/GitLab so the entire feature lands as one clean commit. Teams that value commit granularity and want to see the feature's internal development use a regular merge commit. Know your team's convention before you hit the merge button.

Workflow 3 — Merging a long-lived shared branch (e.g. a release branch back into main): Always use merge, never rebase. Release branches are shared by the whole team. Rebasing would rewrite commits that everyone already has locally, causing a synchronisation disaster. A merge commit here is not noise — it's a meaningful event marker that says 'release 2.4.0 was integrated on this date'.

io/thecodeforge/git/InteractiveRebaseCleanup.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# io.thecodeforge — Interactive Rebase Cleanup Before PR

# ─────────────────────────────────────────────────────────────
# SCENARIO: You've been working on a checkout feature.
# Your commit history is messy with 'WIP' and 'fix typo' commits.
# Before raising a PR, you want to clean it up into meaningful commits.
# ─────────────────────────────────────────────────────────────

# Step 1See what you're working with
git log --oneline
# 8a3f21c Fix typo in promo code validator
# 7d91b4e WIP saving progress before standup
# 6c82e10 Add promo code validation logic
# 5b44a01 Add cart total calculation
# 4e30f99 (main) Set up checkout module

# Step 2Start interactive rebase for the last 4 commits
# The SHA here is the commit BEFORE the ones you want to edit
git rebase -i 4e30f99

# Git opens your editor showing:
# pick 5b44a01 Add cart total calculation
# pick 6c82e10 Add promo code validation logic
# pick 7d91b4e WIP saving progress before standup
# pick 8a3f21c Fix typo in promo code validator

# Step 3Edit the file to squash the WIP and typo fix into the promo commit
# Change 'pick' to 'squash' (or 's') for commits you want to fold in:
# pick 5b44a01 Add cart total calculation
# pick 6c82e10 Add promo code validation logic
# squash 7d91b4e WIP saving progress before standup
# squash 8a3f21c Fix typo in promo code validator

# Save and close. Git opens another editor for the combined commit message.
# Write a clean message: "Add promo code validation with edge case handling"

# Step 4Verify the clean result
git log --oneline
Output
b2e94f7 Add promo code validation with edge case handling
5b44a01 Add cart total calculation
4e30f99 (main) Set up checkout module
Interactive Rebase Is a Pre-PR Superpower
  • squash: fold a commit into the previous one — combines changes, merges messages
  • fixup: fold a commit into the previous one — combines changes, discards message
  • reorder: move commits up or down in the editor to change the sequence
  • reword: change a commit message without changing the code
Production Insight
The squash-vs-merge decision for PRs is a team convention, not a personal choice. Squash-merge produces one clean commit on main — easy to revert, easy to bisect, but loses the feature's internal development history. Regular merge preserves all commits — useful for understanding how the feature evolved, but clutters main with WIP commits. The industry trend is toward squash-merge for most features and regular merge for large features where the internal history matters.
Key Takeaway
Three workflows: rebase for keeping feature branches current, squash/rebase for landing PRs (team convention), merge for shared branches (never rebase). Interactive rebase (rebase -i) is a pre-PR hygiene tool — squash, reorder, reword commits before sharing. Know your team's convention before hitting the merge button.

Resolving Conflicts: How Merge and Rebase Differ Under Pressure

Both commands can hit conflicts when the same lines of code were changed in both branches. But the experience of resolving those conflicts is very different — and this catches a lot of developers off guard the first time they rebase a branch with multiple commits.

With merge, you get exactly one conflict-resolution session. Git combines everything in one shot and stops if it can't. You fix the conflicts, run git add, then git merge --continue (or git commit), and you're done.

With rebase, conflicts can appear multiple times — once for each commit being replayed. If you have five commits and the second one conflicts, you'll resolve it and run git rebase --continue, then potentially hit another conflict at the fourth commit. Each resolution is isolated to the changes in that specific commit, which is actually more precise but also more repetitive. If you're not prepared for this, it feels like you've broken something when the conflict reappears.

The nuclear escape hatch is git rebase --abort, which returns your branch to exactly the state it was in before you started the rebase. There's no equivalent 'undo' once a merge commit is created — you'd need git revert or git reset, both of which are more involved.

io/thecodeforge/git/HandleRebaseConflict.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# io.thecodeforge — Handle Rebase Conflict

# ─────────────────────────────────────────────────────────────
# SCENARIO: Rebasing a feature branch that modifies the same config
# file that main also modified — a conflict is guaranteed.
# ─────────────────────────────────────────────────────────────

# Start the rebase
git rebase main
# Auto-merging config/database.yml
# CONFLICT (content): Merge conflict in run "git rebase --continue".

# Step 1Open the conflicted file and look at what Git shows you
cat config/database.yml
# <<<<<<< HEAD (the tip of main, your new base)
# pool_size: 10
# timeout: 5000
# =======
# pool_size: 25
# timeout: 3000
# >>>>>>> 3c9a11f (Update database pool size for auth service)

# Step 2Decide what the correct value is and edit the file manually
# Edit config/database.yml to the correct final state
# Remove ALL conflict markers (<<<<<<<, =======, >>>>>>>)

# Step 3Stage the resolved file (do NOT git commit here)
git add config/database.yml

# Step 4Tell rebase to continue replaying the remaining commits
git rebase --continue
# Git may open your editor to confirm the commit message — save and close.

# Step 5Confirm the rebase completed cleanly
git log --oneline --graph
Output
* d8e47c2 (HEAD -> feature/user-auth) Update database pool size for auth service
* a1f84b9 Add login endpoint
* c7b83af (main) SECURITY: sanitize SQL inputs in user queries
* 4e21d09 Add rate limiting middleware
* 8b12cde Initial project scaffold
Never git commit During a Rebase Conflict
  • Merge: one conflict session for the entire merge — resolve once, done
  • Rebase: one conflict session per replayed commit — resolve multiple times
  • During rebase conflict: git add <file> then git rebase --continue, never git commit
  • git rebase --abort: complete undo, returns to pre-rebase state — no side effects
Production Insight
The multi-conflict nature of rebase is both its precision advantage and its usability cost. Each conflict is isolated to the specific commit being replayed — you know exactly which change caused the conflict. But on a branch with 10 commits and 3 conflicts, you resolve conflicts 3 separate times. The first time developers experience this, they think something is broken. The fix is team education: explain that multiple conflict sessions during rebase are normal and expected.
Key Takeaway
Merge gives you one conflict session. Rebase gives you one per replayed commit — more precise but more repetitive. During rebase conflicts: git add + git rebase --continue, never git commit. git rebase --abort is a complete undo with no side effects. There is no equivalent clean abort for merge once the merge commit is created.

Recovery Procedure: Upstream Branch Was Rebasing

If a teammate rebased and force-pushed a shared branch (like develop), your local branch is now based on outdated SHAs. The typical symptom is Your branch and 'origin/develop' have diverged. The wrong instinct is to merge — that creates a duplicate set of commits and a confusing history. The correct recovery is a hard reset to the rebased remote, then cherry-pick any local commits you had made on top of the old branch.

Recovery Steps: 1. Do NOT merge. Merging creates duplicate commits from the old base and the new base. 2. Fetch the latest remote state: git fetch origin 3. Align your local branch with the rebased remote: git reset --hard origin/develop. 4. If you had local commits on top of the old develop (commits you haven't pushed or that are unique to your local), recover them by finding their SHAs in git reflog and cherry-picking them: git cherry-pick <sha>. Or, if you had a feature branch based on the old develop, you can rebase that feature branch directly onto the new origin/develop: git checkout feature/your-feature && git rebase origin/develop. 5. After recovery, communicate with your team to ensure everyone is synchronized. No one should push before all have done the hard reset.

Prevention: Branch protection rules that block force-pushes to develop and main. If a rebase of a shared branch is absolutely necessary, it must be announced with a clear timeline and a plan for everyone to reset.

io/thecodeforge/git/RecoverFromUpstreamRebase.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# io.thecodeforge — Recover from Upstream Rebase of Shared Branch

# ─────────────────────────────────────────────────────────────
# SCENARIO: Your teammate rebased develop and force-pushed.
# You have no local work on develop, just need to get back in sync.
# ─────────────────────────────────────────────────────────────

# Step 1First, do NOT merge. Check the divergence:
git status
# On branch develop
# Your branch and 'origin/develop' have diverged.

# Step 2Fetch the rebased remote state
git fetch origin

# Step 3Align your local branch with the rebased remote
# WARNING: This discards any local commits on develop that are not in the remote.
# If you have unpushed commits on develop, find them first via reflog.
git reset --hard origin/develop

# ─────────────────────────────────────────────────────────────
# If you had local commits (e.g., on a feature branch based on old develop):
# ─────────────────────────────────────────────────────────────

# Step 1Checkout your feature branch
git checkout feature/my-feature

# Step 2Rebase directly onto the new origin/develop
# This replays your feature commits on top of the rebuilt develop
git rebase origin/develop

# Step 3Force-push your feature branch (only if you're the only one on it)
git push --force-with-lease origin feature/my-feature
Output
Successfully rebased and updated refs/heads/feature/my-feature.
Don't Merge a Diverged Branch
If you run git pull when your local branch has diverged from the remote, Git will by default do a merge (depending on config). This creates a merge commit that includes all the old commits from the shared branch PLUS all the new rebased commits — effectively duplicating all the work. The proper fix is always a hard reset or a rebase onto the new remote state.
Production Insight
If the shared branch was rebased without notice, the team's reaction window is narrow. The first developer to merge causes the most damage. The fix in the incident report (6 devs lost 3 hours) was to coordinate via team chat: everyone stopped what they were doing, fetched, hard-reset, and verified. The key lesson: automated alerts on force-pushes to protected branches can trigger an immediate team notification, reducing confusion.
Key Takeaway
When a teammate rebases a shared branch, do not merge. Fetch, hard-reset your local branch to origin/<branch>, then cherry-pick or rebase any local work. Coordination prevents duplicate commits and wasted debugging time.

Decision Guide: When to Use Merge vs Rebase

The choice between merge and rebase is not about personal preference — it's about branch type, team workflow, and what kind of history your project needs.

Use Merge When: - Integrating a shared branch (main, develop, release/*, hotfix). These branches have multiple contributors. Rebasing them would rewrite everyone's history. - Landing a large feature that has many commits and you want to preserve the internal development context. The merge commit acts as a bookmark. - Bringing a long-lived branch up to date with its base. Merge creates a single commit that records when the synchronization happened. - Working on a branch that multiple people are actively pushing to. Merge is the only safe way to integrate.

Use Rebase When: - Updating your private feature branch with changes from main. Your branch hasn't been pushed or others haven't pulled from it. - Clean up your branch's commit history before opening a pull request (interactive rebase). - You want a linear, easy-to-read git log that simplifies tools like git bisect and git log --oneline. - You're applying a sequence of patches from another branch (with --onto).

Philosophical Debate: Some teams advocate for rebase-only workflows because they produce a clean history. Others argue merge commits are essential for understanding the project's timeline. In practice, most mature teams use both: merge for shared branches and rebase for feature branches, with interactive rebase for PR preparation. The key is to establish a team convention and stick to it.

io/thecodeforge/git/DecisionGuide.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# io.thecodeforge — Decision Flow: Merge or Rebase?

# Quick personal algorithm:
# 1. Is this branch shared with other developers?
#    YES → merge
#    NO → go to 2
# 2. Is this branch long-lived (more than a week)?
#    YESmerge (preserve context)
#    NO → go to 3
# 3. Do you need to clean up commits before a PR?
#    YES → interactive rebase then push
#    NO → rebase onto target for linear history or merge if team convention
Output
# Example outputs for each decision:
# Shared branch: use merge
# Long-lived branch: use merge
# Private feature branch with messy commits: use rebase -i
# Private feature branch with clean commits: rebase onto main
Establish a Team Convention
The most important thing is consistency. If your team uses merge for everything, that's fine. If they use rebase for features and merge for shared branches, that's also fine. The worst situation is half the team using one strategy and half using another — that creates unpredictable history and confusion during code reviews.
Production Insight
In the production incident (6 devs lost 3 hours), the mistake was using rebase on a shared branch. The fix was adding branch protection rules and documenting the decision guide. The team now has a simple rule: rebase only on branches that you are the sole contributor to. For everything else, merge. This single rule prevents the most common and costly Git disaster.
Key Takeaway
Decision framework: merge for shared/long-lived branches, rebase for private feature branches, interactive rebase for PR preparation. Establish a team convention and enforce it with branch protection rules.
● Production incidentPOST-MORTEMseverity: high

Rebase on develop Branch: 6 Developers Lose 3 Hours Coordinating Recovery

Symptom
Six developers reported 'Your branch and 'origin/develop' have diverged' on their next git pull. Some developers had duplicate commits in their local history. One developer merged the diverged branches, creating a merge commit that contained the same changes twice. The CI pipeline built from develop and deployed code with duplicate logic paths.
Assumption
The team initially assumed a GitHub issue or a corrupted linear history that makes git bisect more remote. They checked GitHub status, repository settings, and network connectivity. Nobody suspected the rebase — the senior developer had force-pushed quietly and did not announce it.
Root cause
1. The senior developer ran git rebase main on the develop branch to remove merge commit clutter. 2. The rebase rewrote all commit SHAs on develop. 3. They force-pushed: git push --force origin develop. 4. Six teammates had feature branches that were branched off the old develop (with old SHAs). 5. On their next git fetch, their local origin/develop now pointed to the rebased commits (new SHAs). 6. Their feature branches were still based on the old develop (old SHAs). 7. Git saw the branches as diverged — the old commits and new commits had different parents. 8. One developer tried to merge origin/develop into their feature branch, creating a merge commit with duplicate changes. 9. The CI pipeline built from develop and deployed the duplicate-logic code.
Fix
1. Immediate: all 6 developers ran git fetch origin && git reset --hard origin/develop to align with the rebased remote. 2. The developer who merged the diverged branches had to git reset --hard to the commit before the merge and re-branch from the rebased develop. 3. Team rule: never rebase develop, main, release/*, or any branch that others have branched from. 4. Added branch protection on GitHub to prevent force-pushes to develop and main. 5. Documented the Golden Rule in the team wiki with a link to this incident.
Key lesson
  • The Golden Rule is non-negotiable: never rebase a branch that another developer has pulled. The moment a commit is shared, its SHA must not change.
  • Force-pushing a rebased shared branch costs every downstream developer time to recover. One bad rebase can cost the team hours.
  • Branch protection rules on GitHub/GitLab prevent force-pushes to protected branches. Configure them for main, develop, and release branches.
  • If you accidentally rebase a shared branch, announce it immediately. Coordinate the recovery before anyone merges the diverged state.
Production debug guideSystematic recovery paths for diverged branches, rebase conflicts, and shared-branch rebase disasters.5 entries
Symptom · 01
'Your branch and origin/main have diverged' after a teammate rebased and force-pushed
Fix
1. Your local branch is based on old SHAs that no longer exist on the remote. 2. Do NOT merge — this creates duplicate commits. 3. Fetch: git fetch origin to get the rebased remote state. 4. Hard-reset: git reset --hard origin/main to align with the rebased remote. 5. If you had local commits on top of the old branch: cherry-pick them onto the new base: git cherry-pick <old-commit-hash>.
Symptom · 02
Rebase conflict appears multiple times during one rebase operation
Fix
1. This is normal — rebase replays commits one by one, and each can conflict independently. 2. Resolve the conflict in the file, then git add <file> and git rebase --continue. 3. Do NOT run git commit during a rebase — use git rebase --continue only. 4. If too complex: git rebase --abort to return to pre-rebase state.
Symptom · 03
git push rejected with 'non-fast-forward' after rebasing local commits
Fix
1. You rebased commits that were already on the remote. The remote has old SHAs, your local has new SHAs. 2. If nobody else has pulled your commits: git push --force is safe. 3. If others have pulled: do NOT force-push. Coordinate with them first. 4. Prevention: only rebase commits that have not been pushed yet.
Symptom · 04
Interactive rebase created unexpected commits or lost changes
Fix
1. You may have dropped a commit during interactive rebase (used 'drop' instead of 'pick'). 2. Run git reflog to find the commit hash before the rebase. 3. Cherry-pick the lost commit: git cherry-pick <hash>. 4. If the entire rebase went wrong: git reset --hard ORIG_HEAD to return to pre-rebase state.
Symptom · 05
Merge commit created during rebase — extra commit in rebased chain
Fix
1. You ran git commit instead of git rebase --continue during conflict resolution. 2. The extra commit is now in your rebased chain. 3. Fix: git rebase -i and squash the extra commit into the correct parent. 4. Prevention: always use git rebase --continue during rebase conflict resolution.
★ Git Rebase vs Merge Triage Cheat SheetFast recovery for diverged branches, rebase conflicts, and shared-branch rebase disasters.
'Your branch and origin have diverged' after teammate rebased shared branch
Immediate action
Hard-reset to the rebased remote. Do not merge.
Commands
git fetch origin (get the rebased remote state)
git reset --hard origin/main (align with rebased remote)
Fix now
If you had local commits: cherry-pick them onto the new base. Announce to team immediately.
Rebase conflict at each commit — resolve, continue, conflict again+
Immediate action
This is normal. Resolve each conflict and continue.
Commands
git status (see conflicted files)
git add <file> && git rebase --continue (resolve and continue)
Fix now
If too complex: git rebase --abort to return to pre-rebase state.
git push rejected — 'non-fast-forward' after rebasing pushed commits+
Immediate action
You rebased commits already on the remote. Check if others have pulled them.
Commands
git log --oneline origin/main..HEAD (see your rebased commits)
git log --oneline HEAD..origin/main (see remote-only commits)
Fix now
If nobody has pulled: git push --force. If others have pulled: coordinate, do not force-push alone.
Lost commits during interactive rebase — dropped instead of squashed+
Immediate action
Use reflog to find the lost commits before they expire.
Commands
git reflog | grep 'rebase' (find the rebase operation and pre-rebase state)
git cherry-pick <hash> (recover the dropped commit)
Fix now
If entire rebase went wrong: git reset --hard ORIG_HEAD.
Extra merge commit in rebased chain — ran git commit instead of rebase --continue+
Immediate action
Squash the extra commit out with interactive rebase.
Commands
git rebase -i HEAD~N (open interactive rebase for recent commits)
squash the extra commit into its parent (change 'pick' to 'squash')
Fix now
Prevention: always use git rebase --continue, never git commit during rebase.
Git Merge vs Rebase
Feature / Aspectgit mergegit rebase
History shapeNon-linear — shows parallel development as a graphLinear — appears as a single straight chain of commits
Creates new commitsYes — one merge commit with two parentsYes — new copies of every replayed commit with new SHAs
Conflict resolutionOne session for the entire mergeOne session per replayed commit that causes a conflict
Safe on shared branchesYes — always safeNo — never rebase branches others are working from
Best forIntegrating long-lived or shared branchesUpdating private feature branches and cleaning up before PRs
ReversibilityUndo with git revert of the merge commitAbort mid-process with git rebase --abort; hard to undo after
Commit SHAs preservedYes — existing commits unchangedNo — all replayed commits get new SHAs
Readability of git logCan get noisy on active reposClean and easy to scan linearly
When to avoidWhen you want a pristine linear historyOn public/shared branches like main, develop, release/*
Interactive modeNot availablegit rebase -i for powerful history editing

Common mistakes to avoid

2 patterns
×

Rebasing a shared branch to clean up history

Symptom
Teammates see diverged branches, duplicate commits, and wasted time coordinating recovery.
Fix
Never rebase branches that others have pulled from. Use merge for shared branches. If you accidentally rebase, announce immediately and coordinate hard-resets.
×

Running git commit instead of git rebase --continue during conflict resolution

Symptom
An extra merge commit appears in the rebased chain, cluttering history.
Fix
During rebase conflict, after staging resolved files, always use git rebase --continue, never git commit.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Explain the Golden Rule of rebasing and why it's important.
Q01 of 01SENIOR

Explain the Golden Rule of rebasing and why it's important.

ANSWER
The Golden Rule is: never rebase a branch that others have pulled from. When you rebase, you rewrite commit SHAs. If someone else has the old SHAs, their history diverges from the remote. It forces them to hard-reset or merge, which can duplicate commits and waste time. The rule ensures that shared history remains stable.
🔥

That's Git. Mark it forged?

9 min read · try the examples if you haven't

Previous
Git Branching and Merging
3 / 19 · Git
Next
Git Stash and Cherry-pick