实测 git 的凭据协议:用 git credential fill / approve / reject 驱动 helper 的 get / store / erase,stdin 是 key=value 行;useHttpPath 决定是否传 path;用 git http-backend 起一个要求 Basic 认证的本机服务,看到 401 → helper get(带 wwwauth 质询)→ 带认证的请求 → store,密码错则 erase;多个 helper 取凭据按顺序第一个给出的赢、存凭据广播;credential-store 明文保存,权限 600。
实测 clean/smudge 过滤器:add 时 clean 后存进 blob(工作区不变),checkout 时 smudge;status/diff 在内容或 stat 变化时调用 clean;clean 有损时真实内容会在检出时丢失;hash-object --no-filters 看原样内容;clean(smudge(x)) 必须等于 x;自定义 merge driver 收到 %O %A %B %L %P,退出 0 采用 %A 里的结果;内置 text/binary/union 对同一冲突的处理。
实测 git maintenance:10 个任务名各自改变仓库的什么(loose-objects 两次才清理松散对象、pack-refs、commit-graph、geometric-repack、gc);prefetch 把远程分支下载到 refs/prefetch;maintenance.strategy 的四种取值实际启动的子命令;--auto 的阈值(commit-graph.auto 与 loose-objects.auto 默认 100,达到才运行);maintenance.<任务>.enabled 的效果。
实测对象数量增长时各命令的耗时:提交数 1,000 → 40,000 时 status 与 log -1 不变、整段历史的遍历随提交数增长;文件数 1,000 → 50,000 时 status、add -A、commit 随文件数增长;松散对象的遍历比打包慢约 10 倍、占盘约 32 倍;带路径的 log 在 commit-graph 与 --changed-paths 下从 246 ms 降到 76 ms;file:// 克隆的耗时与大小。
在真实的 git 2.54.0 仓库里手工解开 .git/objects:blob 是“blob 长度\0内容”,tree 是模式、名字、20 字节二进制 hash 的连续条目,commit 是几行头加说明的纯文本;只用 Python 的 zlib 沿 HEAD 往下走一遍,结果与 git ls-tree -r 完全一致。
用 Python 的 hashlib 手算 git 的 hash:blob 是 SHA-1("blob 长度\0内容"),tree 与 commit 同一公式,手算结果与 git hash-object、write-tree、commit 全部一致;改一个字节 160 位里有 86 位翻转;同内容不同文件名或权限的 blob hash 相同;同样 12 字节放进四种对象类型得到四个不同的 hash。
在真实仓库里放进普通文件、可执行文件、符号链接和子模块,对照 git ls-tree 与 tree 对象的原始字节:四种模式 100644、100755、120000、160000;符号链接的 blob 内容就是目标路径;子模块条目是子仓库的 commit hash,在主仓库里根本找不到这个对象;写其它模式时 git 会规整成这四种之一。
在真实仓库里数 commit 对象的 parent 行(根提交 0、普通 1、合并 2、章鱼合并 3);用 amend 和 cherry-pick 验证 author 保留、committer 与时间改变;同一提交的两个时间戳互相独立,头部的时区只影响显示;parent 的顺序决定 HEAD^1 / HEAD^2 / --first-parent 的含义。
在真实仓库里区分轻量标签与注解标签:轻量标签只是一个 ref 文件(对象数不变),注解标签多一个 tag 对象(对象数 3 → 4,ref 指向它而不是 commit);读 tag 对象原文,用 ^{} 剥开;describe 默认只看注解标签;标签可以指向 tag、tree、blob;push --follow-tags 只推注解标签;删标签后 tag 对象成为悬空对象。
在 5 个提交的链上改第 2 个提交的一个字:第 1 个 hash 不变,第 2~5 个全变,尽管后三个的内容一个字没动;原因是 parent 行进了 commit 的 hash;只改说明时 tree 完全不变,改文件内容只让被改的那个 tree 变;改最新的提交只影响 1 个。
在真实 git 2.54.0 里创建 --object-format=sha256 仓库并与 sha1 仓库对比:repositoryformatversion 变成 1,hash 40 位变 64 位,blob 公式换成 SHA-256 手算一致,tree 条目的 hash 20 字节变 32 字节(10 个文件 359 → 479 字节),index 786 → 970 字节;两种仓库之间 fetch 直接报 mismatched algorithms。
在真实仓库里验证空目录提交不了:git add 成功但什么都不加,clone 出来没有它;暂存区只有文件路径;手工用 mktree / commit-tree 造出带空 tree 条目的提交,fsck 不报错,检出时 git 也不会创建空目录;常用的 .gitkeep 与 .gitignore 两种占位做法。