Merge branch 'nd/diff-quiet-stat-dirty'
authorJunio C Hamano <gitster@pobox.com>
Thu, 27 Feb 2014 22:01:21 +0000 (14:01 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 27 Feb 2014 22:01:21 +0000 (14:01 -0800)
"git diff --quiet -- pathspec1 pathspec2" sometimes did not return
correct status value.

* nd/diff-quiet-stat-dirty:
diff: do not quit early on stat-dirty files
diff.c: move diffcore_skip_stat_unmatch core logic out for reuse later

1  2 
diff.c
diffcore.h
diff --combined diff.c
index 8e4a6a910519b2bb6d6a0337682ab402f2650997,691578b2c6f6d980638980b0c575a8356af5c64d..e8006666d8a528689157aed0e84d23a5477111be
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -30,7 -30,6 +30,7 @@@ static int diff_use_color_default = -1
  static int diff_context_default = 3;
  static const char *diff_word_regex_cfg;
  static const char *external_diff_cmd_cfg;
 +static const char *diff_order_file_cfg;
  int diff_auto_refresh_index = 1;
  static int diff_mnemonic_prefix;
  static int diff_no_prefix;
@@@ -202,8 -201,6 +202,8 @@@ int git_diff_ui_config(const char *var
                return git_config_string(&external_diff_cmd_cfg, var, value);
        if (!strcmp(var, "diff.wordregex"))
                return git_config_string(&diff_word_regex_cfg, var, value);
 +      if (!strcmp(var, "diff.orderfile"))
 +              return git_config_pathname(&diff_order_file_cfg, var, value);
  
        if (!strcmp(var, "diff.ignoresubmodules"))
                handle_ignore_submodules_arg(&default_diff_options, value);
@@@ -238,7 -235,7 +238,7 @@@ int git_diff_basic_config(const char *v
        if (userdiff_config(var, value) < 0)
                return -1;
  
 -      if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
 +      if (starts_with(var, "diff.color.") || starts_with(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
                if (slot < 0)
                        return 0;
                return 0;
        }
  
 -      if (!prefixcmp(var, "submodule."))
 +      if (starts_with(var, "submodule."))
                return parse_submodule_config_option(var, value);
  
        return git_default_config(var, value, cb);
@@@ -672,7 -669,7 +672,7 @@@ 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 ? name_b : name_a);
 +      ecbdata.ws_rule = whitespace_rule(name_b);
        ecbdata.opt = o;
        if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
                mmfile_t mf1, mf2;
@@@ -1218,7 -1215,7 +1218,7 @@@ static void fn_out_consume(void *priv, 
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->plus);
                        return;
 -              } else if (!prefixcmp(line, "\\ ")) {
 +              } else if (starts_with(line, "\\ ")) {
                        /*
                         * Eat the "no newline at eof" marker as if we
                         * saw a "+" or "-" line with nothing on it,
@@@ -2255,7 -2252,7 +2255,7 @@@ static void builtin_diff(const char *na
                        (!two->mode || S_ISGITLINK(two->mode))) {
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
 -              show_submodule_summary(o->file, one ? one->path : two->path,
 +              show_submodule_summary(o->file, one->path ? one->path : two->path,
                                line_prefix,
                                one->sha1, two->sha1, two->dirty_submodule,
                                meta, del, add, reset);
                ecbdata.label_path = lbl;
                ecbdata.color_diff = want_color(o->use_color);
                ecbdata.found_changesp = &o->found_changes;
 -              ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
 +              ecbdata.ws_rule = whitespace_rule(name_b);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.opt = o;
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
                if (!diffopts)
                        ;
 -              else if (!prefixcmp(diffopts, "--unified="))
 +              else if (starts_with(diffopts, "--unified="))
                        xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
 -              else if (!prefixcmp(diffopts, "-u"))
 +              else if (starts_with(diffopts, "-u"))
                        xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
                if (o->word_diff)
                        init_diff_words_data(&ecbdata, o, one, two);
@@@ -2902,16 -2899,11 +2902,16 @@@ static void run_external_diff(const cha
                              struct diff_filespec *one,
                              struct diff_filespec *two,
                              const char *xfrm_msg,
 -                            int complete_rewrite)
 +                            int complete_rewrite,
 +                            struct diff_options *o)
  {
        const char *spawn_arg[10];
        int retval;
        const char **arg = &spawn_arg[0];
 +      struct diff_queue_struct *q = &diff_queued_diff;
 +      const char *env[3] = { NULL };
 +      char env_counter[50];
 +      char env_total[50];
  
        if (one && two) {
                struct diff_tempfile *temp_one, *temp_two;
        }
        *arg = NULL;
        fflush(NULL);
 -      retval = run_command_v_opt(spawn_arg, RUN_USING_SHELL);
 +
 +      env[0] = env_counter;
 +      snprintf(env_counter, sizeof(env_counter), "GIT_DIFF_PATH_COUNTER=%d",
 +               ++o->diff_path_counter);
 +      env[1] = env_total;
 +      snprintf(env_total, sizeof(env_total), "GIT_DIFF_PATH_TOTAL=%d", q->nr);
 +
 +      retval = run_command_v_opt_cd_env(spawn_arg, RUN_USING_SHELL, NULL, env);
        remove_tempfile();
        if (retval) {
                fprintf(stderr, "external diff died, stopping at %s.\n", name);
@@@ -3057,7 -3042,7 +3057,7 @@@ static void run_diff_cmd(const char *pg
  
        if (pgm) {
                run_external_diff(pgm, name, other, one, two, xfrm_msg,
 -                                complete_rewrite);
 +                                complete_rewrite, o);
                return;
        }
        if (one && two)
@@@ -3222,8 -3207,6 +3222,8 @@@ void diff_setup(struct diff_options *op
        options->detect_rename = diff_detect_rename_default;
        options->xdl_opts |= diff_algorithm;
  
 +      options->orderfile = diff_order_file_cfg;
 +
        if (diff_no_prefix) {
                options->a_prefix = options->b_prefix = "";
        } else if (!diff_mnemonic_prefix) {
@@@ -3236,9 -3219,6 +3236,9 @@@ void diff_setup_done(struct diff_option
  {
        int count = 0;
  
 +      if (options->set_default)
 +              options->set_default(options);
 +
        if (options->output_format & DIFF_FORMAT_NAME)
                count++;
        if (options->output_format & DIFF_FORMAT_NAME_STATUS)
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
 +
 +      options->diff_path_counter = 0;
  }
  
  static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
@@@ -3410,10 -3388,10 +3410,10 @@@ int parse_long_opt(const char *opt, con
        if (arg[0] != '-' || arg[1] != '-')
                return 0;
        arg += strlen("--");
 -      if (prefixcmp(arg, opt))
 +      if (!starts_with(arg, opt))
                return 0;
        arg += strlen(opt);
 -      if (*arg == '=') { /* sticked form: --option=value */
 +      if (*arg == '=') { /* stuck form: --option=value */
                *optarg = arg + 1;
                return 1;
        }
@@@ -3441,7 -3419,7 +3441,7 @@@ static int stat_opt(struct diff_option
  
        switch (*arg) {
        case '-':
 -              if (!prefixcmp(arg, "-width")) {
 +              if (starts_with(arg, "-width")) {
                        arg += strlen("-width");
                        if (*arg == '=')
                                width = strtoul(arg + 1, &end, 10);
                                width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
 -              } else if (!prefixcmp(arg, "-name-width")) {
 +              } else if (starts_with(arg, "-name-width")) {
                        arg += strlen("-name-width");
                        if (*arg == '=')
                                name_width = strtoul(arg + 1, &end, 10);
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
 -              } else if (!prefixcmp(arg, "-graph-width")) {
 +              } else if (starts_with(arg, "-graph-width")) {
                        arg += strlen("-graph-width");
                        if (*arg == '=')
                                graph_width = strtoul(arg + 1, &end, 10);
                                graph_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
 -              } else if (!prefixcmp(arg, "-count")) {
 +              } else if (starts_with(arg, "-count")) {
                        arg += strlen("-count");
                        if (*arg == '=')
                                count = strtoul(arg + 1, &end, 10);
@@@ -3525,88 -3503,6 +3525,88 @@@ static int parse_submodule_opt(struct d
        return 1;
  }
  
 +static const char diff_status_letters[] = {
 +      DIFF_STATUS_ADDED,
 +      DIFF_STATUS_COPIED,
 +      DIFF_STATUS_DELETED,
 +      DIFF_STATUS_MODIFIED,
 +      DIFF_STATUS_RENAMED,
 +      DIFF_STATUS_TYPE_CHANGED,
 +      DIFF_STATUS_UNKNOWN,
 +      DIFF_STATUS_UNMERGED,
 +      DIFF_STATUS_FILTER_AON,
 +      DIFF_STATUS_FILTER_BROKEN,
 +      '\0',
 +};
 +
 +static unsigned int filter_bit['Z' + 1];
 +
 +static void prepare_filter_bits(void)
 +{
 +      int i;
 +
 +      if (!filter_bit[DIFF_STATUS_ADDED]) {
 +              for (i = 0; diff_status_letters[i]; i++)
 +                      filter_bit[(int) diff_status_letters[i]] = (1 << i);
 +      }
 +}
 +
 +static unsigned filter_bit_tst(char status, const struct diff_options *opt)
 +{
 +      return opt->filter & filter_bit[(int) status];
 +}
 +
 +static int parse_diff_filter_opt(const char *optarg, struct diff_options *opt)
 +{
 +      int i, optch;
 +
 +      prepare_filter_bits();
 +
 +      /*
 +       * If there is a negation e.g. 'd' in the input, and we haven't
 +       * initialized the filter field with another --diff-filter, start
 +       * from full set of bits, except for AON.
 +       */
 +      if (!opt->filter) {
 +              for (i = 0; (optch = optarg[i]) != '\0'; i++) {
 +                      if (optch < 'a' || 'z' < optch)
 +                              continue;
 +                      opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1;
 +                      opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON];
 +                      break;
 +              }
 +      }
 +
 +      for (i = 0; (optch = optarg[i]) != '\0'; i++) {
 +              unsigned int bit;
 +              int negate;
 +
 +              if ('a' <= optch && optch <= 'z') {
 +                      negate = 1;
 +                      optch = toupper(optch);
 +              } else {
 +                      negate = 0;
 +              }
 +
 +              bit = (0 <= optch && optch <= 'Z') ? filter_bit[optch] : 0;
 +              if (!bit)
 +                      return optarg[i];
 +              if (negate)
 +                      opt->filter &= ~bit;
 +              else
 +                      opt->filter |= bit;
 +      }
 +      return 0;
 +}
 +
 +/* Used only by "diff-files" and "diff --no-index" */
 +void handle_deprecated_show_diff_q(struct diff_options *opt)
 +{
 +      warning("'diff -q' and 'diff-files -q' are deprecated.");
 +      warning("Use 'diff --diff-filter=d' instead to ignore deleted filepairs.");
 +      parse_diff_filter_opt("d", opt);
 +}
 +
  static void enable_patch_output(int *fmt) {
        *fmt &= ~DIFF_FORMAT_NO_OUTPUT;
        *fmt |= DIFF_FORMAT_PATCH;
@@@ -3633,15 -3529,15 +3633,15 @@@ int diff_opt_parse(struct diff_options 
                options->output_format |= DIFF_FORMAT_SHORTSTAT;
        else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat"))
                return parse_dirstat_opt(options, "");
 -      else if (!prefixcmp(arg, "-X"))
 +      else if (starts_with(arg, "-X"))
                return parse_dirstat_opt(options, arg + 2);
 -      else if (!prefixcmp(arg, "--dirstat="))
 +      else if (starts_with(arg, "--dirstat="))
                return parse_dirstat_opt(options, arg + 10);
        else if (!strcmp(arg, "--cumulative"))
                return parse_dirstat_opt(options, "cumulative");
        else if (!strcmp(arg, "--dirstat-by-file"))
                return parse_dirstat_opt(options, "files");
 -      else if (!prefixcmp(arg, "--dirstat-by-file=")) {
 +      else if (starts_with(arg, "--dirstat-by-file=")) {
                parse_dirstat_opt(options, "files");
                return parse_dirstat_opt(options, arg + 18);
        }
                options->output_format |= DIFF_FORMAT_NAME_STATUS;
        else if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
 -      else if (!prefixcmp(arg, "--stat"))
 +      else if (starts_with(arg, "--stat"))
                /* --stat, --stat-width, --stat-name-width, or --stat-count */
                return stat_opt(options, av);
  
        /* renames options */
 -      else if (!prefixcmp(arg, "-B") || !prefixcmp(arg, "--break-rewrites=") ||
 +      else if (starts_with(arg, "-B") || starts_with(arg, "--break-rewrites=") ||
                 !strcmp(arg, "--break-rewrites")) {
                if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -B: %s", arg+2);
        }
 -      else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--find-renames=") ||
 +      else if (starts_with(arg, "-M") || starts_with(arg, "--find-renames=") ||
                 !strcmp(arg, "--find-renames")) {
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -M: %s", arg+2);
        else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
                options->irreversible_delete = 1;
        }
 -      else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") ||
 +      else if (starts_with(arg, "-C") || starts_with(arg, "--find-copies=") ||
                 !strcmp(arg, "--find-copies")) {
                if (options->detect_rename == DIFF_DETECT_COPY)
                        DIFF_OPT_SET(options, FIND_COPIES_HARDER);
                DIFF_OPT_CLR(options, RENAME_EMPTY);
        else if (!strcmp(arg, "--relative"))
                DIFF_OPT_SET(options, RELATIVE_NAME);
 -      else if (!prefixcmp(arg, "--relative=")) {
 +      else if (starts_with(arg, "--relative=")) {
                DIFF_OPT_SET(options, RELATIVE_NAME);
                options->prefix = arg + 11;
        }
                DIFF_OPT_CLR(options, FOLLOW_RENAMES);
        else if (!strcmp(arg, "--color"))
                options->use_color = 1;
 -      else if (!prefixcmp(arg, "--color=")) {
 +      else if (starts_with(arg, "--color=")) {
                int value = git_config_colorbool(NULL, arg+8);
                if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
                options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
 -      else if (!prefixcmp(arg, "--color-words=")) {
 +      else if (starts_with(arg, "--color-words=")) {
                options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
                options->word_regex = arg + 14;
                if (options->word_diff == DIFF_WORDS_NONE)
                        options->word_diff = DIFF_WORDS_PLAIN;
        }
 -      else if (!prefixcmp(arg, "--word-diff=")) {
 +      else if (starts_with(arg, "--word-diff=")) {
                const char *type = arg + 12;
                if (!strcmp(type, "plain"))
                        options->word_diff = DIFF_WORDS_PLAIN;
        else if (!strcmp(arg, "--ignore-submodules")) {
                DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(options, "all");
 -      } else if (!prefixcmp(arg, "--ignore-submodules=")) {
 +      } else if (starts_with(arg, "--ignore-submodules=")) {
                DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(options, arg + 20);
        } else if (!strcmp(arg, "--submodule"))
                DIFF_OPT_SET(options, SUBMODULE_LOG);
 -      else if (!prefixcmp(arg, "--submodule="))
 +      else if (starts_with(arg, "--submodule="))
                return parse_submodule_opt(options, arg + 12);
  
        /* misc options */
                return argcount;
        }
        else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
 -              options->filter = optarg;
 +              int offending = parse_diff_filter_opt(optarg, options);
 +              if (offending)
 +                      die("unknown change class '%c' in --diff-filter=%s",
 +                          offending, optarg);
                return argcount;
        }
        else if (!strcmp(arg, "--abbrev"))
                options->abbrev = DEFAULT_ABBREV;
 -      else if (!prefixcmp(arg, "--abbrev=")) {
 +      else if (starts_with(arg, "--abbrev=")) {
                options->abbrev = strtoul(arg + 9, NULL, 10);
                if (options->abbrev < MINIMUM_ABBREV)
                        options->abbrev = MINIMUM_ABBREV;
@@@ -3926,15 -3819,15 +3926,15 @@@ static int diff_scoreopt_parse(const ch
        cmd = *opt++;
        if (cmd == '-') {
                /* convert the long-form arguments into short-form versions */
 -              if (!prefixcmp(opt, "break-rewrites")) {
 +              if (starts_with(opt, "break-rewrites")) {
                        opt += strlen("break-rewrites");
                        if (*opt == 0 || *opt++ == '=')
                                cmd = 'B';
 -              } else if (!prefixcmp(opt, "find-copies")) {
 +              } else if (starts_with(opt, "find-copies")) {
                        opt += strlen("find-copies");
                        if (*opt == 0 || *opt++ == '=')
                                cmd = 'C';
 -              } else if (!prefixcmp(opt, "find-renames")) {
 +              } else if (starts_with(opt, "find-renames")) {
                        opt += strlen("find-renames");
                        if (*opt == 0 || *opt++ == '=')
                                cmd = 'M';
@@@ -4139,9 -4032,9 +4139,9 @@@ void diff_debug_filespec(struct diff_fi
                DIFF_FILE_VALID(s) ? "valid" : "invalid",
                s->mode,
                s->sha1_valid ? sha1_to_hex(s->sha1) : "");
 -      fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
 +      fprintf(stderr, "queue[%d] %s size %lu\n",
                x, one ? one : "",
 -              s->size, s->xfrm_flags);
 +              s->size);
  }
  
  void diff_debug_filepair(const struct diff_filepair *p, int i)
@@@ -4344,7 -4237,7 +4344,7 @@@ static void patch_id_consume(void *priv
        int new_len;
  
        /* Ignore line numbers when computing the SHA1 of the patch */
 -      if (!prefixcmp(line, "@@ -"))
 +      if (starts_with(line, "@@ -"))
                return;
  
        new_len = remove_space(line, len);
@@@ -4631,32 -4524,27 +4631,32 @@@ free_queue
        }
  }
  
 -static void diffcore_apply_filter(const char *filter)
 +static int match_filter(const struct diff_options *options, const struct diff_filepair *p)
 +{
 +      return (((p->status == DIFF_STATUS_MODIFIED) &&
 +               ((p->score &&
 +                 filter_bit_tst(DIFF_STATUS_FILTER_BROKEN, options)) ||
 +                (!p->score &&
 +                 filter_bit_tst(DIFF_STATUS_MODIFIED, options)))) ||
 +              ((p->status != DIFF_STATUS_MODIFIED) &&
 +               filter_bit_tst(p->status, options)));
 +}
 +
 +static void diffcore_apply_filter(struct diff_options *options)
  {
        int i;
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
 +
        DIFF_QUEUE_CLEAR(&outq);
  
 -      if (!filter)
 +      if (!options->filter)
                return;
  
 -      if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
 +      if (filter_bit_tst(DIFF_STATUS_FILTER_AON, options)) {
                int found;
                for (i = found = 0; !found && i < q->nr; i++) {
 -                      struct diff_filepair *p = q->queue[i];
 -                      if (((p->status == DIFF_STATUS_MODIFIED) &&
 -                           ((p->score &&
 -                             strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
 -                            (!p->score &&
 -                             strchr(filter, DIFF_STATUS_MODIFIED)))) ||
 -                          ((p->status != DIFF_STATUS_MODIFIED) &&
 -                           strchr(filter, p->status)))
 +                      if (match_filter(options, q->queue[i]))
                                found++;
                }
                if (found)
                /* Only the matching ones */
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
 -
 -                      if (((p->status == DIFF_STATUS_MODIFIED) &&
 -                           ((p->score &&
 -                             strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
 -                            (!p->score &&
 -                             strchr(filter, DIFF_STATUS_MODIFIED)))) ||
 -                          ((p->status != DIFF_STATUS_MODIFIED) &&
 -                           strchr(filter, p->status)))
 +                      if (match_filter(options, p))
                                diff_q(&outq, p);
                        else
                                diff_free_filepair(p);
@@@ -4697,6 -4592,38 +4697,38 @@@ static int diff_filespec_is_identical(s
        return !memcmp(one->data, two->data, one->size);
  }
  
+ static int diff_filespec_check_stat_unmatch(struct diff_filepair *p)
+ {
+       if (p->done_skip_stat_unmatch)
+               return p->skip_stat_unmatch_result;
+       p->done_skip_stat_unmatch = 1;
+       p->skip_stat_unmatch_result = 0;
+       /*
+        * 1. Entries that come from stat info dirtiness
+        *    always have both sides (iow, not create/delete),
+        *    one side of the object name is unknown, with
+        *    the same mode and size.  Keep the ones that
+        *    do not match these criteria.  They have real
+        *    differences.
+        *
+        * 2. At this point, the file is known to be modified,
+        *    with the same mode and size, and the object
+        *    name of one side is unknown.  Need to inspect
+        *    the identical contents.
+        */
+       if (!DIFF_FILE_VALID(p->one) || /* (1) */
+           !DIFF_FILE_VALID(p->two) ||
+           (p->one->sha1_valid && p->two->sha1_valid) ||
+           (p->one->mode != p->two->mode) ||
+           diff_populate_filespec(p->one, 1) ||
+           diff_populate_filespec(p->two, 1) ||
+           (p->one->size != p->two->size) ||
+           !diff_filespec_is_identical(p->one, p->two)) /* (2) */
+               p->skip_stat_unmatch_result = 1;
+       return p->skip_stat_unmatch_result;
+ }
  static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
  {
        int i;
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
  
-               /*
-                * 1. Entries that come from stat info dirtiness
-                *    always have both sides (iow, not create/delete),
-                *    one side of the object name is unknown, with
-                *    the same mode and size.  Keep the ones that
-                *    do not match these criteria.  They have real
-                *    differences.
-                *
-                * 2. At this point, the file is known to be modified,
-                *    with the same mode and size, and the object
-                *    name of one side is unknown.  Need to inspect
-                *    the identical contents.
-                */
-               if (!DIFF_FILE_VALID(p->one) || /* (1) */
-                   !DIFF_FILE_VALID(p->two) ||
-                   (p->one->sha1_valid && p->two->sha1_valid) ||
-                   (p->one->mode != p->two->mode) ||
-                   diff_populate_filespec(p->one, 1) ||
-                   diff_populate_filespec(p->two, 1) ||
-                   (p->one->size != p->two->size) ||
-                   !diff_filespec_is_identical(p->one, p->two)) /* (2) */
+               if (diff_filespec_check_stat_unmatch(p))
                        diff_q(&outq, p);
                else {
                        /*
@@@ -4781,7 -4688,7 +4793,7 @@@ void diffcore_std(struct diff_options *
        if (!options->found_follow)
                /* See try_to_follow_renames() in tree-diff.c */
                diff_resolve_rename_copy();
 -      diffcore_apply_filter(options->filter);
 +      diffcore_apply_filter(options);
  
        if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
@@@ -4890,6 -4797,7 +4902,7 @@@ void diff_change(struct diff_options *o
                 unsigned old_dirty_submodule, unsigned new_dirty_submodule)
  {
        struct diff_filespec *one, *two;
+       struct diff_filepair *p;
  
        if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) &&
            is_submodule_ignored(concatpath, options))
        fill_filespec(two, new_sha1, new_sha1_valid, new_mode);
        one->dirty_submodule = old_dirty_submodule;
        two->dirty_submodule = new_dirty_submodule;
+       p = diff_queue(&diff_queued_diff, one, two);
  
-       diff_queue(&diff_queued_diff, one, two);
-       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
-               DIFF_OPT_SET(options, HAS_CHANGES);
+       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               return;
+       if (DIFF_OPT_TST(options, QUICK) && options->skip_stat_unmatch &&
+           !diff_filespec_check_stat_unmatch(p))
+               return;
+       DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
  struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
diff --combined diffcore.h
index 79de8cf28dc6919f069ae44f7f036a46264452e9,6b538bc2fbeb1b0577d5e233357178b1482f1786..1315cfd4ef0fbe0aeb0cae9a14bf09fd53212124
@@@ -29,8 -29,10 +29,8 @@@ struct diff_filespec 
        char *path;
        void *data;
        void *cnt_data;
 -      const char *funcname_pattern_ident;
        unsigned long size;
        int count;               /* Reference count */
 -      int xfrm_flags;          /* for use by the xfrm */
        int rename_used;         /* Count of rename users */
        unsigned short mode;     /* file mode */
        unsigned sha1_valid : 1; /* if true, use sha1 and trust mode;
        unsigned should_free : 1; /* data should be free()'ed */
        unsigned should_munmap : 1; /* data should be munmap()'ed */
        unsigned dirty_submodule : 2;  /* For submodules: its work tree is dirty */
 -      unsigned is_stdin : 1;
  #define DIRTY_SUBMODULE_UNTRACKED 1
  #define DIRTY_SUBMODULE_MODIFIED  2
 +      unsigned is_stdin : 1;
        unsigned has_more_entries : 1; /* only appear in combined diff */
 -      struct userdiff_driver *driver;
        /* data should be considered "binary"; -1 means "don't know yet" */
 -      int is_binary;
 +      int is_binary : 2;
 +      struct userdiff_driver *driver;
  };
  
  extern struct diff_filespec *alloc_filespec(const char *);
@@@ -68,6 -70,8 +68,8 @@@ struct diff_filepair 
        unsigned broken_pair : 1;
        unsigned renamed_pair : 1;
        unsigned is_unmerged : 1;
+       unsigned done_skip_stat_unmatch : 1;
+       unsigned skip_stat_unmatch_result : 1;
  };
  #define DIFF_PAIR_UNMERGED(p) ((p)->is_unmerged)