Safely rebase a Git branch and recover from conflicts
You will move your local commits onto a new base, check the rewritten history, and know how to stop or undo the operation if a conflict appears. Allow 10 to 20 minutes for a clean rebase, or longer if you need to resolve conflicts and run a test suite. The commands below 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. A rebase is normally an unprivileged operation; do not use sudo to fix 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 changing history. Replace the path with your repository directory:
$ cd /path/to/repository
$ git status --short --branch
## feature/login
$ git branch --show-current
feature/login
An empty status line below the branch marker means there are no uncommitted changes. If files are listed, either commit them, stash them deliberately, or stop and decide what they represent. An automatic rebase can use --autostash, but that adds another state transition to recover if the stash cannot be reapplied 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 later with git stash list and restore with git stash pop. Do not drop the stash until the restored files have been checked.
2. Inspect what will be replayed
Choose the base explicitly. The usual target is a local main that has already been updated from its remote. The rebase uses 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 proceed. If your repository uses another primary branch, substitute its exact name. To see the branch's configured upstream rather than guessing it, run:
$ git rev-parse --abbrev-ref --symbolic-full-name '@{u}'
origin/feature/login
The output is only useful if that upstream is the comparison you intend. A plain git rebase uses the configured upstream when one exists, and otherwise stops because it cannot safely infer a base. Giving origin/main makes the operation explicit.
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 can vary, and a fast-forward or already-current branch may produce little output. Verify the result instead of treating a zero exit status as proof that the feature is correct:
$ git status --short --branch
## feature/login
$ git log --oneline --decorate --graph origin/main..HEAD
$ git diff --check origin/main..HEAD
The final command reports whitespace errors if it finds them. Run the project's tests and inspect the diff against the base. Rebase does not prove that the resulting code still behaves as intended.
Warning
The operation rewrites the rebased commits. Do not replace a remote branch with git push --force by reflex. If the branch is shared, coordinate first and prefer the narrower git push --force-with-lease only when you have confirmed that rewriting it 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 commit sides can be confusing during a rebase. With the default merge backend, ours is the already-rebased series based on the new upstream, while theirs is the commit being replayed. Read the conflict markers and surrounding code; do not choose a side merely 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 the editor if you need to stop and reassess. If another conflict appears, repeat this checkpoint. To inspect the patch currently being applied, use:
$ git rebase --show-current-patch
If you decide the current commit should not be replayed, git rebase --skip omits it. Use that only when you have established that its change is already present or no longer wanted; skipping is not a generic conflict-resolution command.
5. Abort safely when the plan is wrong
If the conflict is too risky or the selected base was wrong, restore the pre-rebase branch and working tree with:
$ git rebase --abort
$ git status --short --branch
This 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 where they are. A temporary stash made by --autostash is saved by quit rather than applied. Use quit only when that distinction is intentional.
After a completed rebase, the operation is no longer abortable. If you need the old tip, inspect 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 actions such as reword, edit, squash, fixup or drop, then save the todo file. If you need to change the todo list while it is running, use git rebase --edit-todo. The same --continue, --skip and --abort checkpoint applies.
For a branch with merge commits, ordinary rebase skips those merges. Git 2.43.0 provides --rebase-merges to try to preserve the merge structure, but treat that as a separate, higher-risk operation: review the generated todo list and test the resulting topology before sharing it.
Done means
- The working tree was clean, or its changes were deliberately stashed and later checked.
- You inspected the commits and diff selected for replay.
- The branch now contains the intended commits on the intended base.
- Conflicts were reviewed, marked with
git add, and followed by tests. - You know whether
--abort, the reflog, or a recovery branch is the right undo path. - Any remote rewrite has been agreed with collaborators and uses a current lease.