Git is an incredibly powerful tool for version control. Much of this power stems
from its underlying append-only object database which ensures that once you’ve
made a commit it can never be changed or unmade. This is great because it means
you can refactor history safe in the
knowledge that you can git reset
back to a working version if something goes
wrong.
One thing I’ve yearned for however is the same functionality for uncommitted changes. Wouldn’t it be great if every time git modified your working tree, it took a backup?
Dangerous git commands
Luckily the number of git commands that actually modify the working tree is small, in fact there are only three commands that you’ll usually come across:
git checkout
(with-f
, or with a filename)git reset --hard
git clean -f
If you’ve used git for a significant length of time, and are not some kind of super-hero, then I can guarantee that you’ll have lost work with each of these.
It was after one particularly badly timed git checkout -f
that I finally decided
that something needed to be done. While retyping out the hours of code that I’d
accidentally deleted I came up with a plan: Every time I wanted to do a
git checkout -f
I’d first store a copy of my working tree into the object
database. Then whenever I realise that I’ve thrown away the wrong changes, I
can recover them.
git-cof && git-foc
To this end I wrote git cof
. All it does is take a backup of the working
tree, and then forward any arguments on to git foc
(the name is intentionally
evocative of the expletive I no longer need to use).
git cof
can be used as a drop-in replacement for all uses of git checkout
,
(excepting -m
and -p
because those flags are incompatible with -f
). This
means that if you use it when changing branch, or when checking out a particular
file, you’ll also get a backup of your unsaved changes.
git foc
just automates the restore process. It finds the latest backup commit
made by git cof
, and re-instates any changes that git cof
removed. You do
have to be somewhat careful with git foc
as if you make conflicting changes
between running git cof
and git foc
, you’ll end up with a merge conflict.
Although git foc
only allows you to restore the most recent backup, all the
other backups are also preserved for at least two weeks. If you need them back
you can tail .git/tree_backups
and use normal git commands to reinstate
changes from those commits.
git reset –keep
While I was considering writing an equivalent script to wrap git reset --hard
I discovered git reset --keep
. This mode of git reset
was added in
git version 1.7.1, and is exactly like git reset --hard
except that it
preserves any uncommitted changes in the working tree.
Concretely that means that it makes your current branch point to the commit you specify, and then it re-applies all of your uncommitted changes. If it can’t do this properly, then it will abort and tell you what went wrong.
This means that there’s now no reason to use git reset --hard
ever. You can
always use git reset --keep
. In the rare cases that it aborts because you have
uncommitted changes that conflict with the reset, you can use git cof
to throw
away your local changes in a safe way, and then retry.
Preserving untracked files
One thing that git cof
doesn’t do is preserve files that are untracked (i.e.
you haven’t yet git add
ed them). This is for consistency with most git commands
which refuse to touch untracked files; and also to ensure that it doesn’t
accidentally add a large number of useless files to your git object database.
This means that git cof
doesn’t help avoid the data-loss caused by
git clean -f
yet. I’ve not implemented a solution that does and instead just
avoid using git clean
at all. Instead I use rm
manually as I find that
copying and pasting the list of files to remove one-by-one makes me think a
little harder about whether I actually want to delete it.
Summary
Internalising three things can thus help you avoid losing most work with git:
- Use
git cof
instead ofgit checkout
- Use
git reset --keep
instead ofgit reset --hard
- Use
rm
instead ofgit clean -f
I’ve found that these rules have saved me a considerable amount of time that would otherwise be wasted re-writing lost code.
To install git aliae:
Please report bugs and feature requests to GitHub issues.