运行git stash
以存储 4 个跟踪文件中的一个文件。
使用git stash show
不仅显示该文件,还显示其他暂存文件。为什么?例如:为什么它显示除我存储的文件"应用程序.xml">以外的任何内容?
$ git status
On branch some/0.0.1
Your branch is up to date with 'origin/some/0.0.1'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/main/java/ServiceServiceImpl.java
modified: src/main/java/util/ServiceUtil.java
modified: src/test/SystemTest.java
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/main/java/util/ServiceUtil.java
modified: src/main/resources/application-local.yml
modified: src/main/resources/application.yml
modified: src/main/resources/logback.xml
$ git stash push src/main/resources/application.yml -m "app.xml stashed" <-- stash just one file
Saved working directory and index state On 0.0.1: app.xml stashed
$ git stash list
stash@{0}: On 0.0.1: app.xml stashed
$ git stash show stash@{0} <---------------- Why does it show "staged" files other than the one I stashed (which is src/main/resources/application.yml)
.../java/ServiceServiceImpl.java | 8 ++++----
.../java/util/ServiceUtil.java | 1 +
src/main/resources/application.yml | 20 ++++++++++++++------
.../test/SystemTest.java | 6 +++---
4 files changed, 22 insertions(+), 13 deletions(-)
这是来自男人 git-stash:
显示 [-u|--包含-未跟踪|-仅-未跟踪] [] [] 将存储条目中记录的更改显示为首次创建存储条目时存储内容与提交之间的差异。
TL;博士
你会看到四个隐藏的文件,因为 Git 确实"隐藏"了四个文件。 这严重滥用了"stash"这个词,但四个存储的文件是你已经暂存的三个文件,加上一个多于git stash push -- <pathspec>
暂存到git stash push
所做的工作树提交中。
长
在我看来,git stash
文档有些缺乏。 这对你的情况尤其不利。 现在的文档比 15 年前更好,但仍然不是很好。 (不幸的是,修复它很难。
从根本上说,git stash
是关于进行两次,有时是三次提交。 我喜欢将这些称为i
、w
和(可选)u
提交。i
提交保存索引状态,w
提交保存工作树状态,u
提交(如果存在)保存部分或全部未跟踪的文件。
这三个提交绑定在一起,以便单个提交哈希 ID 可以找到所有三个提交(对于两次提交情况,则为两个)。 以示意图的方式,我们可以像这样绘制这三个提交:
...--o--o--@ <-- current-branch (HEAD)
|
i-w <-- refs/stash
/
u
也就是说,当一切都说完并完成时,refs/stash
ref(不是分支名称)将包含w
提交的哈希 ID。w
提交具有合并提交的形式,因为它有两个或三个父级:w
的第一个父级是@
,当前提交;w
的第二个父级是i
,索引提交;如果存在,则第三个父级是u
,未跟踪的文件提交。
现在,正如所有 Git 文档中明确解释的那样,索引始终包含每个跟踪文件:毕竟,这是跟踪文件的实际定义。1因此,git stash
通常通过执行一个简单的git write-tree
来使i
提交快照,该只是写出索引中现在的任何内容,完全按照git commit
的方式。 然后 Git 执行相当于git add -u
的操作来添加所有修改的工作树文件,并执行另一个git write-tree
来制作此快照。 如果不需要u
提交(如默认git stash save
或git stash push
情况),我们现在已完成树的制作,可以使用git commit-tree
将两个提交写出为提交对象,然后用git update-ref
更新refs/stash
ref(确保打开 reflogs,以防已经存在存储)。
完成两次提交后,我们现在只需要git reset --hard
使现有索引和工作树与当前提交匹配,这就是原始git stash
代码所做的,或多或少。
唉,事情还没有结束:如果是这样,git stash
就不会那么糟糕了。 但是不,我们现在得到了一堆额外的藏匿风格:
git stash -u
,git stash -a
: 这些使第三次提交;--keep-index
:这会改变git reset
的工作方式;- 新!"部分藏匿"与路径规范!(现在你会付多少钱?
我将跳过描述-u
和-a
选项,只是说这些会导致有时很难取消隐藏的存储。--keep-index
选项基本上意味着我们不是将 Git 重置为当前提交,而是将 Git 重置为保存的索引提交,这旨在(并且在极少数情况下,确实是)用于测试存储索引。 不幸的是,一旦测试完成,我们必须git reset --hard
才能恢复东西,然后我们必须仔细记住要git stash apply --index
,如果我们忘记了,我们刚刚销毁了我们的测试版本。 为了防止这成为一个完整的咆哮(它已经完成了大部分),让我在这里停止这部分并引导人们 pre-commit.com,这是一个比git stash --keep-index
更好的计划。 让我们继续讨论原始问题的核心,关于git stash push -- <pathspec>...
.
1请注意git add -N
的奇怪之处,它为未完全跟踪的"新"文件制作索引条目。 Git 在这样的索引条目中表现得很糟糕,以至于拒绝创建新的存储,所以就是这样。(在此处插入和/或面。
Git-stash-push with pathspecs
现在,我知道关于git stash show
的问题,但要到达那里,我们必须解决带有路径规范的推送部分。 这样做会影响进入存储提交的内容以及以复杂方式git reset
(或git clean
-ed)的内容。 您仍然会得到相同的两到三次提交:这仍然是git stash
的基础。
请记住,索引已经包含每个文件。 如果您已对某些文件运行git add
,则它已经包含与当前(HEAD
或@
)提交中显示的版本不同的这些文件的版本。 在您的设置中就是这种情况,如下所示:
$ git status On branch some/0.0.1 Your branch is up to date with 'origin/some/0.0.1'. Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: src/main/java/ServiceServiceImpl.java modified: src/main/java/util/ServiceUtil.java modified: src/test/SystemTest.java
在您运行git stash push
之前显示。 请注意要提交文件的三个更改,即在 Git 索引中更新的三个文件。
您现在运行(我将重新排序@matt的参数):
git stash push -m "app.xml stashed" -- src/main/resources/application.yml
您在此处给出的路径规范参数需要有效地运行git add src/main/resources/application.yml
。 但是git stash push
with pathspecs 希望在其i
提交中保存当前索引,因此它像以前一样这样做。 因此,与当前提交相比,此保存的索引修改了三个文件。
然后,它希望在w
提交中保存由git add
命令修改的当前索引。 所以它这样做:它添加第四个文件并使用git write-tree
(或内部等效项)。 因此,与当前提交相比,此新w
提交修改了所有四个文件。
如果此时要进行第三次提交,则使用路径规范git stash push
会将第三次提交的内容限制为由路径规范命名的未跟踪文件。 然后,它仅从工作树中删除这些文件(使用git clean
或等效文件)。四
然后,在创建此存储后,Git 将恢复(相当于git restore -W --source HEAD
)与路径规范匹配的每个工作树文件。 在这里,这只是一个文件,因此您的工作树将修改的暂存提交文件保留为已修改的暂存提交文件。
关于所有这些的棘手之处在于,因为已经为提交暂存了三个文件,并且用于进行w
提交的索引又获得了一个git add
-ed,因此您的存储确实将这四个文件作为w
提交的更新文件。
请注意,如果从零开始:
git reset --hard
(假设这让我们得到三个文件 f1、f2、f3)然后修改一些文件:
echo modified >> f1; echo modified >> f2
并git add
第一个:
git add f1
然后再次修改:
echo doubly modified >> f1
并运行git stash push -- f2
, Git:
- 将当前索引保存为
i
提交,因此i
的 f1 具有modified
,但其 f2 和 f3 没有; - 并将结果保存为
w
提交,因此w
的 f1 和 f2 都添加了modified
; - f1 保留其"双重修改",但工作树中的 f2 现在未修改。
git add
s f2,git restore
唯一的文件 f2,因此工作树中的git stash
命令使用临时索引(从实际索引的副本开始)而不是实际索引来完成所有这些花哨的步法,因此实际索引不受干扰。 暂存提交f1
(单独修改)仍然是暂存提交,git diff --staged
所示。
这非常复杂。 就个人而言,我建议避免git stash
.
4在实现的早期,带有 pathspecs 的git stash push -u
或git stash push -a
有一个相当可怕的错误,它会在提交u
中存储指定的文件,但随后会清除太多文件。 由于这些是未跟踪的文件,因此它们是无法恢复的!