Side-step sed line-ending "corruption" leading to t6038 failure.
[gitweb.git] / diff.c
diff --git a/diff.c b/diff.c
index 974b6a997a8ee506a0fec05e513374085c85aec6..71efa8edd49fe1c80fb37a0468c68ef5ab0d2596 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -30,6 +30,8 @@ static const char *diff_word_regex_cfg;
 static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
+static int diff_no_prefix;
+static struct diff_options default_diff_options;
 
 static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@ -43,10 +45,6 @@ static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL,       /* FUNCINFO */
 };
 
-static void diff_filespec_load_driver(struct diff_filespec *one);
-static size_t fill_textconv(struct userdiff_driver *driver,
-                           struct diff_filespec *df, char **outbuf);
-
 static int parse_diff_color_slot(const char *var, int ofs)
 {
        if (!strcasecmp(var+ofs, "plain"))
@@ -101,11 +99,18 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                diff_mnemonic_prefix = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "diff.noprefix")) {
+               diff_no_prefix = git_config_bool(var, value);
+               return 0;
+       }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
        if (!strcmp(var, "diff.wordregex"))
                return git_config_string(&diff_word_regex_cfg, var, value);
 
+       if (!strcmp(var, "diff.ignoresubmodules"))
+               handle_ignore_submodules_arg(&default_diff_options, value);
+
        return git_diff_basic_config(var, value, cb);
 }
 
@@ -140,6 +145,9 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (!prefixcmp(var, "submodule."))
+               return parse_submodule_config_option(var, value);
+
        return git_color_default_config(var, value, cb);
 }
 
@@ -875,7 +883,7 @@ static void diff_words_show(struct diff_words_data *diff_words)
        memset(&xecfg, 0, sizeof(xecfg));
        diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex);
        diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex);
-       xpp.flags = XDF_NEED_MINIMAL;
+       xpp.flags = 0;
        /* as only the hunk header will be parsed, we need a 0-context */
        xecfg.ctxlen = 0;
        xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
@@ -911,7 +919,10 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
                free (ecbdata->diff_words->minus.orig);
                free (ecbdata->diff_words->plus.text.ptr);
                free (ecbdata->diff_words->plus.orig);
-               free(ecbdata->diff_words->word_regex);
+               if (ecbdata->diff_words->word_regex) {
+                       regfree(ecbdata->diff_words->word_regex);
+                       free(ecbdata->diff_words->word_regex);
+               }
                free(ecbdata->diff_words);
                ecbdata->diff_words = NULL;
        }
@@ -1805,7 +1816,7 @@ void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const
                options->b_prefix = b;
 }
 
-static struct userdiff_driver *get_textconv(struct diff_filespec *one)
+struct userdiff_driver *get_textconv(struct diff_filespec *one)
 {
        if (!DIFF_FILE_VALID(one))
                return NULL;
@@ -1832,6 +1843,7 @@ static void builtin_diff(const char *name_a,
                         struct diff_filespec *one,
                         struct diff_filespec *two,
                         const char *xfrm_msg,
+                        int must_show_header,
                         struct diff_options *o,
                         int complete_rewrite)
 {
@@ -1889,21 +1901,24 @@ static void builtin_diff(const char *name_a,
        if (lbl[0][0] == '/') {
                /* /dev/null */
                strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, set, two->mode, reset);
-               if (xfrm_msg && xfrm_msg[0])
-                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, 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);
-               if (xfrm_msg && xfrm_msg[0])
-                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, 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);
+                       must_show_header = 1;
                }
-               if (xfrm_msg && xfrm_msg[0])
-                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
+               if (xfrm_msg)
+                       strbuf_addstr(&header, xfrm_msg);
 
                /*
                 * we do not run diff between different kind
@@ -1930,8 +1945,11 @@ static void builtin_diff(const char *name_a,
                        die("unable to read files to diff");
                /* Quite common confusing case */
                if (mf1.size == mf2.size &&
-                   !memcmp(mf1.ptr, mf2.ptr, mf1.size))
+                   !memcmp(mf1.ptr, mf2.ptr, mf1.size)) {
+                       if (must_show_header)
+                               fprintf(o->file, "%s", header.buf);
                        goto free_ab_and_return;
+               }
                fprintf(o->file, "%s", header.buf);
                strbuf_reset(&header);
                if (DIFF_OPT_TST(o, BINARY))
@@ -1949,7 +1967,7 @@ static void builtin_diff(const char *name_a,
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
 
-               if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) {
+               if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) {
                        fprintf(o->file, "%s", header.buf);
                        strbuf_reset(&header);
                }
@@ -1972,7 +1990,7 @@ static void builtin_diff(const char *name_a,
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.opt = o;
                ecbdata.header = header.len ? &header : NULL;
-               xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
+               xpp.flags = o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
@@ -2077,7 +2095,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
 
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
-               xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
+               xpp.flags = o->xdl_opts;
                xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
                              &xpp, &xecfg);
        }
@@ -2125,7 +2143,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xecfg.ctxlen = 1; /* at least one context line */
-               xpp.flags = XDF_NEED_MINIMAL;
+               xpp.flags = 0;
                xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
                              &xpp, &xecfg);
 
@@ -2562,44 +2580,53 @@ static void fill_metainfo(struct strbuf *msg,
                          struct diff_filespec *one,
                          struct diff_filespec *two,
                          struct diff_options *o,
-                         struct diff_filepair *p)
+                         struct diff_filepair *p,
+                         int *must_show_header,
+                         int use_color)
 {
+       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 = "";
 
+       *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:
-               strbuf_addf(msg, "%ssimilarity index %d%%", line_prefix, similarity_index(p));
-               strbuf_addf(msg, "\n%scopy from ", line_prefix);
+               strbuf_addf(msg, "%s%ssimilarity index %d%%",
+                           line_prefix, set, similarity_index(p));
+               strbuf_addf(msg, "%s\n%s%scopy from ",
+                           reset,  line_prefix, set);
                quote_c_style(name, msg, NULL, 0);
-               strbuf_addf(msg, "\n%scopy to ", line_prefix);
+               strbuf_addf(msg, "%s\n%s%scopy to ", reset, line_prefix, set);
                quote_c_style(other, msg, NULL, 0);
-               strbuf_addch(msg, '\n');
+               strbuf_addf(msg, "%s\n", reset);
                break;
        case DIFF_STATUS_RENAMED:
-               strbuf_addf(msg, "%ssimilarity index %d%%", line_prefix, similarity_index(p));
-               strbuf_addf(msg, "\n%srename from ", line_prefix);
+               strbuf_addf(msg, "%s%ssimilarity index %d%%",
+                           line_prefix, set, similarity_index(p));
+               strbuf_addf(msg, "%s\n%s%srename from ",
+                           reset, line_prefix, set);
                quote_c_style(name, msg, NULL, 0);
-               strbuf_addf(msg, "\n%srename to ", line_prefix);
+               strbuf_addf(msg, "%s\n%s%srename to ",
+                           reset, line_prefix, set);
                quote_c_style(other, msg, NULL, 0);
-               strbuf_addch(msg, '\n');
+               strbuf_addf(msg, "%s\n", reset);
                break;
        case DIFF_STATUS_MODIFIED:
                if (p->score) {
-                       strbuf_addf(msg, "%sdissimilarity index %d%%\n",
-                                   line_prefix, similarity_index(p));
+                       strbuf_addf(msg, "%s%sdissimilarity index %d%%%s\n",
+                                   line_prefix,
+                                   set, similarity_index(p), reset);
                        break;
                }
                /* fallthru */
        default:
-               /* nothing */
-               ;
+               *must_show_header = 0;
        }
        if (one && two && hashcmp(one->sha1, two->sha1)) {
                int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
@@ -2610,15 +2637,13 @@ static void fill_metainfo(struct strbuf *msg,
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
                                abbrev = 40;
                }
-               strbuf_addf(msg, "%sindex %.*s..%.*s",
-                           line_prefix, abbrev, sha1_to_hex(one->sha1),
-                           abbrev, sha1_to_hex(two->sha1));
+               strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
+                           find_unique_abbrev(one->sha1, abbrev));
+               strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev));
                if (one->mode == two->mode)
                        strbuf_addf(msg, " %06o", one->mode);
-               strbuf_addch(msg, '\n');
+               strbuf_addf(msg, "%s\n", reset);
        }
-       if (msg->len)
-               strbuf_setlen(msg, msg->len - 1);
 }
 
 static void run_diff_cmd(const char *pgm,
@@ -2633,11 +2658,7 @@ static void run_diff_cmd(const char *pgm,
 {
        const char *xfrm_msg = NULL;
        int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score;
-
-       if (msg) {
-               fill_metainfo(msg, name, other, one, two, o, p);
-               xfrm_msg = msg->len ? msg->buf : NULL;
-       }
+       int must_show_header = 0;
 
        if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
                pgm = NULL;
@@ -2647,6 +2668,17 @@ static void run_diff_cmd(const char *pgm,
                        pgm = drv->external;
        }
 
+       if (msg) {
+               /*
+                * don't use colors when the header is intended for an
+                * external diff driver
+                */
+               fill_metainfo(msg, name, other, one, two, o, p,
+                             &must_show_header,
+                             DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
+               xfrm_msg = msg->len ? msg->buf : NULL;
+       }
+
        if (pgm) {
                run_external_diff(pgm, name, other, one, two, xfrm_msg,
                                  complete_rewrite);
@@ -2654,7 +2686,8 @@ static void run_diff_cmd(const char *pgm,
        }
        if (one && two)
                builtin_diff(name, other ? other : name,
-                            one, two, xfrm_msg, o, complete_rewrite);
+                            one, two, xfrm_msg, must_show_header,
+                            o, complete_rewrite);
        else
                fprintf(o->file, "* Unmerged path %s\n", name);
 }
@@ -2681,10 +2714,16 @@ static void diff_fill_sha1_info(struct diff_filespec *one)
 static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
 {
        /* Strip the prefix but do not molest /dev/null and absolute paths */
-       if (*namep && **namep != '/')
+       if (*namep && **namep != '/') {
                *namep += prefix_length;
-       if (*otherp && **otherp != '/')
+               if (**namep == '/')
+                       ++*namep;
+       }
+       if (*otherp && **otherp != '/') {
                *otherp += prefix_length;
+               if (**otherp == '/')
+                       ++*otherp;
+       }
 }
 
 static void run_diff(struct diff_filepair *p, struct diff_options *o)
@@ -2790,8 +2829,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
 
 void diff_setup(struct diff_options *options)
 {
-       memset(options, 0, sizeof(*options));
-       memset(&diff_queued_diff, 0, sizeof(diff_queued_diff));
+       memcpy(options, &default_diff_options, sizeof(*options));
 
        options->file = stdout;
 
@@ -2807,7 +2845,9 @@ void diff_setup(struct diff_options *options)
                DIFF_OPT_SET(options, COLOR_DIFF);
        options->detect_rename = diff_detect_rename_default;
 
-       if (!diff_mnemonic_prefix) {
+       if (diff_no_prefix) {
+               options->a_prefix = options->b_prefix = "";
+       } else if (!diff_mnemonic_prefix) {
                options->a_prefix = "a/";
                options->b_prefix = "b/";
        }
@@ -2965,9 +3005,100 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va
 
 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;
+}
+
+int parse_long_opt(const char *opt, const char **argv,
+                  const char **optarg)
+{
+       const char *arg = argv[0];
+       if (arg[0] != '-' || arg[1] != '-')
+               return 0;
+       arg += strlen("--");
+       if (prefixcmp(arg, opt))
+               return 0;
+       arg += strlen(opt);
+       if (*arg == '=') { /* sticked form: --option=value */
+               *optarg = arg + 1;
+               return 1;
+       }
+       if (*arg != '\0')
+               return 0;
+       /* separate form: --option value */
+       if (!argv[1])
+               die("Option '--%s' requires a value", opt);
+       *optarg = argv[1];
+       return 2;
+}
+
+static int stat_opt(struct diff_options *options, const char **av)
+{
+       const char *arg = av[0];
+       char *end;
+       int width = options->stat_width;
+       int name_width = options->stat_name_width;
+       int argcount = 1;
+
+       arg += strlen("--stat");
+       end = (char *)arg;
+
+       switch (*arg) {
+       case '-':
+               if (!prefixcmp(arg, "-width")) {
+                       arg += strlen("-width");
+                       if (*arg == '=')
+                               width = strtoul(arg + 1, &end, 10);
+                       else if (!*arg && !av[1])
+                               die("Option '--stat-width' requires a value");
+                       else if (!*arg) {
+                               width = strtoul(av[1], &end, 10);
+                               argcount = 2;
+                       }
+               } else if (!prefixcmp(arg, "-name-width")) {
+                       arg += strlen("-name-width");
+                       if (*arg == '=')
+                               name_width = strtoul(arg + 1, &end, 10);
+                       else if (!*arg && !av[1])
+                               die("Option '--stat-name-width' requires a value");
+                       else if (!*arg) {
+                               name_width = strtoul(av[1], &end, 10);
+                               argcount = 2;
+                       }
+               }
+               break;
+       case '=':
+               width = strtoul(arg+1, &end, 10);
+               if (*end == ',')
+                       name_width = strtoul(end+1, &end, 10);
+       }
+
+       /* Important! This checks all the error cases! */
+       if (*end)
+               return 0;
+       options->output_format |= DIFF_FORMAT_DIFFSTAT;
+       options->stat_name_width = name_width;
+       options->stat_width = width;
+       return argcount;
+}
+
 int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 {
        const char *arg = av[0];
+       const char *optarg;
+       int argcount;
 
        /* Output format options */
        if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch"))
@@ -3004,33 +3135,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->output_format |= DIFF_FORMAT_NAME_STATUS;
        else if (!strcmp(arg, "-s"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
-       else if (!prefixcmp(arg, "--stat")) {
-               char *end;
-               int width = options->stat_width;
-               int name_width = options->stat_name_width;
-               arg += 6;
-               end = (char *)arg;
-
-               switch (*arg) {
-               case '-':
-                       if (!prefixcmp(arg, "-width="))
-                               width = strtoul(arg + 7, &end, 10);
-                       else if (!prefixcmp(arg, "-name-width="))
-                               name_width = strtoul(arg + 12, &end, 10);
-                       break;
-               case '=':
-                       width = strtoul(arg+1, &end, 10);
-                       if (*end == ',')
-                               name_width = strtoul(end+1, &end, 10);
-               }
-
-               /* Important! This checks all the error cases! */
-               if (*end)
-                       return 0;
-               options->output_format |= DIFF_FORMAT_DIFFSTAT;
-               options->stat_name_width = name_width;
-               options->stat_width = width;
-       }
+       else if (!prefixcmp(arg, "--stat"))
+               /* --stat, --stat-width, or --stat-name-width */
+               return stat_opt(options, av);
 
        /* renames options */
        else if (!prefixcmp(arg, "-B")) {
@@ -3124,10 +3231,11 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                else
                        die("bad --word-diff argument: %s", type);
        }
-       else if (!prefixcmp(arg, "--word-diff-regex=")) {
+       else if ((argcount = parse_long_opt("word-diff-regex", av, &optarg))) {
                if (options->word_diff == DIFF_WORDS_NONE)
                        options->word_diff = DIFF_WORDS_PLAIN;
-               options->word_regex = arg + 18;
+               options->word_regex = optarg;
+               return argcount;
        }
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
@@ -3141,9 +3249,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, ALLOW_TEXTCONV);
        else if (!strcmp(arg, "--no-textconv"))
                DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
-       else if (!strcmp(arg, "--ignore-submodules"))
-               DIFF_OPT_SET(options, IGNORE_SUBMODULES);
-       else if (!strcmp(arg, "--submodule"))
+       else if (!strcmp(arg, "--ignore-submodules")) {
+               DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
+               handle_ignore_submodules_arg(options, "all");
+       } else if (!prefixcmp(arg, "--ignore-submodules=")) {
+               DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
+               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"))
@@ -3153,18 +3265,31 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        /* misc options */
        else if (!strcmp(arg, "-z"))
                options->line_termination = 0;
-       else if (!prefixcmp(arg, "-l"))
-               options->rename_limit = strtoul(arg+2, NULL, 10);
-       else if (!prefixcmp(arg, "-S"))
-               options->pickaxe = arg + 2;
+       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;
+               options->pickaxe_opts |= DIFF_PICKAXE_ALL;
        else if (!strcmp(arg, "--pickaxe-regex"))
-               options->pickaxe_opts = DIFF_PICKAXE_REGEX;
-       else if (!prefixcmp(arg, "-O"))
-               options->orderfile = arg + 2;
-       else if (!prefixcmp(arg, "--diff-filter="))
-               options->filter = arg + 14;
+               options->pickaxe_opts |= DIFF_PICKAXE_REGEX;
+       else if ((argcount = short_opt('O', av, &optarg))) {
+               options->orderfile = optarg;
+               return argcount;
+       }
+       else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
+               options->filter = optarg;
+               return argcount;
+       }
        else if (!strcmp(arg, "--abbrev"))
                options->abbrev = DEFAULT_ABBREV;
        else if (!prefixcmp(arg, "--abbrev=")) {
@@ -3174,20 +3299,25 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                else if (40 < options->abbrev)
                        options->abbrev = 40;
        }
-       else if (!prefixcmp(arg, "--src-prefix="))
-               options->a_prefix = arg + 13;
-       else if (!prefixcmp(arg, "--dst-prefix="))
-               options->b_prefix = arg + 13;
+       else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) {
+               options->a_prefix = optarg;
+               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 (!prefixcmp(arg, "--output=")) {
-               options->file = fopen(arg + strlen("--output="), "w");
+       else if ((argcount = parse_long_opt("output", av, &optarg))) {
+               options->file = fopen(optarg, "w");
                if (!options->file)
-                       die_errno("Could not open '%s'", arg + strlen("--output="));
+                       die_errno("Could not open '%s'", optarg);
                options->close_file = 1;
+               return argcount;
        } else
                return 0;
        return 1;
@@ -3731,7 +3861,14 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
                                        len2, p->two->path);
                git_SHA1_Update(&ctx, buffer, len1);
 
-               xpp.flags = XDF_NEED_MINIMAL;
+               if (diff_filespec_is_binary(p->one) ||
+                   diff_filespec_is_binary(p->two)) {
+                       git_SHA1_Update(&ctx, sha1_to_hex(p->one->sha1), 40);
+                       git_SHA1_Update(&ctx, sha1_to_hex(p->two->sha1), 40);
+                       continue;
+               }
+
+               xpp.flags = 0;
                xecfg.ctxlen = 3;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
                xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
@@ -4032,25 +4169,24 @@ void diffcore_fix_diff_index(struct diff_options *options)
 
 void diffcore_std(struct diff_options *options)
 {
-       /* We never run this function more than one time, because the
-        * rename/copy detection logic can only run once.
-        */
-       if (diff_queued_diff.run)
-               return;
-
        if (options->skip_stat_unmatch)
                diffcore_skip_stat_unmatch(options);
-       if (options->break_opt != -1)
-               diffcore_break(options->break_opt);
-       if (options->detect_rename)
-               diffcore_rename(options);
-       if (options->break_opt != -1)
-               diffcore_merge_broken();
+       if (!options->found_follow) {
+               /* See try_to_follow_renames() in tree-diff.c */
+               if (options->break_opt != -1)
+                       diffcore_break(options->break_opt);
+               if (options->detect_rename)
+                       diffcore_rename(options);
+               if (options->break_opt != -1)
+                       diffcore_merge_broken();
+       }
        if (options->pickaxe)
-               diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+               diffcore_pickaxe(options);
        if (options->orderfile)
                diffcore_order(options->orderfile);
-       diff_resolve_rename_copy();
+       if (!options->found_follow)
+               /* See try_to_follow_renames() in tree-diff.c */
+               diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
 
        if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
@@ -4058,7 +4194,7 @@ void diffcore_std(struct diff_options *options)
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
 
-       diff_queued_diff.run = 1;
+       options->found_follow = 0;
 }
 
 int diff_result_code(struct diff_options *opt, int status)
@@ -4076,6 +4212,24 @@ int diff_result_code(struct diff_options *opt, int status)
        return result;
 }
 
+/*
+ * Shall changes to this submodule be ignored?
+ *
+ * Submodule changes can be configured to be ignored separately for each path,
+ * but that configuration can be overridden from the command line.
+ */
+static int is_submodule_ignored(const char *path, struct diff_options *options)
+{
+       int ignored = 0;
+       unsigned orig_flags = options->flags;
+       if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG))
+               set_diffopt_flags_from_submodule_config(options, path);
+       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES))
+               ignored = 1;
+       options->flags = orig_flags;
+       return ignored;
+}
+
 void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
@@ -4083,7 +4237,7 @@ void diff_addremove(struct diff_options *options,
 {
        struct diff_filespec *one, *two;
 
-       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
+       if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options))
                return;
 
        /* This may look odd, but it is a preparation for
@@ -4130,8 +4284,8 @@ void diff_change(struct diff_options *options,
 {
        struct diff_filespec *one, *two;
 
-       if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
-                       && S_ISGITLINK(new_mode))
+       if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) &&
+           is_submodule_ignored(concatpath, options))
                return;
 
        if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
@@ -4213,9 +4367,9 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        return strbuf_detach(&buf, outsize);
 }
 
-static size_t fill_textconv(struct userdiff_driver *driver,
-                           struct diff_filespec *df,
-                           char **outbuf)
+size_t fill_textconv(struct userdiff_driver *driver,
+                    struct diff_filespec *df,
+                    char **outbuf)
 {
        size_t size;