t6042: Add a testcase where undetected rename causes silent file deletion
[gitweb.git] / builtin / grep.c
index bb0f93208fc6c3e418f9db5bcaec4b79c57e2bec..10a1f65310f28f2014bab3f3295205abf6dc59ad 100644 (file)
@@ -244,7 +244,7 @@ static void start_threads(struct grep_opt *opt)
                err = pthread_create(&threads[i], NULL, run, o);
 
                if (err)
-                       die("grep: failed to create thread: %s",
+                       die(_("grep: failed to create thread: %s"),
                            strerror(err));
        }
 }
@@ -302,6 +302,19 @@ static int grep_config(const char *var, const char *value, void *cb)
        default: return 0;
        }
 
+       if (!strcmp(var, "grep.extendedregexp")) {
+               if (git_config_bool(var, value))
+                       opt->regflags |= REG_EXTENDED;
+               else
+                       opt->regflags &= ~REG_EXTENDED;
+               return 0;
+       }
+
+       if (!strcmp(var, "grep.linenumber")) {
+               opt->linenum = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "color.grep"))
                opt->color = git_config_colorbool(var, value, -1);
        else if (!strcmp(var, "color.grep.context"))
@@ -328,106 +341,6 @@ static int grep_config(const char *var, const char *value, void *cb)
        return 0;
 }
 
-/*
- * Return non-zero if max_depth is negative or path has no more then max_depth
- * slashes.
- */
-static int accept_subdir(const char *path, int max_depth)
-{
-       if (max_depth < 0)
-               return 1;
-
-       while ((path = strchr(path, '/')) != NULL) {
-               max_depth--;
-               if (max_depth < 0)
-                       return 0;
-               path++;
-       }
-       return 1;
-}
-
-/*
- * Return non-zero if name is a subdirectory of match and is not too deep.
- */
-static int is_subdir(const char *name, int namelen,
-               const char *match, int matchlen, int max_depth)
-{
-       if (matchlen > namelen || strncmp(name, match, matchlen))
-               return 0;
-
-       if (name[matchlen] == '\0') /* exact match */
-               return 1;
-
-       if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
-               return accept_subdir(name + matchlen + 1, max_depth);
-
-       return 0;
-}
-
-/*
- * git grep pathspecs are somewhat different from diff-tree pathspecs;
- * pathname wildcards are allowed.
- */
-static int pathspec_matches(const char **paths, const char *name, int max_depth)
-{
-       int namelen, i;
-       if (!paths || !*paths)
-               return accept_subdir(name, max_depth);
-       namelen = strlen(name);
-       for (i = 0; paths[i]; i++) {
-               const char *match = paths[i];
-               int matchlen = strlen(match);
-               const char *cp, *meta;
-
-               if (is_subdir(name, namelen, match, matchlen, max_depth))
-                       return 1;
-               if (!fnmatch(match, name, 0))
-                       return 1;
-               if (name[namelen-1] != '/')
-                       continue;
-
-               /* We are being asked if the directory ("name") is worth
-                * descending into.
-                *
-                * Find the longest leading directory name that does
-                * not have metacharacter in the pathspec; the name
-                * we are looking at must overlap with that directory.
-                */
-               for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
-                       char ch = *cp;
-                       if (ch == '*' || ch == '[' || ch == '?') {
-                               meta = cp;
-                               break;
-                       }
-               }
-               if (!meta)
-                       meta = cp; /* fully literal */
-
-               if (namelen <= meta - match) {
-                       /* Looking at "Documentation/" and
-                        * the pattern says "Documentation/howto/", or
-                        * "Documentation/diff*.txt".  The name we
-                        * have should match prefix.
-                        */
-                       if (!memcmp(match, name, namelen))
-                               return 1;
-                       continue;
-               }
-
-               if (meta - match < namelen) {
-                       /* Looking at "Documentation/howto/" and
-                        * the pattern says "Documentation/h*";
-                        * match up to "Do.../h"; this avoids descending
-                        * into "Documentation/technical/".
-                        */
-                       if (!memcmp(match, name, meta - match))
-                               return 1;
-                       continue;
-               }
-       }
-       return 0;
-}
-
 static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
 {
        void *data;
@@ -449,7 +362,7 @@ static void *load_sha1(const unsigned char *sha1, unsigned long *size,
        void *data = lock_and_read_sha1_file(sha1, &type, size);
 
        if (!data)
-               error("'%s': unable to read %s", name, sha1_to_hex(sha1));
+               error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1));
 
        return data;
 }
@@ -500,21 +413,21 @@ static void *load_file(const char *filename, size_t *sz)
        if (lstat(filename, &st) < 0) {
        err_ret:
                if (errno != ENOENT)
-                       error("'%s': %s", filename, strerror(errno));
-               return 0;
+                       error(_("'%s': %s"), filename, strerror(errno));
+               return NULL;
        }
        if (!S_ISREG(st.st_mode))
-               return 0;
+               return NULL;
        *sz = xsize_t(st.st_size);
        i = open(filename, O_RDONLY);
        if (i < 0)
                goto err_ret;
        data = xmalloc(*sz + 1);
        if (st.st_size != read_in_full(i, data, *sz)) {
-               error("'%s': short read %s", filename, strerror(errno));
+               error(_("'%s': short read %s"), filename, strerror(errno));
                close(i);
                free(data);
-               return 0;
+               return NULL;
        }
        close(i);
        data[*sz] = 0;
@@ -573,14 +486,14 @@ static void run_pager(struct grep_opt *opt, const char *prefix)
        argv[path_list->nr] = NULL;
 
        if (prefix && chdir(prefix))
-               die("Failed to chdir: %s", prefix);
+               die(_("Failed to chdir: %s"), prefix);
        status = run_command_v_opt(argv, RUN_USING_SHELL);
        if (status)
                exit(status);
        free(argv);
 }
 
-static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached)
 {
        int hit = 0;
        int nr;
@@ -590,7 +503,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
                struct cache_entry *ce = active_cache[nr];
                if (!S_ISREG(ce->ce_mode))
                        continue;
-               if (!pathspec_matches(paths, ce->name, opt->max_depth))
+               if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
                        continue;
                /*
                 * If CE_VALID is on, we assume worktree file and its cache entry
@@ -617,44 +530,29 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
        return hit;
 }
 
-static int grep_tree(struct grep_opt *opt, const char **paths,
-                    struct tree_desc *tree,
-                    const char *tree_name, const char *base)
+static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
+                    struct tree_desc *tree, struct strbuf *base, int tn_len)
 {
-       int len;
-       int hit = 0;
+       int hit = 0, matched = 0;
        struct name_entry entry;
-       char *down;
-       int tn_len = strlen(tree_name);
-       struct strbuf pathbuf;
-
-       strbuf_init(&pathbuf, PATH_MAX + tn_len);
-
-       if (tn_len) {
-               strbuf_add(&pathbuf, tree_name, tn_len);
-               strbuf_addch(&pathbuf, ':');
-               tn_len = pathbuf.len;
-       }
-       strbuf_addstr(&pathbuf, base);
-       len = pathbuf.len;
+       int old_baselen = base->len;
 
        while (tree_entry(tree, &entry)) {
                int te_len = tree_entry_len(entry.path, entry.sha1);
-               pathbuf.len = len;
-               strbuf_add(&pathbuf, entry.path, te_len);
-
-               if (S_ISDIR(entry.mode))
-                       /* Match "abc/" against pathspec to
-                        * decide if we want to descend into "abc"
-                        * directory.
-                        */
-                       strbuf_addch(&pathbuf, '/');
-
-               down = pathbuf.buf + tn_len;
-               if (!pathspec_matches(paths, down, opt->max_depth))
-                       ;
-               else if (S_ISREG(entry.mode))
-                       hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
+
+               if (matched != 2) {
+                       matched = tree_entry_interesting(&entry, base, tn_len, pathspec);
+                       if (matched == -1)
+                               break; /* no more matches */
+                       if (!matched)
+                               continue;
+               }
+
+               strbuf_add(base, entry.path, te_len);
+
+               if (S_ISREG(entry.mode)) {
+                       hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len);
+               }
                else if (S_ISDIR(entry.mode)) {
                        enum object_type type;
                        struct tree_desc sub;
@@ -663,20 +561,23 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
 
                        data = lock_and_read_sha1_file(entry.sha1, &type, &size);
                        if (!data)
-                               die("unable to read tree (%s)",
+                               die(_("unable to read tree (%s)"),
                                    sha1_to_hex(entry.sha1));
+
+                       strbuf_addch(base, '/');
                        init_tree_desc(&sub, data, size);
-                       hit |= grep_tree(opt, paths, &sub, tree_name, down);
+                       hit |= grep_tree(opt, pathspec, &sub, base, tn_len);
                        free(data);
                }
+               strbuf_setlen(base, old_baselen);
+
                if (hit && opt->status_only)
                        break;
        }
-       strbuf_release(&pathbuf);
        return hit;
 }
 
-static int grep_object(struct grep_opt *opt, const char **paths,
+static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
                       struct object *obj, const char *name)
 {
        if (obj->type == OBJ_BLOB)
@@ -685,20 +586,30 @@ static int grep_object(struct grep_opt *opt, const char **paths,
                struct tree_desc tree;
                void *data;
                unsigned long size;
-               int hit;
+               struct strbuf base;
+               int hit, len;
+
                data = read_object_with_reference(obj->sha1, tree_type,
                                                  &size, NULL);
                if (!data)
-                       die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+                       die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1));
+
+               len = name ? strlen(name) : 0;
+               strbuf_init(&base, PATH_MAX + len + 1);
+               if (len) {
+                       strbuf_add(&base, name, len);
+                       strbuf_addch(&base, ':');
+               }
                init_tree_desc(&tree, data, size);
-               hit = grep_tree(opt, paths, &tree, name, "");
+               hit = grep_tree(opt, pathspec, &tree, &base, base.len);
+               strbuf_release(&base);
                free(data);
                return hit;
        }
-       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_objects(struct grep_opt *opt, const char **paths,
+static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
                        const struct object_array *list)
 {
        unsigned int i;
@@ -708,7 +619,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths,
        for (i = 0; i < nr; i++) {
                struct object *real_obj;
                real_obj = deref_tag(list->objects[i].item, NULL, 0);
-               if (grep_object(opt, paths, real_obj, list->objects[i].name)) {
+               if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) {
                        hit = 1;
                        if (opt->status_only)
                                break;
@@ -717,7 +628,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths,
        return hit;
 }
 
-static int grep_directory(struct grep_opt *opt, const char **paths)
+static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
 {
        struct dir_struct dir;
        int i, hit = 0;
@@ -725,8 +636,12 @@ static int grep_directory(struct grep_opt *opt, const char **paths)
        memset(&dir, 0, sizeof(dir));
        setup_standard_excludes(&dir);
 
-       fill_directory(&dir, paths);
+       fill_directory(&dir, pathspec->raw);
        for (i = 0; i < dir.nr; i++) {
+               const char *name = dir.entries[i]->name;
+               int namelen = strlen(name);
+               if (!match_pathspec_depth(pathspec, name, namelen, 0, NULL))
+                       continue;
                hit |= grep_file(opt, dir.entries[i]->name);
                if (hit && opt->status_only)
                        break;
@@ -747,7 +662,7 @@ static int context_callback(const struct option *opt, const char *arg,
        }
        value = strtol(arg, (char **)&endp, 10);
        if (*endp) {
-               return error("switch `%c' expects a numerical value",
+               return error(_("switch `%c' expects a numerical value"),
                             opt->short_name);
        }
        grep_opt->pre_context = grep_opt->post_context = value;
@@ -757,13 +672,14 @@ static int context_callback(const struct option *opt, const char *arg,
 static int file_callback(const struct option *opt, const char *arg, int unset)
 {
        struct grep_opt *grep_opt = opt->value;
+       int from_stdin = !strcmp(arg, "-");
        FILE *patterns;
        int lno = 0;
        struct strbuf sb = STRBUF_INIT;
 
-       patterns = fopen(arg, "r");
+       patterns = from_stdin ? stdin : fopen(arg, "r");
        if (!patterns)
-               die_errno("cannot open '%s'", arg);
+               die_errno(_("cannot open '%s'"), arg);
        while (strbuf_getline(&sb, patterns, '\n') == 0) {
                char *s;
                size_t len;
@@ -775,7 +691,8 @@ static int file_callback(const struct option *opt, const char *arg, int unset)
                s = strbuf_detach(&sb, &len);
                append_grep_pat(grep_opt, s, len, arg, ++lno, GREP_PATTERN);
        }
-       fclose(patterns);
+       if (!from_stdin)
+               fclose(patterns);
        strbuf_release(&sb);
        return 0;
 }
@@ -831,6 +748,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        struct grep_opt opt;
        struct object_array list = OBJECT_ARRAY_INIT;
        const char **paths = NULL;
+       struct pathspec pathspec;
        struct string_list path_list = STRING_LIST_INIT_NODUP;
        int i;
        int dummy;
@@ -1004,11 +922,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        }
 
        if (!opt.pattern_list)
-               die("no pattern given.");
+               die(_("no pattern given."));
        if (!opt.fixed && opt.ignore_case)
                opt.regflags |= REG_ICASE;
        if ((opt.regflags != REG_NEWLINE) && opt.fixed)
-               die("cannot mix --fixed-strings and regexp");
+               die(_("cannot mix --fixed-strings and regexp"));
 
 #ifndef NO_PTHREADS
        if (online_cpus() == 1 || !grep_threads_ok(&opt))
@@ -1033,7 +951,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                if (!get_sha1(arg, sha1)) {
                        struct object *object = parse_object(sha1);
                        if (!object)
-                               die("bad object %s", arg);
+                               die(_("bad object %s"), arg);
                        add_object_array(object, arg, &list);
                        continue;
                }
@@ -1058,9 +976,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                paths[0] = prefix;
                paths[1] = NULL;
        }
+       init_pathspec(&pathspec, paths);
+       pathspec.max_depth = opt.max_depth;
+       pathspec.recursive = 1;
 
        if (show_in_pager && (cached || list.nr))
-               die("--open-files-in-pager only works on the worktree");
+               die(_("--open-files-in-pager only works on the worktree"));
 
        if (show_in_pager && opt.pattern_list && !opt.pattern_list->next) {
                const char *pager = path_list.items[0].string;
@@ -1085,19 +1006,19 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 
        if (!use_index) {
                if (cached)
-                       die("--cached cannot be used with --no-index.");
+                       die(_("--cached cannot be used with --no-index."));
                if (list.nr)
-                       die("--no-index cannot be used with revs.");
-               hit = grep_directory(&opt, paths);
+                       die(_("--no-index cannot be used with revs."));
+               hit = grep_directory(&opt, &pathspec);
        } else if (!list.nr) {
                if (!cached)
                        setup_work_tree();
 
-               hit = grep_cache(&opt, paths, cached);
+               hit = grep_cache(&opt, &pathspec, cached);
        } else {
                if (cached)
-                       die("both --cached and trees are given.");
-               hit = grep_objects(&opt, paths, &list);
+                       die(_("both --cached and trees are given."));
+               hit = grep_objects(&opt, &pathspec, &list);
        }
 
        if (use_threads)