Undo a botched rebase or amend
Once a rebase finishes you dislike the result. Find the branch's pre-rebase entry with git reflog <branch>, then git reset --hard to that selector.
Git · Command
git reflog shows and manages reference logs. These are local records of every position a branch tip, HEAD or another ref has pointed to, so you can find and restore earlier states.
Last updated
Each time a ref like HEAD or a branch moves (commit, reset, rebase, checkout and so on), Git adds an entry with the old and new object IDs and a reason. Running git reflog with no subcommand defaults to show for HEAD. It lists those entries newest first, labelled ref@{n}. Those selectors, and date forms like ref@{yesterday}, work in any command that takes a revision. Other subcommands list, check, write, delete, drop or expire entries.
git reflog [show] [<log-options>] [<ref>]
git reflog list
git reflog exists <ref>
git reflog write <ref> <old-oid> <new-oid> <message>
git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <ref>@{<specifier>}...
git reflog drop [--all [--single-worktree] | <refs>...]
git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]| Name | Type | Description |
|---|---|---|
<ref> | ref name | The reference whose reflog to show, check or change. For show, it defaults to HEAD. |
<ref>@{<specifier>} | reflog selector | An exact reflog entry for delete to remove, e.g. master@{2}. |
--expire=<time> | time | Used by expire: prunes entries older than this time. The default comes from gc.reflogExpire (90 days); all and never are accepted. |
--expire-unreachable=<time> | time | Used by expire: prunes entries older than this time that the branch's current tip cannot reach. The default comes from gc.reflogExpireUnreachable (30 days). |
--all | flag | For expire or drop: work on the reflogs of all refs (for drop, across all worktrees). |
--single-worktree | flag | When used with --all, only handles reflogs from the current worktree. |
--updateref | flag | If the top entry was pruned, sets the ref to the new top entry (ref@{0}). Ignored for symbolic refs. |
--rewrite | flag | When an entry's predecessor is pruned, changes the entry's old SHA-1 to the new SHA-1 of the entry now before it. |
--stale-fix | flag | Prunes entries that point to broken commits, meaning commits that are unreachable and depend on missing objects. |
-n, --dry-run | flag | Shows what would be pruned without pruning anything. |
--verbose | flag | Prints extra information. |
show prints one line per reflog entry (abbreviated hash, selector and reason). exists signals its answer with the exit code: 0 if the reflog exists, non-zero if not.
# Oops: threw away the last two commits
git reset --hard HEAD~2
# Inspect where HEAD has been
git reflog -n 5
# e.g.
# 1a2b3c4 HEAD@{0}: reset: moving to HEAD~2
# 9f8e7d6 HEAD@{1}: commit: add feature B
# Check that the pre-reset state is the one you want
git log --oneline -3 HEAD@{1}
# Restore it
git reset --hard HEAD@{1}
# Optional: preview pruning without changing anything
git reflog expire --expire-unreachable=now --dry-run --all
# Script-friendly existence check
git reflog exists refs/heads/main && echo "main has a reflog"The reflog still holds the commit HEAD pointed to before the reset, so HEAD@{1} lets you inspect that state and return to it.
Once a rebase finishes you dislike the result. Find the branch's pre-rebase entry with git reflog <branch>, then git reset --hard to that selector.
A commit was left behind after reset or checkout, and no branch points to it anymore. Find its hash in the HEAD reflog, then cherry-pick it or create a branch on it.
Scan the HEAD reflog to see which branches and commits you checked out earlier in the day.
Before running gc, use expire or drop to remove reflog entries that still keep sensitive or bulky commits alive.
git log walks the commit graph through parent links, and that history is shared between repositories. The reflog instead records the successive values a ref such as HEAD or a branch has held in your own repository. git reflog show is really git log -g, which walks those entries instead of commit ancestry.
Follow-up: Why can't you use ranges like A..B with git log -g? Because -g walks reflog entries, not ancestry, so commit exclusions (^commit, A..B, A...B) aren't allowed.
Run git reflog to find the entry from before the reset, usually HEAD@{1}. Check it with git log or git show, then run git reset --hard HEAD@{1} or create a branch there. This works because the reflog still references the old commit.
Follow-up: What if the branch itself was deleted? Deleting a branch also deletes its reflog, but HEAD's reflog may still list the commits. If not, git fsck can find dangling commits as long as they haven't been pruned.
git reflog expire removes them, normally when git gc runs it for you. Entries the current tip can still reach expire after gc.reflogExpire (90 days by default). Unreachable entries expire after gc.reflogExpireUnreachable (30 days by default).
Follow-up: Why does the unreachable limit default to a shorter time? Those entries usually come from amends or rebases, so they're no longer part of the project and can go sooner.
git loggit gcgit resetgit prunegit fsckgit update-refgitrevisionsgit refs