Resolving Rebase Conflicts

A rebase conflict happens when Git tries to replay one of your commits onto a new base and finds that the change can’t be applied cleanly — usually because the same lines were edited on both sides. Unlike a merge conflict, which happens once, a rebase can pause repeatedly, once per commit that doesn’t apply cleanly. Knowing how to read the paused state, fix it, and tell Git to keep going is one of the most important day-to-day Git skills, because rebasing is such a common way to keep feature branches up to date.

Overview / How it works

git rebase doesn’t move your existing commits — it builds brand-new commits. For each commit on your branch that isn’t on the target branch, Git takes the patch (the diff) that commit introduced and tries to apply it on top of the new base, one commit at a time, in the same order they were originally made. Each successfully applied patch becomes a new commit object with a new SHA-1, a new parent, but (normally) the same author, message, and content. This is why rebasing is history-rewriting: the old commits still exist in Git’s object database for a while, but your branch pointer moves to point at the new chain, and the old commits become unreachable and eventually garbage-collected.

A conflict occurs when Git can’t automatically merge a patch’s changes into the current file content — typically because both the commit being replayed and the new base changed overlapping lines. When this happens, Git stops mid-rebase. It does not abort; it pauses with:

  • Your HEAD detached, sitting on the partially-rebased commit chain.
  • A .git/rebase-merge/ (or .git/rebase-apply/ for the older patch-based backend) directory tracking which commits are done and which remain in the "todo" list.
  • The conflicting file(s) in your working tree containing conflict markers (<<<<<<<, =======, >>>>>>>).
  • The index holding the file in an "unmerged" state — git status will list it under Unmerged paths.

Your job is to edit the file(s) so they reflect the intended final content, tell Git the conflict is resolved by staging the file with git add, and then tell the rebase to proceed with git rebase --continue. Git then finishes creating that commit (with the resolved tree) and moves on to the next patch in the todo list — which might conflict again. This can repeat several times for a single rebase if multiple commits touch the same lines.

Because rebase rewrites commits and SHAs, never rebase a branch that other people have already pulled and built work on top of — anyone with the old commits will end up with a diverged, confusing history when they next pull. Rebase freely on your own local, unpublished feature branches.

Syntax

git rebase <upstream>
# ... conflict happens, resolve files, then:
git add <file>
git rebase --continue
Command What it does during a paused rebase
git status Shows which commit is being replayed and which paths are unmerged.
git diff Shows the conflict markers and surrounding context in the working tree.
git add <file> Marks a conflicted file as resolved by staging your final version.
git rebase --continue Finishes the current commit with the resolved content and replays the next patch.
git rebase --skip Drops the current commit entirely (its changes are discarded) and moves to the next one.
git rebase --abort Cancels the whole rebase and returns the branch to exactly where it was before you started.
git mergetool Launches a configured visual merge tool to resolve conflicts interactively.
git rerere ("reuse recorded resolution") When enabled, remembers how you resolved a conflict and auto-applies the same fix if it recurs.

Examples

Example 1: A single conflicting commit

Suppose main and your branch feature/login-page both edited the same validation line in src/login.js.

git switch feature/login-page
git rebase main

Output:

Auto-merging src/login.js
CONFLICT (content): Merge conflict in src/login.js
error: could not apply 4f2a9c1... fix: correct login validation regex
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
Could not apply 4f2a9c1... fix: correct login validation regex

Git has already applied every earlier commit cleanly and stopped at 4f2a9c1. Opening src/login.js shows conflict markers around the validation logic. After editing the file to the correct combined version:

git add src/login.js
git rebase --continue

Output:

Successfully rebased and updated refs/heads/feature/login-page.

Because that was the only conflicting commit, --continue finished the whole rebase in one step.

Example 2: Multiple commits, multiple conflicts

With several commits touching the same file, the rebase can pause more than once. git status mid-rebase tells you exactly where you are:

git status

Output:

interactive rebase in progress; onto 9a1c3de
Last command done (1 command done):
   pick 4f2a9c1 fix: correct login validation regex
Next command to do (2 remaining commands):
   pick 7b81ffa fix: trim whitespace on username field
You are currently rebasing branch 'feature/login-page' on '9a1c3de'.

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   src/login.js

After resolving and continuing once, the next commit (7b81ffa) may conflict again on the same file. Repeat the cycle: edit, git add, git rebase --continue. If a particular commit turns out to be obsolete and you’d rather drop its change entirely instead of resolving it, use git rebase --skip instead of resolving:

git rebase --skip

This discards the currently-conflicting commit’s patch completely and moves on — use it deliberately, not as a shortcut to avoid thinking about the conflict.

Example 3: Backing out, and letting Git remember resolutions

If a rebase turns into a tangle of conflicts you don’t want to fight through right now, abort and return to the pre-rebase state:

git rebase --abort

Output: (no output on success — feature/login-page is back exactly where it was before git rebase main was run.)

If you expect to hit the same conflict repeatedly (common when rebasing the same branch onto main multiple times as it evolves), enable rerere once per machine:

git config --global rerere.enabled true
git rebase main

Output:

Auto-merging src/login.js
CONFLICT (content): Merge conflict in src/login.js
Resolved 'src/login.js' using previous resolution.
error: could not apply 4f2a9c1... fix: correct login validation regex

Git still pauses so you can confirm, but it pre-fills the file with the resolution you used last time — you typically just review and git add straight away.

How it works step by step

  1. Git checks out the target commit (e.g. the tip of main) into a detached HEAD and writes the list of your branch’s unique commits into .git/rebase-merge/git-rebase-todo.
  2. For each todo entry, Git computes that commit’s diff against its original parent and tries to apply it to the current working tree and index — effectively a cherry-pick.
  3. If the patch applies cleanly, Git writes a new commit object (new tree, new parent = the previous new commit, same message/author by default) and advances to the next todo entry automatically.
  4. If it can’t apply cleanly, Git writes conflict markers into the affected file(s), leaves the index with unmerged ("stage 1/2/3") entries for that path, and stops — rebase-merge state files record exactly where it paused.
  5. You edit the working tree file to the correct final content and run git add, which removes the unmerged stage entries and records your resolved version in the index.
  6. git rebase --continue creates the new commit from that resolved index state, then resumes replaying the remaining todo entries.
  7. Once every entry is applied, Git moves the branch reference to the tip of the new commit chain and reattaches HEAD to the branch — the rebase is complete and the old commits become unreachable.

Common Mistakes

Forgetting to stage the resolved file

git rebase --continue

Output:

error: you have not resolved everything, and you have staged changes in your current index that will be committed.
hint: Committing is not possible because you have unmerged files.

Editing the file isn’t enough — Git only considers a conflict resolved once it’s staged. Run git add <file> before git rebase --continue.

Using git commit instead of --continue

git add src/login.js
git commit -m "fix conflict"

This creates an extra, out-of-place commit instead of letting the rebase machinery finish the commit it was already building, and can leave the rebase state confused about what’s next. Always use git rebase --continue while a rebase is in progress; let Git create the commit.

Rebasing a branch other people already pulled

git switch main
git rebase feature/login-page

Rebasing a shared branch rewrites commit SHAs that teammates already have, so their next git pull produces a confusing divergence. Only rebase branches that are still local/unpublished, or that you’re certain no one else has based work on.

Picking a side blindly instead of merging the logic

Deleting one side’s conflict-marker block without reading both changes often silently drops a needed fix. Always read both versions and, when in doubt, run the tests before continuing.

Best Practices

  • Run git status as soon as a rebase pauses — it tells you exactly which commit and which files are involved.
  • Resolve and re-run tests after every --continue, not just at the end — a bad resolution three commits back is hard to spot later.
  • Use git diff before staging to review the final merged content, not just the conflict markers.
  • Enable rerere if you expect to rebase the same branch repeatedly against a moving base.
  • Don’t hesitate to git rebase --abort if you get lost — it’s completely safe and free.
  • Keep commits small and focused; small commits produce smaller, easier-to-read conflicts.
  • Never rebase a branch others have pulled — follow the golden rule of rebasing.
  • After a successful rebase of a branch you’ve already pushed, push with git push --force-with-lease, never a bare --force.

Practice Exercises

  1. Create a repo, make a branch feature/nav-bar, and on both main and the feature branch edit the same line of a shared file differently, then commit both. Run git rebase main from the feature branch, resolve the conflict, and finish with git rebase --continue. Confirm with git log --oneline that your commit now sits on top of the updated main.
  2. Make three commits on a feature branch, each editing the same function in a file that main also changed. Rebase onto main and work through each conflict pause in turn. Expected end state: a clean rebase with three new commits and no leftover conflict markers anywhere in the file.
  3. Start a rebase, resolve one conflict incorrectly on purpose (leave a marker in), then realize the mistake and run git rebase --abort. Confirm your branch is back to its exact original state with git status and git log.

Summary

  • A rebase conflict pauses the rebase per-commit when a patch can’t be applied cleanly onto the new base.
  • Git leaves conflict markers in the working tree and unmerged entries in the index; .git/rebase-merge/ tracks progress.
  • Resolve by editing the file, then git add to stage the resolution, then git rebase --continue.
  • git rebase --skip drops the current commit’s change entirely; git rebase --abort cancels and restores the pre-rebase state.
  • Enable rerere to auto-reuse resolutions for recurring conflicts.
  • Never rebase a shared/published branch, and always push a rebased branch with --force-with-lease.