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


103

我进行了许多分段和非分段更改,我想快速切换到另一个分支然后再切换回去。

因此,我使用以下命令进行了更改:

$ 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

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

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


31
我不得不使用:git stash show -p | git apply --3
xmedeko

2
唯一对我有用的是“以上评论”!
Mehraj Malik

4
感谢@xmedeko,谁能告诉git stash show -p | git apply和git stash show -p | git apply -3吗?
Deepak Mohandas

5
如果您感到恐慌,只需将隐藏的文件列出,git stash show然后逐个列出救援文件:$ git show stash@{0}:your/stashed/file.txt > your_rescued_file.txt。这将从存储中获取文件,并以其他名称保存。现在,您可以安全地尝试适当的营救方法了(请参见以下答案)。如果事情误入歧途,那么您始终会将抢救的文件作为最后的资源。
Danijel

哇,谢谢@xmedeko!再次,您的评论是唯一有效的方法,它是如此简单。+1!
pcdev

Answers:


104

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

这两个或三个提交在一种重要的方式上很特殊:它们不在任何分支上。Git通过特殊名称查找它们stash1 但是,最重要的是,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-untracked--all,则Gitui和之间进行一次额外的提交w。的快照内容u是未跟踪但未被忽略的--include-untracked文件(),或即使被忽略也未被跟踪的文件(--all)。这个额外的u提交没有父级,然后在git stashmakes时w,将w第三个父级设置为此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

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

如果省略--index,则git 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 混帐坚持提取的所有文件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>


哇!您应该获得一枚奖章!您是否会推荐任何有关此类git详细说明的书?太棒了!♂‍♂️
渗入

1
@seelts不幸的是,Git一直在不断发展,因此书籍很快就会过时。但是Git应该被理解为一组工具,这些命令可以操纵文件的提交或操纵提交图等,而您可以将它们组合成对自己有用的东西,而且似乎没有太多的书可以这样做。方式之一。
torek

3
我无法理解问题的解决方案。只是添加--indexgit stash apply --index
Danijel

1
谢谢@torek。我先做了git stash save --all,然后我马上做了git stash apply,但是有些文件丢失了,因为我重命名了它们,然后又重新创建了(存储之前)。有用的是:git checkout stash@{0} -- .我什至都不会打扰,git checkout stash^3 -- .因为现在一切似乎都很好。我没有时间真正了解正在发生的事情,真是可惜。谢谢。
Danijel

1
@ user1169587:这意味着它的意思。也许一个例子会有所帮助。假设第三次u提交包含一个名为path/to/file(带有某些内容)的文件。进一步假设您的工作树中已经有一个名为的文件path/to/file。提取u提交的内容会将您现有的文件内容替换为新的内容,从而破坏名为的文件中的所有工作path/to/file。所以Git不会那样做。您必须自己先破坏这些内容(通过完全删除文件),或者将它们移出有害的方式(通过移动文件)。
torek '20

110

我设法重新创建了您的问题。看来,如果你藏匿未跟踪文件,然后创建这些文件(在你的榜样,foo.txtbar.txt),那么你在本地修改未跟踪文件,当您申请将被覆盖git stash pop

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

git checkout stash -- .

这是我在上一个命令中找到的更多信息


尽管可以对被忽略的文件进行更改,但我的工作树绝对干净。
steinybot '18年

1
看起来使用--all/-a 将包括被忽略的文件,因此可能是相关的。
丹尼尔·史密斯

1
我将假定相同的逻辑适用于被忽略的文件,并将其标记为答案(尽管git merge --squash --strategy-option=theirs stash在这种情况下,我认为这种方法更好)。
steinybot '18年

同意,我喜欢第二种方法!祝您工作顺利!
丹尼尔·史密斯

这很有用,但它没有还原未跟踪的文件-如果您有相同的问题(already exists, no checkout),请在下面查看我的答案。
Erik Koopmans

26

为了扩展Daniel Smith的回答:即使您在创建存储时使用了(或),该代码也仅还原了跟踪的文件。所需的完整代码是:--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}

资料来源:


1
为什么出于安全原因删除存储,为什么不将其保留一段时间呢?
Danijel

@Danijel确保您可以保留藏匿处-我猜取决于您的用例。就我的目的而言,一旦存储被恢复,我就完成了。
Erik Koopmans,

5

除了其他答案外,我还做了点小技巧

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

不起作用..删除的文件仅在存储时再次出现..错误也是如此!
Aditya Rewari
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.