misc:git-stash

git reflog and git stash

A meta-repository that records — in the form of commits — every change you make to your repository. This means that when you create a tree from your index and store it under a commit (all of which is done by commit), you are also, inadvertently adding that commit to the reflog, which can be viewed using the following command:

$ git reflog   
5f1bc85... HEAD@{0}: commit (initial): Initial commit  

The beauty of the reflog is that it persists independently of other changes in your repository. This means I could unlink the above commit from my repository (using reset), yet it would still be referenced by the reflog for another 30 days, protecting it from garbage collection. This gives you a month’s chance to recover the commit should you discover you really need it. Another peculiarity of git is that it tracks also uncommited changes to your working tree. Let's say for example that you modiffied file foo.c in your working tree, that modification not being commited yet still exists in your file system, you can even see the hash for that modification using:

$ git hash-object foo.c
<some hash id>  

What does this do for you? Well, if you find yourself hacking away on your working tree and you reach the end of a long day, a good habit to get into is to stash away your changes:

$ git stash

This takes all your directory’s contents—including both your working tree, and the state of the index — and creates blobs for them in the git repository, a tree to hold those blobs, and a pair of stash commits to hold the working tree and index and record the time when you did the stash. This is a good practice because, although the next day you’ll just pull your changes back out of the stash with stash apply, you’ll have a reflog of all your stashed changes at the end of every day. Here’s what you’d do after coming back to work the next morning (WIP here stands for “Work in progress”):

$ git stash list
stash@{0}: WIP on master: 5f1bc85... Initial commit
$ git reflog show stash # same output, plus the stash commit's hash id
2add13e... stash@{0}: WIP on master: 5f1bc85... Initial commit
$ git stash apply

Because your stashed working tree is stored under a commit, you can work with it like any other branch — at any time! This means you can view the log, see when you stashed it, and checkout any of your past working trees from the moment when you stashed them:

$ git stash list
stash@{0}: WIP on master: 73ab4c1... Initial commit
...
stash@{32}: WIP on master: 5f1bc85... Initial commit
$ git log stash@{32}                  # when did I do it?
$ git show stash@{32}                 # show me what I was working on
$ git checkout -b temp stash@{32}     # let look at that old working tree!

This last command is particularly powerful: behold, I’m now playing around in an uncommitted working tree from over a month ago. I never even added those files to the index; I just used the simple expedient of calling stash before logging out each day, and stash apply when I logged back in. You could even automate these actions with .login and .logout scripts. If you ever want to clean up your stash list—say to keep only the last 30 days of activity— don’t use stash clear; use the reflog expire command instead:

$ git stash clear            # DON'T! You'll lose all that history
$ git reflog expire --expire=30.days refs/stash
<outputs the stash bundles that've been kept>

The beauty of stash is that it lets you apply unobtrusive version control to your working process itself: namely, the various stages of your working tree from day to day. You can even use stash on a regular basis if you like, with something like the following snapshot script:

$ cat <<EOF > /usr/local/bin/git-snapshot
#!/bin/sh
git stash && git stash apply
EOF
$ chmod +x $_
$ git snapshot

There’s no reason you couldn’t run this from a cron job every hour, along with running the reflog expire command every week or month.

  • misc/git-stash.txt
  • Last modified: 2018/08/14 11:25
  • (external edit)