5.2 暂存区管理(Stash)


5.2 暂存区管理(Stash)

本节摘要:Stash 是 Git 的"临时寄存柜",把未提交的工作整包存起来,让工作区恢复干净,处理完急事再取回来。本节讲 stash 的入柜、出柜、清柜全套操作,覆盖带消息储藏、藏未跟踪文件、按文件储藏、从储藏开分支四个进阶场景,并用对比表讲清 apply 与 pop、stash 与 commit 的取舍。

核心问题

阅读完本节,你应当能够:

  1. 说清 stash 存什么、不存什么,以及它和提交的本质区别。
  2. 用 stash、stash list、stash apply、stash pop、stash drop、stash clear 管理寄存柜。
  3. 用 -u 储藏未跟踪文件、用 push 加路径只藏部分文件。
  4. 在储藏对应的提交上开新分支,把中断的工作流无缝接续。

一、问题与直觉

你在 develop 分支上写新功能,改到一半,突然线上出了紧急 bug,要立刻切回 main 修。切分支前你发现工作区一片狼藉——三个文件改了,其中一个还没改完。此时硬切分支会报错:"Your local changes to the following files would be overwritten by checkout."

你面临三选一:

  1. 把半成品硬提交了——污染历史,修完 bug 还要 reset,很麻烦。
  2. 把改动手工拷贝到别处——容易漏、容易错。
  3. 先用 stash 把现场寄存起来——这正是它存在的意义。

Stash 解决的就是"工作没完成、但必须马上切换上下文"的窘境。它把工作区 + 暂存区里未提交的修改打包,存进一个栈里,然后把工作区恢复到上次提交的干净状态。你去处理急事,处理完回来,一条命令把现场原样取回。

类比仓储:你在工作台上铺开了一摊零件(未提交的修改),临时要去做另一件更急的活。不能把零件乱扔(丢改动),也不能把半成品封进档案(提交)。Stash 就是那排带标签的储物柜——整摊零件按标签放进柜子,台面恢复整洁;干完急活,凭标签取回,铺开继续干。

二、核心原理

Stash 到底存了什么

执行 git stash 时,Git 做了四件事:

  1. 记住当前 HEAD 指向的提交。
  2. 把已暂存和未暂存的已跟踪文件修改打包成储藏对象。
  3. 把这个储藏对象挂到一条特殊引用上,形成"储物柜栈"。
  4. 清空工作区与暂存区,恢复到 HEAD 提交的干净状态。

注意一个容易踩的边界:默认情况下 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 同步到远程。换台机器,柜子不在那儿。

图 5-2 Stash 堆栈的结构

图 5-2 Stash 堆栈的结构

基础命令四连

命令 做什么 什么时候用
git stash 把修改入柜 切分支、拉代码前,工作区要变干净
git stash list 列出所有储藏 柜子里存了多条,想确认编号
git stash apply 取回修改,柜子保留 想在多个分支反复用同一份改动
git stash pop 取回修改,柜子移除 一次性恢复,用完即清
git stash drop 删除某条储藏 确定不要了,清理柜子
git stash clear 清空全部储藏 柜子堆满了废弃条目

applypop 的差别最关键: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 -ugit stash --include-untracked:把未跟踪的新文件也一起藏。
  • git stash -agit stash --all:连忽略文件(如构建产物、本地配置)一起藏。

⚠️ 常见坑:git stash -a 会把 .gitignore 忽略的文件也清出工作区,如果里面有运行必需的本机配置(比如数据库连接串),取回时可能引起环境差异。默认别用 -a,除非你确定想清空整个工作目录。

只藏部分文件

git stash push 支持路径参数,只藏指定的文件或目录,其余修改保留:

git stash push docs/ -m "临时存一下文档改动"

这在"改了三个文件,但只想把其中一个藏起来做对比实验"时特别有用。取回、删除的语法不变。

从储藏开分支:把中断的工作接起来

git stash branch <新分支名> [stash@{n}] 是压箱底的实用技巧。它会:

  1. 基于"创建储藏时所在的那个提交"新建并切换到新分支。
  2. 把指定储藏应用上去。
  3. 应用成功且无冲突,自动移除该储藏。
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 与 cherry-pick:两种"取回"思路的互补

讲到 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 想成"工作台的暂停键"——它冻结的是工作区现场,不是开发进度。现场一旦稳定、值得记录,就把它从"暂停"转成"提交"。

常见问题与 FAQ

"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,别让柜子变成仓库。

本节速览

  • stash 的定位:临时寄存未提交的修改,让工作区变干净,不是替代提交的存档工具。
  • 默认边界:已跟踪文件(含暂存和未暂存)才被藏;未跟踪、忽略文件默认不碰。
  • 命令四件套:stash 入柜、list 查看、apply 取回保留、pop 取回即清。
  • 进阶选项:-u 藏未跟踪文件、-a 连忽略文件、push 加路径只藏部分文件。
  • stash branch:在储藏对应的旧提交上开分支,应用几乎零冲突,接续中断工作。
  • 冲突不丢:pop 冲突时储藏保留,解决完记得手动 drop。
  • 本地专属:stash 不随推送走,既是隐私也是局限——换机器取不回。
  • 取舍:临时切换用 stash,长期存档用 commit 或分支。

下一节给历史盖"发布图章"——Tag 标签管理,让每个可发布版本都有个永久坐标。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U