Imported Upstream version 2.14.4
[platform/upstream/git.git] / diff.c
diff --git a/diff.c b/diff.c
index 35d3f07..8406a83 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -2,6 +2,8 @@
  * Copyright (C) 2005 Junio C Hamano
  */
 #include "cache.h"
+#include "config.h"
+#include "tempfile.h"
 #include "quote.h"
 #include "diff.h"
 #include "diffcore.h"
 #include "run-command.h"
 #include "utf8.h"
 #include "userdiff.h"
-#include "sigchain.h"
+#include "submodule-config.h"
 #include "submodule.h"
 #include "ll-merge.h"
+#include "string-list.h"
+#include "argv-array.h"
+#include "graph.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
 #endif
 
 static int diff_detect_rename_default;
+static int diff_indent_heuristic = 1;
 static int diff_rename_limit_default = 400;
 static int diff_suppress_blank_empty;
 static int diff_use_color_default = -1;
+static int diff_context_default = 3;
+static int diff_interhunk_context_default;
 static const char *diff_word_regex_cfg;
 static const char *external_diff_cmd_cfg;
+static const char *diff_order_file_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
 static int diff_no_prefix;
 static int diff_stat_graph_width;
 static int diff_dirstat_permille_default = 30;
 static struct diff_options default_diff_options;
+static long diff_algorithm;
+static unsigned ws_error_highlight_default = WSEH_NEW;
 
 static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
-       GIT_COLOR_NORMAL,       /* PLAIN */
+       GIT_COLOR_NORMAL,       /* CONTEXT */
        GIT_COLOR_BOLD,         /* METAINFO */
        GIT_COLOR_CYAN,         /* FRAGINFO */
        GIT_COLOR_RED,          /* OLD */
@@ -47,47 +58,56 @@ static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_NORMAL,       /* FUNCINFO */
 };
 
-static int parse_diff_color_slot(const char *var, int ofs)
+static NORETURN void die_want_option(const char *option_name)
 {
-       if (!strcasecmp(var+ofs, "plain"))
-               return DIFF_PLAIN;
-       if (!strcasecmp(var+ofs, "meta"))
+       die(_("option '%s' requires a value"), option_name);
+}
+
+static int parse_diff_color_slot(const char *var)
+{
+       if (!strcasecmp(var, "context") || !strcasecmp(var, "plain"))
+               return DIFF_CONTEXT;
+       if (!strcasecmp(var, "meta"))
                return DIFF_METAINFO;
-       if (!strcasecmp(var+ofs, "frag"))
+       if (!strcasecmp(var, "frag"))
                return DIFF_FRAGINFO;
-       if (!strcasecmp(var+ofs, "old"))
+       if (!strcasecmp(var, "old"))
                return DIFF_FILE_OLD;
-       if (!strcasecmp(var+ofs, "new"))
+       if (!strcasecmp(var, "new"))
                return DIFF_FILE_NEW;
-       if (!strcasecmp(var+ofs, "commit"))
+       if (!strcasecmp(var, "commit"))
                return DIFF_COMMIT;
-       if (!strcasecmp(var+ofs, "whitespace"))
+       if (!strcasecmp(var, "whitespace"))
                return DIFF_WHITESPACE;
-       if (!strcasecmp(var+ofs, "func"))
+       if (!strcasecmp(var, "func"))
                return DIFF_FUNCINFO;
        return -1;
 }
 
-static int parse_dirstat_params(struct diff_options *options, const char *params,
+static int parse_dirstat_params(struct diff_options *options, const char *params_string,
                                struct strbuf *errmsg)
 {
-       const char *p = params;
-       int p_len, ret = 0;
+       char *params_copy = xstrdup(params_string);
+       struct string_list params = STRING_LIST_INIT_NODUP;
+       int ret = 0;
+       int i;
 
-       while (*p) {
-               p_len = strchrnul(p, ',') - p;
-               if (!memcmp(p, "changes", p_len)) {
+       if (*params_copy)
+               string_list_split_in_place(&params, params_copy, ',', -1);
+       for (i = 0; i < params.nr; i++) {
+               const char *p = params.items[i].string;
+               if (!strcmp(p, "changes")) {
                        DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
                        DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
-               } else if (!memcmp(p, "lines", p_len)) {
+               } else if (!strcmp(p, "lines")) {
                        DIFF_OPT_SET(options, DIRSTAT_BY_LINE);
                        DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
-               } else if (!memcmp(p, "files", p_len)) {
+               } else if (!strcmp(p, "files")) {
                        DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
                        DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
-               } else if (!memcmp(p, "noncumulative", p_len)) {
+               } else if (!strcmp(p, "noncumulative")) {
                        DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
-               } else if (!memcmp(p, "cumulative", p_len)) {
+               } else if (!strcmp(p, "cumulative")) {
                        DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
                } else if (isdigit(*p)) {
                        char *end;
@@ -99,27 +119,37 @@ static int parse_dirstat_params(struct diff_options *options, const char *params
                                while (isdigit(*++end))
                                        ; /* nothing */
                        }
-                       if (end - p == p_len)
+                       if (!*end)
                                options->dirstat_permille = permille;
                        else {
-                               strbuf_addf(errmsg, _("  Failed to parse dirstat cut-off percentage '%.*s'\n"),
-                                           p_len, p);
+                               strbuf_addf(errmsg, _("  Failed to parse dirstat cut-off percentage '%s'\n"),
+                                           p);
                                ret++;
                        }
                } else {
-                       strbuf_addf(errmsg, _("  Unknown dirstat parameter '%.*s'\n"),
-                                   p_len, p);
+                       strbuf_addf(errmsg, _("  Unknown dirstat parameter '%s'\n"), p);
                        ret++;
                }
 
-               p += p_len;
-
-               if (*p)
-                       p++; /* more parameters, swallow separator */
        }
+       string_list_clear(&params, 0);
+       free(params_copy);
        return ret;
 }
 
+static int parse_submodule_params(struct diff_options *options, const char *value)
+{
+       if (!strcmp(value, "log"))
+               options->submodule_format = DIFF_SUBMODULE_LOG;
+       else if (!strcmp(value, "short"))
+               options->submodule_format = DIFF_SUBMODULE_SHORT;
+       else if (!strcmp(value, "diff"))
+               options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF;
+       else
+               return -1;
+       return 0;
+}
+
 static int git_config_rename(const char *var, const char *value)
 {
        if (!value)
@@ -129,18 +159,94 @@ static int git_config_rename(const char *var, const char *value)
        return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0;
 }
 
+long parse_algorithm_value(const char *value)
+{
+       if (!value)
+               return -1;
+       else if (!strcasecmp(value, "myers") || !strcasecmp(value, "default"))
+               return 0;
+       else if (!strcasecmp(value, "minimal"))
+               return XDF_NEED_MINIMAL;
+       else if (!strcasecmp(value, "patience"))
+               return XDF_PATIENCE_DIFF;
+       else if (!strcasecmp(value, "histogram"))
+               return XDF_HISTOGRAM_DIFF;
+       return -1;
+}
+
+static int parse_one_token(const char **arg, const char *token)
+{
+       const char *rest;
+       if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
+               *arg = rest;
+               return 1;
+       }
+       return 0;
+}
+
+static int parse_ws_error_highlight(const char *arg)
+{
+       const char *orig_arg = arg;
+       unsigned val = 0;
+
+       while (*arg) {
+               if (parse_one_token(&arg, "none"))
+                       val = 0;
+               else if (parse_one_token(&arg, "default"))
+                       val = WSEH_NEW;
+               else if (parse_one_token(&arg, "all"))
+                       val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
+               else if (parse_one_token(&arg, "new"))
+                       val |= WSEH_NEW;
+               else if (parse_one_token(&arg, "old"))
+                       val |= WSEH_OLD;
+               else if (parse_one_token(&arg, "context"))
+                       val |= WSEH_CONTEXT;
+               else {
+                       return -1 - (int)(arg - orig_arg);
+               }
+               if (*arg)
+                       arg++;
+       }
+       return val;
+}
+
 /*
  * These are to give UI layer defaults.
  * The core-level commands such as git-diff-files should
  * never be affected by the setting of diff.renames
  * the user happens to have in the configuration file.
  */
+void init_diff_ui_defaults(void)
+{
+       diff_detect_rename_default = 1;
+}
+
+int git_diff_heuristic_config(const char *var, const char *value, void *cb)
+{
+       if (!strcmp(var, "diff.indentheuristic"))
+               diff_indent_heuristic = git_config_bool(var, value);
+       return 0;
+}
+
 int git_diff_ui_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
+       if (!strcmp(var, "diff.context")) {
+               diff_context_default = git_config_int(var, value);
+               if (diff_context_default < 0)
+                       return -1;
+               return 0;
+       }
+       if (!strcmp(var, "diff.interhunkcontext")) {
+               diff_interhunk_context_default = git_config_int(var, value);
+               if (diff_interhunk_context_default < 0)
+                       return -1;
+               return 0;
+       }
        if (!strcmp(var, "diff.renames")) {
                diff_detect_rename_default = git_config_rename(var, value);
                return 0;
@@ -165,10 +271,34 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                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.orderfile"))
+               return git_config_pathname(&diff_order_file_cfg, var, value);
 
        if (!strcmp(var, "diff.ignoresubmodules"))
                handle_ignore_submodules_arg(&default_diff_options, value);
 
+       if (!strcmp(var, "diff.submodule")) {
+               if (parse_submodule_params(&default_diff_options, value))
+                       warning(_("Unknown value for 'diff.submodule' config variable: '%s'"),
+                               value);
+               return 0;
+       }
+
+       if (!strcmp(var, "diff.algorithm")) {
+               diff_algorithm = parse_algorithm_value(value);
+               if (diff_algorithm < 0)
+                       return -1;
+               return 0;
+       }
+
+       if (!strcmp(var, "diff.wserrorhighlight")) {
+               int val = parse_ws_error_highlight(value);
+               if (val < 0)
+                       return -1;
+               ws_error_highlight_default = val;
+               return 0;
+       }
+
        if (git_color_config(var, value, cb) < 0)
                return -1;
 
@@ -177,6 +307,8 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
 
 int git_diff_basic_config(const char *var, const char *value, void *cb)
 {
+       const char *name;
+
        if (!strcmp(var, "diff.renamelimit")) {
                diff_rename_limit_default = git_config_int(var, value);
                return 0;
@@ -185,14 +317,14 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
        if (userdiff_config(var, value) < 0)
                return -1;
 
-       if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
-               int slot = parse_diff_color_slot(var, 11);
+       if (skip_prefix(var, "diff.color.", &name) ||
+           skip_prefix(var, "color.diff.", &name)) {
+               int slot = parse_diff_color_slot(name);
                if (slot < 0)
                        return 0;
                if (!value)
                        return config_error_nonbool(var);
-               color_parse(value, var, diff_colors[slot]);
-               return 0;
+               return color_parse(value, diff_colors[slot]);
        }
 
        /* like GNU diff's --suppress-blank-empty option  */
@@ -214,9 +346,12 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       if (!prefixcmp(var, "submodule."))
+       if (starts_with(var, "submodule."))
                return parse_submodule_config_option(var, value);
 
+       if (git_diff_heuristic_config(var, value, cb) < 0)
+               return -1;
+
        return git_default_config(var, value, cb);
 }
 
@@ -252,11 +387,26 @@ static const char *external_diff(void)
        return external_diff_cmd;
 }
 
+/*
+ * Keep track of files used for diffing. Sometimes such an entry
+ * refers to a temporary file, sometimes to an existing file, and
+ * sometimes to "/dev/null".
+ */
 static struct diff_tempfile {
-       const char *name; /* filename external diff should read from */
-       char hex[41];
+       /*
+        * filename external diff should read from, or NULL if this
+        * entry is currently not in use:
+        */
+       const char *name;
+
+       char hex[GIT_MAX_HEXSZ + 1];
        char mode[10];
-       char tmp_path[PATH_MAX];
+
+       /*
+        * If this diff_tempfile instance refers to a temporary file,
+        * this tempfile object is used to manage its lifetime.
+        */
+       struct tempfile tempfile;
 } diff_temp[2];
 
 typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
@@ -272,7 +422,6 @@ struct emit_callback {
        const char **label_path;
        struct diff_words_data *diff_words;
        struct diff_options *opt;
-       int *found_changesp;
        struct strbuf *header;
 };
 
@@ -319,7 +468,7 @@ static unsigned long diff_filespec_size(struct diff_filespec *one)
 {
        if (!DIFF_FILE_VALID(one))
                return 0;
-       diff_populate_filespec(one, 1);
+       diff_populate_filespec(one, CHECK_SIZE_ONLY);
        return one->size;
 }
 
@@ -375,12 +524,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
        int nofirst;
        FILE *file = o->file;
 
-       if (o->output_prefix) {
-               struct strbuf *msg = NULL;
-               msg = o->output_prefix(o, o->output_prefix_data);
-               assert(msg);
-               fwrite(msg->buf, msg->len, 1, file);
-       }
+       fputs(diff_line_prefix(o), file);
 
        if (len == 0) {
                has_trailing_newline = (first == '\n');
@@ -427,30 +571,63 @@ static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line
        return ws_blank_line(line, len, ecbdata->ws_rule);
 }
 
-static void emit_add_line(const char *reset,
-                         struct emit_callback *ecbdata,
-                         const char *line, int len)
+static void emit_line_checked(const char *reset,
+                             struct emit_callback *ecbdata,
+                             const char *line, int len,
+                             enum color_diff color,
+                             unsigned ws_error_highlight,
+                             char sign)
 {
-       const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
-       const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+       const char *set = diff_get_color(ecbdata->color_diff, color);
+       const char *ws = NULL;
+
+       if (ecbdata->opt->ws_error_highlight & ws_error_highlight) {
+               ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+               if (!*ws)
+                       ws = NULL;
+       }
 
-       if (!*ws)
-               emit_line_0(ecbdata->opt, set, reset, '+', line, len);
-       else if (new_blank_line_at_eof(ecbdata, line, len))
+       if (!ws)
+               emit_line_0(ecbdata->opt, set, reset, sign, line, len);
+       else if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len))
                /* Blank line at EOF - paint '+' as well */
-               emit_line_0(ecbdata->opt, ws, reset, '+', line, len);
+               emit_line_0(ecbdata->opt, ws, reset, sign, line, len);
        else {
                /* Emit just the prefix, then the rest. */
-               emit_line_0(ecbdata->opt, set, reset, '+', "", 0);
+               emit_line_0(ecbdata->opt, set, reset, sign, "", 0);
                ws_check_emit(line, len, ecbdata->ws_rule,
                              ecbdata->opt->file, set, reset, ws);
        }
 }
 
+static void emit_add_line(const char *reset,
+                         struct emit_callback *ecbdata,
+                         const char *line, int len)
+{
+       emit_line_checked(reset, ecbdata, line, len,
+                         DIFF_FILE_NEW, WSEH_NEW, '+');
+}
+
+static void emit_del_line(const char *reset,
+                         struct emit_callback *ecbdata,
+                         const char *line, int len)
+{
+       emit_line_checked(reset, ecbdata, line, len,
+                         DIFF_FILE_OLD, WSEH_OLD, '-');
+}
+
+static void emit_context_line(const char *reset,
+                             struct emit_callback *ecbdata,
+                             const char *line, int len)
+{
+       emit_line_checked(reset, ecbdata, line, len,
+                         DIFF_CONTEXT, WSEH_CONTEXT, ' ');
+}
+
 static void emit_hunk_header(struct emit_callback *ecbdata,
                             const char *line, int len)
 {
-       const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+       const char *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT);
        const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
        const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
@@ -467,15 +644,15 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
        if (len < 10 ||
            memcmp(line, atat, 2) ||
            !(ep = memmem(line + 2, len - 2, atat, 2))) {
-               emit_line(ecbdata->opt, plain, reset, line, len);
+               emit_line(ecbdata->opt, context, reset, line, len);
                return;
        }
        ep += 2; /* skip over @@ */
 
        /* The hunk header in fraginfo color */
-       strbuf_add(&msgbuf, frag, strlen(frag));
+       strbuf_addstr(&msgbuf, frag);
        strbuf_add(&msgbuf, line, ep - line);
-       strbuf_add(&msgbuf, reset, strlen(reset));
+       strbuf_addstr(&msgbuf, reset);
 
        /*
         * trailing "\r\n"
@@ -489,15 +666,15 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
                if (*ep != ' ' && *ep != '\t')
                        break;
        if (ep != cp) {
-               strbuf_add(&msgbuf, plain, strlen(plain));
+               strbuf_addstr(&msgbuf, context);
                strbuf_add(&msgbuf, cp, ep - cp);
-               strbuf_add(&msgbuf, reset, strlen(reset));
+               strbuf_addstr(&msgbuf, reset);
        }
 
        if (ep < line + len) {
-               strbuf_add(&msgbuf, func, strlen(func));
+               strbuf_addstr(&msgbuf, func);
                strbuf_add(&msgbuf, ep, line + len - ep);
-               strbuf_add(&msgbuf, reset, strlen(reset));
+               strbuf_addstr(&msgbuf, reset);
        }
 
        strbuf_add(&msgbuf, line + len, org_len - len);
@@ -513,25 +690,16 @@ static struct diff_tempfile *claim_diff_tempfile(void) {
        die("BUG: diff is failing to clean up its tempfiles");
 }
 
-static int remove_tempfile_installed;
-
 static void remove_tempfile(void)
 {
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
-               if (diff_temp[i].name == diff_temp[i].tmp_path)
-                       unlink_or_warn(diff_temp[i].name);
+               if (is_tempfile_active(&diff_temp[i].tempfile))
+                       delete_tempfile(&diff_temp[i].tempfile);
                diff_temp[i].name = NULL;
        }
 }
 
-static void remove_tempfile_on_signal(int signo)
-{
-       remove_tempfile();
-       sigchain_pop(signo);
-       raise(signo);
-}
-
 static void print_line_count(FILE *file, int count)
 {
        switch (count) {
@@ -552,7 +720,6 @@ static void emit_rewrite_lines(struct emit_callback *ecb,
 {
        const char *endp = NULL;
        static const char *nneof = " No newline at end of file\n";
-       const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD);
        const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
 
        while (0 < size) {
@@ -562,8 +729,7 @@ static void emit_rewrite_lines(struct emit_callback *ecb,
                len = endp ? (endp - data + 1) : size;
                if (prefix != '+') {
                        ecb->lno_in_preimage++;
-                       emit_line_0(ecb->opt, old, reset, '-',
-                                   data, len);
+                       emit_del_line(reset, ecb, data, len);
                } else {
                        ecb->lno_in_postimage++;
                        emit_add_line(reset, ecb, data, len);
@@ -572,10 +738,10 @@ static void emit_rewrite_lines(struct emit_callback *ecb,
                data += len;
        }
        if (!endp) {
-               const char *plain = diff_get_color(ecb->color_diff,
-                                                  DIFF_PLAIN);
+               const char *context = diff_get_color(ecb->color_diff,
+                                                    DIFF_CONTEXT);
                putc('\n', ecb->opt->file);
-               emit_line_0(ecb->opt, plain, reset, '\\',
+               emit_line_0(ecb->opt, context, reset, '\\',
                            nneof, strlen(nneof));
        }
 }
@@ -598,13 +764,7 @@ static void emit_rewrite_diff(const char *name_a,
        char *data_one, *data_two;
        size_t size_one, size_two;
        struct emit_callback ecbdata;
-       char *line_prefix = "";
-       struct strbuf *msgbuf;
-
-       if (o && o->output_prefix) {
-               msgbuf = o->output_prefix(o, o->output_prefix_data);
-               line_prefix = msgbuf->buf;
-       }
+       const char *line_prefix = diff_line_prefix(o);
 
        if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
                a_prefix = o->b_prefix;
@@ -629,8 +789,7 @@ static void emit_rewrite_diff(const char *name_a,
 
        memset(&ecbdata, 0, sizeof(ecbdata));
        ecbdata.color_diff = want_color(o->use_color);
-       ecbdata.found_changesp = &o->found_changes;
-       ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
+       ecbdata.ws_rule = whitespace_rule(name_b);
        ecbdata.opt = o;
        if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
                mmfile_t mf1, mf2;
@@ -669,7 +828,7 @@ static void emit_rewrite_diff(const char *name_a,
 
 struct diff_words_buffer {
        mmfile_t text;
-       long alloc;
+       unsigned long alloc;
        struct diff_words_orig {
                const char *begin, *end;
        } *orig;
@@ -753,7 +912,7 @@ static int fn_out_diff_words_write_helper(FILE *fp,
 /*
  * '--color-words' algorithm can be described as:
  *
- *   1. collect the minus/plus lines of a diff hunk, divided into
+ *   1. collect the minus/plus lines of a diff hunk, divided into
  *      minus-lines and plus-lines;
  *
  *   2. break both minus-lines and plus-lines into words and
@@ -800,18 +959,14 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
        int minus_first, minus_len, plus_first, plus_len;
        const char *minus_begin, *minus_end, *plus_begin, *plus_end;
        struct diff_options *opt = diff_words->opt;
-       struct strbuf *msgbuf;
-       char *line_prefix = "";
+       const char *line_prefix;
 
        if (line[0] != '@' || parse_hunk_header(line, len,
                        &minus_first, &minus_len, &plus_first, &plus_len))
                return;
 
        assert(opt);
-       if (opt->output_prefix) {
-               msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
-               line_prefix = msgbuf->buf;
-       }
+       line_prefix = diff_line_prefix(opt);
 
        /* POSIX requires that first be decremented by one if len == 0... */
        if (minus_len) {
@@ -862,7 +1017,8 @@ static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex,
 {
        if (word_regex && *begin < buffer->size) {
                regmatch_t match[1];
-               if (!regexec(word_regex, buffer->ptr + *begin, 1, match, 0)) {
+               if (!regexec_buf(word_regex, buffer->ptr + *begin,
+                                buffer->size - *begin, 1, match, 0)) {
                        char *p = memchr(buffer->ptr + *begin + match[0].rm_so,
                                        '\n', match[0].rm_eo - match[0].rm_so);
                        *end = p ? p - buffer->ptr : match[0].rm_eo + *begin;
@@ -935,14 +1091,10 @@ static void diff_words_show(struct diff_words_data *diff_words)
        struct diff_words_style *style = diff_words->style;
 
        struct diff_options *opt = diff_words->opt;
-       struct strbuf *msgbuf;
-       char *line_prefix = "";
+       const char *line_prefix;
 
        assert(opt);
-       if (opt->output_prefix) {
-               msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
-               line_prefix = msgbuf->buf;
-       }
+       line_prefix = diff_line_prefix(opt);
 
        /* special case: only removal */
        if (!diff_words->plus.text.size) {
@@ -965,8 +1117,9 @@ static void diff_words_show(struct diff_words_data *diff_words)
        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,
-                     &xpp, &xecfg);
+       if (xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
+                         &xpp, &xecfg))
+               die("unable to generate word diff");
        free(minus.ptr);
        free(plus.ptr);
        if (diff_words->current_plus != diff_words->plus.text.ptr +
@@ -1049,7 +1202,7 @@ static void init_diff_words_data(struct emit_callback *ecbdata,
                struct diff_words_style *st = ecbdata->diff_words->style;
                st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
                st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
-               st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
+               st->ctx.color = diff_get_color_opt(o, DIFF_CONTEXT);
        }
 }
 
@@ -1066,8 +1219,7 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
                        regfree(ecbdata->diff_words->word_regex);
                        free(ecbdata->diff_words->word_regex);
                }
-               free(ecbdata->diff_words);
-               ecbdata->diff_words = NULL;
+               FREE_AND_NULL(ecbdata->diff_words);
        }
 }
 
@@ -1078,6 +1230,16 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix)
        return "";
 }
 
+const char *diff_line_prefix(struct diff_options *opt)
+{
+       struct strbuf *msgbuf;
+       if (!opt->output_prefix)
+               return "";
+
+       msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
+       return msgbuf->buf;
+}
+
 static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
 {
        const char *cp;
@@ -1115,23 +1277,18 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
 {
        struct emit_callback *ecbdata = priv;
        const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
-       const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+       const char *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
        struct diff_options *o = ecbdata->opt;
-       char *line_prefix = "";
-       struct strbuf *msgbuf;
+       const char *line_prefix = diff_line_prefix(o);
 
-       if (o && o->output_prefix) {
-               msgbuf = o->output_prefix(o, o->output_prefix_data);
-               line_prefix = msgbuf->buf;
-       }
+       o->found_changes = 1;
 
        if (ecbdata->header) {
-               fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf);
+               fprintf(o->file, "%s", ecbdata->header->buf);
                strbuf_reset(ecbdata->header);
                ecbdata->header = NULL;
        }
-       *(ecbdata->found_changesp) = 1;
 
        if (ecbdata->label_path[0]) {
                const char *name_a_tab, *name_b_tab;
@@ -1139,9 +1296,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : "";
                name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
 
-               fprintf(ecbdata->opt->file, "%s%s--- %s%s%s\n",
+               fprintf(o->file, "%s%s--- %s%s%s\n",
                        line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab);
-               fprintf(ecbdata->opt->file, "%s%s+++ %s%s%s\n",
+               fprintf(o->file, "%s%s+++ %s%s%s\n",
                        line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab);
                ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
        }
@@ -1159,15 +1316,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                find_lno(line, ecbdata);
                emit_hunk_header(ecbdata, line, len);
                if (line[len-1] != '\n')
-                       putc('\n', ecbdata->opt->file);
-               return;
-       }
-
-       if (len < 1) {
-               emit_line(ecbdata->opt, reset, reset, line, len);
-               if (ecbdata->diff_words
-                   && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN)
-                       fputs("~\n", ecbdata->opt->file);
+                       putc('\n', o->file);
                return;
        }
 
@@ -1180,7 +1329,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->plus);
                        return;
-               } else if (!prefixcmp(line, "\\ ")) {
+               } else if (starts_with(line, "\\ ")) {
                        /*
                         * Eat the "no newline at eof" marker as if we
                         * saw a "+" or "-" line with nothing on it,
@@ -1192,8 +1341,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                }
                diff_words_flush(ecbdata);
                if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
-                       emit_line(ecbdata->opt, plain, reset, line, len);
-                       fputs("~\n", ecbdata->opt->file);
+                       emit_line(o, context, reset, line, len);
+                       fputs("~\n", o->file);
                } else {
                        /*
                         * Skip the prefix character, if any.  With
@@ -1204,22 +1353,31 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                              line++;
                              len--;
                        }
-                       emit_line(ecbdata->opt, plain, reset, line, len);
+                       emit_line(o, context, reset, line, len);
                }
                return;
        }
 
-       if (line[0] != '+') {
-               const char *color =
-                       diff_get_color(ecbdata->color_diff,
-                                      line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN);
-               ecbdata->lno_in_preimage++;
-               if (line[0] == ' ')
-                       ecbdata->lno_in_postimage++;
-               emit_line(ecbdata->opt, color, reset, line, len);
-       } else {
+       switch (line[0]) {
+       case '+':
                ecbdata->lno_in_postimage++;
                emit_add_line(reset, ecbdata, line + 1, len - 1);
+               break;
+       case '-':
+               ecbdata->lno_in_preimage++;
+               emit_del_line(reset, ecbdata, line + 1, len - 1);
+               break;
+       case ' ':
+               ecbdata->lno_in_postimage++;
+               ecbdata->lno_in_preimage++;
+               emit_context_line(reset, ecbdata, line + 1, len - 1);
+               break;
+       default:
+               /* incomplete line at the end */
+               ecbdata->lno_in_preimage++;
+               emit_line(o, diff_get_color(ecbdata->color_diff, DIFF_CONTEXT),
+                         reset, line, len);
+               break;
        }
 }
 
@@ -1229,6 +1387,7 @@ static char *pprint_rename(const char *a, const char *b)
        const char *new = b;
        struct strbuf name = STRBUF_INIT;
        int pfx_length, sfx_length;
+       int pfx_adjust_for_slash;
        int len_a = strlen(a);
        int len_b = strlen(b);
        int a_midlen, b_midlen;
@@ -1255,7 +1414,18 @@ static char *pprint_rename(const char *a, const char *b)
        old = a + len_a;
        new = b + len_b;
        sfx_length = 0;
-       while (a <= old && b <= new && *old == *new) {
+       /*
+        * If there is a common prefix, it must end in a slash.  In
+        * that case we let this loop run 1 into the prefix to see the
+        * same slash.
+        *
+        * If there is no common prefix, we cannot do this as it would
+        * underrun the input strings.
+        */
+       pfx_adjust_for_slash = (pfx_length ? 1 : 0);
+       while (a + pfx_length - pfx_adjust_for_slash <= old &&
+              b + pfx_length - pfx_adjust_for_slash <= new &&
+              *old == *new) {
                if (*old == '/')
                        sfx_length = len_a - (old - a);
                old--;
@@ -1300,6 +1470,7 @@ struct diffstat_t {
                unsigned is_unmerged:1;
                unsigned is_binary:1;
                unsigned is_renamed:1;
+               unsigned is_interesting:1;
                uintmax_t added, deleted;
        } **files;
 };
@@ -1309,12 +1480,8 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
                                          const char *name_b)
 {
        struct diffstat_file *x;
-       x = xcalloc(sizeof (*x), 1);
-       if (diffstat->nr == diffstat->alloc) {
-               diffstat->alloc = alloc_nr(diffstat->alloc);
-               diffstat->files = xrealloc(diffstat->files,
-                               diffstat->alloc * sizeof(x));
-       }
+       x = xcalloc(1, sizeof(*x));
+       ALLOC_GROW(diffstat->files, diffstat->nr + 1, diffstat->alloc);
        diffstat->files[diffstat->nr++] = x;
        if (name_b) {
                x->from_name = xstrdup(name_a);
@@ -1414,20 +1581,12 @@ int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
         * but nothing about added/removed lines? Is this a bug in Git?").
         */
        if (insertions || deletions == 0) {
-               /*
-                * TRANSLATORS: "+" in (+) is a line addition marker;
-                * do not translate it.
-                */
                strbuf_addf(&sb,
                            (insertions == 1) ? ", %d insertion(+)" : ", %d insertions(+)",
                            insertions);
        }
 
        if (deletions || insertions == 0) {
-               /*
-                * TRANSLATORS: "-" in (-) is a line removal marker;
-                * do not translate it.
-                */
                strbuf_addf(&sb,
                            (deletions == 1) ? ", %d deletion(-)" : ", %d deletions(-)",
                            deletions);
@@ -1447,16 +1606,11 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
        int extra_shown = 0;
-       struct strbuf *msg = NULL;
 
        if (data->nr == 0)
                return;
 
-       if (options->output_prefix) {
-               msg = options->output_prefix(options, options->output_prefix_data);
-               line_prefix = msg->buf;
-       }
-
+       line_prefix = diff_line_prefix(options);
        count = options->stat_count ? options->stat_count : data->nr;
 
        reset = diff_get_color_opt(options, DIFF_RESET);
@@ -1469,8 +1623,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
        for (i = 0; (i < count) && (i < data->nr); i++) {
                struct diffstat_file *file = data->files[i];
                uintmax_t change = file->added + file->deleted;
-               if (!data->files[i]->is_renamed &&
-                        (change == 0)) {
+
+               if (!file->is_interesting && (change == 0)) {
                        count++; /* not shown == room for one more */
                        continue;
                }
@@ -1497,7 +1651,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                if (max_change < change)
                        max_change = change;
        }
-       count = i; /* min(count, data->nr) */
+       count = i; /* where we can stop scanning in data->files[] */
 
        /*
         * We have width = stat_width or term_columns() columns total.
@@ -1522,14 +1676,14 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
         * Binary files are displayed with "Bin XXX -> YYY bytes"
         * instead of the change count and graph. This part is treated
         * similarly to the graph part, except that it is not
-        * "scaled". If total width is too small to accomodate the
+        * "scaled". If total width is too small to accommodate the
         * guaranteed minimum width of the filename part and the
         * separators and this message, this message will "overflow"
         * making the line longer than the maximum width.
         */
 
        if (options->stat_width == -1)
-               width = term_columns() - options->output_prefix_length;
+               width = term_columns() - strlen(line_prefix);
        else
                width = options->stat_width ? options->stat_width : 80;
        number_width = decimal_width(max_change) > number_width ?
@@ -1585,16 +1739,15 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
         */
        for (i = 0; i < count; i++) {
                const char *prefix = "";
-               char *name = data->files[i]->print_name;
-               uintmax_t added = data->files[i]->added;
-               uintmax_t deleted = data->files[i]->deleted;
+               struct diffstat_file *file = data->files[i];
+               char *name = file->print_name;
+               uintmax_t added = file->added;
+               uintmax_t deleted = file->deleted;
                int name_len;
 
-               if (!data->files[i]->is_renamed &&
-                        (added + deleted == 0)) {
-                       total_files--;
+               if (!file->is_interesting && (added + deleted == 0))
                        continue;
-               }
+
                /*
                 * "scale" the filename
                 */
@@ -1610,7 +1763,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                                name = slash;
                }
 
-               if (data->files[i]->is_binary) {
+               if (file->is_binary) {
                        fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
                        fprintf(options->file, " %*s", number_width, "Bin");
@@ -1627,7 +1780,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                        fprintf(options->file, "\n");
                        continue;
                }
-               else if (data->files[i]->is_unmerged) {
+               else if (file->is_unmerged) {
                        fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
                        fprintf(options->file, " Unmerged\n");
@@ -1639,13 +1792,9 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                 */
                add = added;
                del = deleted;
-               adds += add;
-               dels += del;
 
                if (graph_width <= max_change) {
-                       int total = add + del;
-
-                       total = scale_linear(add + del, graph_width, max_change);
+                       int total = scale_linear(add + del, graph_width, max_change);
                        if (total < 2 && add && del)
                                /* width >= 2 due to the sanity check */
                                total = 2;
@@ -1666,16 +1815,24 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options)
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
-       for (i = count; i < data->nr; i++) {
-               uintmax_t added = data->files[i]->added;
-               uintmax_t deleted = data->files[i]->deleted;
-               if (!data->files[i]->is_renamed &&
-                        (added + deleted == 0)) {
+
+       for (i = 0; i < data->nr; i++) {
+               struct diffstat_file *file = data->files[i];
+               uintmax_t added = file->added;
+               uintmax_t deleted = file->deleted;
+
+               if (file->is_unmerged ||
+                   (!file->is_interesting && (added + deleted == 0))) {
                        total_files--;
                        continue;
                }
-               adds += added;
-               dels += deleted;
+
+               if (!file->is_binary) {
+                       adds += added;
+                       dels += deleted;
+               }
+               if (i < count)
+                       continue;
                if (!extra_shown)
                        fprintf(options->file, "%s ...\n", line_prefix);
                extra_shown = 1;
@@ -1695,21 +1852,15 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option
                int added = data->files[i]->added;
                int deleted= data->files[i]->deleted;
 
-               if (data->files[i]->is_unmerged)
-                       continue;
-               if (!data->files[i]->is_renamed && (added + deleted == 0)) {
+               if (data->files[i]->is_unmerged ||
+                   (!data->files[i]->is_interesting && (added + deleted == 0))) {
                        total_files--;
                } else if (!data->files[i]->is_binary) { /* don't count bytes */
                        adds += added;
                        dels += deleted;
                }
        }
-       if (options->output_prefix) {
-               struct strbuf *msg = NULL;
-               msg = options->output_prefix(options,
-                               options->output_prefix_data);
-               fprintf(options->file, "%s", msg->buf);
-       }
+       fprintf(options->file, "%s", diff_line_prefix(options));
        print_stat_summary(options->file, total_files, adds, dels);
 }
 
@@ -1723,12 +1874,7 @@ static void show_numstat(struct diffstat_t *data, struct diff_options *options)
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
 
-               if (options->output_prefix) {
-                       struct strbuf *msg = NULL;
-                       msg = options->output_prefix(options,
-                                       options->output_prefix_data);
-                       fprintf(options->file, "%s", msg->buf);
-               }
+               fprintf(options->file, "%s", diff_line_prefix(options));
 
                if (file->is_binary)
                        fprintf(options->file, "-\t-\t");
@@ -1770,13 +1916,7 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir,
 {
        unsigned long this_dir = 0;
        unsigned int sources = 0;
-       const char *line_prefix = "";
-       struct strbuf *msg = NULL;
-
-       if (opt->output_prefix) {
-               msg = opt->output_prefix(opt, opt->output_prefix_data);
-               line_prefix = msg->buf;
-       }
+       const char *line_prefix = diff_line_prefix(opt);
 
        while (dir->nr) {
                struct dirstat_file *f = dir->files;
@@ -1851,8 +1991,8 @@ static void show_dirstat(struct diff_options *options)
 
                name = p->two->path ? p->two->path : p->one->path;
 
-               if (p->one->sha1_valid && p->two->sha1_valid)
-                       content_changed = hashcmp(p->one->sha1, p->two->sha1);
+               if (p->one->oid_valid && p->two->oid_valid)
+                       content_changed = oidcmp(&p->one->oid, &p->two->oid);
                else
                        content_changed = 1;
 
@@ -1881,16 +2021,16 @@ static void show_dirstat(struct diff_options *options)
                if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
                        diff_populate_filespec(p->one, 0);
                        diff_populate_filespec(p->two, 0);
-                       diffcore_count_changes(p->one, p->two, NULL, NULL, 0,
+                       diffcore_count_changes(p->one, p->two, NULL, NULL,
                                               &copied, &added);
                        diff_free_filespec_data(p->one);
                        diff_free_filespec_data(p->two);
                } else if (DIFF_FILE_VALID(p->one)) {
-                       diff_populate_filespec(p->one, 1);
+                       diff_populate_filespec(p->one, CHECK_SIZE_ONLY);
                        copied = added = 0;
                        diff_free_filespec_data(p->one);
                } else if (DIFF_FILE_VALID(p->two)) {
-                       diff_populate_filespec(p->two, 1);
+                       diff_populate_filespec(p->two, CHECK_SIZE_ONLY);
                        copied = 0;
                        added = p->two->size;
                        diff_free_filespec_data(p->two);
@@ -1924,7 +2064,7 @@ found_damage:
                return;
 
        /* Show all directories with more than x% of the changes */
-       qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
+       QSORT(dir.files, dir.nr, dirstat_compare);
        gather_dirstat(options, &dir, changed, "", 0);
 }
 
@@ -1955,7 +2095,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
                         * bytes per "line".
                         * This is stupid and ugly, but very cheap...
                         */
-                       damage = (damage + 63) / 64;
+                       damage = DIV_ROUND_UP(damage, 64);
                ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
                dir.files[dir.nr].name = file->name;
                dir.files[dir.nr].changed = damage;
@@ -1968,7 +2108,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
                return;
 
        /* Show all directories with more than x% of the changes */
-       qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
+       QSORT(dir.files, dir.nr, dirstat_compare);
        gather_dirstat(options, &dir, changed, "", 0);
 }
 
@@ -2026,15 +2166,10 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
        const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
        const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
        char *err;
-       char *line_prefix = "";
-       struct strbuf *msgbuf;
+       const char *line_prefix;
 
        assert(data->o);
-       if (data->o->output_prefix) {
-               msgbuf = data->o->output_prefix(data->o,
-                       data->o->output_prefix_data);
-               line_prefix = msgbuf->buf;
-       }
+       line_prefix = diff_line_prefix(data->o);
 
        if (line[0] == '+') {
                unsigned bad;
@@ -2075,7 +2210,6 @@ static unsigned char *deflate_it(char *data,
        unsigned char *deflated;
        git_zstream stream;
 
-       memset(&stream, 0, sizeof(stream));
        git_deflate_init(&stream, zlib_compression_level);
        bound = git_deflate_bound(&stream, size);
        deflated = xmalloc(bound);
@@ -2091,7 +2225,8 @@ static unsigned char *deflate_it(char *data,
        return deflated;
 }
 
-static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
+static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two,
+                                 const char *prefix)
 {
        void *cp;
        void *delta;
@@ -2152,7 +2287,8 @@ static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char
        free(data);
 }
 
-static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
+static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two,
+                            const char *prefix)
 {
        fprintf(file, "%sGIT binary patch\n", prefix);
        emit_binary_diff_body(file, one, two, prefix);
@@ -2167,8 +2303,8 @@ int diff_filespec_is_binary(struct diff_filespec *one)
                        one->is_binary = one->driver->binary;
                else {
                        if (!one->data && DIFF_FILE_VALID(one))
-                               diff_populate_filespec(one, 0);
-                       if (one->data)
+                               diff_populate_filespec(one, CHECK_BINARY);
+                       if (one->is_binary == -1 && one->data)
                                one->is_binary = buffer_is_binary(one->data,
                                                one->size);
                        if (one->is_binary == -1)
@@ -2213,28 +2349,44 @@ static void builtin_diff(const char *name_a,
        mmfile_t mf1, mf2;
        const char *lbl[2];
        char *a_one, *b_two;
-       const char *set = diff_get_color_opt(o, DIFF_METAINFO);
+       const char *meta = diff_get_color_opt(o, DIFF_METAINFO);
        const char *reset = diff_get_color_opt(o, DIFF_RESET);
        const char *a_prefix, *b_prefix;
        struct userdiff_driver *textconv_one = NULL;
        struct userdiff_driver *textconv_two = NULL;
        struct strbuf header = STRBUF_INIT;
-       struct strbuf *msgbuf;
-       char *line_prefix = "";
+       const char *line_prefix = diff_line_prefix(o);
 
-       if (o->output_prefix) {
-               msgbuf = o->output_prefix(o, o->output_prefix_data);
-               line_prefix = msgbuf->buf;
+       diff_set_mnemonic_prefix(o, "a/", "b/");
+       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
+               a_prefix = o->b_prefix;
+               b_prefix = o->a_prefix;
+       } else {
+               a_prefix = o->a_prefix;
+               b_prefix = o->b_prefix;
        }
 
-       if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
-                       (!one->mode || S_ISGITLINK(one->mode)) &&
-                       (!two->mode || S_ISGITLINK(two->mode))) {
+       if (o->submodule_format == DIFF_SUBMODULE_LOG &&
+           (!one->mode || S_ISGITLINK(one->mode)) &&
+           (!two->mode || S_ISGITLINK(two->mode))) {
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
-               show_submodule_summary(o->file, one ? one->path : two->path,
-                               one->sha1, two->sha1, two->dirty_submodule,
-                               del, add, reset);
+               show_submodule_summary(o->file, one->path ? one->path : two->path,
+                               line_prefix,
+                               &one->oid, &two->oid,
+                               two->dirty_submodule,
+                               meta, del, add, reset);
+               return;
+       } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF &&
+                  (!one->mode || S_ISGITLINK(one->mode)) &&
+                  (!two->mode || S_ISGITLINK(two->mode))) {
+               const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
+               const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
+               show_submodule_inline_diff(o->file, one->path ? one->path : two->path,
+                               line_prefix,
+                               &one->oid, &two->oid,
+                               two->dirty_submodule,
+                               meta, del, add, reset, o);
                return;
        }
 
@@ -2243,15 +2395,6 @@ static void builtin_diff(const char *name_a,
                textconv_two = get_textconv(two);
        }
 
-       diff_set_mnemonic_prefix(o, "a/", "b/");
-       if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
-               a_prefix = o->b_prefix;
-               b_prefix = o->a_prefix;
-       } else {
-               a_prefix = o->a_prefix;
-               b_prefix = o->b_prefix;
-       }
-
        /* Never use a non-valid filename anywhere if at all possible */
        name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
        name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
@@ -2260,24 +2403,24 @@ 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";
-       strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, set, a_one, b_two, reset);
+       strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
-               strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, set, two->mode, reset);
+               strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, meta, two->mode, reset);
                if (xfrm_msg)
                        strbuf_addstr(&header, xfrm_msg);
                must_show_header = 1;
        }
        else if (lbl[1][0] == '/') {
-               strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, set, one->mode, reset);
+               strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, meta, one->mode, reset);
                if (xfrm_msg)
                        strbuf_addstr(&header, xfrm_msg);
                must_show_header = 1;
        }
        else {
                if (one->mode != two->mode) {
-                       strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, set, one->mode, reset);
-                       strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, set, two->mode, reset);
+                       strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, meta, one->mode, reset);
+                       strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, meta, two->mode, reset);
                        must_show_header = 1;
                }
                if (xfrm_msg)
@@ -2308,6 +2451,19 @@ static void builtin_diff(const char *name_a,
        } else if (!DIFF_OPT_TST(o, TEXT) &&
            ( (!textconv_one && diff_filespec_is_binary(one)) ||
              (!textconv_two && diff_filespec_is_binary(two)) )) {
+               if (!one->data && !two->data &&
+                   S_ISREG(one->mode) && S_ISREG(two->mode) &&
+                   !DIFF_OPT_TST(o, BINARY)) {
+                       if (!oidcmp(&one->oid, &two->oid)) {
+                               if (must_show_header)
+                                       fprintf(o->file, "%s", header.buf);
+                               goto free_ab_and_return;
+                       }
+                       fprintf(o->file, "%s", header.buf);
+                       fprintf(o->file, "%sBinary files %s and %s differ\n",
+                               line_prefix, lbl[0], lbl[1]);
+                       goto free_ab_and_return;
+               }
                if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                        die("unable to read files to diff");
                /* Quite common confusing case */
@@ -2328,6 +2484,7 @@ static void builtin_diff(const char *name_a,
        } else {
                /* Crazy xdl interfaces.. */
                const char *diffopts = getenv("GIT_DIFF_OPTS");
+               const char *v;
                xpparam_t xpp;
                xdemitconf_t xecfg;
                struct emit_callback ecbdata;
@@ -2350,8 +2507,7 @@ static void builtin_diff(const char *name_a,
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
                ecbdata.color_diff = want_color(o->use_color);
-               ecbdata.found_changesp = &o->found_changes;
-               ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
+               ecbdata.ws_rule = whitespace_rule(name_b);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.opt = o;
@@ -2366,14 +2522,15 @@ static void builtin_diff(const char *name_a,
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
                if (!diffopts)
                        ;
-               else if (!prefixcmp(diffopts, "--unified="))
-                       xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
-               else if (!prefixcmp(diffopts, "-u"))
-                       xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
+               else if (skip_prefix(diffopts, "--unified=", &v))
+                       xecfg.ctxlen = strtoul(v, NULL, 10);
+               else if (skip_prefix(diffopts, "-u", &v))
+                       xecfg.ctxlen = strtoul(v, NULL, 10);
                if (o->word_diff)
                        init_diff_words_data(&ecbdata, o, one, two);
-               xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
+                                 &xpp, &xecfg))
+                       die("unable to generate diff for %s", one->path);
                if (o->word_diff)
                        free_diff_words_data(&ecbdata);
                if (textconv_one)
@@ -2397,20 +2554,27 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                             struct diff_filespec *two,
                             struct diffstat_t *diffstat,
                             struct diff_options *o,
-                            int complete_rewrite)
+                            struct diff_filepair *p)
 {
        mmfile_t mf1, mf2;
        struct diffstat_file *data;
        int same_contents;
+       int complete_rewrite = 0;
+
+       if (!DIFF_PAIR_UNMERGED(p)) {
+               if (p->status == DIFF_STATUS_MODIFIED && p->score)
+                       complete_rewrite = 1;
+       }
 
        data = diffstat_add(diffstat, name_a, name_b);
+       data->is_interesting = p->status != DIFF_STATUS_UNKNOWN;
 
        if (!one || !two) {
                data->is_unmerged = 1;
                return;
        }
 
-       same_contents = !hashcmp(one->sha1, two->sha1);
+       same_contents = !oidcmp(&one->oid, &two->oid);
 
        if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
                data->is_binary = 1;
@@ -2443,8 +2607,9 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                xpp.flags = o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
-               xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
+                                 &xpp, &xecfg))
+                       die("unable to generate diffstat for %s", one->path);
        }
 
        diff_free_filespec_data(one);
@@ -2490,8 +2655,9 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
                memset(&xecfg, 0, sizeof(xecfg));
                xecfg.ctxlen = 1; /* at least one context line */
                xpp.flags = 0;
-               xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
+                                 &xpp, &xecfg))
+                       die("unable to generate checkdiff for %s", one->path);
 
                if (data.ws_rule & WS_BLANK_AT_EOF) {
                        struct emit_callback ecbdata;
@@ -2520,12 +2686,9 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
 
 struct diff_filespec *alloc_filespec(const char *path)
 {
-       int namelen = strlen(path);
-       struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
+       struct diff_filespec *spec;
 
-       memset(spec, 0, sizeof(*spec));
-       spec->path = (char *)(spec + 1);
-       memcpy(spec->path, path, namelen+1);
+       FLEXPTR_ALLOC_STR(spec, path, path);
        spec->count = 1;
        spec->is_binary = -1;
        return spec;
@@ -2539,13 +2702,13 @@ void free_filespec(struct diff_filespec *spec)
        }
 }
 
-void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
-                  int sha1_valid, unsigned short mode)
+void fill_filespec(struct diff_filespec *spec, const struct object_id *oid,
+                  int oid_valid, unsigned short mode)
 {
        if (mode) {
                spec->mode = canon_mode(mode);
-               hashcpy(spec->sha1, sha1);
-               spec->sha1_valid = sha1_valid;
+               oidcpy(&spec->oid, oid);
+               spec->oid_valid = oid_valid;
        }
 }
 
@@ -2554,9 +2717,9 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
  * the work tree has that object contents, return true, so that
  * prepare_temp_file() does not have to inflate and extract.
  */
-static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file)
+static int reuse_worktree_file(const char *name, const struct object_id *oid, int want_file)
 {
-       struct cache_entry *ce;
+       const struct cache_entry *ce;
        struct stat st;
        int pos, len;
 
@@ -2585,7 +2748,14 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
         * objects however would tend to be slower as they need
         * to be individually opened and inflated.
         */
-       if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1))
+       if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(oid->hash))
+               return 0;
+
+       /*
+        * Similarly, if we'd have to convert the file contents anyway, that
+        * makes the optimization not worthwhile.
+        */
+       if (!want_file && would_convert_to_git(&the_index, name))
                return 0;
 
        len = strlen(name);
@@ -2598,7 +2768,7 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
         * This is not the sha1 we are looking for, or
         * unreusable because it is not a regular file.
         */
-       if (hashcmp(sha1, ce->sha1) || !S_ISREG(ce->ce_mode))
+       if (oidcmp(oid, &ce->oid) || !S_ISREG(ce->ce_mode))
                return 0;
 
        /*
@@ -2620,21 +2790,22 @@ static int reuse_worktree_file(const char *name, const unsigned char *sha1, int
 
 static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 {
-       int len;
-       char *data = xmalloc(100), *dirty = "";
+       struct strbuf buf = STRBUF_INIT;
+       char *dirty = "";
 
        /* Are we looking at the work tree? */
        if (s->dirty_submodule)
                dirty = "-dirty";
 
-       len = snprintf(data, 100,
-                      "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
-       s->data = data;
-       s->size = len;
-       s->should_free = 1;
+       strbuf_addf(&buf, "Subproject commit %s%s\n",
+                   oid_to_hex(&s->oid), dirty);
+       s->size = buf.len;
        if (size_only) {
                s->data = NULL;
-               free(data);
+               strbuf_release(&buf);
+       } else {
+               s->data = strbuf_detach(&buf, NULL);
+               s->should_free = 1;
        }
        return 0;
 }
@@ -2644,9 +2815,18 @@ static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
  * grab the data for the blob (or file) for our own in-core comparison.
  * diff_filespec has data and size fields for this purpose.
  */
-int diff_populate_filespec(struct diff_filespec *s, int size_only)
+int diff_populate_filespec(struct diff_filespec *s, unsigned int flags)
 {
+       int size_only = flags & CHECK_SIZE_ONLY;
        int err = 0;
+       /*
+        * demote FAIL to WARN to allow inspecting the situation
+        * instead of refusing.
+        */
+       enum safe_crlf crlf_warn = (safe_crlf == SAFE_CRLF_FAIL
+                                   ? SAFE_CRLF_WARN
+                                   : safe_crlf);
+
        if (!DIFF_FILE_VALID(s))
                die("internal error: asking to populate invalid file.");
        if (S_ISDIR(s->mode))
@@ -2661,8 +2841,8 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        if (S_ISGITLINK(s->mode))
                return diff_populate_gitlink(s, size_only);
 
-       if (!s->sha1_valid ||
-           reuse_worktree_file(s->path, s->sha1, 0)) {
+       if (!s->oid_valid ||
+           reuse_worktree_file(s->path, &s->oid, 0)) {
                struct strbuf buf = STRBUF_INIT;
                struct stat st;
                int fd;
@@ -2690,8 +2870,30 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
                        s->should_free = 1;
                        return 0;
                }
-               if (size_only)
+
+               /*
+                * Even if the caller would be happy with getting
+                * only the size, we cannot return early at this
+                * point if the path requires us to run the content
+                * conversion.
+                */
+               if (size_only && !would_convert_to_git(&the_index, s->path))
                        return 0;
+
+               /*
+                * Note: this check uses xsize_t(st.st_size) that may
+                * not be the true size of the blob after it goes
+                * through convert_to_git().  This may not strictly be
+                * correct, but the whole point of big_file_threshold
+                * and is_binary check being that we want to avoid
+                * opening the file and inspecting the contents, this
+                * is probably fine.
+                */
+               if ((flags & CHECK_BINARY) &&
+                   s->size > big_file_threshold && s->is_binary == -1) {
+                       s->is_binary = 1;
+                       return 0;
+               }
                fd = open(s->path, O_RDONLY);
                if (fd < 0)
                        goto err_empty;
@@ -2702,7 +2904,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
                /*
                 * Convert from working tree format to canonical git format
                 */
-               if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) {
+               if (convert_to_git(&the_index, s->path, s->data, s->size, &buf, crlf_warn)) {
                        size_t size = 0;
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
@@ -2713,16 +2915,22 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        }
        else {
                enum object_type type;
-               if (size_only) {
-                       type = sha1_object_info(s->sha1, &s->size);
+               if (size_only || (flags & CHECK_BINARY)) {
+                       type = sha1_object_info(s->oid.hash, &s->size);
                        if (type < 0)
-                               die("unable to read %s", sha1_to_hex(s->sha1));
-               } else {
-                       s->data = read_sha1_file(s->sha1, &type, &s->size);
-                       if (!s->data)
-                               die("unable to read %s", sha1_to_hex(s->sha1));
-                       s->should_free = 1;
+                               die("unable to read %s",
+                                   oid_to_hex(&s->oid));
+                       if (size_only)
+                               return 0;
+                       if (s->size > big_file_threshold && s->is_binary == -1) {
+                               s->is_binary = 1;
+                               return 0;
+                       }
                }
+               s->data = read_sha1_file(s->oid.hash, &type, &s->size);
+               if (!s->data)
+                       die("unable to read %s", oid_to_hex(&s->oid));
+               s->should_free = 1;
        }
        return 0;
 }
@@ -2743,14 +2951,13 @@ void diff_free_filespec_blob(struct diff_filespec *s)
 void diff_free_filespec_data(struct diff_filespec *s)
 {
        diff_free_filespec_blob(s);
-       free(s->cnt_data);
-       s->cnt_data = NULL;
+       FREE_AND_NULL(s->cnt_data);
 }
 
 static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
                           void *blob,
                           unsigned long size,
-                          const unsigned char *sha1,
+                          const struct object_id *oid,
                           int mode)
 {
        int fd;
@@ -2763,8 +2970,7 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
        strbuf_addstr(&template, "XXXXXX_");
        strbuf_addstr(&template, base);
 
-       fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf,
-                       strlen(base) + 1);
+       fd = mks_tempfile_ts(&temp->tempfile, template.buf, strlen(base) + 1);
        if (fd < 0)
                die_errno("unable to create temp-file");
        if (convert_to_working_tree(path,
@@ -2772,13 +2978,12 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
                blob = buf.buf;
                size = buf.len;
        }
-       if (write_in_full(fd, blob, size) != size)
+       if (write_in_full(fd, blob, size) < 0)
                die_errno("unable to write temp-file");
-       close(fd);
-       temp->name = temp->tmp_path;
-       strcpy(temp->hex, sha1_to_hex(sha1));
-       temp->hex[40] = 0;
-       sprintf(temp->mode, "%06o", mode);
+       close_tempfile(&temp->tempfile);
+       temp->name = get_tempfile_path(&temp->tempfile);
+       oid_to_hex_r(temp->hex, oid);
+       xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
        strbuf_release(&buf);
        strbuf_release(&template);
        free(path_dup);
@@ -2795,19 +3000,14 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
                 * a '+' entry produces this for file-1.
                 */
                temp->name = "/dev/null";
-               strcpy(temp->hex, ".");
-               strcpy(temp->mode, ".");
+               xsnprintf(temp->hex, sizeof(temp->hex), ".");
+               xsnprintf(temp->mode, sizeof(temp->mode), ".");
                return temp;
        }
 
-       if (!remove_tempfile_installed) {
-               atexit(remove_tempfile);
-               sigchain_push_common(remove_tempfile_on_signal);
-               remove_tempfile_installed = 1;
-       }
-
-       if (!one->sha1_valid ||
-           reuse_worktree_file(name, one->sha1, 1)) {
+       if (!S_ISGITLINK(one->mode) &&
+           (!one->oid_valid ||
+            reuse_worktree_file(name, &one->oid, 1))) {
                struct stat st;
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
@@ -2819,26 +3019,26 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
                        if (strbuf_readlink(&sb, name, st.st_size) < 0)
                                die_errno("readlink(%s)", name);
                        prep_temp_blob(name, temp, sb.buf, sb.len,
-                                      (one->sha1_valid ?
-                                       one->sha1 : null_sha1),
-                                      (one->sha1_valid ?
+                                      (one->oid_valid ?
+                                       &one->oid : &null_oid),
+                                      (one->oid_valid ?
                                        one->mode : S_IFLNK));
                        strbuf_release(&sb);
                }
                else {
                        /* we can borrow from the file in the work tree */
                        temp->name = name;
-                       if (!one->sha1_valid)
-                               strcpy(temp->hex, sha1_to_hex(null_sha1));
+                       if (!one->oid_valid)
+                               oid_to_hex_r(temp->hex, &null_oid);
                        else
-                               strcpy(temp->hex, sha1_to_hex(one->sha1));
+                               oid_to_hex_r(temp->hex, &one->oid);
                        /* Even though we may sometimes borrow the
                         * contents from the work tree, we always want
                         * one->mode.  mode is trustworthy even when
-                        * !(one->sha1_valid), as long as
+                        * !(one->oid_valid), as long as
                         * DIFF_FILE_VALID(one).
                         */
-                       sprintf(temp->mode, "%06o", one->mode);
+                       xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode);
                }
                return temp;
        }
@@ -2846,11 +3046,21 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
                if (diff_populate_filespec(one, 0))
                        die("cannot read data blob for %s", one->path);
                prep_temp_blob(name, temp, one->data, one->size,
-                              one->sha1, one->mode);
+                              &one->oid, one->mode);
        }
        return temp;
 }
 
+static void add_external_diff_name(struct argv_array *argv,
+                                  const char *name,
+                                  struct diff_filespec *df)
+{
+       struct diff_tempfile *temp = prepare_temp_file(name, df);
+       argv_array_push(argv, temp->name);
+       argv_array_push(argv, temp->hex);
+       argv_array_push(argv, temp->mode);
+}
+
 /* An external diff command takes:
  *
  * diff-cmd name infile1 infile1-sha1 infile1-mode \
@@ -2863,41 +3073,36 @@ static void run_external_diff(const char *pgm,
                              struct diff_filespec *one,
                              struct diff_filespec *two,
                              const char *xfrm_msg,
-                             int complete_rewrite)
+                             int complete_rewrite,
+                             struct diff_options *o)
 {
-       const char *spawn_arg[10];
-       int retval;
-       const char **arg = &spawn_arg[0];
+       struct argv_array argv = ARGV_ARRAY_INIT;
+       struct argv_array env = ARGV_ARRAY_INIT;
+       struct diff_queue_struct *q = &diff_queued_diff;
+
+       argv_array_push(&argv, pgm);
+       argv_array_push(&argv, name);
 
        if (one && two) {
-               struct diff_tempfile *temp_one, *temp_two;
-               const char *othername = (other ? other : name);
-               temp_one = prepare_temp_file(name, one);
-               temp_two = prepare_temp_file(othername, two);
-               *arg++ = pgm;
-               *arg++ = name;
-               *arg++ = temp_one->name;
-               *arg++ = temp_one->hex;
-               *arg++ = temp_one->mode;
-               *arg++ = temp_two->name;
-               *arg++ = temp_two->hex;
-               *arg++ = temp_two->mode;
-               if (other) {
-                       *arg++ = other;
-                       *arg++ = xfrm_msg;
+               add_external_diff_name(&argv, name, one);
+               if (!other)
+                       add_external_diff_name(&argv, name, two);
+               else {
+                       add_external_diff_name(&argv, other, two);
+                       argv_array_push(&argv, other);
+                       argv_array_push(&argv, xfrm_msg);
                }
-       } else {
-               *arg++ = pgm;
-               *arg++ = name;
        }
-       *arg = NULL;
-       fflush(NULL);
-       retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL);
+
+       argv_array_pushf(&env, "GIT_DIFF_PATH_COUNTER=%d", ++o->diff_path_counter);
+       argv_array_pushf(&env, "GIT_DIFF_PATH_TOTAL=%d", q->nr);
+
+       if (run_command_v_opt_cd_env(argv.argv, RUN_USING_SHELL, NULL, env.argv))
+               die(_("external diff died, stopping at %s"), name);
+
        remove_tempfile();
-       if (retval) {
-               fprintf(stderr, "external diff died, stopping at %s.\n", name);
-               exit(1);
-       }
+       argv_array_clear(&argv);
+       argv_array_clear(&env);
 }
 
 static int similarity_index(struct diff_filepair *p)
@@ -2905,6 +3110,22 @@ static int similarity_index(struct diff_filepair *p)
        return p->score * 100 / MAX_SCORE;
 }
 
+static const char *diff_abbrev_oid(const struct object_id *oid, int abbrev)
+{
+       if (startup_info->have_repository)
+               return find_unique_abbrev(oid->hash, abbrev);
+       else {
+               char *hex = oid_to_hex(oid);
+               if (abbrev < 0)
+                       abbrev = FALLBACK_DEFAULT_ABBREV;
+               if (abbrev > GIT_SHA1_HEXSZ)
+                       die("BUG: oid abbreviation out of range: %d", abbrev);
+               if (abbrev)
+                       hex[abbrev] = '\0';
+               return hex;
+       }
+}
+
 static void fill_metainfo(struct strbuf *msg,
                          const char *name,
                          const char *other,
@@ -2917,14 +3138,9 @@ static void fill_metainfo(struct strbuf *msg,
 {
        const char *set = diff_get_color(use_color, DIFF_METAINFO);
        const char *reset = diff_get_color(use_color, DIFF_RESET);
-       struct strbuf *msgbuf;
-       char *line_prefix = "";
+       const char *line_prefix = diff_line_prefix(o);
 
        *must_show_header = 1;
-       if (o->output_prefix) {
-               msgbuf = o->output_prefix(o, o->output_prefix_data);
-               line_prefix = msgbuf->buf;
-       }
        strbuf_init(msg, PATH_MAX * 2 + 300);
        switch (p->status) {
        case DIFF_STATUS_COPIED:
@@ -2959,7 +3175,7 @@ static void fill_metainfo(struct strbuf *msg,
        default:
                *must_show_header = 0;
        }
-       if (one && two && hashcmp(one->sha1, two->sha1)) {
+       if (one && two && oidcmp(&one->oid, &two->oid)) {
                int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
 
                if (DIFF_OPT_TST(o, BINARY)) {
@@ -2968,9 +3184,9 @@ static void fill_metainfo(struct strbuf *msg,
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
                                abbrev = 40;
                }
-               strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
-                           find_unique_abbrev(one->sha1, abbrev));
-               strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev));
+               strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set,
+                           diff_abbrev_oid(&one->oid, abbrev),
+                           diff_abbrev_oid(&two->oid, abbrev));
                if (one->mode == two->mode)
                        strbuf_addf(msg, " %06o", one->mode);
                strbuf_addf(msg, "%s\n", reset);
@@ -3011,7 +3227,7 @@ static void run_diff_cmd(const char *pgm,
 
        if (pgm) {
                run_external_diff(pgm, name, other, one, two, xfrm_msg,
-                                 complete_rewrite);
+                                 complete_rewrite, o);
                return;
        }
        if (one && two)
@@ -3022,23 +3238,23 @@ static void run_diff_cmd(const char *pgm,
                fprintf(o->file, "* Unmerged path %s\n", name);
 }
 
-static void diff_fill_sha1_info(struct diff_filespec *one)
+static void diff_fill_oid_info(struct diff_filespec *one)
 {
        if (DIFF_FILE_VALID(one)) {
-               if (!one->sha1_valid) {
+               if (!one->oid_valid) {
                        struct stat st;
                        if (one->is_stdin) {
-                               hashcpy(one->sha1, null_sha1);
+                               oidclr(&one->oid);
                                return;
                        }
                        if (lstat(one->path, &st) < 0)
                                die_errno("stat '%s'", one->path);
-                       if (index_path(one->sha1, one->path, &st, 0))
+                       if (index_path(one->oid.hash, one->path, &st, 0))
                                die("cannot hash %s", one->path);
                }
        }
        else
-               hashclr(one->sha1);
+               oidclr(&one->oid);
 }
 
 static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
@@ -3081,8 +3297,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
                return;
        }
 
-       diff_fill_sha1_info(one);
-       diff_fill_sha1_info(two);
+       diff_fill_oid_info(one);
+       diff_fill_oid_info(two);
 
        if (!pgm &&
            DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
@@ -3114,11 +3330,10 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
 {
        const char *name;
        const char *other;
-       int complete_rewrite = 0;
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
-               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, 0);
+               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, p);
                return;
        }
 
@@ -3128,12 +3343,10 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
        if (o->prefix_length)
                strip_prefix(o->prefix_length, &name, &other);
 
-       diff_fill_sha1_info(p->one);
-       diff_fill_sha1_info(p->two);
+       diff_fill_oid_info(p->one);
+       diff_fill_oid_info(p->two);
 
-       if (p->status == DIFF_STATUS_MODIFIED && p->score)
-               complete_rewrite = 1;
-       builtin_diffstat(name, other, p->one, p->two, diffstat, o, complete_rewrite);
+       builtin_diffstat(name, other, p->one, p->two, diffstat, o, p);
 }
 
 static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
@@ -3154,8 +3367,8 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
        if (o->prefix_length)
                strip_prefix(o->prefix_length, &name, &other);
 
-       diff_fill_sha1_info(p->one);
-       diff_fill_sha1_info(p->two);
+       diff_fill_oid_info(p->one);
+       diff_fill_oid_info(p->two);
 
        builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
 }
@@ -3166,17 +3379,26 @@ void diff_setup(struct diff_options *options)
 
        options->file = stdout;
 
+       options->abbrev = DEFAULT_ABBREV;
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
        options->dirstat_permille = diff_dirstat_permille_default;
-       options->context = 3;
+       options->context = diff_context_default;
+       options->interhunkcontext = diff_interhunk_context_default;
+       options->ws_error_highlight = ws_error_highlight_default;
        DIFF_OPT_SET(options, RENAME_EMPTY);
 
+       /* pathchange left =NULL by default */
        options->change = diff_change;
        options->add_remove = diff_addremove;
        options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
+       options->xdl_opts |= diff_algorithm;
+       if (diff_indent_heuristic)
+               DIFF_XDL_SET(options, INDENT_HEURISTIC);
+
+       options->orderfile = diff_order_file_cfg;
 
        if (diff_no_prefix) {
                options->a_prefix = options->b_prefix = "";
@@ -3190,6 +3412,9 @@ void diff_setup_done(struct diff_options *options)
 {
        int count = 0;
 
+       if (options->set_default)
+               options->set_default(options);
+
        if (options->output_format & DIFF_FORMAT_NAME)
                count++;
        if (options->output_format & DIFF_FORMAT_NAME_STATUS)
@@ -3199,7 +3424,7 @@ void diff_setup_done(struct diff_options *options)
        if (options->output_format & DIFF_FORMAT_NO_OUTPUT)
                count++;
        if (count > 1)
-               die("--name-only, --name-status, --check and -s are mutually exclusive");
+               die(_("--name-only, --name-status, --check and -s are mutually exclusive"));
 
        /*
         * Most of the time we can say "there are changes"
@@ -3273,7 +3498,7 @@ void diff_setup_done(struct diff_options *options)
                         */
                        read_cache();
        }
-       if (options->abbrev <= 0 || 40 < options->abbrev)
+       if (40 < options->abbrev)
                options->abbrev = 40; /* full */
 
        /*
@@ -3285,6 +3510,11 @@ void diff_setup_done(struct diff_options *options)
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
+
+       options->diff_path_counter = 0;
+
+       if (DIFF_OPT_TST(options, FOLLOW_RENAMES) && options->pathspec.nr != 1)
+               die(_("--follow requires exactly one pathspec"));
 }
 
 static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
@@ -3314,14 +3544,11 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va
        if (c != '-')
                return 0;
        arg++;
-       eq = strchr(arg, '=');
-       if (eq)
-               len = eq - arg;
-       else
-               len = strlen(arg);
+       eq = strchrnul(arg, '=');
+       len = eq - arg;
        if (!len || strncmp(arg, arg_long, len))
                return 0;
-       if (eq) {
+       if (*eq) {
                int n;
                char *end;
                if (!isdigit(*++eq))
@@ -3356,13 +3583,11 @@ int parse_long_opt(const char *opt, const char **argv,
                   const char **optarg)
 {
        const char *arg = argv[0];
-       if (arg[0] != '-' || arg[1] != '-')
+       if (!skip_prefix(arg, "--", &arg))
                return 0;
-       arg += strlen("--");
-       if (prefixcmp(arg, opt))
+       if (!skip_prefix(arg, opt, &arg))
                return 0;
-       arg += strlen(opt);
-       if (*arg == '=') { /* sticked form: --option=value */
+       if (*arg == '=') { /* stuck form: --option=value */
                *optarg = arg + 1;
                return 1;
        }
@@ -3385,47 +3610,44 @@ static int stat_opt(struct diff_options *options, const char **av)
        int count = options->stat_count;
        int argcount = 1;
 
-       arg += strlen("--stat");
+       if (!skip_prefix(arg, "--stat", &arg))
+               die("BUG: stat option does not begin with --stat: %s", arg);
        end = (char *)arg;
 
        switch (*arg) {
        case '-':
-               if (!prefixcmp(arg, "-width")) {
-                       arg += strlen("-width");
+               if (skip_prefix(arg, "-width", &arg)) {
                        if (*arg == '=')
                                width = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
-                               die("Option '--stat-width' requires a value");
+                               die_want_option("--stat-width");
                        else if (!*arg) {
                                width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
-               } else if (!prefixcmp(arg, "-name-width")) {
-                       arg += strlen("-name-width");
+               } else if (skip_prefix(arg, "-name-width", &arg)) {
                        if (*arg == '=')
                                name_width = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
-                               die("Option '--stat-name-width' requires a value");
+                               die_want_option("--stat-name-width");
                        else if (!*arg) {
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
-               } else if (!prefixcmp(arg, "-graph-width")) {
-                       arg += strlen("-graph-width");
+               } else if (skip_prefix(arg, "-graph-width", &arg)) {
                        if (*arg == '=')
                                graph_width = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
-                               die("Option '--stat-graph-width' requires a value");
+                               die_want_option("--stat-graph-width");
                        else if (!*arg) {
                                graph_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
-               } else if (!prefixcmp(arg, "-count")) {
-                       arg += strlen("-count");
+               } else if (skip_prefix(arg, "-count", &arg)) {
                        if (*arg == '=')
                                count = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
-                               die("Option '--stat-count' requires a value");
+                               die_want_option("--stat-count");
                        else if (!*arg) {
                                count = strtoul(av[1], &end, 10);
                                argcount = 2;
@@ -3466,62 +3688,167 @@ static int parse_dirstat_opt(struct diff_options *options, const char *params)
        return 1;
 }
 
-int diff_opt_parse(struct diff_options *options, const char **av, int ac)
+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,
+       DIFF_STATUS_DELETED,
+       DIFF_STATUS_MODIFIED,
+       DIFF_STATUS_RENAMED,
+       DIFF_STATUS_TYPE_CHANGED,
+       DIFF_STATUS_UNKNOWN,
+       DIFF_STATUS_UNMERGED,
+       DIFF_STATUS_FILTER_AON,
+       DIFF_STATUS_FILTER_BROKEN,
+       '\0',
+};
+
+static unsigned int filter_bit['Z' + 1];
+
+static void prepare_filter_bits(void)
+{
+       int i;
+
+       if (!filter_bit[DIFF_STATUS_ADDED]) {
+               for (i = 0; diff_status_letters[i]; i++)
+                       filter_bit[(int) diff_status_letters[i]] = (1 << i);
+       }
+}
+
+static unsigned filter_bit_tst(char status, const struct diff_options *opt)
+{
+       return opt->filter & filter_bit[(int) status];
+}
+
+static int parse_diff_filter_opt(const char *optarg, struct diff_options *opt)
+{
+       int i, optch;
+
+       prepare_filter_bits();
+
+       /*
+        * If there is a negation e.g. 'd' in the input, and we haven't
+        * initialized the filter field with another --diff-filter, start
+        * from full set of bits, except for AON.
+        */
+       if (!opt->filter) {
+               for (i = 0; (optch = optarg[i]) != '\0'; i++) {
+                       if (optch < 'a' || 'z' < optch)
+                               continue;
+                       opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1;
+                       opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON];
+                       break;
+               }
+       }
+
+       for (i = 0; (optch = optarg[i]) != '\0'; i++) {
+               unsigned int bit;
+               int negate;
+
+               if ('a' <= optch && optch <= 'z') {
+                       negate = 1;
+                       optch = toupper(optch);
+               } else {
+                       negate = 0;
+               }
+
+               bit = (0 <= optch && optch <= 'Z') ? filter_bit[optch] : 0;
+               if (!bit)
+                       return optarg[i];
+               if (negate)
+                       opt->filter &= ~bit;
+               else
+                       opt->filter |= bit;
+       }
+       return 0;
+}
+
+static void enable_patch_output(int *fmt) {
+       *fmt &= ~DIFF_FORMAT_NO_OUTPUT;
+       *fmt |= DIFF_FORMAT_PATCH;
+}
+
+static int parse_ws_error_highlight_opt(struct diff_options *opt, const char *arg)
+{
+       int val = parse_ws_error_highlight(arg);
+
+       if (val < 0) {
+               error("unknown value after ws-error-highlight=%.*s",
+                     -1 - val, arg);
+               return 0;
+       }
+       opt->ws_error_highlight = val;
+       return 1;
+}
+
+int diff_opt_parse(struct diff_options *options,
+                  const char **av, int ac, const char *prefix)
 {
        const char *arg = av[0];
        const char *optarg;
        int argcount;
 
+       if (!prefix)
+               prefix = "";
+
        /* Output format options */
-       if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch"))
-               options->output_format |= DIFF_FORMAT_PATCH;
-       else if (opt_arg(arg, 'U', "unified", &options->context))
-               options->output_format |= DIFF_FORMAT_PATCH;
+       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"))
-               options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
-       else if (!strcmp(arg, "--numstat"))
+       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 (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat"))
                return parse_dirstat_opt(options, "");
-       else if (!prefixcmp(arg, "-X"))
-               return parse_dirstat_opt(options, arg + 2);
-       else if (!prefixcmp(arg, "--dirstat="))
-               return parse_dirstat_opt(options, arg + 10);
+       else if (skip_prefix(arg, "-X", &arg))
+               return parse_dirstat_opt(options, arg);
+       else if (skip_prefix(arg, "--dirstat=", &arg))
+               return parse_dirstat_opt(options, arg);
        else if (!strcmp(arg, "--cumulative"))
                return parse_dirstat_opt(options, "cumulative");
        else if (!strcmp(arg, "--dirstat-by-file"))
                return parse_dirstat_opt(options, "files");
-       else if (!prefixcmp(arg, "--dirstat-by-file=")) {
+       else if (skip_prefix(arg, "--dirstat-by-file=", &arg)) {
                parse_dirstat_opt(options, "files");
-               return parse_dirstat_opt(options, arg + 18);
+               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"))
-               options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT;
-       else if (!strcmp(arg, "--name-only"))
+       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"))
+       else if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
-       else if (!prefixcmp(arg, "--stat"))
+       else if (starts_with(arg, "--stat"))
                /* --stat, --stat-width, --stat-name-width, or --stat-count */
                return stat_opt(options, av);
 
        /* renames options */
-       else if (!prefixcmp(arg, "-B") || !prefixcmp(arg, "--break-rewrites=") ||
+       else if (starts_with(arg, "-B") || starts_with(arg, "--break-rewrites=") ||
                 !strcmp(arg, "--break-rewrites")) {
                if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -B: %s", arg+2);
        }
-       else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--find-renames=") ||
+       else if (starts_with(arg, "-M") || starts_with(arg, "--find-renames=") ||
                 !strcmp(arg, "--find-renames")) {
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -M: %s", arg+2);
@@ -3530,7 +3857,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
                options->irreversible_delete = 1;
        }
-       else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") ||
+       else if (starts_with(arg, "-C") || starts_with(arg, "--find-copies=") ||
                 !strcmp(arg, "--find-copies")) {
                if (options->detect_rename == DIFF_DETECT_COPY)
                        DIFF_OPT_SET(options, FIND_COPIES_HARDER);
@@ -3546,9 +3873,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_CLR(options, RENAME_EMPTY);
        else if (!strcmp(arg, "--relative"))
                DIFF_OPT_SET(options, RELATIVE_NAME);
-       else if (!prefixcmp(arg, "--relative=")) {
+       else if (skip_prefix(arg, "--relative=", &arg)) {
                DIFF_OPT_SET(options, RELATIVE_NAME);
-               options->prefix = arg + 11;
+               options->prefix = arg;
        }
 
        /* xdiff options */
@@ -3562,14 +3889,31 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                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-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"))
                options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
        else if (!strcmp(arg, "--histogram"))
                options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF);
+       else if ((argcount = parse_long_opt("diff-algorithm", av, &optarg))) {
+               long value = parse_algorithm_value(optarg);
+               if (value < 0)
+                       return error("option diff-algorithm accepts \"myers\", "
+                                    "\"minimal\", \"patience\" and \"histogram\"");
+               /* clear out previous settings */
+               DIFF_XDL_CLR(options, NEED_MINIMAL);
+               options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
+               options->xdl_opts |= value;
+               return argcount;
+       }
 
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
-               options->output_format |= DIFF_FORMAT_PATCH;
+               enable_patch_output(&options->output_format);
                DIFF_OPT_SET(options, BINARY);
        }
        else if (!strcmp(arg, "--full-index"))
@@ -3582,10 +3926,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, FIND_COPIES_HARDER);
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
-       else if (!strcmp(arg, "--color"))
+       else if (!strcmp(arg, "--no-follow")) {
+               DIFF_OPT_CLR(options, FOLLOW_RENAMES);
+               DIFF_OPT_CLR(options, DEFAULT_FOLLOW_RENAMES);
+       } else if (!strcmp(arg, "--color"))
                options->use_color = 1;
-       else if (!prefixcmp(arg, "--color=")) {
-               int value = git_config_colorbool(NULL, arg+8);
+       else if (skip_prefix(arg, "--color=", &arg)) {
+               int value = git_config_colorbool(NULL, arg);
                if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
                options->use_color = value;
@@ -3596,29 +3943,28 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
-       else if (!prefixcmp(arg, "--color-words=")) {
+       else if (skip_prefix(arg, "--color-words=", &arg)) {
                options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
-               options->word_regex = arg + 14;
+               options->word_regex = arg;
        }
        else if (!strcmp(arg, "--word-diff")) {
                if (options->word_diff == DIFF_WORDS_NONE)
                        options->word_diff = DIFF_WORDS_PLAIN;
        }
-       else if (!prefixcmp(arg, "--word-diff=")) {
-               const char *type = arg + 12;
-               if (!strcmp(type, "plain"))
+       else if (skip_prefix(arg, "--word-diff=", &arg)) {
+               if (!strcmp(arg, "plain"))
                        options->word_diff = DIFF_WORDS_PLAIN;
-               else if (!strcmp(type, "color")) {
+               else if (!strcmp(arg, "color")) {
                        options->use_color = 1;
                        options->word_diff = DIFF_WORDS_COLOR;
                }
-               else if (!strcmp(type, "porcelain"))
+               else if (!strcmp(arg, "porcelain"))
                        options->word_diff = DIFF_WORDS_PORCELAIN;
-               else if (!strcmp(type, "none"))
+               else if (!strcmp(arg, "none"))
                        options->word_diff = DIFF_WORDS_NONE;
                else
-                       die("bad --word-diff argument: %s", type);
+                       die("bad --word-diff argument: %s", arg);
        }
        else if ((argcount = parse_long_opt("word-diff-regex", av, &optarg))) {
                if (options->word_diff == DIFF_WORDS_NONE)
@@ -3641,15 +3987,19 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        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=")) {
+       } else if (skip_prefix(arg, "--ignore-submodules=", &arg)) {
                DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
-               handle_ignore_submodules_arg(options, arg + 20);
+               handle_ignore_submodules_arg(options, arg);
        } else if (!strcmp(arg, "--submodule"))
-               DIFF_OPT_SET(options, SUBMODULE_LOG);
-       else if (!prefixcmp(arg, "--submodule=")) {
-               if (!strcmp(arg + 12, "log"))
-                       DIFF_OPT_SET(options, SUBMODULE_LOG);
-       }
+               options->submodule_format = DIFF_SUBMODULE_LOG;
+       else if (skip_prefix(arg, "--submodule=", &arg))
+               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"))
@@ -3672,17 +4022,22 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--pickaxe-regex"))
                options->pickaxe_opts |= DIFF_PICKAXE_REGEX;
        else if ((argcount = short_opt('O', av, &optarg))) {
-               options->orderfile = optarg;
+               options->orderfile = prefix_filename(prefix, optarg);
                return argcount;
        }
        else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
-               options->filter = 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 (!prefixcmp(arg, "--abbrev=")) {
-               options->abbrev = strtoul(arg + 9, NULL, 10);
+       else if (skip_prefix(arg, "--abbrev=", &arg)) {
+               options->abbrev = strtoul(arg, NULL, 10);
                if (options->abbrev < MINIMUM_ABBREV)
                        options->abbrev = MINIMUM_ABBREV;
                else if (40 < options->abbrev)
@@ -3692,6 +4047,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                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;
@@ -3708,10 +4069,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--no-function-context"))
                DIFF_OPT_CLR(options, FUNCCONTEXT);
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
-               options->file = fopen(optarg, "w");
-               if (!options->file)
-                       die_errno("Could not open '%s'", 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;
@@ -3763,22 +4126,19 @@ static int diff_scoreopt_parse(const char *opt)
        cmd = *opt++;
        if (cmd == '-') {
                /* convert the long-form arguments into short-form versions */
-               if (!prefixcmp(opt, "break-rewrites")) {
-                       opt += strlen("break-rewrites");
+               if (skip_prefix(opt, "break-rewrites", &opt)) {
                        if (*opt == 0 || *opt++ == '=')
                                cmd = 'B';
-               } else if (!prefixcmp(opt, "find-copies")) {
-                       opt += strlen("find-copies");
+               } else if (skip_prefix(opt, "find-copies", &opt)) {
                        if (*opt == 0 || *opt++ == '=')
                                cmd = 'C';
-               } else if (!prefixcmp(opt, "find-renames")) {
-                       opt += strlen("find-renames");
+               } 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 nor -B option */
+               return -1; /* that is not a -M, -Cor -B option */
 
        opt1 = parse_rename_score(&opt);
        if (cmd != 'B')
@@ -3802,11 +4162,7 @@ struct diff_queue_struct diff_queued_diff;
 
 void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
 {
-       if (queue->alloc <= queue->nr) {
-               queue->alloc = alloc_nr(queue->alloc);
-               queue->queue = xrealloc(queue->queue,
-                                       sizeof(dp) * queue->alloc);
-       }
+       ALLOC_GROW(queue->queue, queue->nr + 1, queue->alloc);
        queue->queue[queue->nr++] = dp;
 }
 
@@ -3829,43 +4185,59 @@ void diff_free_filepair(struct diff_filepair *p)
        free(p);
 }
 
-/* This is different from find_unique_abbrev() in that
- * it stuffs the result with dots for alignment.
- */
-const char *diff_unique_abbrev(const unsigned char *sha1, int len)
+const char *diff_aligned_abbrev(const struct object_id *oid, int len)
 {
        int abblen;
        const char *abbrev;
-       if (len == 40)
-               return sha1_to_hex(sha1);
 
-       abbrev = find_unique_abbrev(sha1, len);
+       if (len == GIT_SHA1_HEXSZ)
+               return oid_to_hex(oid);
+
+       abbrev = diff_abbrev_oid(oid, len);
        abblen = strlen(abbrev);
-       if (abblen < 37) {
-               static char hex[41];
+
+       /*
+        * In well-behaved cases, where the abbbreviated result is the
+        * same as the requested length, append three dots after the
+        * abbreviation (hence the whole logic is limited to the case
+        * where abblen < 37); when the actual abbreviated result is a
+        * bit longer than the requested length, we reduce the number
+        * of dots so that they match the well-behaved ones.  However,
+        * if the actual abbreviation is longer than the requested
+        * length by more than three, we give up on aligning, and add
+        * three dots anyway, to indicate that the output is not the
+        * full object name.  Yes, this may be suboptimal, but this
+        * appears only in "diff --raw --abbrev" output and it is not
+        * worth the effort to change it now.  Note that this would
+        * likely to work fine when the automatic sizing of default
+        * abbreviation length is used--we would be fed -1 in "len" in
+        * that case, and will end up always appending three-dots, but
+        * the automatic sizing is supposed to give abblen that ensures
+        * uniqueness across all objects (statistically speaking).
+        */
+       if (abblen < GIT_SHA1_HEXSZ - 3) {
+               static char hex[GIT_MAX_HEXSZ + 1];
                if (len < abblen && abblen <= len + 2)
-                       sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
+                       xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, "..");
                else
-                       sprintf(hex, "%s...", abbrev);
+                       xsnprintf(hex, sizeof(hex), "%s...", abbrev);
                return hex;
        }
-       return sha1_to_hex(sha1);
+
+       return oid_to_hex(oid);
 }
 
 static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
 {
        int line_termination = opt->line_termination;
        int inter_name_termination = line_termination ? '\t' : '\0';
-       if (opt->output_prefix) {
-               struct strbuf *msg = NULL;
-               msg = opt->output_prefix(opt, opt->output_prefix_data);
-               fprintf(opt->file, "%s", msg->buf);
-       }
 
+       fprintf(opt->file, "%s", diff_line_prefix(opt));
        if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) {
                fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode,
-                       diff_unique_abbrev(p->one->sha1, opt->abbrev));
-               fprintf(opt->file, "%s ", diff_unique_abbrev(p->two->sha1, opt->abbrev));
+                       diff_aligned_abbrev(&p->one->oid, opt->abbrev));
+               fprintf(opt->file, "%s ",
+                       diff_aligned_abbrev(&p->two->oid, opt->abbrev));
        }
        if (p->score) {
                fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p),
@@ -3914,11 +4286,11 @@ int diff_unmodified_pair(struct diff_filepair *p)
        /* both are valid and point at the same path.  that is, we are
         * dealing with a change.
         */
-       if (one->sha1_valid && two->sha1_valid &&
-           !hashcmp(one->sha1, two->sha1) &&
+       if (one->oid_valid && two->oid_valid &&
+           !oidcmp(&one->oid, &two->oid) &&
            !one->dirty_submodule && !two->dirty_submodule)
                return 1; /* no change */
-       if (!one->sha1_valid && !two->sha1_valid)
+       if (!one->oid_valid && !two->oid_valid)
                return 1; /* both look at the same file on the filesystem. */
        return 0;
 }
@@ -3979,10 +4351,10 @@ void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
                s->path,
                DIFF_FILE_VALID(s) ? "valid" : "invalid",
                s->mode,
-               s->sha1_valid ? sha1_to_hex(s->sha1) : "");
-       fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
+               s->oid_valid ? oid_to_hex(&s->oid) : "");
+       fprintf(stderr, "queue[%d] %s size %lu\n",
                x, one ? one : "",
-               s->size, s->xfrm_flags);
+               s->size);
 }
 
 void diff_debug_filepair(const struct diff_filepair *p, int i)
@@ -4049,11 +4421,11 @@ static void diff_resolve_rename_copy(void)
                        else
                                p->status = DIFF_STATUS_RENAMED;
                }
-               else if (hashcmp(p->one->sha1, p->two->sha1) ||
+               else if (oidcmp(&p->one->oid, &p->two->oid) ||
                         p->one->mode != p->two->mode ||
                         p->one->dirty_submodule ||
                         p->two->dirty_submodule ||
-                        is_null_sha1(p->one->sha1))
+                        is_null_oid(&p->one->oid))
                        p->status = DIFF_STATUS_MODIFIED;
                else {
                        /* This is a "no-change" entry and should not
@@ -4092,6 +4464,7 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
                name_a = p->two->path;
                name_b = NULL;
                strip_prefix(opt->prefix_length, &name_a, &name_b);
+               fprintf(opt->file, "%s", diff_line_prefix(opt));
                write_name_quoted(name_a, opt->file, opt->line_termination);
        }
 }
@@ -4131,12 +4504,7 @@ static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_fil
 static void diff_summary(struct diff_options *opt, struct diff_filepair *p)
 {
        FILE *file = opt->file;
-       char *line_prefix = "";
-
-       if (opt->output_prefix) {
-               struct strbuf *buf = opt->output_prefix(opt, opt->output_prefix_data);
-               line_prefix = buf->buf;
-       }
+       const char *line_prefix = diff_line_prefix(opt);
 
        switch(p->status) {
        case DIFF_STATUS_DELETED:
@@ -4190,7 +4558,7 @@ static void patch_id_consume(void *priv, char *line, unsigned long len)
        int new_len;
 
        /* Ignore line numbers when computing the SHA1 of the patch */
-       if (!prefixcmp(line, "@@ -"))
+       if (starts_with(line, "@@ -"))
                return;
 
        new_len = remove_space(line, len);
@@ -4199,14 +4567,26 @@ static void patch_id_consume(void *priv, char *line, unsigned long len)
        data->patchlen += new_len;
 }
 
+static void patch_id_add_string(git_SHA_CTX *ctx, const char *str)
+{
+       git_SHA1_Update(ctx, str, strlen(str));
+}
+
+static void patch_id_add_mode(git_SHA_CTX *ctx, unsigned mode)
+{
+       /* large enough for 2^32 in octal */
+       char buf[12];
+       int len = xsnprintf(buf, sizeof(buf), "%06o", mode);
+       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, unsigned char *sha1)
+static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
        git_SHA_CTX ctx;
        struct patch_id_t data;
-       char buffer[PATH_MAX * 4 + 20];
 
        git_SHA1_Init(&ctx);
        memset(&data, 0, sizeof(struct patch_id_t));
@@ -4233,68 +4613,70 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
                if (DIFF_PAIR_UNMERGED(p))
                        continue;
 
-               diff_fill_sha1_info(p->one);
-               diff_fill_sha1_info(p->two);
-               if (fill_mmfile(&mf1, p->one) < 0 ||
-                               fill_mmfile(&mf2, p->two) < 0)
-                       return error("unable to read files to diff");
+               diff_fill_oid_info(p->one);
+               diff_fill_oid_info(p->two);
 
                len1 = remove_space(p->one->path, strlen(p->one->path));
                len2 = remove_space(p->two->path, strlen(p->two->path));
-               if (p->one->mode == 0)
-                       len1 = snprintf(buffer, sizeof(buffer),
-                                       "diff--gita/%.*sb/%.*s"
-                                       "newfilemode%06o"
-                                       "---/dev/null"
-                                       "+++b/%.*s",
-                                       len1, p->one->path,
-                                       len2, p->two->path,
-                                       p->two->mode,
-                                       len2, p->two->path);
-               else if (p->two->mode == 0)
-                       len1 = snprintf(buffer, sizeof(buffer),
-                                       "diff--gita/%.*sb/%.*s"
-                                       "deletedfilemode%06o"
-                                       "---a/%.*s"
-                                       "+++/dev/null",
-                                       len1, p->one->path,
-                                       len2, p->two->path,
-                                       p->one->mode,
-                                       len1, p->one->path);
-               else
-                       len1 = snprintf(buffer, sizeof(buffer),
-                                       "diff--gita/%.*sb/%.*s"
-                                       "---a/%.*s"
-                                       "+++b/%.*s",
-                                       len1, p->one->path,
-                                       len2, p->two->path,
-                                       len1, p->one->path,
-                                       len2, p->two->path);
-               git_SHA1_Update(&ctx, buffer, len1);
+               patch_id_add_string(&ctx, "diff--git");
+               patch_id_add_string(&ctx, "a/");
+               git_SHA1_Update(&ctx, p->one->path, len1);
+               patch_id_add_string(&ctx, "b/");
+               git_SHA1_Update(&ctx, p->two->path, len2);
+
+               if (p->one->mode == 0) {
+                       patch_id_add_string(&ctx, "newfilemode");
+                       patch_id_add_mode(&ctx, p->two->mode);
+                       patch_id_add_string(&ctx, "---/dev/null");
+                       patch_id_add_string(&ctx, "+++b/");
+                       git_SHA1_Update(&ctx, p->two->path, len2);
+               } else if (p->two->mode == 0) {
+                       patch_id_add_string(&ctx, "deletedfilemode");
+                       patch_id_add_mode(&ctx, p->one->mode);
+                       patch_id_add_string(&ctx, "---a/");
+                       git_SHA1_Update(&ctx, p->one->path, len1);
+                       patch_id_add_string(&ctx, "+++/dev/null");
+               } else {
+                       patch_id_add_string(&ctx, "---a/");
+                       git_SHA1_Update(&ctx, p->one->path, len1);
+                       patch_id_add_string(&ctx, "+++b/");
+                       git_SHA1_Update(&ctx, p->two->path, len2);
+               }
+
+               if (diff_header_only)
+                       continue;
+
+               if (fill_mmfile(&mf1, p->one) < 0 ||
+                   fill_mmfile(&mf2, p->two) < 0)
+                       return error("unable to read files to diff");
 
                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);
+                       git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid),
+                                       GIT_SHA1_HEXSZ);
+                       git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid),
+                                       GIT_SHA1_HEXSZ);
                        continue;
                }
 
                xpp.flags = 0;
                xecfg.ctxlen = 3;
                xecfg.flags = 0;
-               xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
+                                 &xpp, &xecfg))
+                       return error("unable to generate patch-id diff for %s",
+                                    p->one->path);
        }
 
-       git_SHA1_Final(sha1, &ctx);
+       git_SHA1_Final(oid->hash, &ctx);
        return 0;
 }
 
-int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
+int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
-       int result = diff_get_patch_id(options, sha1);
+       int result = diff_get_patch_id(options, oid, diff_header_only);
 
        for (i = 0; i < q->nr; i++)
                diff_free_filepair(q->queue[i]);
@@ -4331,25 +4713,25 @@ static int is_summary_empty(const struct diff_queue_struct *q)
 }
 
 static const char rename_limit_warning[] =
-"inexact rename detection was skipped due to too many files.";
+N_("inexact rename detection was skipped due to too many files.");
 
 static const char degrade_cc_to_c_warning[] =
-"only found copies from modified paths due to too many files.";
+N_("only found copies from modified paths due to too many files.");
 
 static const char rename_limit_advice[] =
-"you may want to set your %s variable to at least "
-"%d and retry the command.";
+N_("you may want to set your %s variable to at least "
+   "%d and retry the command.");
 
 void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
 {
        if (degraded_cc)
-               warning(degrade_cc_to_c_warning);
+               warning(_(degrade_cc_to_c_warning));
        else if (needed)
-               warning(rename_limit_warning);
+               warning(_(rename_limit_warning));
        else
                return;
        if (0 < needed && needed < 32767)
-               warning(rename_limit_advice, varname, needed);
+               warning(_(rename_limit_advice), varname, needed);
 }
 
 void diff_flush(struct diff_options *options)
@@ -4397,7 +4779,7 @@ void diff_flush(struct diff_options *options)
                        show_stats(&diffstat, options);
                if (output_format & DIFF_FORMAT_SHORTSTAT)
                        show_shortstats(&diffstat, options);
-               if (output_format & DIFF_FORMAT_DIRSTAT)
+               if (output_format & DIFF_FORMAT_DIRSTAT && dirstat_by_line)
                        show_dirstat_by_line(&diffstat, options);
                free_diffstat_info(&diffstat);
                separator++;
@@ -4417,14 +4799,12 @@ void diff_flush(struct diff_options *options)
            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
+                * options->file to /dev/null should be safe, because 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->file = xfopen("/dev/null", "w");
                options->close_file = 1;
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
@@ -4437,13 +4817,9 @@ void diff_flush(struct diff_options *options)
 
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
-                       if (options->output_prefix) {
-                               struct strbuf *msg = NULL;
-                               msg = options->output_prefix(options,
-                                       options->output_prefix_data);
-                               fwrite(msg->buf, msg->len, 1, stdout);
-                       }
-                       putc(options->line_termination, options->file);
+                       fprintf(options->file, "%s%c",
+                               diff_line_prefix(options),
+                               options->line_termination);
                        if (options->stat_sep) {
                                /* attach patch instead of inline */
                                fputs(options->stat_sep, options->file);
@@ -4481,27 +4857,32 @@ free_queue:
        }
 }
 
-static void diffcore_apply_filter(const char *filter)
+static int match_filter(const struct diff_options *options, const struct diff_filepair *p)
+{
+       return (((p->status == DIFF_STATUS_MODIFIED) &&
+                ((p->score &&
+                  filter_bit_tst(DIFF_STATUS_FILTER_BROKEN, options)) ||
+                 (!p->score &&
+                  filter_bit_tst(DIFF_STATUS_MODIFIED, options)))) ||
+               ((p->status != DIFF_STATUS_MODIFIED) &&
+                filter_bit_tst(p->status, options)));
+}
+
+static void diffcore_apply_filter(struct diff_options *options)
 {
        int i;
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
+
        DIFF_QUEUE_CLEAR(&outq);
 
-       if (!filter)
+       if (!options->filter)
                return;
 
-       if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
+       if (filter_bit_tst(DIFF_STATUS_FILTER_AON, options)) {
                int found;
                for (i = found = 0; !found && i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       if (((p->status == DIFF_STATUS_MODIFIED) &&
-                            ((p->score &&
-                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
-                             (!p->score &&
-                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
-                           ((p->status != DIFF_STATUS_MODIFIED) &&
-                            strchr(filter, p->status)))
+                       if (match_filter(options, q->queue[i]))
                                found++;
                }
                if (found)
@@ -4519,14 +4900,7 @@ static void diffcore_apply_filter(const char *filter)
                /* Only the matching ones */
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
-
-                       if (((p->status == DIFF_STATUS_MODIFIED) &&
-                            ((p->score &&
-                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
-                             (!p->score &&
-                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
-                           ((p->status != DIFF_STATUS_MODIFIED) &&
-                            strchr(filter, p->status)))
+                       if (match_filter(options, p))
                                diff_q(&outq, p);
                        else
                                diff_free_filepair(p);
@@ -4549,6 +4923,38 @@ static int diff_filespec_is_identical(struct diff_filespec *one,
        return !memcmp(one->data, two->data, one->size);
 }
 
+static int diff_filespec_check_stat_unmatch(struct diff_filepair *p)
+{
+       if (p->done_skip_stat_unmatch)
+               return p->skip_stat_unmatch_result;
+
+       p->done_skip_stat_unmatch = 1;
+       p->skip_stat_unmatch_result = 0;
+       /*
+        * 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
+        *    do not match these criteria.  They have real
+        *    differences.
+        *
+        * 2. At this point, the file is known to be modified,
+        *    with the same mode and size, and the object
+        *    name of one side is unknown.  Need to inspect
+        *    the identical contents.
+        */
+       if (!DIFF_FILE_VALID(p->one) || /* (1) */
+           !DIFF_FILE_VALID(p->two) ||
+           (p->one->oid_valid && p->two->oid_valid) ||
+           (p->one->mode != p->two->mode) ||
+           diff_populate_filespec(p->one, CHECK_SIZE_ONLY) ||
+           diff_populate_filespec(p->two, CHECK_SIZE_ONLY) ||
+           (p->one->size != p->two->size) ||
+           !diff_filespec_is_identical(p->one, p->two)) /* (2) */
+               p->skip_stat_unmatch_result = 1;
+       return p->skip_stat_unmatch_result;
+}
+
 static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
 {
        int i;
@@ -4559,27 +4965,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
 
-               /*
-                * 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
-                *    do not match these criteria.  They have real
-                *    differences.
-                *
-                * 2. At this point, the file is known to be modified,
-                *    with the same mode and size, and the object
-                *    name of one side is unknown.  Need to inspect
-                *    the identical contents.
-                */
-               if (!DIFF_FILE_VALID(p->one) || /* (1) */
-                   !DIFF_FILE_VALID(p->two) ||
-                   (p->one->sha1_valid && p->two->sha1_valid) ||
-                   (p->one->mode != p->two->mode) ||
-                   diff_populate_filespec(p->one, 1) ||
-                   diff_populate_filespec(p->two, 1) ||
-                   (p->one->size != p->two->size) ||
-                   !diff_filespec_is_identical(p->one, p->two)) /* (2) */
+               if (diff_filespec_check_stat_unmatch(p))
                        diff_q(&outq, p);
                else {
                        /*
@@ -4610,11 +4996,12 @@ static int diffnamecmp(const void *a_, const void *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);
+       QSORT(q->queue, q->nr, diffnamecmp);
 }
 
 void diffcore_std(struct diff_options *options)
 {
+       /* NOTE please keep the following in sync with diff_tree_combined() */
        if (options->skip_stat_unmatch)
                diffcore_skip_stat_unmatch(options);
        if (!options->found_follow) {
@@ -4633,7 +5020,7 @@ void diffcore_std(struct diff_options *options)
        if (!options->found_follow)
                /* See try_to_follow_renames() in tree-diff.c */
                diff_resolve_rename_copy();
-       diffcore_apply_filter(options->filter);
+       diffcore_apply_filter(options);
 
        if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
@@ -4647,7 +5034,7 @@ int diff_result_code(struct diff_options *opt, int status)
 {
        int result = 0;
 
-       diff_warn_rename_limit("diff.renamelimit",
+       diff_warn_rename_limit("diff.renameLimit",
                               opt->needed_rename_limit,
                               opt->degraded_cc_to_c);
        if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
@@ -4689,8 +5076,8 @@ static int is_submodule_ignored(const char *path, struct diff_options *options)
 
 void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
-                   const unsigned char *sha1,
-                   int sha1_valid,
+                   const struct object_id *oid,
+                   int oid_valid,
                    const char *concatpath, unsigned dirty_submodule)
 {
        struct diff_filespec *one, *two;
@@ -4722,9 +5109,9 @@ void diff_addremove(struct diff_options *options,
        two = alloc_filespec(concatpath);
 
        if (addremove != '+')
-               fill_filespec(one, sha1, sha1_valid, mode);
+               fill_filespec(one, oid, oid_valid, mode);
        if (addremove != '-') {
-               fill_filespec(two, sha1, sha1_valid, mode);
+               fill_filespec(two, oid, oid_valid, mode);
                two->dirty_submodule = dirty_submodule;
        }
 
@@ -4735,27 +5122,24 @@ void diff_addremove(struct diff_options *options,
 
 void diff_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
-                const unsigned char *old_sha1,
-                const unsigned char *new_sha1,
-                int old_sha1_valid, int new_sha1_valid,
+                const struct object_id *old_oid,
+                const struct object_id *new_oid,
+                int old_oid_valid, int new_oid_valid,
                 const char *concatpath,
                 unsigned old_dirty_submodule, unsigned new_dirty_submodule)
 {
        struct diff_filespec *one, *two;
+       struct diff_filepair *p;
 
        if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) &&
            is_submodule_ignored(concatpath, options))
                return;
 
        if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
-               unsigned tmp;
-               const unsigned char *tmp_c;
-               tmp = old_mode; old_mode = new_mode; new_mode = tmp;
-               tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
-               tmp = old_sha1_valid; old_sha1_valid = new_sha1_valid;
-                       new_sha1_valid = tmp;
-               tmp = old_dirty_submodule; old_dirty_submodule = new_dirty_submodule;
-                       new_dirty_submodule = tmp;
+               SWAP(old_mode, new_mode);
+               SWAP(old_oid, new_oid);
+               SWAP(old_oid_valid, new_oid_valid);
+               SWAP(old_dirty_submodule, new_dirty_submodule);
        }
 
        if (options->prefix &&
@@ -4764,14 +5148,20 @@ void diff_change(struct diff_options *options,
 
        one = alloc_filespec(concatpath);
        two = alloc_filespec(concatpath);
-       fill_filespec(one, old_sha1, old_sha1_valid, old_mode);
-       fill_filespec(two, new_sha1, new_sha1_valid, new_mode);
+       fill_filespec(one, old_oid, old_oid_valid, old_mode);
+       fill_filespec(two, new_oid, new_oid_valid, new_mode);
        one->dirty_submodule = old_dirty_submodule;
        two->dirty_submodule = new_dirty_submodule;
+       p = diff_queue(&diff_queued_diff, one, two);
 
-       diff_queue(&diff_queued_diff, one, two);
-       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
-               DIFF_OPT_SET(options, HAS_CHANGES);
+       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               return;
+
+       if (DIFF_OPT_TST(options, QUICK) && options->skip_stat_unmatch &&
+           !diff_filespec_check_stat_unmatch(p))
+               return;
+
+       DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
@@ -4796,7 +5186,7 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        struct diff_tempfile *temp;
        const char *argv[3];
        const char **arg = argv;
-       struct child_process child;
+       struct child_process child = CHILD_PROCESS_INIT;
        struct strbuf buf = STRBUF_INIT;
        int err = 0;
 
@@ -4805,7 +5195,6 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        *arg++ = temp->name;
        *arg = NULL;
 
-       memset(&child, 0, sizeof(child));
        child.use_shell = 1;
        child.argv = argv;
        child.out = -1;
@@ -4834,7 +5223,7 @@ size_t fill_textconv(struct userdiff_driver *driver,
 {
        size_t size;
 
-       if (!driver || !driver->textconv) {
+       if (!driver) {
                if (!DIFF_FILE_VALID(df)) {
                        *outbuf = "";
                        return 0;
@@ -4845,8 +5234,12 @@ size_t fill_textconv(struct userdiff_driver *driver,
                return df->size;
        }
 
-       if (driver->textconv_cache && df->sha1_valid) {
-               *outbuf = notes_cache_get(driver->textconv_cache, df->sha1,
+       if (!driver->textconv)
+               die("BUG: fill_textconv called with non-textconv driver");
+
+       if (driver->textconv_cache && df->oid_valid) {
+               *outbuf = notes_cache_get(driver->textconv_cache,
+                                         &df->oid,
                                          &size);
                if (*outbuf)
                        return size;
@@ -4856,9 +5249,9 @@ size_t fill_textconv(struct userdiff_driver *driver,
        if (!*outbuf)
                die("unable to read files to diff");
 
-       if (driver->textconv_cache && df->sha1_valid) {
+       if (driver->textconv_cache && df->oid_valid) {
                /* ignore errors, as we might be in a readonly repository */
-               notes_cache_put(driver->textconv_cache, df->sha1, *outbuf,
+               notes_cache_put(driver->textconv_cache, &df->oid, *outbuf,
                                size);
                /*
                 * we could save up changes and flush them all at the end,
@@ -4871,3 +5264,42 @@ size_t fill_textconv(struct userdiff_driver *driver,
 
        return size;
 }
+
+int textconv_object(const char *path,
+                   unsigned mode,
+                   const struct object_id *oid,
+                   int oid_valid,
+                   char **buf,
+                   unsigned long *buf_size)
+{
+       struct diff_filespec *df;
+       struct userdiff_driver *textconv;
+
+       df = alloc_filespec(path);
+       fill_filespec(df, oid, oid_valid, mode);
+       textconv = get_textconv(df);
+       if (!textconv) {
+               free_filespec(df);
+               return 0;
+       }
+
+       *buf_size = fill_textconv(textconv, df, buf);
+       free_filespec(df);
+       return 1;
+}
+
+void setup_diff_pager(struct diff_options *opt)
+{
+       /*
+        * If the user asked for our exit code, then either they want --quiet
+        * or --exit-code. We should definitely not bother with a pager in the
+        * former case, as we will generate no output. Since we still properly
+        * report our exit code even when a pager is run, we _could_ run a
+        * pager with --exit-code. But since we have not done so historically,
+        * and because it is easy to find people oneline advising "git diff
+        * --exit-code" in hooks and other scripts, we do not do so.
+        */
+       if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
+           check_pager_config("diff") != 0)
+               setup_pager();
+}