快速“TL;DR”外卖版,以后可以回来学习更多
git stash 在当前的HEAD 提交上挂起一个存储袋——这是一种特殊的合并提交形式,它不在任何分支上。稍后的git stash apply,当您进行任何提交时(可能是 不同的 提交),然后尝试通过查看悬挂的 stash-bag 来恢复 git 计算的 更改以及它挂起的提交。
当您完成更改后,您应该使用git stash drop 从“隐藏”的提交中释放隐藏包。 (而且,git stash pop 只是“应用,然后自动删除”的简写。不过,我建议将这两个步骤分开,以防您不喜欢“应用”的结果并且想稍后再试。)
加长版
git stash 实际上相当复杂。
据说"git makes much more sense once you understand X",对于许多不同的“X”值,概括为“一旦你理解了 git,git 就更有意义了”。 :-)
在这种情况下,真正了解stash,您需要了解提交、分支、索引/暂存区、git 的引用名称空间和合并所有工作,因为@987654330 @ 创建了一个非常特殊的合并提交,它由通常的名称空间之外的名称引用——一种根本不在“分支上”的奇怪合并——并且 git stash apply 使用 git 的合并机制来尝试“重新应用”在进行特殊合并提交时保存的更改,可选择保留分阶段和非分阶段更改之间的区别。
幸运的是,您实际上不需要了解所有这些使用git stash。
在这里,您正在处理某个分支 (master),并且您有一些尚未准备好的更改,因此您不想在分支上提交它们。1与此同时,其他人在远程仓库的origin/master 上放了一些好的东西——或者至少,你希望它是好的东西,所以你想把它们捡起来。
假设您和他们都从以- A - B - C 结尾的提交开始,即C 是您开始在分支master 上工作时在您的repo 中的最终提交。新的“好东西”提交,我们将调用 D 和 E。
在您的情况下,您正在运行 git pull,但由于“工作目录不干净”问题而失败。所以,你运行git stash。这会以其特殊的怪异 stash-y 方式为您提交您的东西,因此您的工作目录现在是干净的。现在你可以git pull。
就提交的绘制而言(像您使用gitk 或git log --graph 获得的图表),您现在有了这样的东西。当您运行git stash 时,存储区是i-w 的小袋子,悬挂在您“开启”的提交中,在您的master 分支中。 (名称i 和w 的原因是它们是存储的“i”索引/暂存区域和“w”ork-tree 部分。)
- A - B - C - D - E <-- HEAD=master, origin/master
|\
i-w <-- the "stash"
如果您开始处理master 并且从未进行过任何 次提交,您会得到这张图。因此,您最近的提交是C。在进行存储之后,git pull 能够将提交 D 和 E 添加到您的本地分支 master。隐藏的工作包仍然挂在C。
如果您自己进行了一些提交——我们将它们称为Y,用于您的提交,Z 只是为了进行两次提交——“stash then pull”的结果如下所示:
.-------- origin/master
- A - B - C - D - E - M <-- HEAD=master
\ /
Y - Z
|\
i-w <-- the "stash"
这一次,在 stash 将其隐藏包挂在 Z 之后,pull(即 fetch 然后 merge)必须进行真正的合并,而不仅仅是“快进” ”。所以它提交M,合并提交。 origin/master 标签仍然指的是提交E,而不是M。你现在在master 上提交M,这是E 和Z 的合并。你比origin/master“领先”。
在任何一种情况下,如果您现在运行 git stash apply,stash 脚本(它是一个使用大量低级 git“管道”命令的 shell 脚本)会有效地2执行以下操作:
git diff stash^ stash > /tmp/patch
git apply /tmp/patch
这将 stash 命名为 w(存储的“工作树”部分)与正确的3 父级不同。换句话说,它会找出正确的父提交(C 或Z,视情况而定)和隐藏的工作树之间的“你改变了什么”。然后它将更改应用到当前签出的版本,即E 或M,同样取决于您从哪里开始。
顺便说一句,git stash show -p 实际上只是运行相同的git diff 命令(当然没有> /tmp/patch 部分)。如果没有-p,它将使用--stat 运行差异。因此,如果您想详细了解git stash apply 将合并的内容,请使用git stash show -p。 (不过,这不会告诉您 git stash apply 可以尝试从存储的索引部分应用什么;这是我对存储脚本的一个小抱怨。)
无论如何,一旦 stash 应用干净,您可以使用 git stash drop 删除对 stash-bag 的引用,以便对其进行垃圾回收。在你放下它之前,它有一个名字(refs/stash,又名stash@{0})所以它会“永远”存在......除了如果你制作一个 new stash,@ 987654394@ 脚本将当前存储“推送”到存储引用日志中(使其名称变为stash@{1})并使新存储使用refs/stash 名称。大多数 reflog 条目会保留 90 天(您可以将其配置为不同)然后过期。默认情况下,存储不会过期,但如果您以其他方式配置,“推送”存储可能会丢失,因此如果您开始根据自己的喜好配置 git,请小心依赖“永久保存”。
请注意,git stash drop 在此处“弹出”存储堆栈,将stash@{2} 重新编号为stash@{1},并使stash@{1} 变为普通stash。使用git stash list 查看存储堆栈。
1不管怎样继续提交它们也不错,然后再执行git rebase -i 以进一步压缩或修复第二、第三、第四、...、第 n 次提交,和/或重写临时“检查点”提交。但这与此无关。
2这有点复杂,因为您可以使用--index 来尝试保持分阶段的更改分阶段进行,但实际上,如果您查看脚本,您会看到实际的命令序列git diff ... | git apply --index。在这种情况下,它确实只是应用了差异!但最终它直接调用git merge-recursive 以合并到工作树中,从而允许从其他地方引入相同的更改。如果你的补丁做了一些“好东西”提交D 和E 也做的事情,那么一个普通的git apply 将会失败。
3这使用了 git 的父命名魔法语法,并在 stash 脚本中进行了一些预先规划。因为存储是这个时髦的合并提交,w 有两个甚至三个父级,但是存储脚本设置它以便“第一个父级”是原始提交,C 或Z,视情况而定。 “第二父”stash^2 是提交时的索引状态,在悬挂的小存储袋中显示为i,而“第三父”(如果存在)是未分级的-并且可能-忽略的文件,来自 git stash save -u 或 git stash save -a。
请注意,在此答案中,我假设您没有仔细上演了工作树的一部分,并且您没有使用git stash apply --index 来恢复阶段性索引。通过不执行任何这些操作,您会使i 提交变得非常多余,因此我们在apply 步骤期间不必担心它。如果您正在使用apply --index 或等效项,并且有暂存项目,则您可能会遇到更多的极端情况,即无法完全应用存储。 p>
这些相同的警告适用于使用-u 或-a 保存的具有第三次提交的存储。
对于这些特别困难的情况,git stash 提供了一种将存储变为成熟的分支的方法——但我将把这一切留给另一个答案。