Merge branch 'tb/grep-column'
authorJunio C Hamano <gitster@pobox.com>
Wed, 18 Jul 2018 19:20:31 +0000 (12:20 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 18 Jul 2018 19:20:31 +0000 (12:20 -0700)
"git grep" learned the "--column" option that gives not just the
line number but the column number of the hit.

* tb/grep-column:
  contrib/git-jump/git-jump: jump to exact location
  grep.c: add configuration variables to show matched option
  builtin/grep.c: add '--column' option to 'git-grep(1)'
  grep.c: display column number of first match
  grep.[ch]: extend grep_opt to allow showing matched column
  grep.c: expose {,inverted} match column in match_line()
  Documentation/config.txt: camel-case lineNumber for consistency

1  2 
Documentation/config.txt
grep.c
grep.h

diff --combined Documentation/config.txt
@@@ -354,7 -354,7 +354,7 @@@ advice.*:
                Advice on what to do when you've accidentally added one
                git repo inside of another.
        ignoredHook::
 -              Advice shown if an hook is ignored because the hook is not
 +              Advice shown if a hook is ignored because the hook is not
                set as executable.
        waitingForEditor::
                Print a message to the terminal whenever Git is waiting for
@@@ -1162,8 -1162,7 +1162,8 @@@ color.diff.<slot>:
  color.decorate.<slot>::
        Use customized color for 'git log --decorate' output.  `<slot>` is one
        of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local
 -      branches, remote-tracking branches, tags, stash and HEAD, respectively.
 +      branches, remote-tracking branches, tags, stash and HEAD, respectively
 +      and `grafted` for grafted commits.
  
  color.grep::
        When set to `always`, always highlight matches.  When `false` (or
@@@ -1182,8 -1181,10 +1182,10 @@@ color.grep.<slot>:
        filename prefix (when not using `-h`)
  `function`;;
        function name lines (when using `-p`)
- `linenumber`;;
+ `lineNumber`;;
        line number prefix (when using `-n`)
+ `column`;;
+       column number prefix (when using `--column`)
  `match`;;
        matching text (same as setting `matchContext` and `matchSelected`)
  `matchContext`;;
@@@ -1798,6 -1799,9 +1800,9 @@@ gitweb.snapshot:
  grep.lineNumber::
        If set to true, enable `-n` option by default.
  
+ grep.column::
+       If set to true, enable the `--column` option by default.
  grep.patternType::
        Set the default matching behavior. Using a value of 'basic', 'extended',
        'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
@@@ -3328,13 -3332,12 +3333,13 @@@ submodule.<name>.ignore:
  submodule.<name>.active::
        Boolean value indicating if the submodule is of interest to git
        commands.  This config option takes precedence over the
 -      submodule.active config option.
 +      submodule.active config option. See linkgit:gitsubmodules[7] for
 +      details.
  
  submodule.active::
        A repeated field which contains a pathspec used to match against a
        submodule's path to determine if the submodule is of interest to git
 -      commands.
 +      commands. See linkgit:gitsubmodules[7] for details.
  
  submodule.recurse::
        Specifies if commands recurse into submodules by default. This
diff --combined grep.c
--- 1/grep.c
--- 2/grep.c
+++ b/grep.c
@@@ -1,31 -1,18 +1,32 @@@
  #include "cache.h"
  #include "config.h"
  #include "grep.h"
 +#include "object-store.h"
  #include "userdiff.h"
  #include "xdiff-interface.h"
  #include "diff.h"
  #include "diffcore.h"
  #include "commit.h"
  #include "quote.h"
 +#include "help.h"
  
  static int grep_source_load(struct grep_source *gs);
  static int grep_source_is_binary(struct grep_source *gs);
  
  static struct grep_opt grep_defaults;
  
 +static const char *color_grep_slots[] = {
 +      [GREP_COLOR_CONTEXT]        = "context",
 +      [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",
 +      [GREP_COLOR_SEP]            = "separator",
 +};
 +
  static void std_output(struct grep_opt *opt, const void *buf, size_t size)
  {
        fwrite(buf, size, 1, stdout);
@@@ -55,14 -42,15 +56,15 @@@ void init_grep_defaults(void
        opt->pathname = 1;
        opt->max_depth = -1;
        opt->pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED;
 -      color_set(opt->color_context, "");
 -      color_set(opt->color_filename, "");
 -      color_set(opt->color_function, "");
 -      color_set(opt->color_lineno, "");
 -      color_set(opt->color_columnno, "");
 -      color_set(opt->color_match_context, GIT_COLOR_BOLD_RED);
 -      color_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);
 -      color_set(opt->color_selected, "");
 -      color_set(opt->color_sep, GIT_COLOR_CYAN);
 +      color_set(opt->colors[GREP_COLOR_CONTEXT], "");
 +      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->color = -1;
        opt->output = std_output;
  }
@@@ -82,8 -70,6 +84,8 @@@ static int parse_pattern_type_arg(cons
        die("bad %s argument: %s", opt, arg);
  }
  
 +define_list_config_array_extra(color_grep_slots, {"match"});
 +
  /*
   * Read the configuration file once and store it in
   * the grep_defaults template.
@@@ -91,7 -77,7 +93,7 @@@
  int grep_config(const char *var, const char *value, void *cb)
  {
        struct grep_opt *opt = &grep_defaults;
 -      char *color = NULL;
 +      const char *slot;
  
        if (userdiff_config(var, value) < 0)
                return -1;
                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);
  
        if (!strcmp(var, "color.grep"))
                opt->color = git_config_colorbool(var, value);
 -      else if (!strcmp(var, "color.grep.context"))
 -              color = opt->color_context;
 -      else if (!strcmp(var, "color.grep.filename"))
 -              color = opt->color_filename;
 -      else if (!strcmp(var, "color.grep.function"))
 -              color = opt->color_function;
 -      else if (!strcmp(var, "color.grep.linenumber"))
 -              color = opt->color_lineno;
 -      else if (!strcmp(var, "color.grep.column"))
 -              color = opt->color_columnno;
 -      else if (!strcmp(var, "color.grep.matchcontext"))
 -              color = opt->color_match_context;
 -      else if (!strcmp(var, "color.grep.matchselected"))
 -              color = opt->color_match_selected;
 -      else if (!strcmp(var, "color.grep.selected"))
 -              color = opt->color_selected;
 -      else if (!strcmp(var, "color.grep.separator"))
 -              color = opt->color_sep;
 -      else if (!strcmp(var, "color.grep.match")) {
 -              int rc = 0;
 -              if (!value)
 -                      return config_error_nonbool(var);
 -              rc |= color_parse(value, opt->color_match_context);
 -              rc |= color_parse(value, opt->color_match_selected);
 -              return rc;
 -      }
 -
 -      if (color) {
 +      if (!strcmp(var, "color.grep.match")) {
 +              if (grep_config("color.grep.matchcontext", value, cb) < 0)
 +                      return -1;
 +              if (grep_config("color.grep.matchselected", value, cb) < 0)
 +                      return -1;
 +      } else if (skip_prefix(var, "color.grep.", &slot)) {
 +              int i = LOOKUP_CONFIG(color_grep_slots, slot);
 +              char *color;
 +
 +              if (i < 0)
 +                      return -1;
 +              color = opt->colors[i];
                if (!value)
                        return config_error_nonbool(var);
                return color_parse(value, color);
  void grep_init(struct grep_opt *opt, const char *prefix)
  {
        struct grep_opt *def = &grep_defaults;
 +      int i;
  
        memset(opt, 0, sizeof(*opt));
        opt->prefix = prefix;
        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;
        opt->output = def->output;
  
 -      color_set(opt->color_context, def->color_context);
 -      color_set(opt->color_filename, def->color_filename);
 -      color_set(opt->color_function, def->color_function);
 -      color_set(opt->color_lineno, def->color_lineno);
 -      color_set(opt->color_columnno, def->color_columnno);
 -      color_set(opt->color_match_context, def->color_match_context);
 -      color_set(opt->color_match_selected, def->color_match_selected);
 -      color_set(opt->color_selected, def->color_selected);
 -      color_set(opt->color_sep, def->color_sep);
 +      for (i = 0; i < NR_GREP_COLORS; i++)
 +              color_set(opt->colors[i], def->colors[i]);
  }
  
  static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
@@@ -1094,12 -1107,12 +1101,12 @@@ static void output_sep(struct grep_opt 
        if (opt->null_following_name)
                opt->output(opt, "\0", 1);
        else
 -              output_color(opt, &sign, 1, opt->color_sep);
 +              output_color(opt, &sign, 1, opt->colors[GREP_COLOR_SEP]);
  }
  
  static void show_name(struct grep_opt *opt, const char *name)
  {
 -      output_color(opt, name, strlen(name), opt->color_filename);
 +      output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]);
        opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
  }
  
@@@ -1244,11 -1257,11 +1251,11 @@@ static int match_one_pattern(struct gre
        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");
                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);
  }
  
  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,
@@@ -1355,7 -1411,7 +1405,7 @@@ static int next_match(struct grep_opt *
  }
  
  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;
        } else if (opt->pre_context || opt->post_context || opt->funcbody) {
                if (opt->last_shown == 0) {
                        if (opt->show_hunk_mark) {
 -                              output_color(opt, "--", 2, opt->color_sep);
 +                              output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]);
                                opt->output(opt, "\n", 1);
                        }
                } else if (lno > opt->last_shown + 1) {
 -                      output_color(opt, "--", 2, opt->color_sep);
 +                      output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]);
                        opt->output(opt, "\n", 1);
                }
        }
        if (opt->heading && opt->last_shown == 0) {
 -              output_color(opt, name, strlen(name), opt->color_filename);
 +              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->color_filename);
 +              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->color_lineno);
 +              output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_LINENO]);
                output_sep(opt, sign);
        }
 -              output_color(opt, buf, strlen(buf), opt->color_columnno);
+       /*
+        * 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);
+       }
        if (opt->color) {
                regmatch_t match;
                enum grep_context ctx = GREP_CONTEXT_BODY;
                int eflags = 0;
  
                if (sign == ':')
 -                      match_color = opt->color_match_selected;
 +                      match_color = opt->colors[GREP_COLOR_MATCH_SELECTED];
                else
 -                      match_color = opt->color_match_context;
 +                      match_color = opt->colors[GREP_COLOR_MATCH_CONTEXT];
                if (sign == ':')
 -                      line_color = opt->color_selected;
 +                      line_color = opt->colors[GREP_COLOR_SELECTED];
                else if (sign == '-')
 -                      line_color = opt->color_context;
 +                      line_color = opt->colors[GREP_COLOR_CONTEXT];
                else if (sign == '=')
 -                      line_color = opt->color_function;
 +                      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)
@@@ -1495,7 -1562,7 +1556,7 @@@ static void show_funcname_line(struct g
                        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 -1627,7 +1621,7 @@@ static void show_pre_context(struct gre
  
                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 -1826,8 +1820,8 @@@ static int grep_source_1(struct grep_op
        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
                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)
                        if (binary_match_only) {
                                opt->output(opt, "Binary file ", 12);
                                output_color(opt, gs->name, strlen(gs->name),
 -                                           opt->color_filename);
 +                                           opt->colors[GREP_COLOR_FILENAME]);
                                opt->output(opt, " matches\n", 9);
                                return 1;
                        }
                                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;
                        /* 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:
                char buf[32];
                if (opt->pathname) {
                        output_color(opt, gs->name, strlen(gs->name),
 -                                   opt->color_filename);
 +                                   opt->colors[GREP_COLOR_FILENAME]);
                        output_sep(opt, ':');
                }
                xsnprintf(buf, sizeof(buf), "%u\n", count);
diff --combined grep.h
--- 1/grep.h
--- 2/grep.h
+++ b/grep.h
@@@ -62,18 -62,6 +62,19 @@@ enum grep_header_field 
        GREP_HEADER_FIELD_MAX
  };
  
 +enum grep_color {
 +      GREP_COLOR_CONTEXT,
 +      GREP_COLOR_FILENAME,
 +      GREP_COLOR_FUNCTION,
 +      GREP_COLOR_LINENO,
++      GREP_COLOR_COLUMNNO,
 +      GREP_COLOR_MATCH_CONTEXT,
 +      GREP_COLOR_MATCH_SELECTED,
 +      GREP_COLOR_SELECTED,
 +      GREP_COLOR_SEP,
 +      NR_GREP_COLORS
 +};
 +
  struct grep_pat {
        struct grep_pat *next;
        const char *origin;
@@@ -139,6 -127,7 +140,7 @@@ struct grep_opt 
        int prefix_length;
        regex_t regexp;
        int linenum;
+       int columnnum;
        int invert;
        int ignore_case;
        int status_only;
        int funcbody;
        int extended_regexp_option;
        int pattern_type_option;
 -      char color_context[COLOR_MAXLEN];
 -      char color_filename[COLOR_MAXLEN];
 -      char color_function[COLOR_MAXLEN];
 -      char color_lineno[COLOR_MAXLEN];
 -      char color_columnno[COLOR_MAXLEN];
 -      char color_match_context[COLOR_MAXLEN];
 -      char color_match_selected[COLOR_MAXLEN];
 -      char color_selected[COLOR_MAXLEN];
 -      char color_sep[COLOR_MAXLEN];
 +      char colors[NR_GREP_COLORS][COLOR_MAXLEN];
        unsigned pre_context;
        unsigned post_context;
        unsigned last_shown;