Merge branch 'jc/pack'
[gitweb.git] / builtin-grep.c
index e4f06f2b3ab23426edc4cf3e547d3c29efa6a86a..c7b45c4d58dd718204e815363a77747d2d5aebb3 100644 (file)
@@ -122,6 +122,8 @@ static int grep_file(struct grep_opt *opt, const char *filename)
        struct stat st;
        int i;
        char *data;
+       size_t sz;
+
        if (lstat(filename, &st) < 0) {
        err_ret:
                if (errno != ENOENT)
@@ -132,11 +134,12 @@ static int grep_file(struct grep_opt *opt, const char *filename)
                return 0; /* empty file -- no grep hit */
        if (!S_ISREG(st.st_mode))
                return 0;
+       sz = xsize_t(st.st_size);
        i = open(filename, O_RDONLY);
        if (i < 0)
                goto err_ret;
-       data = xmalloc(st.st_size + 1);
-       if (st.st_size != read_in_full(i, data, st.st_size)) {
+       data = xmalloc(sz + 1);
+       if (st.st_size != read_in_full(i, data, sz)) {
                error("'%s': short read %s", filename, strerror(errno));
                close(i);
                free(data);
@@ -145,7 +148,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
        close(i);
        if (opt->relative && opt->prefix_length)
                filename += opt->prefix_length;
-       i = grep_buffer(opt, filename, data, st.st_size);
+       i = grep_buffer(opt, filename, data, sz);
        free(data);
        return i;
 }
@@ -184,6 +187,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;
@@ -250,22 +325,12 @@ 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;
+               int kept;
                if (!S_ISREG(ntohl(ce->ce_mode)))
                        continue;
                if (!pathspec_matches(paths, ce->name))
@@ -280,10 +345,10 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                argv[argc++] = name;
                if (argc < MAXARGS && !ce_stage(ce))
                        continue;
-               status = exec_grep(argc, argv);
+               status = flush_grep(opt, argc, nr, argv, &kept);
                if (0 < status)
                        hit = 1;
-               argc = nr;
+               argc = nr + kept;
                if (ce_stage(ce)) {
                        do {
                                i++;
@@ -293,7 +358,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;
        }
@@ -375,7 +440,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                         * decide if we want to descend into "abc"
                         * directory.
                         */
-                       strcpy(path_buf + len + entry.pathlen, "/");
+                       strcpy(path_buf + len + tree_entry_len(entry.path, entry.sha1), "/");
 
                if (!pathspec_matches(paths, down))
                        ;
@@ -385,11 +450,13 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                        enum object_type type;
                        struct tree_desc sub;
                        void *data;
-                       data = read_sha1_file(entry.sha1, &type, &sub.size);
+                       unsigned long size;
+
+                       data = read_sha1_file(entry.sha1, &type, &size);
                        if (!data)
                                die("unable to read tree (%s)",
                                    sha1_to_hex(entry.sha1));
-                       sub.buf = data;
+                       init_tree_desc(&sub, data, size);
                        hit |= grep_tree(opt, paths, &sub, tree_name, down);
                        free(data);
                }
@@ -405,12 +472,13 @@ static int grep_object(struct grep_opt *opt, const char **paths,
        if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
                struct tree_desc tree;
                void *data;
+               unsigned long size;
                int hit;
                data = read_object_with_reference(obj->sha1, tree_type,
-                                                 &tree.size, NULL);
+                                                 &size, NULL);
                if (!data)
                        die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
-               tree.buf = data;
+               init_tree_desc(&tree, data, size);
                hit = grep_tree(opt, paths, &tree, name, "");
                free(data);
                return hit;
@@ -550,7 +618,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                                scan = arg + 1;
                                break;
                        }
-                       if (sscanf(scan, "%u", &num) != 1)
+                       if (strtoul_ui(scan, 10, &num))
                                die(emsg_invalid_context_len, scan);
                        switch (arg[1]) {
                        case 'A':