#!/bin/sh test_description=check-ignore TEST_PASSES_SANITIZE_LEAK=true TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh init_vars () { global_excludes="global-excludes" } enable_global_excludes () { init_vars && git config core.excludesfile "$global_excludes" } expect_in () { dest="$HOME/expected-$1" text="$2" if test -z "$text" then >"$dest" # avoid newline else echo "$text" >"$dest" fi } expect () { expect_in stdout "$1" } expect_from_stdin () { cat >"$HOME/expected-stdout" } test_stderr () { expected="$1" expect_in stderr "$1" && test_cmp "$HOME/expected-stderr" "$HOME/stderr" } broken_c_unquote () { "$PERL_PATH" -pe 's/^"//; s/\\//; s/"$//; tr/\n/\0/' "$@" } broken_c_unquote_verbose () { "$PERL_PATH" -pe 's/ "/ /; s/\\//; s/"$//; tr/:\t\n/\0/' "$@" } stderr_contains () { regexp="$1" if test_grep "$regexp" "$HOME/stderr" then return 0 else echo "didn't find /$regexp/ in $HOME/stderr" cat "$HOME/stderr" return 1 fi } stderr_empty_on_success () { expect_code="$1" if test $expect_code = 0 then test_stderr "" else # If we expect failure then stderr might or might not be empty # due to --quiet - the caller can check its contents return 0 fi } test_check_ignore () { args="$1" expect_code="${2:-0}" global_args="$3" init_vars && rm -f "$HOME/stdout" "$HOME/stderr" "$HOME/cmd" && echo git $global_args check-ignore $quiet_opt $verbose_opt $non_matching_opt $no_index_opt $args \ >"$HOME/cmd" && echo "$expect_code" >"$HOME/expected-exit-code" && test_expect_code "$expect_code" \ git $global_args check-ignore $quiet_opt $verbose_opt $non_matching_opt $no_index_opt $args \ >"$HOME/stdout" 2>"$HOME/stderr" && test_cmp "$HOME/expected-stdout" "$HOME/stdout" && stderr_empty_on_success "$expect_code" } # Runs the same code with 4 different levels of output verbosity: # # 1. with -q / --quiet # 2. with default verbosity # 3. with -v / --verbose # 4. with -v / --verbose, *and* -n / --non-matching # # expecting success each time. Takes advantage of the fact that # check-ignore --verbose output is the same as normal output except # for the extra first column. # # A parameter is used to determine if the tests are run with the # normal case (using the index), or with the --no-index option. # # Arguments: # - (optional) prereqs for this test, e.g. 'SYMLINKS' # - test name # - output to expect from the fourth verbosity mode (the output # from the other verbosity modes is automatically inferred # from this value) # - code to run (should invoke test_check_ignore) # - index option: --index or --no-index test_expect_success_multiple () { prereq= if test $# -eq 5 then prereq=$1 shift fi if test "$4" = "--index" then no_index_opt= else no_index_opt=$4 fi testname="$1" expect_all="$2" code="$3" expect_verbose=$( echo "$expect_all" | grep -v '^:: ' ) expect=$( echo "$expect_verbose" | sed -e 's/.* //' ) test_expect_success $prereq "$testname${no_index_opt:+ with $no_index_opt}" ' expect "$expect" && eval "$code" ' # --quiet is only valid when a single pattern is passed if test $( echo "$expect_all" | wc -l ) = 1 then for quiet_opt in '-q' '--quiet' do opts="${no_index_opt:+$no_index_opt }$quiet_opt" test_expect_success $prereq "$testname${opts:+ with $opts}" " expect '' && $code " done quiet_opt= fi for verbose_opt in '-v' '--verbose' do for non_matching_opt in '' '-n' '--non-matching' do if test -n "$non_matching_opt" then my_expect="$expect_all" else my_expect="$expect_verbose" fi test_code=" expect '$my_expect' && $code " opts="${no_index_opt:+$no_index_opt }$verbose_opt${non_matching_opt:+ $non_matching_opt}" test_expect_success $prereq "$testname${opts:+ with $opts}" "$test_code" done done verbose_opt= non_matching_opt= no_index_opt= } test_expect_success_multi () { test_expect_success_multiple "$@" "--index" } test_expect_success_no_index_multi () { test_expect_success_multiple "$@" "--no-index" } test_expect_success 'setup' ' init_vars && mkdir -p a/b/ignored-dir a/submodule b && if test_have_prereq SYMLINKS then ln -s b a/symlink fi && ( cd a/submodule && git init && echo a >a && git add a && git commit -m"commit in submodule" ) && git add a/submodule && cat <<-\EOF >.gitignore && one ignored-* top-level-dir/ EOF for dir in . a do : >$dir/not-ignored && : >$dir/ignored-and-untracked && : >$dir/ignored-but-in-index || return 1 done && git add -f ignored-but-in-index a/ignored-but-in-index && cat <<-\EOF >a/.gitignore && two* *three EOF cat <<-\EOF >a/b/.gitignore && four five # this comment should affect the line numbers six ignored-dir/ # and so should this blank line: !on* !two EOF echo "seven" >a/b/ignored-dir/.gitignore && test -n "$HOME" && cat <<-\EOF >"$global_excludes" && globalone !globaltwo globalthree EOF mkdir .git/info && cat <<-\EOF >.git/info/exclude per-repo EOF ' ############################################################################ # # test invalid inputs test_expect_success_multi '. corner-case' ':: .' ' test_check_ignore . 1 ' test_expect_success_multi 'empty command line' '' ' test_check_ignore "" 128 && stderr_contains "fatal: no path specified" ' test_expect_success_multi '--stdin with empty STDIN' '' ' test_check_ignore "--stdin" 1 stdin one not-ignored a/one a/not-ignored a/b/on a/b/one a/b/one one "a/b/one two" "a/b/one\"three" a/b/not-ignored a/b/two a/b/twooo globaltwo a/globaltwo a/b/globaltwo b/globaltwo EOF cat <<-\EOF >expected-default one a/one a/b/twooo EOF cat <<-EOF >expected-verbose .gitignore:1:one one .gitignore:1:one a/one a/b/.gitignore:8:!on* a/b/on a/b/.gitignore:8:!on* a/b/one a/b/.gitignore:8:!on* a/b/one one a/b/.gitignore:8:!on* a/b/one two a/b/.gitignore:8:!on* "a/b/one\\"three" a/b/.gitignore:9:!two a/b/two a/.gitignore:1:two* a/b/twooo $global_excludes:2:!globaltwo globaltwo $global_excludes:2:!globaltwo a/globaltwo $global_excludes:2:!globaltwo a/b/globaltwo $global_excludes:2:!globaltwo b/globaltwo EOF broken_c_unquote stdin >stdin0 broken_c_unquote expected-default >expected-default0 broken_c_unquote_verbose expected-verbose >expected-verbose0 test_expect_success '--stdin' ' expect_from_stdin stdin ../one ../not-ignored one not-ignored b/on b/one b/one one "b/one two" "b/one\"three" b/two b/not-ignored b/twooo ../globaltwo globaltwo b/globaltwo ../b/globaltwo c/not-ignored EOF # N.B. we deliberately end STDIN with a non-matching pattern in order # to test that the exit code indicates that one or more of the # provided paths is ignored - in other words, that it represents an # aggregation of all the results, not just the final result. cat <<-EOF >expected-all .gitignore:1:one ../one :: ../not-ignored .gitignore:1:one one :: not-ignored a/b/.gitignore:8:!on* b/on a/b/.gitignore:8:!on* b/one a/b/.gitignore:8:!on* b/one one a/b/.gitignore:8:!on* b/one two a/b/.gitignore:8:!on* "b/one\\"three" a/b/.gitignore:9:!two b/two :: b/not-ignored a/.gitignore:1:two* b/twooo $global_excludes:2:!globaltwo ../globaltwo $global_excludes:2:!globaltwo globaltwo $global_excludes:2:!globaltwo b/globaltwo $global_excludes:2:!globaltwo ../b/globaltwo :: c/not-ignored EOF cat <<-EOF >expected-default ../one one b/twooo EOF grep -v '^:: ' expected-all >expected-verbose broken_c_unquote stdin >stdin0 broken_c_unquote expected-default >expected-default0 broken_c_unquote_verbose expected-verbose >expected-verbose0 test_expect_success '--stdin from subdirectory' ' expect_from_stdin out &) && # We cannot just "echo >in" because check-ignore would get EOF # after echo exited; instead we open the descriptor in our # shell, and then echo to the fd. We make sure to close it at # the end, so that the subprocess does get EOF and dies # properly. # # Similarly, we must keep "out" open so that check-ignore does # not ever get SIGPIPE trying to write to us. Not only would that # produce incorrect results, but then there would be no writer on the # other end of the pipe, and we would potentially block forever trying # to open it. exec 9>in && exec 8&-" && test_when_finished "exec 8<&-" && echo >&9 one && read response <&8 && echo "$response" | grep "^\.gitignore:1:one one" && echo >&9 two && read response <&8 && echo "$response" | grep "^:: two" ' test_expect_success 'existing file and directory' ' test_when_finished "rm one" && test_when_finished "rmdir top-level-dir" && >one && mkdir top-level-dir && git check-ignore one top-level-dir >actual && grep one actual && grep top-level-dir actual ' test_expect_success 'existing directory and file' ' test_when_finished "rm one" && test_when_finished "rmdir top-level-dir" && >one && mkdir top-level-dir && git check-ignore top-level-dir one >actual && grep one actual && grep top-level-dir actual ' test_expect_success 'exact prefix matching (with root)' ' test_when_finished rm -r a && mkdir -p a/git a/git-foo && touch a/git/foo a/git-foo/bar && echo /git/ >a/.gitignore && git check-ignore a/git a/git/foo a/git-foo a/git-foo/bar >actual && cat >expect <<-\EOF && a/git a/git/foo EOF test_cmp expect actual ' test_expect_success 'exact prefix matching (without root)' ' test_when_finished rm -r a && mkdir -p a/git a/git-foo && touch a/git/foo a/git-foo/bar && echo git/ >a/.gitignore && git check-ignore a/git a/git/foo a/git-foo a/git-foo/bar >actual && cat >expect <<-\EOF && a/git a/git/foo EOF test_cmp expect actual ' test_expect_success 'directories and ** matches' ' cat >.gitignore <<-\EOF && data/** !data/**/ !data/**/*.txt EOF git check-ignore file \ data/file data/data1/file1 data/data1/file1.txt \ data/data2/file2 data/data2/file2.txt >actual && cat >expect <<-\EOF && data/file data/data1/file1 data/data2/file2 EOF test_cmp expect actual ' ############################################################################ # # test whitespace handling test_expect_success 'trailing whitespace is ignored' ' mkdir whitespace && >whitespace/trailing && >whitespace/untracked && echo "whitespace/trailing " >ignore && cat >expect <actual 2>err && test_cmp expect actual && test_must_be_empty err ' test_expect_success !MINGW 'quoting allows trailing whitespace' ' rm -rf whitespace && mkdir whitespace && >"whitespace/trailing " && >whitespace/untracked && echo "whitespace/trailing\\ \\ " >ignore && echo whitespace/untracked >expect && git ls-files -o -X ignore whitespace >actual 2>err && test_cmp expect actual && test_must_be_empty err ' test_expect_success !MINGW,!CYGWIN 'correct handling of backslashes' ' rm -rf whitespace && mkdir whitespace && >"whitespace/trailing 1 " && >"whitespace/trailing 2 \\\\" && >"whitespace/trailing 3 \\\\" && >"whitespace/trailing 4 \\ " && >"whitespace/trailing 5 \\ \\ " && >"whitespace/trailing 6 \\a\\" && >whitespace/untracked && sed -e "s/Z$//" >ignore <<-\EOF && whitespace/trailing 1 \ Z whitespace/trailing 2 \\\\Z whitespace/trailing 3 \\\\ Z whitespace/trailing 4 \\\ Z whitespace/trailing 5 \\ \\\ Z whitespace/trailing 6 \\a\\Z EOF echo whitespace/untracked >expect && git ls-files -o -X ignore whitespace >actual 2>err && test_cmp expect actual && test_must_be_empty err ' test_expect_success 'info/exclude trumps core.excludesfile' ' echo >>global-excludes usually-ignored && echo >>.git/info/exclude "!usually-ignored" && >usually-ignored && echo "?? usually-ignored" >expect && git status --porcelain usually-ignored >actual && test_cmp expect actual ' test_expect_success SYMLINKS 'set up ignore file for symlink tests' ' echo "*" >ignore && rm -f .gitignore .git/info/exclude ' test_expect_success SYMLINKS 'symlinks respected in core.excludesFile' ' test_when_finished "rm symlink" && ln -s ignore symlink && test_config core.excludesFile "$(pwd)/symlink" && echo file >expect && git check-ignore file >actual 2>err && test_cmp expect actual && test_must_be_empty err ' test_expect_success SYMLINKS 'symlinks respected in info/exclude' ' test_when_finished "rm .git/info/exclude" && ln -s ../../ignore .git/info/exclude && echo file >expect && git check-ignore file >actual 2>err && test_cmp expect actual && test_must_be_empty err ' test_expect_success SYMLINKS 'symlinks not respected in-tree' ' test_when_finished "rm .gitignore" && ln -s ignore .gitignore && mkdir subdir && ln -s ignore subdir/.gitignore && test_must_fail git check-ignore subdir/file >actual 2>err && test_must_be_empty actual && test_grep "unable to access.*gitignore" err ' test_expect_success EXPENSIVE 'large exclude file ignored in tree' ' test_when_finished "rm .gitignore" && dd if=/dev/zero of=.gitignore bs=101M count=1 && git ls-files -o --exclude-standard 2>err && echo "warning: ignoring excessively large pattern file: .gitignore" >expect && test_cmp expect err ' test_done