Search code examples
gitgit-stash

How does `git stash` treat untracked files?


How does git stash treat untracked files?

Git official doc (link) defines git-stash as

git-stash - Stash the changes in a dirty working directory away

But to me (a beginner of git), it is not very obvious what is the exact definition of "dirty working directory". Are untracked files included?

To be more precise, I notice that in a "dirty" working directory, we can have three types of dirty states:

  1. changes made to a file before it has been run git add (changes are in index)
  2. changes made to a file after it has been run git add (changes are not in index but the file is tracked aka "Changes to be committed")
  3. changes made to a file that git add has never been run (the file is untracked)

When we run git stash, what would happen to #1, #2, and #3?


Solution

  • I am trying to provide my own answer here.

    I am new to git. But to me, the one-line describing the command should be revised to this

    git-stash - Stash away the tracked changes in a dirty working directory
                (untracked changes are ignored)
    

    In other words, git stash (without the option -u) would stash #1 and #2 but not #3. And if #1 and #2 are happening to the same file, then #2 will take precedence over #1.

    With the option -u, git stash -u can include also the untracked files. See this SO article (link) for more details.

    Experiment

    The following experiment shows how git stash (with -u) handles the three cases (a based case, file t1.txt in history) is also included.

    Set up four different states

    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ echo "Hello" > t1.txt ; git add t1.txt ; git commit -m "Add t1.txt"
    [f2 7367b85] Add t1.txt
     1 file changed, 1 insertion(+)
     create mode 100644 tmp/t1.txt
    
    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ echo "Hello in Index" > t1.txt ; git add t1.txt
    
    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ echo "Hello in Working Directory (tracked)" > t1.txt
    
    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ echo "Bonjour in Working Directory (untracked)" > t2.txt
    

    Examine the four states

    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ git status
    On branch f2
    Changes to be committed:
      (use "git reset HEAD <file>..." to unstage)
    
            modified:   t1.txt
    
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
            modified:   t1.txt
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    
            t2.txt
    
    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ git diff --cached t1.txt
    diff --git a/tmp/t1.txt b/tmp/t1.txt
    index e965047..a863c48 100644
    --- a/tmp/t1.txt
    +++ b/tmp/t1.txt
    @@ -1 +1 @@
    -Hello
    +Hello in Index
    
    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ git diff t1.txt
    diff --git a/tmp/t1.txt b/tmp/t1.txt
    index a863c48..a0c6962 100644
    --- a/tmp/t1.txt
    +++ b/tmp/t1.txt
    @@ -1 +1 @@
    -Hello in Index
    +Hello in Working Directory (tracked)
    
    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ cat t2.txt
    Bonjour in Working Directory (untracked)
    

    Now perform the stash

    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ git stash
    Saved working directory and index state WIP on f2: 7367b85 Add t1.txt
    
    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ git status
    On branch f2
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
    
            t2.txt
    
    nothing added to commit but untracked files present (use "git add" to track)
    

    Note that t2.txt was not handled by git stash

    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ git stash show
     tmp/t1.txt | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    

    If you lost t2.txt; git stash won't be able to restore it for you

    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ rm t2.txt
    
    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ git stash pop
    On branch f2
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
    
            modified:   t1.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
    Dropped refs/stash@{0} (6a004ffe723ae18d9d5a314c0f5460622b27e300)
    

    Index vs Tracked (Changes not staged for commit)

    By the way, note also that what happened to the state of

    #1 "Hello in Index" (of t1.txt in index before `git stash`)
    #2 "Hello in Working Directory (tracked)" (of t1.txt in worktree before `git stash`)
    

    #1 was also lost after a pair of git stash and git stash pop operations. What was retained was #2. In other words, the working directory state has taken precedence in the stash operation.

    MINGW64 ~/proj/tmp/sandbox/tmp (f2)
    $ cat t1.txt
    Hello in Working Directory (tracked)