Stashing away your changes

Often, when you've been working on a project, and things are in a messy state not suitable for a permanent conflict, you want to temporarily save the current state and go to work on something else. The answer to this problem is the git stash command.

Stashing takes the dirty state of your working area—that is, your modified tracked files in your worktree (though you can also stash untracked files with the --include-untracked option), and the state of the staging area, then saves this state, and resets both the working directory and the index to the last committed version (to match the HEAD commit), effectively running git reset --hard HEAD. You can then reapply the stashed changes at any time.

Stashes are saved on a stack: by default you apply the last stashed changes (stash@{0}), though you can list stashed changes (with git stash list), and explicitly select any of the stashes.

Using git stash

If you don't expect for the interruption to last long, you can simply stash away your changes, handle the interruption, then unstash them:

$ git stash
$ ... handle interruption ...
$ git stash pop

By default git stash pop will apply the last stashed changes, and delete the stash if applied successfully. To see what stashes you have stored, you can use git stash list:

$ git stash list
stash@{0}: WIP on master: 049d078 Use strtol(), atoi() is deprecated
stash@{1}: WIP on master: c264051 Error checking for <number>

You can use any of the older stashes by specifying the stash name as an argument. For example, you can run git stash apply stash@{1} to apply it, and you can drop it (remove it from the list of stashes) with git stash drop stash@{1}; the git stash pop command is just a shortcut for apply + drop.

The default description that Git gives to a stash (WIP on branch) is useful for remembering where you were when stashing the changes (giving branch and commit), but doesn't help you remember what you were working on, and what is stashed away. However, you can examine the changes recorded in the stash as a diff with git stash show -p. But if you expect that the interruption might be more involved, you should better save the current state to a stash with a description of what you were working on:

$ git stash save 'Add <count>'
Saved working directory and index state On master: Add <count>
HEAD is now at 049d078 Use strtol(), atoi() is deprecated

Git would then use the provided message to describe stashed changes:

$ git stash list
stash@{0}: On master: Add <count>
stash@{1}: WIP on master: c264051 Error checking for <number>

Sometimes the branch you were working on when you ran git stash save has changed enough that git stash pop fails, because there are new revisions past the commit you were on when stashing the changes. If you want to create a regular commit out of the stashed changes, or just test stashed changes, you can use git stash branch <branch name>. This will create a new branch at the revision you were at when saving the changes, switch to this branch, reapply your work there, and drop stashed changes.

Stash and the staging area

By default, stashing resets both the working directory and the staging area to the HEAD version. You can make git stash keep the state of the index, and reset the working area to the staged state, with the --keep-index option.

This is very useful if you used the staging area to untangle changes in the working directory, as described in the section about interactive commits in Chapter 3, Developing with Git, or if you want to split the commit in two as described in Splitting a commit with reset section in this chapter. In both cases you would want to test each change before committing. The workflow would look like the following:

$ git add --interactive
$ git stash --keep-index
$ make test
$ git commit -m 'First part'
$ git stash pop

You can also use git stash --patch to select how the working area should look after stashing away the changes.

When restoring stashed changes, Git will ordinarily try to apply only saved worktree changes, adding them to the current state of the working directory (which must match the staging area). If there are conflicts while applying the state, they are stored in the index as usual—Git won't drop the stash if there were conflicts.

You can also try to restore the saved state of the staging area with the --index option; this will fail if there are conflicts when applying working tree changes (because there is no place to store conflicts; the staging area is busy).

Stash internals

Perhaps you applied stashed changes, did some work, and then for some reason want to un-apply those changes that originally came from the stash. Or you have mistakenly dropped the stash, or cleared all stashes (which you can do with git stash clear), and would like to recover them. Or perhaps you want to see how the file looked when you stashed away changes. For this, you need to know what Git does when creating a stash.

To stash away your changes, Git creates two automatic commits: one for the index (staging area), and one for the working directory. With git stash --include-untracked, Git creates an additional third automatic commit for untracked files.

The commit containing the work in progress in the working directory (the state of files tracked from there) is the stash, and has the commit with the contents of the staging area as its second parent. This commit is stored in a special ref: refs/stash. Both WIP (stash) and index commits have the revision you were on when saving changes as its first (and only for the index commit) parent.

We can see this with git log --graph or gitk:

$ git stash save --quiet 'Add <count>'
$ git log --oneline --graph --decorate --boundary stash ^HEAD
*   81ef667 (refs/stash) On master: Add <count>
|
| * ed95050 index on master: 765b095 Added .gitignore
|/
o 765b095 (HEAD, master) Added .gitignore
$ git show-ref --abbrev
765b095 refs/heads/master
81ef667 refs/stash

We had to use git show-ref here (we could have used git for-each-ref instead), because git branch -a shows only branches, not arbitrary refs.

When saving untracked changes, the situation is similar:

$ git stash --include-untracked
Saved working directory and index state WIP on master: 765b095 Added
 .gitignore
HEAD is now at 765b095 Added .gitignore
$ git log --oneline --graph --decorate --boundary stash ^HEAD
*-.   bb76632 (refs/stash) WIP on master: 765b095 Added .gitignore
| 
| | * 1ae1716 untracked files on master: 765b095 Added .gitignore
| * d093b52 index on master: 765b095 Added .gitignore
|/
o 765b095 (HEAD, B) Added .gitignore

We see that the untracked file commit is the third parent of the WIP commit, and that it doesn't have any parents.

Well, that's how stashing works, but how does Git maintain the stack of stashes? If you have noticed that the git stash list output and the stash@{<n>} notation therein looks like reflog, you have guessed right; Git finds older stashes in the reflog for the refs/stash reference:

$ git reflog stash
81ef667 stash@{0}: On master: Add <count>
bb76632 stash@{1}: WIP on master: Added .gitignore

Un-applying a stash

Let's take the first example from the beginning of the section: un-applying changes from the earlier git stash apply. One possible solution to achieve the required effect is to retrieve the patch associated with working directory changes from a stash, and apply it in reverse:

$ git stash show -p stash@{0} | git apply -R -

Note the -p option to the git stash show command—it forces patch output instead of a summary of changes. We could use git show -m stash@{0} (the -m option is necessary because a WIP commit representing the stash is a merge commit), or even simply git diff stash@{0}^1 stash@{0}, in place of git stash show -p.

Recovering stashes that were dropped erroneously

Let's try the second example: recovering stashes that were accidentally dropped or cleared. If they are still in your repository, you can search all commit objects that are unreachable from other refs and look like stashes (that is, they are merge commits and have a commit message using a strict pattern).

A simplified solution might look like this:

$ git fsck --unreachable |
grep "unreachable commit " | cut -d" " -f3 |
git log --stdin --merges --no-walk --grep="WIP on "

The first line finds all unreachable (lost) objects, the second one filters out everything but commits and extracts their SHA-1 identifiers, and third line filters out even more, showing only merge commits with a commit message containing the "WIP on " string.

This solution would not, however, find stashes with a custom message (those created with git stash save "message").

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset