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
HEADdetached, 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 statuswill 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
- Git checks out the target commit (e.g. the tip of
main) into a detachedHEADand writes the list of your branch’s unique commits into.git/rebase-merge/git-rebase-todo. - 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.
- 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.
- 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-mergestate files record exactly where it paused. - 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. git rebase --continuecreates the new commit from that resolved index state, then resumes replaying the remaining todo entries.- Once every entry is applied, Git moves the branch reference to the tip of the new commit chain and reattaches
HEADto 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 statusas 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 diffbefore staging to review the final merged content, not just the conflict markers. - Enable
rerereif you expect to rebase the same branch repeatedly against a moving base. - Don’t hesitate to
git rebase --abortif 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
- Create a repo, make a branch
feature/nav-bar, and on bothmainand the feature branch edit the same line of a shared file differently, then commit both. Rungit rebase mainfrom the feature branch, resolve the conflict, and finish withgit rebase --continue. Confirm withgit log --onelinethat your commit now sits on top of the updatedmain. - Make three commits on a feature branch, each editing the same function in a file that
mainalso changed. Rebase ontomainand 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. - 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 withgit statusandgit 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 addto stage the resolution, thengit rebase --continue. git rebase --skipdrops the current commit’s change entirely;git rebase --abortcancels and restores the pre-rebase state.- Enable
rerereto auto-reuse resolutions for recurring conflicts. - Never rebase a shared/published branch, and always push a rebased branch with
--force-with-lease.
