Update gitweb README: gitweb is now included with git
[gitweb.git] / builtin-grep.c
index fead35662944070f8502ed0484fe6b6085d7ac86..5fac5701e6ccebdbe83b00d2e2df19343d3aea0e 100644 (file)
@@ -12,6 +12,7 @@
 #include "builtin.h"
 #include <regex.h>
 #include <fnmatch.h>
+#include <sys/wait.h>
 
 /*
  * git grep pathspecs are somewhat different from diff-tree pathspecs;
@@ -409,12 +410,158 @@ static int grep_file(struct grep_opt *opt, const char *filename)
        return i;
 }
 
+static int exec_grep(int argc, const char **argv)
+{
+       pid_t pid;
+       int status;
+
+       argv[argc] = NULL;
+       pid = fork();
+       if (pid < 0)
+               return pid;
+       if (!pid) {
+               execvp("grep", (char **) argv);
+               exit(255);
+       }
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno == EINTR)
+                       continue;
+               return -1;
+       }
+       if (WIFEXITED(status)) {
+               if (!WEXITSTATUS(status))
+                       return 1;
+               return 0;
+       }
+       return -1;
+}
+
+#define MAXARGS 1000
+#define ARGBUF 4096
+#define push_arg(a) do { \
+       if (nr < MAXARGS) argv[nr++] = (a); \
+       else die("maximum number of args exceeded"); \
+       } while (0)
+
+static int external_grep(struct grep_opt *opt, const char **paths, int cached)
+{
+       int i, nr, argc, hit, len;
+       const char *argv[MAXARGS+1];
+       char randarg[ARGBUF];
+       char *argptr = randarg;
+       struct grep_pat *p;
+
+       len = nr = 0;
+       push_arg("grep");
+       if (opt->fixed)
+               push_arg("-F");
+       if (opt->linenum)
+               push_arg("-n");
+       if (opt->regflags & REG_EXTENDED)
+               push_arg("-E");
+       if (opt->regflags & REG_ICASE)
+               push_arg("-i");
+       if (opt->word_regexp)
+               push_arg("-w");
+       if (opt->name_only)
+               push_arg("-l");
+       if (opt->unmatch_name_only)
+               push_arg("-L");
+       if (opt->count)
+               push_arg("-c");
+       if (opt->post_context || opt->pre_context) {
+               if (opt->post_context != opt->pre_context) {
+                       if (opt->pre_context) {
+                               push_arg("-B");
+                               len += snprintf(argptr, sizeof(randarg)-len,
+                                               "%u", opt->pre_context);
+                               if (sizeof(randarg) <= len)
+                                       die("maximum length of args exceeded");
+                               push_arg(argptr);
+                               argptr += len;
+                       }
+                       if (opt->post_context) {
+                               push_arg("-A");
+                               len += snprintf(argptr, sizeof(randarg)-len,
+                                               "%u", opt->post_context);
+                               if (sizeof(randarg) <= len)
+                                       die("maximum length of args exceeded");
+                               push_arg(argptr);
+                               argptr += len;
+                       }
+               }
+               else {
+                       push_arg("-C");
+                       len += snprintf(argptr, sizeof(randarg)-len,
+                                       "%u", opt->post_context);
+                       if (sizeof(randarg) <= len)
+                               die("maximum length of args exceeded");
+                       push_arg(argptr);
+                       argptr += len;
+               }
+       }
+       for (p = opt->pattern_list; p; p = p->next) {
+               push_arg("-e");
+               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 (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+                       continue;
+               if (!pathspec_matches(paths, ce->name))
+                       continue;
+               name = ce->name;
+               if (name[0] == '-') {
+                       int len = ce_namelen(ce);
+                       name = xmalloc(len + 3);
+                       memcpy(name, "./", 2);
+                       memcpy(name + 2, ce->name, len + 1);
+               }
+               argv[argc++] = name;
+               if (argc < MAXARGS)
+                       continue;
+               hit += exec_grep(argc, argv);
+               argc = nr;
+       }
+       if (argc > nr)
+               hit += exec_grep(argc, argv);
+       return 0;
+}
+
 static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 {
        int hit = 0;
        int nr;
        read_cache();
 
+#ifdef __unix__
+       /*
+        * Use the external "grep" command for the case where
+        * we grep through the checked-out files. It tends to
+        * be a lot more optimized
+        */
+       if (!cached) {
+               hit = external_grep(opt, paths, cached);
+               if (hit >= 0)
+                       return hit;
+       }
+#endif
+
        for (nr = 0; nr < active_nr; nr++) {
                struct cache_entry *ce = active_cache[nr];
                if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
@@ -433,11 +580,9 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
                     struct tree_desc *tree,
                     const char *tree_name, const char *base)
 {
-       unsigned mode;
        int len;
        int hit = 0;
-       const char *path;
-       const unsigned char *sha1;
+       struct name_entry entry;
        char *down;
        char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100);
 
@@ -452,36 +597,32 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
        }
        len = strlen(path_buf);
 
-       while (tree->size) {
-               int pathlen;
-               sha1 = tree_entry_extract(tree, &path, &mode);
-               pathlen = strlen(path);
-               strcpy(path_buf + len, path);
+       while (tree_entry(tree, &entry)) {
+               strcpy(path_buf + len, entry.path);
 
-               if (S_ISDIR(mode))
+               if (S_ISDIR(entry.mode))
                        /* Match "abc/" against pathspec to
                         * decide if we want to descend into "abc"
                         * directory.
                         */
-                       strcpy(path_buf + len + pathlen, "/");
+                       strcpy(path_buf + len + entry.pathlen, "/");
 
                if (!pathspec_matches(paths, down))
                        ;
-               else if (S_ISREG(mode))
-                       hit |= grep_sha1(opt, sha1, path_buf);
-               else if (S_ISDIR(mode)) {
+               else if (S_ISREG(entry.mode))
+                       hit |= grep_sha1(opt, entry.sha1, path_buf);
+               else if (S_ISDIR(entry.mode)) {
                        char type[20];
                        struct tree_desc sub;
                        void *data;
-                       data = read_sha1_file(sha1, type, &sub.size);
+                       data = read_sha1_file(entry.sha1, type, &sub.size);
                        if (!data)
                                die("unable to read tree (%s)",
-                                   sha1_to_hex(sha1));
+                                   sha1_to_hex(entry.sha1));
                        sub.buf = data;
                        hit |= grep_tree(opt, paths, &sub, tree_name, down);
                        free(data);
                }
-               update_tree_entry(tree);
        }
        return hit;
 }