git pull --rebase Scrambled My Conflict History

git pull --rebase stopped on a confusing conflict, then git log looks wrong — new SHAs, reordered commits, a 'fix conflict' commit. What is normal, what is broken, and how to recover.

You ran git pull --rebase origin main to sync your feature branch. Git stopped mid-rebase with a conflict in src/config.ts that shows changes you never made, or a blob of markers stitching together three developers’ commits. After you resolve and continue, git log --oneline looks unfamiliar: SHAs are different, commits are in a new order, committer timestamps changed, and sometimes there’s a stray Merge branch 'main' or fix conflict commit you didn’t intend.

Fastest path: if you haven’t pushed yet, run git rebase --abort to get back to exactly where you started, then re-read the divergence with git log --oneline --left-right HEAD...origin/main before retrying. Most of what looks “scrambled” is normal: a rebase rewrites every local commit’s SHA and committer date by design. The genuinely broken cases (dropped commits, a fix conflict commit, a duplicated change) all have clean fixes below.

What is normal vs. what is broken

Symptom after the rebaseNormal?What it means
Every local commit has a new SHANormalRebase re-applies each commit on the new base, so the hash changes
Committer timestamps all updated to “now”NormalAuthor date is preserved; committer date is reset on re-apply
Commits reordered relative to remote onesNormalYour commits now sit on top of origin/main, not interleaved by time
A Merge branch 'main' or fix conflict commit appearedBrokenYou ran git commit instead of git rebase --continue during the rebase
One of your commits is goneBrokenA local merge commit was dropped, or a commit was skipped
The same change shows up twiceBrokenA commit conflicted, then was re-applied, leaving a near-duplicate

Run git log --oneline --graph -15 and git log --format="%h %ai %ci %s" -8 (compare author date %ai to committer date %ci) to classify which row you’re in before you touch anything.

Common causes

Ordered by hit rate, highest first.

1. You ran git commit instead of git rebase --continue

This is the most common way history “scrambles.” A commit conflicted, you fixed the file, git added it, then typed git commit out of habit. That creates a new standalone commit (often auto-named Merge branch 'main' or whatever you typed) inside the rebase instead of folding the resolution back into the commit being replayed. The rebase then keeps going, leaving an orphaned fix conflict-style commit in your history.

How to spot it: git log --oneline -10 shows a commit with a generic message like fix conflict, merge, or resolve that you don’t recognize as a real change.

2. Long-running branch with many remote commits to replay over

The feature branch diverged from main dozens of commits ago. git pull --rebase replays each of your 10 commits on top of, say, 40 new remote commits, and any of those replays can conflict. The cumulative conflict noise is what feels overwhelming, even though each individual conflict is small.

How to spot it: git rev-list --count HEAD..origin/main — if this is more than 10–15, expect the rebase to touch a lot of stale context.

3. The same file changed in several of your local commits

Your branch has three separate commits all touching src/config.ts. On replay, each one can conflict against the remote’s version, producing three separate resolutions that are hard to reason about in isolation — and easy to resolve inconsistently.

How to spot it: git log --oneline -- src/config.ts on your local branch (commits in origin/main..HEAD). If the file appears in more than one of your commits, expect repeat conflicts.

4. A local merge commit confused the rebase and dropped commits

Your branch contains a merge commit (often from an earlier git pull without --rebase). By default git rebase flattens history and does not preserve merge commits — it replays the underlying commits linearly, which reorders them and can drop a side branch you assumed was safe.

How to spot it: git log --oneline --merges origin/main..HEAD — if any merge commits print, they will be flattened. Use git rebase --rebase-merges (or git pull --rebase=merges) if you genuinely need to keep that structure.

5. pull.rebase is set globally, so every git pull silently rebases

git config --global pull.rebase true makes every git pull rebase, even when you expected a merge. You resolve conflicts thinking you’re in a merge, run git commit, and produce exactly the broken state in cause #1.

How to spot it: git config pull.rebase returns true and you didn’t pass --rebase on the command line.

6. git stash pop mid-rebase mixed extra changes into a commit

The rebase paused on a conflict, you ran git stash pop to restore unrelated work, then git add -A and continued — folding stashed changes into a replayed commit where they don’t belong.

How to spot it: git show HEAD (or the suspect commit) contains changes outside that commit’s logical scope.

Shortest path to fix

Step 1: Abort if the rebase is still in progress

git rebase --abort

This returns your branch to its exact pre-rebase state. To check whether a rebase is still mid-flight (the in-progress state lives in .git/rebase-merge/):

git status                       # prints "interactive rebase in progress" if so
git rev-parse --verify REBASE_HEAD   # succeeds (prints a SHA) only during a rebase

There is no git rebase --status subcommand — git status is the canonical check.

Step 2: Re-read the divergence before retrying

git fetch origin
git log --oneline --left-right HEAD...origin/main

--left-right marks local commits with < and remote commits with >. Count each group so you know what the rebase will replay and what it will replay over.

Step 3: For a widely-diverged or shared branch, merge instead of rebase

git pull --no-rebase origin main   # creates one merge commit, no per-commit replay

A merge commit is honest about the integration point and avoids replaying every local commit. Always use merge on a branch others have already pulled — rebasing rewrites SHAs that teammates now have, which causes its own mess later.

Step 4: If you want a clean rebase, squash your local commits first

# Collapse your local commits into one before pulling
git rebase -i origin/main
# In the editor, leave the first commit as 'pick' and mark the rest 'squash' (or 'fixup')
git pull --rebase origin main      # now at most one commit needs replaying → at most one conflict

git rebase -i origin/main opens the list of commits in origin/main..HEAD. Squashing first turns “ten conflicts across ten replays” into one.

Step 5: Resolve conflicts in the correct order (never git commit)

# When a conflict appears during git pull --rebase:
git status                 # lists the conflicted files
# Edit each conflicted file: pick the right code, delete the <<<<<<< / ======= / >>>>>>> markers
git add src/config.ts      # stage every resolved file
git rebase --continue      # NOT git commit

git rebase --continue may open your editor to confirm the commit message — that’s expected; save and close. To skip the editor on a long rebase, run GIT_EDITOR=true git rebase --continue. If a replayed commit’s changes are already present upstream, Git prints “No changes — did you forget to use ‘git add’?”; if you truly have nothing to add, use git rebase --skip rather than forcing an empty commit. Repeat for each replayed commit.

Step 6: Remove a stray fix conflict commit (cause #1)

If you already created a junk commit during the rebase:

git tag backup/before-cleanup HEAD     # safety net
git rebase -i origin/main
# In the editor: change the 'fix conflict' line's 'pick' to 'fixup' to fold it into the
# commit above it, or to 'drop' if it's pure noise

Step 7: Verify the final state

git log --oneline -10
git diff origin/main
git status
git grep -n '<<<<<<<\|>>>>>>>'         # must print nothing — no leftover conflict markers

If you already pushed the broken history, publish the cleaned-up version with:

git fetch origin
git push --force-with-lease            # refuses to clobber teammates' new commits

Prefer --force-with-lease over --force; fetch first so the “lease” reflects the real remote.

How to recover a dropped commit

If Step 7 shows a commit is missing, it isn’t gone — the reflog still points at it:

git reflog --date=relative | head -20      # find the SHA from "before" the rebase
git branch recover-it <that-sha>           # park it on a branch
git cherry-pick <missing-sha>              # or replay just the lost commit onto your branch

See Commits Disappeared After a Rebase for the full recovery walkthrough.

Prevention

  • Keep feature branches short-lived. Rebasing 3 commits over 5 remote commits is trivial; rebasing 10 over 40 is where chaos lives.
  • During a rebase, after git add always run git rebase --continue — never git commit or git merge. That single habit prevents most “scrambled history.”
  • Set the default back to merge and rebase only on purpose: git config --global pull.rebase false.
  • Enable rerere so Git records and auto-replays your conflict resolutions: git config --global rerere.enabled true. This is the single biggest time-saver on long, repetitive rebases.
  • Squash WIP commits into coherent units before pulling, so each replayed commit is independently meaningful.
  • Preview what’s incoming first: git fetch && git log --oneline HEAD..origin/main. No surprises means no panic-git commit mid-rebase.
  • On any branch teammates have pulled, use git pull --no-rebase — never rewrite published history.

FAQ

Q: My commit timestamps all changed after the rebase. Is that a problem? A: No. Git preserves the author date (when you originally wrote the code) but resets the committer date to now, because each commit was physically re-created on the new base. git log --format="%ai" shows the unchanged author date. Only switch to merge if an audit trail depends on committer time specifically.

Q: git pull --rebase dropped one of my commits entirely. How do I get it back? A: It’s still in the reflog. Run git reflog | head -20, find the SHA from before the rebase, then git branch recover-it <sha> and git cherry-pick the missing commit back. Dropped commits almost always come from a flattened local merge commit (cause #4). The detailed steps are in Commits Disappeared After a Rebase.

Q: I created a fix conflict commit by running git commit during the rebase. How do I remove it? A: git rebase -i origin/main, then change that commit’s pick to fixup (folds its changes into the commit above) or drop (if it added nothing real). Tag backup/before-cleanup HEAD first so you can always get back.

Q: How do I keep git pull --rebase from running automatically? A: Check git config pull.rebase. If it’s true, the global setting is forcing it. Reset with git config --global pull.rebase false, then opt into rebase explicitly per pull. There’s no built-in “ask me first” prompt, but a .git/hooks/pre-rebase script can gate it: read -p "Rebase? [y/N]: " yn && [ "$yn" = y ].

Q: What is rerere and should I enable it? A: rerere (Reuse Recorded Resolution) records how you resolved each conflict and auto-applies the same resolution if the identical conflict reappears later in the rebase. It’s invaluable for long rebases with repetitive conflicts. Enable it with git config --global rerere.enabled true; inspect what it remembered with git rerere diff.

Q: After git rebase --continue, Git opened an editor asking me to edit a commit message. Did something go wrong? A: No — recent Git opens the editor on continue so you can confirm or tweak the message of the just-resolved commit. Save and close to proceed. To skip it on a long rebase, run GIT_EDITOR=true git rebase --continue.

Tags: #git #version-control #Troubleshooting