Merge branch 'jc/diff-populate-filespec-size-only-fix' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 21 Mar 2017 22:03:26 +0000 (15:03 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 21 Mar 2017 22:03:26 +0000 (15:03 -0700)
"git diff --quiet" relies on the size field in diff_filespec to be
correctly populated, but diff_populate_filespec() helper function
made an incorrect short-cut when asked only to populate the size
field for paths that need to go through convert_to_git() (e.g. CRLF
conversion).

* jc/diff-populate-filespec-size-only-fix:
diff: do not short-cut CHECK_SIZE_ONLY check in diff_populate_filespec()

1  2 
diff.c
diff --combined diff.c
index 051761be405ece65423d43830f08ed7b7dec9756,37e60ca601fe4c3c106fe7dbdbf3d7fe05adfcf1..a628ac3a95108ca79aef0b41b7051ab2fcbe693a
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -18,7 -18,6 +18,7 @@@
  #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; /* experimental */
  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;
@@@ -43,7 -40,6 +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"))
@@@ -139,11 -130,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;
@@@ -173,61 -162,12 +173,61 @@@ 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);
 +      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 -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;
                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;
  
@@@ -421,6 -344,7 +421,6 @@@ struct emit_callback 
        const char **label_path;
        struct diff_words_data *diff_words;
        struct diff_options *opt;
 -      int *found_changesp;
        struct strbuf *header;
  };
  
@@@ -788,6 -712,7 +788,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) {
@@@ -1016,8 -941,7 +1016,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;
@@@ -1282,13 -1206,12 +1282,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;
        }
@@@ -1683,7 -1615,7 +1683,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 ?
@@@ -1991,8 -1923,8 +1991,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;
  
                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);
@@@ -2064,7 -1996,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);
  }
  
@@@ -2108,7 -2040,7 +2108,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);
  }
  
@@@ -2357,37 -2289,16 +2357,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);
@@@ -2574,7 -2495,7 +2574,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;
@@@ -2707,8 -2628,8 +2707,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;
        }
  }
  
@@@ -2751,13 -2672,6 +2751,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;
  
        /*
@@@ -2797,8 -2711,7 +2797,8 @@@ static int diff_populate_gitlink(struc
        if (s->dirty_submodule)
                dirty = "-dirty";
  
 -      strbuf_addf(&buf, "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
 +      strbuf_addf(&buf, "Subproject commit %s%s\n",
 +                  oid_to_hex(&s->oid), dirty);
        s->size = buf.len;
        if (size_only) {
                s->data = NULL;
@@@ -2841,8 -2754,8 +2841,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;
                        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(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;
        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;
@@@ -2941,7 -2870,7 +2958,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);
 -      sha1_to_hex_r(temp->hex, sha1);
 +      oid_to_hex_r(temp->hex, oid);
        xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
        strbuf_release(&buf);
        strbuf_release(&template);
@@@ -2990,8 -2919,8 +3007,8 @@@ static struct diff_tempfile *prepare_te
        }
  
        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)
 +                      if (!one->oid_valid)
                                sha1_to_hex_r(temp->hex, null_sha1);
                        else
 -                              sha1_to_hex_r(temp->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
                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;
  }
@@@ -3094,22 -3023,6 +3111,22 @@@ static int similarity_index(struct diff
        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,
        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)) {
                            (!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);
@@@ -3225,20 -3138,20 +3242,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)
@@@ -3363,14 -3276,12 +3380,14 @@@ void diff_setup(struct diff_options *op
  
        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 = diff_context_default;
 -      options->ws_error_highlight = WSEH_NEW;
 +      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->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;
  
@@@ -3408,7 -3317,7 +3425,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"
                         */
                        read_cache();
        }
 -      if (options->abbrev <= 0 || 40 < options->abbrev)
 +      if (40 < options->abbrev)
                options->abbrev = 40; /* full */
  
        /*
@@@ -3604,7 -3513,7 +3621,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;
@@@ -3759,14 -3668,40 +3776,14 @@@ 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;
@@@ -3875,10 -3810,6 +3892,10 @@@ int diff_opt_parse(struct diff_options 
                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"))
                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);
 +      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"))
                            offending, optarg);
                return argcount;
        }
 +      else if (!strcmp(arg, "--no-abbrev"))
 +              options->abbrev = 0;
        else if (!strcmp(arg, "--abbrev"))
                options->abbrev = DEFAULT_ABBREV;
        else if (skip_prefix(arg, "--abbrev=", &arg)) {
                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;
                if (!options->file)
                        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;
@@@ -4171,46 -4088,27 +4188,46 @@@ void diff_free_filepair(struct diff_fil
        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_SHA1_HEXSZ + 1];
                if (len < abblen && abblen <= len + 2)
                        xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, "..");
                else
                        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)
        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),
@@@ -4272,11 -4169,11 +4289,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;
  }
@@@ -4337,7 -4234,7 +4354,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);
@@@ -4407,11 -4304,11 +4424,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
@@@ -4450,7 -4347,6 +4467,7 @@@ static void flush_one_pair(struct diff_
                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);
        }
  }
@@@ -4554,7 -4450,7 +4571,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]);
@@@ -4693,25 -4583,25 +4710,25 @@@ static int is_summary_empty(const struc
  }
  
  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)
@@@ -4927,7 -4817,7 +4944,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) ||
@@@ -4978,7 -4868,7 +4995,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)
@@@ -5118,10 -5008,14 +5135,10 @@@ void diff_change(struct diff_options *o
                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_sha1, new_sha1);
 +              SWAP(old_sha1_valid, new_sha1_valid);
 +              SWAP(old_dirty_submodule, new_dirty_submodule);
        }
  
        if (options->prefix &&
@@@ -5219,9 -5113,8 +5236,9 @@@ size_t fill_textconv(struct userdiff_dr
        if (!driver->textconv)
                die("BUG: fill_textconv called with non-textconv driver");
  
 -      if (driver->textconv_cache && df->sha1_valid) {
 -              *outbuf = notes_cache_get(driver->textconv_cache, df->sha1,
 +      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,