仅存储一个文件后"git stash show"



运行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是关于进行两次,有时是三次提交。 我喜欢将这些称为iw和(可选)u提交。i提交保存索引状态,w提交保存工作树状态,u提交(如果存在)保存部分或全部未跟踪的文件。

这三个提交绑定在一起,以便单个提交哈希 ID 可以找到所有三个提交(对于两次提交情况,则为两个)。 以示意图的方式,我们可以像这样绘制这三个提交:

...--o--o--@   <-- current-branch (HEAD)
|
i-w   <-- refs/stash
/
u

也就是说,当一切都说完并完成时,refs/stashref(不是分支名称)将包含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 savegit stash push情况),我们现在已完成树的制作,可以使用git commit-tree将两个提交写出为提交对象,然后用git update-ref更新refs/stashref(确保打开 reflogs,以防已经存在存储)。

完成两次提交后,我们现在只需要git reset --hard使现有索引和工作树与当前提交匹配,这就是原始git stash代码所做的,或多或少。

唉,事情还没有结束:如果是这样,git stash就不会那么糟糕了。 但是不,我们现在得到了一堆额外的藏匿风格:

  • git stash -ugit 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 pushwith 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 没有;
  • git adds f2,
  • 并将结果保存为w提交,因此w的 f1 和 f2 都添加了modified;
  • git restore唯一的文件 f2,因此工作树中的
  • f1 保留其"双重修改",但工作树中的 f2 现在未修改。

git stash命令使用临时索引(从实际索引的副本开始)而不是实际索引来完成所有这些花哨的步法,因此实际索引不受干扰。 暂存提交f1(单独修改)仍然是暂存提交,git diff --staged所示。

这非常复杂。 就个人而言,我建议避免git stash.


4在实现的早期,带有 pathspecs 的git stash push -ugit stash push -a有一个相当可怕的错误,它会在提交u中存储指定的文件,但随后会清除太多文件。 由于这些是未跟踪的文件,因此它们是无法恢复的!

最新更新