如何反向应用一个"stash"?

327

我有一个小的补丁保存在我的git储藏中。 我使用git stash apply将其应用于我的工作副本。现在,我想通过反向应用补丁(类似于git revert但是针对储藏)来撤销这些更改。

有人知道如何做到这一点吗?

澄清:我的工作副本中还有其他更改。 我的特殊情况很难描述,但您可以想象一些调试或实验代码在储藏中。现在,它与我的工作副本中的其他更改混合在一起,并且我想查看来自储藏的更改和不带有这些更改的效果。

看起来储藏目前不支持此功能,但git stash apply --reverse将是一个不错的特性。


1
难道不能通过对比当前版本和上一个版本之间的差异来创建一个反向补丁,然后应用它吗? - ralphtheninja
工作树中是否有除应用的存储之外的更改? - Greg Bacon
将此添加到这里,本应是一个常见问题解答,而不是一个问题... https://stackoverflow.com/questions/59973103/git-how-to-undo-revert-a-saved-stash-by-message-easily/59973105#59973105 - Don Thomas Boyle
14个回答

243
根据git-stash手册,“一个存储是用提交表示的,其树记录了工作目录的状态,它的第一个父提交是创建存储时HEAD所指向的提交”,git stash show -p会为我们提供“存储状态和原始父提交之间差异的补丁”。
为保留其他更改,请使用git stash show -p | patch --reverse,如以下示例所示。
$ git init
Initialized empty Git repository in /tmp/repo/.git/

$ echo Hello, world >messages

$ git add messages

$ git commit -am 'Initial commit'
[master (root-commit)]: created 1ff2478: "Initial commit"
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 messages

$ echo Hello again >>messages

$ git stash

$ git status
# On branch master
nothing to commit (working directory clean)

$ git stash apply
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   messages
#
no changes added to commit (use "git add" and/or "git commit -a")

$ echo Howdy all >>messages

$ git diff
diff --git a/messages b/messages
index a5c1966..eade523 100644
--- a/messages
+++ b/messages
@@ -1 +1,3 @@
 Hello, world
+Hello again
+Howdy all

$ git stash show -p | patch --reverse
patching file messages
Hunk #1 succeeded at 1 with fuzz 1.

$ git diff
diff --git a/messages b/messages
index a5c1966..364fc91 100644
--- a/messages
+++ b/messages
@@ -1 +1,2 @@
 Hello, world
+Howdy all

编辑:

对此进行轻微改进的方法是使用 git apply 代替补丁:

git stash show -p | git apply --reverse

另外,您还可以使用git apply -R作为git apply --reverse的速记方式。

最近我发现这非常方便...


2
太棒了,谢谢。看起来这可能是一个不错的Stash功能。 - Pat Notz
7
是的,“git apply -R”对我来说是一种进步,至少在我使用带有Git Bash的Windows电脑时,“patch --reverse”无法定位要打补丁的文件(我不知道为什么另一种方法可以)。+1,并且解释得很好。 - hakre
3
@Greg Bacon,嗨,我试图按照你概述的脚本操作,但是当我运行git stash show -p | git apply -R -v时,补丁失败了,并显示以下消息:检查补丁消息...错误:在搜索以下内容时发生错误:Hello, world Hello again error: patch failed: messages:1。你知道可能出了什么问题吗? - Max Koretskyi
8
我收到了错误信息:error: patch failed: /src/filename.java:46,表示在路径为/src/filename.java的文件的第46行发生了补丁失败的错误。同时出现了另一个错误:error: src/filename.java patch does not apply,表示无法应用在路径为src/filename.java的文件上的补丁。 - Tim Boland
1
…| git apply --reverse 对我有效。…| patch --reverse 失败,显示 can't find file to patch at input line 5。我使用的是 Archlinux 系统,patch 版本为 2.7.6。 - Hi-Angel
显示剩余3条评论

229
git checkout -f

会删除任何非提交更改。


9
谢谢,你帮助我克服了一些不易察觉的改变。 - Fa.Shapouri
12
这个要简单得多。 - Mark A
这给我带来了一些奇怪的问题。我的单元测试失败了,显示出无法读取ARM的模糊信息。我不得不清理我的构建文件夹、派生数据,然后重新启动我的机器才能恢复正常。 - ScottyBlades
19
但是,原帖并没有要求这样做,因为这将删除所有未提交的更改,不仅包括“stash apply”引入的更改,还有可能导致有用的更改丢失。 - Kostas
这就是我想要的,我能够运行它,切换到我的原始分支并运行stash apply来获取我的更改。 - Max Conradt

95

git stash[save] 命令会保存你的工作目录状态和索引状态,并将它们暂时隐藏起来,让索引和工作区恢复到 HEAD 版本。

git stash apply 命令会恢复这些更改,而 git reset --hard 命令会再次删除它们。

git stash pop 命令会恢复这些更改并删除顶部的存储更改,所以在这种情况下,git stash [save] 命令将返回到先前(弹出之前)的状态。


41

V1版的git手册中提到了如何取消应用一个储藏(stash)。以下是摘录:

新版的V2 git手册 中没有提到如何取消应用一个储藏,但下面的方法仍然适用:

取消应用一个储藏 在某些场景下,您可能想要应用储藏中的变更,进行一些操作,但随后又需要取消应用最初来自储藏的这些变更。Git没有提供撤销储藏的命令,但可以通过简单地检索与储藏相关的补丁并以相反的顺序应用它来实现此效果:

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

再次强调,如果你没有指定一个stash,Git会默认使用最近的stash:

$ git stash show -p | git apply -R

你可能想要创建一个别名,并将stash-unapply命令有效添加到你的Git。例如:

你可以通过创建别名来实现这个目的:

$ git config --global alias.unstash '!git stash show -p | git apply -R'

请注意,此别名使用“!”,这意味着它将运行shell命令而不是Git命令。

$ git config --global alias.stash-unapply '!git stash show -p | git apply -R'
$ git stash apply
$ #... work work work
$ git stash-unapply

2
无论出于何种原因,您提供的这个有用的章节“取消应用存储”已经从本书的第二版 -最新版本是2.1.146,2019-04-15- V2-Git工具-存储和清理 中删除。可能是因为作者认为有更好的方法来完成此操作,而我似乎找不到。 - NadAlaba
1
@NadAlaba 感谢提醒,我已经更新了答案,注明了v1和v2之间的区别...奇怪的是Git作者删除了关于取消应用存储的部分。 - Choco Smith
1
这个回答特别有用,因为它展示了如何指定应用的备份。感谢您包含了这个重要信息!对于那些可能也包括未跟踪文件的人,可以使用 git stash show -p --all stash@{3} | git apply -R - Kay V

15

这件事情耽搁太久了,但如果我正确理解问题,我找到了一个简单的解决方案,请注意,以下是用我自己的术语进行的解释:

git stash [save] 会保存当前更改,并将当前分支设置为“干净状态”

git stash list 的输出结果类似于: stash@{0}: On develop: saved testing-stuff

git apply stash@{0} 将把当前分支设置为 执行 stash [save] 前的状态。

git checkout . 将把当前分支设置为 执行 stash [save] 后的状态。

存储在 stash 中的代码不会丢失,可以再次使用 git apply stash@{0} 找到它。

总之,这对我很有用!


为了确保,我首先应用了 git stash apply --reverse,然后像你提到的那样回到了 git stash apply stash@{x}。没有任何问题。 - Carlos Garcia

14

如何恢复储藏的更改?

除了其他人提到的方法,最简单的方法是首先执行

git reset HEAD

然后检查所有本地更改

git checkout . 

3
这绝对是最简单的方法,只要你完全没有想要保存的本地工作。如果你将错误的存储应用于分支或遇到不想解决的合并冲突,这是快速轻松的方法,通过完全恢复你的工作集到分支的最新提交来撤消它。 - Shadoninja

6

您可以应用两个命令

git reset . // 用于撤销文件更改

然后

git checkout . // 用于撤销修改


6
请通过为每个命令添加解释来提高答案的质量。谢谢。 - Slavik N

3
您可以按照我分享的图片进行操作,以取消误触 stashing 的操作。

2
git stash show -p | git apply --reverse

警告:并非所有情况下都适用:“git apply -R(man) 无法正确处理两次更改相同路径的补丁,这已在 Git 2.30(Q1 2021)中得到修正。

这在将路径从常规文件更改为符号链接(反之亦然)的补丁中最为相关。

查看提交b0f266d(2020年10月20日),作者为Jonathan Tan (jhowtan)
(由Junio C Hamano -- gitster --提交c23cd78中合并,于2020年11月2日)

apply:当使用-R时,同时反转章节列表

协助者:Junio C Hamano
签名者:Jonathan Tan

一个将符号链接更改为文件的补丁被写成了两个部分(在代码中表示为“struct patch”):首先,删除符号链接;其次,创建文件。当使用-R应用该补丁时,这两个部分会被颠倒,因此我们得到:(1)创建符号链接,然后(2)删除文件。这会导致问题,因为Git观察到所谓的文件不是文件而是符号链接,在检查“删除文件”部分时导致“错误类型”的错误消息。我们想要的是:(1)删除文件,然后(2)创建符号链接。在代码中,当从check_preimage()调用previous_patch()进行检查时,这反映在previous_patch()的行为上。先创建再删除意味着在检查删除时,previous_patch()返回创建部分,触发模式冲突,导致“错误类型”的错误消息。但是,删除然后创建意味着在检查删除时,previous_patch()返回NULL,因此删除模式将与lstat进行检查,这正是我们想要的。补丁还可以包含引用同一文件的两个部分的其他方式,例如,在7a07841c0b(“git-apply:更好地处理多次操作相同路径的补丁”,2008-06-27,Git v1.6.0-rc0 -- merge)中。git apply -R(man)以同样的方式失败,而此提交使该情况成功。因此,在构建部分列表时,当传递-R时,以相反的顺序构建它们(通过在列表的前面添加而不是后面添加)。

2
除了@Greg Bacon的答案之外,如果二进制文件已添加到索引并且是使用stash的一部分,则可以执行以下操作。
git stash show -p | git apply --reverse

可能导致
error: cannot apply binary patch to '<YOUR_NEW_FILE>' without full index line
error: <YOUR_NEW_FILE>: patch does not apply

添加--binary可以解决问题,但不幸的是我还没有弄清楚为什么。

 git stash show -p --binary | git apply --reverse

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