diff.c: emit_diff_symbol learns DIFF_SYMBOL_HEADER
[gitweb.git] / diff.c
diff --git a/diff.c b/diff.c
index 74283d9001fbc48d11ba5c449ea7c16692791c88..78f7c6f82fd44f528809b02576fdb1f4405d55bc 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -2,6 +2,7 @@
  * Copyright (C) 2005 Junio C Hamano
  */
 #include "cache.h"
+#include "config.h"
 #include "tempfile.h"
 #include "quote.h"
 #include "diff.h"
@@ -27,7 +28,7 @@
 #endif
 
 static int diff_detect_rename_default;
-static int diff_indent_heuristic; /* experimental */
+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;
@@ -290,9 +291,6 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
-       if (git_diff_heuristic_config(var, value, cb) < 0)
-               return -1;
-
        if (!strcmp(var, "diff.wserrorhighlight")) {
                int val = parse_ws_error_highlight(value);
                if (val < 0)
@@ -351,6 +349,9 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
        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);
 }
 
@@ -559,68 +560,180 @@ static void emit_line(struct diff_options *o, const char *set, const char *reset
        emit_line_0(o, set, reset, line[0], line+1, len-1);
 }
 
-static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)
-{
-       if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) &&
-             ecbdata->blank_at_eof_in_preimage &&
-             ecbdata->blank_at_eof_in_postimage &&
-             ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage &&
-             ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage))
-               return 0;
-       return ws_blank_line(line, len, ecbdata->ws_rule);
-}
+enum diff_symbol {
+       DIFF_SYMBOL_HEADER,
+       DIFF_SYMBOL_FILEPAIR_PLUS,
+       DIFF_SYMBOL_FILEPAIR_MINUS,
+       DIFF_SYMBOL_WORDS_PORCELAIN,
+       DIFF_SYMBOL_WORDS,
+       DIFF_SYMBOL_CONTEXT,
+       DIFF_SYMBOL_CONTEXT_INCOMPLETE,
+       DIFF_SYMBOL_PLUS,
+       DIFF_SYMBOL_MINUS,
+       DIFF_SYMBOL_NO_LF_EOF,
+       DIFF_SYMBOL_CONTEXT_FRAGINFO,
+       DIFF_SYMBOL_CONTEXT_MARKER,
+       DIFF_SYMBOL_SEPARATOR
+};
+/*
+ * Flags for content lines:
+ * 0..12 are whitespace rules
+ * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
+ * 16 is marking if the line is blank at EOF
+ */
+#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<16)
+#define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)
 
-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)
+static void emit_line_ws_markup(struct diff_options *o,
+                               const char *set, const char *reset,
+                               const char *line, int len, char sign,
+                               unsigned ws_rule, int blank_at_eof)
 {
-       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 (o->ws_error_highlight & ws_rule) {
+               ws = diff_get_color_opt(o, DIFF_WHITESPACE);
                if (!*ws)
                        ws = NULL;
        }
 
        if (!ws)
-               emit_line_0(ecbdata->opt, set, reset, sign, line, len);
-       else if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len))
+               emit_line_0(o, set, reset, sign, line, len);
+       else if (blank_at_eof)
                /* Blank line at EOF - paint '+' as well */
-               emit_line_0(ecbdata->opt, ws, reset, sign, line, len);
+               emit_line_0(o, ws, reset, sign, line, len);
        else {
                /* Emit just the prefix, then the rest. */
-               emit_line_0(ecbdata->opt, set, reset, sign, "", 0);
-               ws_check_emit(line, len, ecbdata->ws_rule,
-                             ecbdata->opt->file, set, reset, ws);
+               emit_line_0(o, set, reset, sign, "", 0);
+               ws_check_emit(line, len, ws_rule,
+                             o->file, set, reset, ws);
        }
 }
 
+static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s,
+                            const char *line, int len, unsigned flags)
+{
+       static const char *nneof = " No newline at end of file\n";
+       const char *context, *reset, *set, *meta;
+       switch (s) {
+       case DIFF_SYMBOL_NO_LF_EOF:
+               context = diff_get_color_opt(o, DIFF_CONTEXT);
+               reset = diff_get_color_opt(o, DIFF_RESET);
+               putc('\n', o->file);
+               emit_line_0(o, context, reset, '\\',
+                           nneof, strlen(nneof));
+               break;
+       case DIFF_SYMBOL_CONTEXT_FRAGINFO:
+               emit_line(o, "", "", line, len);
+               break;
+       case DIFF_SYMBOL_CONTEXT_INCOMPLETE:
+       case DIFF_SYMBOL_CONTEXT_MARKER:
+               context = diff_get_color_opt(o, DIFF_CONTEXT);
+               reset = diff_get_color_opt(o, DIFF_RESET);
+               emit_line(o, context, reset, line, len);
+               break;
+       case DIFF_SYMBOL_SEPARATOR:
+               fprintf(o->file, "%s%c",
+                       diff_line_prefix(o),
+                       o->line_termination);
+               break;
+       case DIFF_SYMBOL_CONTEXT:
+               set = diff_get_color_opt(o, DIFF_CONTEXT);
+               reset = diff_get_color_opt(o, DIFF_RESET);
+               emit_line_ws_markup(o, set, reset, line, len, ' ',
+                                   flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
+               break;
+       case DIFF_SYMBOL_PLUS:
+               set = diff_get_color_opt(o, DIFF_FILE_NEW);
+               reset = diff_get_color_opt(o, DIFF_RESET);
+               emit_line_ws_markup(o, set, reset, line, len, '+',
+                                   flags & DIFF_SYMBOL_CONTENT_WS_MASK,
+                                   flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
+               break;
+       case DIFF_SYMBOL_MINUS:
+               set = diff_get_color_opt(o, DIFF_FILE_OLD);
+               reset = diff_get_color_opt(o, DIFF_RESET);
+               emit_line_ws_markup(o, set, reset, line, len, '-',
+                                   flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
+               break;
+       case DIFF_SYMBOL_WORDS_PORCELAIN:
+               context = diff_get_color_opt(o, DIFF_CONTEXT);
+               reset = diff_get_color_opt(o, DIFF_RESET);
+               emit_line(o, context, reset, line, len);
+               fputs("~\n", o->file);
+               break;
+       case DIFF_SYMBOL_WORDS:
+               context = diff_get_color_opt(o, DIFF_CONTEXT);
+               reset = diff_get_color_opt(o, DIFF_RESET);
+               /*
+                * Skip the prefix character, if any.  With
+                * diff_suppress_blank_empty, there may be
+                * none.
+                */
+               if (line[0] != '\n') {
+                       line++;
+                       len--;
+               }
+               emit_line(o, context, reset, line, len);
+               break;
+       case DIFF_SYMBOL_FILEPAIR_PLUS:
+               meta = diff_get_color_opt(o, DIFF_METAINFO);
+               reset = diff_get_color_opt(o, DIFF_RESET);
+               fprintf(o->file, "%s%s+++ %s%s%s\n", diff_line_prefix(o), meta,
+                       line, reset,
+                       strchr(line, ' ') ? "\t" : "");
+               break;
+       case DIFF_SYMBOL_FILEPAIR_MINUS:
+               meta = diff_get_color_opt(o, DIFF_METAINFO);
+               reset = diff_get_color_opt(o, DIFF_RESET);
+               fprintf(o->file, "%s%s--- %s%s%s\n", diff_line_prefix(o), meta,
+                       line, reset,
+                       strchr(line, ' ') ? "\t" : "");
+               break;
+       case DIFF_SYMBOL_HEADER:
+               fprintf(o->file, "%s", line);
+               break;
+       default:
+               die("BUG: unknown diff symbol");
+       }
+}
+
+static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)
+{
+       if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) &&
+             ecbdata->blank_at_eof_in_preimage &&
+             ecbdata->blank_at_eof_in_postimage &&
+             ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage &&
+             ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage))
+               return 0;
+       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)
 {
-       emit_line_checked(reset, ecbdata, line, len,
-                         DIFF_FILE_NEW, WSEH_NEW, '+');
+       unsigned flags = WSEH_NEW | ecbdata->ws_rule;
+       if (new_blank_line_at_eof(ecbdata, line, len))
+               flags |= DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF;
+
+       emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_PLUS, line, len, flags);
 }
 
 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, '-');
+       unsigned flags = WSEH_OLD | ecbdata->ws_rule;
+       emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_MINUS, line, len, flags);
 }
 
 static void emit_context_line(const char *reset,
                              struct emit_callback *ecbdata,
                              const char *line, int len)
 {
-       emit_line_checked(reset, ecbdata, line, len,
-                         DIFF_CONTEXT, WSEH_CONTEXT, ' ');
+       unsigned flags = WSEH_CONTEXT | ecbdata->ws_rule;
+       emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT, line, len, flags);
 }
 
 static void emit_hunk_header(struct emit_callback *ecbdata,
@@ -643,7 +756,8 @@ 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, context, reset, line, len);
+               emit_diff_symbol(ecbdata->opt,
+                                DIFF_SYMBOL_CONTEXT_MARKER, line, len, 0);
                return;
        }
        ep += 2; /* skip over @@ */
@@ -677,7 +791,9 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
        }
 
        strbuf_add(&msgbuf, line + len, org_len - len);
-       emit_line(ecbdata->opt, "", "", msgbuf.buf, msgbuf.len);
+       strbuf_complete_line(&msgbuf);
+       emit_diff_symbol(ecbdata->opt,
+                        DIFF_SYMBOL_CONTEXT_FRAGINFO, msgbuf.buf, msgbuf.len, 0);
        strbuf_release(&msgbuf);
 }
 
@@ -718,7 +834,6 @@ static void emit_rewrite_lines(struct emit_callback *ecb,
                               int prefix, const char *data, int size)
 {
        const char *endp = NULL;
-       static const char *nneof = " No newline at end of file\n";
        const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
 
        while (0 < size) {
@@ -736,13 +851,8 @@ static void emit_rewrite_lines(struct emit_callback *ecb,
                size -= len;
                data += len;
        }
-       if (!endp) {
-               const char *context = diff_get_color(ecb->color_diff,
-                                                    DIFF_CONTEXT);
-               putc('\n', ecb->opt->file);
-               emit_line_0(ecb->opt, context, reset, '\\',
-                           nneof, strlen(nneof));
-       }
+       if (!endp)
+               emit_diff_symbol(ecb->opt, DIFF_SYMBOL_NO_LF_EOF, NULL, 0, 0);
 }
 
 static void emit_rewrite_diff(const char *name_a,
@@ -754,8 +864,6 @@ static void emit_rewrite_diff(const char *name_a,
                              struct diff_options *o)
 {
        int lc_a, lc_b;
-       const char *name_a_tab, *name_b_tab;
-       const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
        const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
        const char *reset = diff_get_color(o->use_color, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
@@ -775,8 +883,6 @@ static void emit_rewrite_diff(const char *name_a,
 
        name_a += (*name_a == '/');
        name_b += (*name_b == '/');
-       name_a_tab = strchr(name_a, ' ') ? "\t" : "";
-       name_b_tab = strchr(name_b, ' ') ? "\t" : "";
 
        strbuf_reset(&a_name);
        strbuf_reset(&b_name);
@@ -803,11 +909,13 @@ static void emit_rewrite_diff(const char *name_a,
 
        lc_a = count_lines(data_one, size_one);
        lc_b = count_lines(data_two, size_two);
-       fprintf(o->file,
-               "%s%s--- %s%s%s\n%s%s+++ %s%s%s\n%s%s@@ -",
-               line_prefix, metainfo, a_name.buf, name_a_tab, reset,
-               line_prefix, metainfo, b_name.buf, name_b_tab, reset,
-               line_prefix, fraginfo);
+
+       emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_MINUS,
+                        a_name.buf, a_name.len, 0);
+       emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_PLUS,
+                        b_name.buf, b_name.len, 0);
+
+       fprintf(o->file, "%s%s@@ -", line_prefix, fraginfo);
        if (!o->irreversible_delete)
                print_line_count(o->file, lc_a);
        else
@@ -1218,8 +1326,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);
        }
 }
 
@@ -1276,30 +1383,25 @@ static void find_lno(const char *line, struct emit_callback *ecbdata)
 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 *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;
-       const char *line_prefix = diff_line_prefix(o);
 
        o->found_changes = 1;
 
        if (ecbdata->header) {
-               fprintf(o->file, "%s", ecbdata->header->buf);
+               emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
+                                ecbdata->header->buf, ecbdata->header->len, 0);
                strbuf_reset(ecbdata->header);
                ecbdata->header = NULL;
        }
 
        if (ecbdata->label_path[0]) {
-               const char *name_a_tab, *name_b_tab;
-
-               name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : "";
-               name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
-
-               fprintf(o->file, "%s%s--- %s%s%s\n",
-                       line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab);
-               fprintf(o->file, "%s%s+++ %s%s%s\n",
-                       line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab);
+               emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_MINUS,
+                                ecbdata->label_path[0],
+                                strlen(ecbdata->label_path[0]), 0);
+               emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_PLUS,
+                                ecbdata->label_path[1],
+                                strlen(ecbdata->label_path[1]), 0);
                ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
        }
 
@@ -1315,12 +1417,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                len = sane_truncate_line(ecbdata, line, len);
                find_lno(line, ecbdata);
                emit_hunk_header(ecbdata, line, len);
-               if (line[len-1] != '\n')
-                       putc('\n', o->file);
                return;
        }
 
        if (ecbdata->diff_words) {
+               enum diff_symbol s =
+                       ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN ?
+                       DIFF_SYMBOL_WORDS_PORCELAIN : DIFF_SYMBOL_WORDS;
                if (line[0] == '-') {
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->minus);
@@ -1340,21 +1443,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                        return;
                }
                diff_words_flush(ecbdata);
-               if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
-                       emit_line(o, context, reset, line, len);
-                       fputs("~\n", o->file);
-               } else {
-                       /*
-                        * Skip the prefix character, if any.  With
-                        * diff_suppress_blank_empty, there may be
-                        * none.
-                        */
-                       if (line[0] != '\n') {
-                             line++;
-                             len--;
-                       }
-                       emit_line(o, context, reset, line, len);
-               }
+               emit_diff_symbol(o, s, line, len, 0);
                return;
        }
 
@@ -1375,8 +1464,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
        default:
                /* incomplete line at the end */
                ecbdata->lno_in_preimage++;
-               emit_line(o, diff_get_color(ecbdata->color_diff, DIFF_CONTEXT),
-                         reset, line, len);
+               emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE,
+                                line, len, 0);
                break;
        }
 }
@@ -2435,7 +2524,8 @@ static void builtin_diff(const char *name_a,
                if (complete_rewrite &&
                    (textconv_one || !diff_filespec_is_binary(one)) &&
                    (textconv_two || !diff_filespec_is_binary(two))) {
-                       fprintf(o->file, "%s", header.buf);
+                       emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
+                                        header.buf, header.len, 0);
                        strbuf_reset(&header);
                        emit_rewrite_diff(name_a, name_b, one, two,
                                                textconv_one, textconv_two, o);
@@ -2445,7 +2535,8 @@ static void builtin_diff(const char *name_a,
        }
 
        if (o->irreversible_delete && lbl[1][0] == '/') {
-               fprintf(o->file, "%s", header.buf);
+               emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf,
+                                header.len, 0);
                strbuf_reset(&header);
                goto free_ab_and_return;
        } else if (!DIFF_OPT_TST(o, TEXT) &&
@@ -2456,10 +2547,13 @@ static void builtin_diff(const char *name_a,
                    !DIFF_OPT_TST(o, BINARY)) {
                        if (!oidcmp(&one->oid, &two->oid)) {
                                if (must_show_header)
-                                       fprintf(o->file, "%s", header.buf);
+                                       emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
+                                                        header.buf, header.len,
+                                                        0);
                                goto free_ab_and_return;
                        }
-                       fprintf(o->file, "%s", header.buf);
+                       emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
+                                        header.buf, header.len, 0);
                        fprintf(o->file, "%sBinary files %s and %s differ\n",
                                line_prefix, lbl[0], lbl[1]);
                        goto free_ab_and_return;
@@ -2470,10 +2564,11 @@ static void builtin_diff(const char *name_a,
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size)) {
                        if (must_show_header)
-                               fprintf(o->file, "%s", header.buf);
+                               emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
+                                                header.buf, header.len, 0);
                        goto free_ab_and_return;
                }
-               fprintf(o->file, "%s", header.buf);
+               emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf, header.len, 0);
                strbuf_reset(&header);
                if (DIFF_OPT_TST(o, BINARY))
                        emit_binary_diff(o->file, &mf1, &mf2, line_prefix);
@@ -2491,7 +2586,8 @@ static void builtin_diff(const char *name_a,
                const struct userdiff_funcname *pe;
 
                if (must_show_header) {
-                       fprintf(o->file, "%s", header.buf);
+                       emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
+                                        header.buf, header.len, 0);
                        strbuf_reset(&header);
                }
 
@@ -2702,13 +2798,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->oid.hash, sha1);
-               spec->oid_valid = sha1_valid;
+               oidcpy(&spec->oid, oid);
+               spec->oid_valid = oid_valid;
        }
 }
 
@@ -2717,7 +2813,7 @@ 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)
 {
        const struct cache_entry *ce;
        struct stat st;
@@ -2748,14 +2844,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(name))
+       if (!want_file && would_convert_to_git(&the_index, name))
                return 0;
 
        len = strlen(name);
@@ -2768,7 +2864,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->oid.hash) || !S_ISREG(ce->ce_mode))
+       if (oidcmp(oid, &ce->oid) || !S_ISREG(ce->ce_mode))
                return 0;
 
        /*
@@ -2842,7 +2938,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags)
                return diff_populate_gitlink(s, size_only);
 
        if (!s->oid_valid ||
-           reuse_worktree_file(s->path, s->oid.hash, 0)) {
+           reuse_worktree_file(s->path, &s->oid, 0)) {
                struct strbuf buf = STRBUF_INIT;
                struct stat st;
                int fd;
@@ -2877,7 +2973,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags)
                 * point if the path requires us to run the content
                 * conversion.
                 */
-               if (size_only && !would_convert_to_git(s->path))
+               if (size_only && !would_convert_to_git(&the_index, s->path))
                        return 0;
 
                /*
@@ -2904,7 +3000,7 @@ int diff_populate_filespec(struct diff_filespec *s, unsigned int flags)
                /*
                 * Convert from working tree format to canonical git format
                 */
-               if (convert_to_git(s->path, s->data, s->size, &buf, crlf_warn)) {
+               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;
@@ -2951,8 +3047,7 @@ 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,
@@ -3008,7 +3103,7 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
 
        if (!S_ISGITLINK(one->mode) &&
            (!one->oid_valid ||
-            reuse_worktree_file(name, one->oid.hash, 1))) {
+            reuse_worktree_file(name, &one->oid, 1))) {
                struct stat st;
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
@@ -3030,13 +3125,13 @@ static struct diff_tempfile *prepare_temp_file(const char *name,
                        /* we can borrow from the file in the work tree */
                        temp->name = name;
                        if (!one->oid_valid)
-                               sha1_to_hex_r(temp->hex, null_sha1);
+                               oid_to_hex_r(temp->hex, &null_oid);
                        else
                                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).
                         */
                        xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode);
@@ -3239,7 +3334,7 @@ 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->oid_valid) {
@@ -3283,8 +3378,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
        const char *other;
        const char *attr_path;
 
-       name  = p->one->path;
-       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       name  = one->path;
+       other = (strcmp(name, two->path) ? two->path : NULL);
        attr_path = name;
        if (o->prefix_length)
                strip_prefix(o->prefix_length, &name, &other);
@@ -3298,8 +3393,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) &&
@@ -3344,8 +3439,8 @@ 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);
 
        builtin_diffstat(name, other, p->one, p->two, diffstat, o, p);
 }
@@ -3368,8 +3463,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);
 }
@@ -4071,9 +4166,7 @@ int diff_opt_parse(struct diff_options *options,
                DIFF_OPT_CLR(options, FUNCCONTEXT);
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
                char *path = prefix_filename(prefix, optarg);
-               options->file = fopen(path, "w");
-               if (!options->file)
-                       die_errno("Could not open '%s'", path);
+               options->file = xfopen(path, "w");
                options->close_file = 1;
                if (options->use_color != GIT_COLOR_ALWAYS)
                        options->use_color = GIT_COLOR_NEVER;
@@ -4584,7 +4677,7 @@ static void patch_id_add_mode(git_SHA_CTX *ctx, unsigned mode)
 }
 
 /* returns 0 upon success, and writes result into sha1 */
-static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1, int diff_header_only)
+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;
@@ -4616,8 +4709,8 @@ 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);
+               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));
@@ -4656,9 +4749,9 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1,
                if (diff_filespec_is_binary(p->one) ||
                    diff_filespec_is_binary(p->two)) {
                        git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid),
-                                       40);
+                                       GIT_SHA1_HEXSZ);
                        git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid),
-                                       40);
+                                       GIT_SHA1_HEXSZ);
                        continue;
                }
 
@@ -4671,15 +4764,15 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1,
                                     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_header_only)
+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, diff_header_only);
+       int result = diff_get_patch_id(options, oid, diff_header_only);
 
        for (i = 0; i < q->nr; i++)
                diff_free_filepair(q->queue[i]);
@@ -4737,6 +4830,21 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
                warning(_(rename_limit_advice), varname, needed);
 }
 
+static void diff_flush_patch_all_file_pairs(struct diff_options *o)
+{
+       int i;
+       struct diff_queue_struct *q = &diff_queued_diff;
+
+       if (WSEH_NEW & WS_RULE_MASK)
+               die("BUG: WS rules bit mask overlaps with diff symbol flags");
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (check_pair_status(p))
+                       diff_flush_patch(p, o);
+       }
+}
+
 void diff_flush(struct diff_options *options)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
@@ -4807,9 +4915,7 @@ void diff_flush(struct diff_options *options)
                 */
                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];
@@ -4822,20 +4928,14 @@ void diff_flush(struct diff_options *options)
 
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
-                       fprintf(options->file, "%s%c",
-                               diff_line_prefix(options),
-                               options->line_termination);
+                       emit_diff_symbol(options, DIFF_SYMBOL_SEPARATOR, NULL, 0, 0);
                        if (options->stat_sep) {
                                /* attach patch instead of inline */
                                fputs(options->stat_sep, options->file);
                        }
                }
 
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       if (check_pair_status(p))
-                               diff_flush_patch(p, options);
-               }
+               diff_flush_patch_all_file_pairs(options);
        }
 
        if (output_format & DIFF_FORMAT_CALLBACK)
@@ -5081,8 +5181,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;
@@ -5114,9 +5214,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;
        }
 
@@ -5127,9 +5227,9 @@ 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)
 {
@@ -5142,8 +5242,8 @@ void diff_change(struct diff_options *options,
 
        if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
                SWAP(old_mode, new_mode);
-               SWAP(old_sha1, new_sha1);
-               SWAP(old_sha1_valid, new_sha1_valid);
+               SWAP(old_oid, new_oid);
+               SWAP(old_oid_valid, new_oid_valid);
                SWAP(old_dirty_submodule, new_dirty_submodule);
        }
 
@@ -5153,8 +5253,8 @@ 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);
@@ -5244,7 +5344,7 @@ size_t fill_textconv(struct userdiff_driver *driver,
 
        if (driver->textconv_cache && df->oid_valid) {
                *outbuf = notes_cache_get(driver->textconv_cache,
-                                         df->oid.hash,
+                                         &df->oid,
                                          &size);
                if (*outbuf)
                        return size;
@@ -5256,7 +5356,7 @@ size_t fill_textconv(struct userdiff_driver *driver,
 
        if (driver->textconv_cache && df->oid_valid) {
                /* ignore errors, as we might be in a readonly repository */
-               notes_cache_put(driver->textconv_cache, df->oid.hash, *outbuf,
+               notes_cache_put(driver->textconv_cache, &df->oid, *outbuf,
                                size);
                /*
                 * we could save up changes and flush them all at the end,
@@ -5270,6 +5370,29 @@ 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)
 {
        /*