commit

10 阅读3分钟

简介

commit 是 Git 最核心的东西

一句话定义

commit 是一次“完整项目快照”的记录 + 元数据说明。

它不是“记录修改”,
而是记录:

某一时刻,整个项目长什么样。

1.commit 不是“差异”,而是“快照”

很多人误以为:

commit 记录的是改动内容(diff)

Git 实际上存的是:

整个目录树的状态(snapshot)

举例:

第一次提交:

main.go
README.md

第二次你只改了 main.go。

Git 内部仍然会记录:

main.go(新版本)
README.md(旧版本复用)

它是一个完整树结构。

2.一个 commit 里面到底包含什么?

一个 commit 对象包含:

 tree(项目文件结构)
 parent(父提交)
 author(作者)
 committer(提交者)
 时间
 commit message

我们探讨其中重点的几个部分

1)parent(形成链条)

每个 commit 都会指向它的父 commit。

比如:

AB → C

C 里面会记录:

parent: B

这就是为什么 Git 能形成历史链。

如果是 merge commit:

parent: B
parent: E

会有两个父提交。

2)author / committer

Git 区分两个概念:

  • author:原始作者
  • committer:真正提交的人

3)tree(最重要)

tree 是:

这个提交对应的“目录结构”

它包含:

  • 文件(blob 对象)
  • 子目录(tree 对象)

blob 对象和tree 对象也称为git的基本对象

他们的关系为:

blob   → 文件内容
tree   → 目录结构
commit → 指向 tree

注意注意:

每一次 commit 的 tree 代表的是 整个项目的完整目录结构,不是只包含你这次修改的目录。

但——

Git 会“复用没有变化的对象”,
不会重复存储整个项目。

这点非常关键。

commit 记录的是“完整快照”(强调)

Git 不会把整个项目再存一遍,这是 Git 最聪明的地方。

假设你的项目是:

project/            (tree 对象)
 ├── main.go        文件(blob 对象)
 ├── config.yaml    文件(blob 对象)
 └── service/       (tree 对象)
      ├── user.go   文件(blob 对象)
      └── order.go  文件(blob 对象)

现在你只修改了:

service/user.go

你执行:

git commit

新的commit会变成:

project/
 ├── main.go        (复用旧 blob)
 ├── config.yaml    (复用旧 blob)
 └── service/
      ├── user.go   (新 blob)
      └── order.go  (复用旧 blob

当你修改 user.go,并进行commit提交后:

Git 会:

1️⃣ 生成新的 blob(内容变了)
2️⃣ 生成新的 service tree
3️⃣ 生成新的根 tree
4️⃣ 生成新的 commit

所以,只有那条路径上的tree对象是新的。

4)commit message

比如:

feat: add payment API

这是纯文本。

3.commit 是不可变的

一旦 commit 生成:

  • 不能修改
  • 不能编辑
  • 不能改内容

所谓“修改 commit”,

其实是:

创建一个新的 commit
然后让分支指向它

这就是为什么 rebase 会“重写历史”。

4.为什么 Git 这么设计?

因为:

  • blob 是按内容 hash 存储
  • 相同内容只存一份
  • tree 只是指针集合

核心问题

commit 的 tree 是整个项目结构
但 Git 只会新增变化路径上的对象
其他全部复用

这也是为什么 Git 很快

  • 创建分支几乎零成本
  • commit 速度很快
  • 存储空间高效

5.commit的操作命令

1)仓库必须已经初始化

要么:

git init

要么:

git clone ...

否则不能 commit。

2)必须有“已暂存(staged)”的内容

Git 提交的是:

暂存区(index)里的内容
❌ 不是工作区(Working Directory)里的内容

举个例子

你修改了:

main.go

现在状态是:

git status

会看到:

modified: main.go

这时候不能直接提交这个修改。

必须先:

git add main.go

这样文件才进入暂存区。

3)完整的 commit 操作流程

修改文件之后,查看状态

git status

添加到暂存区

添加单个文件:

git add main.go

添加全部改动:

git add .

提交

git commit -m "feat: add login API"

到这里,你成功在g仓库中创建了一个新的commit对象