Merge branch 'ap/maint-diff-rename-avoid-overlap'
authorJunio C Hamano <gitster@pobox.com>
Mon, 25 Mar 2013 21:00:37 +0000 (14:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 25 Mar 2013 21:00:37 +0000 (14:00 -0700)
The logic used by "git diff -M --stat" to shorten the names of
files before and after a rename did not work correctly when the
common prefix and suffix between the two filenames overlapped.

* ap/maint-diff-rename-avoid-overlap:
tests: make sure rename pretty print works
diff: prevent pprint_rename from underrunning input
diff: Fix rename pretty-print when suffix and prefix overlap

1  2 
diff.c
diff --combined diff.c
index 052974eb9729cca92c9e86be6e71f46052bcb788,d641c2676f357e2901c11f83cff4e7176dd0f544..db952a5bc71ac967cb045bada8c71f5fda218ca2
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -15,7 -15,6 +15,7 @@@
  #include "sigchain.h"
  #include "submodule.h"
  #include "ll-merge.h"
 +#include "string-list.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
  static int diff_detect_rename_default;
  static int diff_rename_limit_default = 400;
  static int diff_suppress_blank_empty;
 -int diff_use_color_default = -1;
 +static int diff_use_color_default = -1;
 +static int diff_context_default = 3;
  static const char *diff_word_regex_cfg;
  static const char *external_diff_cmd_cfg;
  int diff_auto_refresh_index = 1;
  static int diff_mnemonic_prefix;
  static int diff_no_prefix;
 +static int diff_stat_graph_width;
  static int diff_dirstat_permille_default = 30;
  static struct diff_options default_diff_options;
 +static long diff_algorithm;
  
  static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@@ -71,30 -67,26 +71,30 @@@ static int parse_diff_color_slot(const 
        return -1;
  }
  
 -static int parse_dirstat_params(struct diff_options *options, const char *params,
 +static int parse_dirstat_params(struct diff_options *options, const char *params_string,
                                struct strbuf *errmsg)
  {
 -      const char *p = params;
 -      int p_len, ret = 0;
 +      char *params_copy = xstrdup(params_string);
 +      struct string_list params = STRING_LIST_INIT_NODUP;
 +      int ret = 0;
 +      int i;
  
 -      while (*p) {
 -              p_len = strchrnul(p, ',') - p;
 -              if (!memcmp(p, "changes", p_len)) {
 +      if (*params_copy)
 +              string_list_split_in_place(&params, params_copy, ',', -1);
 +      for (i = 0; i < params.nr; i++) {
 +              const char *p = params.items[i].string;
 +              if (!strcmp(p, "changes")) {
                        DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
                        DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
 -              } else if (!memcmp(p, "lines", p_len)) {
 +              } else if (!strcmp(p, "lines")) {
                        DIFF_OPT_SET(options, DIRSTAT_BY_LINE);
                        DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
 -              } else if (!memcmp(p, "files", p_len)) {
 +              } else if (!strcmp(p, "files")) {
                        DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
                        DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
 -              } else if (!memcmp(p, "noncumulative", p_len)) {
 +              } else if (!strcmp(p, "noncumulative")) {
                        DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
 -              } else if (!memcmp(p, "cumulative", p_len)) {
 +              } else if (!strcmp(p, "cumulative")) {
                        DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
                } else if (isdigit(*p)) {
                        char *end;
                                while (isdigit(*++end))
                                        ; /* nothing */
                        }
 -                      if (end - p == p_len)
 +                      if (!*end)
                                options->dirstat_permille = permille;
                        else {
 -                              strbuf_addf(errmsg, _("  Failed to parse dirstat cut-off percentage '%.*s'\n"),
 -                                          p_len, p);
 +                              strbuf_addf(errmsg, _("  Failed to parse dirstat cut-off percentage '%s'\n"),
 +                                          p);
                                ret++;
                        }
                } else {
 -                      strbuf_addf(errmsg, _("  Unknown dirstat parameter '%.*s'\n"),
 -                                  p_len, p);
 +                      strbuf_addf(errmsg, _("  Unknown dirstat parameter '%s'\n"), p);
                        ret++;
                }
  
 -              p += p_len;
 -
 -              if (*p)
 -                      p++; /* more parameters, swallow separator */
        }
 +      string_list_clear(&params, 0);
 +      free(params_copy);
        return ret;
  }
  
 +static int parse_submodule_params(struct diff_options *options, const char *value)
 +{
 +      if (!strcmp(value, "log"))
 +              DIFF_OPT_SET(options, SUBMODULE_LOG);
 +      else if (!strcmp(value, "short"))
 +              DIFF_OPT_CLR(options, SUBMODULE_LOG);
 +      else
 +              return -1;
 +      return 0;
 +}
 +
  static int git_config_rename(const char *var, const char *value)
  {
        if (!value)
        return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0;
  }
  
 +long parse_algorithm_value(const char *value)
 +{
 +      if (!value)
 +              return -1;
 +      else if (!strcasecmp(value, "myers") || !strcasecmp(value, "default"))
 +              return 0;
 +      else if (!strcasecmp(value, "minimal"))
 +              return XDF_NEED_MINIMAL;
 +      else if (!strcasecmp(value, "patience"))
 +              return XDF_PATIENCE_DIFF;
 +      else if (!strcasecmp(value, "histogram"))
 +              return XDF_HISTOGRAM_DIFF;
 +      return -1;
 +}
 +
  /*
   * These are to give UI layer defaults.
   * The core-level commands such as git-diff-files should
  int git_diff_ui_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
 -              diff_use_color_default = git_config_colorbool(var, value, -1);
 +              diff_use_color_default = git_config_colorbool(var, value);
 +              return 0;
 +      }
 +      if (!strcmp(var, "diff.context")) {
 +              diff_context_default = git_config_int(var, value);
 +              if (diff_context_default < 0)
 +                      return -1;
                return 0;
        }
        if (!strcmp(var, "diff.renames")) {
                diff_no_prefix = git_config_bool(var, value);
                return 0;
        }
 +      if (!strcmp(var, "diff.statgraphwidth")) {
 +              diff_stat_graph_width = git_config_int(var, value);
 +              return 0;
 +      }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
        if (!strcmp(var, "diff.wordregex"))
        if (!strcmp(var, "diff.ignoresubmodules"))
                handle_ignore_submodules_arg(&default_diff_options, value);
  
 +      if (!strcmp(var, "diff.submodule")) {
 +              if (parse_submodule_params(&default_diff_options, value))
 +                      warning(_("Unknown value for 'diff.submodule' config variable: '%s'"),
 +                              value);
 +              return 0;
 +      }
 +
 +      if (!strcmp(var, "diff.algorithm")) {
 +              diff_algorithm = parse_algorithm_value(value);
 +              if (diff_algorithm < 0)
 +                      return -1;
 +              return 0;
 +      }
 +
 +      if (git_color_config(var, value, cb) < 0)
 +              return -1;
 +
        return git_diff_basic_config(var, value, cb);
  }
  
@@@ -232,8 -174,11 +232,8 @@@ int git_diff_basic_config(const char *v
                return 0;
        }
  
 -      switch (userdiff_config(var, value)) {
 -              case 0: break;
 -              case -1: return -1;
 -              default: return 0;
 -      }
 +      if (userdiff_config(var, value) < 0)
 +              return -1;
  
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
        if (!prefixcmp(var, "submodule."))
                return parse_submodule_config_option(var, value);
  
 -      return git_color_default_config(var, value, cb);
 +      return git_default_config(var, value, cb);
  }
  
  static char *quote_two(const char *one, const char *two)
@@@ -425,7 -370,12 +425,7 @@@ static void emit_line_0(struct diff_opt
        int nofirst;
        FILE *file = o->file;
  
 -      if (o->output_prefix) {
 -              struct strbuf *msg = NULL;
 -              msg = o->output_prefix(o, o->output_prefix_data);
 -              assert(msg);
 -              fwrite(msg->buf, msg->len, 1, file);
 -      }
 +      fputs(diff_line_prefix(o), file);
  
        if (len == 0) {
                has_trailing_newline = (first == '\n');
@@@ -619,7 -569,6 +619,7 @@@ static void emit_rewrite_lines(struct e
        if (!endp) {
                const char *plain = diff_get_color(ecb->color_diff,
                                                   DIFF_PLAIN);
 +              putc('\n', ecb->opt->file);
                emit_line_0(ecb->opt, plain, reset, '\\',
                            nneof, strlen(nneof));
        }
@@@ -634,16 -583,23 +634,16 @@@ static void emit_rewrite_diff(const cha
                              struct diff_options *o)
  {
        int lc_a, lc_b;
 -      int color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
        const char *name_a_tab, *name_b_tab;
 -      const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
 -      const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
 -      const char *reset = diff_get_color(color_diff, DIFF_RESET);
 +      const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
 +      const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
 +      const char *reset = diff_get_color(o->use_color, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        char *data_one, *data_two;
        size_t size_one, size_two;
        struct emit_callback ecbdata;
 -      char *line_prefix = "";
 -      struct strbuf *msgbuf;
 -
 -      if (o && o->output_prefix) {
 -              msgbuf = o->output_prefix(o, o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(o);
  
        if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
                a_prefix = o->b_prefix;
        size_two = fill_textconv(textconv_two, two, &data_two);
  
        memset(&ecbdata, 0, sizeof(ecbdata));
 -      ecbdata.color_diff = color_diff;
 +      ecbdata.color_diff = want_color(o->use_color);
        ecbdata.found_changesp = &o->found_changes;
        ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
        ecbdata.opt = o;
@@@ -839,14 -795,18 +839,14 @@@ static void fn_out_diff_words_aux(void 
        int minus_first, minus_len, plus_first, plus_len;
        const char *minus_begin, *minus_end, *plus_begin, *plus_end;
        struct diff_options *opt = diff_words->opt;
 -      struct strbuf *msgbuf;
 -      char *line_prefix = "";
 +      const char *line_prefix;
  
        if (line[0] != '@' || parse_hunk_header(line, len,
                        &minus_first, &minus_len, &plus_first, &plus_len))
                return;
  
        assert(opt);
 -      if (opt->output_prefix) {
 -              msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      line_prefix = diff_line_prefix(opt);
  
        /* POSIX requires that first be decremented by one if len == 0... */
        if (minus_len) {
@@@ -970,10 -930,14 +970,10 @@@ static void diff_words_show(struct diff
        struct diff_words_style *style = diff_words->style;
  
        struct diff_options *opt = diff_words->opt;
 -      struct strbuf *msgbuf;
 -      char *line_prefix = "";
 +      const char *line_prefix;
  
        assert(opt);
 -      if (opt->output_prefix) {
 -              msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      line_prefix = diff_line_prefix(opt);
  
        /* special case: only removal */
        if (!diff_words->plus.text.size) {
@@@ -1021,74 -985,10 +1021,74 @@@ static void diff_words_flush(struct emi
                diff_words_show(ecbdata->diff_words);
  }
  
 +static void diff_filespec_load_driver(struct diff_filespec *one)
 +{
 +      /* Use already-loaded driver */
 +      if (one->driver)
 +              return;
 +
 +      if (S_ISREG(one->mode))
 +              one->driver = userdiff_find_by_path(one->path);
 +
 +      /* Fallback to default settings */
 +      if (!one->driver)
 +              one->driver = userdiff_find_by_name("default");
 +}
 +
 +static const char *userdiff_word_regex(struct diff_filespec *one)
 +{
 +      diff_filespec_load_driver(one);
 +      return one->driver->word_regex;
 +}
 +
 +static void init_diff_words_data(struct emit_callback *ecbdata,
 +                               struct diff_options *orig_opts,
 +                               struct diff_filespec *one,
 +                               struct diff_filespec *two)
 +{
 +      int i;
 +      struct diff_options *o = xmalloc(sizeof(struct diff_options));
 +      memcpy(o, orig_opts, sizeof(struct diff_options));
 +
 +      ecbdata->diff_words =
 +              xcalloc(1, sizeof(struct diff_words_data));
 +      ecbdata->diff_words->type = o->word_diff;
 +      ecbdata->diff_words->opt = o;
 +      if (!o->word_regex)
 +              o->word_regex = userdiff_word_regex(one);
 +      if (!o->word_regex)
 +              o->word_regex = userdiff_word_regex(two);
 +      if (!o->word_regex)
 +              o->word_regex = diff_word_regex_cfg;
 +      if (o->word_regex) {
 +              ecbdata->diff_words->word_regex = (regex_t *)
 +                      xmalloc(sizeof(regex_t));
 +              if (regcomp(ecbdata->diff_words->word_regex,
 +                          o->word_regex,
 +                          REG_EXTENDED | REG_NEWLINE))
 +                      die ("Invalid regular expression: %s",
 +                           o->word_regex);
 +      }
 +      for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
 +              if (o->word_diff == diff_words_styles[i].type) {
 +                      ecbdata->diff_words->style =
 +                              &diff_words_styles[i];
 +                      break;
 +              }
 +      }
 +      if (want_color(o->use_color)) {
 +              struct diff_words_style *st = ecbdata->diff_words->style;
 +              st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
 +              st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
 +              st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
 +      }
 +}
 +
  static void free_diff_words_data(struct emit_callback *ecbdata)
  {
        if (ecbdata->diff_words) {
                diff_words_flush(ecbdata);
 +              free (ecbdata->diff_words->opt);
                free (ecbdata->diff_words->minus.text.ptr);
                free (ecbdata->diff_words->minus.orig);
                free (ecbdata->diff_words->plus.text.ptr);
  
  const char *diff_get_color(int diff_use_color, enum color_diff ix)
  {
 -      if (diff_use_color)
 +      if (want_color(diff_use_color))
                return diff_colors[ix];
        return "";
  }
  
 +const char *diff_line_prefix(struct diff_options *opt)
 +{
 +      struct strbuf *msgbuf;
 +      if (!opt->output_prefix)
 +              return "";
 +
 +      msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
 +      return msgbuf->buf;
 +}
 +
  static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
  {
        const char *cp;
@@@ -1159,7 -1049,13 +1159,7 @@@ static void fn_out_consume(void *priv, 
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
        struct diff_options *o = ecbdata->opt;
 -      char *line_prefix = "";
 -      struct strbuf *msgbuf;
 -
 -      if (o && o->output_prefix) {
 -              msgbuf = o->output_prefix(o, o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(o);
  
        if (ecbdata->header) {
                fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf);
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->plus);
                        return;
 +              } else if (!prefixcmp(line, "\\ ")) {
 +                      /*
 +                       * Eat the "no newline at eof" marker as if we
 +                       * saw a "+" or "-" line with nothing on it,
 +                       * and return without diff_words_flush() to
 +                       * defer processing. If this is the end of
 +                       * preimage, more "+" lines may come after it.
 +                       */
 +                      return;
                }
                diff_words_flush(ecbdata);
                if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
@@@ -1264,6 -1151,7 +1264,7 @@@ static char *pprint_rename(const char *
        const char *new = b;
        struct strbuf name = STRBUF_INIT;
        int pfx_length, sfx_length;
+       int pfx_adjust_for_slash;
        int len_a = strlen(a);
        int len_b = strlen(b);
        int a_midlen, b_midlen;
        old = a + len_a;
        new = b + len_b;
        sfx_length = 0;
-       while (a <= old && b <= new && *old == *new) {
+       /*
+        * If there is a common prefix, it must end in a slash.  In
+        * that case we let this loop run 1 into the prefix to see the
+        * same slash.
+        *
+        * If there is no common prefix, we cannot do this as it would
+        * underrun the input strings.
+        */
+       pfx_adjust_for_slash = (pfx_length ? 1 : 0);
+       while (a + pfx_length - pfx_adjust_for_slash <= old &&
+              b + pfx_length - pfx_adjust_for_slash <= new &&
+              *old == *new) {
                if (*old == '/')
                        sfx_length = len_a - (old - a);
                old--;
@@@ -1335,7 -1234,6 +1347,7 @@@ struct diffstat_t 
                unsigned is_unmerged:1;
                unsigned is_binary:1;
                unsigned is_renamed:1;
 +              unsigned is_interesting:1;
                uintmax_t added, deleted;
        } **files;
  };
@@@ -1379,15 -1277,13 +1391,15 @@@ const char mime_boundary_leader[] = "--
  
  static int scale_linear(int it, int width, int max_change)
  {
 +      if (!it)
 +              return 0;
        /*
 -       * make sure that at least one '-' is printed if there were deletions,
 -       * and likewise for '+'.
 +       * make sure that at least one '-' or '+' is printed if
 +       * there is any change to this path. The easiest way is to
 +       * scale linearly as if the alloted width is one column shorter
 +       * than it is, and then add 1 to the result.
         */
 -      if (max_change < 2)
 -              return it;
 -      return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
 +      return 1 + (it * (width - 1) / max_change);
  }
  
  static void show_name(FILE *file,
@@@ -1427,204 -1323,76 +1439,204 @@@ static void fill_print_name(struct diff
        file->print_name = pname;
  }
  
 +int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      int ret;
 +
 +      if (!files) {
 +              assert(insertions == 0 && deletions == 0);
 +              return fprintf(fp, "%s\n", " 0 files changed");
 +      }
 +
 +      strbuf_addf(&sb,
 +                  (files == 1) ? " %d file changed" : " %d files changed",
 +                  files);
 +
 +      /*
 +       * For binary diff, the caller may want to print "x files
 +       * changed" with insertions == 0 && deletions == 0.
 +       *
 +       * Not omitting "0 insertions(+), 0 deletions(-)" in this case
 +       * is probably less confusing (i.e skip over "2 files changed
 +       * but nothing about added/removed lines? Is this a bug in Git?").
 +       */
 +      if (insertions || deletions == 0) {
 +              /*
 +               * TRANSLATORS: "+" in (+) is a line addition marker;
 +               * do not translate it.
 +               */
 +              strbuf_addf(&sb,
 +                          (insertions == 1) ? ", %d insertion(+)" : ", %d insertions(+)",
 +                          insertions);
 +      }
 +
 +      if (deletions || insertions == 0) {
 +              /*
 +               * TRANSLATORS: "-" in (-) is a line removal marker;
 +               * do not translate it.
 +               */
 +              strbuf_addf(&sb,
 +                          (deletions == 1) ? ", %d deletion(-)" : ", %d deletions(-)",
 +                          deletions);
 +      }
 +      strbuf_addch(&sb, '\n');
 +      ret = fputs(sb.buf, fp);
 +      strbuf_release(&sb);
 +      return ret;
 +}
 +
  static void show_stats(struct diffstat_t *data, struct diff_options *options)
  {
        int i, len, add, del, adds = 0, dels = 0;
        uintmax_t max_change = 0, max_len = 0;
 -      int total_files = data->nr;
 -      int width, name_width;
 +      int total_files = data->nr, count;
 +      int width, name_width, graph_width, number_width = 0, bin_width = 0;
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
 -      struct strbuf *msg = NULL;
 +      int extra_shown = 0;
  
        if (data->nr == 0)
                return;
  
 -      if (options->output_prefix) {
 -              msg = options->output_prefix(options, options->output_prefix_data);
 -              line_prefix = msg->buf;
 -      }
 -
 -      width = options->stat_width ? options->stat_width : 80;
 -      name_width = options->stat_name_width ? options->stat_name_width : 50;
 +      line_prefix = diff_line_prefix(options);
 +      count = options->stat_count ? options->stat_count : data->nr;
  
 -      /* Sanity: give at least 5 columns to the graph,
 -       * but leave at least 10 columns for the name.
 -       */
 -      if (width < 25)
 -              width = 25;
 -      if (name_width < 10)
 -              name_width = 10;
 -      else if (width < name_width + 15)
 -              name_width = width - 15;
 -
 -      /* Find the longest filename and max number of changes */
        reset = diff_get_color_opt(options, DIFF_RESET);
        add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
        del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
  
 -      for (i = 0; i < data->nr; i++) {
 +      /*
 +       * Find the longest filename and max number of changes
 +       */
 +      for (i = 0; (i < count) && (i < data->nr); i++) {
                struct diffstat_file *file = data->files[i];
                uintmax_t change = file->added + file->deleted;
 +
 +              if (!file->is_interesting && (change == 0)) {
 +                      count++; /* not shown == room for one more */
 +                      continue;
 +              }
                fill_print_name(file);
                len = strlen(file->print_name);
                if (max_len < len)
                        max_len = len;
  
 -              if (file->is_binary || file->is_unmerged)
 +              if (file->is_unmerged) {
 +                      /* "Unmerged" is 8 characters */
 +                      bin_width = bin_width < 8 ? 8 : bin_width;
 +                      continue;
 +              }
 +              if (file->is_binary) {
 +                      /* "Bin XXX -> YYY bytes" */
 +                      int w = 14 + decimal_width(file->added)
 +                              + decimal_width(file->deleted);
 +                      bin_width = bin_width < w ? w : bin_width;
 +                      /* Display change counts aligned with "Bin" */
 +                      number_width = 3;
                        continue;
 +              }
 +
                if (max_change < change)
                        max_change = change;
        }
 +      count = i; /* where we can stop scanning in data->files[] */
  
 -      /* 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.
 +      /*
 +       * We have width = stat_width or term_columns() columns total.
 +       * We want a maximum of min(max_len, stat_name_width) for the name part.
 +       * We want a maximum of min(max_change, stat_graph_width) for the +- part.
 +       * We also need 1 for " " and 4 + decimal_width(max_change)
 +       * for " | NNNN " and one the empty column at the end, altogether
 +       * 6 + decimal_width(max_change).
 +       *
 +       * If there's not enough space, we will use the smaller of
 +       * stat_name_width (if set) and 5/8*width for the filename,
 +       * and the rest for constant elements + graph part, but no more
 +       * than stat_graph_width for the graph part.
 +       * (5/8 gives 50 for filename and 30 for the constant parts + graph
 +       * for the standard terminal size).
 +       *
 +       * In other words: stat_width limits the maximum width, and
 +       * stat_name_width fixes the maximum width of the filename,
 +       * and is also used to divide available columns if there
 +       * aren't enough.
         *
 -       * From here on, name_width is the width of the name area,
 -       * and width is the width of the graph area.
 +       * Binary files are displayed with "Bin XXX -> YYY bytes"
 +       * instead of the change count and graph. This part is treated
 +       * similarly to the graph part, except that it is not
 +       * "scaled". If total width is too small to accomodate the
 +       * guaranteed minimum width of the filename part and the
 +       * separators and this message, this message will "overflow"
 +       * making the line longer than the maximum width.
         */
 -      name_width = (name_width < max_len) ? name_width : max_len;
 -      if (width < (name_width + 10) + max_change)
 -              width = width - (name_width + 10);
 +
 +      if (options->stat_width == -1)
 +              width = term_columns() - options->output_prefix_length;
        else
 -              width = max_change;
 +              width = options->stat_width ? options->stat_width : 80;
 +      number_width = decimal_width(max_change) > number_width ?
 +              decimal_width(max_change) : number_width;
  
 -      for (i = 0; i < data->nr; i++) {
 +      if (options->stat_graph_width == -1)
 +              options->stat_graph_width = diff_stat_graph_width;
 +
 +      /*
 +       * Guarantee 3/8*16==6 for the graph part
 +       * and 5/8*16==10 for the filename part
 +       */
 +      if (width < 16 + 6 + number_width)
 +              width = 16 + 6 + number_width;
 +
 +      /*
 +       * First assign sizes that are wanted, ignoring available width.
 +       * strlen("Bin XXX -> YYY bytes") == bin_width, and the part
 +       * starting from "XXX" should fit in graph_width.
 +       */
 +      graph_width = max_change + 4 > bin_width ? max_change : bin_width - 4;
 +      if (options->stat_graph_width &&
 +          options->stat_graph_width < graph_width)
 +              graph_width = options->stat_graph_width;
 +
 +      name_width = (options->stat_name_width > 0 &&
 +                    options->stat_name_width < max_len) ?
 +              options->stat_name_width : max_len;
 +
 +      /*
 +       * Adjust adjustable widths not to exceed maximum width
 +       */
 +      if (name_width + number_width + 6 + graph_width > width) {
 +              if (graph_width > width * 3/8 - number_width - 6) {
 +                      graph_width = width * 3/8 - number_width - 6;
 +                      if (graph_width < 6)
 +                              graph_width = 6;
 +              }
 +
 +              if (options->stat_graph_width &&
 +                  graph_width > options->stat_graph_width)
 +                      graph_width = options->stat_graph_width;
 +              if (name_width > width - number_width - 6 - graph_width)
 +                      name_width = width - number_width - 6 - graph_width;
 +              else
 +                      graph_width = width - number_width - 6 - name_width;
 +      }
 +
 +      /*
 +       * From here name_width is the width of the name area,
 +       * and graph_width is the width of the graph area.
 +       * max_change is used to scale graph properly.
 +       */
 +      for (i = 0; i < count; i++) {
                const char *prefix = "";
 -              char *name = data->files[i]->print_name;
 -              uintmax_t added = data->files[i]->added;
 -              uintmax_t deleted = data->files[i]->deleted;
 +              struct diffstat_file *file = data->files[i];
 +              char *name = file->print_name;
 +              uintmax_t added = file->added;
 +              uintmax_t deleted = file->deleted;
                int name_len;
  
 +              if (!file->is_interesting && (added + deleted == 0))
 +                      continue;
 +
                /*
                 * "scale" the filename
                 */
                                name = slash;
                }
  
 -              if (data->files[i]->is_binary) {
 +              if (file->is_binary) {
                        fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
 -                      fprintf(options->file, "  Bin ");
 -                      fprintf(options->file, "%s%"PRIuMAX"%s",
 +                      fprintf(options->file, " %*s", number_width, "Bin");
 +                      if (!added && !deleted) {
 +                              putc('\n', options->file);
 +                              continue;
 +                      }
 +                      fprintf(options->file, " %s%"PRIuMAX"%s",
                                del_c, deleted, reset);
                        fprintf(options->file, " -> ");
                        fprintf(options->file, "%s%"PRIuMAX"%s",
                        fprintf(options->file, "\n");
                        continue;
                }
 -              else if (data->files[i]->is_unmerged) {
 +              else if (file->is_unmerged) {
                        fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
 -                      fprintf(options->file, "  Unmerged\n");
 -                      continue;
 -              }
 -              else if (!data->files[i]->is_renamed &&
 -                       (added + deleted == 0)) {
 -                      total_files--;
 +                      fprintf(options->file, " Unmerged\n");
                        continue;
                }
  
                 */
                add = added;
                del = deleted;
 -              adds += add;
 -              dels += del;
  
 -              if (width <= max_change) {
 -                      add = scale_linear(add, width, max_change);
 -                      del = scale_linear(del, width, max_change);
 +              if (graph_width <= max_change) {
 +                      int total = add + del;
 +
 +                      total = scale_linear(add + del, graph_width, max_change);
 +                      if (total < 2 && add && del)
 +                              /* width >= 2 due to the sanity check */
 +                              total = 2;
 +                      if (add < del) {
 +                              add = scale_linear(add, graph_width, max_change);
 +                              del = total - add;
 +                      } else {
 +                              del = scale_linear(del, graph_width, max_change);
 +                              add = total - del;
 +                      }
                }
                fprintf(options->file, "%s", line_prefix);
                show_name(options->file, prefix, name, len);
 -              fprintf(options->file, "%5"PRIuMAX"%s", added + deleted,
 -                              added + deleted ? " " : "");
 +              fprintf(options->file, " %*"PRIuMAX"%s",
 +                      number_width, added + deleted,
 +                      added + deleted ? " " : "");
                show_graph(options->file, '+', add, add_c, reset);
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
 +
 +      for (i = 0; i < data->nr; i++) {
 +              struct diffstat_file *file = data->files[i];
 +              uintmax_t added = file->added;
 +              uintmax_t deleted = file->deleted;
 +
 +              if (file->is_unmerged ||
 +                  (!file->is_interesting && (added + deleted == 0))) {
 +                      total_files--;
 +                      continue;
 +              }
 +
 +              if (!file->is_binary) {
 +                      adds += added;
 +                      dels += deleted;
 +              }
 +              if (i < count)
 +                      continue;
 +              if (!extra_shown)
 +                      fprintf(options->file, "%s ...\n", line_prefix);
 +              extra_shown = 1;
 +      }
        fprintf(options->file, "%s", line_prefix);
 -      fprintf(options->file,
 -             " %d files changed, %d insertions(+), %d deletions(-)\n",
 -             total_files, adds, dels);
 +      print_stat_summary(options->file, total_files, adds, dels);
  }
  
  static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
                return;
  
        for (i = 0; i < data->nr; i++) {
 -              if (!data->files[i]->is_binary &&
 -                  !data->files[i]->is_unmerged) {
 -                      int added = data->files[i]->added;
 -                      int deleted= data->files[i]->deleted;
 -                      if (!data->files[i]->is_renamed &&
 -                          (added + deleted == 0)) {
 -                              total_files--;
 -                      } else {
 -                              adds += added;
 -                              dels += deleted;
 -                      }
 +              int added = data->files[i]->added;
 +              int deleted= data->files[i]->deleted;
 +
 +              if (data->files[i]->is_unmerged ||
 +                  (!data->files[i]->is_interesting && (added + deleted == 0))) {
 +                      total_files--;
 +              } else if (!data->files[i]->is_binary) { /* don't count bytes */
 +                      adds += added;
 +                      dels += deleted;
                }
        }
 -      if (options->output_prefix) {
 -              struct strbuf *msg = NULL;
 -              msg = options->output_prefix(options,
 -                              options->output_prefix_data);
 -              fprintf(options->file, "%s", msg->buf);
 -      }
 -      fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n",
 -             total_files, adds, dels);
 +      fprintf(options->file, "%s", diff_line_prefix(options));
 +      print_stat_summary(options->file, total_files, adds, dels);
  }
  
  static void show_numstat(struct diffstat_t *data, struct diff_options *options)
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
  
 -              if (options->output_prefix) {
 -                      struct strbuf *msg = NULL;
 -                      msg = options->output_prefix(options,
 -                                      options->output_prefix_data);
 -                      fprintf(options->file, "%s", msg->buf);
 -              }
 +              fprintf(options->file, "%s", diff_line_prefix(options));
  
                if (file->is_binary)
                        fprintf(options->file, "-\t-\t");
@@@ -1795,7 -1547,13 +1807,7 @@@ static long gather_dirstat(struct diff_
  {
        unsigned long this_dir = 0;
        unsigned int sources = 0;
 -      const char *line_prefix = "";
 -      struct strbuf *msg = NULL;
 -
 -      if (opt->output_prefix) {
 -              msg = opt->output_prefix(opt, opt->output_prefix_data);
 -              line_prefix = msg->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(opt);
  
        while (dir->nr) {
                struct dirstat_file *f = dir->files;
@@@ -2040,15 -1798,21 +2052,15 @@@ static int is_conflict_marker(const cha
  static void checkdiff_consume(void *priv, char *line, unsigned long len)
  {
        struct checkdiff_t *data = priv;
 -      int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
        int marker_size = data->conflict_marker_size;
 -      const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
 -      const char *reset = diff_get_color(color_diff, DIFF_RESET);
 -      const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
 +      const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
 +      const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
 +      const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
        char *err;
 -      char *line_prefix = "";
 -      struct strbuf *msgbuf;
 +      const char *line_prefix;
  
        assert(data->o);
 -      if (data->o->output_prefix) {
 -              msgbuf = data->o->output_prefix(data->o,
 -                      data->o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      line_prefix = diff_line_prefix(data->o);
  
        if (line[0] == '+') {
                unsigned bad;
@@@ -2105,8 -1869,7 +2117,8 @@@ static unsigned char *deflate_it(char *
        return deflated;
  }
  
 -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
 +static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two,
 +                                const char *prefix)
  {
        void *cp;
        void *delta;
        free(data);
  }
  
 -static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
 +static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two,
 +                           const char *prefix)
  {
        fprintf(file, "%sGIT binary patch\n", prefix);
        emit_binary_diff_body(file, one, two, prefix);
        emit_binary_diff_body(file, two, one, prefix);
  }
  
 -static void diff_filespec_load_driver(struct diff_filespec *one)
 -{
 -      /* Use already-loaded driver */
 -      if (one->driver)
 -              return;
 -
 -      if (S_ISREG(one->mode))
 -              one->driver = userdiff_find_by_path(one->path);
 -
 -      /* Fallback to default settings */
 -      if (!one->driver)
 -              one->driver = userdiff_find_by_name("default");
 -}
 -
  int diff_filespec_is_binary(struct diff_filespec *one)
  {
        if (one->is_binary == -1) {
@@@ -2200,6 -1976,12 +2212,6 @@@ static const struct userdiff_funcname *
        return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
  }
  
 -static const char *userdiff_word_regex(struct diff_filespec *one)
 -{
 -      diff_filespec_load_driver(one);
 -      return one->driver->word_regex;
 -}
 -
  void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
  {
        if (!options->a_prefix)
@@@ -2229,13 -2011,19 +2241,13 @@@ static void builtin_diff(const char *na
        mmfile_t mf1, mf2;
        const char *lbl[2];
        char *a_one, *b_two;
 -      const char *set = diff_get_color_opt(o, DIFF_METAINFO);
 +      const char *meta = diff_get_color_opt(o, DIFF_METAINFO);
        const char *reset = diff_get_color_opt(o, DIFF_RESET);
        const char *a_prefix, *b_prefix;
        struct userdiff_driver *textconv_one = NULL;
        struct userdiff_driver *textconv_two = NULL;
        struct strbuf header = STRBUF_INIT;
 -      struct strbuf *msgbuf;
 -      char *line_prefix = "";
 -
 -      if (o->output_prefix) {
 -              msgbuf = o->output_prefix(o, o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(o);
  
        if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
                        (!one->mode || S_ISGITLINK(one->mode)) &&
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one ? one->path : two->path,
                                one->sha1, two->sha1, two->dirty_submodule,
 -                              del, add, reset);
 +                              meta, del, add, reset);
                return;
        }
  
        b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
 -      strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, set, a_one, b_two, reset);
 +      strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
 -              strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, set, two->mode, reset);
 +              strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, meta, two->mode, reset);
                if (xfrm_msg)
                        strbuf_addstr(&header, xfrm_msg);
                must_show_header = 1;
        }
        else if (lbl[1][0] == '/') {
 -              strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, set, one->mode, reset);
 +              strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, meta, one->mode, reset);
                if (xfrm_msg)
                        strbuf_addstr(&header, xfrm_msg);
                must_show_header = 1;
        }
        else {
                if (one->mode != two->mode) {
 -                      strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, set, one->mode, reset);
 -                      strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, set, two->mode, reset);
 +                      strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, meta, one->mode, reset);
 +                      strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, meta, two->mode, reset);
                        must_show_header = 1;
                }
                if (xfrm_msg)
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
  
 -              if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) {
 +              if (must_show_header) {
                        fprintf(o->file, "%s", header.buf);
                        strbuf_reset(&header);
                }
                memset(&xecfg, 0, sizeof(xecfg));
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
 -              ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
 +              ecbdata.color_diff = want_color(o->use_color);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
 +              if (DIFF_OPT_TST(o, FUNCCONTEXT))
 +                      xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
                if (!diffopts)
                        xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
                else if (!prefixcmp(diffopts, "-u"))
                        xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
 -              if (o->word_diff) {
 -                      int i;
 -
 -                      ecbdata.diff_words =
 -                              xcalloc(1, sizeof(struct diff_words_data));
 -                      ecbdata.diff_words->type = o->word_diff;
 -                      ecbdata.diff_words->opt = o;
 -                      if (!o->word_regex)
 -                              o->word_regex = userdiff_word_regex(one);
 -                      if (!o->word_regex)
 -                              o->word_regex = userdiff_word_regex(two);
 -                      if (!o->word_regex)
 -                              o->word_regex = diff_word_regex_cfg;
 -                      if (o->word_regex) {
 -                              ecbdata.diff_words->word_regex = (regex_t *)
 -                                      xmalloc(sizeof(regex_t));
 -                              if (regcomp(ecbdata.diff_words->word_regex,
 -                                              o->word_regex,
 -                                              REG_EXTENDED | REG_NEWLINE))
 -                                      die ("Invalid regular expression: %s",
 -                                                      o->word_regex);
 -                      }
 -                      for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
 -                              if (o->word_diff == diff_words_styles[i].type) {
 -                                      ecbdata.diff_words->style =
 -                                              &diff_words_styles[i];
 -                                      break;
 -                              }
 -                      }
 -                      if (DIFF_OPT_TST(o, COLOR_DIFF)) {
 -                              struct diff_words_style *st = ecbdata.diff_words->style;
 -                              st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
 -                              st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
 -                              st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
 -                      }
 -              }
 +              if (o->word_diff)
 +                      init_diff_words_data(&ecbdata, o, one, two);
                xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
                              &xpp, &xecfg);
                if (o->word_diff)
@@@ -2407,37 -2227,22 +2419,37 @@@ static void builtin_diffstat(const cha
                             struct diff_filespec *two,
                             struct diffstat_t *diffstat,
                             struct diff_options *o,
 -                           int complete_rewrite)
 +                           struct diff_filepair *p)
  {
        mmfile_t mf1, mf2;
        struct diffstat_file *data;
 +      int same_contents;
 +      int complete_rewrite = 0;
 +
 +      if (!DIFF_PAIR_UNMERGED(p)) {
 +              if (p->status == DIFF_STATUS_MODIFIED && p->score)
 +                      complete_rewrite = 1;
 +      }
  
        data = diffstat_add(diffstat, name_a, name_b);
 +      data->is_interesting = p->status != DIFF_STATUS_UNKNOWN;
  
        if (!one || !two) {
                data->is_unmerged = 1;
                return;
        }
  
 +      same_contents = !hashcmp(one->sha1, two->sha1);
 +
        if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
                data->is_binary = 1;
 -              data->added = diff_filespec_size(two);
 -              data->deleted = diff_filespec_size(one);
 +              if (same_contents) {
 +                      data->added = 0;
 +                      data->deleted = 0;
 +              } else {
 +                      data->added = diff_filespec_size(two);
 +                      data->deleted = diff_filespec_size(one);
 +              }
        }
  
        else if (complete_rewrite) {
                data->added = count_lines(two->data, two->size);
        }
  
 -      else {
 +      else if (!same_contents) {
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = o->xdl_opts;
 +              xecfg.ctxlen = o->context;
 +              xecfg.interhunkctxlen = o->interhunkcontext;
                xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
                              &xpp, &xecfg);
        }
@@@ -2557,12 -2360,12 +2569,12 @@@ void free_filespec(struct diff_filespe
  }
  
  void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
 -                 unsigned short mode)
 +                 int sha1_valid, unsigned short mode)
  {
        if (mode) {
                spec->mode = canon_mode(mode);
                hashcpy(spec->sha1, sha1);
 -              spec->sha1_valid = !is_null_sha1(sha1);
 +              spec->sha1_valid = sha1_valid;
        }
  }
  
@@@ -2635,6 -2438,22 +2647,6 @@@ static int reuse_worktree_file(const ch
        return 0;
  }
  
 -static int populate_from_stdin(struct diff_filespec *s)
 -{
 -      struct strbuf buf = STRBUF_INIT;
 -      size_t size = 0;
 -
 -      if (strbuf_read(&buf, 0, 0) < 0)
 -              return error("error while reading from stdin %s",
 -                                   strerror(errno));
 -
 -      s->should_munmap = 0;
 -      s->data = strbuf_detach(&buf, &size);
 -      s->size = size;
 -      s->should_free = 1;
 -      return 0;
 -}
 -
  static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
  {
        int len;
@@@ -2684,6 -2503,9 +2696,6 @@@ int diff_populate_filespec(struct diff_
                struct stat st;
                int fd;
  
 -              if (!strcmp(s->path, "-"))
 -                      return populate_from_stdin(s);
 -
                if (lstat(s->path, &st) < 0) {
                        if (errno == ENOENT) {
                        err_empty:
@@@ -2934,9 -2756,14 +2946,9 @@@ static void fill_metainfo(struct strbu
  {
        const char *set = diff_get_color(use_color, DIFF_METAINFO);
        const char *reset = diff_get_color(use_color, DIFF_RESET);
 -      struct strbuf *msgbuf;
 -      char *line_prefix = "";
 +      const char *line_prefix = diff_line_prefix(o);
  
        *must_show_header = 1;
 -      if (o->output_prefix) {
 -              msgbuf = o->output_prefix(o, o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
        strbuf_init(msg, PATH_MAX * 2 + 300);
        switch (p->status) {
        case DIFF_STATUS_COPIED:
@@@ -3003,8 -2830,9 +3015,8 @@@ static void run_diff_cmd(const char *pg
        int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score;
        int must_show_header = 0;
  
 -      if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
 -              pgm = NULL;
 -      else {
 +
 +      if (DIFF_OPT_TST(o, ALLOW_EXTERNAL)) {
                struct userdiff_driver *drv = userdiff_find_by_path(attr_path);
                if (drv && drv->external)
                        pgm = drv->external;
                 */
                fill_metainfo(msg, name, other, one, two, o, p,
                              &must_show_header,
 -                            DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
 +                            want_color(o->use_color) && !pgm);
                xfrm_msg = msg->len ? msg->buf : NULL;
        }
  
@@@ -3039,7 -2867,7 +3051,7 @@@ static void diff_fill_sha1_info(struct 
        if (DIFF_FILE_VALID(one)) {
                if (!one->sha1_valid) {
                        struct stat st;
 -                      if (!strcmp(one->path, "-")) {
 +                      if (one->is_stdin) {
                                hashcpy(one->sha1, null_sha1);
                                return;
                        }
@@@ -3084,9 -2912,6 +3096,9 @@@ static void run_diff(struct diff_filepa
        if (o->prefix_length)
                strip_prefix(o->prefix_length, &name, &other);
  
 +      if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
 +              pgm = NULL;
 +
        if (DIFF_PAIR_UNMERGED(p)) {
                run_diff_cmd(pgm, name, NULL, attr_path,
                             NULL, NULL, NULL, o, p);
@@@ -3126,10 -2951,11 +3138,10 @@@ static void run_diffstat(struct diff_fi
  {
        const char *name;
        const char *other;
 -      int complete_rewrite = 0;
  
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
 -              builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, 0);
 +              builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, p);
                return;
        }
  
        diff_fill_sha1_info(p->one);
        diff_fill_sha1_info(p->two);
  
 -      if (p->status == DIFF_STATUS_MODIFIED && p->score)
 -              complete_rewrite = 1;
 -      builtin_diffstat(name, other, p->one, p->two, diffstat, o, complete_rewrite);
 +      builtin_diffstat(name, other, p->one, p->two, diffstat, o, p);
  }
  
  static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
@@@ -3179,14 -3007,13 +3191,14 @@@ void diff_setup(struct diff_options *op
        options->break_opt = -1;
        options->rename_limit = -1;
        options->dirstat_permille = diff_dirstat_permille_default;
 -      options->context = 3;
 +      options->context = diff_context_default;
 +      DIFF_OPT_SET(options, RENAME_EMPTY);
  
        options->change = diff_change;
        options->add_remove = diff_addremove;
 -      if (diff_use_color_default > 0)
 -              DIFF_OPT_SET(options, COLOR_DIFF);
 +      options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
 +      options->xdl_opts |= diff_algorithm;
  
        if (diff_no_prefix) {
                options->a_prefix = options->b_prefix = "";
        }
  }
  
 -int diff_setup_done(struct diff_options *options)
 +void diff_setup_done(struct diff_options *options)
  {
        int count = 0;
  
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
 -
 -      return 0;
  }
  
  static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
@@@ -3391,8 -3220,6 +3403,8 @@@ static int stat_opt(struct diff_option
        char *end;
        int width = options->stat_width;
        int name_width = options->stat_name_width;
 +      int graph_width = options->stat_graph_width;
 +      int count = options->stat_count;
        int argcount = 1;
  
        arg += strlen("--stat");
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
 +              } else if (!prefixcmp(arg, "-graph-width")) {
 +                      arg += strlen("-graph-width");
 +                      if (*arg == '=')
 +                              graph_width = strtoul(arg + 1, &end, 10);
 +                      else if (!*arg && !av[1])
 +                              die("Option '--stat-graph-width' requires a value");
 +                      else if (!*arg) {
 +                              graph_width = strtoul(av[1], &end, 10);
 +                              argcount = 2;
 +                      }
 +              } else if (!prefixcmp(arg, "-count")) {
 +                      arg += strlen("-count");
 +                      if (*arg == '=')
 +                              count = strtoul(arg + 1, &end, 10);
 +                      else if (!*arg && !av[1])
 +                              die("Option '--stat-count' requires a value");
 +                      else if (!*arg) {
 +                              count = strtoul(av[1], &end, 10);
 +                              argcount = 2;
 +                      }
                }
                break;
        case '=':
                width = strtoul(arg+1, &end, 10);
                if (*end == ',')
                        name_width = strtoul(end+1, &end, 10);
 +              if (*end == ',')
 +                      count = strtoul(end+1, &end, 10);
        }
  
        /* Important! This checks all the error cases! */
                return 0;
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
 +      options->stat_graph_width = graph_width;
        options->stat_width = width;
 +      options->stat_count = count;
        return argcount;
  }
  
@@@ -3476,14 -3279,6 +3488,14 @@@ static int parse_dirstat_opt(struct dif
        return 1;
  }
  
 +static int parse_submodule_opt(struct diff_options *options, const char *value)
 +{
 +      if (parse_submodule_params(options, value))
 +              die(_("Failed to parse --submodule option parameter: '%s'"),
 +                      value);
 +      return 1;
 +}
 +
  int diff_opt_parse(struct diff_options *options, const char **av, int ac)
  {
        const char *arg = av[0];
        else if (!strcmp(arg, "-s"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
        else if (!prefixcmp(arg, "--stat"))
 -              /* --stat, --stat-width, or --stat-name-width */
 +              /* --stat, --stat-width, --stat-name-width, or --stat-count */
                return stat_opt(options, av);
  
        /* renames options */
        }
        else if (!strcmp(arg, "--no-renames"))
                options->detect_rename = 0;
 +      else if (!strcmp(arg, "--rename-empty"))
 +              DIFF_OPT_SET(options, RENAME_EMPTY);
 +      else if (!strcmp(arg, "--no-rename-empty"))
 +              DIFF_OPT_CLR(options, RENAME_EMPTY);
        else if (!strcmp(arg, "--relative"))
                DIFF_OPT_SET(options, RELATIVE_NAME);
        else if (!prefixcmp(arg, "--relative=")) {
        }
  
        /* xdiff options */
 +      else if (!strcmp(arg, "--minimal"))
 +              DIFF_XDL_SET(options, NEED_MINIMAL);
 +      else if (!strcmp(arg, "--no-minimal"))
 +              DIFF_XDL_CLR(options, NEED_MINIMAL);
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE);
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
        else if (!strcmp(arg, "--ignore-space-at-eol"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
        else if (!strcmp(arg, "--patience"))
 -              DIFF_XDL_SET(options, PATIENCE_DIFF);
 +              options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
 +      else if (!strcmp(arg, "--histogram"))
 +              options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF);
 +      else if (!prefixcmp(arg, "--diff-algorithm=")) {
 +              long value = parse_algorithm_value(arg+17);
 +              if (value < 0)
 +                      return error("option diff-algorithm accepts \"myers\", "
 +                                   "\"minimal\", \"patience\" and \"histogram\"");
 +              /* clear out previous settings */
 +              DIFF_XDL_CLR(options, NEED_MINIMAL);
 +              options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
 +              options->xdl_opts |= value;
 +      }
  
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
                DIFF_OPT_SET(options, FIND_COPIES_HARDER);
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
 +      else if (!strcmp(arg, "--no-follow"))
 +              DIFF_OPT_CLR(options, FOLLOW_RENAMES);
        else if (!strcmp(arg, "--color"))
 -              DIFF_OPT_SET(options, COLOR_DIFF);
 +              options->use_color = 1;
        else if (!prefixcmp(arg, "--color=")) {
 -              int value = git_config_colorbool(NULL, arg+8, -1);
 -              if (value == 0)
 -                      DIFF_OPT_CLR(options, COLOR_DIFF);
 -              else if (value > 0)
 -                      DIFF_OPT_SET(options, COLOR_DIFF);
 -              else
 +              int value = git_config_colorbool(NULL, arg+8);
 +              if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
 +              options->use_color = value;
        }
        else if (!strcmp(arg, "--no-color"))
 -              DIFF_OPT_CLR(options, COLOR_DIFF);
 +              options->use_color = 0;
        else if (!strcmp(arg, "--color-words")) {
 -              DIFF_OPT_SET(options, COLOR_DIFF);
 +              options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
        else if (!prefixcmp(arg, "--color-words=")) {
 -              DIFF_OPT_SET(options, COLOR_DIFF);
 +              options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
                options->word_regex = arg + 14;
        }
                if (!strcmp(type, "plain"))
                        options->word_diff = DIFF_WORDS_PLAIN;
                else if (!strcmp(type, "color")) {
 -                      DIFF_OPT_SET(options, COLOR_DIFF);
 +                      options->use_color = 1;
                        options->word_diff = DIFF_WORDS_COLOR;
                }
                else if (!strcmp(type, "porcelain"))
                handle_ignore_submodules_arg(options, arg + 20);
        } else if (!strcmp(arg, "--submodule"))
                DIFF_OPT_SET(options, SUBMODULE_LOG);
 -      else if (!prefixcmp(arg, "--submodule=")) {
 -              if (!strcmp(arg + 12, "log"))
 -                      DIFF_OPT_SET(options, SUBMODULE_LOG);
 -      }
 +      else if (!prefixcmp(arg, "--submodule="))
 +              return parse_submodule_opt(options, arg + 12);
  
        /* misc options */
        else if (!strcmp(arg, "-z"))
        else if (opt_arg(arg, '\0', "inter-hunk-context",
                         &options->interhunkcontext))
                ;
 +      else if (!strcmp(arg, "-W"))
 +              DIFF_OPT_SET(options, FUNCCONTEXT);
 +      else if (!strcmp(arg, "--function-context"))
 +              DIFF_OPT_SET(options, FUNCCONTEXT);
 +      else if (!strcmp(arg, "--no-function-context"))
 +              DIFF_OPT_CLR(options, FUNCCONTEXT);
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
                options->file = fopen(optarg, "w");
                if (!options->file)
@@@ -3884,8 -3656,12 +3896,8 @@@ static void diff_flush_raw(struct diff_
  {
        int line_termination = opt->line_termination;
        int inter_name_termination = line_termination ? '\t' : '\0';
 -      if (opt->output_prefix) {
 -              struct strbuf *msg = NULL;
 -              msg = opt->output_prefix(opt, opt->output_prefix_data);
 -              fprintf(opt->file, "%s", msg->buf);
 -      }
  
 +      fprintf(opt->file, "%s", diff_line_prefix(opt));
        if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) {
                fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode,
                        diff_unique_abbrev(p->one->sha1, opt->abbrev));
@@@ -4155,7 -3931,12 +4167,7 @@@ static void show_rename_copy(FILE *file
  static void diff_summary(struct diff_options *opt, struct diff_filepair *p)
  {
        FILE *file = opt->file;
 -      char *line_prefix = "";
 -
 -      if (opt->output_prefix) {
 -              struct strbuf *buf = opt->output_prefix(opt, opt->output_prefix_data);
 -              line_prefix = buf->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(opt);
  
        switch(p->status) {
        case DIFF_STATUS_DELETED:
@@@ -4456,9 -4237,7 +4468,9 @@@ void diff_flush(struct diff_options *op
  
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
 -                      putc(options->line_termination, options->file);
 +                      fprintf(options->file, "%s%c",
 +                              diff_line_prefix(options),
 +                              options->line_termination);
                        if (options->stat_sep) {
                                /* attach patch instead of inline */
                                fputs(options->stat_sep, options->file);
@@@ -4662,7 -4441,7 +4674,7 @@@ int diff_result_code(struct diff_option
  {
        int result = 0;
  
 -      diff_warn_rename_limit("diff.renamelimit",
 +      diff_warn_rename_limit("diff.renameLimit",
                               opt->needed_rename_limit,
                               opt->degraded_cc_to_c);
        if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
@@@ -4705,7 -4484,6 +4717,7 @@@ static int is_submodule_ignored(const c
  void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
 +                  int sha1_valid,
                    const char *concatpath, unsigned dirty_submodule)
  {
        struct diff_filespec *one, *two;
        two = alloc_filespec(concatpath);
  
        if (addremove != '+')
 -              fill_filespec(one, sha1, mode);
 +              fill_filespec(one, sha1, sha1_valid, mode);
        if (addremove != '-') {
 -              fill_filespec(two, sha1, mode);
 +              fill_filespec(two, sha1, sha1_valid, mode);
                two->dirty_submodule = dirty_submodule;
        }
  
@@@ -4752,7 -4530,6 +4764,7 @@@ void diff_change(struct diff_options *o
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
 +               int old_sha1_valid, int new_sha1_valid,
                 const char *concatpath,
                 unsigned old_dirty_submodule, unsigned new_dirty_submodule)
  {
                const unsigned char *tmp_c;
                tmp = old_mode; old_mode = new_mode; new_mode = tmp;
                tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
 +              tmp = old_sha1_valid; old_sha1_valid = new_sha1_valid;
 +                      new_sha1_valid = tmp;
                tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
                        new_dirty_submodule = tmp;
        }
  
        one = alloc_filespec(concatpath);
        two = alloc_filespec(concatpath);
 -      fill_filespec(one, old_sha1, old_mode);
 -      fill_filespec(two, new_sha1, new_mode);
 +      fill_filespec(one, old_sha1, old_sha1_valid, old_mode);
 +      fill_filespec(two, new_sha1, new_sha1_valid, new_mode);
        one->dirty_submodule = old_dirty_submodule;
        two->dirty_submodule = new_dirty_submodule;
  
@@@ -4886,19 -4661,3 +4898,19 @@@ size_t fill_textconv(struct userdiff_dr
  
        return size;
  }
 +
 +void setup_diff_pager(struct diff_options *opt)
 +{
 +      /*
 +       * If the user asked for our exit code, then either they want --quiet
 +       * or --exit-code. We should definitely not bother with a pager in the
 +       * former case, as we will generate no output. Since we still properly
 +       * report our exit code even when a pager is run, we _could_ run a
 +       * pager with --exit-code. But since we have not done so historically,
 +       * and because it is easy to find people oneline advising "git diff
 +       * --exit-code" in hooks and other scripts, we do not do so.
 +       */
 +      if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
 +          check_pager_config("diff") != 0)
 +              setup_pager();
 +}