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 rebase | Normal? | What it means |
|---|---|---|
| Every local commit has a new SHA | Normal | Rebase re-applies each commit on the new base, so the hash changes |
| Committer timestamps all updated to “now” | Normal | Author date is preserved; committer date is reset on re-apply |
| Commits reordered relative to remote ones | Normal | Your commits now sit on top of origin/main, not interleaved by time |
A Merge branch 'main' or fix conflict commit appeared | Broken | You ran git commit instead of git rebase --continue during the rebase |
| One of your commits is gone | Broken | A local merge commit was dropped, or a commit was skipped |
| The same change shows up twice | Broken | A 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 addalways rungit rebase --continue— nevergit commitorgit 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 commitmid-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.