The Danger of Rebase in Git — 7 Ways It Can Go Wrong (and How to Recover)

Published  ·  LithiumGit Team  ·  13 min read

Git rebase is one of the most useful commands in Git — and one of the easiest to get badly wrong. It gives you a clean, linear history, but it gets there by rewriting history, and rewritten history behaves in ways that surprise even experienced developers: commits show up twice, a teammate's work disappears after a push, or a conflict is resolved the wrong way with no trace of what happened. This guide walks through the seven real dangers of rebase, what causes each one, how to recover when it happens, and a checklist for rebasing safely.

💡 New to rebase?Start with Git merge vs rebase — this article assumes you know what a rebase does and focuses on what can go wrong.

Why Rebase Is Dangerous: It Copies Commits, It Doesn't Move Them

Every danger in this article comes from one fact. When you run git rebase main, Git does not pick up your commits and move them. A commit's hash is calculated from its contents, and its contents include the hash of its parent — so a commit can never be given a new parent. Instead, Git creates a brand-new copy of each of your commits on top of main, then moves your branch label to the last copy.

Beforefeature branched off at C1C1C2C3mainF1F2featureAfter git rebase mainfeature now ends at F2′C1C2C3F1′F2′same changes, new hashesmainfeatureF1F2The originals, left behind — no branch points here.Only the reflog still remembers them.

Rebase creates F1′ and F2′ as new commits with new hashes. The original F1 and F2 still exist, but no branch points at them any more.

On your own machine, that's harmless — the old F1 and F2 are simply forgotten. The trouble starts when someone else still has the originals, or when the copies turn out not to be quite the same as the originals. Each danger below is a variation of this picture.

The one rule behind every rebase dangerRebase replaces commits with copies. Anything that still refers to the originals — a teammate's clone, an open pull request, a branch built on top, a passing CI run — is now out of sync with your branch.

Danger 1: Rebasing a Shared Branch Duplicates Commits

Alice and Bob both work on feature, which contains F1 and F2. Alice rebases it onto the latest main and force-pushes. Meanwhile, Bob has committed B1 on top of his copy of F2.

If Bob's Git is set to merge when pulling (pull.rebase=false, a common setting), running git pull sees two unrelated lines of history — Alice's F1′ and F2′ on the remote, and his own F1, F2, and B1 — and does what it always does with diverged branches: it merges them.

Bob's feature after git pullAlice rebased and force-pushed; Bob merged the result into his old copyC1C2C3F1′F2′F1F2B1MfeatureF1 and F1′, F2 and F2′ — the same changes, now in history twice

Bob's branch now contains both the originals and the rebased copies. If he pushes, the duplicates land on the shared branch for everyone.

Git does not treat F1 and F1′ as the same commit. They have different hashes, so to Git they are simply two commits that happen to make similar changes. The log now shows every feature change twice, any difference between an original and its copy can turn into a conflict, and once Bob pushes, the duplicates spread to everyone else on the team.

💡 The Golden Rule of RebasingOnly rebase commits that exist nowhere but on your machine, or on a branch that only you push to. Once someone else might have pulled a commit, merge instead of rebasing.

Danger 2: A Force Push Can Silently Erase a Teammate's Work

After a rebase, your local branch and the remote branch have diverged, so a normal git push is rejected. The usual response is git push --force, which tells the remote to replace its branch with yours no matter what is on it.

If Bob pushed B1 to feature while you were rebasing, your force push throws it away. The remote branch now matches your rebased history exactly, and B1 is no longer on it. Git doesn't warn you, and Bob won't find out until he next pulls.

Remote, before your pushBob pushed B1 while you rebasedC1F1F2B1origin/featureAfter git push --forceB1 is no longer on the remoteC1C2C3F1′F2′origin/featureNo warning — B1 now exists only on Bob's machine

git push --force replaced the remote branch with your rebased history. B1 still exists on Bob's machine, but it is gone from the remote.

Use --force-with-lease, and add --force-if-includes

--force-with-lease adds a safety check: it only overwrites the remote branch if it still points where your last fetch saw it. If someone pushed in the meantime, the push is rejected with stale info, so you can fetch and look before trying again.

Terminal
# Dangerous: overwrites the remote branch unconditionally
git push --force

# Safer: refuses if the remote branch moved since your last fetch
git push --force-with-lease

# Safest: also refuses if you fetched new commits but never integrated them (Git 2.30+)
git push --force-with-lease --force-if-includes
⚠️ Background fetches quietly defeat --force-with-leaseMany editors and Git GUIs fetch automatically in the background. A fetch updates your remote-tracking branch, so --force-with-lease now believes you have seen Bob's B1 — even though it never made it into your branch — and lets the push overwrite it. --force-if-includes closes that gap by also checking that the remote's latest commit was actually integrated into your local branch. Make it the default for every lease push with git config --global push.useForceIfIncludes true.

Danger 3: Conflicts Are Confusing — and Their Resolutions Leave No Trace

A merge stops for conflicts at most once. A rebase can stop once per replayed commit, and you may resolve the same area of code several times over as each commit is replayed. Three things make this riskier than it looks.

"Ours" and "theirs" are swapped

During a rebase, Git builds the new history by checking out the branch you are rebasing onto and replaying your commits on top of it. So from Git's point of view, the upstream branch is "ours" and your own commit is "theirs" — the opposite of what most people expect.

Side of the conflictDuring git merge feature (on main)During git rebase main (on feature)
ours · <<<<<<< side · Current changesmain — the branch you are onmain, plus your commits already replayed
theirs · >>>>>>> side · Incoming changesfeature — the branch being merged inYour commit that is being replayed
git checkout --ours fileKeeps your branch's versionKeeps main's version and discards your change

LithiumGit's conflict editor labels the two sides by Git's conflict markers: Current changes is the <<<<<<< side and Incoming changes is the >>>>>>> side. During a rebase, that means Current is the branch you are rebasing onto and Incoming is your own commit. Click Accept Current Change out of habit and you keep their code and throw away yours. To help you keep your bearings, LithiumGit shows the message of the commit being replayed right above the rebase's Continue, Skip, and Abort buttons.

A bad resolution is invisible

When you resolve a conflict during a merge, the resolution is recorded in the merge commit, where reviewers can see it and git show can display it. During a rebase, your resolution is folded silently into the rewritten commit. If you accidentally delete a line while resolving, the history simply says you wrote the code that way — nothing records that a conflict ever happened.

"Skip" drops the whole commit

⚠️ Skip is not "skip this conflict"git rebase --skip — and the Skip button in LithiumGit, which runs it — throws away the entire commit being replayed, conflicting and non-conflicting changes alike. Use it only when the commit is genuinely no longer needed, for example because the same change already landed on main.

Danger 4: Rebased Commits Were Never Tested

Each original commit was written, and hopefully tested, against the old base. The rebased copies sit on a base they have never been run against. A rebase can finish without a single conflict and still leave broken commits behind.

A typical example: someone on main renames getUser() to fetchUser() and updates every call site. Your F1 adds a new call to getUser() in a different file. The two changes touch different lines, so the rebase applies cleanly — and F1′ doesn't compile. You notice at the end, add a "fix build after rebase" commit, and push. The branch tip works, but F1′ and F2′ stay broken forever, which makes git bisect unreliable for anyone who later hunts a bug through those commits.

Terminal
# Run your tests after every replayed commit; the rebase stops at the first failure
git rebase --exec "npm test" main

When a test fails, the rebase pauses on that commit, so you can fix it right there with git commit --amend and run git rebase --continue — instead of patching it with an extra commit at the end.

Danger 5: Merge Commits Quietly Disappear

By default, git rebase flattens your branch. If it contains merge commits — say you merged a colleague's branch into yours last week — rebase drops those merge commits and replays everything as one straight line. The branch's structure is gone, and conflicts you already resolved in those merges can come back, this time one commit at a time.

Terminal
# Keep merge commits and the branch's shape while rebasing
git rebase --rebase-merges main

--rebase-merges recreates each merge rather than copying it, so a conflict you resolved by hand inside a merge commit still has to be resolved again.

Danger 6: Interactive Rebase Can Delete Commits Without Asking

Interactive rebase (git rebase -i) opens a to-do list of commits for you to reorder, squash, edit, or drop. It's a powerful cleanup tool, and it takes the list literally: delete a line and that commit is gone, with no confirmation and no warning. Similarly, a fixup that you meant to be a squash keeps the changes but silently discards that commit's message.

Terminal
# git rebase -i HEAD~3 opens a to-do list like this:
pick 5c6b7a8 Add login form
pick 1f2e3d4 Add login form validation
pick 9a8b7c6 Add password reset email

# Deleting a line drops that commit. Make Git refuse instead,
# so dropping a commit requires an explicit "drop" command:
git config --global rebase.missingCommitsCheck error

Danger 7: Branches Built on Top Get Left Behind

Suppose feature-b was branched from feature-a so you could keep working while feature-a is in review. If you rebase feature-a onto main, feature-b still points at the original feature-a commits — the ones you just replaced. Rebase feature-b later and Git can end up replaying those old commits as well, which leads to duplicate commits or conflicts with their own copies.

Terminal
# From the top branch, rebase the whole stack and move every branch in it (Git 2.38+)
git checkout feature-b
git rebase --update-refs main

# Or make that the default for every rebase
git config --global rebase.updateRefs true

How to Recover From a Bad Rebase

The good news: a rebase almost never destroys committed work on your own machine. The originals are still in your repository — you just need to know where to look.

While the rebase is still running

Terminal
# Stop, and put everything back exactly as it was before the rebase started
git rebase --abort

In LithiumGit, a paused rebase shows the commit being replayed with Continue, Skip, and Abort buttons in the changes view. Abort runs the same command. Continue is blocked until every conflicted file is resolved, so you can't accidentally carry on with conflict markers still in the code.

Right after it finished

Before it starts, rebase saves the commit your branch pointed at in ORIG_HEAD. If you realize straight away that something went wrong, jump back to it:

Terminal
# Return the branch to where it was before the rebase
git reset --hard ORIG_HEAD
⚠️ Use ORIG_HEAD immediately — or not at allORIG_HEAD is overwritten by the next reset, merge, pull, or rebase. And reset --hard discards uncommitted changes, so check what reset actually throws away before running it.

Days later: use the reflog

Your reflog records every position your branch has pointed at. A whole rebase is recorded in the branch's reflog as a single entry, so the commit just before it is the tip from before the rebase:

Terminal
git reflog show feature
c0c9e56 feature@{0}: rebase (finish): refs/heads/feature onto 665e498769bacc2e62b3b38c3f628d06851c3307
cc76475 feature@{1}: commit: Add login form validation
a137658 feature@{2}: commit: Add login form

# Look at the old tip without touching anything
git log --oneline feature@{1}

# Keep it safe on its own branch...
git branch feature-before-rebase feature@{1}

# ...or move feature back to it
git reset --hard feature@{1}

Rebased-away commits are kept for about 30 days by default (gc.reflogExpireUnreachable) before Git's garbage collection may delete them. And the reflog is local: it only knows about commits that were on your machine. If a force push erased a teammate's commit, that commit lives in their clone and their reflog, not yours — so they are the one who can push it back.

When someone else rebased a branch you were working on

If you are Bob from Danger 1, don't merge the rewritten branch into your old copy. Fetch, then move only your commits onto the new history:

Terminal
git fetch origin

# Replay only the commits made after the old origin/feature onto the new one
git rebase --onto origin/feature origin/feature@{1}

Here origin/feature@{1} means "where origin/feature pointed before that fetch" — the old F2. Everything after it, B1, is replayed onto F2′, and the old F1 and F2 are left behind. In many cases git pull --rebase works this out on its own, but check the result with git log --graph before you push.

The 7 Dangers of Rebase at a Glance

DangerWhat goes wrongSafeguard
Rebasing a shared branchTeammates end up with every commit twiceOnly rebase commits nobody else has
Force pushingA teammate's pushed commits vanish from the remote--force-with-lease --force-if-includes
Resolving conflictsOurs and theirs swap; resolutions leave no traceRead the sides carefully; review with git range-diff
Untested copiesIntermediate commits no longer buildgit rebase --exec "npm test"
Merge commitsThe branch is flattened and merge resolutions are lostgit rebase --rebase-merges
Interactive rebaseA deleted line silently drops a commitrebase.missingCommitsCheck error
Stacked branchesDependent branches still point at the old commitsgit rebase --update-refs

A Safe Rebase Checklist

  1. Make sure the commits are yours alone — not pushed yet, or pushed only to a branch nobody else uses.
  2. Start from a clean working tree — commit or stash anything in progress.
  3. Create a backup branch, so getting back is one command rather than a reflog hunt.
  4. Rebase with tests if you can, so every replayed commit is checked.
  5. Compare old and new with git range-diff, which pairs each original commit with its copy and shows only what changed between them — exactly where a conflict-resolution mistake shows up.
  6. Push with a lease, never a bare --force.
  7. Tell your team if the branch was shared after all, before they pull.
  8. Delete the backup once you are happy with the result.
Terminal — the whole routine
git status
git branch backup/feature
git rebase --exec "npm test" main
git range-diff main backup/feature feature
git push --force-with-lease --force-if-includes
git branch -D backup/feature

One-time setup that makes every rebase safer

Terminal
# --force-with-lease also checks that you integrated what you fetched
git config --global push.useForceIfIncludes true

# Refuse to silently drop a commit deleted from the interactive to-do list
git config --global rebase.missingCommitsCheck error

# Move stacked branches along with the commits they point at
git config --global rebase.updateRefs true

# Remember how you resolved a conflict and reapply it if the same conflict comes back,
# for example when you abort a rebase and start it again
git config --global rerere.enabled true
💡 Rule of thumbRebase what's yours, merge what's shared. Rebase is a great tool for tidying commits before anyone else sees them. Once a commit is shared, treat it as permanent.

Frequently Asked Questions

Is git rebase dangerous?
Rebase is safe on commits that exist only on your machine. It becomes dangerous on commits other people already have, because rebase does not move commits — it replaces them with new copies that have new hashes. Anyone still holding the originals ends up with diverged history, duplicate commits, or, after a force push, lost work.
Can I undo a git rebase?
Yes. While a rebase is still in progress, git rebase --abort puts everything back the way it was before it started. Right after a rebase finishes, git reset --hard ORIG_HEAD moves the branch back. Later on, find the pre-rebase commit with git reflog show <branch> — it is usually <branch>@{1} — and reset to it. Rebased-away commits are kept for about 30 days by default.
Why does rebasing create duplicate commits?
Rebased commits are new commits with new hashes, even when their changes are identical. If a teammate still has the original commits and merges the rebased branch into their copy, Git keeps both versions, so every change appears twice in the history.
What is the difference between git push --force and --force-with-lease?
git push --force overwrites the remote branch no matter what is on it, which can delete commits other people pushed. git push --force-with-lease only overwrites the branch if it still points where your last fetch saw it, and rejects the push otherwise. Adding --force-if-includes also protects you when a background fetch updated your remote-tracking branch without you integrating the new commits.
Why are ours and theirs swapped during a git rebase?
Rebase works by checking out the branch you are rebasing onto and replaying your commits on top of it one by one. So during a rebase, "ours" (Current changes) is the upstream branch plus any commits already replayed, and "theirs" (Incoming changes) is your own commit being replayed — the opposite of a merge.
What does git rebase --skip do?
It drops the commit currently being replayed entirely, not just its conflicting parts. Only use it when that commit's changes are no longer needed, for example because the same change already exists on the branch you are rebasing onto.
Does git rebase change the commit author or date?
Rebase keeps each commit's original author and author date, but the rebased copies get you as the committer, the current time as the commit date, and new hashes. Commit signatures are not carried over, so signed commits have to be re-signed — with your key — using git rebase --gpg-sign.