实际上撤销 git stash pop

19

这个问题的标题相同,但并不是同一个问题。那个问题实际上是在询问“放弃 git stash pop 的结果”。而这个问题实际上是

撤销 git stash pop

换句话说

// in branch foo
git stash
git checkout bar
git stash pop       # ERROR. I didn't want to pop on top of bar, lots of conflicts
git stash undo-pop  # NEED COMMAND TO PUT STASH AND LOCAL FILES BACK AS THEY WERE
git checkout foo
git stash pop

有没有办法让所有东西回到我键入git stash pop之前的状态呢?换句话说,实际上撤消弹出并将存储的东西放回存储区,并将本地文件恢复到我键入git stash pop之前的状态。
这也不是与如何在Git中恢复丢失的隐藏内容?重复。虽然在某些情况下可能有所帮助。

通常情况下,您可以再次使用 git stash 来实现此操作。 - axiac
3
我无法使用 git stash,因为文件在 bar 之上处于冲突状态。 - gman
然后将这些信息放在问题中,因为它会产生很大的影响。 - axiac
4
这已经在问题中说明了。此外,“UNDO” 这个词的整个意义就是这样的。 - gman
我也会定期遇到这个问题,当之前有暂存文件时处理起来很棘手(对于新手来说至少不是很明显)。git stash没有像git mergegit rebase那样的--abort选项;这将是很好的。请点赞:https://github.com/git/git.github.io/issues/599 - trusktr
4个回答

29

如果要将状态恢复到git stash pop之前的状态,请使用以下命令:

git reset --hard bar

这种形式的git reset命令将索引和工作目录的状态恢复到bar分支的头部。

由于在第一个git stash pop中存在冲突,所以该存储仍然位于存储堆栈的顶部。

从那里,您可以执行git checkout foogit stash pop


11

Greg Hewgill的回答是正确的(并且得到了赞同,原帖作者应该接受它),但在这里还有一些额外的注意事项,以防有人想要更广泛地使用这个答案。首先让我们看一下使用的特定命令序列:

git stash
git checkout bar
git stash pop       # ERROR ... lots of conflicts
现在,让我们列出一些注意事项:
  • 重要的是git stash pop失败了。(Greg已经指出这个问题。)
  • 创建存储时未使用git stash --keep-index
  • 运行git stash后,你没有对工作目录进行任何更改。
  • git checkout命令成功执行,因此它可能已经对你的工作目录进行了更改——实际上,为了使pop失败,它必须已经这样做了——但是你的工作目录仍然是“干净”的,就像git status所说的那样。

最后一个点是关键,即在尝试git stash pop之前,git status会告诉你你的工作目录是clean的。如果你在git checkout bar之前或之后对工作目录进行了更改,你将会有更大的麻烦。

由于你没有做这些事情,git reset --hard就是答案。

为什么要这样做

通常,git stash做的事情是创建两个提交。其中一个保存当前的index,另一个保存当前的work-tree1。这些提交在一些方面是稍微特殊的;最重要的是它们都不在任何分支上2。创建完提交之后,git stash通常会运行git reset --hard3

git reset --hard步骤所做的事情是将索引和工作目录与当前提交匹配。也就是说,我们已经将(整个)索引和(跟踪部分的)工作目录保存在存储中;因此,无论当前的HEAD提交和索引之间有什么不同,都可以重新设置为相同的内容;无论当前的HEAD提交和工作目录之间有什么不同,也可以重新设置为相同的内容。

git reset之后,索引和工作目录都是“干净的”,就像git status所说的那样:它们都与HEAD提交匹配。然后你可以git checkout其他分支,因为你没有未保存的工作。然后你可以尝试git stash apply,甚至git stash pop,将你的更改“移动”到这个其他分支。

git stash apply 失败时,就像这种情况一样,备份会保存在已保存的备份中。当前的索引和工作树现在充满了合并冲突。如果你运行 git reset --hard,Git 将像往常一样重新设置索引和工作树以匹配 HEAD 提交,因此你将回到执行 git checkout 步骤后所处的相同情况。由于你没有未保存的工作(你的已保存工作仍然在备份提交中),所以你在这里是安全的。
(如果你有未保存的工作,git stash apply 步骤将尝试合并备份的工作树更改,从而破坏了那些未保存的工作。通常情况下,这非常难以撤消。)
虽然 git stash 通常会创建两个提交,但如果你使用 --all--include-untracked 运行它,它将创建三个提交。我喜欢称它们为 i(索引)、w(工作树)和 u(未跟踪文件)提交。
当使用 --all--include-untracked 时,savepush 步骤将不仅运行 git reset --hard:它还将运行 git clean 来删除第三个提交中的内容(仅未跟踪文件或包括被忽略的未跟踪文件)。在应用这样的备份之前,你可能需要重复执行 git clean,这很棘手而且很烦人。
稍后,当你运行 git stash apply 时,如果存在 u 提交,Git 将(尝试)应用它。它将始终(尝试)应用 w 提交。它将仅在你给出 --index 标志时(尝试)应用 i 提交。许多版本的 git stash 存在一些小错误,涉及到整个“单独索引恢复”问题。它们倾向于影响那些想要在例如预提交挂钩中使用 --keep-index--index 标志的人。
请注意,git stash pop 只是 git stash apply && git stash drop:也就是说,尝试应用备份,然后,如果 Git 认为 apply 进行得很好,也会 drop 备份。我发现最好先使用 git stash apply,以避免即使 Git 认为它进行得很好,也放弃备份,因为 Git 和我有时对“进行得很好”这个概念存在分歧。 :-)

2Git使用名称refs/stash来记住当前的暂存内容,并且滥用reflog来维护“暂存栈”的其余部分。分支名称在内部都采用refs/heads/name的形式,因此refs/stash不是分支名称。

3如果您使用git stash --keep-index,它会运行更多的命令而不仅仅是git reset --hard:它将保存的索引状态提取到工作树中。这里的目标是使工作树设置与索引设置相同,以便您可以测试即将提交的内容。正如脚注1中所述,许多版本的git stash存在一个小但相当严重的错误,即如果正确的工作树版本与HEAD版本匹配,则暂存的工作树状态会意外地采用索引版本而不是工作树版本。


1

所以,我不能百分之百确定所有情况的答案。在我的情况下,当我输入 git stash pop 并且我在分支 bar 上时,出现了冲突。因此,尽管它应用了暂存,但它没有删除暂存。因此,要撤消并正确地将暂存应用于分支 foo,只需

git reset --hard      # resets bar to before the stash pop
git checkout -b foo
git stash pop         

如果没有与分支bar的冲突,那么就只是这样。
git stash             # restash
git checkout -b foo
git stash pop

如果你弹出了一个存储,并且没有冲突,那么要撤消 git stash pop,只需再次运行 git stash 即可。 - Greg Hewgill
1
你确定吗?当与 bar 合并时,是否有可能会出现一些合并匹配,因此重新存储的差异不会与原始存储区匹配,而原始存储区是为另一个分支而设计的?我猜它只是存储树的状态而不是差异,所以是的,git stash 应该可以工作。 - gman

1
你可以反向打补丁到存储区(正如已经提到的那样,如果补丁无法应用,Git 不会删除存储区)。
git stash show -p stash@{0} | git apply -R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接