实际上撤消 git stash pop


Actually undo git stash pop

这个问题的标题相同,但不是同一个问题。这个问题实际上是在问"丢弃 git stash pop 的结果"。这个问题其实是

撤消 git stash pop

换句话说

1
2
3
4
5
6
7
// 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 之前的状态?换句话说,实际上撤消 pop 并将隐藏的东西放回 stash 并将本地文件恢复到我输入 git stash pop 之前的状态。

这也不是如何在 Git 中恢复丢弃的存储?虽然这在某些情况下可能会有所帮助


在您的示例中,要恢复到 git stash pop 之前的状态,请使用:

1
git reset --hard bar

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

因为你在第一个 git stash pop 上发生了冲突,所以存储仍然在存储堆栈的顶部。

从那里,你可以再次git checkout foogit stash pop


Greg Hewgill 的答案是正确的(并且被赞成,并且 OP 应该接受它)但是这里还有几个额外的警告,以防有人想以更一般的方式使用答案。我们先来看看具体使用的命令顺序:

1
2
3
git stash
git checkout bar
git stash pop       # ERROR ... lots of conflicts

现在,让我们列出注意事项:

  • git stash pop 失败很重要。 (格雷格已经注意到了这一点。)
  • 创建存储时您没有使用 git stash --keep-index
  • 运行 git stash 后,您没有对工作树进行任何更改。
  • git checkout 命令成功了,所以它可能已经对您的工作树进行了更改——事实上,它必须这样做才能使 pop 失败——但是您的工作树仍然是"干净的",因为 git status会说。

这是最后一点,git status 会(在尝试 git stash pop 之前)告诉您您的工作树是 clean,这是关键。如果您在 git checkout bar 之前或之后对工作树进行了更改,您将遇到更多麻烦。

因为你没有做那些事情,git reset --hard就是答案。

为什么会这样

git stash 所做的通常是进行两次提交。一个保存当前索引,另一个保存当前工作树。1 这些提交在某些方面有些特殊;最重要的是它们根本不在分支上。2 提交后,git stash 然后正常运行 git reset --hard.3

git reset --hard 步骤的作用是使索引和工作树与当前提交匹配。也就是说,我们将(整个)索引和(整个跟踪的部分)工作树保存在存储库中;因此,无论当前 HEAD 提交和索引之间有什么不同,都可以重新设置为相同; HEAD 提交和工作树之间的任何不同之处都可以重新设置为相同。

git reset 之后,索引和工作树都是"干净的",正如 git status 所说:它们都匹配 HEAD 提交。然后您可以 git checkout 其他分支,因为您没有未保存的工作。然后,您可以尝试 git stash apply,甚至 git stash pop,将您的更改"移动"到另一个分支。

如果失败(如本例所示),则存储将保留在已保存的存储中。当前索引和工作树现在充满了合并冲突。如果你运行 git reset --hard,Git 将像往常一样重新设置索引和工作树以匹配 HEAD 提交,这样你将回到 git checkout 步骤之后的相同情况。由于您没有未保存的工作(您保存的工作仍在 stash 提交中),所以您在这里没问题。

(如果您确实有未保存的工作,则 git stash apply 步骤将通过尝试合并隐藏的工作树更改来破坏未保存的工作。这通常很难撤消。)

1虽然 git stash 通常会进行两次提交,但如果您使用 --all--include-untracked 运行它,它将进行三次提交。我喜欢将这些称为 i(索引)、w(工作树)和 u(取消跟踪文件)提交。

当使用 --all--include-untracked 时,savepush 步骤将不仅仅是 git reset --hard:它还将运行 git clean 以删除进入第三次提交的任何内容(仅限未跟踪文件,或未跟踪的包括忽略的文件)。在应用这样的存储之前,您可能必须重复此 git clean 工作,这既棘手又烦人。

稍后,当您运行 git stash apply 时,Git 将(尝试)应用 u 提交(如果存在)。它将始终(尝试)应用 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 来记住当前的存储,并且(ab)使用 refs/stash 的 reflog 来维护"存储堆栈"的其余部分。内部分支名称都具有 refs/heads/name 形式,因此 refs/stash 不是分支名称。

3如果使用git stash --keep-index,它运行的不仅仅是git reset --hard:它会将保存的索引状态提取到工作树中。这里的目标是让工作树按照索引的设置方式设置,以便您可以测试您将要提交的内容。如脚注 1 中所述,这里有一个小但相当讨厌的错误,这里有许多版本的 git stash,如果正确的工作树版本与HEAD 版本。


您可以对存储进行反向修补(如前所述,它应该仍然存在,因为如果它不干净地应用,git 不会删除存储)

1
git stash show -p stash@{0} | git apply -R

所以,我不能在所有情况下都 100% 确定答案。在我的情况下,当我输入 git stash pop 并且我在分支 bar 上时存在冲突。因此,当它应用存储时,它并没有丢弃存储。所以要撤消并正确地将存储应用到分支 foo 它只是

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

如果与分支 bar 没有冲突,那么它只是

1
2
3
git stash             # restash
git checkout -b foo
git stash pop