Everyone has done it. A git reset --hard on the wrong branch, a rebase that
ate three commits, a force-push over a colleague's work. The commits are not
actually gone, git keeps them in the reflog for weeks, but recovering them means
remembering the incantations and reading cryptic HEAD@{4} lines under
pressure.
git-undo is the panic button. One command shows every state your repository
can be restored to as a plain-language timeline, tells you exactly how much work
each one brings back, and restores it safely.
git-undo on main 5 recoverable states
⟲ reset to HEAD~3 now ╭────────────────────────────────────────╮
▌ ✚ committed: wire up login form just now ↩3 │ ✚ committed: wire up login form │
✚ committed: add auth endpoints just now ↩2 │ │
✚ committed: add user model just now ↩1 │ HEAD@{1} 3376decf just now │
✚ initial commit: init project just now │ by dev │
│ │
│ Restoring brings back 3 commits: │
│ 3376dec wire up login form │
│ 1241a55 add auth endpoints │
│ c3c1fbe add user model │
│ │
╰────────────────────────────────────────╯
↑/↓ move enter rescue branch c inspect x reset q quit
The picker opens already pointing at the work you most likely lost, the first state with commits to bring back, not at where you are now.
$ git reset --hard HEAD~3 # oh no
$ git undo # arrow to the lost commit, press enterenter creates a branch at that commit. Your current branch is never touched,
so the recovery itself can't lose anything. Check the branch out when you're
ready.
go install github.com/Vinayak0090/git-undo/cmd/git-undo@latestNeeds Go 1.23 or newer. Because the binary is named git-undo and sits on your
PATH, git picks it up as a subcommand automatically: git undo just works.
git undo # interactive picker (alias for git-undo)
git-undo --plain # print the states, no UI (good for pipes, logs, no-TTY)
git-undo --deep # also hunt for commits lost even from the reflog (slower)--plain is also used automatically when output is not a terminal:
5 recoverable states (most recent first):
→ 1. ⟲ reset to HEAD~3
2c49cbc9 just now HEAD@{0}
2. ✚ committed: wire up login form (brings back 3 commits)
3376decf just now HEAD@{1}
3. ✚ committed: add auth endpoints (brings back 2 commits)
1241a553 just now HEAD@{2}
4. ✚ committed: add user model (brings back 1 commit)
c3c1fbe3 just now HEAD@{3}
5. ✚ initial commit: init project
2c49cbc9 just now HEAD@{4}
Rescue any state safely: git branch rescue/<short-sha> <short-sha>
Or run git-undo in a terminal for the interactive picker.
Restoring is reviewable and safe by default. Every action shows you the exact git command before it runs.
| Key | Action | Safe? |
|---|---|---|
enter |
Create a rescue/<sha> branch at the state |
Yes, nothing else moves |
c |
Check the state out as a detached HEAD to look around | Yes, no branch moves |
x |
Hard-reset the current branch to the state | Backs the branch up to git-undo-backup/<time> first, then asks to confirm |
There is no action that can silently throw work away. The destructive one makes
a backup branch and waits for a y.
- The HEAD reflog, every position HEAD has been at: commits, resets, rebases, amends, merges, checkouts. This is where reset-away and rebased-away work lives.
- Stashes, including ones you forgot you made.
- Dangling commits (with
--deep), commits unreachable from any ref or reflog, such as the contents of a dropped stash. This runsgit fsck, so it is opt-in.
For each state it computes how many commits restoring it would bring back
relative to where you are now (git rev-list <state> --not HEAD), which is the
↩3 badge and the "brings back N commits" list. That is the difference between
a raw reflog dump and knowing which line is the one you want.
It only ever reads until you pick an action, and the actions it offers are ordinary git commands shown to you in full first. The safe default creates a new branch and changes nothing else, so worst case you end up with one extra branch to delete. Nothing here can make your situation worse.
MIT. See LICENSE.