Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to view untracked files that were "git stash -u"

Tags:

git

git-stash

Stashing content in git is very useful. when stashing untrack files and going through your stash like this

echo test > foo
git stash -u # foo is stashed
git stash show -p stash@{0}

untracked files are not shown. how can we see them?

thanks

like image 766
Mathieu J. Avatar asked Sep 16 '18 19:09

Mathieu J.


1 Answers

You just need to look at the third stash commit. But "just need to" understates things a little bit, and this makes no sense until you know what the three stash commits are. To see what I mean, read on.

Setup: what to know about stashes

When you run git stash save or git stash push, Git's normal action is to create two commits, neither of which are on any branch. If you draw the "before" picture like this, you would have this series of commits:

...--o--o--*   <-- branch (HEAD)

After git stash save completes, you have two new commits that are not on branch branch and not on any other branch either:

...--o--o--*   <-- branch (HEAD)
           |\
           i-w   <-- the stash

The w commit saves the work-tree state while the i commit saves the index. Each of these two commits is very much like any other commit, and in fact, the i commit is made using most of the normal git commit mechanism: Git writes the current index to an internal tree object using git write-tree, then makes a commit object using git commit-tree. If you don't know what these internal objects are, don't worry too much about that: the point is that this is also how git commit does most of its job, by writing a tree and then a commit object. (The rest of git commit consists of collecting your log message first, and updating the branch name at the end.)

The w commit is a little more complicated because it has the form (but not intent) of a merge commit, with two parents instead of one. But basically it saves a snapshot of the work-tree, as if you had run git add on all your tracked files.

When you add --all or --include-untracked (-a or -u for short), what git stash does is that it adds a third commit, which we can draw this way:

...--o--o--*   <-- branch (HEAD)
           |\
           i-w   <-- the stash
            /
           u

The third commit is a snapshot, but it is a very odd snapshot. It contains only the untracked files—either the untracked-but-not-ignored files (git stash save -u), or the untracked files including the untracked-and-ignored files (git stash save -a). It also has no parent commit.

So we have 3 commits in total for:
i - index
w - working-tree
u - untracked

The stash "stack", commit hash IDs, and parents

One reason1 that Git added the push verb, as in git stash push—which is otherwise basically a synonym for save—is that when you make a new stash, Git uses the stash reflog to keep track of earlier stashes. The current stash is stash@{0} in reflog terms, and the earlier stash becomes stash@{1}.

Each of these names is just a specific case of a more general thing that Git gives you: you can refer to any commit by any name that resolves to the correct hash ID. The "true name" of any commit is its big ugly hash ID. The gitrevisions documentation has the complete description of all the ways you can spell a hash ID to Git; using a name like branch or stash is one such way.

Using the name stash locates commit w specifically. Git then uses w itself to find commit i, and, if it exists, commit u. Git can do this because each commit includes the hash IDs of its parent commits. What makes w have the form of a merge commit is that it has at least two parents: i, the index commit, and *, the commit you were sitting on when you ran git stash save or git stash push in the first place.

We can modify most revision specifiers (like stash) by adding a caret ^ character and a number to look specifically at the numbered parent. Writing stash^1 is a way of naming commit *; writing stash^2 is a way of naming commit i. If commit u exists, writing stash^3 names it. Note that on some systems (Windows), ^ may be a special character that requires doubling or quoting, so instead of stash^3 you might need stash^^3.


1The other reason was to add the ability to do partial stashing using pathspecs: git stash save took any extra arguments as a message to include in the stash commits, so they needed a new verb that used -m to specify the message, leaving room for pathspec arguments.


Viewing the u commit

We can view any commit using git show. For stash w commits this misfires because Git thinks that the w commit is a merge, so we can use git stash show instead. (It is a merge, just not one that git show can show properly.) My earlier answer to a related question calls for using git diff on the u commit, because in that particular case, we don't want to get the header that git show shows, but if we just want to look at the untracked file commit, it's OK to use git show here:

git show stash^3

for instance. Here is the output for the foo example above:

$ git show stash^3
commit 4c9bd2486706980f5a492d19c49270381db2d796
Author: Chris Torek <chris.torek gmail.com>
Date:   Sun Sep 16 12:35:03 2018 -0700

    untracked files on master: f72737e initial

diff --git a/foo b/foo
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/foo
@@ -0,0 +1 @@
+foo

Using a name like stash@{1} identifies the commit in reflog entry #1, which is the next stash in your "stash stack". (The reflog starts counting from zero, so stash and stash@{0} mean the same commit.) So for stash@{1} you would need stash@{1}^3 or perhaps stash@{1}^^3.

like image 143
torek Avatar answered Nov 09 '22 03:11

torek