我有一堆阶段性和非阶段性的更改,我想快速切换到另一个分支然后切换回来。
所以我使用以下方式进行了更改:
$ 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 stash
进行两次提交,或三次提交。默认为2;如果你使用--all
或--include-untracked
选项的任何拼写,你得到三。
这两个或三个提交在一个重要方面是特殊的:它们不在分支上。 Git通过特殊名称stash
找到它们。然而,最重要的是Git允许你 - 并且让你做这两三次提交。要理解这一点,我们需要查看这些提交中的内容。
每个提交都可以列出一个或多个父提交。这些形成了一个图形,后面的提交指向早期的图形。存储通常持有两个提交,我喜欢将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在制作u
和i
之间进行额外的提交,w
。 u
的快照内容是那些未跟踪但未被忽略的文件(--include-untracked
),或者即使被忽略而未被跟踪的文件(--all
)。这个额外的u
提交没有父级,然后当git stash
制作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 merge
2(不允许它将结果作为正常合并提交或处理),使用存储的原始提交(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 -u
或git 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>
。
我设法重新创建了你的问题。看起来如果您存储未跟踪的文件然后创建这些文件(在您的示例中,foo.txt
和bar.txt
),那么您对未应用的文件进行本地更改,这些文件将在您应用git stash pop
时被覆盖。
要解决此问题,可以使用以下命令:
git checkout stash -- .
这将覆盖任何未保存的本地更改,因此请小心。 Here is some further information I found on the previous command。
要扩展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}
都可以使用相同的资源资料来源:
stash^3
提交的信息)