Recover the pre-rebase branch tip without losing current work
Separate an in-progress abort from completed-rebase recovery, preserve both histories, locate the old tip in reflog, compare it, and reset or republish deliberately.
You finish an interactive rebase and realize that a commit disappeared, the wrong branch was rebased, or conflict resolution changed behavior. Do not start another rebase and do not guess a HEAD~N count. Git normally still knows the former branch tip. Preserve the state you have, identify the exact reflog entry, compare both histories, and then choose whether to move the branch.
First determine whether rebase is still running
git status
# If Git says a rebase is in progress:
git rebase --abort
git rebase --abort returns the branch to its original state when the rebase machinery is still active. Completed rebases have no active operation to abort; they require recovery through references or the reflog.
Freeze the current result before investigating
git status --short
git branch rescue/rebased-result HEAD
git tag -a investigation/rebase-result -m 'State before rebase recovery' HEAD
A lightweight rescue branch is usually enough. The tag is optional but creates a named audit point. Do this before resetting so the newly rewritten commits remain easy to find even if reflog entries later expire.
Protect uncommitted work separately
git status --short
git stash push --include-untracked -m "before recovering completed rebase"
A branch points to commits; it does not preserve modified or untracked files. Commit intentional work to a temporary branch or stash it, then verify the worktree is clean. Do not use git reset --hard while relying on uncommitted files for recovery.
Read the branch movement record
git reflog show --date=iso --decorate HEAD
git reflog show --date=iso --decorate feature/payments
The reflog records updates to local references. Look for an entry such as rebase (finish): returning to refs/heads/feature/payments; the entry immediately before the rebase sequence is often the pre-rebase tip. Use timestamps, subjects and commit IDs rather than assuming the same numeric position on another clone.
Resolve the candidate without moving anything
git show --stat --oneline HEAD@{7}
git rev-parse --verify HEAD@{7}^{commit}
old_tip=$(git rev-parse HEAD@{7}^{commit})
printf 'candidate old tip: %s\n' "$old_tip"
Reflog selectors are evaluated when the command runs. Save the resolved object ID before additional operations add reflog entries. The ^{commit} suffix verifies that the candidate resolves to a commit.
Do not treat ORIG_HEAD as permanent evidence
git show --oneline --decorate ORIG_HEAD
git reflog show --date=iso feature/payments
Rebase may set ORIG_HEAD to the original tip, but Git’s rebase documentation warns that other commands—such as another reset—can overwrite it during the operation. Use it as a clue, not the only recovery record. The branch reflog is the stronger trail.
Name the original history before comparing it
git branch rescue/pre-rebase "$old_tip"
git log --graph --oneline --decorate --all --boundary -30
Now both outcomes have stable names: rescue/rebased-result and rescue/pre-rebase. This turns an anxious recovery into a normal comparison and prevents later reflog changes from hiding either side.
Compare patches, not only commit hashes
git range-diff rescue/pre-rebase...rescue/rebased-result
git diff --stat rescue/pre-rebase..rescue/rebased-result
git log --left-right --cherry-pick --oneline rescue/pre-rebase...rescue/rebased-result
Rebase intentionally changes commit IDs. A hash mismatch is expected. range-diff compares two patch series and helps reveal dropped, reordered or materially changed commits. Run tests from both rescue branches when behavior, generated files or merge topology matters.
Choose reset mode from the desired worktree result
| Mode | Index | Tracked worktree | Typical recovery use |
|---|---|---|---|
--soft | Preserved | Preserved | Move branch while keeping all changes staged |
--mixed | Reset | Preserved | Keep changes as unstaged edits |
--hard | Reset | Overwritten | Exact return to old committed tree |
--keep | Reset | Preserves non-conflicting local edits | Safer move with selected local changes |
--hard can delete tracked changes and may overwrite untracked files that obstruct checkout. Use it only after the rescue reference exists and uncommitted work is safely stored.
Move the original branch deliberately
git switch feature/payments
git status --short
git reset --hard rescue/pre-rebase
git log -1 --oneline --decorate
git status --short
This restores the branch name and worktree to the former committed state. The rewritten outcome remains reachable through rescue/rebased-result, so you can cherry-pick a good correction later rather than repeating the whole rebase.
Recover only a missing commit when the rebase is mostly right
git switch rescue/rebased-result
git switch -c feature/payments-repaired
git cherry-pick <missing-old-commit-id>
git range-diff rescue/pre-rebase...feature/payments-repaired
Undoing the entire rebase is not always the best outcome. If one commit was dropped, build a repaired branch from the rebased result and apply only the missing patch. Resolve conflicts, test, and compare the series again.
Handle a branch that was already pushed
git fetch origin
git log --graph --oneline --decorate --all --branches='feature/payments*' -30
git push --force-with-lease origin feature/payments:feature/payments
Rewriting a published branch changes history for collaborators. Agree on the intended tip first and ask others to preserve local work. --force-with-lease refuses the update when the remote-tracking expectation no longer matches; plain --force can erase someone else’s newly pushed work.
Give collaborators a recovery path
git fetch origin
git branch rescue/my-old-work feature/payments
git switch feature/payments
git reset --hard origin/feature/payments
A collaborator with local commits should first name their current tip, then align the shared branch and reapply intentional commits. Do not tell a team to reset blindly without checking each worktree and branch.
Account for reflog retention and clone boundaries
Reflogs are local and expire according to repository configuration. Another clone does not automatically contain your pre-rebase reflog. If the entry is gone, search rescue branches, tags, colleagues’ clones, code-review refs, CI checkout records, server reflogs if available, or unreachable objects before garbage collection.
git fsck --no-reflogs --unreachable
git log --all --reflog --oneline --decorate
git fsck output is a last-resort lead, not proof that an object is the desired tip. Inspect candidates and create a reference immediately once verified.
Close recovery with tests and cleanup
git switch feature/payments
make test
git diff --exit-code origin/main...HEAD
# Only after review and an agreed retention period:
git branch -d rescue/rebased-result
git branch -d rescue/pre-rebase
Test the restored or repaired history, verify the intended remote relationship, and keep rescue references until review is complete. Delete them with non-forcing -d first; Git will warn when commits remain unmerged.
A completed rebase rarely means the old commits vanished immediately. Name the current result, resolve the old tip from reflog, preserve it too, and compare before moving a branch.
Sources: the solved Stack Overflow question (question by webmat; accepted answer by Lara Bailey, CC BY-SA), and Git’s official documentation for reflog, rebase, reset, range-diff, and push --force-with-lease.
Primary source: Review the official reference ↗