19 — 贮藏与清理:stash 是抽屉,gc 是打包箱

19 --- 贮藏与清理:stash 是抽屉,gc 是打包箱

摘要: 本章介绍 Git 中两个实用工具:git stash 用于临时贮藏未提交的改动(像把桌面物品扫进抽屉),方便切换分支处理紧急任务;git gc 用于垃圾回收和打包(像整理档案柜),将散落的小对象压缩成 pack 文件以节省空间。通过对比表格、动手实验和真实场景,帮助读者掌握 stash 的暂存与恢复流程、gc 的安全清理机制,并建立安全使用习惯。

写在前面:这一章要解决什么

你有没有遇到过这种情况:正在写功能写到一半,突然老师说「先修一下这个紧急 bug」。你想切到别的分支去修 bug,但当前分支的代码还没写完、不想提交。怎么办?

这就是 git stash 存在的意义------把没写完的代码暂时藏进抽屉里,等修完 bug 再拿出来继续写。

这一章还讲 git gc(垃圾回收)------仓库用久了会越来越大,gc 就像打包箱,把散落的小文件打包压缩。

学完后,你应该能:

  1. git stash 临时藏起没写完的改动
  2. git stash pop 拿回之前藏的改动
  3. 理解 stash 底层是特殊的 commit
  4. 知道什么时候该用 git gc
  5. 理解包文件(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 做了这些事:

  1. 把工作区和暂存区的差异保存为一个特殊的 commit
  2. 这个 commit 有两个 parent(工作区状态和暂存区状态)
  3. 把它存在 refs/stash 引用下
  4. 把工作区和暂存区恢复到 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 做两件事:

  1. 打包(pack) :把大量小对象合并成一个大文件(.pack),同时生成索引(.idx
  2. 清理:删除真正不需要的悬空对象(超过 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 基本流程

  1. 修改一个文件但不要提交
  2. git stash 藏起来
  3. 确认工作区干净
  4. git stash pop 取回来
  5. 确认改动恢复了

通过标准: 改动能完整藏起和取回。

实验 B:多次 stash

  1. 做第一组改动,git stash push -m "改动1"
  2. 做第二组改动,git stash push -m "改动2"
  3. git stash list 看到两条记录
  4. git stash apply stash@{1} 取回第一条
  5. git stash drop stash@{1} 删除第一条

通过标准: 能管理多条 stash 记录。

实验 C:观察 gc

  1. 建仓库,做 20 次提交
  2. git count-objects -v 记录对象数量
  3. git gc
  4. 再次 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 -ugit 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 延伸阅读

13.5 检查清单

  • 能用 stash 藏起改动并取回
  • 知道 stash list / pop / apply / drop 的区别
  • 理解 stash 底层是特殊的 commit
  • 知道 gc 做了什么(打包 + 清理)
  • 知道 gc 不会删有用的数据
  • 完成了实验 A、B、C
相关推荐
用户330144867633 小时前
20 — 远程进阶:强制推送的保险绳——force-with-lease
git
Cooper253 小时前
别急着改 Git 历史:rebase、cherry-pick、bisect 与 reflog 的安全操作顺序
git
刚入门的大一新生4 小时前
Linux-版本控制器Git
git
扶苏10024 小时前
同一个 Git 项目整出两份,切分支互不影响?两种方案实测
大数据·git·elasticsearch
ZJU_统一阿萨姆5 小时前
【Git】Github 开源许可证详解
git·开源·github
Geek-Chow1 天前
Git 标签与发布流程:轻量标签、附注标签与签名标签
git·tags
wear工程师1 天前
Git 冲突怎么解决:别只删标记,先看懂暂存区的 1、2、3 阶段
git
村雨遥1 天前
覆盖提交不等于删除,这才是彻底清除 Git 历史的正确姿势
git·github
淮北4941 天前
ubuntu22 默认输入法调整频率
运维·服务器·git·ubuntu·elasticsearch