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

prune 与宽限期:unreachable 对象什么时候真被删

用修改文件时间的办法验证 git gc 的 2 周宽限期:13 天前的不可达 blob 保留、15 天前的被删;git prune --expire=now 立刻删掉没有引用的新对象但不碰暂存区里的对象;reflog 仍引用着 reset 丢掉的提交,清掉 reflog 再 prune 才真正消失;pack 里的不可达对象由 cruft pack 的 .mtimes 计时。

Git prune gc 不可达对象 宽限期 reflog cruft 真实验证 动画演示
Git 内部原理 2026-10-11

fsck 与悬空对象:误删的 commit 怎么找回

在真实仓库里演示用 git fsck 找回丢失的内容:git branch -D 之后的 dangling commit 用 git branch 找回整条历史;dangling 是不可达对象的顶端(1 个),unreachable 列出全部(3 个 commit、3 个 tree、4 个 blob);add 过但被 reset --hard 冲掉的文件能从 lost-found 捞回;stash drop 之后 stash 提交仍可 apply;fsck 能报告改坏和缺失的对象。

Git fsck dangling 恢复 stash lost-found 真实验证 动画演示
Git 内部原理 2026-10-11

HEAD 与 detached HEAD

在真实仓库里观察 HEAD:在分支上 .git/HEAD 是 ref: refs/heads/main,detached 时直接是 hash,symbolic-ref 报错;在 detached HEAD 上提交后切走,git 警告会丢下一个提交,reflog 里能找回;ORIG_HEAD、MERGE_HEAD、CHERRY_PICK_HEAD、FETCH_HEAD 是只在对应操作中出现的文件;新仓库和 --orphan 的未出生分支。

Git HEAD detached HEAD symbolic-ref reflog 真实验证 动画演示
Git 内部原理 2026-10-11

packed-refs 与松散 ref 谁优先

在真实仓库里看 ref 的两种存放:松散文件与 .git/packed-refs;pack-refs --prune 把 5 个 ref 移进一个按名字排序的文件(注解标签多一行 ^peeled);同一个 ref 两处都有时松散文件优先,删掉它旧值会重新露出;删 packed 的分支要重写 packed-refs;5000 个 tag 时 git tag -l 从 90.9 ms 降到 14.6 ms,单个查找没有变化;git gc 默认会 pack-refs。

Git refs packed-refs pack-refs 松散引用 真实验证 动画演示
Git 内部原理 2026-10-11

reflog:记录什么、什么时候过期

在真实仓库里看 reflog:.git/logs/HEAD 和每个分支一份的文本文件,记录 commit、amend、checkout、merge、rebase 的每次移动;分支有、标签默认没有、裸仓库默认没有;用造出不同年龄的条目验证 git reflog expire 默认保留可达的 90 天、不可达的 30 天;用 HEAD@{1} 撤销 reset --hard,expire --expire=now 之后就没有退路。

Git reflog HEAD@{n} 过期 gc 真实验证 动画演示
Git 内部原理 2026-10-11

reftable 后端:--ref-format=reftable 的目录结构

在真实 git 2.54.0 里创建 reftable 仓库并解析它:HEAD 占位值、refs/heads 说明文件、.git/reftable 下按更新序号命名的表文件与 tables.list;解析表的 24 字节头、68 字节尾与 CRC-32;5000 个 tag 的 reftable 为 135,479 字节(packed-refs 为 290,103),列举与 packed-refs 一样快;事务写入只产生 1 个表,git refs migrate 可在两种后端间转换。

Git reftable refs ref-format 真实验证 动画演示
Git 内部原理 2026-10-11

refspec 的语法与语义

用本机的裸仓库当远程,逐项验证 refspec:默认 +refs/heads/*:refs/remotes/origin/* 只匹配分支;<src>:<dst> 的 * 必须成对;无 + 只允许快进,有 + 强制更新;空 src 推送即删除;fetch <ref> 机会性更新 origin/*;^ 负 refspec 排除分支;标签的自动跟随与 --no-tags;push HEAD:refs/review/42 可推到任意命名空间。

Git refspec fetch push 远程跟踪 真实验证 动画演示
Git 内部原理 2026-10-11

远程跟踪分支 refs/remotes 的更新时机

用同一个远程的两个克隆验证 refs/remotes 的更新时机:只有 fetch、pull、push、clone 会更新 origin/*,commit、checkout、ls-remote 不会;git status 的落后提示基于缓存;push 被拒绝时不更新;远程删除分支后本地的 origin/* 变 stale,要 fetch --prune 才删;origin/HEAD 不会自己跟随远程默认分支;remote-tracking ref 也有 reflog。

Git 远程跟踪分支 origin/main fetch prune 真实验证 动画演示
Git 内部原理 2026-10-11

upstream 与 ahead/behind 的计算

用同一个远程的两个克隆验证 upstream 与 ahead/behind:upstream 只是 branch.<名>.remote 和 .merge 两项配置;领先 = origin/main..main 的提交数,落后 = main..origin/main,git rev-list --left-right --count 一次给出两个数;@{u} 与 @{push} 可以指向不同的 remote(simple 下会报错);只有起点是 remote-tracking 分支才自动设置 upstream;远程分支删除后变成 gone。

Git upstream ahead behind rev-list 真实验证 动画演示
Git 内部原理 2026-10-11

分支改名与删除对对象的影响

在真实仓库里验证分支改名与删除对对象的影响:git branch -m 只动 ref 文件、reflog、config 与 HEAD(对象数 5 → 5,upstream 仍指向 origin/old-name);-M 覆盖已有分支会让原提交变成不可达对象;-D 删除分支后对象数不变(13 → 13),要清 reflog 并 gc --prune=now 才会真删(降到 8);-d 的“已合并”判据是 upstream 优先;检出中的分支无法删除。

Git 分支 改名 删除 git branch 对象 真实验证 动画演示
Git 内部原理 2026-10-11

index 的 stat 缓存:git status 为什么快

解析 .git/index 里每个条目的 stat 缓存(ctime、mtime、dev、ino、mode、uid、gid、size)并与 os.stat 对账;git status 只在 stat 不同时才读文件;core.trustctime=false 加 core.checkStat=minimal 会让原地改写且恢复 mtime 的改动不可见;10,000 个文件时 stat 缓存失效使 git status 从 29 ms 变成 204 ms,cp -a 搬仓库后首次 status 734 ms。

Git index stat git status 性能 真实验证 动画演示
Git 内部原理 2026-10-11

racy git:时间戳相同时怎么避免误判

验证 racy git:index 条目的 mtime ≥ index 文件的修改时间时,stat 一致不可信,git 会比较内容——同样“原地改写并恢复 mtime”的伪装,非 racy 条目看不出(A),racy 条目被发现(AM);racy 的粒度约为 1 秒;文件 mtime 在未来时,1 万个文件的 git status 每次 207 ms,正常只要 30 ms。

Git racy git index mtime git status 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号