本节摘要:Stash 是 Git 的"临时寄存柜",把未提交的工作整包存起来,让工作区恢复干净,处理完急事再取回来。本节讲 stash 的入柜、出柜、清柜全套操作,覆盖带消息储藏、藏未跟踪文件、按文件储藏、从储藏开分支四个进阶场景,并用对比表讲清 apply 与 pop、stash 与 commit 的取舍。
阅读完本节,你应当能够:
你在 develop 分支上写新功能,改到一半,突然线上出了紧急 bug,要立刻切回 main 修。切分支前你发现工作区一片狼藉——三个文件改了,其中一个还没改完。此时硬切分支会报错:"Your local changes to the following files would be overwritten by checkout."
你面临三选一:
Stash 解决的就是"工作没完成、但必须马上切换上下文"的窘境。它把工作区 + 暂存区里未提交的修改打包,存进一个栈里,然后把工作区恢复到上次提交的干净状态。你去处理急事,处理完回来,一条命令把现场原样取回。
类比仓储:你在工作台上铺开了一摊零件(未提交的修改),临时要去做另一件更急的活。不能把零件乱扔(丢改动),也不能把半成品封进档案(提交)。Stash 就是那排带标签的储物柜——整摊零件按标签放进柜子,台面恢复整洁;干完急活,凭标签取回,铺开继续干。
执行 git stash 时,Git 做了四件事:
注意一个容易踩的边界:默认情况下 stash 不碰未跟踪文件(untracked)和忽略文件(ignored)。你新创建但还没 add 过的文件,git stash 会原样留在工作区。这个设计是有意的——未跟踪文件本来就不属于 Git 管的范围,stash 默认也当它不存在。要一起藏,得加选项(见 5.2.3)。
git stash 内部其实创建了两个甚至三个提交,藏在一条独立于当前分支的引用上(refs/stash)。第一个提交记录"暂存区里的内容",第二个提交记录"工作区里的改动",它们的共同父提交是当前 HEAD。git stash list 里看到的 stash@{0} 这种编号,就是这条引用链上的位置——和 reflog 的编号方式同源。
为什么要把"已暂存"和"未暂存"分开记?因为取回时 Git 要能还原你储藏那一刻的暂存状态——如果当时有文件是"已 add 未 commit"的,apply 回来也应该保持"已暂存",而不是变成普通改动。这就是为什么 stash 存取是"无损"的:它不仅存了内容,还存了内容的"状态层次"。
一个推论:既然 stash 是提交,它就受提交生命周期的约束。储藏对象可以被 git fsck 找到、可以被 reflog 引用,甚至理论上能被其他命令处理。这也提醒你——stash 不是垃圾桶,别指望它当垃圾回收站用。它的正确身份是"工作区的快照备份",仅此而已。
stash 是纯本地操作,储藏对象不会随 push/fetch 同步到远程。换台机器,柜子不在那儿。
stash 是纯本地操作,储藏对象不会随 push/fetch 同步到远程。换台机器,柜子不在那儿。

| 命令 | 做什么 | 什么时候用 |
|---|---|---|
| git stash | 把修改入柜 | 切分支、拉代码前,工作区要变干净 |
| git stash list | 列出所有储藏 | 柜子里存了多条,想确认编号 |
| git stash apply | 取回修改,柜子保留 | 想在多个分支反复用同一份改动 |
| git stash pop | 取回修改,柜子移除 | 一次性恢复,用完即清 |
| git stash drop | 删除某条储藏 | 确定不要了,清理柜子 |
| git stash clear | 清空全部储藏 | 柜子堆满了废弃条目 |
apply 和 pop 的差别最关键:apply 取回后储藏还在柜子里,pop 取回后储藏被移除。默认都是操作最近的 stash@{0},指定某条用 git stash apply stash@{1}。
另一个高频易混点:git stash pop 应用时如果产生冲突,储藏不会被移除——它担心你还没解决完,先留着保险。冲突解决后记得手动 git stash drop 清掉。
多储藏并存时,没标签的 WIP on develop 完全分不清谁是谁。储藏时带一条消息:
git stash push -m "feat: 用户资料页 - 未完成"
git stash list 会显示:
stash@{0}: On develop: feat: 用户资料页 - 未完成 stash@{1}: WIP on main: abc1234 初始化项目
推荐用 git stash push -m 而非旧写法 git stash save——前者语义更清晰,也支持后面讲的高级选项。
git stash -u 或 git stash --include-untracked:把未跟踪的新文件也一起藏。git stash -a 或 git stash --all:连忽略文件(如构建产物、本地配置)一起藏。⚠️ 常见坑:git stash -a 会把 .gitignore 忽略的文件也清出工作区,如果里面有运行必需的本机配置(比如数据库连接串),取回时可能引起环境差异。默认别用 -a,除非你确定想清空整个工作目录。
git stash push 支持路径参数,只藏指定的文件或目录,其余修改保留:
git stash push docs/ -m "临时存一下文档改动"
这在"改了三个文件,但只想把其中一个藏起来做对比实验"时特别有用。取回、删除的语法不变。
git stash branch <新分支名> [stash@{n}] 是压箱底的实用技巧。它会:
git stash branch feature/continue-work stash@{1}
典型场景:你在 main 上攒了一批改动,做了一半,然后 main 被别的同事推了新提交。此时直接 stash pop 大概率冲突。而 git stash branch 会在储藏对应的旧提交上开分支——那里的代码还是你当初开发时的状态,应用改动几乎零冲突,还能让分支从正确的位置接续工作。这正是它存在的意义:给被时间甩在身后的改动一个合适的舞台。
把流程串起来看,你会对"什么时候该用哪条命令"有体感。假设你正在 main 上开发支付功能,文件改到一半,测试还挂着红叉。这时线上报了一个紧急的登录 bug,要马上切分支修。
完整操作:
git status # 确认有哪些改动,心里有数 git stash push -m "支付功能未完成" # 半成品入柜,工作区变干净 git checkout -b hotfix/login # 切到新分支修线上 bug # ...修复、测试、提交... git checkout main # 修完切回 main git stash pop # 取回支付功能的半成品
如果修复时 main 被别人推了新提交,pop 可能冲突——先 git stash apply 试试,冲突了再慢慢解决,解决完记得 git stash drop。这套流程是日常开发里最常用的 stash 场景,值得多练几遍形成肌肉记忆。
讲到 stash,很容易联想到 3.5 节的 cherry-pick——两者都是"把别处的改动搬到当前分支",但作用对象完全不同,容易混。一张表说清:
| 维度 | git stash apply | git cherry-pick |
|---|---|---|
| 搬什么 | 未提交的工作区现场 | 某个已提交的提交 |
| 来源 | 本仓库的储藏栈 | 本仓库或远程的任何提交 |
| 搬完后源保留吗 | apply 保留,pop 移除 | 原提交不受影响 |
| 典型场景 | 临时切换上下文 | 把 hotfix 提交挑到发布分支 |
实际工作中两者常配合:紧急修复往往先用 stash 存现场,修完用 cherry-pick 把修复提交挑到需要它的分支上,再 pop 恢复现场。理解"未提交的现场归 stash,已提交的变更归 cherry-pick",就不会在两个命令之间迷路。
| 维度 | stash | 临时 commit + reset |
|---|---|---|
| 历史污染 | 不产生提交,历史干净 | 留下临时提交痕迹,要 reset |
| 多条并行现场 | 栈可存多条,带标签区分 | 一条分支只能有一个 HEAD |
| 恢复粒度 | 一条命令整体取回 | 要先 reset 再找回 |
| 安全性 | 纯本地,不随推送走 | 提交了有被推送的风险 |
| 适用场景 | 几小时内的临时切换 | 想保留一个"检查点"再继续 |
我的建议:几小时内的临时切换用 stash,跨越一天的"存档点"用 commit(或分支)。stash 设计上就是临时的——它甚至没有日期排序、没有描述链。真正的开发节点应该进历史,而不是堆在柜子里。
💡 关键直觉:把 stash 想成"工作台的暂停键"——它冻结的是工作区现场,不是开发进度。现场一旦稳定、值得记录,就把它从"暂停"转成"提交"。
"stash 之后 git status 干净了,改动去哪了?" 改动被打包挂到了储物柜引用上,不在任何分支历史里,但也没丢。git stash list 能看到它,apply/pop 能取回。它不是提交,所以 log 里看不到。
"apply 和 pop 到底怎么选?" 想在多个分支复用同一份改动用 apply(柜子保留);只恢复这一次、之后不再需要用 pop(柜子自动清)。拿不准就先 apply——反正 pop 失败了储藏也不会丢。
"stash pop 冲突了怎么办?" 冲突时 Git 会停下,把冲突标记写进文件,储藏保留。手动解决冲突(第 3 章 3.4 的方法),add 后确认,最后 git stash drop 把已取出的储藏清掉。
"stash 能存二进制文件吗?" 能。stash 存的是文件快照差异,对二进制同样有效,只是冲突判断不如文本直观——二进制冲突基本只能"选一边"。
"stash 会跟 push 一起到远程吗?" 不会。stash 是纯本地机制,储藏内容不参与 push/fetch。这也意味着它是隐私的——你的半成品不会意外暴露给队友。
"为什么切分支前要 stash?" 因为切换分支要求工作区干净。如果目标分支与当前工作区的修改有交集,checkout 会直接拒绝;就算没交集,带着一堆半成品切过去也容易混淆"这些改动属于哪个分支"。stash 把现场收干净,切过去处理完再 pop 回来,逻辑清晰不串台。
"stash 有数量上限吗?" 没有硬性上限,但每一条都会占仓库空间,而且列表越长越难分辨。建议养成立刻使用的习惯:一条储藏只存在"当前任务从暂停到恢复"的时间段里,处理完马上 pop,别让柜子变成仓库。
下一节给历史盖"发布图章"——Tag 标签管理,让每个可发布版本都有个永久坐标。