返回博客

git stash:只收起一个文件,不惊动其余

2026年9月21日

代码评审来了,三个文件改到一半,只能动其中一个。把全部收进 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.mdmain.py 纹丝没动。-m 的备注可选,但等 git stash list 出现第二条时,那三秒钟就值回票价——名叫「WIP」的 stash 老得很快。

两个有用的细节:

  • pathspec 放在 -- 之后。 双横线后面的都是路径,不是开关。和 git checkout -- <路径> 同一约定,而且不是装饰:在旧版本里 git stash push -- main.pygit 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 与自托管的一线笔记。

下一篇实战指南,直达邮箱

每篇一封。修完就走。

self-hosted · 无第三方 · 一键退订

这是什么?

一线笔记 #1:Agent 运行的网站——机器发版,我批准。

喜欢这些文章?我的本职工作就是这样的工程。 雇用我