代码评审来了,三个文件改到一半,只能动其中一个。把全部收进 stash 再把另外两个重新敲一遍——这是没人怀念的仪式。
TL;DR:git stash push -m "备注" -- <路径> 只 stash 指定的路径,脏树的其他部分原地不动。 用 git stash pop 取回(或者用 git restore --source stash@{0} -- <路径> 从任意一个 stash 里单独拎出一个文件)。pathspec 形式随 git 2.13 发布(2017 年 5 月)——最近八年的任何工具链都有它。
git stash push -m "app.conf prod tweak" -- app.conf
git stash list
# stash@{0}: On main: app.conf prod tweak
git stash pop stash 的是文件,不是整棵树。
git 里怎么只 stash 一个文件?
光秃秃的 git stash 会扫走整个工作区——所有已跟踪的修改进 stash,一切回到 HEAD。当脏树是混合状态时,这就错了:一个文件可以交付了,其余是真的没写完。答案是带 pathspec 的 push,依据 git-stash 文档:
# 之前:三个脏文件
$ git status --short
M app.conf
M notes.md
M main.py
# 只 stash app.conf
$ git stash push -m "app.conf prod tweak" -- app.conf
Saved working directory and index state On main: app.conf prod tweak
$ git status --short
M notes.md
M main.py app.conf 回到已提交的状态,进了 stash@{0};notes.md 和 main.py 纹丝没动。-m 的备注可选,但等 git stash list 出现第二条时,那三秒钟就值回票价——名叫「WIP」的 stash 老得很快。
两个有用的细节:
- pathspec 放在
--之后。 双横线后面的都是路径,不是开关。和git checkout -- <路径>同一约定,而且不是装饰:在旧版本里git stash push -- main.py和git stash push main.py行为不同,裸路径可能被误读。 - 未跟踪文件需要
-u。 崭新的文件没有可 stash 的已跟踪历史,普通push会跳过它。git stash push -u -- newfile.py会带上它;-a更进一步,连被忽略的也扫进去。
怎么只 stash 一个文件的一部分?
文件本身就是半成品——三块好的 hunk,一块见不得人的——交互式流程会把它切开:
git stash -p # 或者: git stash push -p Git 逐个 hunk 询问 Stash this hunk [y,n,q,a,d,j,g,/,e,p,?]?。进 stash 的答 y,留下的答 n。结果是一个只含你批准内容的 stash,文件的其余部分照旧脏在工作区。同一套逐 hunk 的机制也驱动着 git add -p,肌肉记忆可以直接平移。
git stash push -- <路径> | git stash -p | |
|---|---|---|
| 粒度 | 整个文件 | 单个 hunk |
| 速度 | 一条命令,可脚本化 | 交互式,逐 hunk |
| CI 可复现 | 是(路径作为参数) | 否——提示符前得有人 |
| 适合 | 「这个文件,其余不要」 | 「这处改动,那处不要」 |
| 自 | git 2.13(2017 年 5 月) | 早已有之 |
从文档自身的模型得出的经验法则:边界是文件就用 pathspec,边界在文件内部就用 -p。
怎么取回 stash 里的文件?
git stash pop 恢复 stash@{0} 并删除该条目。这是常规路径——但 pop 对 stash 而言是全有或全无,遇到冲突会中止,条目保留。两个更细的选择:
# 1. 只应用,不删除(可以安全地重复)
git stash apply stash@{0}
# 2. 从 stash 里取出一个文件,条目原样保留
git restore --source stash@{0} -- app.conf
# 同一操作的 2.23 之前写法:
git checkout stash@{0} -- app.conf restore/checkout 形式回答的是「三个改动一起 stash 了,现在只要其中一个」——它把该路径的 stash 内容复制进工作区,stash@{0} 原样不动。注意:它会覆盖工作区里的那份;如果这条路径上的现有修改还要紧,先 diff:
git diff stash@{0} -- app.conf stash 以真实提交的形式存在于一根类似 reflog 的栈上,所以 stash@{0} 接受任何 commit-ish 语法;误删的 stash 也能从 reflog 里捞回来——直到垃圾回收把它吃掉。
什么时候 stash 是错的工具?
stash 是草稿纸,不是分支:在 git branch 里没有名字,没有评审,默认不显示 diff,条目无声堆积直到被遗忘。如果进行中的工作需要扛过跨机器、跨天数的上下文切换,就提交到分支上——带着历史比无名堆栈强。等这些停好的 commit 要落到别处,git cherry-pick 负责搬运。如果目标是撤销而不是停放,git undo last commit 的决策树适用。
从文档化行为里整理的诚实故障账本:
| 症状 | 原因 | 修法 |
|---|---|---|
| stash 之后文件不见了 | 未跟踪路径被跳过 | git stash push -u -- <路径> |
pop 时 error: Your local changes ... would be overwritten | 该路径在 stash 之后又变了 | 先提交或 stash 新状态,再 pop |
| 取回的改动消失了 | pop 撞上冲突并中止 | 解决冲突后 git stash apply |
| 「刚才是哪个 stash?」 | 一堆没有名字的 stash | 每次都写 -m 备注 |
给 git stash push 加上 pathspec 的那页 2017 年 5 月的发布说明已经八岁,而大多数 git stash 的肌肉记忆比它还老。值得重学一次:如今全树扫荡才是特例,不是默认。
FAQ
git 里怎么只 stash 一个文件?
git stash push -m "备注" -- path/to/file —— pathspec 形式只 stash 指定的路径,其他已修改文件原样保留。需要 git 2.13 或更新(2017 年 5 月)。
怎么从 stash 里单独取出一个文件?
git restore --source stash@{0} -- path/to/file (旧写法是 git checkout stash@{0} -- path/to/file)。它把 stash 的版本复制进工作区,但不删除 stash 条目。
为什么 git stash 跳过了我的新文件?
未跟踪(untracked)文件不属于普通 stash。加上 -u 就会包含:git stash push -u -- path/to/file。被忽略的文件需要 -a。
— mrsaynothing
— mrsaynothing
关于 AI、Linux 与自托管的一线笔记。
下一篇实战指南,直达邮箱
每篇一封。修完就走。
这是什么?一线笔记 #1:Agent 运行的网站——机器发版,我批准。
喜欢这些文章?我的本职工作就是这样的工程。 雇用我