bigywater.tech
Git · 14

符号链接在 git 里怎么存

符号链接在 git 里到底存了什么?这篇创建相对、绝对、悬空、指向目录的四种链接,看暂存区和 blob 里的内容,clone 后哪些还有效,git add 带斜杠的行为,以及 core.symlinks=false 的环境里会发生什么。

120000
所有符号链接在暂存区里的模式,不管目标存不存在
绝对路径
abs 链接的 blob 里存的是完整的绝对路径,clone 后仍指向原仓库
T
core.symlinks=false 的检出里,git status 对每个链接都显示 T(类型改变)

存了什么

实测 · 本机执行输出
  $ git add -A && git ls-files -s | grep -v target | grep -v inner
  120000 aaadab1724b969bfa6ce90945d36d20c84b45d39 0	abs
  120000 9a7bfc0da3899383875492dc43a26526c9e137c1 0	dangling
  120000 d09b80733baa4f6b198f2cf2d62bbfc5b6cbf1f0 0	dir/up
  120000 87245193225f8ff56488ceab0dcd11467fe098d0 0	dirlink
  120000 4cbb553f3f4ac2ee7b01ff6c951d6bf583c39c15 0	rel
  $ for f in rel abs dirlink dangling dir/up; do printf "%-9s readlink=%-34s blob=" $f "$(readlink $f)"; git cat-file -p :$f; echo; done
  rel       readlink=target.txt                         blob=target.txt
  abs       readlink=/work/l10/target.txt blob=/work/l10/target.txt
  dirlink   readlink=dir                                blob=dir
  dangling  readlink=nothing.txt                        blob=nothing.txt
  dir/up    readlink=../outside                         blob=../outside

符号链接作为一个 120000 条目,blob 内容就是 readlink 的原样字符串。git 不检查目标:相对、绝对、不存在、指向仓库外面的,都照样记录。

可移植性

实测 · 本机执行输出
  $ git clone -q . ../l10clone && cd ../l10clone && for f in rel abs dirlink dangling; do printf "%-9s -> %-28s 能读到内容: " $f "$(readlink $f)"; (cat $f/inner.txt 2>/dev/null || cat $f 2>/dev/null) || echo 否; done
  rel       -> target.txt                   能读到内容: real
  abs       -> /work/l10/target.txt 能读到内容: real
  dirlink   -> dir                          能读到内容: inner
  dangling  -> nothing.txt                  能读到内容: 否

相对链接换个位置检出仍然有效;绝对链接把作者机器上的路径带到了别处,通常是悬空的。悬空链接检出后依旧悬空。

指向目录的链接

实测 · 本机执行输出
  $ git add ln; git ls-files -s
  120000 ac558a3e1bf44424bf2af97380ee201860ba8a58 0	ln
  $ git rm -q --cached ln; git add ln/ 2>&1; git ls-files -s
  fatal: pathspec 'ln/' is beyond a symbolic link

git add ln 记录链接本身;git add ln/ 带了斜杠,被当成穿过符号链接的路径,git 拒绝。

链接目标变化

实测 · 本机执行输出
  $ ln -sfn dir rel; git status --short; git diff rel
   M rel
  diff --git a/rel b/rel
  index 4cbb553..8724519 120000
  --- a/rel
  +++ b/rel
  @@ -1 +1 @@
  -target.txt
  \ No newline at end of file
  +dir
  \ No newline at end of file
  $ git checkout -q -- rel; echo changed >> target.txt; git status --short
   M target.txt

改链接目标让链接的 blob 内容变化,git diff 显示两个路径字符串(模式仍是 120000);被指向文件的内容变化是另一个文件的改动,和链接无关。

core.symlinks=false

实测 · 本机执行输出
  $ git -c core.symlinks=false clone -q . ../l10nosym && cd ../l10nosym && ls -l rel | cut -c1-10; cat rel; echo "; git status:"; git status --short; git ls-files -s rel
  -rw-r--r--
  target.txt; git status:
   T abs
   T dangling
   T dir/up
   T dirlink
   T rel
  120000 4cbb553f3f4ac2ee7b01ff6c951d6bf583c39c15 0	rel
  在 core.symlinks=false 的仓库里修改这个“文件”再提交:
  $ cd ../l10nosym && printf "dir" > rel && git add rel && git commit -q -m 改了链接目标 && git ls-files -s rel
  100644 87245193225f8ff56488ceab0dcd11467fe098d0 0	rel

在不能创建符号链接的环境里,git 把链接检出成含路径字符串的普通文件,暂存区仍是 120000,所以 status 显示类型改变。在那里提交修改过的文件,会把链接永久变成普通文件,需要小心。

分步演示

符号链接
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。符号链接在 macOS 文件系统上创建;core.symlinks=false 是用命令行 -c 强制指定的,模拟不支持链接的环境,并不是真的在 Windows 上
    • 没有验证:Windows 上 core.symlinks 的自动探测与权限要求;git 对链接目标逃出仓库的安全检查(检出时的 path traversal 防护);链接指向 .git 目录等特殊路径时的限制;子模块路径上的链接
    京ICP备2026067502号-1京公网安备11010502063487号