The confusing way ln handles relative paths

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the unix category.

Last Updated: 2024-11-23

A repo contained the folder ./githooks. I wanted to symbolically link the pre-commit file here to .git/hooks in order to get my git hook to run (while also being in version control)

I ran the following from .

$ ln -s githooks/pre-commit .git/hooks/pre-commit

It ran without an error exit code, but then the hook did not work:

$ ./.git/hooks/pre-commit
zsh: no such file or directory: ./.git/hooks/pre-commit

Inspecting the symbolic link with ls -l showed:

$ ls -l .git/hooks/pre-commit
lrwxr-xr-x  1 jack  staff  19 Dec 30 13:07 .git/hooks/pre-commit -> githooks/pre-commit

This seems OK, but watch what happens if I cd out of the current codebase and into the parent folder before running ls -l again

cd ..
ls -l child/.git/hooks/pre-commit
lrwxr-xr-x  1 jack  staff  19 Dec 30 13:07 child/.git/hooks/pre-commit -> githooks/pre-commit

Notice how the symlink is pointing to the same directory as before - i.e. it was not updated to be aware of my working directory.

The key here is that the symlink needs to be given a relative path to where it's going relative to its position. Thus the command, back up in the root directory of my project, would have been

$ ln -s ../../githooks/pre-commit .git/hooks/pre-commit
# i.e. tell the OS how to get from .git/hooks to githooks

This is difficult to remember so a best practice is to use absolute paths instead

ln -s $(pwd)/githooks/pre-commit .git/hooks/pre-commit

Another easy way to prevent this confusion would to always cd into the directory where the symlink will live before running the linking command. Now autocomplete will help you figure out the correct relative path.

Lessons

Use absolute paths with ln -s