Tighten refspec processing
[gitweb.git] / builtin-grep.c
index 981f3d4d8eb079f5985beaaf94014d5745c5aacc..ef299108f529fa5fa931d765828f04535c3a6351 100644 (file)
 #include "builtin.h"
 #include "grep.h"
 
+#ifndef NO_EXTERNAL_GREP
+#ifdef __unix__
+#define NO_EXTERNAL_GREP 0
+#else
+#define NO_EXTERNAL_GREP 1
+#endif
+#endif
+
 /*
  * git grep pathspecs are somewhat different from diff-tree pathspecs;
  * pathname wildcards are allowed.
@@ -153,7 +161,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
        return i;
 }
 
-#ifdef __unix__
+#if !NO_EXTERNAL_GREP
 static int exec_grep(int argc, const char **argv)
 {
        pid_t pid;
@@ -187,6 +195,78 @@ static int exec_grep(int argc, const char **argv)
        else die("maximum number of args exceeded"); \
        } while (0)
 
+/*
+ * If you send a singleton filename to grep, it does not give
+ * the name of the file.  GNU grep has "-H" but we would want
+ * that behaviour in a portable way.
+ *
+ * So we keep two pathnames in argv buffer unsent to grep in
+ * the main loop if we need to do more than one grep.
+ */
+static int flush_grep(struct grep_opt *opt,
+                     int argc, int arg0, const char **argv, int *kept)
+{
+       int status;
+       int count = argc - arg0;
+       const char *kept_0 = NULL;
+
+       if (count <= 2) {
+               /*
+                * Because we keep at least 2 paths in the call from
+                * the main loop (i.e. kept != NULL), and MAXARGS is
+                * far greater than 2, this usually is a call to
+                * conclude the grep.  However, the user could attempt
+                * to overflow the argv buffer by giving too many
+                * options to leave very small number of real
+                * arguments even for the call in the main loop.
+                */
+               if (kept)
+                       die("insanely many options to grep");
+
+               /*
+                * If we have two or more paths, we do not have to do
+                * anything special, but we need to push /dev/null to
+                * get "-H" behaviour of GNU grep portably but when we
+                * are not doing "-l" nor "-L" nor "-c".
+                */
+               if (count == 1 &&
+                   !opt->name_only &&
+                   !opt->unmatch_name_only &&
+                   !opt->count) {
+                       argv[argc++] = "/dev/null";
+                       argv[argc] = NULL;
+               }
+       }
+
+       else if (kept) {
+               /*
+                * Called because we found many paths and haven't finished
+                * iterating over the cache yet.  We keep two paths
+                * for the concluding call.  argv[argc-2] and argv[argc-1]
+                * has the last two paths, so save the first one away,
+                * replace it with NULL while sending the list to grep,
+                * and recover them after we are done.
+                */
+               *kept = 2;
+               kept_0 = argv[argc-2];
+               argv[argc-2] = NULL;
+               argc -= 2;
+       }
+
+       status = exec_grep(argc, argv);
+
+       if (kept_0) {
+               /*
+                * Then recover them.  Now the last arg is beyond the
+                * terminating NULL which is at argc, and the second
+                * from the last is what we saved away in kept_0
+                */
+               argv[arg0++] = kept_0;
+               argv[arg0] = argv[argc+1];
+       }
+       return status;
+}
+
 static int external_grep(struct grep_opt *opt, const char **paths, int cached)
 {
        int i, nr, argc, hit, len, status;
@@ -222,7 +302,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                        if (opt->pre_context) {
                                push_arg("-B");
                                len += snprintf(argptr, sizeof(randarg)-len,
-                                               "%u", opt->pre_context);
+                                               "%u", opt->pre_context) + 1;
                                if (sizeof(randarg) <= len)
                                        die("maximum length of args exceeded");
                                push_arg(argptr);
@@ -231,7 +311,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                        if (opt->post_context) {
                                push_arg("-A");
                                len += snprintf(argptr, sizeof(randarg)-len,
-                                               "%u", opt->post_context);
+                                               "%u", opt->post_context) + 1;
                                if (sizeof(randarg) <= len)
                                        die("maximum length of args exceeded");
                                push_arg(argptr);
@@ -241,7 +321,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                else {
                        push_arg("-C");
                        len += snprintf(argptr, sizeof(randarg)-len,
-                                       "%u", opt->post_context);
+                                       "%u", opt->post_context) + 1;
                        if (sizeof(randarg) <= len)
                                die("maximum length of args exceeded");
                        push_arg(argptr);
@@ -253,23 +333,13 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                push_arg(p->pattern);
        }
 
-       /*
-        * To make sure we get the header printed out when we want it,
-        * add /dev/null to the paths to grep.  This is unnecessary
-        * (and wrong) with "-l" or "-L", which always print out the
-        * name anyway.
-        *
-        * GNU grep has "-H", but this is portable.
-        */
-       if (!opt->name_only && !opt->unmatch_name_only)
-               push_arg("/dev/null");
-
        hit = 0;
        argc = nr;
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
                char *name;
-               if (!S_ISREG(ntohl(ce->ce_mode)))
+               int kept;
+               if (!S_ISREG(ce->ce_mode))
                        continue;
                if (!pathspec_matches(paths, ce->name))
                        continue;
@@ -281,12 +351,12 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                        memcpy(name + 2, ce->name, len + 1);
                }
                argv[argc++] = name;
-               if (argc < MAXARGS && !ce_stage(ce))
-                       continue;
-               status = exec_grep(argc, argv);
-               if (0 < status)
-                       hit = 1;
-               argc = nr;
+               if (MAXARGS <= argc) {
+                       status = flush_grep(opt, argc, nr, argv, &kept);
+                       if (0 < status)
+                               hit = 1;
+                       argc = nr + kept;
+               }
                if (ce_stage(ce)) {
                        do {
                                i++;
@@ -296,7 +366,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                }
        }
        if (argc > nr) {
-               status = exec_grep(argc, argv);
+               status = flush_grep(opt, argc, nr, argv, NULL);
                if (0 < status)
                        hit = 1;
        }
@@ -310,7 +380,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
        int nr;
        read_cache();
 
-#ifdef __unix__
+#if !NO_EXTERNAL_GREP
        /*
         * Use the external "grep" command for the case where
         * we grep through the checked-out files. It tends to
@@ -325,7 +395,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 
        for (nr = 0; nr < active_nr; nr++) {
                struct cache_entry *ce = active_cache[nr];
-               if (!S_ISREG(ntohl(ce->ce_mode)))
+               if (!S_ISREG(ce->ce_mode))
                        continue;
                if (!pathspec_matches(paths, ce->name))
                        continue;
@@ -434,19 +504,6 @@ static const char emsg_missing_context_len[] =
 static const char emsg_missing_argument[] =
 "option requires an argument -%s";
 
-static int strtoul_ui(char const *s, unsigned int *result)
-{
-       unsigned long ul;
-       char *p;
-
-       errno = 0;
-       ul = strtoul(s, &p, 10);
-       if (errno || *p || p == s || (unsigned int) ul != ul)
-               return -1;
-       *result = ul;
-       return 0;
-}
-
 int cmd_grep(int argc, const char **argv, const char *prefix)
 {
        int hit = 0;
@@ -529,6 +586,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp("-l", arg) ||
+                   !strcmp("--name-only", arg) ||
                    !strcmp("--files-with-matches", arg)) {
                        opt.name_only = 1;
                        continue;
@@ -569,7 +627,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                                scan = arg + 1;
                                break;
                        }
-                       if (strtoul_ui(scan, &num))
+                       if (strtoul_ui(scan, 10, &num))
                                die(emsg_invalid_context_len, scan);
                        switch (arg[1]) {
                        case 'A':
@@ -595,7 +653,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                                die("'%s': %s", argv[1], strerror(errno));
                        while (fgets(buf, sizeof(buf), patterns)) {
                                int len = strlen(buf);
-                               if (buf[len-1] == '\n')
+                               if (len && buf[len-1] == '\n')
                                        buf[len-1] = 0;
                                /* ignore empty line like grep does */
                                if (!buf[0])