小编典典

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

all

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

因此,我使用以下方法进行了更改:

$ 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

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

为什么首先会发生此错误,下次如何避免它?


阅读 190

收藏
2022-08-01

共1个答案

小编典典

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

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

藏匿处有什么

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

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

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

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

如果添加--include-untrackedor --all,Git 会u在 makeiw.
的快照内容u是那些未被跟踪但未被忽略的--include-untracked文件 ( ),或者即使它们被忽略但未被跟踪的文件 (
--all)。这个额外的u提交 没有 父级,然后当git stashmake时w,它​​将w第三个
父级设置为这个u提交,这样你就得到了:

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

此时,Git 还会 删除 提交中结束的所有工作树文件ugit 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,代码保留存储并提供与 for 相同的失败状态git stash apply

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

要做到这一点,您可以git clean自己动手——但请记住,未跟踪的文件(无论是否忽略)在 Git
存储库中没有其他存在,因此请确保这些文件都可以被销毁!或者,您可以创建一个临时目录,并将文件移到那里以妥善保管——甚至可以再做一个git stash save -uor git 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>,也许。

2022-08-01