符号链接在 git 里到底存了什么?这篇创建相对、绝对、悬空、指向目录的四种链接,看暂存区和 blob 里的内容,clone 后哪些还有效,git add 带斜杠的行为,以及 core.symlinks=false 的环境里会发生什么。
$ 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);被指向文件的内容变化是另一个文件的改动,和链接无关。
$ 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 显示类型改变。在那里提交修改过的文件,会把链接永久变成普通文件,需要小心。