19 --- 贮藏与清理:stash 是抽屉,gc 是打包箱
摘要: 本章介绍 Git 中两个实用工具:git stash 用于临时贮藏未提交的改动(像把桌面物品扫进抽屉),方便切换分支处理紧急任务;git gc 用于垃圾回收和打包(像整理档案柜),将散落的小对象压缩成 pack 文件以节省空间。通过对比表格、动手实验和真实场景,帮助读者掌握 stash 的暂存与恢复流程、gc 的安全清理机制,并建立安全使用习惯。
写在前面:这一章要解决什么
你有没有遇到过这种情况:正在写功能写到一半,突然老师说「先修一下这个紧急 bug」。你想切到别的分支去修 bug,但当前分支的代码还没写完、不想提交。怎么办?
这就是 git stash 存在的意义------把没写完的代码暂时藏进抽屉里,等修完 bug 再拿出来继续写。
这一章还讲 git gc(垃圾回收)------仓库用久了会越来越大,gc 就像打包箱,把散落的小文件打包压缩。
学完后,你应该能:
- 用
git stash临时藏起没写完的改动 - 用
git stash pop拿回之前藏的改动 - 理解 stash 底层是特殊的 commit
- 知道什么时候该用
git gc - 理解包文件(pack)是什么
读者设定: 大一同学,已经会用分支和基本命令,但还没用过 stash 和 gc。
1. 定位:为什么要学贮藏和清理
1.1 一句话先记住
stash = 把桌面上的东西暂时扫进抽屉,等会儿再拿出来;gc = 把档案柜里散落的文件打包装箱,节省空间。
1.2 不知道 stash 会怎样
| 没有 stash | 有 stash |
|---|---|
| 写到一半想切分支,Git 不让(有未提交的改动) | git stash 藏起来,随时切 |
| 只好先提交一个「写了一半」的存档 | 不需要提交半成品,保持历史干净 |
| 或者把改动复制到别的地方保存 | 一条命令搞定,不用手动复制 |
| 忘了复制到哪里了 | git stash list 随时查看所有藏起来的东西 |
1.3 和你已经会的事对比
| 你已经会的 | 和这一章的关系 |
|---|---|
第 04 章的 git restore |
restore 是丢弃改动,stash 是暂时藏起来(以后还用) |
| 第 05 章的分支 | 切分支时经常需要先 stash |
| 第 10 章的对象模型 | stash 底层就是特殊的 commit 对象 |
2. 本质:stash 到底做了什么
2.1 stash 的底层原理
当你执行 git stash 时,Git 做了这些事:
- 把工作区和暂存区的差异保存为一个特殊的 commit
- 这个 commit 有两个 parent(工作区状态和暂存区状态)
- 把它存在
refs/stash引用下 - 把工作区和暂存区恢复到 HEAD 的状态
白话: 你把桌面上没做完的作业放进抽屉,桌面恢复到上次交作业的状态。抽屉里的作业随时可以拿出来。
2.2 stash 的家族
| 命令 | 作用 | 白话 |
|---|---|---|
git stash |
藏起当前改动 | 把东西扫进抽屉 |
git stash list |
查看所有藏起来的东西 | 打开抽屉看看里面有什么 |
git stash pop |
拿回最近藏的改动 | 从抽屉里拿出最近放进去的东西 |
git stash apply |
拿回改动但不删除 stash 记录 | 从抽屉里复制一份,原件还在 |
git stash drop |
删除某个 stash 记录 | 把抽屉里的东西扔掉 |
git stash clear |
删除所有 stash 记录 | 清空整个抽屉 |
2.3 stash 可以多次使用
bash
git stash list
输出类似:
text
stash@{0}: WIP on main: a1b2c3 添加登录页面
stash@{1}: WIP on feature: d4e5f6 修改样式
stash@{2}: WIP on main: h7i8j9 临时实验
白话翻译: 抽屉里有 3 样东西,stash@{0} 是最近放进去的。
3. gc:垃圾回收和打包
3.1 为什么需要 gc
你的仓库用久了,.git/objects/ 里会有很多小文件:
- 每次提交都会新增 blob、tree、commit 对象
- reset、rebase 后可能留下「悬空」的对象(没有引用指向的)
- 这些对象散落在大量小文件里,占空间、读写慢
3.2 gc 做了什么
git gc 做两件事:
- 打包(pack) :把大量小对象合并成一个大文件(
.pack),同时生成索引(.idx) - 清理:删除真正不需要的悬空对象(超过 2 周、没有引用指向的)
白话:
- 打包 = 把散落的文件装进压缩包
- 清理 = 把确认没人要的废纸扔掉
3.3 gc 会丢数据吗
不会。 gc 只会删除同时满足以下条件的对象:
- 没有任何引用指向它
- reflog 里也没有记录
- 已经超过宽限期(默认 2 周)
如果你刚做了一个 reset --hard,然后立刻 git gc------别担心,reflog 里有记录,gc 不会删。
3.4 git repack 和 git prune
| 命令 | 作用 | 白话 |
|---|---|---|
git gc |
打包 + 清理 | 大扫除 |
git repack |
只做打包 | 只把文件装箱 |
git prune |
只做清理 | 丢弃无用对象 |
git gc --aggressive |
更彻底的打包(耗时更长) | 深度清洁 |
4. 动手准备
建一个可丢弃的练习目录:
bash
mkdir learn-git-19
cd learn-git-19
git init -b main
echo "第一版" > f.txt && git add f.txt && git commit -m "第一次提交"
演示身份:Ada Example <ada@example.com>
5. 跟着做
实验 1:stash 基本流程
bash
# 开始写新功能(但不提交)
echo "新功能代码" > feature.txt
git add feature.txt
# 突然要修 bug,先藏起来
git stash
# 白话翻译:
# Saved working directory and index state WIP on main: ... 第一次提交
# 查看桌面------改动不见了
git status
# 输出:nothing to commit, working tree clean
# 查看抽屉里有什么
git stash list
# 输出:stash@{0}: WIP on main: ... 第一次提交
# 修完 bug 后,拿回之前的改动
git stash pop
# 查看桌面------改动回来了
git status
# feature.txt 又出现了!
实验 2:带消息的 stash
bash
echo "另一个功能" > feature2.txt
git add feature2.txt
# 给 stash 加个备注,方便以后辨认
git stash push -m "feature2 写到一半"
实验 3:观察 gc 的效果
bash
# 先看看 .git/objects 有多少文件
find .git/objects -type f | wc -l
# 运行 gc
git gc
# 再看,文件数量大幅减少
find .git/objects -type f | wc -l
# 查看 pack 文件
ls .git/objects/pack/
# 输出类似:pack-xxx.idx pack-xxx.pack
6. 命令分组
stash 相关
| 命令 | 作用 | 注意 |
|---|---|---|
git stash |
藏起改动 | 默认不包括未跟踪的文件 |
git stash -u |
藏起改动(包括未跟踪文件) | 新建的文件也会藏 |
git stash push -m "备注" |
带备注的 stash | 方便以后找 |
git stash list |
列出所有 stash | 最近的在最上面 |
git stash pop |
取出并删除最近 stash | 如果有冲突会保留 |
git stash apply stash@{n} |
取出但不删除 | 可以多次 apply |
git stash drop stash@{n} |
删除某个 stash | 删了就没了 |
git stash clear |
清空所有 stash | 谨慎使用 |
git stash branch 新分支名 |
从 stash 创建新分支 | 适合 stash 冲突时 |
gc 相关
| 命令 | 作用 | 注意 |
|---|---|---|
git gc |
打包 + 清理 | 日常足够 |
git gc --aggressive |
深度打包 | 大仓库才需要,耗时长 |
git prune |
只清悬空对象 | gc 的子步骤 |
git repack |
只打包 | gc 的子步骤 |
git count-objects |
查看对象统计 | 看 repo 有多大 |
7. 对照表
stash pop vs apply
| 维度 | stash pop |
stash apply |
|---|---|---|
| 取出改动 | 是 | 是 |
| 删除 stash 记录 | 是 | 否 |
| 冲突时 | stash 记录保留,需要手动处理 | stash 记录保留,需要手动处理 |
| 用哪个 | 取出后不需要再用的 | 取出后还想保留原 stash 的 |
stash vs commit
| 维度 | git stash |
临时 commit |
|---|---|---|
| 需要写说明吗 | 不需要(可以加 -m) | 需要 |
| 在 log 里能看到吗 | 不能(除非用 stash list) |
能 |
| 适合场景 | 临时存放,很快会取出来 | 稍微正式一点的保存点 |
| 可以跨分支吗 | 可以 | 可以 |
8. 安全习惯
| 规矩 | 为什么 |
|---|---|
stash 前先看 git status |
确认你知道藏了什么 |
| 不要长期依赖 stash | stash 不是长期存储,超过 1 周就该提交或删了 |
git stash pop 遇到冲突别慌 |
手动解决冲突就行,stash 记录还保留着 |
| gc 前确认 reflog 里有重要记录 | gc 不会删有引用的对象,但了解原理更安心 |
不要频繁手动 git gc |
Git 会自动 gc,手动执行只在仓库很大时需要 |
9. 真实场景
场景一:写到一半要切分支修 bug
bash
# 写到一半
echo "新功能" > feature.txt
# 突然要修 bug
git stash push -m "feature 写了一半"
git switch hotfix-branch
# 修 bug......
git add .
git commit -m "修复紧急 bug"
git switch main
# 拿回之前的改动
git stash pop
场景二:stash 跨分支迁移改动
你在 wrong-branch 上写了一些代码,发现应该写在 right-branch 上:
bash
git stash
git switch right-branch
git stash pop
场景三:仓库越来越大的处理
bash
# 查看仓库大小
du -sh .git
# 查看对象数量
git count-objects -v
# 如果对象很多,运行 gc
git gc
# 再次查看大小
du -sh .git # 应该变小了
10. 进阶补充(首读可跳过)
10.1 stash 的底层实现
stash 实际上是 1-3 个 commit 对象:
stash@{0}的第一个 parent 是原分支的 HEAD commit- 如果暂存区有改动,第二个 parent 记录暂存区状态
- 如果用了
-u,第三个 parent 记录未跟踪文件
10.2 部分暂存
bash
# 只 stash 某个文件
git stash push -m "只藏 style.css" -- style.css
10.3 pack 文件的增量
Git 的 pack 文件采用增量存储:如果两个版本的文件只差几个字节,pack 只存差异部分,非常节省空间。
11. 小实验
实验 A:stash 基本流程
- 修改一个文件但不要提交
git stash藏起来- 确认工作区干净
git stash pop取回来- 确认改动恢复了
通过标准: 改动能完整藏起和取回。
实验 B:多次 stash
- 做第一组改动,
git stash push -m "改动1" - 做第二组改动,
git stash push -m "改动2" git stash list看到两条记录git stash apply stash@{1}取回第一条git stash drop stash@{1}删除第一条
通过标准: 能管理多条 stash 记录。
实验 C:观察 gc
- 建仓库,做 20 次提交
git count-objects -v记录对象数量git gc- 再次
git count-objects -v,对比变化
通过标准: 理解 gc 把松散对象打包成了 pack 文件。
12. 常见问题
问:stash 会在 push 时推到远程吗?
不会。stash 是纯本地的,不会影响远程仓库。
问:stash 最多能存多少个?
没有硬性限制,但建议不超过 5-10 个。太多会导致混乱。超过 1 周不用的 stash 应该提交或删除。
问:stash pop 遇到冲突怎么办?
和 merge 冲突一样,手动解决冲突,然后 git add 标记已解决。stash 记录会保留(因为 pop 没有成功完成),解决完如果你不需要这个 stash 了,手动 git stash drop。
问:gc 会删我还没提交的代码吗?
不会。gc 只删「没有任何引用指向、reflog 里也没有记录、超过宽限期」的对象。你工作区里的改动不受 gc 影响。
问:什么时候需要手动 gc?
通常不需要------Git 会在合适的时机自动执行。只有仓库特别大或者你刚做了大量清理工作时才需要手动执行。
问:git stash -u 和 git stash 有什么区别?
git stash 只藏已跟踪文件的改动。git stash -u 还会藏新建的未跟踪文件。如果你新建了一个文件还没 git add,普通 stash 不会藏它。
13. 总结
13.1 一页速记
| 项目 | 要点 |
|---|---|
| stash | 把没写完的代码暂时藏起来,等会儿取回来 |
| stash pop | 取出并删除 |
| stash apply | 取出但保留 |
| stash list | 查看所有藏起来的东西 |
| gc | 打包散落对象 + 清理无用对象,不会丢数据 |
| pack | 把很多小文件压缩成一个大文件 |
13.2 本系列中的位置
text
17 拣选与还原 → 18 重置三兄弟 → 19 贮藏与清理(你在这里) → 20 远程进阶
↑ 「暂时放一放」和「打扫卫生」
13.3 思维升华
stash 是 Git 给你的「等我一下」按钮。 写到一半被打断?不要慌,也不要急着提交半成品------stash 一下,等会儿回来继续。
13.4 延伸阅读
- Pro Git --- 贮藏与清理
- git-stash 文档
- git-gc 文档
- git-count-objects 文档
- 本仓库图示署名:
assets/diagrams/ATTRIBUTION.md
13.5 检查清单
- 能用 stash 藏起改动并取回
- 知道 stash list / pop / apply / drop 的区别
- 理解 stash 底层是特殊的 commit
- 知道 gc 做了什么(打包 + 清理)
- 知道 gc 不会删有用的数据
- 完成了实验 A、B、C