Code

submodule: fix relative url parsing for scp-style origin
[git.git] / diff.c
diff --git a/diff.c b/diff.c
index 7a321f4254cf25101f9c9ed5df1621e3bf4b2e29..a2d8c7f9a7a3bbfca889dedfbda8d6f351fbf016 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -14,6 +14,7 @@
 #include "userdiff.h"
 #include "sigchain.h"
 #include "submodule.h"
+#include "ll-merge.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -194,6 +195,7 @@ struct emit_callback {
        struct diff_words_data *diff_words;
        int *found_changesp;
        FILE *file;
+       struct strbuf *header;
 };
 
 static int count_lines(const char *data, int size)
@@ -694,7 +696,6 @@ static void diff_words_show(struct diff_words_data *diff_words)
 {
        xpparam_t xpp;
        xdemitconf_t xecfg;
-       xdemitcb_t ecb;
        mmfile_t minus, plus;
 
        /* special case: only removal */
@@ -716,7 +717,7 @@ static void diff_words_show(struct diff_words_data *diff_words)
        /* 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,
-                     &xpp, &xecfg, &ecb);
+                     &xpp, &xecfg);
        free(minus.ptr);
        free(plus.ptr);
        if (diff_words->current_plus != diff_words->plus.text.ptr +
@@ -797,6 +798,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
 
+       if (ecbdata->header) {
+               fprintf(ecbdata->file, "%s", ecbdata->header->buf);
+               strbuf_reset(ecbdata->header);
+               ecbdata->header = NULL;
+       }
        *(ecbdata->found_changesp) = 1;
 
        if (ecbdata->label_path[0]) {
@@ -942,7 +948,7 @@ struct diffstat_t {
                unsigned is_unmerged:1;
                unsigned is_binary:1;
                unsigned is_renamed:1;
-               unsigned int added, deleted;
+               uintmax_t added, deleted;
        } **files;
 };
 
@@ -1034,7 +1040,7 @@ static void fill_print_name(struct diffstat_file *file)
 static void show_stats(struct diffstat_t *data, struct diff_options *options)
 {
        int i, len, add, del, adds = 0, dels = 0;
-       int max_change = 0, max_len = 0;
+       uintmax_t max_change = 0, max_len = 0;
        int total_files = data->nr;
        int width, name_width;
        const char *reset, *set, *add_c, *del_c;
@@ -1063,7 +1069,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
 
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
-               int change = file->added + file->deleted;
+               uintmax_t change = file->added + file->deleted;
                fill_print_name(file);
                len = strlen(file->print_name);
                if (max_len < len)
@@ -1091,8 +1097,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        for (i = 0; i < data->nr; i++) {
                const char *prefix = "";
                char *name = data->files[i]->print_name;
-               int added = data->files[i]->added;
-               int deleted = data->files[i]->deleted;
+               uintmax_t added = data->files[i]->added;
+               uintmax_t deleted = data->files[i]->deleted;
                int name_len;
 
                /*
@@ -1113,9 +1119,11 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                if (data->files[i]->is_binary) {
                        show_name(options->file, prefix, name, len);
                        fprintf(options->file, "  Bin ");
-                       fprintf(options->file, "%s%d%s", del_c, deleted, reset);
+                       fprintf(options->file, "%s%"PRIuMAX"%s",
+                               del_c, deleted, reset);
                        fprintf(options->file, " -> ");
-                       fprintf(options->file, "%s%d%s", add_c, added, reset);
+                       fprintf(options->file, "%s%"PRIuMAX"%s",
+                               add_c, added, reset);
                        fprintf(options->file, " bytes");
                        fprintf(options->file, "\n");
                        continue;
@@ -1144,7 +1152,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                        del = scale_linear(del, width, max_change);
                }
                show_name(options->file, prefix, name, len);
-               fprintf(options->file, "%5d%s", added + deleted,
+               fprintf(options->file, "%5"PRIuMAX"%s", added + deleted,
                                added + deleted ? " " : "");
                show_graph(options->file, '+', add, add_c, reset);
                show_graph(options->file, '-', del, del_c, reset);
@@ -1194,7 +1202,8 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options)
                        fprintf(options->file, "-\t-\t");
                else
                        fprintf(options->file,
-                               "%d\t%d\t", file->added, file->deleted);
+                               "%"PRIuMAX"\t%"PRIuMAX"\t",
+                               file->added, file->deleted);
                if (options->line_termination) {
                        fill_print_name(file);
                        if (!file->is_renamed)
@@ -1364,37 +1373,32 @@ static void free_diffstat_info(struct diffstat_t *diffstat)
 struct checkdiff_t {
        const char *filename;
        int lineno;
+       int conflict_marker_size;
        struct diff_options *o;
        unsigned ws_rule;
        unsigned status;
 };
 
-static int is_conflict_marker(const char *line, unsigned long len)
+static int is_conflict_marker(const char *line, int marker_size, unsigned long len)
 {
        char firstchar;
        int cnt;
 
-       if (len < 8)
+       if (len < marker_size + 1)
                return 0;
        firstchar = line[0];
        switch (firstchar) {
-       case '=': case '>': case '<':
+       case '=': case '>': case '<': case '|':
                break;
        default:
                return 0;
        }
-       for (cnt = 1; cnt < 7; cnt++)
+       for (cnt = 1; cnt < marker_size; cnt++)
                if (line[cnt] != firstchar)
                        return 0;
-       /* line[0] thru line[6] are same as firstchar */
-       if (firstchar == '=') {
-               /* divider between ours and theirs? */
-               if (len != 8 || line[7] != '\n')
-                       return 0;
-       } else if (len < 8 || !isspace(line[7])) {
-               /* not divider before ours nor after theirs */
+       /* line[1] thru line[marker_size-1] are same as firstchar */
+       if (len < marker_size + 1 || !isspace(line[marker_size]))
                return 0;
-       }
        return 1;
 }
 
@@ -1402,6 +1406,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
 {
        struct checkdiff_t *data = priv;
        int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
+       int marker_size = data->conflict_marker_size;
        const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
        const char *reset = diff_get_color(color_diff, DIFF_RESET);
        const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
@@ -1410,7 +1415,7 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
        if (line[0] == '+') {
                unsigned bad;
                data->lineno++;
-               if (is_conflict_marker(line + 1, len - 1)) {
+               if (is_conflict_marker(line + 1, marker_size, len - 1)) {
                        data->status |= 1;
                        fprintf(data->o->file,
                                "%s:%d: leftover conflict marker\n",
@@ -1601,6 +1606,7 @@ static void builtin_diff(const char *name_a,
        const char *reset = diff_get_color_opt(o, DIFF_RESET);
        const char *a_prefix, *b_prefix;
        const char *textconv_one = NULL, *textconv_two = NULL;
+       struct strbuf header = STRBUF_INIT;
 
        if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
                        (!one->mode || S_ISGITLINK(one->mode)) &&
@@ -1608,7 +1614,7 @@ static void builtin_diff(const char *name_a,
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one ? one->path : two->path,
-                               one->sha1, two->sha1,
+                               one->sha1, two->sha1, two->dirty_submodule,
                                del, add, reset);
                return;
        }
@@ -1635,25 +1641,26 @@ static void builtin_diff(const char *name_a,
        b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
-       fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
+       strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
-               fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset);
+               strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else if (lbl[1][0] == '/') {
-               fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
+               strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else {
                if (one->mode != two->mode) {
-                       fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset);
-                       fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset);
+                       strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
+                       strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
                }
                if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
+
                /*
                 * we do not run diff between different kind
                 * of objects.
@@ -1663,6 +1670,8 @@ static void builtin_diff(const char *name_a,
                if (complete_rewrite &&
                    (textconv_one || !diff_filespec_is_binary(one)) &&
                    (textconv_two || !diff_filespec_is_binary(two))) {
+                       fprintf(o->file, "%s", header.buf);
+                       strbuf_reset(&header);
                        emit_rewrite_diff(name_a, name_b, one, two,
                                                textconv_one, textconv_two, o);
                        o->found_changes = 1;
@@ -1680,6 +1689,8 @@ static void builtin_diff(const char *name_a,
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
                        goto free_ab_and_return;
+               fprintf(o->file, "%s", header.buf);
+               strbuf_reset(&header);
                if (DIFF_OPT_TST(o, BINARY))
                        emit_binary_diff(o->file, &mf1, &mf2);
                else
@@ -1692,10 +1703,14 @@ static void builtin_diff(const char *name_a,
                const char *diffopts = getenv("GIT_DIFF_OPTS");
                xpparam_t xpp;
                xdemitconf_t xecfg;
-               xdemitcb_t ecb;
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
 
+               if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) {
+                       fprintf(o->file, "%s", header.buf);
+                       strbuf_reset(&header);
+               }
+
                if (textconv_one) {
                        size_t size;
                        mf1.ptr = run_textconv(textconv_one, one, &size);
@@ -1725,6 +1740,7 @@ static void builtin_diff(const char *name_a,
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.file = o->file;
+               ecbdata.header = header.len ? &header : NULL;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
@@ -1758,7 +1774,7 @@ static void builtin_diff(const char *name_a,
                        }
                }
                xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
-                             &xpp, &xecfg, &ecb);
+                             &xpp, &xecfg);
                if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
                        free_diff_words_data(&ecbdata);
                if (textconv_one)
@@ -1769,6 +1785,7 @@ static void builtin_diff(const char *name_a,
        }
 
  free_ab_and_return:
+       strbuf_release(&header);
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
        free(a_one);
@@ -1810,13 +1827,12 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
-               xdemitcb_t ecb;
 
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
-                             &xpp, &xecfg, &ecb);
+                             &xpp, &xecfg);
        }
 
  free_and_return:
@@ -1841,6 +1857,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
        data.lineno = 0;
        data.o = o;
        data.ws_rule = whitespace_rule(attr_path);
+       data.conflict_marker_size = ll_merge_marker_size(attr_path);
 
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
@@ -1857,14 +1874,13 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
-               xdemitcb_t ecb;
 
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xecfg.ctxlen = 1; /* at least one context line */
                xpp.flags = XDF_NEED_MINIMAL;
                xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
-                             &xpp, &xecfg, &ecb);
+                             &xpp, &xecfg);
 
                if (data.ws_rule & WS_BLANK_AT_EOF) {
                        struct emit_callback ecbdata;
@@ -1978,7 +1994,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
         * If ce is marked as "assume unchanged", there is no
         * guarantee that work tree matches what we are looking for.
         */
-       if (ce->ce_flags & CE_VALID)
+       if ((ce->ce_flags & CE_VALID) || ce_skip_worktree(ce))
                return 0;
 
        /*
@@ -2010,9 +2026,14 @@ static int populate_from_stdin(struct diff_filespec *s)
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
        int len;
-       char *data = xmalloc(100);
+       char *data = xmalloc(100), *dirty = "";
+
+       /* Are we looking at the work tree? */
+       if (!s->sha1_valid && s->dirty_submodule)
+               dirty = "-dirty";
+
        len = snprintf(data, 100,
-               "Subproject commit %s\n", sha1_to_hex(s->sha1));
+                      "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
        s->data = data;
        s->size = len;
        s->should_free = 1;
@@ -2275,7 +2296,7 @@ static void run_external_diff(const char *pgm,
        }
        *arg = NULL;
        fflush(NULL);
-       retval = run_command_v_opt(spawn_arg, 0);
+       retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL);
        remove_tempfile();
        if (retval) {
                fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@ -2551,6 +2572,20 @@ int diff_setup_done(struct diff_options *options)
        if (count > 1)
                die("--name-only, --name-status, --check and -s are mutually exclusive");
 
+       /*
+        * Most of the time we can say "there are changes"
+        * only by checking if there are changed paths, but
+        * --ignore-whitespace* options force us to look
+        * inside contents.
+        */
+
+       if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
+               DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
+       else
+               DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
+
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
 
@@ -2611,7 +2646,7 @@ int diff_setup_done(struct diff_options *options)
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
-       if (DIFF_OPT_TST(options, QUIET)) {
+       if (DIFF_OPT_TST(options, QUICK)) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
@@ -2802,7 +2837,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
-               DIFF_OPT_SET(options, QUIET);
+               DIFF_OPT_SET(options, QUICK);
        else if (!strcmp(arg, "--ext-diff"))
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
@@ -3322,7 +3357,6 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
        for (i = 0; i < q->nr; i++) {
                xpparam_t xpp;
                xdemitconf_t xecfg;
-               xdemitcb_t ecb;
                mmfile_t mf1, mf2;
                struct diff_filepair *p = q->queue[i];
                int len1, len2;
@@ -3384,7 +3418,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
                xecfg.ctxlen = 3;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
                xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
-                             &xpp, &xecfg, &ecb);
+                             &xpp, &xecfg);
        }
 
        git_SHA1_Final(sha1, &ctx);
@@ -3484,6 +3518,29 @@ void diff_flush(struct diff_options *options)
                separator++;
        }
 
+       if (output_format & DIFF_FORMAT_NO_OUTPUT &&
+           DIFF_OPT_TST(options, EXIT_WITH_STATUS) &&
+           DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+               /*
+                * run diff_flush_patch for the exit status. setting
+                * options->file to /dev/null should be safe, becaue we
+                * aren't supposed to produce any output anyway.
+                */
+               if (options->close_file)
+                       fclose(options->file);
+               options->file = fopen("/dev/null", "w");
+               if (!options->file)
+                       die_errno("Could not open /dev/null");
+               options->close_file = 1;
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (check_pair_status(p))
+                               diff_flush_patch(p, options);
+                       if (options->found_changes)
+                               break;
+               }
+       }
+
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
                        putc(options->line_termination, options->file);
@@ -3511,6 +3568,18 @@ free_queue:
        q->nr = q->alloc = 0;
        if (options->close_file)
                fclose(options->file);
+
+       /*
+        * Report the content-level differences with HAS_CHANGES;
+        * diff_addremove/diff_change does not set the bit when
+        * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
+        */
+       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+               if (options->found_changes)
+                       DIFF_OPT_SET(options, HAS_CHANGES);
+               else
+                       DIFF_OPT_CLR(options, HAS_CHANGES);
+       }
 }
 
 static void diffcore_apply_filter(const char *filter)
@@ -3594,7 +3663,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
                struct diff_filepair *p = q->queue[i];
 
                /*
-                * 1. Entries that come from stat info dirtyness
+                * 1. Entries that come from stat info dirtiness
                 *    always have both sides (iow, not create/delete),
                 *    one side of the object name is unknown, with
                 *    the same mode and size.  Keep the ones that
@@ -3630,6 +3699,23 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
        *q = outq;
 }
 
+static int diffnamecmp(const void *a_, const void *b_)
+{
+       const struct diff_filepair *a = *((const struct diff_filepair **)a_);
+       const struct diff_filepair *b = *((const struct diff_filepair **)b_);
+       const char *name_a, *name_b;
+
+       name_a = a->one ? a->one->path : a->two->path;
+       name_b = b->one ? b->one->path : b->two->path;
+       return strcmp(name_a, name_b);
+}
+
+void diffcore_fix_diff_index(struct diff_options *options)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
+}
+
 void diffcore_std(struct diff_options *options)
 {
        if (options->skip_stat_unmatch)
@@ -3647,7 +3733,7 @@ void diffcore_std(struct diff_options *options)
        diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
 
-       if (diff_queued_diff.nr)
+       if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
@@ -3671,7 +3757,7 @@ int diff_result_code(struct diff_options *opt, int status)
 void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
-                   const char *concatpath)
+                   const char *concatpath, unsigned dirty_submodule)
 {
        struct diff_filespec *one, *two;
 
@@ -3703,18 +3789,22 @@ void diff_addremove(struct diff_options *options,
 
        if (addremove != '+')
                fill_filespec(one, sha1, mode);
-       if (addremove != '-')
+       if (addremove != '-') {
                fill_filespec(two, sha1, mode);
+               two->dirty_submodule = dirty_submodule;
+       }
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 void diff_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *concatpath)
+                const char *concatpath,
+                unsigned old_dirty_submodule, unsigned new_dirty_submodule)
 {
        struct diff_filespec *one, *two;
 
@@ -3727,6 +3817,8 @@ void diff_change(struct diff_options *options,
                const unsigned char *tmp_c;
                tmp = old_mode; old_mode = new_mode; new_mode = tmp;
                tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+               tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
+                       new_dirty_submodule = tmp;
        }
 
        if (options->prefix &&
@@ -3737,9 +3829,12 @@ void diff_change(struct diff_options *options,
        two = alloc_filespec(concatpath);
        fill_filespec(one, old_sha1, old_mode);
        fill_filespec(two, new_sha1, new_mode);
+       one->dirty_submodule = old_dirty_submodule;
+       two->dirty_submodule = new_dirty_submodule;
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 void diff_unmerge(struct diff_options *options,
@@ -3766,6 +3861,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        const char **arg = argv;
        struct child_process child;
        struct strbuf buf = STRBUF_INIT;
+       int err = 0;
 
        temp = prepare_temp_file(spec->path, spec);
        *arg++ = pgm;
@@ -3773,18 +3869,23 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        *arg = NULL;
 
        memset(&child, 0, sizeof(child));
+       child.use_shell = 1;
        child.argv = argv;
        child.out = -1;
-       if (start_command(&child) != 0 ||
-           strbuf_read(&buf, child.out, 0) < 0 ||
-           finish_command(&child) != 0) {
-               close(child.out);
-               strbuf_release(&buf);
+       if (start_command(&child)) {
                remove_tempfile();
-               error("error running textconv command '%s'", pgm);
                return NULL;
        }
+
+       if (strbuf_read(&buf, child.out, 0) < 0)
+               err = error("error reading from textconv command '%s'", pgm);
        close(child.out);
+
+       if (finish_command(&child) || err) {
+               strbuf_release(&buf);
+               remove_tempfile();
+               return NULL;
+       }
        remove_tempfile();
 
        return strbuf_detach(&buf, outsize);