`git stash` 如何处理未跟踪的文件?

How does `git stash` treat untracked files?

git stash 如何处理未跟踪的文件?

Git 官方文档 (link) 将 git-stash 定义为

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

但是对我(git的初学者)来说,不是很清楚什么是 "dirty working directory" 的确切定义。未追踪 包含文件?

更准确地说,我注意到 在 "dirty" 工作目录中,我们可以有三种脏状态:

  1. 在 运行 git add 之前对文件所做的更改(更改在索引中)
  2. 在 运行 git add 之后对文件所做的更改(更改不在索引中,但文件被跟踪 "Changes to be committed")
  3. git add 从未 运行 的文件所做的更改(文件未跟踪)

当我们运行git stash时,#1、#2、#3会怎样?

我认为他们的意思是,实际的 更改 只是 git 正在跟踪的文件中的更改。

以一种在 git 中有意义的方式,因为如果您有一个未跟踪的文件,那么它不是更改,除非您跟踪它,否则它什么也不是。您甚至可以使用未跟踪的更改在分支之间自由切换。

从 git 的外部来看,这是错误的,因为它是本地存储库中的实际更改,但由于我们正在谈论 git 功能,那么 [=25] 内部的视图=] 是正确的,尽管它看起来有点不严谨。

这可能看起来像是一个见仁见智的问题,但由于 change 是 git 中的关键字,因此它必须具有单一含义。

较长的描述解决了它:

The command saves your local modifications away and reverts the working directory to match the HEAD commit.

我试图在这里提供我自己的答案。

我是 git 的新手。但是对我来说,描述命令的一行应该修改为这个

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

换句话说,git stash(没有选项 -u)会隐藏 #1 和 #2 但不会隐藏 #3。如果#1 和#2 发生在同一个文件上,那么#2 将优先于#1。

使用选项 -ugit stash -u 也可以包括未跟踪的文件。 有关详细信息,请参阅这篇 SO 文章 (link)。

实验

以下实验显示了 git stash(使用 -u)如何处理 三个案例(基础案例,历史文件t1.txt)也包括在内。

设置四种不同的状态

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

考察四种状态

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)

现在执行隐藏

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)

请注意 t2.txt 未被 git stash

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

如果输了t2.txt; git stash 将无法为您恢复它

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)

索引与跟踪(更改未准备提交)

顺便说一句,还要注意

的状态发生了什么
#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在一对git stashgit stash pop操作后也丢失了。 保留的是#2。换句话说,工作目录状态在存储操作中优先。

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

How does git stash treat untracked files?

在 Git 2.16.x/2.17 之前,可能很糟糕!
git stash -- <pathspec>”错误地删除了未跟踪的文件 与路径规范匹配的目录,已更正。

参见 commit bba067d (06 Jan 2018) by Thomas Gummerer (tgummerer)
帮助:Junio C Hamano (gitster).
(由 Junio C Hamano -- gitster -- in commit 087d1a8 合并,2018 年 1 月 23 日)

stash: don't delete untracked files that match pathspec

参见 Git Glossary pathspec)

Currently when 'git stash push -- <pathspec>' is used, untracked files that match the pathspec will be deleted, even though they do not end up in a stash anywhere.

This is because the original commit introducing the pathspec feature in git stash push (df6bba0 ("stash: teach 'push' (and 'create_stash') to honor pathspec", 2017-02-28, Git v2.13.0-rc0)) used the sequence of:

git reset <pathspec> && git ls-files --modified <pathspec> | 
  git checkout-index && git clean <pathspec>

The intention was to emulate what 'git reset --hard -- <pathspec>' would do.
The call to 'git clean' was supposed to clean up the files that were unstaged by 'git reset'.
This would work fine if the pathspec doesn't match any files that were untracked before 'git stash push -- <pathspec>'.
However if <pathspec> matches a file that was untracked before invoking the 'stash' command, all untracked files matching the pathspec would inadvertently be deleted as well, even though they wouldn't end up in the stash, and are therefore lost.

This behaviour was never what was intended, only blobs that also end up in the stash should be reset to their state in HEAD, previously untracked files should be left alone.

To achieve this:

  • first match what's in the index and what's in the working tree by adding all changes to the index,
  • ask diff-index what changed between HEAD and the current index, and
  • then apply that patch in reverse to get rid of the changes, which includes removal of added files and resurrection of removed files.