知其然,知其所以然
Git 内部原理 2026-10-11

upload-pack 的交互:用 GIT_TRACE_PACKET 看 want/have

用 GIT_TRACE_PACKET 和 git upload-pack --advertise-refs 看 v0 协议的交互:服务端先列 ref 并把 capabilities 用 \0 接在第一个 ref 后,客户端 want … done,服务端 NAK/ACK 后在 side-band 里发 PACK;pkt-line 是 4 位十六进制长度(含自身)加内容,0000 是 flush;增量 fetch 靠 have 只传缺的 4 个对象;已是最新时不发 want。

Git upload-pack GIT_TRACE_PACKET want have 协议 真实验证 动画演示
Git 内部原理 2026-10-11

protocol v2 与 v0 的区别

实测协议 v2 与 v0:本机 git 默认 v2;v2 的服务端广告只有能力,ref 由客户端用 command=ls-refs(可带 ref-prefix)按需请求,再用 command=fetch 取对象;有 5000 个 tag 的远程 fetch 一个分支时,v0 服务端发出 5007 行(5002 个 ref),v2 只发 15 行(1 个 ref);无变化的 fetch 跟踪行数 10008 对 48。

Git 协议 v2 ls-refs GIT_TRACE_PACKET 真实验证 动画演示
Git 内部原理 2026-10-11

浅克隆 --depth

实测浅克隆:本地路径 clone 会忽略 --depth(warning),file:// 才生效;.git/shallow 记录边界提交,边界提交的 parent 字段在但对象不在;2000 个提交的仓库 --depth 1 只有 52 个对象、96 KB(完整是 6000 个对象、4197 KB);--deepen 与 --unshallow 恢复;blame、describe 等命令在边界处受限;--shallow-since、--shallow-exclude 按时间或引用截断。

Git 浅克隆 --depth shallow 真实验证 动画演示
Git 内部原理 2026-10-11

部分克隆 --filter=blob:none

实测部分克隆:300 个 20 KB blob 的仓库完整克隆 1,200 个对象 5932 KB,--filter=blob:none 只有 900 个对象 67 KB,tree:0 只有 300 个对象 37 KB;配置里 remote.origin.promisor 与 .promisor 文件标记承诺者;访问缺失的 blob 时后台 git fetch --stdin 取回;checkout 一次批量取 39 个 blob,git log -p 触发 260 次逐个 fetch。

Git partial clone --filter promisor 按需下载 真实验证 动画演示
Git 内部原理 2026-10-11

fetch 的协商:怎么只下载缺的对象

实测 fetch 的协商:落后的客户端第一批发 16 个 have、1 轮;客户端有 500 个本地独有提交时默认的 consecutive 算法发 1008 个 have、6 轮,skipping 只要 16 个 have、1 轮,noop 一个都不发但多下载 350 KB;--negotiation-tip 限制协商起点;对 213 KB 文件改一行的 fetch,thin pack 只有 308 字节,非 thin 要 136,125 字节。

Git fetch 协商 have negotiation thin pack 真实验证 动画演示
Git 内部原理 2026-10-11

push 的 receive-pack、非快进拒绝与 --force-with-lease

实测 push:receive-pack 的协议是“旧值 新值 ref”加 pack,回 unpack ok 与 ok ref;非快进被客户端拒绝(fetch first),--force 覆盖后被覆盖的提交对象仍在但无分支引用;--force-with-lease 在没 fetch 时拒绝(stale info),fetch 过但没合并时仍放行,加 --force-if-includes 才拒绝;服务端 receive.denyNonFastForwards 与 denyCurrentBranch 的拒绝;--atomic 全成功或全失败。

Git push receive-pack force-with-lease 非快进 真实验证 动画演示
Git 内部原理 2026-10-11

hooks:触发时机、顺序与退出码的影响

装上只记录调用的 hook 实测触发时机与顺序:commit 是 pre-commit → prepare-commit-msg → commit-msg → post-commit;前两个检查类 hook 非 0 中止提交,post-commit 被忽略,--no-verify 只跳过前者;checkout、merge、rebase、push 的 hook 参数;服务端 pre-receive → update(每个 ref)→ post-receive → post-update;没有可执行位的 hook 被忽略,clone 不复制 hook,core.hooksPath 可共享。

Git hooks pre-commit pre-receive 退出码 真实验证 动画演示
Git 内部原理 2026-10-11

submodule:gitlink 与 .gitmodules

实测子模块:submodule add 留下 .gitmodules、.git/config 的 submodule.* 项、子模块目录里指向 .git/modules/ 的 .git 文件;超级项目的 tree 里只有一个 160000 条目,值是子仓库的提交 hash;子仓库有新提交时 status 显示 M、submodule status 以 + 开头;普通 clone 后子模块为空(-),update --init 才检出记录的提交(分离 HEAD);切换超级项目的提交不会自动更新子模块;.gitmodules 改 URL 要 sync。

Git submodule gitlink .gitmodules 真实验证 动画演示
Git 内部原理 2026-10-11

filter-branch 改写历史

实测 git filter-branch 删除误提交的密码:--index-filter 加 --prune-empty 使 6 个提交变 4 个且 hash 变化;改写前的 ref 备份在 refs/original/ 下,旧密码 blob 仍可读出;轻量标签被重写而注解标签对象不会(除非 --tag-name-filter cat);真正删除要清掉 refs/original、旧标签和 reflog 再 gc --prune=now;300 个提交上 index-filter 11.7 秒、tree-filter 18.4 秒;官方推荐改用 git filter-repo。

Git filter-branch 历史改写 refs/original 真实验证 动画演示
Git 内部原理 2026-10-11

notes

实测 git notes:笔记不改变提交的 hash,存在 refs/notes/commits 这个普通分支里,tree 里“文件名=被注解的提交 hash、内容=笔记”,每次修改多一个提交;可以有多个命名空间(--ref、--notes、notes.displayRef);rebase 后笔记留在旧提交上,notes.rewriteRef 才会复制;50 条以下不扇出,100 条起按两位十六进制目录扇出;笔记不随 push / fetch / clone 同步,要显式 refspec。

Git notes refs/notes 笔记 真实验证 动画演示
Git 内部原理 2026-10-11

bundle:离线传输仓库

解析 git bundle 的字节:文本头(# v2 git bundle、每行 hash ref,增量时多出以 - 开头的前置条件行)加一个空行加一个标准 packfile;完整 bundle 的 pack 含 30 个对象,与可达对象数一致;git bundle verify / list-heads;把 bundle 当远程 clone 或 fetch;增量 bundle 只有 9 个对象、855 字节,接收方缺少前置提交时 verify 报错。

Git bundle packfile 离线传输 真实验证 动画演示
Git 内部原理 2026-10-11

SSH 签名提交与本地验证

实测 SSH 签名提交:gpg.format=ssh 与 user.signingkey 签出带 gpgsig 头(-----BEGIN SSH SIGNATURE-----)的 commit;验证要配置 gpg.ssh.allowedSignersFile,否则 No signature;手工去掉 gpgsig 头后用 ssh-keygen -Y verify -n git 验证与 git 一致,改一个字或换命名空间都失败;篡改提交验证失败;陌生人签名显示 No principal matched,白名单可设 valid-before 过期;%G? 状态码 G/U/N;签名标签。

Git SSH 签名 gpg.format allowed_signers 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号