Subject: Re: bug#21027: --exclude-dir matches search dir
From: Paul Eggert <[email-masked]>
To: Aron Griffis <[email-masked]>, [email-masked]
Date: Mon, 13 Jul 2015 16:35:19 -0700
List-Id: <debbugs-submit.debbugs.gnu.org>

Aron Griffis wrote:
> This generates output:
>
> $ grep -rl --exclude-dir=usr LINUX /usr/include
>
> and this doesn't:
>
> $ grep -rl --exclude-dir='usr*' LINUX /usr/include
>
> This feels like a bug.

Thanks for reporting that.  I installed the attached patch.

From 3b6896988c4e139115f24a2c6a804dbdb6bd744c Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Mon, 13 Jul 2015 16:31:26 -0700
Subject: [PATCH] grep: fix bug with --exclude-dir and command line

Reported by Aron Griffis in: http://bugs.gnu.org/21027
* NEWS: Document this.
* src/grep.c (grepdirent): Don't check whether the file is skipped
when on the command line, as that's the caller's responsibility.
(main): Anchor the exclude patterns.
* tests/include-exclude: Adjust test case to match fixed behavior.
Add some more test cases.
---
 NEWS                  |  4 ++++
 src/grep.c            | 14 ++++++++------
 tests/include-exclude | 12 +++++++++++-
 3 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/NEWS b/NEWS
index 35c4aad..47b86ae 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,10 @@ GNU grep NEWS                                    -*- outline -*-
   'grep -D skip PATTERN FILE' no longer hangs if FILE is a fifo.
   [bug introduced in grep-2.12]
 
+  --exclude and related options are now matched against entire
+  command-line arguments, not against command-line components.
+  [bug introduced in grep-2.6]
+
 
 * Noteworthy changes in release 2.21 (2014-11-23) [stable]
 
diff --git a/src/grep.c b/src/grep.c
index a735ea5..d8ea70f 100644
--- a/src/grep.c
+++ b/src/grep.c
@@ -1482,9 +1482,10 @@ grepdirent (FTS *fts, FTSENT *ent, bool command_line)
       return true;
     }
 
-  if (skipped_file (ent->fts_name, command_line,
-                    (ent->fts_info == FTS_D || ent->fts_info == FTS_DC
-                     || ent->fts_info == FTS_DNR)))
+  if (!command_line
+      && skipped_file (ent->fts_name, false,
+                       (ent->fts_info == FTS_D || ent->fts_info == FTS_DC
+                        || ent->fts_info == FTS_DNR)))
     {
       fts_set (fts, ent, FTS_SKIP);
       return true;
@@ -2448,14 +2449,14 @@ main (int argc, char **argv)
         if (!excluded_patterns)
           excluded_patterns = new_exclude ();
         add_exclude (excluded_patterns, optarg,
-                     (EXCLUDE_WILDCARDS
+                     (EXCLUDE_ANCHORED | EXCLUDE_WILDCARDS
                       | (opt == INCLUDE_OPTION ? EXCLUDE_INCLUDE : 0)));
         break;
       case EXCLUDE_FROM_OPTION:
         if (!excluded_patterns)
           excluded_patterns = new_exclude ();
         if (add_exclude_file (add_exclude, excluded_patterns, optarg,
-                              EXCLUDE_WILDCARDS, '\n') != 0)
+                              EXCLUDE_ANCHORED | EXCLUDE_WILDCARDS, '\n') != 0)
           {
             error (EXIT_TROUBLE, errno, "%s", optarg);
           }
@@ -2465,7 +2466,8 @@ main (int argc, char **argv)
         if (!excluded_directory_patterns)
           excluded_directory_patterns = new_exclude ();
         strip_trailing_slashes (optarg);
-        add_exclude (excluded_directory_patterns, optarg, EXCLUDE_WILDCARDS);
+        add_exclude (excluded_directory_patterns, optarg,
+                     EXCLUDE_ANCHORED | EXCLUDE_WILDCARDS);
         break;
 
       case GROUP_SEPARATOR_OPTION:
diff --git a/tests/include-exclude b/tests/include-exclude
index 55819c5..8b2f5aa 100755
--- a/tests/include-exclude
+++ b/tests/include-exclude
@@ -14,6 +14,7 @@ printf '%s\n' x/a:aaa x/b:bbb     > exp-not-dir || framework_failure_
 printf '%s\n' x/a:aaa             > exp-a       || framework_failure_
 printf '%s\n' a:aaa               > exp-aa      || framework_failure_
 printf '%s\n' aaa                 > exp-aaa     || framework_failure_
+printf '%s\n' ./x/a ./x/b ./x/dir/d > exp-dotnames || framework_failure+
 
 grep -r --exclude='a*'    . x > out || fail=1
 sort out > k && mv k out
@@ -51,7 +52,7 @@ grep -r --include='a*'    . x > out || fail=1
 compare exp-a out || fail=1
 
 # --include (without --recursive) uses different code
-grep --directories=skip --include=a --exclude-dir=dir '^aaa$' x/* > out \
+grep --directories=skip --include=x/a --exclude-dir=dir '^aaa$' x/* > out \
     || fail=1
 compare exp-a out || fail=1
 
@@ -61,4 +62,13 @@ compare exp-aa out || fail=1
 grep --exclude=- '^aaa$' - < x/a > out || fail=1
 compare exp-aaa out || fail=1
 
+for exclude in 'x' 'x*'; do
+    grep -rl --exclude-dir="$exclude" . x > out
+    test $? -eq 1 || fail=1
+    compare /dev/null out || fail=1
+
+    grep -rl --exclude-dir="$exclude" . ./x > out || fail=1
+    compare exp-dotnames out || fail=1
+done
+
 Exit $fail
-- 
2.1.0