Merge branch 'is/parsing-line-range'
authorJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:41 +0000 (15:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:41 +0000 (15:30 -0700)
Parsing of -L[<N>][,[<M>]] parameters "git blame" and "git log"
take has been tweaked.

* is/parsing-line-range:
log: prevent error if line range ends past end of file
blame: prevent error if range ends past end of file

1  2 
builtin/blame.c
line-log.c
diff --combined builtin/blame.c
index 468b17c30c3594f63c9c9b1eedc2f436b1f89337,e1359b1927274c07329ae20eb89fa1662742a1df..5c93d169dd431b666b25086ac4bb5303e8b26309
@@@ -7,9 -7,7 +7,9 @@@
  
  #include "cache.h"
  #include "config.h"
 +#include "color.h"
  #include "builtin.h"
 +#include "repository.h"
  #include "commit.h"
  #include "diff.h"
  #include "revision.h"
@@@ -24,9 -22,7 +24,9 @@@
  #include "line-log.h"
  #include "dir.h"
  #include "progress.h"
 +#include "object-store.h"
  #include "blame.h"
 +#include "string-list.h"
  
  static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
  
@@@ -50,8 -46,6 +50,8 @@@ static int xdl_opts
  static int abbrev = -1;
  static int no_whole_file_rename;
  static int show_progress;
 +static char repeated_meta_color[COLOR_MAXLEN];
 +static int coloring_mode;
  
  static struct date_mode blame_date_mode = { DATE_ISO8601 };
  static size_t blame_date_width;
@@@ -322,12 -316,10 +322,12 @@@ static const char *format_time(timestam
  #define OUTPUT_PORCELAIN      010
  #define OUTPUT_SHOW_NAME      020
  #define OUTPUT_SHOW_NUMBER    040
 -#define OUTPUT_SHOW_SCORE      0100
 -#define OUTPUT_NO_AUTHOR       0200
 +#define OUTPUT_SHOW_SCORE     0100
 +#define OUTPUT_NO_AUTHOR      0200
  #define OUTPUT_SHOW_EMAIL     0400
 -#define OUTPUT_LINE_PORCELAIN 01000
 +#define OUTPUT_LINE_PORCELAIN 01000
 +#define OUTPUT_COLOR_LINE     02000
 +#define OUTPUT_SHOW_AGE_WITH_COLOR    04000
  
  static void emit_porcelain_details(struct blame_origin *suspect, int repeat)
  {
@@@ -375,64 -367,6 +375,64 @@@ static void emit_porcelain(struct blame
                putchar('\n');
  }
  
 +static struct color_field {
 +      timestamp_t hop;
 +      char col[COLOR_MAXLEN];
 +} *colorfield;
 +static int colorfield_nr, colorfield_alloc;
 +
 +static void parse_color_fields(const char *s)
 +{
 +      struct string_list l = STRING_LIST_INIT_DUP;
 +      struct string_list_item *item;
 +      enum { EXPECT_DATE, EXPECT_COLOR } next = EXPECT_COLOR;
 +
 +      colorfield_nr = 0;
 +
 +      /* Ideally this would be stripped and split at the same time? */
 +      string_list_split(&l, s, ',', -1);
 +      ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);
 +
 +      for_each_string_list_item(item, &l) {
 +              switch (next) {
 +              case EXPECT_DATE:
 +                      colorfield[colorfield_nr].hop = approxidate(item->string);
 +                      next = EXPECT_COLOR;
 +                      colorfield_nr++;
 +                      ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);
 +                      break;
 +              case EXPECT_COLOR:
 +                      if (color_parse(item->string, colorfield[colorfield_nr].col))
 +                              die(_("expecting a color: %s"), item->string);
 +                      next = EXPECT_DATE;
 +                      break;
 +              }
 +      }
 +
 +      if (next == EXPECT_COLOR)
 +              die (_("must end with a color"));
 +
 +      colorfield[colorfield_nr].hop = TIME_MAX;
 +      string_list_clear(&l, 0);
 +}
 +
 +static void setup_default_color_by_age(void)
 +{
 +      parse_color_fields("blue,12 month ago,white,1 month ago,red");
 +}
 +
 +static void determine_line_heat(struct blame_entry *ent, const char **dest_color)
 +{
 +      int i = 0;
 +      struct commit_info ci;
 +      get_commit_info(ent->suspect->commit, &ci, 1);
 +
 +      while (i < colorfield_nr && ci.author_time > colorfield[i].hop)
 +              i++;
 +
 +      *dest_color = colorfield[i].col;
 +}
 +
  static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt)
  {
        int cnt;
        struct commit_info ci;
        char hex[GIT_MAX_HEXSZ + 1];
        int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP);
 +      const char *default_color = NULL, *color = NULL, *reset = NULL;
  
        get_commit_info(suspect->commit, &ci, 1);
        oid_to_hex_r(hex, &suspect->commit->object.oid);
  
        cp = blame_nth_line(sb, ent->lno);
 +
 +      if (opt & OUTPUT_SHOW_AGE_WITH_COLOR) {
 +              determine_line_heat(ent, &default_color);
 +              color = default_color;
 +              reset = GIT_COLOR_RESET;
 +      }
 +
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
                char ch;
                int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;
  
 +              if (opt & OUTPUT_COLOR_LINE) {
 +                      if (cnt > 0) {
 +                              color = repeated_meta_color;
 +                              reset = GIT_COLOR_RESET;
 +                      } else  {
 +                              color = default_color ? default_color : NULL;
 +                              reset = default_color ? GIT_COLOR_RESET : NULL;
 +                      }
 +              }
 +              if (color)
 +                      fputs(color, stdout);
 +
                if (suspect->commit->object.flags & UNINTERESTING) {
                        if (blank_boundary)
                                memset(hex, ' ', length);
                        printf(" %*d) ",
                               max_digits, ent->lno + 1 + cnt);
                }
 +              if (reset)
 +                      fputs(reset, stdout);
                do {
                        ch = *cp++;
                        putchar(ch);
@@@ -545,7 -457,7 +545,7 @@@ static void output(struct blame_scorebo
                        struct commit *commit = ent->suspect->commit;
                        if (commit->object.flags & MORE_THAN_ONE_PATH)
                                continue;
 -                      for (suspect = commit->util; suspect; suspect = suspect->next) {
 +                      for (suspect = get_blame_suspects(commit); suspect; suspect = suspect->next) {
                                if (suspect->guilty && count++) {
                                        commit->object.flags |= MORE_THAN_ONE_PATH;
                                        break;
@@@ -578,7 -490,7 +578,7 @@@ static int read_ancestry(const char *gr
                /* The format is just "Commit Parent1 Parent2 ...\n" */
                struct commit_graft *graft = read_graft_line(&buf);
                if (graft)
 -                      register_commit_graft(graft, 0);
 +                      register_commit_graft(the_repository, graft, 0);
        }
        fclose(fp);
        strbuf_release(&buf);
  
  static int update_auto_abbrev(int auto_abbrev, struct blame_origin *suspect)
  {
 -      const char *uniq = find_unique_abbrev(suspect->commit->object.oid.hash,
 +      const char *uniq = find_unique_abbrev(&suspect->commit->object.oid,
                                              auto_abbrev);
        int len = strlen(uniq);
        if (auto_abbrev < len)
@@@ -695,30 -607,6 +695,30 @@@ static int git_blame_config(const char 
                parse_date_format(value, &blame_date_mode);
                return 0;
        }
 +      if (!strcmp(var, "color.blame.repeatedlines")) {
 +              if (color_parse_mem(value, strlen(value), repeated_meta_color))
 +                      warning(_("invalid color '%s' in color.blame.repeatedLines"),
 +                              value);
 +              return 0;
 +      }
 +      if (!strcmp(var, "color.blame.highlightrecent")) {
 +              parse_color_fields(value);
 +              return 0;
 +      }
 +
 +      if (!strcmp(var, "blame.coloring")) {
 +              if (!strcmp(value, "repeatedLines")) {
 +                      coloring_mode |= OUTPUT_COLOR_LINE;
 +              } else if (!strcmp(value, "highlightRecent")) {
 +                      coloring_mode |= OUTPUT_SHOW_AGE_WITH_COLOR;
 +              } else if (!strcmp(value, "none")) {
 +                      coloring_mode &= ~(OUTPUT_COLOR_LINE |
 +                                          OUTPUT_SHOW_AGE_WITH_COLOR);
 +              } else {
 +                      warning(_("invalid value for blame.coloring"));
 +                      return 0;
 +              }
 +      }
  
        if (git_diff_heuristic_config(var, value, cb) < 0)
                return -1;
@@@ -767,7 -655,7 +767,7 @@@ static int is_a_rev(const char *name
  
        if (get_oid(name, &oid))
                return 0;
 -      return OBJ_NONE < sha1_object_info(oid.hash, NULL);
 +      return OBJ_NONE < oid_object_info(the_repository, &oid, NULL);
  }
  
  int cmd_blame(int argc, const char **argv, const char *prefix)
                OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
                OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
                OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
 +              OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE),
 +              OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR),
  
                /*
                 * The following two options are parsed by parse_revision_opt()
        unsigned int range_i;
        long anchor;
  
 +      setup_default_color_by_age();
        git_config(git_blame_config, &output_option);
        init_revisions(&revs, NULL);
        revs.date_mode = blame_date_mode;
        for (;;) {
                switch (parse_options_step(&ctx, options, blame_opt_usage)) {
                case PARSE_OPT_HELP:
 +              case PARSE_OPT_ERROR:
                        exit(129);
                case PARSE_OPT_DONE:
                        if (ctx.argv[0])
@@@ -1002,13 -886,13 +1002,13 @@@ parse_done
                                    nth_line_cb, &sb, lno, anchor,
                                    &bottom, &top, sb.path))
                        usage(blame_usage);
-               if (lno < top || ((lno || bottom) && lno < bottom))
+               if ((!lno && (top || bottom)) || lno < bottom)
                        die(Q_("file %s has only %lu line",
                               "file %s has only %lu lines",
                               lno), path, lno);
                if (bottom < 1)
                        bottom = 1;
-               if (top < 1)
+               if (top < 1 || lno < top)
                        top = lno;
                bottom--;
                range_set_append_unsafe(&ranges, bottom, top);
  
        blame_coalesce(&sb);
  
 -      if (!(output_option & OUTPUT_PORCELAIN))
 +      if (!(output_option & (OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR)))
 +              output_option |= coloring_mode;
 +
 +      if (!(output_option & OUTPUT_PORCELAIN)) {
                find_alignment(&sb, &output_option);
 +              if (!*repeated_meta_color &&
 +                  (output_option & OUTPUT_COLOR_LINE))
 +                      xsnprintf(repeated_meta_color,
 +                                sizeof(repeated_meta_color),
 +                                "%s", GIT_COLOR_CYAN);
 +      }
 +      if (output_option & OUTPUT_ANNOTATE_COMPAT)
 +              output_option &= ~(OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR);
  
        output(&sb, output_option);
        free((void *)sb.final_buf);
diff --combined line-log.c
index fa9cfd5bdbb5dfe4a1ca5c7f7711435caf091805,8ad074e58d104517f4ccaad484922431034109cb..e5af5d0e455abd1361e696783e468a074a49fc2f
@@@ -501,7 -501,8 +501,7 @@@ static void fill_blob_sha1(struct commi
        unsigned mode;
        struct object_id oid;
  
 -      if (get_tree_entry(commit->object.oid.hash, spec->path,
 -                         oid.hash, &mode))
 +      if (get_tree_entry(&commit->object.oid, spec->path, &oid, &mode))
                die("There is no path %s in the commit", spec->path);
        fill_filespec(spec, &oid, 1, mode);
  
@@@ -598,11 -599,11 +598,11 @@@ parse_lines(struct commit *commit, cons
                                    lines, anchor, &begin, &end,
                                    full_name))
                        die("malformed -L argument '%s'", range_part);
-               if (lines < end || ((lines || begin) && lines < begin))
+               if ((!lines && (begin || end)) || lines < begin)
                        die("file %s has only %lu lines", name_part, lines);
                if (begin < 1)
                        begin = 1;
-               if (end < 1)
+               if (end < 1 || lines < end)
                        end = lines;
                begin--;
                line_log_data_insert(&ranges, full_name, begin, end);
@@@ -816,8 -817,8 +816,8 @@@ static void queue_diffs(struct line_log
        assert(commit);
  
        DIFF_QUEUE_CLEAR(&diff_queued_diff);
 -      diff_tree_oid(parent ? &parent->tree->object.oid : NULL,
 -                    &commit->tree->object.oid, "", opt);
 +      diff_tree_oid(parent ? get_commit_tree_oid(parent) : NULL,
 +                    get_commit_tree_oid(commit), "", opt);
        if (opt->detect_rename) {
                filter_diffs_for_paths(range, 1);
                if (diff_might_be_rename())