Rebase a Git Branch and Recover from Conflicts
Rebase moves your local commits onto a new base, and it rewrites commit IDs while it does it. This guide checks the branch first, replays the commits, resolves conflicts one at a time, and shows how to abort cleanly if the plan turns out to be wrong. Allow 10 to 20 minutes for a clean rebase, longer with conflicts and a test suite to run. The commands use Git 2.43.0, the version installed on the reference machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Checkpoint
Stop after step 3 if the branch contains commits already shared with other people. Rebasing changes commit IDs, so a later force-push can disrupt their work. Rebase is normally unprivileged, do not use sudo to paper over a repository ownership or permission problem without first understanding why it exists.
1. Start with a clean, identifiable branch
Enter the repository and confirm the branch and working tree before touching history. Swap in your own repository directory:
$ cd /path/to/repository
$ git status --short --branch
## feature/login
$ git branch --show-current
feature/login
- Empty status below the branch marker means no uncommitted changes.
- Files listed there mean commit them, stash them deliberately, or stop and decide what they represent.
An automatic rebase can use --autostash, but that is one more state transition to recover if the stash does not reapply cleanly. A deliberate manual stash is easier to audit:
$ git stash push -u -m "before rebase feature/login"
Saved working directory and index state On feature/login: before rebase feature/login
Check it later with git stash list and restore with git stash pop. Do not drop the stash until you have checked the restored files.
2. Inspect what will be replayed
Choose the base explicitly. The usual target is a local main already updated from its remote. Rebase takes the commits reachable from your branch but not from the upstream argument, then replays them one at a time:
$ git fetch origin
$ git log --oneline --decorate --graph origin/main..HEAD
$ git diff --stat origin/main...HEAD
git fetch updates remote-tracking references without merging them into your branch. Read the commit list and diff before you go further; substitute your own primary branch name if it is not main. To see the branch's actual configured upstream instead of guessing:
$ git rev-parse --abbrev-ref --symbolic-full-name '@{u}'
origin/feature/login
That output is only useful if it names the comparison you actually intend. A plain git rebase uses the configured upstream when one exists and otherwise stops rather than guess a base, giving origin/main explicitly removes the ambiguity.
3. Rebase onto the current base
Run the rebase while checked out on the feature branch:
$ git rebase origin/main
Successfully rebased and updated refs/heads/feature/login.
The exact message varies, and a fast-forward or already-current branch may print little. Verify instead of treating a zero exit status as proof the feature works:
$ git status --short --branch
## feature/login
$ git log --oneline --decorate --graph origin/main..HEAD
$ git diff --check origin/main..HEAD
That last command flags whitespace errors if it finds any. Run the project's tests and inspect the diff against the base, rebase proves Git replayed the commits, not that the resulting code still behaves.
Warning
This rewrites the rebased commits. Do not reach for git push --force on a shared branch by reflex. Coordinate first, and prefer the narrower git push --force-with-lease only once you have confirmed rewriting is acceptable and your remote-tracking reference is current.
4. Resolve one conflict at a time
If Git stops, it prints the conflicting paths and leaves the rebase in progress. Inspect the state:
$ git status
interactive rebase in progress; onto 1234abc
You are currently rebasing branch 'feature/login' on '1234abc'.
(fix conflicts and then run "git rebase --continue")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/login.c
The sides can be confusing mid-rebase. With the default merge backend, ours is the already-rebased series based on the new upstream, and theirs is the commit being replayed in, do not pick a side just because its label sounds right.
- Edit each file, remove the conflict markers, and test the result.
- Mark only a reviewed resolution.
$ git diff
$ git add src/login.c
$ git diff --cached --check
$ git rebase --continue
Git may open an editor for the replayed commit message. Save and close it, or cancel if you need to stop and reassess. Another conflict means repeating this checkpoint. To see the patch currently being applied:
$ git rebase --show-current-patch
Tip
If a commit should not be replayed at all, git rebase --skip omits it, but only once you have established its change is already present or genuinely unwanted. It is not a generic conflict-resolution shortcut.
5. Abort safely when the plan is wrong
If the conflict is too risky or the base was wrong, restore the pre-rebase branch and working tree:
$ git rebase --abort
$ git status --short --branch
That is the recovery command for an in-progress rebase. It is not the same as git rebase --quit: quit stops the rebase but leaves HEAD, the index and the working tree exactly where they are, and a temporary stash made by --autostash is saved rather than reapplied. Use quit only when that distinction is exactly what you want.
Once a rebase has completed, it is no longer abortable. Need the old tip? Check the branch reflog rather than assuming ORIG_HEAD still points to it, other commands can overwrite that pseudo-reference:
$ git reflog --date=local feature/login
$ git show --stat 'feature/login@{1}'
Identify the old commit from the reflog before taking any reset or branch-repointing action. A recovery reset changes the branch again, so make a temporary safety branch first if the old state matters:
$ git branch recovery-before-reset 'feature/login@{1}'
6. Use interactive rebase for local cleanup
For commits that have not been shared, interactive mode lets you reorder, edit, squash or drop them. This example opens the last four commits:
$ git rebase --interactive HEAD~4
Keep the first line as pick unless you have a specific reason to change it. Change later lines to reword, edit, squash, fixup or drop, then save the todo file; git rebase --edit-todo lets you change it again while running. The same --continue, --skip and --abort checkpoints apply here too.
Branches with merge commits are a different case: ordinary rebase skips those merges. Git 2.43.0 provides --rebase-merges to try preserving the merge structure, but treat it as a separate, higher-risk operation, review the generated todo list and test the resulting topology before sharing it.
Done means
- Working tree clean, or its changes deliberately stashed and later checked.
- Commits and diff inspected before anything was replayed.
- Branch lands on the intended base with the intended commits.
- Conflicts reviewed, marked with
git add, and followed by tests. - Undo path known: whether
--abort, the reflog, or a recovery branch is right for this situation. - Any remote rewrite agreed with collaborators and pushed with a current lease.