]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'js/unmap-before-ext-diff'
authorJunio C Hamano <gitster@pobox.com>
Thu, 25 Jul 2019 20:59:21 +0000 (13:59 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 25 Jul 2019 20:59:21 +0000 (13:59 -0700)
Windows update.

* js/unmap-before-ext-diff:
  diff: munmap() file contents before running external diff

1  2 
diff.c

diff --combined diff.c
index 41469930102ff3f807576384a77be59bc26a8613,0d2b287300f9d7f72c672e6e377958f24ff2124c..efe42b341ae15ebceb1e216518ab3a542be361dc
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -23,9 -23,7 +23,9 @@@
  #include "argv-array.h"
  #include "graph.h"
  #include "packfile.h"
 +#include "parse-options.h"
  #include "help.h"
 +#include "fetch-object.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -105,6 -103,11 +105,6 @@@ static const char *color_diff_slots[] 
        [DIFF_FILE_NEW_BOLD]          = "newBold",
  };
  
 -static NORETURN void die_want_option(const char *option_name)
 -{
 -      die(_("option '%s' requires a value"), option_name);
 -}
 -
  define_list_config_array_extra(color_diff_slots, {"plain"});
  
  static int parse_diff_color_slot(const char *var)
@@@ -175,10 -178,6 +175,10 @@@ static int parse_submodule_params(struc
                options->submodule_format = DIFF_SUBMODULE_SHORT;
        else if (!strcmp(value, "diff"))
                options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF;
 +      /*
 +       * Please update $__git_diff_submodule_formats in
 +       * git-completion.bash when you add new formats.
 +       */
        else
                return -1;
        return 0;
@@@ -205,10 -204,6 +205,10 @@@ long parse_algorithm_value(const char *
                return XDF_PATIENCE_DIFF;
        else if (!strcasecmp(value, "histogram"))
                return XDF_HISTOGRAM_DIFF;
 +      /*
 +       * Please update $__git_diff_algorithms in git-completion.bash
 +       * when you add new algorithms.
 +       */
        return -1;
  }
  
@@@ -1618,7 -1613,8 +1618,7 @@@ static int new_blank_line_at_eof(struc
        return ws_blank_line(line, len, ecbdata->ws_rule);
  }
  
 -static void emit_add_line(const char *reset,
 -                        struct emit_callback *ecbdata,
 +static void emit_add_line(struct emit_callback *ecbdata,
                          const char *line, int len)
  {
        unsigned flags = WSEH_NEW | ecbdata->ws_rule;
        emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_PLUS, line, len, flags);
  }
  
 -static void emit_del_line(const char *reset,
 -                        struct emit_callback *ecbdata,
 +static void emit_del_line(struct emit_callback *ecbdata,
                          const char *line, int len)
  {
        unsigned flags = WSEH_OLD | ecbdata->ws_rule;
        emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_MINUS, line, len, flags);
  }
  
 -static void emit_context_line(const char *reset,
 -                            struct emit_callback *ecbdata,
 +static void emit_context_line(struct emit_callback *ecbdata,
                              const char *line, int len)
  {
        unsigned flags = WSEH_CONTEXT | ecbdata->ws_rule;
@@@ -1673,10 -1671,7 +1673,10 @@@ static void emit_hunk_header(struct emi
        if (ecbdata->opt->flags.dual_color_diffed_diffs)
                strbuf_addstr(&msgbuf, reverse);
        strbuf_addstr(&msgbuf, frag);
 -      strbuf_add(&msgbuf, line, ep - line);
 +      if (ecbdata->opt->flags.suppress_hunk_header_line_count)
 +              strbuf_add(&msgbuf, atat, sizeof(atat));
 +      else
 +              strbuf_add(&msgbuf, line, ep - line);
        strbuf_addstr(&msgbuf, reset);
  
        /*
@@@ -1747,6 -1742,7 +1747,6 @@@ static void emit_rewrite_lines(struct e
                               int prefix, const char *data, int size)
  {
        const char *endp = NULL;
 -      const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
  
        while (0 < size) {
                int len;
                len = endp ? (endp - data + 1) : size;
                if (prefix != '+') {
                        ecb->lno_in_preimage++;
 -                      emit_del_line(reset, ecb, data, len);
 +                      emit_del_line(ecb, data, len);
                } else {
                        ecb->lno_in_postimage++;
 -                      emit_add_line(reset, ecb, data, len);
 +                      emit_add_line(ecb, data, len);
                }
                size -= len;
                data += len;
@@@ -2295,7 -2291,7 +2295,7 @@@ const char *diff_line_prefix(struct dif
        return msgbuf->buf;
  }
  
 -static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
 +static unsigned long sane_truncate_line(char *line, unsigned long len)
  {
        const char *cp;
        unsigned long allot;
@@@ -2329,6 -2325,7 +2329,6 @@@ static void find_lno(const char *line, 
  static void fn_out_consume(void *priv, char *line, unsigned long len)
  {
        struct emit_callback *ecbdata = priv;
 -      const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
        struct diff_options *o = ecbdata->opt;
  
        o->found_changes = 1;
        if (line[0] == '@') {
                if (ecbdata->diff_words)
                        diff_words_flush(ecbdata);
 -              len = sane_truncate_line(ecbdata, line, len);
 +              len = sane_truncate_line(line, len);
                find_lno(line, ecbdata);
                emit_hunk_header(ecbdata, line, len);
                return;
        switch (line[0]) {
        case '+':
                ecbdata->lno_in_postimage++;
 -              emit_add_line(reset, ecbdata, line + 1, len - 1);
 +              emit_add_line(ecbdata, line + 1, len - 1);
                break;
        case '-':
                ecbdata->lno_in_preimage++;
 -              emit_del_line(reset, ecbdata, line + 1, len - 1);
 +              emit_del_line(ecbdata, line + 1, len - 1);
                break;
        case ' ':
                ecbdata->lno_in_postimage++;
                ecbdata->lno_in_preimage++;
 -              emit_context_line(reset, ecbdata, line + 1, len - 1);
 +              emit_context_line(ecbdata, line + 1, len - 1);
                break;
        default:
                /* incomplete line at the end */
@@@ -4186,6 -4183,7 +4186,6 @@@ static void run_external_diff(const cha
                              struct diff_filespec *one,
                              struct diff_filespec *two,
                              const char *xfrm_msg,
 -                            int complete_rewrite,
                              struct diff_options *o)
  {
        struct argv_array argv = ARGV_ARRAY_INIT;
        argv_array_pushf(&env, "GIT_DIFF_PATH_COUNTER=%d", ++o->diff_path_counter);
        argv_array_pushf(&env, "GIT_DIFF_PATH_TOTAL=%d", q->nr);
  
+       diff_free_filespec_data(one);
+       diff_free_filespec_data(two);
        if (run_command_v_opt_cd_env(argv.argv, RUN_USING_SHELL, NULL, env.argv))
                die(_("external diff died, stopping at %s"), name);
  
@@@ -4343,7 -4343,8 +4345,7 @@@ static void run_diff_cmd(const char *pg
        }
  
        if (pgm) {
 -              run_external_diff(pgm, name, other, one, two, xfrm_msg,
 -                                complete_rewrite, o);
 +              run_external_diff(pgm, name, other, one, two, xfrm_msg, o);
                return;
        }
        if (one && two)
@@@ -4492,8 -4493,6 +4494,8 @@@ static void run_checkdiff(struct diff_f
        builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
  }
  
 +static void prep_parse_options(struct diff_options *options);
 +
  void repo_diff_setup(struct repository *r, struct diff_options *options)
  {
        memcpy(options, &default_diff_options, sizeof(*options));
  
        options->color_moved = diff_color_moved_default;
        options->color_moved_ws_handling = diff_color_moved_ws_default;
 +
 +      prep_parse_options(options);
  }
  
  void diff_setup_done(struct diff_options *options)
  
        if (!options->use_color || external_diff())
                options->color_moved = 0;
 -}
 -
 -static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
 -{
 -      char c, *eq;
 -      int len;
  
 -      if (*arg != '-')
 -              return 0;
 -      c = *++arg;
 -      if (!c)
 -              return 0;
 -      if (c == arg_short) {
 -              c = *++arg;
 -              if (!c)
 -                      return 1;
 -              if (val && isdigit(c)) {
 -                      char *end;
 -                      int n = strtoul(arg, &end, 10);
 -                      if (*end)
 -                              return 0;
 -                      *val = n;
 -                      return 1;
 -              }
 -              return 0;
 -      }
 -      if (c != '-')
 -              return 0;
 -      arg++;
 -      eq = strchrnul(arg, '=');
 -      len = eq - arg;
 -      if (!len || strncmp(arg, arg_long, len))
 -              return 0;
 -      if (*eq) {
 -              int n;
 -              char *end;
 -              if (!isdigit(*++eq))
 -                      return 0;
 -              n = strtoul(eq, &end, 10);
 -              if (*end)
 -                      return 0;
 -              *val = n;
 -      }
 -      return 1;
 -}
 -
 -static int diff_scoreopt_parse(const char *opt);
 -
 -static inline int short_opt(char opt, const char **argv,
 -                          const char **optarg)
 -{
 -      const char *arg = argv[0];
 -      if (arg[0] != '-' || arg[1] != opt)
 -              return 0;
 -      if (arg[2] != '\0') {
 -              *optarg = arg + 2;
 -              return 1;
 -      }
 -      if (!argv[1])
 -              die("Option '%c' requires a value", opt);
 -      *optarg = argv[1];
 -      return 2;
 +      FREE_AND_NULL(options->parseopts);
  }
  
  int parse_long_opt(const char *opt, const char **argv,
        return 2;
  }
  
 -static int stat_opt(struct diff_options *options, const char **av)
 +static int diff_opt_stat(const struct option *opt, const char *value, int unset)
  {
 -      const char *arg = av[0];
 -      char *end;
 +      struct diff_options *options = opt->value;
        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;
 +      char *end;
  
 -      if (!skip_prefix(arg, "--stat", &arg))
 -              BUG("stat option does not begin with --stat: %s", arg);
 -      end = (char *)arg;
 +      BUG_ON_OPT_NEG(unset);
  
 -      switch (*arg) {
 -      case '-':
 -              if (skip_prefix(arg, "-width", &arg)) {
 -                      if (*arg == '=')
 -                              width = strtoul(arg + 1, &end, 10);
 -                      else if (!*arg && !av[1])
 -                              die_want_option("--stat-width");
 -                      else if (!*arg) {
 -                              width = strtoul(av[1], &end, 10);
 -                              argcount = 2;
 -                      }
 -              } else if (skip_prefix(arg, "-name-width", &arg)) {
 -                      if (*arg == '=')
 -                              name_width = strtoul(arg + 1, &end, 10);
 -                      else if (!*arg && !av[1])
 -                              die_want_option("--stat-name-width");
 -                      else if (!*arg) {
 -                              name_width = strtoul(av[1], &end, 10);
 -                              argcount = 2;
 -                      }
 -              } else if (skip_prefix(arg, "-graph-width", &arg)) {
 -                      if (*arg == '=')
 -                              graph_width = strtoul(arg + 1, &end, 10);
 -                      else if (!*arg && !av[1])
 -                              die_want_option("--stat-graph-width");
 -                      else if (!*arg) {
 -                              graph_width = strtoul(av[1], &end, 10);
 -                              argcount = 2;
 -                      }
 -              } else if (skip_prefix(arg, "-count", &arg)) {
 -                      if (*arg == '=')
 -                              count = strtoul(arg + 1, &end, 10);
 -                      else if (!*arg && !av[1])
 -                              die_want_option("--stat-count");
 -                      else if (!*arg) {
 -                              count = strtoul(av[1], &end, 10);
 -                              argcount = 2;
 -                      }
 +      if (!strcmp(opt->long_name, "stat")) {
 +              if (value) {
 +                      width = strtoul(value, &end, 10);
 +                      if (*end == ',')
 +                              name_width = strtoul(end+1, &end, 10);
 +                      if (*end == ',')
 +                              count = strtoul(end+1, &end, 10);
 +                      if (*end)
 +                              return error(_("invalid --stat value: %s"), value);
                }
 -              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);
 -      }
 +      } else if (!strcmp(opt->long_name, "stat-width")) {
 +              width = strtoul(value, &end, 10);
 +              if (*end)
 +                      return error(_("%s expects a numerical value"),
 +                                   opt->long_name);
 +      } else if (!strcmp(opt->long_name, "stat-name-width")) {
 +              name_width = strtoul(value, &end, 10);
 +              if (*end)
 +                      return error(_("%s expects a numerical value"),
 +                                   opt->long_name);
 +      } else if (!strcmp(opt->long_name, "stat-graph-width")) {
 +              graph_width = strtoul(value, &end, 10);
 +              if (*end)
 +                      return error(_("%s expects a numerical value"),
 +                                   opt->long_name);
 +      } else if (!strcmp(opt->long_name, "stat-count")) {
 +              count = strtoul(value, &end, 10);
 +              if (*end)
 +                      return error(_("%s expects a numerical value"),
 +                                   opt->long_name);
 +      } else
 +              BUG("%s should not get here", opt->long_name);
  
 -      /* Important! This checks all the error cases! */
 -      if (*end)
 -              return 0;
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
        options->stat_graph_width = graph_width;
        options->stat_width = width;
        options->stat_count = count;
 -      return argcount;
 +      return 0;
  }
  
  static int parse_dirstat_opt(struct diff_options *options, const char *params)
        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;
 -}
 -
  static const char diff_status_letters[] = {
        DIFF_STATUS_ADDED,
        DIFF_STATUS_COPIED,
@@@ -4763,19 -4849,10 +4765,19 @@@ static unsigned filter_bit_tst(char sta
        return opt->filter & filter_bit[(int) status];
  }
  
 -static int parse_diff_filter_opt(const char *optarg, struct diff_options *opt)
 +unsigned diff_filter_bit(char status)
 +{
 +      prepare_filter_bits();
 +      return filter_bit[(int) status];
 +}
 +
 +static int diff_opt_diff_filter(const struct option *option,
 +                              const char *optarg, int unset)
  {
 +      struct diff_options *opt = option->value;
        int i, optch;
  
 +      BUG_ON_OPT_NEG(unset);
        prepare_filter_bits();
  
        /*
  
                bit = (0 <= optch && optch <= 'Z') ? filter_bit[optch] : 0;
                if (!bit)
 -                      return optarg[i];
 +                      return error(_("unknown change class '%c' in --diff-filter=%s"),
 +                                   optarg[i], optarg);
                if (negate)
                        opt->filter &= ~bit;
                else
@@@ -4822,29 -4898,25 +4824,29 @@@ static void enable_patch_output(int *fm
        *fmt |= DIFF_FORMAT_PATCH;
  }
  
 -static int parse_ws_error_highlight_opt(struct diff_options *opt, const char *arg)
 +static int diff_opt_ws_error_highlight(const struct option *option,
 +                                     const char *arg, int unset)
  {
 +      struct diff_options *opt = option->value;
        int val = parse_ws_error_highlight(arg);
  
 -      if (val < 0) {
 -              error("unknown value after ws-error-highlight=%.*s",
 -                    -1 - val, arg);
 -              return 0;
 -      }
 +      BUG_ON_OPT_NEG(unset);
 +      if (val < 0)
 +              return error(_("unknown value after ws-error-highlight=%.*s"),
 +                           -1 - val, arg);
        opt->ws_error_highlight = val;
 -      return 1;
 +      return 0;
  }
  
 -static int parse_objfind_opt(struct diff_options *opt, const char *arg)
 +static int diff_opt_find_object(const struct option *option,
 +                              const char *arg, int unset)
  {
 +      struct diff_options *opt = option->value;
        struct object_id oid;
  
 +      BUG_ON_OPT_NEG(unset);
        if (get_oid(arg, &oid))
 -              return error("unable to resolve '%s'", arg);
 +              return error(_("unable to resolve '%s'"), arg);
  
        if (!opt->objfind)
                opt->objfind = xcalloc(1, sizeof(*opt->objfind));
        opt->flags.recursive = 1;
        opt->flags.tree_in_recursive = 1;
        oidset_insert(opt->objfind, &oid);
 -      return 1;
 +      return 0;
  }
  
 -int diff_opt_parse(struct diff_options *options,
 -                 const char **av, int ac, const char *prefix)
 +static int diff_opt_anchored(const struct option *opt,
 +                           const char *arg, int unset)
  {
 -      const char *arg = av[0];
 -      const char *optarg;
 -      int argcount;
 +      struct diff_options *options = opt->value;
  
 -      if (!prefix)
 -              prefix = "";
 +      BUG_ON_OPT_NEG(unset);
 +      options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
 +      ALLOC_GROW(options->anchors, options->anchors_nr + 1,
 +                 options->anchors_alloc);
 +      options->anchors[options->anchors_nr++] = xstrdup(arg);
 +      return 0;
 +}
  
 -      /* Output format options */
 -      if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch")
 -          || opt_arg(arg, 'U', "unified", &options->context))
 -              enable_patch_output(&options->output_format);
 -      else if (!strcmp(arg, "--raw"))
 -              options->output_format |= DIFF_FORMAT_RAW;
 -      else if (!strcmp(arg, "--patch-with-raw")) {
 -              enable_patch_output(&options->output_format);
 -              options->output_format |= DIFF_FORMAT_RAW;
 -      } else if (!strcmp(arg, "--numstat"))
 -              options->output_format |= DIFF_FORMAT_NUMSTAT;
 -      else if (!strcmp(arg, "--shortstat"))
 -              options->output_format |= DIFF_FORMAT_SHORTSTAT;
 -      else if (skip_prefix(arg, "-X", &arg) ||
 -               skip_to_optional_arg(arg, "--dirstat", &arg))
 -              return parse_dirstat_opt(options, arg);
 -      else if (!strcmp(arg, "--cumulative"))
 -              return parse_dirstat_opt(options, "cumulative");
 -      else if (skip_to_optional_arg(arg, "--dirstat-by-file", &arg)) {
 -              parse_dirstat_opt(options, "files");
 -              return parse_dirstat_opt(options, arg);
 -      }
 -      else if (!strcmp(arg, "--check"))
 -              options->output_format |= DIFF_FORMAT_CHECKDIFF;
 -      else if (!strcmp(arg, "--summary"))
 -              options->output_format |= DIFF_FORMAT_SUMMARY;
 -      else if (!strcmp(arg, "--patch-with-stat")) {
 -              enable_patch_output(&options->output_format);
 -              options->output_format |= DIFF_FORMAT_DIFFSTAT;
 -      } else if (!strcmp(arg, "--name-only"))
 -              options->output_format |= DIFF_FORMAT_NAME;
 -      else if (!strcmp(arg, "--name-status"))
 -              options->output_format |= DIFF_FORMAT_NAME_STATUS;
 -      else if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch"))
 -              options->output_format |= DIFF_FORMAT_NO_OUTPUT;
 -      else if (starts_with(arg, "--stat"))
 -              /* --stat, --stat-width, --stat-name-width, or --stat-count */
 -              return stat_opt(options, av);
 -      else if (!strcmp(arg, "--compact-summary")) {
 -               options->flags.stat_with_summary = 1;
 -               options->output_format |= DIFF_FORMAT_DIFFSTAT;
 -      } else if (!strcmp(arg, "--no-compact-summary"))
 -               options->flags.stat_with_summary = 0;
 -      else if (skip_prefix(arg, "--output-indicator-new=", &arg))
 -              options->output_indicators[OUTPUT_INDICATOR_NEW] = arg[0];
 -      else if (skip_prefix(arg, "--output-indicator-old=", &arg))
 -              options->output_indicators[OUTPUT_INDICATOR_OLD] = arg[0];
 -      else if (skip_prefix(arg, "--output-indicator-context=", &arg))
 -              options->output_indicators[OUTPUT_INDICATOR_CONTEXT] = arg[0];
 -
 -      /* renames options */
 -      else if (starts_with(arg, "-B") ||
 -               skip_to_optional_arg(arg, "--break-rewrites", NULL)) {
 -              if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
 -                      return error("invalid argument to -B: %s", arg+2);
 -      }
 -      else if (starts_with(arg, "-M") ||
 -               skip_to_optional_arg(arg, "--find-renames", NULL)) {
 -              if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
 -                      return error("invalid argument to -M: %s", arg+2);
 -              options->detect_rename = DIFF_DETECT_RENAME;
 -      }
 -      else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
 -              options->irreversible_delete = 1;
 -      }
 -      else if (starts_with(arg, "-C") ||
 -               skip_to_optional_arg(arg, "--find-copies", NULL)) {
 -              if (options->detect_rename == DIFF_DETECT_COPY)
 -                      options->flags.find_copies_harder = 1;
 -              if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
 -                      return error("invalid argument to -C: %s", arg+2);
 -              options->detect_rename = DIFF_DETECT_COPY;
 +static int diff_opt_binary(const struct option *opt,
 +                         const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      BUG_ON_OPT_ARG(arg);
 +      enable_patch_output(&options->output_format);
 +      options->flags.binary = 1;
 +      return 0;
 +}
 +
 +static int diff_opt_break_rewrites(const struct option *opt,
 +                                 const char *arg, int unset)
 +{
 +      int *break_opt = opt->value;
 +      int opt1, opt2;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      if (!arg)
 +              arg = "";
 +      opt1 = parse_rename_score(&arg);
 +      if (*arg == 0)
 +              opt2 = 0;
 +      else if (*arg != '/')
 +              return error(_("%s expects <n>/<m> form"), opt->long_name);
 +      else {
 +              arg++;
 +              opt2 = parse_rename_score(&arg);
        }
 -      else if (!strcmp(arg, "--no-renames"))
 -              options->detect_rename = 0;
 -      else if (!strcmp(arg, "--rename-empty"))
 -              options->flags.rename_empty = 1;
 -      else if (!strcmp(arg, "--no-rename-empty"))
 -              options->flags.rename_empty = 0;
 -      else if (skip_to_optional_arg_default(arg, "--relative", &arg, NULL)) {
 -              options->flags.relative_name = 1;
 -              if (arg)
 -                      options->prefix = arg;
 -      }
 -
 -      /* 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"))
 -              DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
 -      else if (!strcmp(arg, "--ignore-space-at-eol"))
 -              DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
 -      else if (!strcmp(arg, "--ignore-cr-at-eol"))
 -              DIFF_XDL_SET(options, IGNORE_CR_AT_EOL);
 -      else if (!strcmp(arg, "--ignore-blank-lines"))
 -              DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
 -      else if (!strcmp(arg, "--indent-heuristic"))
 -              DIFF_XDL_SET(options, INDENT_HEURISTIC);
 -      else if (!strcmp(arg, "--no-indent-heuristic"))
 -              DIFF_XDL_CLR(options, INDENT_HEURISTIC);
 -      else if (!strcmp(arg, "--patience")) {
 -              int i;
 -              options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
 -              /*
 -               * Both --patience and --anchored use PATIENCE_DIFF
 -               * internally, so remove any anchors previously
 -               * specified.
 -               */
 -              for (i = 0; i < options->anchors_nr; i++)
 -                      free(options->anchors[i]);
 -              options->anchors_nr = 0;
 -      } 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;
 -      } else if (skip_prefix(arg, "--anchored=", &arg)) {
 -              options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
 -              ALLOC_GROW(options->anchors, options->anchors_nr + 1,
 -                         options->anchors_alloc);
 -              options->anchors[options->anchors_nr++] = xstrdup(arg);
 -      }
 -
 -      /* flags options */
 -      else if (!strcmp(arg, "--binary")) {
 -              enable_patch_output(&options->output_format);
 -              options->flags.binary = 1;
 -      }
 -      else if (!strcmp(arg, "--full-index"))
 -              options->flags.full_index = 1;
 -      else if (!strcmp(arg, "-a") || !strcmp(arg, "--text"))
 -              options->flags.text = 1;
 -      else if (!strcmp(arg, "-R"))
 -              options->flags.reverse_diff = 1;
 -      else if (!strcmp(arg, "--find-copies-harder"))
 -              options->flags.find_copies_harder = 1;
 -      else if (!strcmp(arg, "--follow"))
 -              options->flags.follow_renames = 1;
 -      else if (!strcmp(arg, "--no-follow")) {
 -              options->flags.follow_renames = 0;
 -              options->flags.default_follow_renames = 0;
 -      } else if (skip_to_optional_arg_default(arg, "--color", &arg, "always")) {
 -              int value = git_config_colorbool(NULL, arg);
 -              if (value < 0)
 -                      return error("option `color' expects \"always\", \"auto\", or \"never\"");
 -              options->use_color = value;
 -      }
 -      else if (!strcmp(arg, "--no-color"))
 -              options->use_color = 0;
 -      else if (!strcmp(arg, "--color-moved")) {
 +      if (*arg != 0)
 +              return error(_("%s expects <n>/<m> form"), opt->long_name);
 +      *break_opt = opt1 | (opt2 << 16);
 +      return 0;
 +}
 +
 +static int diff_opt_char(const struct option *opt,
 +                       const char *arg, int unset)
 +{
 +      char *value = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      if (arg[1])
 +              return error(_("%s expects a character, got '%s'"),
 +                           opt->long_name, arg);
 +      *value = arg[0];
 +      return 0;
 +}
 +
 +static int diff_opt_color_moved(const struct option *opt,
 +                              const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      if (unset) {
 +              options->color_moved = COLOR_MOVED_NO;
 +      } else if (!arg) {
                if (diff_color_moved_default)
                        options->color_moved = diff_color_moved_default;
                if (options->color_moved == COLOR_MOVED_NO)
                        options->color_moved = COLOR_MOVED_DEFAULT;
 -      } else if (!strcmp(arg, "--no-color-moved"))
 -              options->color_moved = COLOR_MOVED_NO;
 -      else if (skip_prefix(arg, "--color-moved=", &arg)) {
 +      } else {
                int cm = parse_color_moved(arg);
                if (cm < 0)
 -                      return error("bad --color-moved argument: %s", arg);
 +                      return error(_("bad --color-moved argument: %s"), arg);
                options->color_moved = cm;
 -      } else if (!strcmp(arg, "--no-color-moved-ws")) {
 +      }
 +      return 0;
 +}
 +
 +static int diff_opt_color_moved_ws(const struct option *opt,
 +                                 const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +      unsigned cm;
 +
 +      if (unset) {
                options->color_moved_ws_handling = 0;
 -      } else if (skip_prefix(arg, "--color-moved-ws=", &arg)) {
 -              unsigned cm = parse_color_moved_ws(arg);
 -              if (cm & COLOR_MOVED_WS_ERROR)
 -                      return -1;
 -              options->color_moved_ws_handling = cm;
 -      } else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) {
 -              options->use_color = 1;
 -              options->word_diff = DIFF_WORDS_COLOR;
 +              return 0;
        }
 -      else if (!strcmp(arg, "--word-diff")) {
 -              if (options->word_diff == DIFF_WORDS_NONE)
 -                      options->word_diff = DIFF_WORDS_PLAIN;
 +
 +      cm = parse_color_moved_ws(arg);
 +      if (cm & COLOR_MOVED_WS_ERROR)
 +              return error(_("invalid mode '%s' in --color-moved-ws"), arg);
 +      options->color_moved_ws_handling = cm;
 +      return 0;
 +}
 +
 +static int diff_opt_color_words(const struct option *opt,
 +                              const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      options->use_color = 1;
 +      options->word_diff = DIFF_WORDS_COLOR;
 +      options->word_regex = arg;
 +      return 0;
 +}
 +
 +static int diff_opt_compact_summary(const struct option *opt,
 +                                  const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_ARG(arg);
 +      if (unset) {
 +              options->flags.stat_with_summary = 0;
 +      } else {
 +              options->flags.stat_with_summary = 1;
 +              options->output_format |= DIFF_FORMAT_DIFFSTAT;
 +      }
 +      return 0;
 +}
 +
 +static int diff_opt_diff_algorithm(const struct option *opt,
 +                                 const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +      long value = parse_algorithm_value(arg);
 +
 +      BUG_ON_OPT_NEG(unset);
 +      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 0;
 +}
 +
 +static int diff_opt_dirstat(const struct option *opt,
 +                          const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      if (!strcmp(opt->long_name, "cumulative")) {
 +              if (arg)
 +                      BUG("how come --cumulative take a value?");
 +              arg = "cumulative";
 +      } else if (!strcmp(opt->long_name, "dirstat-by-file"))
 +              parse_dirstat_opt(options, "files");
 +      parse_dirstat_opt(options, arg ? arg : "");
 +      return 0;
 +}
 +
 +static int diff_opt_find_copies(const struct option *opt,
 +                              const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      if (!arg)
 +              arg = "";
 +      options->rename_score = parse_rename_score(&arg);
 +      if (*arg != 0)
 +              return error(_("invalid argument to %s"), opt->long_name);
 +
 +      if (options->detect_rename == DIFF_DETECT_COPY)
 +              options->flags.find_copies_harder = 1;
 +      else
 +              options->detect_rename = DIFF_DETECT_COPY;
 +
 +      return 0;
 +}
 +
 +static int diff_opt_find_renames(const struct option *opt,
 +                               const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      if (!arg)
 +              arg = "";
 +      options->rename_score = parse_rename_score(&arg);
 +      if (*arg != 0)
 +              return error(_("invalid argument to %s"), opt->long_name);
 +
 +      options->detect_rename = DIFF_DETECT_RENAME;
 +      return 0;
 +}
 +
 +static int diff_opt_follow(const struct option *opt,
 +                         const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_ARG(arg);
 +      if (unset) {
 +              options->flags.follow_renames = 0;
 +              options->flags.default_follow_renames = 0;
 +      } else {
 +              options->flags.follow_renames = 1;
 +      }
 +      return 0;
 +}
 +
 +static int diff_opt_ignore_submodules(const struct option *opt,
 +                                    const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      if (!arg)
 +              arg = "all";
 +      options->flags.override_submodule_config = 1;
 +      handle_ignore_submodules_arg(options, arg);
 +      return 0;
 +}
 +
 +static int diff_opt_line_prefix(const struct option *opt,
 +                              const char *optarg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      options->line_prefix = optarg;
 +      options->line_prefix_length = strlen(options->line_prefix);
 +      graph_setup_line_prefix(options);
 +      return 0;
 +}
 +
 +static int diff_opt_no_prefix(const struct option *opt,
 +                            const char *optarg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      BUG_ON_OPT_ARG(optarg);
 +      options->a_prefix = "";
 +      options->b_prefix = "";
 +      return 0;
 +}
 +
 +static enum parse_opt_result diff_opt_output(struct parse_opt_ctx_t *ctx,
 +                                           const struct option *opt,
 +                                           const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +      char *path;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      path = prefix_filename(ctx->prefix, arg);
 +      options->file = xfopen(path, "w");
 +      options->close_file = 1;
 +      if (options->use_color != GIT_COLOR_ALWAYS)
 +              options->use_color = GIT_COLOR_NEVER;
 +      free(path);
 +      return 0;
 +}
 +
 +static int diff_opt_patience(const struct option *opt,
 +                           const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +      int i;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      BUG_ON_OPT_ARG(arg);
 +      options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
 +      /*
 +       * Both --patience and --anchored use PATIENCE_DIFF
 +       * internally, so remove any anchors previously
 +       * specified.
 +       */
 +      for (i = 0; i < options->anchors_nr; i++)
 +              free(options->anchors[i]);
 +      options->anchors_nr = 0;
 +      return 0;
 +}
 +
 +static int diff_opt_pickaxe_regex(const struct option *opt,
 +                                const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      options->pickaxe = arg;
 +      options->pickaxe_opts |= DIFF_PICKAXE_KIND_G;
 +      return 0;
 +}
 +
 +static int diff_opt_pickaxe_string(const struct option *opt,
 +                                 const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      options->pickaxe = arg;
 +      options->pickaxe_opts |= DIFF_PICKAXE_KIND_S;
 +      return 0;
 +}
 +
 +static int diff_opt_relative(const struct option *opt,
 +                           const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      options->flags.relative_name = 1;
 +      if (arg)
 +              options->prefix = arg;
 +      return 0;
 +}
 +
 +static int diff_opt_submodule(const struct option *opt,
 +                            const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      if (!arg)
 +              arg = "log";
 +      if (parse_submodule_params(options, arg))
 +              return error(_("failed to parse --submodule option parameter: '%s'"),
 +                           arg);
 +      return 0;
 +}
 +
 +static int diff_opt_textconv(const struct option *opt,
 +                           const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_ARG(arg);
 +      if (unset) {
 +              options->flags.allow_textconv = 0;
 +      } else {
 +              options->flags.allow_textconv = 1;
 +              options->flags.textconv_set_via_cmdline = 1;
        }
 -      else if (skip_prefix(arg, "--word-diff=", &arg)) {
 +      return 0;
 +}
 +
 +static int diff_opt_unified(const struct option *opt,
 +                          const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +      char *s;
 +
 +      BUG_ON_OPT_NEG(unset);
 +
 +      if (arg) {
 +              options->context = strtol(arg, &s, 10);
 +              if (*s)
 +                      return error(_("%s expects a numerical value"), "--unified");
 +      }
 +      enable_patch_output(&options->output_format);
 +
 +      return 0;
 +}
 +
 +static int diff_opt_word_diff(const struct option *opt,
 +                            const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      if (arg) {
                if (!strcmp(arg, "plain"))
                        options->word_diff = DIFF_WORDS_PLAIN;
                else if (!strcmp(arg, "color")) {
                else if (!strcmp(arg, "none"))
                        options->word_diff = DIFF_WORDS_NONE;
                else
 -                      die("bad --word-diff argument: %s", arg);
 -      }
 -      else if ((argcount = parse_long_opt("word-diff-regex", av, &optarg))) {
 +                      return error(_("bad --word-diff argument: %s"), arg);
 +      } else {
                if (options->word_diff == DIFF_WORDS_NONE)
                        options->word_diff = DIFF_WORDS_PLAIN;
 -              options->word_regex = optarg;
 -              return argcount;
        }
 -      else if (!strcmp(arg, "--exit-code"))
 -              options->flags.exit_with_status = 1;
 -      else if (!strcmp(arg, "--quiet"))
 -              options->flags.quick = 1;
 -      else if (!strcmp(arg, "--ext-diff"))
 -              options->flags.allow_external = 1;
 -      else if (!strcmp(arg, "--no-ext-diff"))
 -              options->flags.allow_external = 0;
 -      else if (!strcmp(arg, "--textconv")) {
 -              options->flags.allow_textconv = 1;
 -              options->flags.textconv_set_via_cmdline = 1;
 -      } else if (!strcmp(arg, "--no-textconv"))
 -              options->flags.allow_textconv = 0;
 -      else if (skip_to_optional_arg_default(arg, "--ignore-submodules", &arg, "all")) {
 -              options->flags.override_submodule_config = 1;
 -              handle_ignore_submodules_arg(options, arg);
 -      } else if (skip_to_optional_arg_default(arg, "--submodule", &arg, "log"))
 -              return parse_submodule_opt(options, arg);
 -      else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
 -              return parse_ws_error_highlight_opt(options, arg);
 -      else if (!strcmp(arg, "--ita-invisible-in-index"))
 -              options->ita_invisible_in_index = 1;
 -      else if (!strcmp(arg, "--ita-visible-in-index"))
 -              options->ita_invisible_in_index = 0;
 -
 -      /* misc options */
 -      else if (!strcmp(arg, "-z"))
 -              options->line_termination = 0;
 -      else if ((argcount = short_opt('l', av, &optarg))) {
 -              options->rename_limit = strtoul(optarg, NULL, 10);
 -              return argcount;
 -      }
 -      else if ((argcount = short_opt('S', av, &optarg))) {
 -              options->pickaxe = optarg;
 -              options->pickaxe_opts |= DIFF_PICKAXE_KIND_S;
 -              return argcount;
 -      } else if ((argcount = short_opt('G', av, &optarg))) {
 -              options->pickaxe = optarg;
 -              options->pickaxe_opts |= DIFF_PICKAXE_KIND_G;
 -              return argcount;
 -      }
 -      else if (!strcmp(arg, "--pickaxe-all"))
 -              options->pickaxe_opts |= DIFF_PICKAXE_ALL;
 -      else if (!strcmp(arg, "--pickaxe-regex"))
 -              options->pickaxe_opts |= DIFF_PICKAXE_REGEX;
 -      else if ((argcount = short_opt('O', av, &optarg))) {
 -              options->orderfile = prefix_filename(prefix, optarg);
 -              return argcount;
 -      } else if (skip_prefix(arg, "--find-object=", &arg))
 -              return parse_objfind_opt(options, arg);
 -      else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
 -              int offending = parse_diff_filter_opt(optarg, options);
 -              if (offending)
 -                      die("unknown change class '%c' in --diff-filter=%s",
 -                          offending, optarg);
 -              return argcount;
 -      }
 -      else if (!strcmp(arg, "--no-abbrev"))
 -              options->abbrev = 0;
 -      else if (!strcmp(arg, "--abbrev"))
 -              options->abbrev = DEFAULT_ABBREV;
 -      else if (skip_prefix(arg, "--abbrev=", &arg)) {
 -              options->abbrev = strtoul(arg, NULL, 10);
 -              if (options->abbrev < MINIMUM_ABBREV)
 -                      options->abbrev = MINIMUM_ABBREV;
 -              else if (the_hash_algo->hexsz < options->abbrev)
 -                      options->abbrev = the_hash_algo->hexsz;
 -      }
 -      else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) {
 -              options->a_prefix = optarg;
 -              return argcount;
 -      }
 -      else if ((argcount = parse_long_opt("line-prefix", av, &optarg))) {
 -              options->line_prefix = optarg;
 -              options->line_prefix_length = strlen(options->line_prefix);
 -              graph_setup_line_prefix(options);
 -              return argcount;
 -      }
 -      else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
 -              options->b_prefix = optarg;
 -              return argcount;
 -      }
 -      else if (!strcmp(arg, "--no-prefix"))
 -              options->a_prefix = options->b_prefix = "";
 -      else if (opt_arg(arg, '\0', "inter-hunk-context",
 -                       &options->interhunkcontext))
 -              ;
 -      else if (!strcmp(arg, "-W"))
 -              options->flags.funccontext = 1;
 -      else if (!strcmp(arg, "--function-context"))
 -              options->flags.funccontext = 1;
 -      else if (!strcmp(arg, "--no-function-context"))
 -              options->flags.funccontext = 0;
 -      else if ((argcount = parse_long_opt("output", av, &optarg))) {
 -              char *path = prefix_filename(prefix, optarg);
 -              options->file = xfopen(path, "w");
 -              options->close_file = 1;
 -              if (options->use_color != GIT_COLOR_ALWAYS)
 -                      options->use_color = GIT_COLOR_NEVER;
 -              free(path);
 -              return argcount;
 -      } else
 -              return 0;
 -      return 1;
 +      return 0;
 +}
 +
 +static int diff_opt_word_diff_regex(const struct option *opt,
 +                                  const char *arg, int unset)
 +{
 +      struct diff_options *options = opt->value;
 +
 +      BUG_ON_OPT_NEG(unset);
 +      if (options->word_diff == DIFF_WORDS_NONE)
 +              options->word_diff = DIFF_WORDS_PLAIN;
 +      options->word_regex = arg;
 +      return 0;
 +}
 +
 +static void prep_parse_options(struct diff_options *options)
 +{
 +      struct option parseopts[] = {
 +              OPT_GROUP(N_("Diff output format options")),
 +              OPT_BITOP('p', "patch", &options->output_format,
 +                        N_("generate patch"),
 +                        DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT),
 +              OPT_BIT_F('s', "no-patch", &options->output_format,
 +                        N_("suppress diff output"),
 +                        DIFF_FORMAT_NO_OUTPUT, PARSE_OPT_NONEG),
 +              OPT_BITOP('u', NULL, &options->output_format,
 +                        N_("generate patch"),
 +                        DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT),
 +              OPT_CALLBACK_F('U', "unified", options, N_("<n>"),
 +                             N_("generate diffs with <n> lines context"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_unified),
 +              OPT_BOOL('W', "function-context", &options->flags.funccontext,
 +                       N_("generate diffs with <n> lines context")),
 +              OPT_BIT_F(0, "raw", &options->output_format,
 +                        N_("generate the diff in raw format"),
 +                        DIFF_FORMAT_RAW, PARSE_OPT_NONEG),
 +              OPT_BITOP(0, "patch-with-raw", &options->output_format,
 +                        N_("synonym for '-p --raw'"),
 +                        DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW,
 +                        DIFF_FORMAT_NO_OUTPUT),
 +              OPT_BITOP(0, "patch-with-stat", &options->output_format,
 +                        N_("synonym for '-p --stat'"),
 +                        DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT,
 +                        DIFF_FORMAT_NO_OUTPUT),
 +              OPT_BIT_F(0, "numstat", &options->output_format,
 +                        N_("machine friendly --stat"),
 +                        DIFF_FORMAT_NUMSTAT, PARSE_OPT_NONEG),
 +              OPT_BIT_F(0, "shortstat", &options->output_format,
 +                        N_("output only the last line of --stat"),
 +                        DIFF_FORMAT_SHORTSTAT, PARSE_OPT_NONEG),
 +              OPT_CALLBACK_F('X', "dirstat", options, N_("<param1,param2>..."),
 +                             N_("output the distribution of relative amount of changes for each sub-directory"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
 +                             diff_opt_dirstat),
 +              OPT_CALLBACK_F(0, "cumulative", options, NULL,
 +                             N_("synonym for --dirstat=cumulative"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_NOARG,
 +                             diff_opt_dirstat),
 +              OPT_CALLBACK_F(0, "dirstat-by-file", options, N_("<param1,param2>..."),
 +                             N_("synonym for --dirstat=files,param1,param2..."),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
 +                             diff_opt_dirstat),
 +              OPT_BIT_F(0, "check", &options->output_format,
 +                        N_("warn if changes introduce conflict markers or whitespace errors"),
 +                        DIFF_FORMAT_CHECKDIFF, PARSE_OPT_NONEG),
 +              OPT_BIT_F(0, "summary", &options->output_format,
 +                        N_("condensed summary such as creations, renames and mode changes"),
 +                        DIFF_FORMAT_SUMMARY, PARSE_OPT_NONEG),
 +              OPT_BIT_F(0, "name-only", &options->output_format,
 +                        N_("show only names of changed files"),
 +                        DIFF_FORMAT_NAME, PARSE_OPT_NONEG),
 +              OPT_BIT_F(0, "name-status", &options->output_format,
 +                        N_("show only names and status of changed files"),
 +                        DIFF_FORMAT_NAME_STATUS, PARSE_OPT_NONEG),
 +              OPT_CALLBACK_F(0, "stat", options, N_("<width>[,<name-width>[,<count>]]"),
 +                             N_("generate diffstat"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_stat),
 +              OPT_CALLBACK_F(0, "stat-width", options, N_("<width>"),
 +                             N_("generate diffstat with a given width"),
 +                             PARSE_OPT_NONEG, diff_opt_stat),
 +              OPT_CALLBACK_F(0, "stat-name-width", options, N_("<width>"),
 +                             N_("generate diffstat with a given name width"),
 +                             PARSE_OPT_NONEG, diff_opt_stat),
 +              OPT_CALLBACK_F(0, "stat-graph-width", options, N_("<width>"),
 +                             N_("generate diffstat with a given graph width"),
 +                             PARSE_OPT_NONEG, diff_opt_stat),
 +              OPT_CALLBACK_F(0, "stat-count", options, N_("<count>"),
 +                             N_("generate diffstat with limited lines"),
 +                             PARSE_OPT_NONEG, diff_opt_stat),
 +              OPT_CALLBACK_F(0, "compact-summary", options, NULL,
 +                             N_("generate compact summary in diffstat"),
 +                             PARSE_OPT_NOARG, diff_opt_compact_summary),
 +              OPT_CALLBACK_F(0, "binary", options, NULL,
 +                             N_("output a binary diff that can be applied"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_NOARG, diff_opt_binary),
 +              OPT_BOOL(0, "full-index", &options->flags.full_index,
 +                       N_("show full pre- and post-image object names on the \"index\" lines")),
 +              OPT_COLOR_FLAG(0, "color", &options->use_color,
 +                             N_("show colored diff")),
 +              OPT_CALLBACK_F(0, "ws-error-highlight", options, N_("<kind>"),
 +                             N_("highlight whitespace errors in the 'context', 'old' or 'new' lines in the diff"),
 +                             PARSE_OPT_NONEG, diff_opt_ws_error_highlight),
 +              OPT_SET_INT('z', NULL, &options->line_termination,
 +                          N_("do not munge pathnames and use NULs as output field terminators in --raw or --numstat"),
 +                          0),
 +              OPT__ABBREV(&options->abbrev),
 +              OPT_STRING_F(0, "src-prefix", &options->a_prefix, N_("<prefix>"),
 +                           N_("show the given source prefix instead of \"a/\""),
 +                           PARSE_OPT_NONEG),
 +              OPT_STRING_F(0, "dst-prefix", &options->b_prefix, N_("<prefix>"),
 +                           N_("show the given destination prefix instead of \"b/\""),
 +                           PARSE_OPT_NONEG),
 +              OPT_CALLBACK_F(0, "line-prefix", options, N_("<prefix>"),
 +                             N_("prepend an additional prefix to every line of output"),
 +                             PARSE_OPT_NONEG, diff_opt_line_prefix),
 +              OPT_CALLBACK_F(0, "no-prefix", options, NULL,
 +                             N_("do not show any source or destination prefix"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_NOARG, diff_opt_no_prefix),
 +              OPT_INTEGER_F(0, "inter-hunk-context", &options->interhunkcontext,
 +                            N_("show context between diff hunks up to the specified number of lines"),
 +                            PARSE_OPT_NONEG),
 +              OPT_CALLBACK_F(0, "output-indicator-new",
 +                             &options->output_indicators[OUTPUT_INDICATOR_NEW],
 +                             N_("<char>"),
 +                             N_("specify the character to indicate a new line instead of '+'"),
 +                             PARSE_OPT_NONEG, diff_opt_char),
 +              OPT_CALLBACK_F(0, "output-indicator-old",
 +                             &options->output_indicators[OUTPUT_INDICATOR_OLD],
 +                             N_("<char>"),
 +                             N_("specify the character to indicate an old line instead of '-'"),
 +                             PARSE_OPT_NONEG, diff_opt_char),
 +              OPT_CALLBACK_F(0, "output-indicator-context",
 +                             &options->output_indicators[OUTPUT_INDICATOR_CONTEXT],
 +                             N_("<char>"),
 +                             N_("specify the character to indicate a context instead of ' '"),
 +                             PARSE_OPT_NONEG, diff_opt_char),
 +
 +              OPT_GROUP(N_("Diff rename options")),
 +              OPT_CALLBACK_F('B', "break-rewrites", &options->break_opt, N_("<n>[/<m>]"),
 +                             N_("break complete rewrite changes into pairs of delete and create"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
 +                             diff_opt_break_rewrites),
 +              OPT_CALLBACK_F('M', "find-renames", options, N_("<n>"),
 +                             N_("detect renames"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
 +                             diff_opt_find_renames),
 +              OPT_SET_INT_F('D', "irreversible-delete", &options->irreversible_delete,
 +                            N_("omit the preimage for deletes"),
 +                            1, PARSE_OPT_NONEG),
 +              OPT_CALLBACK_F('C', "find-copies", options, N_("<n>"),
 +                             N_("detect copies"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
 +                             diff_opt_find_copies),
 +              OPT_BOOL(0, "find-copies-harder", &options->flags.find_copies_harder,
 +                       N_("use unmodified files as source to find copies")),
 +              OPT_SET_INT_F(0, "no-renames", &options->detect_rename,
 +                            N_("disable rename detection"),
 +                            0, PARSE_OPT_NONEG),
 +              OPT_BOOL(0, "rename-empty", &options->flags.rename_empty,
 +                       N_("use empty blobs as rename source")),
 +              OPT_CALLBACK_F(0, "follow", options, NULL,
 +                             N_("continue listing the history of a file beyond renames"),
 +                             PARSE_OPT_NOARG, diff_opt_follow),
 +              OPT_INTEGER('l', NULL, &options->rename_limit,
 +                          N_("prevent rename/copy detection if the number of rename/copy targets exceeds given limit")),
 +
 +              OPT_GROUP(N_("Diff algorithm options")),
 +              OPT_BIT(0, "minimal", &options->xdl_opts,
 +                      N_("produce the smallest possible diff"),
 +                      XDF_NEED_MINIMAL),
 +              OPT_BIT_F('w', "ignore-all-space", &options->xdl_opts,
 +                        N_("ignore whitespace when comparing lines"),
 +                        XDF_IGNORE_WHITESPACE, PARSE_OPT_NONEG),
 +              OPT_BIT_F('b', "ignore-space-change", &options->xdl_opts,
 +                        N_("ignore changes in amount of whitespace"),
 +                        XDF_IGNORE_WHITESPACE_CHANGE, PARSE_OPT_NONEG),
 +              OPT_BIT_F(0, "ignore-space-at-eol", &options->xdl_opts,
 +                        N_("ignore changes in whitespace at EOL"),
 +                        XDF_IGNORE_WHITESPACE_AT_EOL, PARSE_OPT_NONEG),
 +              OPT_BIT_F(0, "ignore-cr-at-eol", &options->xdl_opts,
 +                        N_("ignore carrier-return at the end of line"),
 +                        XDF_IGNORE_CR_AT_EOL, PARSE_OPT_NONEG),
 +              OPT_BIT_F(0, "ignore-blank-lines", &options->xdl_opts,
 +                        N_("ignore changes whose lines are all blank"),
 +                        XDF_IGNORE_BLANK_LINES, PARSE_OPT_NONEG),
 +              OPT_BIT(0, "indent-heuristic", &options->xdl_opts,
 +                      N_("heuristic to shift diff hunk boundaries for easy reading"),
 +                      XDF_INDENT_HEURISTIC),
 +              OPT_CALLBACK_F(0, "patience", options, NULL,
 +                             N_("generate diff using the \"patience diff\" algorithm"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_NOARG,
 +                             diff_opt_patience),
 +              OPT_BITOP(0, "histogram", &options->xdl_opts,
 +                        N_("generate diff using the \"histogram diff\" algorithm"),
 +                        XDF_HISTOGRAM_DIFF, XDF_DIFF_ALGORITHM_MASK),
 +              OPT_CALLBACK_F(0, "diff-algorithm", options, N_("<algorithm>"),
 +                             N_("choose a diff algorithm"),
 +                             PARSE_OPT_NONEG, diff_opt_diff_algorithm),
 +              OPT_CALLBACK_F(0, "anchored", options, N_("<text>"),
 +                             N_("generate diff using the \"anchored diff\" algorithm"),
 +                             PARSE_OPT_NONEG, diff_opt_anchored),
 +              OPT_CALLBACK_F(0, "word-diff", options, N_("<mode>"),
 +                             N_("show word diff, using <mode> to delimit changed words"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_word_diff),
 +              OPT_CALLBACK_F(0, "word-diff-regex", options, N_("<regex>"),
 +                             N_("use <regex> to decide what a word is"),
 +                             PARSE_OPT_NONEG, diff_opt_word_diff_regex),
 +              OPT_CALLBACK_F(0, "color-words", options, N_("<regex>"),
 +                             N_("equivalent to --word-diff=color --word-diff-regex=<regex>"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_color_words),
 +              OPT_CALLBACK_F(0, "color-moved", options, N_("<mode>"),
 +                             N_("moved lines of code are colored differently"),
 +                             PARSE_OPT_OPTARG, diff_opt_color_moved),
 +              OPT_CALLBACK_F(0, "color-moved-ws", options, N_("<mode>"),
 +                             N_("how white spaces are ignored in --color-moved"),
 +                             0, diff_opt_color_moved_ws),
 +
 +              OPT_GROUP(N_("Other diff options")),
 +              OPT_CALLBACK_F(0, "relative", options, N_("<prefix>"),
 +                             N_("when run from subdir, exclude changes outside and show relative paths"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
 +                             diff_opt_relative),
 +              OPT_BOOL('a', "text", &options->flags.text,
 +                       N_("treat all files as text")),
 +              OPT_BOOL('R', NULL, &options->flags.reverse_diff,
 +                       N_("swap two inputs, reverse the diff")),
 +              OPT_BOOL(0, "exit-code", &options->flags.exit_with_status,
 +                       N_("exit with 1 if there were differences, 0 otherwise")),
 +              OPT_BOOL(0, "quiet", &options->flags.quick,
 +                       N_("disable all output of the program")),
 +              OPT_BOOL(0, "ext-diff", &options->flags.allow_external,
 +                       N_("allow an external diff helper to be executed")),
 +              OPT_CALLBACK_F(0, "textconv", options, NULL,
 +                             N_("run external text conversion filters when comparing binary files"),
 +                             PARSE_OPT_NOARG, diff_opt_textconv),
 +              OPT_CALLBACK_F(0, "ignore-submodules", options, N_("<when>"),
 +                             N_("ignore changes to submodules in the diff generation"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
 +                             diff_opt_ignore_submodules),
 +              OPT_CALLBACK_F(0, "submodule", options, N_("<format>"),
 +                             N_("specify how differences in submodules are shown"),
 +                             PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
 +                             diff_opt_submodule),
 +              OPT_SET_INT_F(0, "ita-invisible-in-index", &options->ita_invisible_in_index,
 +                            N_("hide 'git add -N' entries from the index"),
 +                            1, PARSE_OPT_NONEG),
 +              OPT_SET_INT_F(0, "ita-visible-in-index", &options->ita_invisible_in_index,
 +                            N_("treat 'git add -N' entries as real in the index"),
 +                            0, PARSE_OPT_NONEG),
 +              OPT_CALLBACK_F('S', NULL, options, N_("<string>"),
 +                             N_("look for differences that change the number of occurrences of the specified string"),
 +                             0, diff_opt_pickaxe_string),
 +              OPT_CALLBACK_F('G', NULL, options, N_("<regex>"),
 +                             N_("look for differences that change the number of occurrences of the specified regex"),
 +                             0, diff_opt_pickaxe_regex),
 +              OPT_BIT_F(0, "pickaxe-all", &options->pickaxe_opts,
 +                        N_("show all changes in the changeset with -S or -G"),
 +                        DIFF_PICKAXE_ALL, PARSE_OPT_NONEG),
 +              OPT_BIT_F(0, "pickaxe-regex", &options->pickaxe_opts,
 +                        N_("treat <string> in -S as extended POSIX regular expression"),
 +                        DIFF_PICKAXE_REGEX, PARSE_OPT_NONEG),
 +              OPT_FILENAME('O', NULL, &options->orderfile,
 +                           N_("control the order in which files appear in the output")),
 +              OPT_CALLBACK_F(0, "find-object", options, N_("<object-id>"),
 +                             N_("look for differences that change the number of occurrences of the specified object"),
 +                             PARSE_OPT_NONEG, diff_opt_find_object),
 +              OPT_CALLBACK_F(0, "diff-filter", options, N_("[(A|C|D|M|R|T|U|X|B)...[*]]"),
 +                             N_("select files by diff type"),
 +                             PARSE_OPT_NONEG, diff_opt_diff_filter),
 +              { OPTION_CALLBACK, 0, "output", options, N_("<file>"),
 +                N_("Output to a specific file"),
 +                PARSE_OPT_NONEG, NULL, 0, diff_opt_output },
 +
 +              OPT_END()
 +      };
 +
 +      ALLOC_ARRAY(options->parseopts, ARRAY_SIZE(parseopts));
 +      memcpy(options->parseopts, parseopts, sizeof(parseopts));
 +}
 +
 +int diff_opt_parse(struct diff_options *options,
 +                 const char **av, int ac, const char *prefix)
 +{
 +      if (!prefix)
 +              prefix = "";
 +
 +      ac = parse_options(ac, av, prefix, options->parseopts, NULL,
 +                         PARSE_OPT_KEEP_DASHDASH |
 +                         PARSE_OPT_KEEP_UNKNOWN |
 +                         PARSE_OPT_NO_INTERNAL_HELP |
 +                         PARSE_OPT_ONE_SHOT |
 +                         PARSE_OPT_STOP_AT_NON_OPTION);
 +
 +      return ac;
  }
  
  int parse_rename_score(const char **cp_p)
        return (int)((num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale));
  }
  
 -static int diff_scoreopt_parse(const char *opt)
 -{
 -      int opt1, opt2, cmd;
 -
 -      if (*opt++ != '-')
 -              return -1;
 -      cmd = *opt++;
 -      if (cmd == '-') {
 -              /* convert the long-form arguments into short-form versions */
 -              if (skip_prefix(opt, "break-rewrites", &opt)) {
 -                      if (*opt == 0 || *opt++ == '=')
 -                              cmd = 'B';
 -              } else if (skip_prefix(opt, "find-copies", &opt)) {
 -                      if (*opt == 0 || *opt++ == '=')
 -                              cmd = 'C';
 -              } else if (skip_prefix(opt, "find-renames", &opt)) {
 -                      if (*opt == 0 || *opt++ == '=')
 -                              cmd = 'M';
 -              }
 -      }
 -      if (cmd != 'M' && cmd != 'C' && cmd != 'B')
 -              return -1; /* that is not a -M, -C, or -B option */
 -
 -      opt1 = parse_rename_score(&opt);
 -      if (cmd != 'B')
 -              opt2 = 0;
 -      else {
 -              if (*opt == 0)
 -                      opt2 = 0;
 -              else if (*opt != '/')
 -                      return -1; /* we expect -B80/99 or -B80 */
 -              else {
 -                      opt++;
 -                      opt2 = parse_rename_score(&opt);
 -              }
 -      }
 -      if (*opt != 0)
 -              return -1;
 -      return opt1 | (opt2 << 16);
 -}
 -
  struct diff_queue_struct diff_queued_diff;
  
  void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
@@@ -5993,22 -5742,6 +5995,22 @@@ static int remove_space(char *line, in
        return dst - line;
  }
  
 +void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx)
 +{
 +      unsigned char hash[GIT_MAX_RAWSZ];
 +      unsigned short carry = 0;
 +      int i;
 +
 +      git_SHA1_Final(hash, ctx);
 +      git_SHA1_Init(ctx);
 +      /* 20-byte sum, with carry */
 +      for (i = 0; i < GIT_SHA1_RAWSZ; ++i) {
 +              carry += result->hash[i] + hash[i];
 +              result->hash[i] = carry;
 +              carry >>= 8;
 +      }
 +}
 +
  static void patch_id_consume(void *priv, char *line, unsigned long len)
  {
        struct patch_id_t *data = priv;
@@@ -6033,8 -5766,8 +6035,8 @@@ static void patch_id_add_mode(git_SHA_C
        git_SHA1_Update(ctx, buf, len);
  }
  
 -/* returns 0 upon success, and writes result into sha1 */
 -static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only)
 +/* returns 0 upon success, and writes result into oid */
 +static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only, int stable)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
        git_SHA1_Init(&ctx);
        memset(&data, 0, sizeof(struct patch_id_t));
        data.ctx = &ctx;
 +      oidclr(oid);
  
        for (i = 0; i < q->nr; i++) {
                xpparam_t xpp;
                                  patch_id_consume, &data, &xpp, &xecfg))
                        return error("unable to generate patch-id diff for %s",
                                     p->one->path);
 +
 +              if (stable)
 +                      flush_one_hunk(oid, &ctx);
        }
  
 -      git_SHA1_Final(oid->hash, &ctx);
 +      if (!stable)
 +              git_SHA1_Final(oid->hash, &ctx);
 +
        return 0;
  }
  
 -int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only)
 +int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only, int stable)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
 -      int result = diff_get_patch_id(options, oid, diff_header_only);
 +      int result = diff_get_patch_id(options, oid, diff_header_only, stable);
  
        for (i = 0; i < q->nr; i++)
                diff_free_filepair(q->queue[i]);
@@@ -6499,47 -6226,14 +6501,47 @@@ static int diffnamecmp(const void *a_, 
        return strcmp(name_a, name_b);
  }
  
 -void diffcore_fix_diff_index(struct diff_options *options)
 +void diffcore_fix_diff_index(void)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        QSORT(q->queue, q->nr, diffnamecmp);
  }
  
 +static void add_if_missing(struct repository *r,
 +                         struct oid_array *to_fetch,
 +                         const struct diff_filespec *filespec)
 +{
 +      if (filespec && filespec->oid_valid &&
 +          oid_object_info_extended(r, &filespec->oid, NULL,
 +                                   OBJECT_INFO_FOR_PREFETCH))
 +              oid_array_append(to_fetch, &filespec->oid);
 +}
 +
  void diffcore_std(struct diff_options *options)
  {
 +      if (options->repo == the_repository &&
 +          repository_format_partial_clone) {
 +              /*
 +               * Prefetch the diff pairs that are about to be flushed.
 +               */
 +              int i;
 +              struct diff_queue_struct *q = &diff_queued_diff;
 +              struct oid_array to_fetch = OID_ARRAY_INIT;
 +
 +              for (i = 0; i < q->nr; i++) {
 +                      struct diff_filepair *p = q->queue[i];
 +                      add_if_missing(options->repo, &to_fetch, p->one);
 +                      add_if_missing(options->repo, &to_fetch, p->two);
 +              }
 +              if (to_fetch.nr)
 +                      /*
 +                       * NEEDSWORK: Consider deduplicating the OIDs sent.
 +                       */
 +                      fetch_objects(repository_format_partial_clone,
 +                                    to_fetch.oid, to_fetch.nr);
 +              oid_array_clear(&to_fetch);
 +      }
 +
        /* NOTE please keep the following in sync with diff_tree_combined() */
        if (options->skip_stat_unmatch)
                diffcore_skip_stat_unmatch(options);