快速版
git stash
储藏了一些还没准备好的更改。它创建了一个悬挂式储藏袋,是一种特殊的合并提交,不在任何分支上。当你以后调用 git stash apply
恢复这些更改时,Git 会根据悬挂式储藏袋和它所悬挂的提交计算出更改内容。
完成更改后,应该使用 git stash drop
从它所悬挂的提交中删除储藏袋。(git stash pop
是“应用并自动删除”的缩写。建议将两个步骤分开进行,以便稍后重试 "应用" 的结果。)
详细版
git stash
实际上相当复杂。
据说 “只有你理解X,才能真正理解 Git”,其中的“X”指的可能是许多不同的东西。这意味着“只有你理解 Git 才能真正理解 Git”。:-)
在这种情况下,要真正理解 stash
,您需要了解提交、分支、索引/暂存区、Git 引用名称空间和合并等的工作原理,因为 git stash
创建了一种非常特殊的合并提交,该提交由普通名称空间之外的名称引用——一种奇怪的合并方式,根本没有“在分支上”,而且 git stash apply
使用 Git 的合并机制尝试“重新应用”保存的更改,可选择保留已暂存和未暂存更改之间的区别。
幸运的是,您实际上不需要完全理解所有这些内容才能使用 git stash
。
在这里,您正在某个分支(master
)上工作,并且有一些还没准备好的更改,因此您不想将它们提交到分支上。1 与此同时,其他人在远程仓库的 origin/master
中添加了一些好的东西,或者至少希望是好的。
设想您和他们都从以 - A - B - C
结尾的提交开始,即 C
是您在开始在分支 master
上工作时在您的仓库中的最终提交。新的“好东西”提交我们称之为 D
和 E
。
如果您运行git pull
时出现“工作目录不清洁”的问题,您需要运行git stash
。这将以其特殊的奇怪方式为您提交内容,以使您的工作目录现在变得干净。现在您可以运行git pull
。
在绘制提交图形方面(例如使用gitk
或git log --graph
获得的图形),您现在将拥有类似于这样的东西。当您运行git stash
时,stash是挂在您的master
分支上处于“on”状态的提交的小w袋,因为这些是stash的“i”ndex /暂存区和“w”ork-tree部分的原因。
- A - B - C - D - E <
|\
i-w <
如果你在master
分支上开始工作,但从未进行过任何提交,则以下图形是你所看到的。因此,你最近一次的提交是C
。创建了存储,git pull
能够将提交D
和E
添加到你的本地分支master
中。存储的工作袋仍然挂在C
处。
如果你自己进行了几次提交——我们称之为Y
和Z
,以便有两个提交——“存储后拉取”的结果如下:
.-------- 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“plumbing”命令的shell脚本)有效地执行以下操作:2
git diff stash^ stash > /tmp/patch
git apply /tmp/patch
对“工作树”部分的存储(即w
)执行 git stash show -p
命令,将其与正确的父提交进行比较。也就是说,它找出了适当的父提交(根据需要是C
或Z
),并找出了存储工作树之间的“更改内容”。然后将这些更改应用于当前检出的版本,该版本可以是E
或M
,这取决于您的起点。
顺便说一句,git stash show -p
实际上只运行相同的git diff
命令(当然没有> /tmp/patch
部分)。如果不加-p
,则使用--stat
选项运行diff。因此,如果想详细了解git stash apply
将合并的内容,请使用git stash show -p
。(但是,这无法显示git stash apply
尝试从隐藏部分应用到索引部分的内容;这是我对stash脚本提的小小的抱怨)
无论如何,一旦stash成功应用,您就可以使用git stash drop
删除stash-bag的引用,以便进行垃圾回收。在您删除它之前,它有一个名称(refs/stash
,也就是stash@{0}
),因此它会一直保留...除非您创建了一个新的stash,这时stash
脚本会将当前stash“推入”到stash reflog中(使其名称变为stash@{1}
)并将新的stash使用refs/stash
名称。大多数reflog条目会保留90天(可以配置不同),然后过期。默认情况下,stash不会过期,但如果您进行其他配置,则可能会丢失“推送”的stash,请小心使用“永久保存”。
请注意,git stash drop
在此处“弹出”stash堆栈,将stash@{2}
重新编号为stash@{1}
,并使stash@{1}
成为普通stash
。使用git stash list
查看stash堆栈。
1提交它们并在稍后使用git rebase -i
压缩或修改第二、第三、第四个、...、第n个提交以及重写临时的“检查点”提交并不是坏事。但这与此无关。
2这个命令有点复杂,因为你可以使用--index
来尝试保持暂存的更改,但实际上,如果你查看脚本,就会看到实际的命令序列是git diff ... | git apply --index
。在这种情况下,它确实只是应用了一个差异!最终,它直接调用git merge-recursive
合并工作树,并允许从其他地方引入相同的更改。如果您的补丁执行与“好东西”提交D
和E
相同的操作,则普通的git apply
将失败。
3这里使用了git的父命名魔法语法,并在stash
脚本中进行了一些先期规划。因为stash是这个奇怪的合并提交,所以w
有两个甚至三个父项,但是stash脚本设置了“第一个父项”是原始提交C
或适当的Z
。如果存在,“第二个父项”stash^2
是提交时的索引状态,显示为挂起的小stash-bag中的 i ,而“第三个父项”是未暂存和可能被忽略的文件,来自git stash save -u
或git stash save -a
。
请注意,在此答案中,我假设您没有仔细地暂存工作树的一部分,并且您没有使用git stash apply --index
来恢复暂存的索引。通过不做任何操作,您使i提交几乎是多余的,因此我们在apply
步骤期间不需要担心它。如果您正在使用apply --index
或等效命令,并且已经准备好了项目,那么您可以进入更多的角落情况,其中stash无法干净地应用。
对于那些保存有第三个提交的-u
或-a
的stash,这些同样的警告也适用于它们的更多角落情况。
对于这些额外困难的情况,git stash
提供了一种将stash转换为一个完整的分支的方法,但是我会把所有这些留给另一个答案。