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

问题描述 投票:17回答:3

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

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

$ 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

现在避免了灾难,但这提出了一些问题。

为什么这个错误首先发生,下次如何避免?

git git-stash
3个回答
16
投票

作为一个额外的解释,请注意git stash进行两次提交,或三次提交。默认为2;如果你使用--all--include-untracked选项的任何拼写,你得到三。

这两个或三个提交在一个重要方面是特殊的:它们不在分支上。 Git通过特殊名称stash找到它们。然而,最重要的是Git允许你 - 并且让你做这两三次提交。要理解这一点,我们需要查看这些提交中的内容。

What's inside a stash

每个提交都可以列出一个或多个父提交。这些形成了一个图形,后面的提交指向早期的图形。存储通常持有两个提交,我喜欢将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在制作ui之间进行额外的提交,wu的快照内容是那些未跟踪但未被忽略的文件(--include-untracked),或者即使被忽略而未被跟踪的文件(--all)。这个额外的u提交没有父级,然后当git stash制作w时,它将w的第三个父级设置为此u提交,以便您获得:

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

此时,Git还会删除在u提交中使用的任何工作树文件(使用git clean来执行此操作)。

Restoring a stash

当你去恢复藏匿时,你可以选择使用--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不介意,但你会混淆自己。 :-)

2 git 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>


24
投票

我设法重新创建了你的问题。看起来如果您存储未跟踪的文件然后创建这些文件(在您的示例中,foo.txtbar.txt),那么您对未应用的文件进行本地更改,这些文件将在您应用git stash pop时被覆盖。

要解决此问题,可以使用以下命令:

git checkout stash -- .

这将覆盖任何未保存的本地更改,因此请小心。 Here is some further information I found on the previous command


3
投票

要扩展Daniel Smith's answer:即使您在创建存储时使用了--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}都可以使用相同的资源

资料来源:

© www.soinside.com 2019 - 2024. All rights reserved.