2.5 提交快照(git commit)


2.5 提交快照(git commit)

本节摘要git commit 把暂存区固化成一条永久的历史记录——它创建提交对象、生成 SHA-1 标识、更新分支指针。本节讲清提交的本质与四步内部动作,重点落在提交消息的写法上,再演示 -m、编辑器、-a、--amend、-v 五种用法,最后用 git log 查看提交历史。

本节导航

阅读完本节,你应当能够:

  1. 说出 commit 在仓库内部做的四件事(快照、对象、指针、历史链)。
  2. 写出一句合格的提交消息:主题行 + 空行 + 正文。
  3. 用 -m、-a、--amend、-v 等选项完成常见提交场景。
  4. 用 git log 查看提交历史并理解其输出结构。

一、问题与直觉

假设你是一个图书管理员,收到一批新书。你的工作不是把它们堆在走廊(那相当于工作区),也不是写张"待入库"清单(那相当于暂存区),而是真正登记入库——给每本书贴上唯一编号、记下归档日期、写清来源和归属书架,让它们从此成为馆藏的一部分,任何人按编号都能找到。

git commit 就是这个"入库动作"。它把暂存区里的内容正式写进仓库的历史,从此这条记录就"钉"在项目的时间线上了——不是临时文件,是永久档案。

有一个动作必须强调:commit 之后,你的提交就有了一个唯一的 SHA-1 哈希,相当于档案编号。这个编号不是随机的,它是根据提交内容(文件快照、作者、时间、父提交)算出来的指纹——内容一变,编号就变。这就是为什么 Git 说"历史不可篡改":想改历史,等于换编号,等于重新造一条历史。这个性质在第 5 章改历史时会把我们折腾得很惨,但现在它是一道可靠的安全锁。

二、核心原理

commit 在仓库内部做的四件事

执行 git commit 时,Git 依次完成:

  1. 创建快照:读取暂存区,把整个项目当前状态打包成一份完整快照(未变文件复用之前的对象)。
  2. 生成提交对象:把快照、作者、提交时间、提交消息、父提交指针打包成一个 Commit 对象。
  3. 更新分支指针:当前分支(比如 main)的指针移动到新提交。
  4. 连接历史链:新提交的"父指针"指向旧提交,形成一条可回溯的链。

commit 的本质动作就是"把暂存区的快照正式挂进历史"。它不直接看工作区——工作区里没 add 的改动,commit 一概不认。

提交对象到底存了什么

好奇的话,可以用命令看一个提交的内部结构:

git cat-file -p HEAD

输出大致是这样:

tree 3a4f2c... ← 本次提交时项目的目录结构(Tree 对象) parent e9d1b8... ← 上一个提交(第一个提交没有这一行) author 张三 <zhangsan@example.com> 1700000000 +0800 committer 张三 <zhangsan@example.com> 1700000000 +0800 修复订单超时 bug

这一小段就是 Commit 对象的全部内容:指向一棵 Tree(当时整个项目的目录树)、指向一个父提交(除非是初始提交)、作者与提交者信息、以及提交消息。看到"tree"这行你就明白——一个提交里装着整个项目在那个时刻的完整快照,这正是第 1.4 节"存快照而非差异"的物理形态。而"parent"这行把提交串成链,就是历史本身。

图 2-5 提交对象链的存储结构

图 2-5 提交对象链的存储结构

一次完整的提交实操演示

跟着敲一遍,体会 commit 前后的变化:

echo hello > a.txt git add a.txt git status # a.txt 在 "to be committed" git commit -m "添加 a.txt" # 提交 git status # 工作区干净 git log --oneline # 看到第一条提交 echo world >> a.txt # 修改已跟踪文件 git commit -am "扩展 a.txt" # -a 自动暂存并提交 git log --oneline # 两条提交

特别留意第二次提交:-am 一步完成 add + commit,因为 a.txt 是已跟踪文件。如果是新文件,-a 就无能为力了——这也是很多新人困惑"为什么 -a 没提交我的新文件"的原因。

提交消息:被严重低估的工程资产

提交消息是 commit 最容易被忽视、也最值得投入的部分。它有三个真实作用:

  • 给未来自己看:三个月后排查问题,靠提交消息快速定位相关改动。
  • 给团队看:代码审查时,好的提交消息让 reviewer 几秒钟抓住改动意图。
  • 给自动化看:很多工具会解析提交消息(比如"feat:" 前缀触发语义化版本、CI 规则)。

推荐的提交消息格式是"主题行 + 空行 + 正文":

  • 主题行:一句话概括,祈使句,不超过 50 字符。用"添加登录页"而不是"添加了登录页"。
  • 正文:解释"为什么改",而不是复述"改了什么"。读者用 diff 就能看到改了什么,提交消息的价值在解释动机。

一个对比:

# 差:看不出意图 update # 好:一眼看懂 修复订单超时 bug 调用支付接口时增加了 5 秒超时上限,并补充了重试逻辑。

主题行与正文的关系,像邮件的标题与正文:标题让人扫一眼决定要不要点开,正文是实质内容。两者都重要,但主题行是门面。

commit 的几种用法

一、最简单:-m 直接给消息

git commit -m "修复登录页按钮错位"

二、多行消息:多次 -m

git commit -m "添加用户中心" -m "含个人信息页与订单列表,后端接口为新增的 profile 路由。"

第二个 -m 的内容会成为正文,两个 -m 之间 Git 自动插空行。

三、打开编辑器写消息

直接 git commit(不带 -m),Git 会打开你配置的默认编辑器,里面预置了 status 输出作参考。写好消息保存退出即提交。

四、-a 跳过 add

git commit -a 会自动暂存已跟踪文件的修改和删除并提交。注意它不处理未跟踪的新文件,新文件仍需先 add。这个选项适合"改的都是老文件、懒得一个个 add"的场景。

git commit -am "统一时间格式"

五、--amend 修补上一次提交

提交后发现消息写错了、或漏了一个小文件,用 --amend 把暂存区与上一次提交合并,形成一条新提交替换旧提交:

git add 忘记的文件 git commit --amend --no-edit

--no-edit 表示沿用原提交消息。重要警告:amend 会改写历史,只适用于尚未推送到远程的提交,推出去再 amend,等于给别人制造混乱(第 5 章细讲)。

六、-v 在编辑器里看 diff

git commit -v 打开编辑器时,在消息模板下方附上本次提交的完整 diff,写消息前先确认"我要提交的到底是什么"。

三、工程实践要点

提交频率与粒度的取舍

提交频率没有标准答案,但有两条经验:

  • 完成一个有意义的逻辑单元就提交,而不是攒一整天。粒度太粗,回滚和排查都困难。
  • 每个提交保持"独立可运行"。别提交一个改一半、编译不过的中间态——万一要回滚到这个提交,整个项目是坏的。
提交习惯 优点 代价
小步频繁提交 历史精细、易回滚、易审查 提交信息要写得多
大块低频提交 省事 历史粗糙、难定位、难回滚

我的判断:新手从"小步频繁"练起,虽然初期觉得啰嗦,但这是最能吃透 Git 价值的习惯。等你有经验了,自然会找到适合自己的粒度。

举一个具体的粒度对比。假设你在实现"用户登录"功能,涉及接口、页面、样式三个文件:

  • 好的做法:完成"接口联调"提交一次("接入登录接口"),页面骨架提交一次("添加登录表单"),样式微调提交一次("美化登录按钮")。任何一步出问题,都能单独回滚。
  • 差的做法:三个文件憋到全部做完一次性提交("完成登录功能")。一旦样式改动有 bug,你想回滚样式,却连同接口一起回滚了。

前者的"代价"是三次提交信息要写三句,后者的"代价"是排查问题时多花几倍时间。怎么选,答案很清楚。

提交前的一分钟检查

每次提交前,快速过三问:

  1. git status:确认要提交的内容都在暂存区,没有漏网之鱼?
  2. git diff --cached:看一眼已暂存的改动,确认没有手滑?
  3. 提交消息:主题行一句话能说清吗?有没有交代"为什么"?

这三问只要一分钟,但能省下后面无数"我怎么把那个文件提交进去了"的后悔。补一句心理建设:提交前花一分钟检查,不是强迫症,而是把"改错了"的成本从"污染历史"降低到"未遂事件"——检查发生在提交之前,一切都还来得及。

⚠️ 常见坑:git commit -a 会把所有已跟踪文件的修改一次全提交,包括你可能不想一起提交的改动。用 -a 前先 git status 确认。

💡 关键直觉:把提交想成给项目的"里程碑立碑"。碑文(提交消息)写得好,后人(包括三个月后的你)扫一眼就知道这座碑纪念了什么;碑文写"update",这座碑就跟没立一样。

常见问题与 FAQ

"git commit 后想改消息怎么办?" 没推远程就 git commit --amend 改;已推远程,就再推一次新提交修改,或用 revert(第 5 章讲)。记住 amend 只适合本地未推送的提交。

"提交了不该提交的文件怎么办?" 分两步:用 restore --staged 或 reset 把它从暂存区撤下(如果还没提交);已提交则用 rm 或改 .gitignore 后新提交移除。注意:已提交进历史的文件,删除它需要新提交"反向操作",历史里仍会留下它——彻底清除历史里的文件要重写历史,属第 5 章范畴。

"提交信息用中文还是英文?" 团队统一即可,个人项目随你喜欢。关键是格式一致、能说清"为什么"。混合语言反而最乱。

"空提交(什么都没改)能提交吗?" 默认不能——Git 会拒绝没有改动的提交。如果你确实需要(比如触发 CI),用 git commit --allow-empty

"commit 会提交没 add 的新文件吗?" 不会。commit 只认暂存区,工作区的新文件没 add 就永远进不了这次提交。这是很多新人"我明明 commit 了怎么文件没进仓库"的原因。

几种常见的坏提交信息

"update" 式:两个字,什么都没说。update 了什么?为什么?这类消息让历史彻底失去检索价值,属于最典型的无效提交信息。

"fix" 式:比 update 好一点,但仍缺关键信息。修了什么 bug?修之前是什么行为?写"修复订单金额溢出导致负数"就具体得多。

"小改" 式:看似诚实,实则无用。"小改配置"和"调整 42 行字体大小"的信息量完全不同。

提交信息里塞代码:把一大段 diff 或 shell 命令贴进提交消息。diff 在 git show 里能看到,重复贴进去只是噪音。

解决办法其实不复杂:把提交消息当成给未来同事(包括未来的你)的一封短信,主题行说清"干了什么",正文说清"为什么"。一次训练,终身受益——而且写多了之后,你会发现它其实是在逼你把每个改动想清楚,这本身就是一种代码质量提升。

一节小结

  • commit 的本质:把暂存区快照固化成 Commit 对象,更新分支指针,挂进历史链。
  • SHA-1 标识:提交的哈希由内容算出,内容变则编号变,构成历史完整性。
  • 消息格式:主题行(≤50 字,祈使句)+ 空行 + 正文(讲为什么)。
  • 五种用法:-m、多次 -m、编辑器、-a(跳过 add)、--amend(修补上一条)。
  • amend 的边界:只改本地未推送的提交,推出去的别 amend。
  • -v 的价值:提交前看 diff,确认提交内容符合预期。
  • 频率建议:小步频繁提交,每条保持独立可运行。

下一节查看状态与历史:git status 回答"现在怎么了",git log 回答"过去怎么了"。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U