grep: teach --untracked and --exclude-standard options
authorJunio C Hamano <gitster@pobox.com>
Tue, 27 Sep 2011 20:43:12 +0000 (13:43 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 5 Oct 2011 01:33:45 +0000 (18:33 -0700)
In a working tree of a git managed repository, "grep --untracked" would
find the specified patterns from files in untracked files in addition to
its usual behaviour of finding them in the tracked files.

By default, when working with "--no-index" option, "grep" does not pay
attention to .gitignore mechanism. "grep --no-index --exclude-standard"
can be used to tell the command to use .gitignore and stop reporting hits
from files that would be ignored. Also, when working without "--no-index",
"grep" honors .gitignore mechanism, and "grep --no-exclude-standard" can
be used to tell the command to include hits from files that are ignored.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-grep.txt
builtin-grep.c
t/t7002-grep.sh
index e019e760b4b4d58dfbe8819941947008319aedac..6269007b9449aea8a762849b28a80561174e4438 100644 (file)
@@ -9,7 +9,7 @@ git-grep - Print lines matching a pattern
 SYNOPSIS
 --------
 [verse]
 SYNOPSIS
 --------
 [verse]
-'git grep' [--cached]
+'git grep' [--cached] [--untracked] [--exclude-standard]
           [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
           [-v | --invert-match] [-h|-H] [--full-name]
           [-E | --extended-regexp] [-G | --basic-regexp]
           [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
           [-v | --invert-match] [-h|-H] [--full-name]
           [-E | --extended-regexp] [-G | --basic-regexp]
@@ -36,6 +36,19 @@ OPTIONS
        Instead of searching in the working tree files, check
        the blobs registered in the index file.
 
        Instead of searching in the working tree files, check
        the blobs registered in the index file.
 
+--untracked::
+       In addition to searching in the tracked files in the working
+       tree, search also in untracked files.
+
+--no-exclude-standard::
+       Also search in ignored files by not honoring the `.gitignore`
+       mechanism. Only useful with `--untracked`.
+
+--exclude-standard::
+       Do not pay attention to ignored files specified via the `.gitignore`
+       mechanism.  Only useful when searching files in the current directory
+       with `--no-index`.
+
 -a::
 --text::
        Process binary files as if they were text.
 -a::
 --text::
        Process binary files as if they were text.
index a10946db3c49797ef717734924416df0879b55a1..2da6bc61b66eea3594b3d0321de4a39cc3f49f8f 100644 (file)
@@ -646,12 +646,14 @@ static int grep_object(struct grep_opt *opt, const char **paths,
        die("unable to grep from object of type %s", typename(obj->type));
 }
 
        die("unable to grep from object of type %s", typename(obj->type));
 }
 
-static int grep_directory(struct grep_opt *opt, const char **paths)
+static int grep_directory(struct grep_opt *opt, const char **paths, int exc_std)
 {
        struct dir_struct dir;
        int i, hit = 0;
 
        memset(&dir, 0, sizeof(dir));
 {
        struct dir_struct dir;
        int i, hit = 0;
 
        memset(&dir, 0, sizeof(dir));
+       if (exc_std)
+               setup_standard_excludes(&dir);
 
        fill_directory(&dir, paths);
        for (i = 0; i < dir.nr; i++) {
 
        fill_directory(&dir, paths);
        for (i = 0; i < dir.nr; i++) {
@@ -749,7 +751,7 @@ static int help_callback(const struct option *opt, const char *arg, int unset)
 int cmd_grep(int argc, const char **argv, const char *prefix)
 {
        int hit = 0;
 int cmd_grep(int argc, const char **argv, const char *prefix)
 {
        int hit = 0;
-       int cached = 0;
+       int cached = 0, untracked = 0, opt_exclude = -1;
        int seen_dashdash = 0;
        int external_grep_allowed__ignored;
        struct grep_opt opt;
        int seen_dashdash = 0;
        int external_grep_allowed__ignored;
        struct grep_opt opt;
@@ -764,6 +766,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                { OPTION_BOOLEAN, 0, "index", &use_index, NULL,
                        "finds in contents not managed by git",
                        PARSE_OPT_NOARG | PARSE_OPT_NEGHELP },
                { OPTION_BOOLEAN, 0, "index", &use_index, NULL,
                        "finds in contents not managed by git",
                        PARSE_OPT_NOARG | PARSE_OPT_NEGHELP },
+               OPT_BOOLEAN(0, "untracked", &untracked,
+                       "search in both tracked and untracked files"),
+               OPT_SET_INT(0, "exclude-standard", &opt_exclude,
+                           "search also in ignored files", 1),
                OPT_GROUP(""),
                OPT_BOOLEAN('v', "invert-match", &opt.invert,
                        "show non-matching lines"),
                OPT_GROUP(""),
                OPT_BOOLEAN('v', "invert-match", &opt.invert,
                        "show non-matching lines"),
@@ -950,18 +956,23 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                paths[1] = NULL;
        }
 
                paths[1] = NULL;
        }
 
-       if (!use_index) {
+       if (!use_index && (untracked || cached))
+               die("--cached or --untracked cannot be used with --no-index.");
+
+       if (!use_index || untracked) {
                int hit;
                int hit;
-               if (cached)
-                       die("--cached cannot be used with --no-index.");
+               int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
                if (list.nr)
                if (list.nr)
-                       die("--no-index cannot be used with revs.");
-               hit = grep_directory(&opt, paths);
+                       die("--no-index or --untracked cannot be used with revs.");
+               hit = grep_directory(&opt, paths, use_exclude);
                if (use_threads)
                        hit |= wait_all();
                return !hit;
        }
 
                if (use_threads)
                        hit |= wait_all();
                return !hit;
        }
 
+       if (0 <= opt_exclude)
+               die("--exclude or --no-exclude cannot be used for tracked contents.");
+
        if (!list.nr) {
                int hit;
                if (!cached)
        if (!list.nr) {
                int hit;
                if (!cached)
index 918d33f7d1ab4d4d44dd6b13ca7e6029fe148ad5..bf7877d93051eddf96da3e4cb7ebc27a760ba910 100755 (executable)
@@ -455,6 +455,23 @@ test_expect_success 'outside of git repository' '
                test_must_fail git grep o &&
                git grep --no-index o >../../actual.sub &&
                test_cmp ../../expect.sub ../../actual.sub
                test_must_fail git grep o &&
                git grep --no-index o >../../actual.sub &&
                test_cmp ../../expect.sub ../../actual.sub
+       ) &&
+
+       echo ".*o*" >non/git/.gitignore &&
+       (
+               GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
+               export GIT_CEILING_DIRECTORIES &&
+               cd non/git &&
+               test_must_fail git grep o &&
+               git grep --no-index --exclude-standard o >../actual.full &&
+               test_cmp ../expect.full ../actual.full &&
+
+               {
+                       echo ".gitignore:.*o*"
+                       cat ../expect.full
+               } >../expect.with.ignored &&
+               git grep --no-index --no-exclude o >../actual.full &&
+               test_cmp ../expect.with.ignored ../actual.full
        )
 '
 
        )
 '
 
@@ -465,9 +482,12 @@ test_expect_success 'inside git repository but with --no-index' '
        echo world >is/git/sub/file2 &&
        echo ".*o*" >is/git/.gitignore &&
        {
        echo world >is/git/sub/file2 &&
        echo ".*o*" >is/git/.gitignore &&
        {
-               echo ".gitignore:.*o*" &&
                echo file1:hello &&
                echo sub/file2:world
                echo file1:hello &&
                echo sub/file2:world
+       } >is/expect.unignored &&
+       {
+               echo ".gitignore:.*o*" &&
+               cat is/expect.unignored
        } >is/expect.full &&
        : >is/expect.empty &&
        echo file2:world >is/expect.sub
        } >is/expect.full &&
        : >is/expect.empty &&
        echo file2:world >is/expect.sub
@@ -476,12 +496,24 @@ test_expect_success 'inside git repository but with --no-index' '
                git init &&
                test_must_fail git grep o >../actual.full &&
                test_cmp ../expect.empty ../actual.full &&
                git init &&
                test_must_fail git grep o >../actual.full &&
                test_cmp ../expect.empty ../actual.full &&
+
+               git grep --untracked o >../actual.unignored &&
+               test_cmp ../expect.unignored ../actual.unignored &&
+
                git grep --no-index o >../actual.full &&
                test_cmp ../expect.full ../actual.full &&
                git grep --no-index o >../actual.full &&
                test_cmp ../expect.full ../actual.full &&
+
+               git grep --no-index --exclude-standard o >../actual.unignored &&
+               test_cmp ../expect.unignored ../actual.unignored &&
+
                cd sub &&
                test_must_fail git grep o >../../actual.sub &&
                test_cmp ../../expect.empty ../../actual.sub &&
                cd sub &&
                test_must_fail git grep o >../../actual.sub &&
                test_cmp ../../expect.empty ../../actual.sub &&
+
                git grep --no-index o >../../actual.sub &&
                git grep --no-index o >../../actual.sub &&
+               test_cmp ../../expect.sub ../../actual.sub &&
+
+               git grep --untracked o >../../actual.sub &&
                test_cmp ../../expect.sub ../../actual.sub
        )
 '
                test_cmp ../../expect.sub ../../actual.sub
        )
 '