Git Internals
传输协议、协商与 pack 交换
理解 fetch / clone / push 时的协商过程,能帮助你看懂 Git 不是在“整仓同步”,而是在交换对象与引用状态。
- 想建立稳定 Git 心智模型的学习者
- 经常遇到历史、引用、恢复问题的开发者
- 会看基础命令输出
- 知道提交、分支、HEAD 这些名词
- 只背底层术语却不连接到实际命令
- 把对象、引用、工作区混成一层理解
很多人第一次使用 Git 时,会把 clone、fetch、push 想成:
- “把仓库整个复制过来”
- “把我本地的东西整个发过去”
但 Git 真正的传输逻辑远比这更精细。
它关心的是:
- 双方各自已有哪部分对象
- 哪些引用需要更新
- 哪些对象需要被打包传输
这也是为什么 Git 在大型仓库里依然能保持很强的效率。
Git 传输到底在交换什么
从底层看,Git 主要在交换两类信息:
- 引用状态:例如分支和标签指向什么提交
- 对象数据:为让这些引用成立,双方还缺哪些对象
也就是说,fetch 不是在“复制整个仓库目录”,而是在做一次对象图同步协商。
为什么协商过程很关键
因为大多数时候,本地和远端并不是完全陌生的两个仓库。
它们往往已经共享大量历史。
所以 Git 需要先判断:
- 你已经有什么
- 我还缺什么
- 哪些对象不必重复发送
这个“先判断再传”的过程,就是它高效的根源之一。
pack 在这里扮演什么角色
对象理论上可以一个个传,但这样效率太差。
所以 Git 通常会把需要发送的一组对象打成 pack,再进行传输。
这意味着:
- 传输效率更高
- 重复内容能被更好压缩
- fetch / clone / push 的体感成本会低很多
所以理解 pack,不只是存储优化问题,也是在理解网络同步为什么能这么做。
refs/heads/mainorigin/mainobjects
refs 协商pack 交换
refs/heads/mainrefs/tags/v2.0pack objects
clone 和 fetch 的底层差异
从体验看:
clone像第一次拉全仓fetch像同步增量
但它们在底层都离不开同样的核心问题:
- 远端有哪些 refs
- 本地已有多少对象
- 需要再补哪些 pack
不同的是,第一次 clone 时,本地通常“几乎什么都没有”,所以协商空间更小;而 fetch 通常更依赖双方已有历史的重叠。
push 为什么也是协商
很多人误以为 push 是“我把我这边的提交直接扔给服务器”。
其实 push 也涉及:
- 远端当前 refs 状态
- 是否允许更新这些 refs
- 远端是否缺少相关对象
- 更新是否会违反 fast-forward 或保护规则
所以 push 既是对象传输,也是引用更新请求。
一旦你用这个视角看 fetch 和 push,就更容易理解为什么网络传输、分支保护和 packfiles 会连成一体。
为什么这能帮助你理解实际问题
这个视角能帮你更好理解:
- 为什么 fetch 很快
- 为什么 push 会被拒绝
- 为什么大仓库更依赖 pack 优化
- 为什么某些网络或协议问题会直接表现成 clone/fetch 异常
常见误区
clone 就是下载一个项目目录
不是。它在下载对象数据库和引用图,而不是简单文件夹压缩包。
fetch 一定会把远端所有东西都拉下来
不一定。核心仍然是 refs 与对象协商。
push 只是上传我的提交
也不完整。它还在请求远端更新引用,并接受远端规则校验。
一句最值得记住的话
Git 的网络传输本质上是对象和引用的协商交换,而不是整仓盲目复制。