bigywater.tech
Git · 60

本地 clone:硬链接与 --no-hardlinks

在本机 git clone 一个本地仓库时,git 其实不是复制对象而是做硬链接。--no-hardlinks、file://、--shared 各自又做了什么?占多少磁盘、快不快、安不安全?这篇用三千多个松散对象的仓库逐种实测。

0.0 vs 12.2 MB
硬链接 clone 与 --no-hardlinks clone 在 .git/objects 上额外占用的磁盘
1977 ms
硬链接 clone 的耗时——在 macOS APFS 上反而比真复制(1103 ms)慢
bad object HEAD
源仓库把对象 prune 之后,--shared clone 的 git log 的报错

四种方式

实测 · 本机执行输出
  $ git count-objects -vH | grep -E "^(count|size|in-pack|size-pack):"
  count: 3003
  size: 11.80 MiB
  in-pack: 4525
  size-pack: 388.06 KiB

本地路径 clone 默认把源仓库 .git/objects 下的文件做硬链接(链接数变成 2);--no-hardlinks 真复制;file:// 走与远程相同的协议,把对象重新打包成一个 pack,不用链接;--shared 完全不复制,在 objects/info/alternates 里记下源仓库的对象目录。

磁盘与时间

实测 · 本机执行输出
  方式                                         耗时      松散  pack       松散链接数    pack 链接数clone 的 objects 额外磁盘  alternates
  git clone /路径(默认,本地路径)                 1977ms   3,003     1           2           2          0.0 MB          无
  git clone --no-hardlinks /路径           1103ms   3,003     1           1           1         12.2 MB          无
  git clone file:///路径                    795ms       0     1    (没有松散对象)           1          0.7 MB          无
  git clone --shared /路径                  729ms       0     0    (没有松散对象)   (没有 pack)          0.0 MB          有
  (“objects 额外磁盘”=把源和 clone 的 .git/objects 放在同一个 du 里统计,clone 多出来的部分;硬链接的文件在 du 里只算一次)

硬链接和 shared 不额外占对象的磁盘空间,真复制占 12.2 MB。但硬链接在 APFS 上要创建几千个链接,比真复制还慢;file:// 因为把松散对象打成一个 pack,文件少,最快。

两种 clone 的结构

实测 · 本机执行输出
  $ ls .git/objects | head -3 | tr "\n" " "; echo "...(松散对象目录)"; ls .git/objects/pack
  info pack ...(松散对象目录)
  pack-9d5ddcc0fd80a04f18c67e41f281192786a263fe.idx
  pack-9d5ddcc0fd80a04f18c67e41f281192786a263fe.pack
  pack-9d5ddcc0fd80a04f18c67e41f281192786a263fe.rev
  $ ls .git/objects/info; git config --get remote.origin.url | sed "s#.*/#…/#"
  packs
  …/l56src

file:// 的 clone 的 .git/objects 里只有 info 和 pack,没有松散对象目录;路径方式保持源仓库的结构。

安全性

实测 · 本机执行输出
  $ cat .git/objects/info/alternates | sed "s#.*/ltmp#<ltmp>#"; ls .git/objects | tr "\n" " "; echo "(--shared 的 clone 自己没有对象文件)"
  <ltmp>/l56a/.git/objects
  info pack (--shared 的 clone 自己没有对象文件)
  在源仓库里把最新的提交永远删掉(reset、清 reflog、gc --prune=now):
  $ git log --oneline | wc -l | sed "s/^ */源仓库现在的提交数: /"
  源仓库现在的提交数: 2
  [硬链接的 clone] git fsck → (无输出:完整)
  [硬链接的 clone] git log → bfb737a 提交 3 | d75eab8 提交 2 | 0db3e3f 提交 1
  [--shared 的 clone] git fsck → error: refs/heads/main: invalid sha1 pointer bfb737aa61ba6a7bcbf8862277d4d219aed950c0
error: refs/remotes/origin/HEAD: invalid sha1 pointer bfb737aa61ba6a7bcbf8862277d4d219aed950c0
  [--shared 的 clone] git log → fatal: bad object HEAD

硬链接的文件在源仓库删除后仍然存在,clone 是自给自足的。--shared 的 clone 依赖源仓库的对象:源仓库 gc/prune 掉的对象,shared clone 里也就没了,fsck 报 invalid sha1 pointer。所以 shared 只适合源仓库不会清理的场合(如参考仓库、CI 缓存)。

分步演示

本地 clone
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。源仓库由脚本生成(约 7,500 个小文件的三次提交,最后一次的 3,003 个对象保持松散);耗时是 3 次中位数(含检出工作区);磁盘用 du -sk 统计 .git/objects,源与 clone 放在同一次 du 里使硬链接只算一次;文件系统是 APFS
    • 没有验证:Linux ext4/xfs 上硬链接的耗时(预期更快,没有实测);--reference 与 --dissociate;git gc 对 alternates 的保护;跨文件系统 clone 时硬链接失败自动退回复制;git clone --local 的显式写法
    京ICP备2026067502号-1京公网安备11010502063487号