add git-check-ignore sub-command
authorAdam Spiers <git@adamspiers.org>
Sun, 6 Jan 2013 16:58:13 +0000 (16:58 +0000)
committerJunio C Hamano <gitster@pobox.com>
Sun, 6 Jan 2013 22:26:38 +0000 (14:26 -0800)
This works in a similar manner to git-check-attr.

Thanks to Jeff King and Junio C Hamano for the idea:
http://thread.gmane.org/gmane.comp.version-control.git/108671/focus=108815

Signed-off-by: Adam Spiers <git@adamspiers.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
.gitignore
Documentation/git-check-ignore.txt [new file with mode: 0644]
Documentation/gitignore.txt
Documentation/technical/api-directory-listing.txt
Makefile
builtin.h
builtin/check-ignore.c [new file with mode: 0644]
command-list.txt
contrib/completion/git-completion.bash
git.c
t/t0008-ignores.sh [new file with mode: 0755]

index f1acd3e777f5b8be846b3bdabf4743b84ad4fbca..20ef4e8d383eed0c87e5b26329a1d5947b66cd72 100644 (file)
@@ -19,6 +19,7 @@
 /git-bundle
 /git-cat-file
 /git-check-attr
+/git-check-ignore
 /git-check-ref-format
 /git-checkout
 /git-checkout-index
diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt
new file mode 100644 (file)
index 0000000..854e4d0
--- /dev/null
@@ -0,0 +1,89 @@
+git-check-ignore(1)
+===================
+
+NAME
+----
+git-check-ignore - Debug gitignore / exclude files
+
+
+SYNOPSIS
+--------
+[verse]
+'git check-ignore' [options] pathname...
+'git check-ignore' [options] --stdin < <list-of-paths>
+
+DESCRIPTION
+-----------
+
+For each pathname given via the command-line or from a file via
+`--stdin`, show the pattern from .gitignore (or other input files to
+the exclude mechanism) that decides if the pathname is excluded or
+included.  Later patterns within a file take precedence over earlier
+ones.
+
+OPTIONS
+-------
+-q, --quiet::
+       Don't output anything, just set exit status.  This is only
+       valid with a single pathname.
+
+-v, --verbose::
+       Also output details about the matching pattern (if any)
+       for each given pathname.
+
+--stdin::
+       Read file names from stdin instead of from the command-line.
+
+-z::
+       The output format is modified to be machine-parseable (see
+       below).  If `--stdin` is also given, input paths are separated
+       with a NUL character instead of a linefeed character.
+
+OUTPUT
+------
+
+By default, any of the given pathnames which match an ignore pattern
+will be output, one per line.  If no pattern matches a given path,
+nothing will be output for that path; this means that path will not be
+ignored.
+
+If `--verbose` is specified, the output is a series of lines of the form:
+
+<source> <COLON> <linenum> <COLON> <pattern> <HT> <pathname>
+
+<pathname> is the path of a file being queried, <pattern> is the
+matching pattern, <source> is the pattern's source file, and <linenum>
+is the line number of the pattern within that source.  If the pattern
+contained a `!` prefix or `/` suffix, it will be preserved in the
+output.  <source> will be an absolute path when referring to the file
+configured by `core.excludesfile`, or relative to the repository root
+when referring to `.git/info/exclude` or a per-directory exclude file.
+
+If `-z` is specified, the pathnames in the output are delimited by the
+null character; if `--verbose` is also specified then null characters
+are also used instead of colons and hard tabs:
+
+<source> <NULL> <linenum> <NULL> <pattern> <NULL> <pathname> <NULL>
+
+
+EXIT STATUS
+-----------
+
+0::
+       One or more of the provided paths is ignored.
+
+1::
+       None of the provided paths are ignored.
+
+128::
+       A fatal error was encountered.
+
+SEE ALSO
+--------
+linkgit:gitignore[5]
+linkgit:gitconfig[5]
+linkgit:git-ls-files[5]
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 2e7328b8306f4e949e22456b33d495226c1f014b..f401b8cc6a831e291d4e0026b27355bb12a74967 100644 (file)
@@ -153,8 +153,10 @@ The second .gitignore prevents git from ignoring
 
 SEE ALSO
 --------
-linkgit:git-rm[1], linkgit:git-update-index[1],
-linkgit:gitrepository-layout[5]
+linkgit:git-rm[1],
+linkgit:git-update-index[1],
+linkgit:gitrepository-layout[5],
+linkgit:git-check-ignore[1]
 
 GIT
 ---
index fbceb629ab3c81b9cc42c0aaee4b2c866466c07c..9d3e3527e4f2b149170352e35a2fae32670a3478 100644 (file)
@@ -81,6 +81,6 @@ marked. If you to exclude files, make sure you have loaded index first.
 
 * Use `dir.entries[]`.
 
-* Call `free_directory()` when none of the contained elements are no longer in use.
+* Call `clear_directory()` when none of the contained elements are no longer in use.
 
 (JC)
index 48facad01a3a2c74a403661d68d45e0aa22a5887..8476fc87ef23dff4cc4c0b79b5e573d006be63b7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -822,6 +822,7 @@ BUILTIN_OBJS += builtin/branch.o
 BUILTIN_OBJS += builtin/bundle.o
 BUILTIN_OBJS += builtin/cat-file.o
 BUILTIN_OBJS += builtin/check-attr.o
+BUILTIN_OBJS += builtin/check-ignore.o
 BUILTIN_OBJS += builtin/check-ref-format.o
 BUILTIN_OBJS += builtin/checkout-index.o
 BUILTIN_OBJS += builtin/checkout.o
index dffb34ef4e29799bcb9a8c78bdf439547845794f..d57faf49874364115041b8f08942542f3ceb0c2c 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -58,6 +58,7 @@ extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout(int argc, const char **argv, const char *prefix);
 extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
 extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
+extern int cmd_check_ignore(int argc, const char **argv, const char *prefix);
 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c
new file mode 100644 (file)
index 0000000..709535c
--- /dev/null
@@ -0,0 +1,173 @@
+#include "builtin.h"
+#include "cache.h"
+#include "dir.h"
+#include "quote.h"
+#include "pathspec.h"
+#include "parse-options.h"
+
+static int quiet, verbose, stdin_paths;
+static const char * const check_ignore_usage[] = {
+"git check-ignore [options] pathname...",
+"git check-ignore [options] --stdin < <list-of-paths>",
+NULL
+};
+
+static int null_term_line;
+
+static const struct option check_ignore_options[] = {
+       OPT__QUIET(&quiet, N_("suppress progress reporting")),
+       OPT__VERBOSE(&verbose, N_("be verbose")),
+       OPT_GROUP(""),
+       OPT_BOOLEAN(0, "stdin", &stdin_paths,
+                   N_("read file names from stdin")),
+       OPT_BOOLEAN('z', NULL, &null_term_line,
+                   N_("input paths are terminated by a null character")),
+       OPT_END()
+};
+
+static void output_exclude(const char *path, struct exclude *exclude)
+{
+       char *bang  = exclude->flags & EXC_FLAG_NEGATIVE  ? "!" : "";
+       char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : "";
+       if (!null_term_line) {
+               if (!verbose) {
+                       write_name_quoted(path, stdout, '\n');
+               } else {
+                       quote_c_style(exclude->el->src, NULL, stdout, 0);
+                       printf(":%d:%s%s%s\t",
+                              exclude->srcpos,
+                              bang, exclude->pattern, slash);
+                       quote_c_style(path, NULL, stdout, 0);
+                       fputc('\n', stdout);
+               }
+       } else {
+               if (!verbose) {
+                       printf("%s%c", path, '\0');
+               } else {
+                       printf("%s%c%d%c%s%s%s%c%s%c",
+                              exclude->el->src, '\0',
+                              exclude->srcpos, '\0',
+                              bang, exclude->pattern, slash, '\0',
+                              path, '\0');
+               }
+       }
+}
+
+static int check_ignore(const char *prefix, const char **pathspec)
+{
+       struct dir_struct dir;
+       const char *path, *full_path;
+       char *seen;
+       int num_ignored = 0, dtype = DT_UNKNOWN, i;
+       struct path_exclude_check check;
+       struct exclude *exclude;
+
+       /* read_cache() is only necessary so we can watch out for submodules. */
+       if (read_cache() < 0)
+               die(_("index file corrupt"));
+
+       memset(&dir, 0, sizeof(dir));
+       dir.flags |= DIR_COLLECT_IGNORED;
+       setup_standard_excludes(&dir);
+
+       if (!pathspec || !*pathspec) {
+               if (!quiet)
+                       fprintf(stderr, "no pathspec given.\n");
+               return 0;
+       }
+
+       path_exclude_check_init(&check, &dir);
+       /*
+        * look for pathspecs matching entries in the index, since these
+        * should not be ignored, in order to be consistent with
+        * 'git status', 'git add' etc.
+        */
+       seen = find_pathspecs_matching_against_index(pathspec);
+       for (i = 0; pathspec[i]; i++) {
+               path = pathspec[i];
+               full_path = prefix_path(prefix, prefix
+                                       ? strlen(prefix) : 0, path);
+               full_path = check_path_for_gitlink(full_path);
+               die_if_path_beyond_symlink(full_path, prefix);
+               if (!seen[i] && path[0]) {
+                       exclude = last_exclude_matching_path(&check, full_path,
+                                                            -1, &dtype);
+                       if (exclude) {
+                               if (!quiet)
+                                       output_exclude(path, exclude);
+                               num_ignored++;
+                       }
+               }
+       }
+       free(seen);
+       clear_directory(&dir);
+       path_exclude_check_clear(&check);
+
+       return num_ignored;
+}
+
+static int check_ignore_stdin_paths(const char *prefix)
+{
+       struct strbuf buf, nbuf;
+       char **pathspec = NULL;
+       size_t nr = 0, alloc = 0;
+       int line_termination = null_term_line ? 0 : '\n';
+       int num_ignored;
+
+       strbuf_init(&buf, 0);
+       strbuf_init(&nbuf, 0);
+       while (strbuf_getline(&buf, stdin, line_termination) != EOF) {
+               if (line_termination && buf.buf[0] == '"') {
+                       strbuf_reset(&nbuf);
+                       if (unquote_c_style(&nbuf, buf.buf, NULL))
+                               die("line is badly quoted");
+                       strbuf_swap(&buf, &nbuf);
+               }
+               ALLOC_GROW(pathspec, nr + 1, alloc);
+               pathspec[nr] = xcalloc(strlen(buf.buf) + 1, sizeof(*buf.buf));
+               strcpy(pathspec[nr++], buf.buf);
+       }
+       ALLOC_GROW(pathspec, nr + 1, alloc);
+       pathspec[nr] = NULL;
+       num_ignored = check_ignore(prefix, (const char **)pathspec);
+       maybe_flush_or_die(stdout, "attribute to stdout");
+       strbuf_release(&buf);
+       strbuf_release(&nbuf);
+       free(pathspec);
+       return num_ignored;
+}
+
+int cmd_check_ignore(int argc, const char **argv, const char *prefix)
+{
+       int num_ignored;
+
+       git_config(git_default_config, NULL);
+
+       argc = parse_options(argc, argv, prefix, check_ignore_options,
+                            check_ignore_usage, 0);
+
+       if (stdin_paths) {
+               if (argc > 0)
+                       die(_("cannot specify pathnames with --stdin"));
+       } else {
+               if (null_term_line)
+                       die(_("-z only makes sense with --stdin"));
+               if (argc == 0)
+                       die(_("no path specified"));
+       }
+       if (quiet) {
+               if (argc > 1)
+                       die(_("--quiet is only valid with a single pathname"));
+               if (verbose)
+                       die(_("cannot have both --quiet and --verbose"));
+       }
+
+       if (stdin_paths) {
+               num_ignored = check_ignore_stdin_paths(prefix);
+       } else {
+               num_ignored = check_ignore(prefix, argv);
+               maybe_flush_or_die(stdout, "ignore to stdout");
+       }
+
+       return !num_ignored;
+}
index 14ea67af038fbfce388dd20b8adcda503106911d..ef7f39c8b07bf6fa4057701d5bc7a46fec9fd40a 100644 (file)
@@ -12,6 +12,7 @@ git-branch                              mainporcelain common
 git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
 git-check-attr                          purehelpers
+git-check-ignore                        purehelpers
 git-checkout                            mainporcelain common
 git-checkout-index                      plumbingmanipulators
 git-check-ref-format                    purehelpers
index 2e1b5e14b92fe61e02df94ab165c8aab6c06c9cb..1fb896b8c53a30f42c14e6bd9c947b1ff07bed75 100755 (executable)
@@ -842,6 +842,7 @@ __git_list_porcelain_commands ()
                archimport)       : import;;
                cat-file)         : plumbing;;
                check-attr)       : plumbing;;
+               check-ignore)     : plumbing;;
                check-ref-format) : plumbing;;
                checkout-index)   : plumbing;;
                commit-tree)      : plumbing;;
diff --git a/git.c b/git.c
index d232de92e496bd5750015edce22d3b67f236b605..0b31e668533b7fdd9f0c2eb441a0d44b7fb6713a 100644 (file)
--- a/git.c
+++ b/git.c
@@ -340,6 +340,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "check-attr", cmd_check_attr, RUN_SETUP },
+               { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
                { "check-ref-format", cmd_check_ref_format },
                { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
                { "checkout-index", cmd_checkout_index,
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
new file mode 100755 (executable)
index 0000000..9b0fcd6
--- /dev/null
@@ -0,0 +1,632 @@
+#!/bin/sh
+
+test_description=check-ignore
+
+. ./test-lib.sh
+
+init_vars () {
+       global_excludes="$(pwd)/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"
+}
+
+stderr_contains () {
+       regexp="$1"
+       if 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 $args \
+               >"$HOME/cmd" &&
+       test_expect_code "$expect_code" \
+               git $global_args check-ignore $quiet_opt $verbose_opt $args \
+               >"$HOME/stdout" 2>"$HOME/stderr" &&
+       test_cmp "$HOME/expected-stdout" "$HOME/stdout" &&
+       stderr_empty_on_success "$expect_code"
+}
+
+test_expect_success_multi () {
+       prereq=
+       if test $# -eq 4
+       then
+               prereq=$1
+               shift
+       fi
+       testname="$1" expect_verbose="$2" code="$3"
+
+       expect=$( echo "$expect_verbose" | sed -e 's/.* //' )
+
+       test_expect_success $prereq "$testname" '
+               expect "$expect" &&
+               eval "$code"
+       '
+
+       for quiet_opt in '-q' '--quiet'
+       do
+               test_expect_success $prereq "$testname${quiet_opt:+ with $quiet_opt}" "
+                       expect '' &&
+                       $code
+               "
+       done
+       quiet_opt=
+
+       for verbose_opt in '-v' '--verbose'
+       do
+               test_expect_success $prereq "$testname${verbose_opt:+ with $verbose_opt}" "
+                       expect '$expect_verbose' &&
+                       $code
+               "
+       done
+       verbose_opt=
+}
+
+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-*
+       EOF
+       touch {,a/}{not-ignored,ignored-{and-untracked,but-in-index}} &&
+       git add -f {,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
+       cat <<-\EOF >>.git/info/exclude
+               per-repo
+       EOF
+'
+
+############################################################################
+#
+# test invalid inputs
+
+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 </dev/null &&
+       if test -n "$quiet_opt"; then
+               test_stderr ""
+       else
+               test_stderr "no pathspec given."
+       fi
+'
+
+test_expect_success '-q with multiple args' '
+       expect "" &&
+       test_check_ignore "-q one two" 128 &&
+       stderr_contains "fatal: --quiet is only valid with a single pathname"
+'
+
+test_expect_success '--quiet with multiple args' '
+       expect "" &&
+       test_check_ignore "--quiet one two" 128 &&
+       stderr_contains "fatal: --quiet is only valid with a single pathname"
+'
+
+for verbose_opt in '-v' '--verbose'
+do
+       for quiet_opt in '-q' '--quiet'
+       do
+               test_expect_success "$quiet_opt $verbose_opt" "
+                       expect '' &&
+                       test_check_ignore '$quiet_opt $verbose_opt foo' 128 &&
+                       stderr_contains 'fatal: cannot have both --quiet and --verbose'
+               "
+       done
+done
+
+test_expect_success '--quiet with multiple args' '
+       expect "" &&
+       test_check_ignore "--quiet one two" 128 &&
+       stderr_contains "fatal: --quiet is only valid with a single pathname"
+'
+
+test_expect_success_multi 'erroneous use of --' '' '
+       test_check_ignore "--" 128 &&
+       stderr_contains "fatal: no path specified"
+'
+
+test_expect_success_multi '--stdin with superfluous arg' '' '
+       test_check_ignore "--stdin foo" 128 &&
+       stderr_contains "fatal: cannot specify pathnames with --stdin"
+'
+
+test_expect_success_multi '--stdin -z with superfluous arg' '' '
+       test_check_ignore "--stdin -z foo" 128 &&
+       stderr_contains "fatal: cannot specify pathnames with --stdin"
+'
+
+test_expect_success_multi '-z without --stdin' '' '
+       test_check_ignore "-z" 128 &&
+       stderr_contains "fatal: -z only makes sense with --stdin"
+'
+
+test_expect_success_multi '-z without --stdin and superfluous arg' '' '
+       test_check_ignore "-z foo" 128 &&
+       stderr_contains "fatal: -z only makes sense with --stdin"
+'
+
+test_expect_success_multi 'needs work tree' '' '
+       (
+               cd .git &&
+               test_check_ignore "foo" 128
+       ) &&
+       stderr_contains "fatal: This operation must be run in a work tree"
+'
+
+############################################################################
+#
+# test standard ignores
+
+# First make sure that the presence of a file in the working tree
+# does not impact results, but that the presence of a file in the
+# index does.
+
+for subdir in '' 'a/'
+do
+       if test -z "$subdir"
+       then
+               where="at top-level"
+       else
+               where="in subdir $subdir"
+       fi
+
+       test_expect_success_multi "non-existent file $where not ignored" '' "
+               test_check_ignore '${subdir}non-existent' 1
+       "
+
+       test_expect_success_multi "non-existent file $where ignored" \
+               ".gitignore:1:one       ${subdir}one" "
+               test_check_ignore '${subdir}one'
+       "
+
+       test_expect_success_multi "existing untracked file $where not ignored" '' "
+               test_check_ignore '${subdir}not-ignored' 1
+       "
+
+       test_expect_success_multi "existing tracked file $where not ignored" '' "
+               test_check_ignore '${subdir}ignored-but-in-index' 1
+       "
+
+       test_expect_success_multi "existing untracked file $where ignored" \
+               ".gitignore:2:ignored-* ${subdir}ignored-and-untracked" "
+               test_check_ignore '${subdir}ignored-and-untracked'
+       "
+done
+
+# Having established the above, from now on we mostly test against
+# files which do not exist in the working tree or index.
+
+test_expect_success 'sub-directory local ignore' '
+       expect "a/3-three" &&
+       test_check_ignore "a/3-three a/three-not-this-one"
+'
+
+test_expect_success 'sub-directory local ignore with --verbose'  '
+       expect "a/.gitignore:2:*three   a/3-three" &&
+       test_check_ignore "--verbose a/3-three a/three-not-this-one"
+'
+
+test_expect_success 'local ignore inside a sub-directory' '
+       expect "3-three" &&
+       (
+               cd a &&
+               test_check_ignore "3-three three-not-this-one"
+       )
+'
+test_expect_success 'local ignore inside a sub-directory with --verbose' '
+       expect "a/.gitignore:2:*three   3-three" &&
+       (
+               cd a &&
+               test_check_ignore "--verbose 3-three three-not-this-one"
+       )
+'
+
+test_expect_success_multi 'nested include' \
+       'a/b/.gitignore:8:!on*  a/b/one' '
+       test_check_ignore "a/b/one"
+'
+
+############################################################################
+#
+# test ignored sub-directories
+
+test_expect_success_multi 'ignored sub-directory' \
+       'a/b/.gitignore:5:ignored-dir/  a/b/ignored-dir' '
+       test_check_ignore "a/b/ignored-dir"
+'
+
+test_expect_success 'multiple files inside ignored sub-directory' '
+       expect_from_stdin <<-\EOF &&
+               a/b/ignored-dir/foo
+               a/b/ignored-dir/twoooo
+               a/b/ignored-dir/seven
+       EOF
+       test_check_ignore "a/b/ignored-dir/foo a/b/ignored-dir/twoooo a/b/ignored-dir/seven"
+'
+
+test_expect_success 'multiple files inside ignored sub-directory with -v' '
+       expect_from_stdin <<-\EOF &&
+               a/b/.gitignore:5:ignored-dir/   a/b/ignored-dir/foo
+               a/b/.gitignore:5:ignored-dir/   a/b/ignored-dir/twoooo
+               a/b/.gitignore:5:ignored-dir/   a/b/ignored-dir/seven
+       EOF
+       test_check_ignore "-v a/b/ignored-dir/foo a/b/ignored-dir/twoooo a/b/ignored-dir/seven"
+'
+
+test_expect_success 'cd to ignored sub-directory' '
+       expect_from_stdin <<-\EOF &&
+               foo
+               twoooo
+               ../one
+               seven
+               ../../one
+       EOF
+       (
+               cd a/b/ignored-dir &&
+               test_check_ignore "foo twoooo ../one seven ../../one"
+       )
+'
+
+test_expect_success 'cd to ignored sub-directory with -v' '
+       expect_from_stdin <<-\EOF &&
+               a/b/.gitignore:5:ignored-dir/   foo
+               a/b/.gitignore:5:ignored-dir/   twoooo
+               a/b/.gitignore:8:!on*   ../one
+               a/b/.gitignore:5:ignored-dir/   seven
+               .gitignore:1:one        ../../one
+       EOF
+       (
+               cd a/b/ignored-dir &&
+               test_check_ignore "-v foo twoooo ../one seven ../../one"
+       )
+'
+
+############################################################################
+#
+# test handling of symlinks
+
+test_expect_success_multi SYMLINKS 'symlink' '' '
+       test_check_ignore "a/symlink" 1
+'
+
+test_expect_success_multi SYMLINKS 'beyond a symlink' '' '
+       test_check_ignore "a/symlink/foo" 128 &&
+       test_stderr "fatal: '\''a/symlink/foo'\'' is beyond a symbolic link"
+'
+
+test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' '
+       (
+               cd a &&
+               test_check_ignore "symlink/foo" 128
+       ) &&
+       test_stderr "fatal: '\''symlink/foo'\'' is beyond a symbolic link"
+'
+
+############################################################################
+#
+# test handling of submodules
+
+test_expect_success_multi 'submodule' '' '
+       test_check_ignore "a/submodule/one" 128 &&
+       test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+'
+
+test_expect_success_multi 'submodule from subdirectory' '' '
+       (
+               cd a &&
+               test_check_ignore "submodule/one" 128
+       ) &&
+       test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+'
+
+############################################################################
+#
+# test handling of global ignore files
+
+test_expect_success 'global ignore not yet enabled' '
+       expect_from_stdin <<-\EOF &&
+               .git/info/exclude:7:per-repo    per-repo
+               a/.gitignore:2:*three   a/globalthree
+               .git/info/exclude:7:per-repo    a/per-repo
+       EOF
+       test_check_ignore "-v globalone per-repo a/globalthree a/per-repo not-ignored a/globaltwo"
+'
+
+test_expect_success 'global ignore' '
+       enable_global_excludes &&
+       expect_from_stdin <<-\EOF &&
+               globalone
+               per-repo
+               globalthree
+               a/globalthree
+               a/per-repo
+               globaltwo
+       EOF
+       test_check_ignore "globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"
+'
+
+test_expect_success 'global ignore with -v' '
+       enable_global_excludes &&
+       expect_from_stdin <<-EOF &&
+               $global_excludes:1:globalone    globalone
+               .git/info/exclude:7:per-repo    per-repo
+               $global_excludes:3:globalthree  globalthree
+               a/.gitignore:2:*three   a/globalthree
+               .git/info/exclude:7:per-repo    a/per-repo
+               $global_excludes:2:!globaltwo   globaltwo
+       EOF
+       test_check_ignore "-v globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo"
+'
+
+############################################################################
+#
+# test --stdin
+
+cat <<-\EOF >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/on
+       a/b/one
+       a/b/one one
+       a/b/one two
+       "a/b/one\"three"
+       a/b/two
+       a/b/twooo
+       globaltwo
+       a/globaltwo
+       a/b/globaltwo
+       b/globaltwo
+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
+
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' stdin | \
+       tr "\n" "\0" >stdin0
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' expected-default | \
+       tr "\n" "\0" >expected-default0
+sed -e 's/     "/      /' -e 's/\\//' -e 's/"$//' expected-verbose | \
+       tr ":\t\n" "\0" >expected-verbose0
+
+test_expect_success '--stdin' '
+       expect_from_stdin <expected-default &&
+       test_check_ignore "--stdin" <stdin
+'
+
+test_expect_success '--stdin -q' '
+       expect "" &&
+       test_check_ignore "-q --stdin" <stdin
+'
+
+test_expect_success '--stdin -v' '
+       expect_from_stdin <expected-verbose &&
+       test_check_ignore "-v --stdin" <stdin
+'
+
+for opts in '--stdin -z' '-z --stdin'
+do
+       test_expect_success "$opts" "
+               expect_from_stdin <expected-default0 &&
+               test_check_ignore '$opts' <stdin0
+       "
+
+       test_expect_success "$opts -q" "
+               expect "" &&
+               test_check_ignore '-q $opts' <stdin0
+       "
+
+       test_expect_success "$opts -v" "
+               expect_from_stdin <expected-verbose0 &&
+               test_check_ignore '-v $opts' <stdin0
+       "
+done
+
+cat <<-\EOF >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
+EOF
+cat <<-\EOF >expected-default
+       ../one
+       one
+       b/on
+       b/one
+       b/one one
+       b/one two
+       "b/one\"three"
+       b/two
+       b/twooo
+       ../globaltwo
+       globaltwo
+       b/globaltwo
+       ../b/globaltwo
+EOF
+cat <<-EOF >expected-verbose
+       .gitignore:1:one        ../one
+       .gitignore:1:one        one
+       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
+       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
+EOF
+
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' stdin | \
+       tr "\n" "\0" >stdin0
+sed -e 's/^"//' -e 's/\\//' -e 's/"$//' expected-default | \
+       tr "\n" "\0" >expected-default0
+sed -e 's/     "/      /' -e 's/\\//' -e 's/"$//' expected-verbose | \
+       tr ":\t\n" "\0" >expected-verbose0
+
+test_expect_success '--stdin from subdirectory' '
+       expect_from_stdin <expected-default &&
+       (
+               cd a &&
+               test_check_ignore "--stdin" <../stdin
+       )
+'
+
+test_expect_success '--stdin from subdirectory with -v' '
+       expect_from_stdin <expected-verbose &&
+       (
+               cd a &&
+               test_check_ignore "--stdin -v" <../stdin
+       )
+'
+
+for opts in '--stdin -z' '-z --stdin'
+do
+       test_expect_success "$opts from subdirectory" '
+               expect_from_stdin <expected-default0 &&
+               (
+                       cd a &&
+                       test_check_ignore "'"$opts"'" <../stdin0
+               )
+       '
+
+       test_expect_success "$opts from subdirectory with -v" '
+               expect_from_stdin <expected-verbose0 &&
+               (
+                       cd a &&
+                       test_check_ignore "'"$opts"' -v" <../stdin0
+               )
+       '
+done
+
+
+test_done