How does Git handle symbolic links?

Viewed 546893

If I have a file or directory that is a symbolic link and I commit it to a Git repository, what happens to it?

I would assume that it leaves it as a symbolic link until the file is deleted and then if you pull the file back from an old version it just creates a normal file.

What does it do when I delete the file it references? Does it just commit the dangling link?

4 Answers

From linux symlink manual (assuming you are in Linux):

A symbolic link is a special type of file whose contents are a string that is the pathname of another file, the file to which the link refers. (The contents of a symbolic link can be read using readlink(2).)

So a symbolic link is one more file, just as a README.md or a Makefile. Git just stores the contents of the link (i.e. the aforementioned path of the file system object that it links to) in a 'blob' just like it would for any other file. It then stores the name, mode and type (including the fact that it is a symlink) in the tree object that represents its containing directory.

When you checkout a tree containing the link, it restores the object as a symlink regardless of whether the target file system object exists or not.

If you delete the file that the symlink references it doesn't affect the Git-controlled symlink in any way. You will have a dangling reference. It is up to the user to either remove or change the link to point to something valid if needed.

Special case: When "git checkout"(man) removes a path that does not exist in the commit it is checking out, it wasn't careful enough not to follow symbolic links, which has been corrected with Git 2.32 (Q2 2021).

See commit fab78a0, commit 462b4e8 (18 Mar 2021) by Matheus Tavares (matheustavares).
(Merged by Junio C Hamano -- gitster -- in commit 9210c68, 30 Mar 2021)

checkout: don't follow symlinks when removing entries

Signed-off-by: Matheus Tavares

At 1d718a5 ("do not overwrite untracked symlinks", 2011-02-20, Git v1.7.5-rc0 -- merge), symlink.c:check_leading_path() started returning different codes for FL_ENOENT and FL_SYMLINK.
But one of its callers, unlink_entry(), was not adjusted for this change, so it started to follow symlinks on the leading path of to-be-removed entries.
Fix that and add a regression test.

And because we no longer try to unlink such paths, we also don't get the warning from remove_or_warn().

For the regular file and symlink cases, it's questionable whether the warning was useful in the first place: unlink_entry() removes tracked paths that should no longer be present in the state we are checking out to.
If the path had its leading dir replaced by another file, it means that the basename already doesn't exist, so there is no need for a warning.
Sure, we are leaving a regular file or symlink behind at the path's dirname, but this file is either untracked now (so again, no need to warn), or it will be replaced by a tracked file during the next phase of this checkout

Related