Git如何处理符号链接?

如果我有一个文件或目录是符号链接,并且我将其提交到Git存储库,它会发生什么?

我假设它会将其作为符号链接保留,直到文件被删除,然后如果您从旧版本中拉回文件,它只会创建一个正常文件。

当我删除它引用的文件时它会做什么?它只是提交悬空链接吗?

562007 次浏览

linux符号链接手册(假设你在Linux):

符号链接是一种特殊类型的文件,其内容是一个字符串,该字符串是另一个文件的路径名,即链接所引用的文件。(符号链接的内容可以使用readlink(2)读取。)

因此,符号链接是另一个文件,就像README.mdMakefile一样。Git只是将链接的内容(即它链接到的文件系统对象的上述路径)存储在“blob”中,就像它存储任何其他文件一样。然后,它将名称、模式和类型(包括它是符号链接的事实)存储在代表其包含目录的树对象中。

当您签出包含链接的树时,无论目标文件系统对象是否存在,它都会将对象恢复为符号链接。

如果您删除符号链接引用的文件,它不会以任何方式影响Git控制的符号链接。您将有一个悬空引用。如果需要,用户可以删除或更改链接以指向有效的内容。

编者注:这篇文章可能包含过时的信息。请参阅评论和这个问题关于自1.6.1以来Git的变化。

符号链接目录:

重要的是要注意当有一个目录是软链接时会发生什么。任何带有更新的Git拉取都会删除链接并使其成为正常目录。这是我很难学到的。一些见解这里这里。

示例

之前

 ls -llrwxrwxrwx 1 admin adm   29 Sep 30 15:28 src/somedir -> /mnt/somedir

git add/commit/push

It remains the same

之后git pull和一些更新发现

 drwxrwsr-x 2 admin adm 4096 Oct  2 05:54 src/somedir

您可以通过将符号链接添加到索引来查看Git对符号链接的作用。索引就像预提交。当索引提交时,您可以使用git checkout将索引中的所有内容带回工作目录。那么,当您将符号链接添加到索引时,Git会做什么?

首先,创建一个符号链接:

$ ln -s /path/referenced/by/symlink symlink

Git还不知道这个文件。git ls-files允许您检查您的索引(-s打印stat样的输出):

$ git ls-files -s ./symlink[nothing]

现在,将符号链接添加到索引。当您将文件添加到索引时,Git会将其内容复制到对象存储中。

$ git add ./symlink

那么,添加了什么?

$ git ls-files -s ./symlink120000 1596f9db1b9610f238b78dd168ae33faa2dec15c 0       symlink

哈希是对在对象存储中创建的打包对象的引用。如果您在存储库根目录中查找.git/objects/15/96f9db1b9610f238b78dd168ae33faa2dec15c,您可以检查此对象。这是Git存储在存储库中的文件,您可以稍后查看。如果您检查此文件,您会发现它非常小。它不存储链接文件的内容。要确认这一点,请使用git cat-file打印打包存储库对象的内容:

$ git cat-file -p 1596f9db1b9610f238b78dd168ae33faa2dec15c/path/referenced/by/symlink

(注意120000ls-files输出中列出的模式。对于常规文件,它类似于100644。)

但是,当您将此对象从存储库签出并进入您的文件系统时,Git对该对象有什么作用?它取决于core.symlinks配置。来自#1

core.symlinks

如果为false,符号链接将作为包含链接文本的小型普通文件签出。

因此,使用存储库中的符号链接,在签出时,您可以获得一个引用完整文件系统路径的文本文件,或者一个适当的符号链接,具体取决于core.symlinks配置的值。

无论哪种方式,符号链接引用的路径的内容都不会存储在存储库中(当然,除非引用的路径也在存储库中)。

特殊情况:当“#0man)删除了它正在签出的提交中不存在的路径时,它没有足够小心地不遵循符号链接,这已在Git 2.32(2021年第二季度)中得到纠正。

提交Fab78a0提交462b4e8(2021年3月18日)by马修斯·塔瓦雷斯(#0)
(2021年3月30日,被提交9210c68中的Junio C Hamano----#0----合并)

#0:删除条目时不要遵循符号链接

签名人:Matheus Tavares

1d718a5(“不要覆盖未跟踪的符号链接”,2011-02-20,Git v1.7.5-rc0--合并),symlink.c:check_leading_path()开始为FL_ENOENTFL_SYMLINK返回不同的代码。
但是它的一个调用者unlink_entry()没有针对这个变化进行调整,所以它开始遵循要删除条目的领先路径上的符号链接。
修复并添加回归测试。

因为我们不再尝试解除关联,我们也不会从remove_or_warn()得到警告。

对于常规文件和符号链接情况,警告是否有用是值得怀疑的:unlink_entry()删除了跟踪的路径,这些路径不应再出现在我们要签出的状态中。
如果路径的前导目录被另一个文件替换,则意味着基础名称已经不存在,因此不需要警告。
当然,我们会在路径的迪尔名处留下一个常规文件或符号链接,但是这个文件现在要么未被跟踪(所以同样,不需要警告),要么在这个结帐的下一阶段将被跟踪的文件替换