Merge branch 'jc/maint-diff-core-safecrlf' into maint
authorJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 05:48:13 +0000 (22:48 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 05:48:13 +0000 (22:48 -0700)
Avoid failing "git diff" when core.safecrlf is set to true, because
the user cannot tell where the breakage is in preparation for fixing
and committing.

* jc/maint-diff-core-safecrlf:
diff: demote core.safecrlf=true to core.safecrlf=warn

1  2 
diff.c
diff --combined diff.c
index f0b3e7cfe34a99c0e5fea85fcd00ec54e9b578d8,3e406f1e83816ff31d5bddc32db57b1f6e31f407..71e4df96d48e3409f2bb84ca0c145e4f6278e985
--- 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
@@@ -26,8 -25,7 +26,8 @@@
  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;
@@@ -36,7 -34,6 +36,7 @@@ 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 -68,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
@@@ -171,12 -141,6 +171,12 @@@ int git_diff_ui_config(const char *var
                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_detect_rename_default = git_config_rename(var, value);
                return 0;
        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;
  
@@@ -425,7 -375,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');
@@@ -643,7 -598,13 +643,7 @@@ static void emit_rewrite_diff(const cha
        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;
@@@ -839,14 -800,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 -935,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) {
@@@ -1109,16 -1078,6 +1109,16 @@@ const char *diff_get_color(int diff_use
        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 -1118,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);
@@@ -1264,7 -1229,6 +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--;
@@@ -1347,7 -1300,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;
  };
@@@ -1495,11 -1447,16 +1495,11 @@@ static void show_stats(struct diffstat_
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
        int extra_shown = 0;
 -      struct strbuf *msg = NULL;
  
        if (data->nr == 0)
                return;
  
 -      if (options->output_prefix) {
 -              msg = options->output_prefix(options, options->output_prefix_data);
 -              line_prefix = msg->buf;
 -      }
 -
 +      line_prefix = diff_line_prefix(options);
        count = options->stat_count ? options->stat_count : data->nr;
  
        reset = diff_get_color_opt(options, DIFF_RESET);
        for (i = 0; (i < count) && (i < data->nr); i++) {
                struct diffstat_file *file = data->files[i];
                uintmax_t change = file->added + file->deleted;
 -              if (!data->files[i]->is_renamed &&
 -                       (change == 0)) {
 +
 +              if (!file->is_interesting && (change == 0)) {
                        count++; /* not shown == room for one more */
                        continue;
                }
                if (max_change < change)
                        max_change = change;
        }
 -      count = i; /* min(count, data->nr) */
 +      count = i; /* where we can stop scanning in data->files[] */
  
        /*
         * We have width = stat_width or term_columns() columns total.
         * 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
 +       * "scaled". If total width is too small to accommodate 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.
         */
        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 (!data->files[i]->is_renamed &&
 -                       (added + deleted == 0)) {
 -                      total_files--;
 +              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, " %*s", number_width, "Bin");
                        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");
                 */
                add = added;
                del = deleted;
 -              adds += add;
 -              dels += del;
  
                if (graph_width <= max_change) {
                        int total = add + del;
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
 -      for (i = count; i < data->nr; i++) {
 -              uintmax_t added = data->files[i]->added;
 -              uintmax_t deleted = data->files[i]->deleted;
 -              if (!data->files[i]->is_renamed &&
 -                       (added + deleted == 0)) {
 +
 +      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;
                }
 -              adds += added;
 -              dels += deleted;
 +
 +              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;
@@@ -1743,15 -1695,21 +1743,15 @@@ static void show_shortstats(struct diff
                int added = data->files[i]->added;
                int deleted= data->files[i]->deleted;
  
 -              if (data->files[i]->is_unmerged)
 -                      continue;
 -              if (!data->files[i]->is_renamed && (added + deleted == 0)) {
 +              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, "%s", diff_line_prefix(options));
        print_stat_summary(options->file, total_files, adds, dels);
  }
  
@@@ -1765,7 -1723,12 +1765,7 @@@ static void show_numstat(struct diffsta
        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");
@@@ -1807,7 -1770,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;
@@@ -2057,10 -2026,15 +2057,10 @@@ static void checkdiff_consume(void *pri
        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;
@@@ -2117,8 -2091,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);
@@@ -2241,13 -2213,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 *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one ? one->path : two->path,
 +                              line_prefix,
                                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)
@@@ -2420,20 -2397,13 +2420,20 @@@ 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;
@@@ -2677,6 -2647,14 +2677,14 @@@ static int diff_populate_gitlink(struc
  int diff_populate_filespec(struct diff_filespec *s, int size_only)
  {
        int err = 0;
+       /*
+        * demote FAIL to WARN to allow inspecting the situation
+        * instead of refusing.
+        */
+       enum safe_crlf crlf_warn = (safe_crlf == SAFE_CRLF_FAIL
+                                   ? SAFE_CRLF_WARN
+                                   : safe_crlf);
        if (!DIFF_FILE_VALID(s))
                die("internal error: asking to populate invalid file.");
        if (S_ISDIR(s->mode))
                /*
                 * Convert from working tree format to canonical git format
                 */
-               if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) {
+               if (convert_to_git(s->path, s->data, s->size, &buf, crlf_warn)) {
                        size_t size = 0;
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
@@@ -2947,9 -2925,14 +2955,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:
@@@ -3139,10 -3122,11 +3147,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)
@@@ -3192,14 -3178,13 +3200,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;
        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 = "";
@@@ -3489,14 -3474,6 +3497,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];
                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 ((argcount = parse_long_opt("diff-algorithm", av, &optarg))) {
 +              long value = parse_algorithm_value(optarg);
 +              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;
 +              return argcount;
 +      }
  
        /* 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"))
                options->use_color = 1;
        else if (!prefixcmp(arg, "--color=")) {
                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"))
@@@ -3898,8 -3864,12 +3906,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));
@@@ -4169,7 -4139,12 +4177,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:
@@@ -4470,9 -4445,13 +4478,9 @@@ void diff_flush(struct diff_options *op
  
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
 -                      if (options->output_prefix) {
 -                              struct strbuf *msg = NULL;
 -                              msg = options->output_prefix(options,
 -                                      options->output_prefix_data);
 -                              fwrite(msg->buf, msg->len, 1, stdout);
 -                      }
 -                      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);
@@@ -4676,7 -4655,7 +4684,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) &&
@@@ -4900,19 -4879,3 +4908,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();
 +}