unpack-trees: add a note about path invalidation
[gitweb.git] / grep.c
diff --git a/grep.c b/grep.c
index 7e2f440955a01ae305664df37f77ed87a55c2b06..2b26cee08d559ceba6dd5a83ae90aaa0155ebca8 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -20,6 +20,7 @@ static const char *color_grep_slots[] = {
        [GREP_COLOR_FILENAME]       = "filename",
        [GREP_COLOR_FUNCTION]       = "function",
        [GREP_COLOR_LINENO]         = "lineNumber",
+       [GREP_COLOR_COLUMNNO]       = "column",
        [GREP_COLOR_MATCH_CONTEXT]  = "matchContext",
        [GREP_COLOR_MATCH_SELECTED] = "matchSelected",
        [GREP_COLOR_SELECTED]       = "selected",
@@ -59,10 +60,12 @@ void init_grep_defaults(void)
        color_set(opt->colors[GREP_COLOR_FILENAME], "");
        color_set(opt->colors[GREP_COLOR_FUNCTION], "");
        color_set(opt->colors[GREP_COLOR_LINENO], "");
+       color_set(opt->colors[GREP_COLOR_COLUMNNO], "");
        color_set(opt->colors[GREP_COLOR_MATCH_CONTEXT], GIT_COLOR_BOLD_RED);
        color_set(opt->colors[GREP_COLOR_MATCH_SELECTED], GIT_COLOR_BOLD_RED);
        color_set(opt->colors[GREP_COLOR_SELECTED], "");
        color_set(opt->colors[GREP_COLOR_SEP], GIT_COLOR_CYAN);
+       opt->only_matching = 0;
        opt->color = -1;
        opt->output = std_output;
 }
@@ -110,6 +113,10 @@ int grep_config(const char *var, const char *value, void *cb)
                opt->linenum = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "grep.column")) {
+               opt->columnnum = git_config_bool(var, value);
+               return 0;
+       }
 
        if (!strcmp(var, "grep.fullname")) {
                opt->relative = !git_config_bool(var, value);
@@ -153,10 +160,12 @@ void grep_init(struct grep_opt *opt, const char *prefix)
        opt->pattern_tail = &opt->pattern_list;
        opt->header_tail = &opt->header_list;
 
+       opt->only_matching = def->only_matching;
        opt->color = def->color;
        opt->extended_regexp_option = def->extended_regexp_option;
        opt->pattern_type_option = def->pattern_type_option;
        opt->linenum = def->linenum;
+       opt->columnnum = def->columnnum;
        opt->max_depth = def->max_depth;
        opt->pathname = def->pathname;
        opt->relative = def->relative;
@@ -1244,11 +1253,11 @@ static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
        return hit;
 }
 
-static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
-                          enum grep_context ctx, int collect_hits)
+static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, char *bol,
+                          char *eol, enum grep_context ctx, ssize_t *col,
+                          ssize_t *icol, int collect_hits)
 {
        int h = 0;
-       regmatch_t match;
 
        if (!x)
                die("Not a valid grep expression");
@@ -1257,25 +1266,52 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
                h = 1;
                break;
        case GREP_NODE_ATOM:
-               h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0);
+               {
+                       regmatch_t tmp;
+                       h = match_one_pattern(x->u.atom, bol, eol, ctx,
+                                             &tmp, 0);
+                       if (h && (*col < 0 || tmp.rm_so < *col))
+                               *col = tmp.rm_so;
+               }
                break;
        case GREP_NODE_NOT:
-               h = !match_expr_eval(x->u.unary, bol, eol, ctx, 0);
+               /*
+                * Upon visiting a GREP_NODE_NOT, col and icol become swapped.
+                */
+               h = !match_expr_eval(opt, x->u.unary, bol, eol, ctx, icol, col,
+                                    0);
                break;
        case GREP_NODE_AND:
-               if (!match_expr_eval(x->u.binary.left, bol, eol, ctx, 0))
-                       return 0;
-               h = match_expr_eval(x->u.binary.right, bol, eol, ctx, 0);
+               h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col,
+                                   icol, 0);
+               if (h || opt->columnnum) {
+                       /*
+                        * Don't short-circuit AND when given --column, since a
+                        * NOT earlier in the tree may turn this into an OR. In
+                        * this case, see the below comment.
+                        */
+                       h &= match_expr_eval(opt, x->u.binary.right, bol, eol,
+                                            ctx, col, icol, 0);
+               }
                break;
        case GREP_NODE_OR:
-               if (!collect_hits)
-                       return (match_expr_eval(x->u.binary.left,
-                                               bol, eol, ctx, 0) ||
-                               match_expr_eval(x->u.binary.right,
-                                               bol, eol, ctx, 0));
-               h = match_expr_eval(x->u.binary.left, bol, eol, ctx, 0);
-               x->u.binary.left->hit |= h;
-               h |= match_expr_eval(x->u.binary.right, bol, eol, ctx, 1);
+               if (!(collect_hits || opt->columnnum)) {
+                       /*
+                        * Don't short-circuit OR when given --column (or
+                        * collecting hits) to ensure we don't skip a later
+                        * child that would produce an earlier match.
+                        */
+                       return (match_expr_eval(opt, x->u.binary.left, bol, eol,
+                                               ctx, col, icol, 0) ||
+                               match_expr_eval(opt, x->u.binary.right, bol,
+                                               eol, ctx, col, icol, 0));
+               }
+               h = match_expr_eval(opt, x->u.binary.left, bol, eol, ctx, col,
+                                   icol, 0);
+               if (collect_hits)
+                       x->u.binary.left->hit |= h;
+               h |= match_expr_eval(opt, x->u.binary.right, bol, eol, ctx, col,
+                                    icol, collect_hits);
                break;
        default:
                die("Unexpected node type (internal error) %d", x->node);
@@ -1286,27 +1322,43 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
 }
 
 static int match_expr(struct grep_opt *opt, char *bol, char *eol,
-                     enum grep_context ctx, int collect_hits)
+                     enum grep_context ctx, ssize_t *col,
+                     ssize_t *icol, int collect_hits)
 {
        struct grep_expr *x = opt->pattern_expression;
-       return match_expr_eval(x, bol, eol, ctx, collect_hits);
+       return match_expr_eval(opt, x, bol, eol, ctx, col, icol, collect_hits);
 }
 
 static int match_line(struct grep_opt *opt, char *bol, char *eol,
+                     ssize_t *col, ssize_t *icol,
                      enum grep_context ctx, int collect_hits)
 {
        struct grep_pat *p;
-       regmatch_t match;
+       int hit = 0;
 
        if (opt->extended)
-               return match_expr(opt, bol, eol, ctx, collect_hits);
+               return match_expr(opt, bol, eol, ctx, col, icol,
+                                 collect_hits);
 
        /* we do not call with collect_hits without being extended */
        for (p = opt->pattern_list; p; p = p->next) {
-               if (match_one_pattern(p, bol, eol, ctx, &match, 0))
-                       return 1;
+               regmatch_t tmp;
+               if (match_one_pattern(p, bol, eol, ctx, &tmp, 0)) {
+                       hit |= 1;
+                       if (!opt->columnnum) {
+                               /*
+                                * Without --column, any single match on a line
+                                * is enough to know that it needs to be
+                                * printed. With --column, scan _all_ patterns
+                                * to find the earliest.
+                                */
+                               break;
+                       }
+                       if (*col < 0 || tmp.rm_so < *col)
+                               *col = tmp.rm_so;
+               }
        }
-       return 0;
+       return hit;
 }
 
 static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
@@ -1354,11 +1406,44 @@ static int next_match(struct grep_opt *opt, char *bol, char *eol,
        return hit;
 }
 
+static void show_line_header(struct grep_opt *opt, const char *name,
+                            unsigned lno, ssize_t cno, char sign)
+{
+       if (opt->heading && opt->last_shown == 0) {
+               output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]);
+               opt->output(opt, "\n", 1);
+       }
+       opt->last_shown = lno;
+
+       if (!opt->heading && opt->pathname) {
+               output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]);
+               output_sep(opt, sign);
+       }
+       if (opt->linenum) {
+               char buf[32];
+               xsnprintf(buf, sizeof(buf), "%d", lno);
+               output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_LINENO]);
+               output_sep(opt, sign);
+       }
+       /*
+        * Treat 'cno' as the 1-indexed offset from the start of a non-context
+        * line to its first match. Otherwise, 'cno' is 0 indicating that we are
+        * being called with a context line.
+        */
+       if (opt->columnnum && cno) {
+               char buf[32];
+               xsnprintf(buf, sizeof(buf), "%"PRIuMAX, (uintmax_t)cno);
+               output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_COLUMNNO]);
+               output_sep(opt, sign);
+       }
+}
+
 static void show_line(struct grep_opt *opt, char *bol, char *eol,
-                     const char *name, unsigned lno, char sign)
+                     const char *name, unsigned lno, ssize_t cno, char sign)
 {
        int rest = eol - bol;
-       const char *match_color, *line_color = NULL;
+       const char *match_color = NULL;
+       const char *line_color = NULL;
 
        if (opt->file_break && opt->last_shown == 0) {
                if (opt->show_hunk_mark)
@@ -1374,54 +1459,55 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
                        opt->output(opt, "\n", 1);
                }
        }
-       if (opt->heading && opt->last_shown == 0) {
-               output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]);
-               opt->output(opt, "\n", 1);
-       }
-       opt->last_shown = lno;
-
-       if (!opt->heading && opt->pathname) {
-               output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]);
-               output_sep(opt, sign);
-       }
-       if (opt->linenum) {
-               char buf[32];
-               xsnprintf(buf, sizeof(buf), "%d", lno);
-               output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_LINENO]);
-               output_sep(opt, sign);
+       if (!opt->only_matching) {
+               /*
+                * In case the line we're being called with contains more than
+                * one match, leave printing each header to the loop below.
+                */
+               show_line_header(opt, name, lno, cno, sign);
        }
-       if (opt->color) {
+       if (opt->color || opt->only_matching) {
                regmatch_t match;
                enum grep_context ctx = GREP_CONTEXT_BODY;
                int ch = *eol;
                int eflags = 0;
 
-               if (sign == ':')
-                       match_color = opt->colors[GREP_COLOR_MATCH_SELECTED];
-               else
-                       match_color = opt->colors[GREP_COLOR_MATCH_CONTEXT];
-               if (sign == ':')
-                       line_color = opt->colors[GREP_COLOR_SELECTED];
-               else if (sign == '-')
-                       line_color = opt->colors[GREP_COLOR_CONTEXT];
-               else if (sign == '=')
-                       line_color = opt->colors[GREP_COLOR_FUNCTION];
+               if (opt->color) {
+                       if (sign == ':')
+                               match_color = opt->colors[GREP_COLOR_MATCH_SELECTED];
+                       else
+                               match_color = opt->colors[GREP_COLOR_MATCH_CONTEXT];
+                       if (sign == ':')
+                               line_color = opt->colors[GREP_COLOR_SELECTED];
+                       else if (sign == '-')
+                               line_color = opt->colors[GREP_COLOR_CONTEXT];
+                       else if (sign == '=')
+                               line_color = opt->colors[GREP_COLOR_FUNCTION];
+               }
                *eol = '\0';
                while (next_match(opt, bol, eol, ctx, &match, eflags)) {
                        if (match.rm_so == match.rm_eo)
                                break;
 
-                       output_color(opt, bol, match.rm_so, line_color);
+                       if (opt->only_matching)
+                               show_line_header(opt, name, lno, cno, sign);
+                       else
+                               output_color(opt, bol, match.rm_so, line_color);
                        output_color(opt, bol + match.rm_so,
                                     match.rm_eo - match.rm_so, match_color);
+                       if (opt->only_matching)
+                               opt->output(opt, "\n", 1);
                        bol += match.rm_eo;
+                       cno += match.rm_eo;
                        rest -= match.rm_eo;
                        eflags = REG_NOTBOL;
                }
                *eol = ch;
        }
-       output_color(opt, bol, rest, line_color);
-       opt->output(opt, "\n", 1);
+       if (!opt->only_matching) {
+               output_color(opt, bol, rest, line_color);
+               opt->output(opt, "\n", 1);
+       }
 }
 
 #ifndef NO_PTHREADS
@@ -1495,7 +1581,7 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
                        break;
 
                if (match_funcname(opt, gs, bol, eol)) {
-                       show_line(opt, bol, eol, gs->name, lno, '=');
+                       show_line(opt, bol, eol, gs->name, lno, 0, '=');
                        break;
                }
        }
@@ -1560,7 +1646,7 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
 
                while (*eol != '\n')
                        eol++;
-               show_line(opt, bol, eol, gs->name, cur, sign);
+               show_line(opt, bol, eol, gs->name, cur, 0, sign);
                bol = eol + 1;
                cur++;
        }
@@ -1759,6 +1845,8 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
        while (left) {
                char *eol, ch;
                int hit;
+               ssize_t cno;
+               ssize_t col = -1, icol = -1;
 
                /*
                 * look_ahead() skips quickly to the line that possibly
@@ -1782,7 +1870,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol))
                        ctx = GREP_CONTEXT_BODY;
 
-               hit = match_line(opt, bol, eol, ctx, collect_hits);
+               hit = match_line(opt, bol, eol, &col, &icol, ctx, collect_hits);
                *eol = ch;
 
                if (collect_hits)
@@ -1823,7 +1911,18 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                                show_pre_context(opt, gs, bol, eol, lno);
                        else if (opt->funcname)
                                show_funcname_line(opt, gs, bol, lno);
-                       show_line(opt, bol, eol, gs->name, lno, ':');
+                       cno = opt->invert ? icol : col;
+                       if (cno < 0) {
+                               /*
+                                * A negative cno indicates that there was no
+                                * match on the line. We are thus inverted and
+                                * being asked to show all lines that _don't_
+                                * match a given expression. Therefore, set cno
+                                * to 0 to suggest the whole line matches.
+                                */
+                               cno = 0;
+                       }
+                       show_line(opt, bol, eol, gs->name, lno, cno + 1, ':');
                        last_hit = lno;
                        if (opt->funcbody)
                                show_function = 1;
@@ -1852,7 +1951,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
                        /* If the last hit is within the post context,
                         * we need to show this line.
                         */
-                       show_line(opt, bol, eol, gs->name, lno, '-');
+                       show_line(opt, bol, eol, gs->name, lno, col + 1, '-');
                }
 
        next_line: