]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jc/ws-error-highlight'
authorJunio C Hamano <gitster@pobox.com>
Wed, 26 Oct 2016 20:14:43 +0000 (13:14 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 26 Oct 2016 20:14:43 +0000 (13:14 -0700)
"git diff/log --ws-error-highlight=<kind>" lacked the corresponding
configuration variable to set it by default.

* jc/ws-error-highlight:
  diff: introduce diff.wsErrorHighlight option
  diff.c: move ws-error-highlight parsing helpers up
  diff.c: refactor parse_ws_error_highlight()
  t4015: split out the "setup" part of ws-error-highlight test

1  2 
Documentation/diff-config.txt
Documentation/diff-options.txt
diff.c

index b27a38f8965179fc312531c0500418742cb6641b,c0396e66a593ca3c86496aca5295a3de44832acf..58f4bd6afa12240f9e416860ac12d82776815ee9
@@@ -75,7 -75,7 +75,7 @@@ diff.ignoreSubmodules:
        commands such as 'git diff-files'. 'git checkout' also honors
        this setting when reporting uncommitted changes. Setting it to
        'all' disables the submodule summary normally shown by 'git commit'
 -      and 'git status' when 'status.submoduleSummary' is set unless it is
 +      and 'git status' when `status.submoduleSummary` is set unless it is
        overridden by using the --ignore-submodules command-line option.
        The 'git submodule' commands are not affected by this setting.
  
@@@ -105,16 -105,12 +105,16 @@@ diff.orderFile:
  
  diff.renameLimit::
        The number of files to consider when performing the copy/rename
 -      detection; equivalent to the 'git diff' option '-l'.
 +      detection; equivalent to the 'git diff' option `-l`.
  
  diff.renames::
 -      Tells Git to detect renames.  If set to any boolean value, it
 -      will enable basic rename detection.  If set to "copies" or
 -      "copy", it will detect copies, as well.
 +      Whether and how Git detects renames.  If set to "false",
 +      rename detection is disabled. If set to "true", basic rename
 +      detection is enabled.  If set to "copies" or "copy", Git will
 +      detect copies, as well.  Defaults to true.  Note that this
 +      affects only 'git diff' Porcelain like linkgit:git-diff[1] and
 +      linkgit:git-log[1], and not lower level commands such as
 +      linkgit:git-diff-files[1].
  
  diff.suppressBlankEmpty::
        A boolean to inhibit the standard behavior of printing a space
  
  diff.submodule::
        Specify the format in which differences in submodules are
 -      shown.  The "log" format lists the commits in the range like
 -      linkgit:git-submodule[1] `summary` does.  The "short" format
 -      format just shows the names of the commits at the beginning
 -      and end of the range.  Defaults to short.
 +      shown.  The "short" format just shows the names of the commits
 +      at the beginning and end of the range. The "log" format lists
 +      the commits in the range like linkgit:git-submodule[1] `summary`
 +      does. The "diff" format shows an inline diff of the changed
 +      contents of the submodule. Defaults to "short".
  
  diff.wordRegex::
        A POSIX Extended Regular Expression used to determine what is a "word"
@@@ -171,12 -166,6 +171,12 @@@ diff.tool:
  
  include::mergetools-diff.txt[]
  
 +diff.indentHeuristic::
 +diff.compactionHeuristic::
 +      Set one of these options to `true` to enable one of two
 +      experimental heuristics that shift diff hunk boundaries to
 +      make patches easier to read.
 +
  diff.algorithm::
        Choose a diff algorithm.  The variants are as follows:
  +
        low-occurrence common elements".
  --
  +
+ diff.wsErrorHighlight::
+       A comma separated list of `old`, `new`, `context`, that
+       specifies how whitespace errors on lines are highlighted
+       with `color.diff.whitespace`.  Can be overridden by the
+       command line option `--ws-error-highlight=<kind>`
index 2d77a196269b2d50de9c661fa8869badf155730a,dfd6bc99c6684310f22d931468204f041030cc3f..29630c238912ef9b70ee1a8e9f3906a4d1f63ddd
@@@ -26,12 -26,12 +26,12 @@@ ifndef::git-format-patch[
  ifdef::git-diff[]
        This is the default.
  endif::git-diff[]
 -endif::git-format-patch[]
  
  -s::
  --no-patch::
        Suppress diff output. Useful for commands like `git show` that
        show the patch by default, or to cancel the effect of `--patch`.
 +endif::git-format-patch[]
  
  -U<n>::
  --unified=<n>::
@@@ -63,8 -63,6 +63,8 @@@ ifndef::git-format-patch[
        Synonym for `-p --raw`.
  endif::git-format-patch[]
  
 +include::diff-heuristic-options.txt[]
 +
  --minimal::
        Spend extra time to make sure the smallest possible
        diff is produced.
@@@ -205,16 -203,13 +205,16 @@@ any of those replacements occurred
        of the `--diff-filter` option on what the status letters mean.
  
  --submodule[=<format>]::
 -      Specify how differences in submodules are shown.  When `--submodule`
 -      or `--submodule=log` is given, the 'log' format is used.  This format lists
 -      the commits in the range like linkgit:git-submodule[1] `summary` does.
 -      Omitting the `--submodule` option or specifying `--submodule=short`,
 -      uses the 'short' format. This format just shows the names of the commits
 -      at the beginning and end of the range.  Can be tweaked via the
 -      `diff.submodule` configuration variable.
 +      Specify how differences in submodules are shown.  When specifying
 +      `--submodule=short` the 'short' format is used.  This format just
 +      shows the names of the commits at the beginning and end of the range.
 +      When `--submodule` or `--submodule=log` is specified, the 'log'
 +      format is used.  This format lists the commits in the range like
 +      linkgit:git-submodule[1] `summary` does.  When `--submodule=diff`
 +      is specified, the 'diff' format is used.  This format shows an
 +      inline diff of the changes in the submodule contents between the
 +      commit range.  Defaults to `diff.submodule` or the 'short' format
 +      if the config option is unset.
  
  --color[=<when>]::
        Show colored diff.
@@@ -276,7 -271,7 +276,7 @@@ For example, `--word-diff-regex=.` wil
  and, correspondingly, show differences character by character.
  +
  The regex can also be set via a diff driver or configuration option, see
 -linkgit:gitattributes[1] or linkgit:git-config[1].  Giving it explicitly
 +linkgit:gitattributes[5] or linkgit:git-config[1].  Giving it explicitly
  overrides any diff driver or configuration setting.  Diff drivers
  override configuration settings.
  
@@@ -291,8 -286,8 +291,8 @@@ endif::git-format-patch[
  
  ifndef::git-format-patch[]
  --check::
 -      Warn if changes introduce whitespace errors.  What are
 -      considered whitespace errors is controlled by `core.whitespace`
 +      Warn if changes introduce conflict markers or whitespace errors.
 +      What are considered whitespace errors is controlled by `core.whitespace`
        configuration.  By default, trailing whitespaces (including
        lines that solely consist of whitespaces) and a space character
        that is immediately followed by a tab character inside the
        lines are highlighted.  E.g. `--ws-error-highlight=new,old`
        highlights whitespace errors on both deleted and added lines.
        `all` can be used as a short-hand for `old,new,context`.
+       The `diff.wsErrorHighlight` configuration variable can be
+       used to specify the default behaviour.
  
  endif::git-format-patch[]
  
@@@ -417,9 -414,6 +419,9 @@@ ifndef::git-format-patch[
        paths are selected if there is any file that matches
        other criteria in the comparison; if there is no file
        that matches other criteria, nothing is selected.
 ++
 +Also, these upper-case letters can be downcased to exclude.  E.g.
 +`--diff-filter=ad` excludes added and deleted paths.
  
  -S<string>::
        Look for differences that change the number of occurrences of
@@@ -567,8 -561,5 +569,8 @@@ endif::git-format-patch[
  --no-prefix::
        Do not show any source or destination prefix.
  
 +--line-prefix=<prefix>::
 +      Prepend an additional prefix to every line of output.
 +
  For more detailed explanation on these common options, see also
  linkgit:gitdiffcore[7].
diff --combined diff.c
index f5d6d7e4f6700d86089ac84e930c7898ae01c6d8,9acf04f5b02b113f11801c8c5d19b6ec5f84cb62..569f615d3b13b2a57bc0f91ac35ae0fd12c52ef0
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
  #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
@@@ -27,8 -27,6 +27,8 @@@
  #endif
  
  static int diff_detect_rename_default;
 +static int diff_indent_heuristic; /* experimental */
 +static int diff_compaction_heuristic; /* experimental */
  static int diff_rename_limit_default = 400;
  static int diff_suppress_blank_empty;
  static int diff_use_color_default = -1;
@@@ -43,6 -41,7 +43,7 @@@ 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,       /* FUNCINFO */
  };
  
 +static NORETURN void die_want_option(const char *option_name)
 +{
 +      die(_("option '%s' requires a value"), option_name);
 +}
 +
  static int parse_diff_color_slot(const char *var)
  {
        if (!strcasecmp(var, "context") || !strcasecmp(var, "plain"))
@@@ -138,11 -132,9 +139,11 @@@ static int parse_dirstat_params(struct 
  static int parse_submodule_params(struct diff_options *options, const char *value)
  {
        if (!strcmp(value, "log"))
 -              DIFF_OPT_SET(options, SUBMODULE_LOG);
 +              options->submodule_format = DIFF_SUBMODULE_LOG;
        else if (!strcmp(value, "short"))
 -              DIFF_OPT_CLR(options, SUBMODULE_LOG);
 +              options->submodule_format = DIFF_SUBMODULE_SHORT;
 +      else if (!strcmp(value, "diff"))
 +              options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF;
        else
                return -1;
        return 0;
@@@ -172,32 -164,49 +173,69 @@@ long parse_algorithm_value(const char *
        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);
 +              if (diff_indent_heuristic)
 +                      diff_compaction_heuristic = 0;
 +      }
 +      if (!strcmp(var, "diff.compactionheuristic")) {
 +              diff_compaction_heuristic = git_config_bool(var, value);
 +              if (diff_compaction_heuristic)
 +                      diff_indent_heuristic = 0;
 +      }
 +      return 0;
 +}
 +
  int git_diff_ui_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                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)
+                       return -1;
+               ws_error_highlight_default = val;
+               return 0;
+       }
        if (git_color_config(var, value, cb) < 0)
                return -1;
  
@@@ -353,7 -368,7 +400,7 @@@ static struct diff_tempfile 
         */
        const char *name;
  
 -      char hex[41];
 +      char hex[GIT_SHA1_HEXSZ + 1];
        char mode[10];
  
        /*
@@@ -376,6 -391,7 +423,6 @@@ struct emit_callback 
        const char **label_path;
        struct diff_words_data *diff_words;
        struct diff_options *opt;
 -      int *found_changesp;
        struct strbuf *header;
  };
  
@@@ -743,6 -759,7 +790,6 @@@ static void emit_rewrite_diff(const cha
  
        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);
        ecbdata.opt = o;
        if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
@@@ -971,8 -988,7 +1018,8 @@@ static int find_word_boundaries(mmfile_
  {
        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;
@@@ -1237,13 -1253,12 +1284,13 @@@ static void fn_out_consume(void *priv, 
        struct diff_options *o = ecbdata->opt;
        const char *line_prefix = diff_line_prefix(o);
  
 +      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;
                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;
        }
                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;
        }
  
                }
                diff_words_flush(ecbdata);
                if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
 -                      emit_line(ecbdata->opt, context, 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
                              line++;
                              len--;
                        }
 -                      emit_line(ecbdata->opt, context, reset, line, len);
 +                      emit_line(o, context, reset, line, len);
                }
                return;
        }
        default:
                /* incomplete line at the end */
                ecbdata->lno_in_preimage++;
 -              emit_line(ecbdata->opt,
 -                        diff_get_color(ecbdata->color_diff, DIFF_CONTEXT),
 +              emit_line(o, diff_get_color(ecbdata->color_diff, DIFF_CONTEXT),
                          reset, line, len);
                break;
        }
@@@ -1638,7 -1662,7 +1685,7 @@@ static void show_stats(struct diffstat_
         */
  
        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 ?
@@@ -1946,8 -1970,8 +1993,8 @@@ static void show_dirstat(struct diff_op
  
                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;
  
@@@ -2019,7 -2043,7 +2066,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);
  }
  
@@@ -2063,7 -2087,7 +2110,7 @@@ static void show_dirstat_by_line(struc
                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);
  }
  
@@@ -2312,37 -2336,16 +2359,37 @@@ static void builtin_diff(const char *na
        struct strbuf header = STRBUF_INIT;
        const char *line_prefix = diff_line_prefix(o);
  
 -      if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
 -                      (!one->mode || S_ISGITLINK(one->mode)) &&
 -                      (!two->mode || S_ISGITLINK(two->mode))) {
 +      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 (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->path ? one->path : two->path,
                                line_prefix,
 -                              one->sha1, two->sha1, two->dirty_submodule,
 +                              &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;
        }
  
        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
                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;
                if (!one->data && !two->data &&
                    S_ISREG(one->mode) && S_ISREG(two->mode) &&
                    !DIFF_OPT_TST(o, BINARY)) {
 -                      if (!hashcmp(one->sha1, two->sha1)) {
 +                      if (!oidcmp(&one->oid, &two->oid)) {
                                if (must_show_header)
                                        fprintf(o->file, "%s", header.buf);
                                goto free_ab_and_return;
                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);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
@@@ -2529,7 -2542,7 +2576,7 @@@ static void builtin_diffstat(const cha
                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;
@@@ -2641,9 -2654,12 +2688,9 @@@ static void builtin_checkdiff(const cha
  
  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;
@@@ -2662,8 -2678,8 +2709,8 @@@ void fill_filespec(struct diff_filespe
  {
        if (mode) {
                spec->mode = canon_mode(mode);
 -              hashcpy(spec->sha1, sha1);
 -              spec->sha1_valid = sha1_valid;
 +              hashcpy(spec->oid.hash, sha1);
 +              spec->oid_valid = sha1_valid;
        }
  }
  
@@@ -2706,13 -2722,6 +2753,13 @@@ static int reuse_worktree_file(const ch
        if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1))
                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))
 +              return 0;
 +
        len = strlen(name);
        pos = cache_name_pos(name, len);
        if (pos < 0)
         * 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 (hashcmp(sha1, ce->oid.hash) || !S_ISREG(ce->ce_mode))
                return 0;
  
        /*
  
  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;
  }
@@@ -2796,8 -2804,8 +2843,8 @@@ int diff_populate_filespec(struct diff_
        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.hash, 0)) {
                struct strbuf buf = STRBUF_INIT;
                struct stat st;
                int fd;
        else {
                enum object_type type;
                if (size_only || (flags & CHECK_BINARY)) {
 -                      type = sha1_object_info(s->sha1, &s->size);
 +                      type = sha1_object_info(s->oid.hash, &s->size);
                        if (type < 0)
 -                              die("unable to read %s", sha1_to_hex(s->sha1));
 +                              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) {
                                return 0;
                        }
                }
 -              s->data = read_sha1_file(s->sha1, &type, &s->size);
 +              s->data = read_sha1_file(s->oid.hash, &type, &s->size);
                if (!s->data)
 -                      die("unable to read %s", sha1_to_hex(s->sha1));
 +                      die("unable to read %s", oid_to_hex(&s->oid));
                s->should_free = 1;
        }
        return 0;
@@@ -2896,7 -2903,7 +2943,7 @@@ void diff_free_filespec_data(struct dif
  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;
                die_errno("unable to write temp-file");
        close_tempfile(&temp->tempfile);
        temp->name = get_tempfile_path(&temp->tempfile);
 -      strcpy(temp->hex, sha1_to_hex(sha1));
 -      temp->hex[40] = 0;
 -      sprintf(temp->mode, "%06o", mode);
 +      oid_to_hex_r(temp->hex, oid);
 +      xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
        strbuf_release(&buf);
        strbuf_release(&template);
        free(path_dup);
@@@ -2939,14 -2947,14 +2986,14 @@@ static struct diff_tempfile *prepare_te
                 * 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 (!S_ISGITLINK(one->mode) &&
 -          (!one->sha1_valid ||
 -           reuse_worktree_file(name, one->sha1, 1))) {
 +          (!one->oid_valid ||
 +           reuse_worktree_file(name, one->oid.hash, 1))) {
                struct stat st;
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
                        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)
 +                              sha1_to_hex_r(temp->hex, null_sha1);
                        else
 -                              strcpy(temp->hex, sha1_to_hex(one->sha1));
 +                              sha1_to_hex_r(temp->hex, one->oid.hash);
                        /* 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
                         * DIFF_FILE_VALID(one).
                         */
 -                      sprintf(temp->mode, "%06o", one->mode);
 +                      xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode);
                }
                return temp;
        }
                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;
  }
@@@ -3098,7 -3106,7 +3145,7 @@@ static void fill_metainfo(struct strbu
        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)) {
                                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));
 +                          find_unique_abbrev(one->oid.hash, abbrev));
 +              strbuf_add_unique_abbrev(msg, two->oid.hash, abbrev);
                if (one->mode == two->mode)
                        strbuf_addf(msg, " %06o", one->mode);
                strbuf_addf(msg, "%s\n", reset);
@@@ -3164,20 -3172,20 +3211,20 @@@ static void run_diff_cmd(const char *pg
  static void diff_fill_sha1_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)
@@@ -3307,7 -3315,7 +3354,7 @@@ void diff_setup(struct diff_options *op
        options->rename_limit = -1;
        options->dirstat_permille = diff_dirstat_permille_default;
        options->context = diff_context_default;
-       options->ws_error_highlight = WSEH_NEW;
+       options->ws_error_highlight = ws_error_highlight_default;
        DIFF_OPT_SET(options, RENAME_EMPTY);
  
        /* pathchange left =NULL by default */
        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);
 +      else if (diff_compaction_heuristic)
 +              DIFF_XDL_SET(options, COMPACTION_HEURISTIC);
  
        options->orderfile = diff_order_file_cfg;
  
@@@ -3347,7 -3351,7 +3394,7 @@@ void diff_setup_done(struct diff_option
        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"
@@@ -3543,7 -3547,7 +3590,7 @@@ static int stat_opt(struct diff_option
                        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;
                        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;
                        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;
                        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;
@@@ -3698,55 -3702,25 +3745,29 @@@ static void enable_patch_output(int *fm
        *fmt |= DIFF_FORMAT_PATCH;
  }
  
- static int parse_one_token(const char **arg, const char *token)
+ static int parse_ws_error_highlight_opt(struct diff_options *opt, const char *arg)
  {
-       const char *rest;
-       if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
-               *arg = rest;
-               return 1;
-       }
-       return 0;
- }
+       int val = parse_ws_error_highlight(arg);
  
- static int parse_ws_error_highlight(struct diff_options *opt, 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 {
-                       error("unknown value after ws-error-highlight=%.*s",
-                             (int)(arg - orig_arg), orig_arg);
-                       return 0;
-               }
-               if (*arg)
-                       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)
 +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")
            || opt_arg(arg, 'U', "unified", &options->context))
                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);
 +              DIFF_XDL_CLR(options, COMPACTION_HEURISTIC);
 +      } else if (!strcmp(arg, "--no-indent-heuristic"))
 +              DIFF_XDL_CLR(options, INDENT_HEURISTIC);
 +      else if (!strcmp(arg, "--compaction-heuristic")) {
 +              DIFF_XDL_SET(options, COMPACTION_HEURISTIC);
 +              DIFF_XDL_CLR(options, INDENT_HEURISTIC);
 +      } else if (!strcmp(arg, "--no-compaction-heuristic"))
 +              DIFF_XDL_CLR(options, COMPACTION_HEURISTIC);
        else if (!strcmp(arg, "--patience"))
                options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
        else if (!strcmp(arg, "--histogram"))
                DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(options, arg);
        } else if (!strcmp(arg, "--submodule"))
 -              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(options, arg);
+               return parse_ws_error_highlight_opt(options, arg);
  
        /* misc options */
        else if (!strcmp(arg, "-z"))
        else if (!strcmp(arg, "--pickaxe-regex"))
                options->pickaxe_opts |= DIFF_PICKAXE_REGEX;
        else if ((argcount = short_opt('O', av, &optarg))) {
 -              options->orderfile = optarg;
 +              const char *path = prefix_filename(prefix, strlen(prefix), optarg);
 +              options->orderfile = xstrdup(path);
                return argcount;
        }
        else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
                options->a_prefix = optarg;
                return argcount;
        }
 +      else if ((argcount = parse_long_opt("line-prefix", av, &optarg))) {
 +              options->line_prefix = optarg;
 +              options->line_prefix_length = strlen(options->line_prefix);
 +              graph_setup_line_prefix(options);
 +              return argcount;
 +      }
        else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
                options->b_prefix = optarg;
                return argcount;
        else if (!strcmp(arg, "--no-function-context"))
                DIFF_OPT_CLR(options, FUNCCONTEXT);
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
 -              options->file = fopen(optarg, "w");
 +              const char *path = prefix_filename(prefix, strlen(prefix), optarg);
 +              options->file = fopen(path, "w");
                if (!options->file)
 -                      die_errno("Could not open '%s'", optarg);
 +                      die_errno("Could not open '%s'", path);
                options->close_file = 1;
 +              if (options->use_color != GIT_COLOR_ALWAYS)
 +                      options->use_color = GIT_COLOR_NEVER;
                return argcount;
        } else
                return 0;
@@@ -4136,8 -4090,7 +4157,8 @@@ void diff_free_filepair(struct diff_fil
        free(p);
  }
  
 -/* This is different from find_unique_abbrev() in that
 +/*
 + * 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)
  
        abbrev = find_unique_abbrev(sha1, len);
        abblen = strlen(abbrev);
 +
 +      /*
 +       * 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 < 37) {
                static char hex[41];
                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);
@@@ -4188,9 -4121,8 +4209,9 @@@ static void diff_flush_raw(struct diff_
        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_unique_abbrev(p->one->oid.hash, opt->abbrev));
 +              fprintf(opt->file, "%s ",
 +                      diff_unique_abbrev(p->two->oid.hash, opt->abbrev));
        }
        if (p->score) {
                fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p),
@@@ -4239,11 -4171,11 +4260,11 @@@ int diff_unmodified_pair(struct diff_fi
        /* 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;
  }
@@@ -4304,7 -4236,7 +4325,7 @@@ void diff_debug_filespec(struct diff_fi
                s->path,
                DIFF_FILE_VALID(s) ? "valid" : "invalid",
                s->mode,
 -              s->sha1_valid ? sha1_to_hex(s->sha1) : "");
 +              s->oid_valid ? oid_to_hex(&s->oid) : "");
        fprintf(stderr, "queue[%d] %s size %lu\n",
                x, one ? one : "",
                s->size);
@@@ -4374,11 -4306,11 +4395,11 @@@ static void diff_resolve_rename_copy(vo
                        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
@@@ -4520,7 -4452,7 +4541,7 @@@ static void patch_id_consume(void *priv
  }
  
  /* 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, unsigned char *sha1, int diff_header_only)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
  
                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");
  
                len1 = remove_space(p->one->path, strlen(p->one->path));
                len2 = remove_space(p->two->path, strlen(p->two->path));
                                        len2, p->two->path);
                git_SHA1_Update(&ctx, buffer, len1);
  
 +              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),
 +                                      40);
 +                      git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid),
 +                                      40);
                        continue;
                }
  
        return 0;
  }
  
 -int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
 +int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1, 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, sha1, diff_header_only);
  
        for (i = 0; i < q->nr; i++)
                diff_free_filepair(q->queue[i]);
@@@ -4893,7 -4819,7 +4914,7 @@@ static int diff_filespec_check_stat_unm
         */
        if (!DIFF_FILE_VALID(p->one) || /* (1) */
            !DIFF_FILE_VALID(p->two) ||
 -          (p->one->sha1_valid && p->two->sha1_valid) ||
 +          (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) ||
@@@ -4944,7 -4870,7 +4965,7 @@@ static int diffnamecmp(const void *a_, 
  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)
@@@ -5175,7 -5101,7 +5196,7 @@@ size_t fill_textconv(struct userdiff_dr
  {
        size_t size;
  
 -      if (!driver || !driver->textconv) {
 +      if (!driver) {
                if (!DIFF_FILE_VALID(df)) {
                        *outbuf = "";
                        return 0;
                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.hash,
                                          &size);
                if (*outbuf)
                        return size;
        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.hash, *outbuf,
                                size);
                /*
                 * we could save up changes and flush them all at the end,