Git: How can I see the contents of untracked stashed files without applying a stash?

, Somewhere over the East Coast, USA

I thought that I could see the state of an abitrary Git stash by doing:

$ git checkout stash@{0}

It works in the simple case when I stash changes in tracked files.

[mlm@matt-macbook:~/tmp]
$ mkdir git-stash

[mlm@matt-macbook:~/tmp]
$ cd git-stash

[mlm@matt-macbook:~/tmp/git-stash]
$ git init
Initialized empty Git repository in /Users/mlm/tmp/git-stash/.git/

(master)[mlm@matt-macbook:~/tmp/git-stash]
$ git commit --allow-empty -m 'Initial empty commit'
[master (root-commit) 21a1e37] Initial empty commit

(master)[mlm@matt-macbook:~/tmp/git-stash]
$ echo 0 >a

(master)[mlm@matt-macbook:~/tmp/git-stash]
$ git add a

(master)[mlm@matt-macbook:~/tmp/git-stash]
$ git commit -m 'Add file a'
[master d32a50c] Add file a
 1 file changed, 1 insertion(+)
 create mode 100644 a

(master)[mlm@matt-macbook:~/tmp/git-stash]
$ echo 1 >a

(master)[mlm@matt-macbook:~/tmp/git-stash]
$ git stash
Saved working directory and index state WIP on master: d32a50c Add file a
HEAD is now at d32a50c Add file a

(master)[mlm@matt-macbook:~/tmp/git-stash]
$ git checkout stash@{0}
Note: checking out 'stash@{0}'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at c0d712a... WIP on master: d32a50c Add file a

((c0d712a...))[mlm@matt-macbook:~/tmp/git-stash]
$ cat a
1

((c0d712a...))[mlm@matt-macbook:~/tmp/git-stash]
$ git diff master...
diff --git a/a b/a
index 573541a..d00491f 100644
--- a/a
+++ b/a
@@ -1 +1 @@
-0
+1

But the results are surprising when the stash includes untracked files.

[mlm@matt-macbook:~/tmp]
$ mkdir git-stash-untracked

[mlm@matt-macbook:~/tmp]
$ cd git-stash-untracked

[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ git init
Initialized empty Git repository in /Users/mlm/tmp/git-stash-untracked/.git/

(master)[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ git commit --allow-empty -m 'Initial empty commit'
[master (root-commit) 4d24e50] Initial empty commit

(master)[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ echo 0 >a

(master)[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ git stash save -u
Saved working directory and index state WIP on master: 4d24e50 Initial empty commit
HEAD is now at 4d24e50 Initial empty commit

(master)[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ git checkout stash@{0}
Note: checking out 'stash@{0}'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at ac53020... WIP on master: 4d24e50 Initial empty commit

((ac53020...))[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ cat a
cat: a: No such file or directory

((ac53020...))[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ git diff master...

Where is a?

git log can show it.

((ac53020...))[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ git log --graph --topo-order -u
*-.   commit ac530204c1f3e245078f3e479e2f13eff3a215ed
|\ \  Merge: 4d24e50 085d975 a88e84c
| | | Author: Matt McClure <matt.mcclure@...>
| | | Date:   Fri Jan 10 10:38:39 2014 -0500
| | | 
| | |     WIP on master: 4d24e50 Initial empty commit
| | |    
| | * commit a88e84c84d14655b9efb8c9f8665d07b798d3dcc
| |   Author: Matt McClure <matt.mcclure@...>
| |   Date:   Fri Jan 10 10:38:38 2014 -0500
| |   
| |       untracked files on master: 4d24e50 Initial empty commit
| |   
| |   diff --git a/a b/a
| |   new file mode 100644
| |   index 0000000..573541a
| |   --- /dev/null
| |   +++ b/a
| |   @@ -0,0 +1 @@
| |   +0
| |   
| * commit 085d97557e2310c04e546e1b644904a8a2757eac
|/  Author: Matt McClure <matt.mcclure@...>
|   Date:   Fri Jan 10 10:38:38 2014 -0500
|   
|       index on master: 4d24e50 Initial empty commit
|  
* commit 4d24e5066049a31ff5232e94f5638ba8860c2f9e
  Author: Matt McClure <matt.mcclure@...>
  Date:   Fri Jan 10 10:38:21 2014 -0500

      Initial empty commit

The stash@{0} commit has three parents. The first parent was the HEAD of the working tree at the time the stash was created. The second parent recorded the state of the index. And the third parent recorded the state of untracked files in the working tree.

So why doesn’t a appear in my working tree?

Git hides changes in merge commits unless you ask to see them.

((ac53020...))[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ git log --graph --topo-order -m -u
*-.   commit ac530204c1f3e245078f3e479e2f13eff3a215ed (from a88e84c84d14655b9efb8c9f8665d07b798d3dcc)
|\ \  Merge: 4d24e50 085d975 a88e84c
| | | Author: Matt McClure <matt.mcclure@...>
| | | Date:   Fri Jan 10 10:38:39 2014 -0500
| | | 
| | |     WIP on master: 4d24e50 Initial empty commit
| | | 
| | | diff --git a/a b/a
| | | deleted file mode 100644
| | | index 573541a..0000000
| | | --- a/a
| | | +++ /dev/null
| | | @@ -1 +0,0 @@
| | | -0
| | |    
| | * commit a88e84c84d14655b9efb8c9f8665d07b798d3dcc
| |   Author: Matt McClure <matt.mcclure@...>
| |   Date:   Fri Jan 10 10:38:38 2014 -0500
| |   
| |       untracked files on master: 4d24e50 Initial empty commit
| |   
| |   diff --git a/a b/a
| |   new file mode 100644
| |   index 0000000..573541a
| |   --- /dev/null
| |   +++ b/a
| |   @@ -0,0 +1 @@
| |   +0
| |   
| * commit 085d97557e2310c04e546e1b644904a8a2757eac
|/  Author: Matt McClure <matt.mcclure@...>
|   Date:   Fri Jan 10 10:38:38 2014 -0500
|   
|       index on master: 4d24e50 Initial empty commit
|  
* commit 4d24e5066049a31ff5232e94f5638ba8860c2f9e
  Author: Matt McClure <matt.mcclure@...>
  Date:   Fri Jan 10 10:38:21 2014 -0500

      Initial empty commit

Now I can see that the stash@{0} merge commit reverses the creation of a.

I wonder why Git does that instead of making a stash that looks like this hypothetical alternative:

((ac53020...))[mlm@matt-macbook:~/tmp/git-stash-untracked]
$ git log --graph --topo-order -m -u
*-.   commit ac530204c1f3e245078f3e479e2f13eff3a215ed (from a88e84c84d14655b9efb8c9f8665d07b798d3dcc)
|\ \  Merge: 4d24e50 085d975 a88e84c
| | | Author: Matt McClure <matt.mcclure@...>
| | | Date:   Fri Jan 10 10:38:39 2014 -0500
| | | 
| | |     WIP on master: 4d24e50 Initial empty commit
| | | 
| | * commit a88e84c84d14655b9efb8c9f8665d07b798d3dcc
| | | Author: Matt McClure <matt.mcclure@...>
| |/  Date:   Fri Jan 10 10:38:38 2014 -0500
| |   
| |       untracked files on master: 4d24e50 Initial empty commit
| |   
| |   diff --git a/a b/a
| |   new file mode 100644
| |   index 0000000..573541a
| |   --- /dev/null
| |   +++ b/a
| |   @@ -0,0 +1 @@
| |   +0
| |   
| * commit 085d97557e2310c04e546e1b644904a8a2757eac
|/  Author: Matt McClure <matt.mcclure@...>
|   Date:   Fri Jan 10 10:38:38 2014 -0500
|   
|       index on master: 4d24e50 Initial empty commit
|  
* commit 4d24e5066049a31ff5232e94f5638ba8860c2f9e
  Author: Matt McClure <matt.mcclure@...>
  Date:   Fri Jan 10 10:38:21 2014 -0500

      Initial empty commit

In any case, now I can see all of the changes in a stash, using git log --graph --topo-order -m -u


References:

Enjoyed reading this post? Discuss it on Reddit, or follow me on Twitter.