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"