Symbolic files/directories and gitignore
I have seen numerous people, including myself use incorrect gitignore[1] patterns when referencing symbolic files and directories. So in this article I investigate how gitignore’s pattern matching is affected by symbolic links.
TLDR⌗
The quick synopsis for files is that the gitignore’s pattern matching is not affected by symbolic links. The summary for directories is that symbolic directories match like files, because a symbolic link is a ‘special kind of file that points to another file’[2]. Therefore a pattern ending with a ‘/’ would match a real directory but not a symbolic directory with the same name.
Real File⌗
file | filesuffix | prefixfile | |
---|---|---|---|
./file | |||
/file | Ignored. | ||
file | Ignored. | ||
./file/ | |||
/file/ | |||
file/ |
Symbolic File⌗
file | filesuffix | prefixfile | |
---|---|---|---|
./file | |||
/file | Ignored. | ||
file | Ignored. | ||
./file/ | |||
/file/ | |||
file/ |
Real Directory⌗
directory/file | directorysuffix/file | prefixdirectory/file | |
---|---|---|---|
./directory | |||
/directory | Ignored. | ||
directory | Ignored. | ||
./directory/ | |||
/directory/ | Ignored. | ||
directory/ | Ignored. |
Symbolic Directory⌗
directory | directorysuffix | prefixdirectory | |
---|---|---|---|
./directory | |||
/directory | Ignored. | ||
directory | Ignored. | ||
./directory/ | |||
/directory/ | |||
directory/ |
Symbolic Directory Test Script
Appendix⌗
Real File Test Script⌗
#!/usr/bin/env sh
set -o errexit
cwd=$(pwd)
# Variables.
file="file"
suffix_file="${file}suffix"
prefix_file="prefix${file}"
print_file_row() {
echo "|\`${gitignore_content}\`|$(git ls-files . --exclude-standard --others | grep -q "^${file}$" && echo "" || echo "Ignored.")|$(git ls-files . --exclude-standard --others | grep -q "^${suffix_file}$" && echo "" || echo "Ignored.")|$(git ls-files . --exclude-standard --others | grep -q "^${prefix_file}$" && echo "" || echo "Ignored.")|"
}
# Setup.
test_directory=$(mktemp -d)
cd "${test_directory}"
git init >/dev/null 2>&1
touch "${file}"
touch "${suffix_file}"
touch "${prefix_file}"
# Testing.
echo "||\`${file}\`|\`${suffix_file}\`|\`${prefix_file}\`|"
echo "|---|---|---|---|"
gitignore_content="./${file}" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="/${file}" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="${file}" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="./${file}/" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="/${file}/" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="${file}/" && echo "${gitignore_content}" >.gitignore && print_file_row
echo ""
# Cleaning up.
cd "${cwd}"
rm -rf "${test_directory}"
Symbolic File Test Script⌗
#!/usr/bin/env sh
set -o errexit
cwd=$(pwd)
# Variables.
file="file"
suffix_file="${file}suffix"
prefix_file="prefix${file}"
print_file_row() {
echo "|\`${gitignore_content}\`|$(git ls-files . --exclude-standard --others | grep -q "^${file}$" && echo "" || echo "Ignored.")|$(git ls-files . --exclude-standard --others | grep -q "^${suffix_file}$" && echo "" || echo "Ignored.")|$(git ls-files . --exclude-standard --others | grep -q "^${prefix_file}$" && echo "" || echo "Ignored.")|"
}
# Setup.
test_directory=$(mktemp -d)
cd "${test_directory}"
git init >/dev/null 2>&1
real_file=$(mktemp)
touch "${real_file}"
ln -s "${real_file}" "${file}"
ln -s "${real_file}" "${suffix_file}"
ln -s "${real_file}" "${prefix_file}"
# Testing.
echo "||\`${file}\`|\`${suffix_file}\`|\`${prefix_file}\`|"
echo "|---|---|---|---|"
gitignore_content="./${file}" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="/${file}" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="${file}" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="./${file}/" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="/${file}/" && echo "${gitignore_content}" >.gitignore && print_file_row
gitignore_content="${file}/" && echo "${gitignore_content}" >.gitignore && print_file_row
echo ""
# Cleaning up.
cd "${cwd}"
rm -rf "${test_directory}"
Real Directory Test Script⌗
#!/usr/bin/env sh
set -o errexit
cwd=$(pwd)
# Variables.
file="file"
directory="directory"
suffix_directory="${directory}suffix"
prefix_directory="prefix${directory}"
directory_file="${directory}/${file}"
suffix_directory_file="${suffix_directory}/${file}"
prefix_directory_file="${prefix_directory}/${file}"
is_ignored() {
is_ignored_result="$(git ls-files . --exclude-standard --others | grep -q -E "$1" && echo "" || echo "Ignored.")"
}
print_directory_row() {
# ($|/) as directories are ignored and symlinks are files.
directory_result=$(is_ignored "^${directory}($|/)" && echo "${is_ignored_result}")
suffix_directory_result=$(is_ignored "^${suffix_directory}($|/)" && echo "${is_ignored_result}")
prefix_directory_result=$(is_ignored "^${prefix_directory}($|/)" && echo "${is_ignored_result}")
echo "|\`${gitignore_content}\`|${directory_result}|${suffix_directory_result}|${prefix_directory_result}|"
}
# Setup.
test_directory=$(mktemp -d)
cd "${test_directory}"
git init >/dev/null 2>&1
mkdir "${directory}"
touch "${directory_file}"
mkdir "${suffix_directory}"
touch "${suffix_directory_file}"
mkdir "${prefix_directory}"
touch "${prefix_directory_file}"
# Testing.
echo "||\`${directory}\`|\`${suffix_directory}\`|\`${prefix_directory}\`|"
echo "|---|---|---|---|"
gitignore_content="./${directory}" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="/${directory}" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="${directory}" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="./${directory}/" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="/${directory}/" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="${directory}/" && echo "${gitignore_content}" >.gitignore && print_directory_row
echo ""
# Cleaning up.
cd "${cwd}"
rm -rf "${test_directory}"
Symbolic Directory Test Script⌗
#!/usr/bin/env sh
set -o errexit
cwd=$(pwd)
# Variables.
file="file"
directory="directory"
suffix_directory="${directory}suffix"
prefix_directory="prefix${directory}"
is_ignored() {
is_ignored_result="$(git ls-files . --exclude-standard --others | grep -q -E "$1" && echo "" || echo "Ignored.")"
}
print_directory_row() {
# ($|/) as directories are ignored and symlinks are files.
directory_result=$(is_ignored "^${directory}($|/)" && echo "${is_ignored_result}")
suffix_directory_result=$(is_ignored "^${suffix_directory}($|/)" && echo "${is_ignored_result}")
prefix_directory_result=$(is_ignored "^${prefix_directory}($|/)" && echo "${is_ignored_result}")
echo "|\`${gitignore_content}\`|${directory_result}|${suffix_directory_result}|${prefix_directory_result}|"
}
# Setup.
test_directory=$(mktemp -d)
cd "${test_directory}"
git init >/dev/null 2>&1
real_directory=$(mktemp -d)
real_directory_file="${real_directory}/${file}"
touch "${real_directory_file}"
ln -s "${real_directory}" "${directory}"
ln -s "${real_directory}" "${suffix_directory}"
ln -s "${real_directory}" "${prefix_directory}"
# Testing.
echo "||\`${directory}\`|\`${suffix_directory}\`|\`${prefix_directory}\`|"
echo "|---|---|---|---|"
gitignore_content="./${directory}" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="/${directory}" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="${directory}" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="./${directory}/" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="/${directory}/" && echo "${gitignore_content}" >.gitignore && print_directory_row
gitignore_content="${directory}/" && echo "${gitignore_content}" >.gitignore && print_directory_row
echo ""
# Cleaning up.
cd "${cwd}"
rm -rf "${test_directory}"
References⌗
Read other posts