为什么 git stash pop 说它无法从 stash 条目中恢复未跟踪的文件?

Why does git stash pop say that it could not restore untracked files from stash entry?

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

所以我使用以下方法进行更改:

$ git stash push -a

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

然后当我去 pop 存储时,我得到了很多错误:

$ 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

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

为什么一开始会出现这个错误,下次我该如何避免?

我成功重现了你的问题。似乎如果您存储未跟踪的文件然后创建这些文件(在您的示例中,foo.txtbar.txt),那么您对未跟踪的文件进行了本地更改,当您应用 git stash pop.

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

git checkout stash -- .

Here is some further information I found on the previous command.

作为补充说明,请注意 git stash 进行两次提交或三次提交。默认为两个;如果您使用 --all--include-untracked 选项的任何拼写,您将得到三个。

这两个或三个提交在一个重要方面是特殊的:它们在 no 分支上。 Git 通过特殊名称 stash 找到它们。1 不过,最重要的是 Git 让你——和 makes 你——完成这两个或三个提交。要理解这一点,我们需要查看这些提交中的内容。

藏品里面有什么

每个提交都可以列出一个或多个 parent 提交。这些形成了一个图表,其中后来的提交指向较早的提交。存储通常包含两个提交,我喜欢将索引/暂存区内容称为 i,工作树内容称为 w。还请记住,每个提交都包含一个快照。在正常提交中,此快照是 索引/暂存区内容制作的。所以 i 提交实际上是一个完全正常的提交!它只是不在任何分支上:

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

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

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

如果添加 --include-untracked--all,Git 在 iw 之间进行额外提交 u . u 的快照内容是那些未被跟踪但未被忽略的文件(--include-untracked),或者即使被忽略也未被跟踪的文件(--all)。这个额外的 u 提交有 没有 父级,然后当 git stash 生成 w 时,它设置 wthird 这个 u 提交的父级,这样你会得到:

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

Git 同时, 删除u 提交中结束的任何工作树文件(使用 git clean做到这一点)。

正在恢复存储

当您前往 恢复 一个藏匿处时,您可以选择使用 --index 或不使用它。这告诉 git stash apply(或内部使用 apply 的任何命令,例如 pop)它应该 使用 i承诺尝试修改您当前的索引。此修改是通过以下方式完成的:

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

(或多或少;这里有很多细节阻碍了基本思想)。

如果省略 --indexgit stash apply 将完全忽略 i 提交。

如果存储只有两个提交,git stash apply 现在可以应用 w 提交。它通过调用 git merge2(不允许它提交或将结果视为正常合并),使用进行隐藏的原始提交(i 的父级,和 w 的第一个父级)作为合并基础,w 作为 --theirs 提交,当前(HEAD)提交作为合并的目标.如果合并成功,那么一切都很好——嗯,至少 Git 是这么认为的——并且 git stash apply 本身也成功了。如果您使用 git stash pop 应用存储,代码现在 删除 存储。3 如果合并失败,Git 声明申请失败。如果您使用 git stash pop,代码将保留存储并提供与 git stash apply.

相同的失败状态

但是如果您有 第三次 提交——如果在您正在应用的存储中有一个 u 提交——那么事情就变了! 没有选项假装u提交不存在。4 Git坚持提取所有文件 u 提交到当前工作树。这意味着这些文件要么根本不存在,要么与 u 提交中的内容相同。

要做到这一点,您可以自己使用 git clean — 但请记住,未跟踪的文件(无论是否忽略)在 Git 存储库中没有其他存在,因此请确保这些文件都可以被摧毁!或者,您可以创建一个临时目录,然后将文件移到那里以便妥善保管——或者甚至再做一个 git stash save -ugit stash save -a,因为这些将为您 运行 git clean。但这只会给你留下另一个 u 风格的藏匿处以供稍后处理。


1这其实就是refs/stash。如果你创建一个名为 stash 的分支,这很重要:分支的全名是 refs/heads/stash,所以它们并不冲突。但不要那样做:Git 不会介意,但你会把自己弄糊涂。 :-)

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>,也许。

扩展 :该代码仅恢复 tracked 文件,即使您在以下情况下使用 --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 中),然后删除存储。一些注意事项:

  • 小心 - 这会用你的隐藏内容覆盖所有内容!
  • 使用 git checkout 恢复文件会导致它们全部自动暂存,所以我添加了 git reset 来取消暂存所有内容。
  • 一些资源使用 stash@{0}stash@{0}^3,在我的测试中,无论是否使用 @{0}
  • ,它都一样

来源:

  • Force git stash to overwrite added files(链接在 Daniel Smith 的回答中)
  • How can I checkout an untracked file in a git stash?(有关魔术 stash^3 提交的信息)
  • Atlassian guide to git stash(查看 "How git stash works" 了解内部提交的详细信息)

除了其他答案,我做了一个小技巧

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

我刚遇到这个问题。由于上面的答案在不丢失存储文件的情况下不起作用,所以我即兴创作。

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

> 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. 我 re-applied 存储未暂存。

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