EXPERIMENTAL SOFTWARE, MAY DO DESTRUCTIVE THINGS TO YOUR GIT REPOSITORY. PROBABLY DO NOT USE THIS ON A REPO YOU CARE ABOUT.
Have you ever made a mistake with git and wished you could just type git undo
instead of having to remember a weird incantation? That's the idea behind git oops
.
$ git rebase -i main
# do something bad here
$ git oops undo
# fixed!
The goal is to experiment and see if it's possible to build a standalone undo
feature for git, in the style of the undo features in GitUp,
jj, and git-branchless.
You can think of it as "version control for your version control" -- it takes snapshots of your repository and makes those into git commits that you can restore later to go back to a previous state.
This is really just a prototype -- I think the idea of a standalone undo
feature for git is cool and I'm mostly putting this out there in case it can
serve as inspiration for a better tool that actually works reliably. There's a
long list of problems at the end of this README and it's not remotely ready for
production use.
- Put the
git-oops
script into your PATH somewhere - Install
pygit2
globally on your system - Run
git oops init
in a repository to install the hooks (danger! will overwrite your existing hooks!) - If you'd like, alias
git undo
togit oops undo
Now git-oops
will automatically take a snapshot any time you do anything in
your Git repo.
git oops undo
will undo the last operation.git oops history
shows you the history of all snapshots taken using a curses-based interface and lets you interactively pick one to restore.
git oops record
manually records a snapshot.git oops restore SNAPSHOT_ID
restores a specific snapshot
when git oops record
takes a snapshot, here's what it does:
- save your staging area and workdir: It creates a commit for your current staging area and working directory (very similarly to how
git stash
does). - get HEAD
- get all your branches and tags
- check for uniqueness: If the snapshot is exactly the same as the previous snapshot, it'll exit
- record everything in a commit. Here's an example commit (from this repository). The metadata is stored in the commit message.
FormatVersion: 1
HEAD: refs/heads/main
Index: 20568a3a49feda34ad6aaa3aff7d7a578a8dee0d
Workdir: 4d1a195dc04ab74cfe1cd94da826ce5b0069d264
Refs:
refs/heads/libgit2: c02fc253375108ec797b6af3ca957e8ea0cc36b9
refs/heads/main: 1b4cdfab2900b3b99473560e76e3f91c560364a0
refs/heads/test: 9ac4a5d8e10b04cdddab698e8a9053e7e645543c
refs/heads/test2: 4247707e426f4b890ecd7314376c4d706a2d799d
- update the git-undo reference: It updates
refs/git-undo
to point at the commit it created in step 5. - update the reflog: it updates the reflog for
refs/git-undo
to include the new commit
More details about other commands:
git oops history
retrieves the history from the reflog forrefs/git-undo
git oops restore COMMIT_ID
:- retrieves COMMIT_ID
- runs
git -c core.hooksPath=/dev/null restore --source WORKDIR_COMMIT
- runs
git -c core.hooksPath=/dev/null restore --staged --source INDEX_COMMIT
- updates all the branches and tags from the snapshot. It will not delete any branches or tags, to avoid deleting their reflog.
git oops history
:- runs the equivalent of
git reflog git-oops
to get a list of histories - gives you an interactive UI to choose one to restore
- runs the equivalent of
git oops undo
:- runs the equivalent of
git reflog git-oops
to get a list of histories - finds the first one where any of your references changed and restores that one
- runs the equivalent of
git oops init
installs the following hooks: post-applypatch, post-checkout, pre-commit, post-commit, post-merge, post-rewrite, post-index-change, reference-transaction- when the hooks run, it runs
git oops record
- when the hooks run, it runs
You could imagine using this to share repository snapshots with your coworkers, like
you run:
$ git oops record
23823fe
$ git branch my-broken-snapshot 23823fe
$ git push my-broken-snapshot
they run:
# make a fresh clone
$ git clone your-repo
$ git fetch origin my-broken-snapshot
$ git oops restore 23823fe
now they can see what weird state you ended up in and help you fix it!
I think this doesn't quite work as is though.
Current problems include:
git oops init
overwrites your git hooks and doesn't give you any way to uninstall them. You need to uninstall it manually- It doesn't really support multiple undos
- there's no preview for what it's going to do so it's kind of scary
- makes all of your commits and rebases slower, in some cases MUCH slower. Maybe Python is not a good choice of language? Not sure.
- it's mostly untested so I don't trust it
- probably a million other things
People who helped: Kamal Marhubi, Dave Vasilevsky, Marie Flanagan, David Turner
Inspired by GitUp, jj, and git-branchless, if you want an undo feature to actually use one of those tools is a better bet.