跳转到内容
关系图谱

Git stash 详解

5,029 字 19 分钟

属性 1
status
学习中

Git stash 详解

git stash 是 Git 中用于临时保存未提交修改的命令。它会将工作区和暂存区的修改保存到一个栈结构中,让工作区恢复到干净状态,方便你切换分支、拉取更新或处理紧急任务。之后可以随时恢复这些修改。

一句话理解

git stash 就像一个「剪贴板栈」——你可以随时把当前的修改「剪切」走,处理完其他事情后再「粘贴」回来。

基本语法

bash
git stash [push [-p] [-S] [-k] [-u | -a] [-m <message>] [-- <pathspec>…]]
git stash list
git stash show [<stash>]
git stash pop [<stash>]
git stash apply [<stash>]
git stash drop [<stash>]
git stash clear
git stash branch <branchname> [<stash>]

核心概念

stash 的工作原理

git stash 实际上会创建两到三个特殊的提交对象来保存当前状态:

  • Index commit:保存暂存区(git add 过的修改)
  • Worktree commit:保存工作区的修改
  • Untracked commit(可选):保存未跟踪的文件(使用 -u-a 时)

stash 栈结构

stash 使用后进先出(LIFO)的栈结构管理多个存储:

stash 引用格式

stash@{N}N 是从 0 开始的索引,0 表示最新的 stash。每次新增 stash 时,旧的索引会自动 +1。

保存修改

git stash(快速保存)

最简单的用法,保存所有已跟踪文件的修改(包括暂存和未暂存的):

bash
# 快速保存(等价于 git stash push)
git stash

# 带描述信息(强烈推荐)
git stash push -m "feat: 用户登录页面半成品"

# 简写形式
git stash -m "WIP: 重构认证模块"
始终添加描述信息

养成使用 -m 添加描述的习惯。没有描述的 stash 在 git stash list 中只会显示基于分支名和提交信息的默认标题,时间久了完全记不住每个 stash 保存了什么。

包含未跟踪文件

默认情况下,git stash 不会保存未跟踪文件(新建但未 git add 过的文件)和被忽略的文件。

bash
# 包含未跟踪文件(untracked files)
git stash push -u
git stash push --include-untracked

# 包含所有文件(含 .gitignore 中的文件)
git stash push -a
git stash push --all
-a 慎用

git stash -a 会保存 .gitignore 中的文件(如 node_modules/build/),可能导致 stash 体积非常大。通常 -u 就够了。

保存部分文件

bash
# 只保存指定文件
git stash push -m "保存配置文件" -- src/config.ts .env

# 只保存某个目录
git stash push -m "保存 API 模块" -- src/api/

# 交互式选择要保存的代码块
git stash push -p

--keep-index:保留暂存区

bash
# 保存工作区修改,但保留暂存区不变
git stash push --keep-index

# 简写
git stash push -k
--keep-index 的使用场景

当你已经 git add 了一部分文件准备提交,但想先测试暂存区中的代码是否能正常工作:

bash
git add src/feature.ts
git stash push -k -m "暂存工作区其余修改"
# 此时工作区只有已暂存的文件,可以运行测试
npm test
# 测试通过后恢复其余修改
git stash pop

--staged:只保存暂存区(Git 2.35+)

bash
# 只保存已暂存的修改,工作区未暂存的修改保持不变
git stash push --staged
git stash push -S
--staged vs --keep-index
参数保存到 stash 的内容工作区状态
--keep-index暂存 + 未暂存的修改只保留暂存区的修改
--staged只保存暂存区的修改暂存区清空,未暂存修改保持不变

查看 stash

列出所有 stash

bash
git stash list
# stash@{0}: On main: feat: 用户登录页面半成品
# stash@{1}: On develop: WIP: 重构认证模块
# stash@{2}: On main: WIP on main: abc1234 初始化项目

查看 stash 内容

bash
# 查看最新 stash 的文件变更摘要
git stash show
#  src/auth.ts | 15 ++++++++++++---
#  src/api.ts  |  8 +++++---
#  2 files changed, 17 insertions(+), 6 deletions(-)

# 查看详细 diff
git stash show -p

# 查看指定 stash 的内容
git stash show -p stash@{2}

# 只查看文件名
git stash show --name-only

# 查看 stash 中的指定文件
git stash show -p stash@{0} -- src/auth.ts

恢复修改

pop vs apply

命令恢复修改删除 stash适用场景
git stash pop只需要恢复一次(最常用)
git stash apply想在多个分支上应用同一个 stash
bash
# 恢复最新的 stash 并从栈中删除
git stash pop

# 恢复最新的 stash 但保留在栈中
git stash apply

# 恢复指定的 stash
git stash pop stash@{2}
git stash apply stash@{2}

恢复时的暂存状态

默认情况下,popapply 会将所有修改都放到工作区(未暂存状态),即使某些修改之前是已暂存的。

bash
# 恢复时保留原始的暂存状态
git stash pop --index
git stash apply --index
--index 的重要性

如果你在 stash 前精心安排了暂存区(部分文件已 git add),恢复时务必加 --index,否则所有修改都会变成未暂存状态,你的暂存工作白费了。

处理恢复冲突

如果 stash 恢复时工作区已有修改,或分支已发生变化,可能产生冲突:

bash
# 恢复 stash
git stash pop
# CONFLICT (content): Merge conflict in src/main.ts

# 手动解决冲突后
git add src/main.ts

# 注意:pop 冲突时不会自动删除 stash
# 需要手动删除
git stash drop
pop 冲突时 stash 不会被删除

如果 git stash pop 遇到冲突,stash 不会被自动删除(与成功 pop 时不同)。解决冲突后需要手动 git stash drop 清理。

删除 stash

bash
# 删除最新的 stash
git stash drop

# 删除指定的 stash
git stash drop stash@{2}

# 清空所有 stash(不可逆!)
git stash clear
git stash clear 不可逆

清空后的 stash 无法通过常规方式找回。虽然理论上可以用 git fsck --lost-found 尝试恢复,但不保证成功。

从 stash 创建分支

git stash branch 会基于创建 stash 时的提交创建新分支,并自动应用 stash:

bash
# 从最新 stash 创建新分支
git stash branch feature-from-stash

# 从指定 stash 创建新分支
git stash branch fix/hotfix stash@{2}
何时用 stash branch

最适合以下场景:

  • stash 后 main 分支已经有了大量新提交,直接 pop 会产生冲突
  • 你意识到这些修改应该在一个独立的功能分支上完成
  • pop 冲突后,想换个干净的环境恢复 stash

实战示例

示例 1:紧急切换分支

bash
# 正在 feature 分支开发,突然需要修复线上 bug
git stash push -m "WIP: feature-x 开发中"

# 切到 main 修复 bug
git checkout main
git checkout -b hotfix/urgent-bug
# ... 修复并提交 ...
git checkout main
git merge hotfix/urgent-bug
git push

# 回到 feature 继续开发
git checkout feature-x
git stash pop

示例 2:拉取远程更新前保存修改

bash
# 本地有未提交的修改,直接 pull 会被拒绝
git stash push -m "拉取前备份"
git pull --rebase origin main
git stash pop

# 如果 pop 有冲突,解决后:
git add .
git stash drop

示例 3:多个 stash 的管理

bash
# 保存不同功能的修改到不同 stash
git stash push -m "feat: 用户页面" -- src/pages/user/
git stash push -m "feat: API 模块" -- src/api/

# 查看所有 stash
git stash list
# stash@{0}: On main: feat: API 模块
# stash@{1}: On main: feat: 用户页面

# 恢复指定的 stash(注意索引)
git stash pop stash@{1}   # 恢复用户页面

示例 4:测试暂存区代码

bash
# 已暂存了部分修改准备提交
git add src/feature.ts src/test.ts

# 暂存其余修改,只保留暂存区用于测试
git stash push -k -m "临时隐藏未暂存修改"

# 运行测试
npm test

# 测试通过 → 提交
git commit -m "feat: 新功能"

# 恢复其余修改
git stash pop

示例 5:配合 reset 移动提交到其他分支

bash
# 不小心在 main 上提交了,想移到 feature 分支
git reset --soft HEAD~2
git stash push -m "移动到 feature 分支的修改"
git checkout -b feature-branch
git stash pop
git commit -m "feat: 正确的功能提交"

示例 6:保存未跟踪的配置文件

bash
# 新建了 .env.local,还没 add,但需要保留
git stash push -u -m "包含本地配置文件"

# 恢复时未跟踪文件也会恢复
git stash pop

实际项目中的 stash 场景

场景 1:代码审查时临时保存工作

收到同事的 PR 审查请求,需要切到他的分支测试,但自己手上有未完成的工作:

bash
# 1. 保存当前工作
git stash push -u -m "WIP: 正在开发的订单模块"

# 2. 拉取并切到同事的分支
git fetch origin
git checkout pr/feature-auth

# 3. 审查代码、运行测试
npm test
# ... 写审查意见 ...

# 4. 回到自己的分支继续工作
git checkout feature-order
git stash pop

场景 2:在 debug 中使用 stash 隔离问题

当不确定 bug 是来自自己的修改还是已有代码时,stash 可以快速隔离:

bash
# 当前工作区有大量修改,出现了 bug
# 问题:bug 是我的修改引入的,还是之前就有?

# 1. 保存所有修改
git stash push -u -m "debug: 隔离测试"

# 2. 在干净的代码上运行测试
npm test
# 如果测试通过 → bug 是你的修改引入的
# 如果测试失败 → bug 在之前的代码中

# 3. 恢复修改
git stash pop

# 4. 如果是自己的修改导致的,可以用 -p 逐步恢复
git stash push -u -m "debug: 全部修改"
git stash pop --index   # 先恢复全部
# 或者用 -p 交互式恢复,逐块确认

场景 3:处理线上紧急 hotfix

开发进行到一半,突然接到线上紧急 bug 报告:

场景 4:多个任务之间快速切换

当你需要在多个功能之间频繁切换时,stash 栈可以像「多个剪贴板」一样使用:

bash
# 任务 A:开发支付模块
git stash push -m "task-A: 支付模块 - 完成了 checkout 逻辑"

# 切换到任务 B
git checkout feature-b
# ... 工作一段时间 ...
git stash push -m "task-B: 用户设置 - 完成了表单验证"

# 切换到任务 C
git checkout feature-c
# ... 紧急修复 ...
git commit -m "fix: 修复紧急问题"

# 回到任务 A
git checkout feature-a
git stash list
# stash@{0}: On feature-b: task-B: 用户设置 - 完成了表单验证
# stash@{1}: On feature-a: task-A: 支付模块 - 完成了 checkout 逻辑

git stash pop "stash@{1}"   # 恢复任务 A 的修改
stash 栈不适合长期存储

如果修改需要保存超过几个小时,建议创建 WIP 提交而非 stash:

bash
# WIP 提交(更持久、更安全)
git add .
git commit -m "WIP: 支付模块开发中(未完成)"

# 后续继续开发时
git reset --soft HEAD~1   # 取消 WIP 提交,保留修改

因为 stash 是纯本地的——不会被 push,也不会被 clone,换电脑就丢了。

场景 5:使用 stash 进行 A/B 性能对比

对比修改前后的性能差异:

bash
# 1. 记录修改后的性能
node benchmark.js
# Result: 1200ms

# 2. stash 所有修改
git stash push -m "性能优化修改"

# 3. 测试原始代码的性能
node benchmark.js
# Result: 1800ms

# 4. 恢复修改
git stash pop

# 结论:优化提升了 33% 性能

场景 6:stash 配合 git worktree 避免频繁切换

如果你需要同时在多个分支上工作,git worktree 比反复 stash + checkout 更高效:

bash
# 传统方式:stash → checkout → pop → ... → stash → checkout → pop
# 每次切换都要 stash/pop,很烦

# 更好的方式:使用 worktree 创建多个工作目录
git worktree add ../project-hotfix hotfix/urgent
git worktree add ../project-feature feature-x

# 现在你可以在不同终端/编辑器窗口中同时编辑不同分支
# 完全不需要 stash!

# 清理
git worktree remove ../project-hotfix
何时用 stash,何时用 worktree
场景推荐方式
临时切换分支(< 30 分钟)stash
长时间并行开发多个功能worktree
临时查看某个分支的代码stash
同时运行多个分支的服务worktree
CI/CD 脚本中临时保存状态stash

实用技巧

技巧 1:stash 命名规范

在团队中或自己管理多个 stash 时,使用一致的命名规范:

bash
# 推荐的命名格式:[类型]: [简述] - [状态]
git stash push -m "feat: 支付模块 - 完成 70%"
git stash push -m "fix: 登录验证 bug - 待测试"
git stash push -m "debug: 隔离性能问题"
git stash push -m "backup: reset 前的完整备份"
git stash push -m "review: PR#456 审查前保存"

技巧 2:导出 stash 为 patch 文件

如果需要将 stash 分享给同事或跨仓库应用:

bash
# 导出 stash 为 patch 文件
git stash show -p stash@{0} > my-changes.patch

# 在另一个仓库/机器上应用
git apply my-changes.patch

# 或发送给同事
# 同事执行:git apply my-changes.patch

技巧 3:查看 stash 中特定文件的内容

bash
# 查看 stash 中某个文件的完整内容(不是 diff)
git show stash@{0}:src/config.ts

# 只从 stash 中恢复特定文件(不影响其他修改)
git checkout stash@{0} -- src/config.ts

# 查看 stash 中哪些文件被修改了
git stash show --name-only stash@{0}

# 对比 stash 内容和当前工作区
git diff stash@{0}

技巧 4:配置 Git alias 提升效率

bash
# 在 ~/.gitconfig 中添加
[alias]
    # 快速 stash(带描述)
    ss = stash push -m
    # stash 含未跟踪文件
    ssu = stash push -u -m
    # 查看 stash 列表
    sl = stash list
    # 查看最新 stash 的详细内容
    sd = stash show -p
    # 恢复最新 stash
    sp = stash pop
    # 恢复但保留
    sa = stash apply

# 使用
git ss "WIP: 开发中"          # 快速保存
git ssu "含新文件的修改"       # 含未跟踪文件
git sl                         # 查看列表
git sd                         # 查看内容
git sp                         # 恢复

技巧 5:定期清理过期的 stash

bash
# 查看所有 stash 的创建时间
git stash list --date=relative
# stash@{0}: 2 hours ago: On main: WIP
# stash@{1}: 3 days ago: On feature: 旧的修改
# stash@{2}: 2 weeks ago: On develop: 已经不需要了

# 逐个检查内容后决定是否删除
git stash show -p stash@{2}
git stash drop stash@{2}

# 或者一次性清理所有(确认不需要后)
git stash clear
stash 清理习惯

建议每周检查一次 stash 列表。超过一周的 stash 通常已经不需要了——如果需要,应该已经创建了正式的提交或分支。

常见陷阱

陷阱 1:忘记 stash 的存在

最常见的问题不是 stash 丢失,而是忘记自己 stash 过东西。几天后发现代码不见了,以为丢失了:

bash
# 养成习惯:每次 stash 后检查列表
git stash
git stash list   # 确认保存成功

# 切换分支前也检查
git stash list   # 有没有未恢复的 stash?
陷阱 2:在错误的分支上 pop

stash pop 会应用到当前分支,不是创建 stash 时的分支:

bash
# 在 feature-a 上 stash
git checkout feature-a
git stash push -m "feature-a 的修改"

# 忘记切回来,直接在 main 上 pop
git checkout main
git stash pop   # ⚠️ feature-a 的修改被应用到了 main 上!

# 补救:如果还没有 add/commit
git stash       # 重新 stash
git checkout feature-a
git stash pop   # 在正确的分支上恢复
陷阱 3:stash 积压导致混乱

stash 栈积压了太多条目,分不清哪个是哪个:

bash
git stash list
# stash@{0}: WIP on main: abc1234 fix something
# stash@{1}: WIP on main: def5678 fix something
# stash@{2}: WIP on develop: ghi9012 init
# ... 20 个没有描述的 stash ...

# 预防方法:
# 1. 始终使用 -m 添加描述
# 2. 及时 pop 或 drop
# 3. 长期修改用 WIP commit 而非 stash
陷阱 4:stash 不保存 .gitignore 中的文件(默认)

如果你的项目依赖 .env 文件或 node_modules,默认 stash 不会保存它们。切换分支后这些文件可能状态不对:

bash
# 需要时显式包含
git stash push -u   # 包含未跟踪文件
git stash push -a   # 包含被忽略的文件(慎用,体积可能很大)

常见问题

stash 后切换分支,pop 到哪个分支上?

git stash pop 会将修改应用到当前所在的分支,不一定是创建 stash 时的分支。这意味着你可以在分支 A 上 stash,然后在分支 B 上 pop。

如果分支差异太大导致冲突,考虑使用 git stash branch 创建新分支来恢复。

stash 的修改可以在多个分支上应用吗?

可以。使用 git stash apply(而非 pop)会保留 stash,你可以在不同分支上多次 apply:

bash
git checkout branch-a
git stash apply stash@{0}

git checkout branch-b
git stash apply stash@{0}
git stashgit stash push 有什么区别?

在现代 Git 中,git stash 等价于 git stash pushpush 是较新的子命令,支持更多选项(如 -- 指定路径、-S 只暂存 staged)。旧版本使用 git stash save,但 save 已被弃用

bash
# 推荐写法
git stash push -m "描述"

# 等价的简写
git stash -m "描述"

# 已弃用(不要用)
git stash save "描述"
stash 会保存 .gitignore 中的文件吗?

默认不会。需要使用 -a--all)参数:

参数已跟踪修改未跟踪文件被忽略文件
默认
-u
-a
如何找回被误删(drop/clear)的 stash?

stash 条目的提交对象仍然可能存在于 Git 对象数据库中:

bash
# 查找悬空的 stash 提交
git fsck --no-reflog | grep commit

# 查看可能是 stash 的提交
git show <sha>

# 如果确认是要恢复的 stash,应用它
git stash apply <sha>

但这不保证成功——如果 git gc 已经运行过,对象可能已被清理。

stash 和未完成的合并/rebase 冲突?

git stash 在以下状态下无法使用

  • 有未解决的合并冲突
  • 正在 rebase 过程中且有冲突

必须先解决冲突或 abort 操作,才能 stash。

在 Windows PowerShell 中 stash@{0} 报错?

PowerShell 会将 @{} 解释为哈希表语法。解决方式:

powershell
# 方式 1:用引号包裹
git stash pop "stash@{0}"

# 方式 2:用反引号转义
git stash pop stash@`{0`}

# 方式 3:使用 Git Bash

stash 与其他命令的配合

stash + reset:安全地 hard reset

bash
git stash push -m "reset 前备份"
git reset --hard origin/main
# 如果需要恢复
git stash pop

详见 Git reset

stash + rebase:变基前保存修改

bash
git stash push -m "rebase 前备份"
git rebase origin/main
git stash pop

stash + checkout:临时查看其他分支

bash
git stash
git checkout other-branch
# 查看完毕
git checkout original-branch
git stash pop

速查表

目标命令
快速保存git stash
带描述保存git stash push -m "描述"
含未跟踪文件git stash push -u
含所有文件git stash push -a
只保存指定文件git stash push -- <file>
交互式保存git stash push -p
保留暂存区git stash push -k
只保存暂存区(2.35+)git stash push -S
查看列表git stash list
查看内容git stash show -p [stash@{N}]
恢复并删除git stash pop [stash@{N}]
恢复但保留git stash apply [stash@{N}]
恢复时保留暂存状态git stash pop --index
删除指定 stashgit stash drop [stash@{N}]
清空所有 stashgit stash clear
从 stash 创建分支git stash branch <name> [stash@{N}]

相关命令

  • git reset — 重置 HEAD、暂存区和工作区,常与 stash 配合使用
  • git revert — 通过反向提交撤销已推送的提交
  • git rebase — 变基操作,常在 rebase 前用 stash 保存修改
  • git worktree — 多工作目录,stash 频繁切换的替代方案
  • git switch / git checkout — 切换分支,stash 的常见配合场景
  • git clean — 删除未跟踪文件,功能与 stash -u 互补

参考资料