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/ |
|
|
|
Real File Test Script
Symbolic File
|
file |
filesuffix |
prefixfile |
./file |
|
|
|
/file |
Ignored. |
|
|
file |
Ignored. |
|
|
./file/ |
|
|
|
/file/ |
|
|
|
file/ |
|
|
|
Symbolic File Test Script
Real Directory
|
directory/file |
directorysuffix/file |
prefixdirectory/file |
./directory |
|
|
|
/directory |
Ignored. |
|
|
directory |
Ignored. |
|
|
./directory/ |
|
|
|
/directory/ |
Ignored. |
|
|
directory/ |
Ignored. |
|
|
Real Directory Test Script
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
- [1] https://git-scm.com/docs/gitignore
- [2] https://kb.iu.edu/d/abbe