Friendly, safe undo for
git— without ever losing work.
git-undo is a tiny zero-dependency Python CLI that wraps git reflog in a way humans can actually read, and lets you undo and redo destructive operations with a single command. Every undo automatically creates a backup branch, so you can always recover.
$ git undo
About to undo: commit
Subject: commit: WIP: refactor auth flow
Reset HEAD to: HEAD@{1} (1f3a4b9) using --soft
Backup branch: undo/backup-20260530-204512 -> a17e2c3
Proceed? [y/N] y
Done.
Recover the previous state with: git checkout undo/backup-20260530-204512
Or roll forward with: git undo redo
git reflog already lets you recover from almost any mistake — but its UX is hostile:
- Selectors like
HEAD@{17}are cryptic. - Subjects like
rebase -i (continue)are ambiguous about what they did to the working tree. - Picking the right
git reset --soft|--mixed|--hardfor the situation is its own footgun. - And you have to remember to
git branch backupfirst, every time, or risk losing work.
git-undo handles all of that for you, with sensible defaults and an explicit confirmation step.
pip install git-undoThis installs a git-undo executable, which Git automatically picks up as the git undo subcommand (any git-foo on your PATH becomes git foo).
Undo the most recent operation. Picks the appropriate reset mode automatically:
| Last operation | Reset mode | Why |
|---|---|---|
commit / amend |
--soft |
Keep your changes staged so you can re-commit |
reset / rebase / merge / cherry-pick / pull / checkout / … |
--hard |
Fully restore the prior working tree |
Before resetting, it creates a branch named undo/backup-YYYYMMDD-HHMMSS pointing at the current HEAD. Nothing is ever lost.
git undo # interactive
git undo -y # no confirmation
git undo --force # allow even with a dirty working treePretty-print the reflog (newest first):
$ git undo list -n 6
HEAD@{0} a17e2c3 commit 2026-05-30T20:44:01+00:00 WIP: refactor auth flow
HEAD@{1} 1f3a4b9 rebase 2026-05-30T20:30:11+00:00 rebase -i (finish): returning to refs/heads/main
HEAD@{2} 8e1c0d2 reset 2026-05-30T19:55:48+00:00 moving to HEAD~3
HEAD@{3} 4c0a91d commit 2026-05-30T19:30:02+00:00 fix flaky test
HEAD@{4} d6f7e22 merge 2026-05-30T18:15:55+00:00 Merge branch 'feature/login'
HEAD@{5} 0b3d8a1 checkout 2026-05-30T18:01:12+00:00 moving from main to feature/login
Jump back to a specific reflog selector:
git undo to 'HEAD@{3}'Restore the state from before your last git undo:
$ git undo
... (undoes the last commit)
$ git undo redo
Redone. HEAD restored to a17e2c3 (mode: --soft).
Backup branch from the previous undo: undo/backup-20260530-204512- Backup branch is always created first, before any destructive action.
- Confirmation prompt by default. Use
-yto skip when you're sure. - Refuses to run with a dirty working tree unless you pass
--force(in which case the backup branch still captures everything). - Doesn't do anything you couldn't do manually. Under the hood it's just
git branch+git resetagainst entries you can already see ingit reflog.
Every backup branch is a normal git branch. If you ever regret an undo:
git checkout undo/backup-20260530-204512Or check what branches git-undo has left behind:
git branch --list 'undo/backup-*'Once you no longer need them, delete:
git branch -D undo/backup-20260530-204512
# Or all at once:
git branch --list 'undo/backup-*' | xargs -r git branch -DDoes this replace git reflog?
No — it's a friendlier UI on top of it. git reflog is still the source of truth.
What if I undo while in the middle of a rebase / merge / conflict?
git-undo will refuse on a dirty tree by default. Pass --force if you know what you're doing; the backup branch still snapshots the current HEAD before the reset.
Does this need an internet connection or send any data anywhere?
No. It's a pure local CLI that shells out to git. No network calls, no telemetry, no dependencies beyond Python's standard library.
git clone https://github.com/cryptopecx/git-undo
cd git-undo
python -m venv .venv && source .venv/bin/activate
pip install -e . pytest ruff
ruff check .
ruff format --check .
pytest -qMIT — see LICENSE.