为什么Git Stash Pop说它无法从Stash条目中恢复未跟踪的文件?

我有一堆分阶段和未分阶段的更改,我想快速切换到另一个分支,然后再切换回来。

因此,我使用以下命令暂存我的更改:

$ git stash push -a

(事后看来,我可能会使用--include-untracked,而不是--all

然后,当我去弹出储藏室时,我得到了一大堆错误:

$ git stash pop
foo.txt already exists, no checkout
bar.txt already exists, no checkout
...
Could not restore untracked files from stash entry

似乎没有从存储中恢复任何更改。

我还尝试$ git stash branch temp,但显示相同的错误。

我确实想出了一个解决办法,那就是使用:

$ git stash show -p | git apply

灾难暂时避免了,但这引发了一些问题。

为什么一开始会发生这种错误?下次我该如何避免这种错误?

88986 次浏览

我设法重现了你的问题。如果您隐藏未跟踪的文件,然后创建这些文件(在您的示例中,foo.txtbar.txt),则当您应用git stash pop时,未跟踪文件的本地更改将被覆盖。

要解决此问题,可以使用以下命令。这将覆盖任何未保存的本地更改,因此要小心。

git checkout stash -- .

下面是我在前面的命令中找到的一些进一步的信息。

作为一点附加说明,请注意,git stash进行两次提交或三次提交。默认值为2;如果使用--all--include-untracked选项的任何拼写,则会得到3。

这两个或三个提交在一个重要方面是特殊的:它们位于没有分支上。Git通过特殊名称stash来定位它们。1然而,最重要的是Git允许您(使)对这两个或三个提交执行什么操作。为了理解这一点,我们需要看看这些提交中的内容。

储藏室里有什么?

每个提交可以列出一个或多个父母次提交。这些形成了一个图,其中后面的提交指向前面的提交。Stash通常保存两个提交,我喜欢将索引/暂存区域内容称为i,将工作树内容称为w。还请记住,每个提交都保存一个快照。在正常提交中,此快照是索引/暂存区内容创建的。因此,i提交实际上是完全正常的提交!它只是不在任何分支上:

...--o--o--o   <-- branch (HEAD)
|
i

如果您正在创建一个普通的存储,git stash代码现在通过复制所有跟踪的工作树文件(到临时辅助索引中)来创建w。Git将此w提交的第一个父对象设置为指向HEAD提交,并将第二个父对象设置为指向ABC_4_提交。最后,它将stash设置为指向此w提交:

...--o--o--o   <-- branch (HEAD)
|\
i-w   <-- stash

如果添加--include-untracked--all,Git会在iw之间进行额外的提交,uu的快照内容是未跟踪但未忽略的文件(--include-untracked),或即使被忽略也未跟踪的文件(--all)。此额外的u提交具有--all3父级,然后当git stash生成w时,它将w--all4父级设置为此u提交,以便您获得:

...--o--o--o   <-- branch (HEAD)
|\
i-w   <-- stash
/
u

在这一点上,Git还将删除任何在_ABC_中结束的工作树文件0提交(使用git clean来完成)。

恢复一个储藏室

当您转到还原 A存储时,您可以选择使用--index或不使用它。这将告知git stash apply(或内部使用apply的任何命令,如pop),它应使用i提交,以尝试修改当前索引。此修改通过以下方式完成:

git diff <hash-of-i> <hash-of-i's-parent> | git apply --index

(或多或少;这里有一大堆妨碍基本思想的细节)。

如果省略--index,则git stash apply将完全忽略i COMMIT.

如果存储只有两个提交,则git stash apply现在可以应用w提交。它通过调用git mergew1(不允许它提交或将结果视为正常合并),使用在其上进行存储的原始提交(i的父代,以及w的第一个父代)作为合并基础,w作为--theirs提交,并将当前(HEAD)提交作为合并的目标。如果合并成功,则一切都好—好,至少w2是这样认为的—并且git stash apply本身成功。如果您使用git stash pop来应用存储,代码现在w3存储。w4如果合并失败,Git声明应用失败。如果使用git stash pop,则代码将保留存储,并提供与git stash apply相同的失败状态。

但是,如果您有第三提交(如果在您应用的存储中有u提交),那么情况就会发生变化!没有假装u COMMIT不存在的选项。4 Git坚持将u提交的ABC_7_的所有文件提取到当前工作树中。这意味着这些文件要么根本不存在,要么与u COMMIT中的内容相同。

为此,您可以自己使用git clean—但请记住,未跟踪的文件(无论忽略与否)在Git存储库中都不存在,因此请确保这些文件都可以销毁!或者,您可以创建一个临时目录,并将文件移动到该目录中妥善保存,甚至可以执行另一个git stash save -ugit stash save -a,因为它们将为您运行git clean。但这只会给你留下另一个u风格的储藏室,以后再处理。


1这实际上是refs/stash。如果您创建了一个名为stash的分支,这一点很重要:该分支的全名是refs/heads/stash,因此它们并不冲突。但不要这样做:饭桶不会介意,但你会把自己弄糊涂。

2git stash代码实际上在这里直接使用git merge-recursive。由于多种原因,这是必要的,并且还有一个副作用,即确保在解决冲突和提交时,Git不会将其视为合并。

3这就是为什么我建议避免git stash pop,而支持git stash apply。您有机会查看所应用的内容,并确定是否正确应用了真实的。如果没有,请你的货还在吗?,这意味着您可以使用git stash branch来完美地恢复所有内容。好吧,假设缺少讨厌的u提交。

4确实应该有:git stash apply --skip-untracked或其他。还应该有一种变体,其表示将所有u提交文件放入新目录中,例如,可能是git stash apply --untracked-into <dir>

扩展丹尼尔·史密斯的回答:该代码仅恢复已跟踪文件,即使您在创建存储时使用--include-untracked(或-u)。所需的完整代码为:

git checkout stash -- .
git checkout stash^3 -- .
git stash drop


# Optional to unstage the changes (auto-staged by default).
git reset

这将完全恢复已跟踪的内容(在stash中)和未跟踪的内容(在stash^3中),然后删除存储。几点说明:

  • 小心-这将用您的存储内容覆盖所有内容!
  • 恢复_为ABC_0的文件会导致它们全部自动进入暂存状态,因此我添加了git reset来取消暂存所有内容。
  • 一些资源使用stash@{0}stash@{0}^3,在我的测试中,无论有没有@{0},它都能正常工作。

来源:

除了其他答案,我还做了一个小把戏。

  • 已删除所有新文件(已存在的文件,例如问题中的foo.txt和bar.txt)
  • git stash apply(可以使用任何命令,例如APPLY、POP等)

我刚遇到这个问题。因为上面的答案在不丢失文件的情况下不起作用,所以我即兴发挥了。

我的存储,使用git stash -u,有未跟踪的新添加的文件。这些文件中的一些来自代码生成,并在我尝试弹出存储之前单独提交到主分支。当我打开我的储藏室时,我得到了以下信息:

> git stash pop
web-app/src/app/rest-services/api/models/model1.ts already exists, no checkout
web-app/src/app/rest-services/api/models/model2.ts already exists, no checkout
web-app/src/app/rest-services/api/models/model3.ts already exists, no checkout
web-app/src/app/rest-services/api/models/model4.ts already exists, no checkout
error: could not restore untracked files from stash

在Git无法合并2个不同的新文件的理论下,我删除了工作目录中的每个命名文件。然后,我能够应用完整的存储,而不会丢失文件或出现错误。

解决此问题的一种简单方法是将现有冲突文件重命名为非冲突文件。

例如,如果运行git stash apply/pop并获取:

foo.md already exists, no checkout
error: could not restore untracked files from stash

尝试将foo.md重命名为foo.new.md,然后重新运行git stash apply/pop,这次将不会出现错误,您可以使用foo.mdfoo.new.md

以下是我为使其发挥作用而采取的步骤:

  1. 我在工作目录中删除了消息中提到的每个文件。
  2. 我重新申请了未分期的储藏。

应用存储中的所有文件时应没有任何错误。