Symbolic links and gitignore
I've often seen developers, myself included, use .gitignore patterns[1] that do not work when used with symbolic links (symlinks)[2]. While not universal, it's common to leave a trailing / on the end of a directory name to indicate it is a directory.
However, if you use a trailing / in a gitignore pattern you will have issues with symbolic links. My gut feeling was that Git would treat a symbolic link like what it's linking to. But since symbolic links are a 'special kind of file that points to another file'[2], Git seems to treat them as files. As this has tripped me up a couple of times I want to confirm this and come up with some best practices for the future.
Symbolic links
| Type | target |
target/ |
|---|---|---|
| File | 🚫 | 📦 |
| Symbolic Link → File | 🚫 | 📦 |
| Directory | 🚫 | 🚫 |
| Symbolic Link → Directory | 🚫 | 📦 |
gitignore-symlink-test.sh
#!/usr/bin/env sh
set -o errexit
set -o nounset
# Emoji output: 🚫 = ignored, 📦 = tracked
check_ignored() {
git ls-files . --exclude-standard --others | grep -q -E "$1" && echo "📦" || echo "🚫"
}
run_test() {
test_dir=$(mktemp -d)
cd "${test_dir}"
git init >/dev/null 2>&1
case "$1" in
real_file)
touch "target"
;;
symlink_file)
touch "source_file"
ln -s "source_file" "target"
;;
real_dir)
mkdir "target"
touch "target/inner.txt"
;;
symlink_dir)
mkdir "source_dir"
touch "source_dir/inner.txt"
ln -s "source_dir" "target"
;;
esac
echo "target" > .gitignore
result_no_slash=$(check_ignored "^target($|/)")
echo "target/" > .gitignore
result_with_slash=$(check_ignored "^target($|/)")
echo "| $2 | ${result_no_slash} | ${result_with_slash} |"
}
echo "| Type | \`target\` | \`target/\` |"
echo "|---|---|---|"
run_test "real_file" "File"
run_test "symlink_file" "Symbolic Link → File"
run_test "real_dir" "Directory"
run_test "symlink_dir" "Symbolic Link → Directory"