Git如何处理符号链接?

2054

如果我有一个符号链接的文件或目录,并将其提交到Git仓库中,会发生什么?

我认为它会保留符号链接状态,直到该文件被删除,如果您从旧版本中拉取该文件,则会创建一个普通文件。

当我删除它所引用的文件时,会发生什么?它会提交悬空的链接吗?


30
.gitignore 把符号链接视为文件而不是文件夹。 - 0xcaff
12
显然,这个问题的答案比暗示的更加复杂。举个例子,我在我的代码库中创建一个符号链接指向该库中的某个大文件,推送更改,然后将这些更改拉到另一台机器上,会发生什么?大文件会在两个位置都以大文件的形式存储,还是符号链接会被保留,以便在新机器上,链接文件指向原始的大文件? - jvriesem
10
这是一个旧的线程,但这条评论可能仍然有用。回应jviesem所说,软链接基本上是另一个文件名的文件。因此,一旦你将它拉到另一台机器上,链接将被下载并且它将有原始文件系统上大文件的名称。如果在新机器上名称无效,那么链接将具有无效名称,大文件不会下载到新机器上。 - lasaro
11
在Git存储库中避免断链的方法是始终在创建符号链接时使用相对路径,根据需要使用 "../.."。 - Wildcard
15
注意,在大多数Windows版本中,您需要提升权限才能创建符号链接。如果您正在Windows上,而git pull创建文件而不是符号链接,请尝试以管理员身份运行Git客户端。 - axmrnv
显示剩余3条评论
4个回答

1639

来自Linux符号链接手册(假设您在Linux中):

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

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

当您检出包含链接的树时,它会将该对象作为符号链接还原,而不管目标文件系统对象是否存在。

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


369
顺便说一下,如果你使用的是不支持符号链接的文件系统(比如 FAT),而你的代码库中有使用到符号链接的话,你可以将 core.symlinks 配置选项设为 false,这样在检出时就会将符号链接作为包含链接文本的小普通文本文件处理。 - Jakub Narębski
24
@JakubNarębski 我之前见过这个。我们仓库里有一个文本文件,只有一行内容,是我们使用的库的路径。当时我无法弄清它的目的。现在我知道发生了什么。 - Matt K
49
我不敢轻易评论得到很多赞的回答,但我认为“就像对待普通文件一样”这个措辞可能会误导新手。它只是在内容上类似于普通文件,关键的区别在于对于普通文件,blob 是文件的内容,而对于符号链接,则 blob 保存了它所链接的文件的路径名。 - Matthew Hannigan
15
@JakubNarębski 关于“小型纯文本文件”...您希望它们是小的和纯文本的,但当然一个blob是一个blob,潜在地可能是巨大和二进制的。当一个文件因为被误认为是符号链接时,可以参见https://dev59.com/MGMl5IYBdhLWcg3wbGh1。 - Matthew Hannigan
8
请确保检查全局设置和本地设置中的符号链接设置。如果从TortoiseGit或Windows复制设置,则可能会导致symlinks = false干扰它们。请注意,不要改变原文的意思。 - phyatt
显示剩余3条评论

408

将符号链接添加到索引中,您可以看到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 ./symlink
120000 1596f9db1b9610f238b78dd168ae33faa2dec15c 0       symlink

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

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

(注意:120000 是在 ls-files 输出中列出的模式。 对于普通文件,它会是类似于 100644 的东西。)
Git 在从存储库中检出对象并复制到您的文件系统时会对此对象执行什么操作呢?这取决于 core.symlinks 配置。来自man git-config

core.symlinks

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

因此,在存储库中有一个符号链接时,当您检出时,根据 core.symlinks 配置的值,您可能会得到一个带有完整文件系统路径引用的文本文件或一个适当的符号链接。 无论哪种方式,符号链接引用的路径内容都不会存储在存储库中(当然,除非引用的路径也在存储库中)。

1
如果链接指向存储库内的路径,那么存储在远程的链接路径是否保证是相对路径?那么指向存储库外部的路径呢?它是绝对路径还是相对于项目根目录的路径?这取决于链接的方式吗? - geekley
是的,我认为这取决于你如何创建符号链接。如果你创建它并且目标以“/”为前缀,则它将是绝对路径,并且可能指向你的存储库之外。否则,它将是相对路径。如果它是相对路径并且指向你的存储库内部,那么它应该是可移植的,至少在类Unix系统之间是这样的。 - Dmitry Minkovsky
1
@geekley 不确定在编辑过程中如何意外删除了此内容,但是您可以使用 git cat-file -p 查看 git 存储对象的内容,因此如果您的符号链接哈希值为 c6e5580589892eb40407c74a825afaa6c9315787,则可以执行 git cat-file -p c6e5580589892eb40407c74a825afaa6c9315787 并查看该打包文件的内容,这只是一个符号链接。 - Dmitry Minkovsky
4
我喜欢这个答案比被采纳的更好。 - Nike

154
“编辑”注:此帖可能包含过时信息。请参见评论和this question,了解自Git 1.6.1以来的更改。
符号链接目录:
重要的是要注意当存在一个软链接目录时会发生什么。任何具有更新的Git pull都会删除该链接并使其成为普通目录。这是我通过艰难的方式学到的。一些见解在这里这里
例子 之前
 ls -l
 lrwxrwxrwx 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

6
值得注意的是,关于符号链接目录的警告不适用于版本化的符号链接。问题的主要情况是将工作树中的某些或全部内容链接到不同的路径(例如链接到具有更多磁盘空间的不同分区),并期望git通过现有的符号链接来检出代码。也就是说,如果您的项目包含对文件或目录进行版本化的符号链接,则正常的符号链接作为Blob的行为将保留符号链接,正确地版本化更改以及按预期运行。 - John Whitley
22
所有版本的 Git 都存在这种行为,还是已经修复了? - jbotnik
24
看起来这种行为现在已经被固定了,参见:http://stackoverflow.com/a/1943656/1334781。 - Ron Wertlen
2
Shekar:你能否修改你的答案以反映近年来Git的变化? - einpoklum
3
@RonWertlen 这是指向同一个问题的链接。 - alx - recommends codidact
显示剩余2条评论

5
特殊情况:当 "git checkout"(man) 移除一个在其检出的提交中不存在的路径时,它没有足够小心地避免跟随符号链接,这已在 Git 2.32(2021 年第二季度)中得到了纠正。

请查看由Matheus Tavares (matheustavares)于2021年3月18日提交的提交fab78a0提交462b4e8
(在2021年3月30日由Junio C Hamano -- gitster --合并到提交9210c68)

结帐:在删除条目时不要跟随符号链接

签名作者:Matheus Tavares

1d718a5(“不要覆盖未跟踪的符号链接”,2011年2月20日,Git v1.7.5-rc0 -- 合并)中,symlink.c:check_leading_path()开始返回FL_ENOENTFL_SYMLINK的不同代码。
但是它的一个调用者unlink_entry()没有调整这个变化,所以它开始遵循即将被删除的条目的前导路径上的符号链接。
修复这个问题并添加回归测试。

因为我们不再尝试取消链接这些路径,所以我们也不会收到remove_or_warn()的警告。

对于常规文件和符号链接情况,值得怀疑的是,首先是否有用的警告:unlink_entry()删除了应该不存在于我们正在检出的状态中的已跟踪路径。
如果该路径的前导目录被另一个文件替换,这意味着基本名称已经不存在,因此不需要警告。
当然,我们在路径的dirname处留下了一个常规文件或符号链接,但是这个文件现在要么未跟踪(因此,无需警告),要么在此检出的下一阶段将被跟踪文件替换。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接