Merge branch 'jc/diff-stat'
authorJunio C Hamano <junkio@cox.net>
Sun, 1 Oct 2006 04:29:18 +0000 (21:29 -0700)
committerJunio C Hamano <junkio@cox.net>
Sun, 1 Oct 2006 04:29:18 +0000 (21:29 -0700)
* jc/diff-stat:
diff --stat: ensure at least one '-' for deletions, and one '+' for additions
diff --stat=width[,name-width]: allow custom diffstat output width.
diff --stat: color output.
diff --stat: allow custom diffstat output width.

1  2 
diff.c
diff.h
diff --combined diff.c
index 17f5a911d3b407bf9265087c845014bcb6be80c7,2df085f3c67afd96b5fcea5ade6f45c5e330198b..fb8243261cb3cc9165dbe990586d3865fad4ee61
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -20,13 -20,12 +20,13 @@@ static int diff_use_color_default
  
  static char diff_colors[][COLOR_MAXLEN] = {
        "\033[m",       /* reset */
 -      "",             /* normal */
 -      "\033[1m",      /* bold */
 -      "\033[36m",     /* cyan */
 -      "\033[31m",     /* red */
 -      "\033[32m",     /* green */
 -      "\033[33m"      /* yellow */
 +      "",             /* PLAIN (normal) */
 +      "\033[1m",      /* METAINFO (bold) */
 +      "\033[36m",     /* FRAGINFO (cyan) */
 +      "\033[31m",     /* OLD (red) */
 +      "\033[32m",     /* NEW (green) */
 +      "\033[33m",     /* COMMIT (yellow) */
 +      "\033[41m",     /* WHITESPACE (red background) */
  };
  
  static int parse_diff_color_slot(const char *var, int ofs)
@@@ -43,8 -42,6 +43,8 @@@
                return DIFF_FILE_NEW;
        if (!strcasecmp(var+ofs, "commit"))
                return DIFF_COMMIT;
 +      if (!strcasecmp(var+ofs, "whitespace"))
 +              return DIFF_WHITESPACE;
        die("bad config variable '%s'", var);
  }
  
@@@ -208,7 -205,7 +208,7 @@@ static void emit_rewrite_diff(const cha
        diff_populate_filespec(two, 0);
        lc_a = count_lines(one->data, one->size);
        lc_b = count_lines(two->data, two->size);
 -      printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
 +      printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b);
        print_line_count(lc_a);
        printf(" +");
        print_line_count(lc_b);
@@@ -386,89 -383,9 +386,89 @@@ const char *diff_get_color(int diff_use
        return "";
  }
  
 +static void emit_line(const char *set, const char *reset, const char *line, int len)
 +{
 +      if (len > 0 && line[len-1] == '\n')
 +              len--;
 +      fputs(set, stdout);
 +      fwrite(line, len, 1, stdout);
 +      puts(reset);
 +}
 +
 +static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
 +{
 +      int col0 = ecbdata->nparents;
 +      int last_tab_in_indent = -1;
 +      int last_space_in_indent = -1;
 +      int i;
 +      int tail = len;
 +      int need_highlight_leading_space = 0;
 +      const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
 +      const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
 +
 +      if (!*ws) {
 +              emit_line(set, reset, line, len);
 +              return;
 +      }
 +
 +      /* The line is a newly added line.  Does it have funny leading
 +       * whitespaces?  In indent, SP should never precede a TAB.
 +       */
 +      for (i = col0; i < len; i++) {
 +              if (line[i] == '\t') {
 +                      last_tab_in_indent = i;
 +                      if (0 <= last_space_in_indent)
 +                              need_highlight_leading_space = 1;
 +              }
 +              else if (line[i] == ' ')
 +                      last_space_in_indent = i;
 +              else
 +                      break;
 +      }
 +      fputs(set, stdout);
 +      fwrite(line, col0, 1, stdout);
 +      fputs(reset, stdout);
 +      if (((i == len) || line[i] == '\n') && i != col0) {
 +              /* The whole line was indent */
 +              emit_line(ws, reset, line + col0, len - col0);
 +              return;
 +      }
 +      i = col0;
 +      if (need_highlight_leading_space) {
 +              while (i < last_tab_in_indent) {
 +                      if (line[i] == ' ') {
 +                              fputs(ws, stdout);
 +                              putchar(' ');
 +                              fputs(reset, stdout);
 +                      }
 +                      else
 +                              putchar(line[i]);
 +                      i++;
 +              }
 +      }
 +      tail = len - 1;
 +      if (line[tail] == '\n' && i < tail)
 +              tail--;
 +      while (i < tail) {
 +              if (!isspace(line[tail]))
 +                      break;
 +              tail--;
 +      }
 +      if ((i < tail && line[tail + 1] != '\n')) {
 +              /* This has whitespace between tail+1..len */
 +              fputs(set, stdout);
 +              fwrite(line + i, tail - i + 1, 1, stdout);
 +              fputs(reset, stdout);
 +              emit_line(ws, reset, line + tail + 1, len - tail - 1);
 +      }
 +      else
 +              emit_line(set, reset, line + i, len - i);
 +}
 +
  static void fn_out_consume(void *priv, char *line, unsigned long len)
  {
        int i;
 +      int color;
        struct emit_callback *ecbdata = priv;
        const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
                ;
        if (2 <= i && i < len && line[i] == ' ') {
                ecbdata->nparents = i - 1;
 -              set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
 +              emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
 +                        reset, line, len);
 +              return;
        }
 -      else if (len < ecbdata->nparents)
 +
 +      if (len < ecbdata->nparents) {
                set = reset;
 -      else {
 -              int nparents = ecbdata->nparents;
 -              int color = DIFF_PLAIN;
 -              if (ecbdata->diff_words && nparents != 1)
 -                      /* fall back to normal diff */
 -                      free_diff_words_data(ecbdata);
 -              if (ecbdata->diff_words) {
 -                      if (line[0] == '-') {
 -                              diff_words_append(line, len,
 -                                              &ecbdata->diff_words->minus);
 -                              return;
 -                      } else if (line[0] == '+') {
 -                              diff_words_append(line, len,
 -                                              &ecbdata->diff_words->plus);
 -                              return;
 -                      }
 -                      if (ecbdata->diff_words->minus.text.size ||
 -                                      ecbdata->diff_words->plus.text.size)
 -                              diff_words_show(ecbdata->diff_words);
 -                      line++;
 -                      len--;
 -              } else
 -                      for (i = 0; i < nparents && len; i++) {
 -                              if (line[i] == '-')
 -                                      color = DIFF_FILE_OLD;
 -                              else if (line[i] == '+')
 -                                      color = DIFF_FILE_NEW;
 -                      }
 -              set = diff_get_color(ecbdata->color_diff, color);
 +              emit_line(reset, reset, line, len);
 +              return;
        }
 -      if (len > 0 && line[len-1] == '\n')
 +
 +      color = DIFF_PLAIN;
 +      if (ecbdata->diff_words && ecbdata->nparents != 1)
 +              /* fall back to normal diff */
 +              free_diff_words_data(ecbdata);
 +      if (ecbdata->diff_words) {
 +              if (line[0] == '-') {
 +                      diff_words_append(line, len,
 +                                        &ecbdata->diff_words->minus);
 +                      return;
 +              } else if (line[0] == '+') {
 +                      diff_words_append(line, len,
 +                                        &ecbdata->diff_words->plus);
 +                      return;
 +              }
 +              if (ecbdata->diff_words->minus.text.size ||
 +                  ecbdata->diff_words->plus.text.size)
 +                      diff_words_show(ecbdata->diff_words);
 +              line++;
                len--;
 -      fputs (set, stdout);
 -      fwrite (line, len, 1, stdout);
 -      puts (reset);
 +              emit_line(set, reset, line, len);
 +              return;
 +      }
 +      for (i = 0; i < ecbdata->nparents && len; i++) {
 +              if (line[i] == '-')
 +                      color = DIFF_FILE_OLD;
 +              else if (line[i] == '+')
 +                      color = DIFF_FILE_NEW;
 +      }
 +
 +      if (color != DIFF_FILE_NEW) {
 +              emit_line(diff_get_color(ecbdata->color_diff, color),
 +                        reset, line, len);
 +              return;
 +      }
 +      emit_add_line(reset, ecbdata, line, len);
  }
  
  static char *pprint_rename(const char *a, const char *b)
@@@ -635,21 -545,76 +635,76 @@@ static void diffstat_consume(void *priv
                x->deleted++;
  }
  
- static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
- static const char minuses[]= "----------------------------------------------------------------------";
  const char mime_boundary_leader[] = "------------";
  
- static void show_stats(struct diffstat_t* data)
+ static int scale_linear(int it, int width, int max_change)
+ {
+       /*
+        * make sure that at least one '-' is printed if there were deletions,
+        * and likewise for '+'.
+        */
+       if (max_change < 2)
+               return it;
+       return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+ }
+ static void show_name(const char *prefix, const char *name, int len,
+                     const char *reset, const char *set)
+ {
+       printf(" %s%s%-*s%s |", set, prefix, len, name, reset);
+ }
+ static void show_graph(char ch, int cnt, const char *set, const char *reset)
+ {
+       if (cnt <= 0)
+               return;
+       printf("%s", set);
+       while (cnt--)
+               putchar(ch);
+       printf("%s", reset);
+ }
+ static void show_stats(struct diffstat_t* data, struct diff_options *options)
  {
        int i, len, add, del, total, adds = 0, dels = 0;
-       int max, max_change = 0, max_len = 0;
+       int max_change = 0, max_len = 0;
        int total_files = data->nr;
+       int width, name_width;
+       const char *reset, *set, *add_c, *del_c;
  
        if (data->nr == 0)
                return;
  
+       width = options->stat_width ? options->stat_width : 80;
+       name_width = options->stat_name_width ? options->stat_name_width : 50;
+       /* Sanity: give at least 5 columns to the graph,
+        * but leave at least 10 columns for the name.
+        */
+       if (width < name_width + 15) {
+               if (name_width <= 25)
+                       width = name_width + 15;
+               else
+                       name_width = width - 15;
+       }
+       /* Find the longest filename and max number of changes */
+       reset = diff_get_color(options->color_diff, DIFF_RESET);
+       set = diff_get_color(options->color_diff, DIFF_PLAIN);
+       add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW);
+       del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD);
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
+               int change = file->added + file->deleted;
+               len = quote_c_style(file->name, NULL, NULL, 0);
+               if (len) {
+                       char *qname = xmalloc(len + 1);
+                       quote_c_style(file->name, qname, NULL, 0);
+                       free(file->name);
+                       file->name = qname;
+               }
  
                len = strlen(file->name);
                if (max_len < len)
  
                if (file->is_binary || file->is_unmerged)
                        continue;
-               if (max_change < file->added + file->deleted)
-                       max_change = file->added + file->deleted;
+               if (max_change < change)
+                       max_change = change;
        }
  
+       /* Compute the width of the graph part;
+        * 10 is for one blank at the beginning of the line plus
+        * " | count " between the name and the graph.
+        *
+        * From here on, name_width is the width of the name area,
+        * and width is the width of the graph area.
+        */
+       name_width = (name_width < max_len) ? name_width : max_len;
+       if (width < (name_width + 10) + max_change)
+               width = width - (name_width + 10);
+       else
+               width = max_change;
        for (i = 0; i < data->nr; i++) {
                const char *prefix = "";
                char *name = data->files[i]->name;
                int added = data->files[i]->added;
                int deleted = data->files[i]->deleted;
-               if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
-                       char *qname = xmalloc(len + 1);
-                       quote_c_style(name, qname, NULL, 0);
-                       free(name);
-                       data->files[i]->name = name = qname;
-               }
+               int name_len;
  
                /*
                 * "scale" the filename
                 */
-               len = strlen(name);
-               max = max_len;
-               if (max > 50)
-                       max = 50;
-               if (len > max) {
+               len = name_width;
+               name_len = strlen(name);
+               if (name_width < name_len) {
                        char *slash;
                        prefix = "...";
-                       max -= 3;
-                       name += len - max;
+                       len -= 3;
+                       name += name_len - len;
                        slash = strchr(name, '/');
                        if (slash)
                                name = slash;
                }
-               len = max;
-               /*
-                * scale the add/delete
-                */
-               max = max_change;
-               if (max + len > 70)
-                       max = 70 - len;
  
                if (data->files[i]->is_binary) {
-                       printf(" %s%-*s |  Bin\n", prefix, len, name);
+                       show_name(prefix, name, len, reset, set);
+                       printf("  Bin\n");
                        goto free_diffstat_file;
                }
                else if (data->files[i]->is_unmerged) {
-                       printf(" %s%-*s |  Unmerged\n", prefix, len, name);
+                       show_name(prefix, name, len, reset, set);
+                       printf("  Unmerged\n");
                        goto free_diffstat_file;
                }
                else if (!data->files[i]->is_renamed &&
                        goto free_diffstat_file;
                }
  
+               /*
+                * scale the add/delete
+                */
                add = added;
                del = deleted;
                total = add + del;
                adds += add;
                dels += del;
  
-               if (max_change > 0) {
-                       total = (total * max + max_change / 2) / max_change;
-                       add = (add * max + max_change / 2) / max_change;
-                       del = total - add;
+               if (width <= max_change) {
+                       add = scale_linear(add, width, max_change);
+                       del = scale_linear(del, width, max_change);
+                       total = add + del;
                }
-               printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
-                               len, name, added + deleted,
-                               add, pluses, del, minuses);
+               show_name(prefix, name, len, reset, set);
+               printf("%5d ", added + deleted);
+               show_graph('+', add, add_c, reset);
+               show_graph('-', del, del_c, reset);
+               putchar('\n');
        free_diffstat_file:
                free(data->files[i]->name);
                free(data->files[i]);
        }
        free(data->files);
-       printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
-                       total_files, adds, dels);
+       printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
+              set, total_files, adds, dels, reset);
  }
  
  struct checkdiff_t {
@@@ -1769,8 -1738,33 +1828,33 @@@ int diff_opt_parse(struct diff_options 
        else if (!strcmp(arg, "--patch-with-raw")) {
                options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
        }
-       else if (!strcmp(arg, "--stat"))
+       else if (!strncmp(arg, "--stat", 6)) {
+               char *end;
+               int width = options->stat_width;
+               int name_width = options->stat_name_width;
+               arg += 6;
+               end = (char *)arg;
+               switch (*arg) {
+               case '-':
+                       if (!strncmp(arg, "-width=", 7))
+                               width = strtoul(arg + 7, &end, 10);
+                       else if (!strncmp(arg, "-name-width=", 12))
+                               name_width = strtoul(arg + 12, &end, 10);
+                       break;
+               case '=':
+                       width = strtoul(arg+1, &end, 10);
+                       if (*end == ',')
+                               name_width = strtoul(end+1, &end, 10);
+               }
+               /* Important! This checks all the error cases! */
+               if (*end)
+                       return 0;
                options->output_format |= DIFF_FORMAT_DIFFSTAT;
+               options->stat_name_width = name_width;
+               options->stat_width = width;
+       }
        else if (!strcmp(arg, "--check"))
                options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
@@@ -2528,7 -2522,7 +2612,7 @@@ void diff_flush(struct diff_options *op
                        if (check_pair_status(p))
                                diff_flush_stat(p, options, &diffstat);
                }
-               show_stats(&diffstat);
+               show_stats(&diffstat, options);
                separator++;
        }
  
diff --combined diff.h
index 3435fe7b3f868ff1cefd5a20a3409993ce0a413b,e06d0f41885fa6d224cb1904e6ee06dbc3324e12..b48c9914e7e3802d17870bbc0fd68c454fded61c
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -69,6 -69,9 +69,9 @@@ struct diff_options 
        const char *stat_sep;
        long xdl_opts;
  
+       int stat_width;
+       int stat_name_width;
        int nr_paths;
        const char **paths;
        int *pathlens;
@@@ -86,7 -89,6 +89,7 @@@ enum color_diff 
        DIFF_FILE_OLD = 4,
        DIFF_FILE_NEW = 5,
        DIFF_COMMIT = 6,
 +      DIFF_WHITESPACE = 7,
  };
  const char *diff_get_color(int diff_use_color, enum color_diff ix);