Merge branch 'jc/rename-degrade-cc-to-c' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 31 May 2011 19:00:02 +0000 (12:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 31 May 2011 19:00:02 +0000 (12:00 -0700)
* jc/rename-degrade-cc-to-c:
diffcore-rename: fall back to -C when -C -C busts the rename limit
diffcore-rename: record filepair for rename src
diffcore-rename: refactor "too many candidates" logic
builtin/diff.c: remove duplicated call to diff_result_code()

1  2 
builtin/diff.c
builtin/log.c
diff.c
diff.h
diffcore-rename.c
merge-recursive.c
t/t4001-diff-rename.sh
diff --combined builtin/diff.c
index 717fa1a3414e18cbdd4c6eb1e0785761687688d7,171ac39274ed8f2e7d5a03de2f88a280b9276b2f..14bd14fce0fa6b9c9b047142d23d4a2237b57a36
@@@ -71,9 -71,9 +71,9 @@@ static int builtin_diff_b_f(struct rev_
                usage(builtin_diff_usage);
  
        if (lstat(path, &st))
 -              die_errno("failed to stat '%s'", path);
 +              die_errno(_("failed to stat '%s'"), path);
        if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
 -              die("'%s': not a regular file or symlink", path);
 +              die(_("'%s': not a regular file or symlink"), path);
  
        diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
  
@@@ -197,12 -197,16 +197,11 @@@ static void refresh_index_quietly(void
        discard_cache();
        read_cache();
        refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
 -
 -      if (active_cache_changed &&
 -          !write_cache(fd, active_cache, active_nr))
 -              commit_locked_index(lock_file);
 -
 -      rollback_lock_file(lock_file);
 +      update_index_if_able(&the_index, lock_file);
  }
  
  static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv)
  {
-       int result;
        unsigned int options = 0;
  
        while (1 < argc && argv[1][0] == '-') {
                else if (!strcmp(argv[1], "-h"))
                        usage(builtin_diff_usage);
                else
 -                      return error("invalid option: %s", argv[1]);
 +                      return error(_("invalid option: %s"), argv[1]);
                argv++; argc--;
        }
  
                perror("read_cache_preload");
                return -1;
        }
-       result = run_diff_files(revs, options);
-       return diff_result_code(&revs->diffopt, result);
+       return run_diff_files(revs, options);
  }
  
  int cmd_diff(int argc, const char **argv, const char *prefix)
        DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
  
        if (nongit)
 -              die("Not a git repository");
 +              die(_("Not a git repository"));
        argc = setup_revisions(argc, argv, &rev, NULL);
        if (!rev.diffopt.output_format) {
                rev.diffopt.output_format = DIFF_FORMAT_PATCH;
                if (diff_setup_done(&rev.diffopt) < 0)
 -                      die("diff_setup_done failed");
 +                      die(_("diff_setup_done failed"));
        }
  
        DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
                        obj = parse_object(obj->sha1);
                obj = deref_tag(obj, NULL, 0);
                if (!obj)
 -                      die("invalid object '%s' given.", name);
 +                      die(_("invalid object '%s' given."), name);
                if (obj->type == OBJ_COMMIT)
                        obj = &((struct commit *)obj)->tree->object;
                if (obj->type == OBJ_TREE) {
                        if (ARRAY_SIZE(ent) <= ents)
 -                              die("more than %d trees given: '%s'",
 +                              die(_("more than %d trees given: '%s'"),
                                    (int) ARRAY_SIZE(ent), name);
                        obj->flags |= flags;
                        ent[ents].item = obj;
                }
                if (obj->type == OBJ_BLOB) {
                        if (2 <= blobs)
 -                              die("more than two blobs given: '%s'", name);
 +                              die(_("more than two blobs given: '%s'"), name);
                        hashcpy(blob[blobs].sha1, obj->sha1);
                        blob[blobs].name = name;
                        blob[blobs].mode = list->mode;
                        continue;
  
                }
 -              die("unhandled object '%s' given.", name);
 +              die(_("unhandled object '%s' given."), name);
        }
        if (rev.prune_data.nr) {
                if (!path)
diff --combined builtin/log.c
index c6dce9b8950410b08e13c11aa940404a7fcd2b78,707fd5706c37290bd65fcdf1f482202c54205881..55abe07610bce0a959b1878be128e09906f8fea4
@@@ -49,8 -49,13 +49,8 @@@ static int parse_decoration_style(cons
        return -1;
  }
  
 -static void cmd_log_init(int argc, const char **argv, const char *prefix,
 -                       struct rev_info *rev, struct setup_revision_opt *opt)
 +static void cmd_log_init_defaults(struct rev_info *rev)
  {
 -      int i;
 -      int decoration_given = 0;
 -      struct userformat_want w;
 -
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
        if (fmt_pretty)
  
        if (default_date_mode)
                rev->date_mode = parse_date_format(default_date_mode);
 +}
  
 +static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
 +                       struct rev_info *rev, struct setup_revision_opt *opt)
 +{
 +      int i;
 +      int decoration_given = 0;
 +      struct userformat_want w;
        /*
         * Check for -h before setup_revisions(), or "git log -h" will
         * fail when run without a git directory.
                        const char *v = skip_prefix(arg, "--decorate=");
                        decoration_style = parse_decoration_style(arg, v);
                        if (decoration_style < 0)
 -                              die("invalid --decorate option: %s", arg);
 +                              die(_("invalid --decorate option: %s"), arg);
                        decoration_given = 1;
                } else if (!strcmp(arg, "--no-decorate")) {
                        decoration_style = 0;
                } else if (!strcmp(arg, "-h")) {
                        usage(builtin_log_usage);
                } else
 -                      die("unrecognized argument: %s", arg);
 +                      die(_("unrecognized argument: %s"), arg);
        }
  
        /*
        setup_pager();
  }
  
 +static void cmd_log_init(int argc, const char **argv, const char *prefix,
 +                       struct rev_info *rev, struct setup_revision_opt *opt)
 +{
 +      cmd_log_init_defaults(rev);
 +      cmd_log_init_finish(argc, argv, prefix, rev, opt);
 +}
 +
  /*
   * This gives a rough estimate for how many commits we
   * will print out in the list.
@@@ -162,7 -153,7 +162,7 @@@ static void show_early_header(struct re
                if (rev->commit_format != CMIT_FMT_ONELINE)
                        putchar(rev->diffopt.line_termination);
        }
 -      printf("Final output: %d %s\n", nr, stage);
 +      printf(_("Final output: %d %s\n"), nr, stage);
  }
  
  static struct itimerval early_output_timer;
@@@ -256,12 -247,14 +256,14 @@@ static void finish_early_output(struct 
  static int cmd_log_walk(struct rev_info *rev)
  {
        struct commit *commit;
+       int saved_nrl = 0;
+       int saved_dcctc = 0;
  
        if (rev->early_output)
                setup_early_output(rev);
  
        if (prepare_revision_walk(rev))
 -              die("revision walk setup failed");
 +              die(_("revision walk setup failed"));
  
        if (rev->early_output)
                finish_early_output(rev);
                }
                free_commit_list(commit->parents);
                commit->parents = NULL;
+               if (saved_nrl < rev->diffopt.needed_rename_limit)
+                       saved_nrl = rev->diffopt.needed_rename_limit;
+               if (rev->diffopt.degraded_cc_to_c)
+                       saved_dcctc = 1;
        }
+       rev->diffopt.degraded_cc_to_c = saved_dcctc;
+       rev->diffopt.needed_rename_limit = saved_nrl;
        if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
            DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
                return 02;
@@@ -358,7 -358,7 +367,7 @@@ static int show_object(const unsigned c
        int offset = 0;
  
        if (!buf)
 -              return error("Could not read object %s", sha1_to_hex(sha1));
 +              return error(_("Could not read object %s"), sha1_to_hex(sha1));
  
        if (show_tag_object)
                while (offset < size && buf[offset] != '\n') {
@@@ -445,7 -445,7 +454,7 @@@ int cmd_show(int argc, const char **arg
                                break;
                        o = parse_object(t->tagged->sha1);
                        if (!o)
 -                              ret = error("Could not read object %s",
 +                              ret = error(_("Could not read object %s"),
                                            sha1_to_hex(t->tagged->sha1));
                        objects[i].item = o;
                        i--;
                        ret = cmd_log_walk(&rev);
                        break;
                default:
 -                      ret = error("Unknown type: %d", o->type);
 +                      ret = error(_("Unknown type: %d"), o->type);
                }
        }
        free(objects);
@@@ -495,11 -495,16 +504,11 @@@ int cmd_log_reflog(int argc, const cha
        rev.verbose_header = 1;
        memset(&opt, 0, sizeof(opt));
        opt.def = "HEAD";
 -      cmd_log_init(argc, argv, prefix, &rev, &opt);
 -
 -      /*
 -       * This means that we override whatever commit format the user gave
 -       * on the cmd line.  Sad, but cmd_log_init() currently doesn't
 -       * allow us to set a different default.
 -       */
 +      cmd_log_init_defaults(&rev);
        rev.commit_format = CMIT_FMT_ONELINE;
        rev.use_terminator = 1;
        rev.always_show_header = 1;
 +      cmd_log_init_finish(argc, argv, prefix, &rev, &opt);
  
        return cmd_log_walk(&rev);
  }
@@@ -564,7 -569,7 +573,7 @@@ static int git_format_config(const cha
  {
        if (!strcmp(var, "format.headers")) {
                if (!value)
 -                      die("format.headers without value");
 +                      die(_("format.headers without value"));
                add_header(value);
                return 0;
        }
@@@ -627,7 -632,7 +636,7 @@@ static FILE *realstdout = NULL
  static const char *output_directory = NULL;
  static int outdir_offset;
  
 -static int reopen_stdout(struct commit *commit, struct rev_info *rev)
 +static int reopen_stdout(struct commit *commit, struct rev_info *rev, int quiet)
  {
        struct strbuf filename = STRBUF_INIT;
        int suffix_len = strlen(fmt_patch_suffix) + 1;
                strbuf_addstr(&filename, output_directory);
                if (filename.len >=
                    PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
 -                      return error("name of output directory is too long");
 +                      return error(_("name of output directory is too long"));
                if (filename.buf[filename.len - 1] != '/')
                        strbuf_addch(&filename, '/');
        }
  
        get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
  
 -      if (!DIFF_OPT_TST(&rev->diffopt, QUICK))
 +      if (!quiet)
                fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
  
        if (freopen(filename.buf, "w", stdout) == NULL)
 -              return error("Cannot open patch file %s", filename.buf);
 +              return error(_("Cannot open patch file %s"), filename.buf);
  
        strbuf_release(&filename);
        return 0;
@@@ -661,7 -666,7 +670,7 @@@ static void get_patch_ids(struct rev_in
        unsigned flags1, flags2;
  
        if (rev->pending.nr != 2)
 -              die("Need exactly one range.");
 +              die(_("Need exactly one range."));
  
        o1 = rev->pending.objects[0].item;
        flags1 = o1->flags;
        flags2 = o2->flags;
  
        if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
 -              die("Not a range.");
 +              die(_("Not a range."));
  
        init_patch_ids(ids);
  
        add_pending_object(&check_rev, o1, "o1");
        add_pending_object(&check_rev, o2, "o2");
        if (prepare_revision_walk(&check_rev))
 -              die("revision walk setup failed");
 +              die(_("revision walk setup failed"));
  
        while ((commit = get_revision(&check_rev)) != NULL) {
                /* ignore merges */
@@@ -706,7 -711,7 +715,7 @@@ static void gen_message_id(struct rev_i
        const char *email_end = strrchr(committer, '>');
        struct strbuf buf = STRBUF_INIT;
        if (!email_start || !email_end || email_start > email_end - 1)
 -              die("Could not extract email from committer identity.");
 +              die(_("Could not extract email from committer identity."));
        strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
                    (unsigned long) time(NULL),
                    (int)(email_end - email_start - 1), email_start + 1);
@@@ -722,8 -727,7 +731,8 @@@ static void print_signature(void
  static void make_cover_letter(struct rev_info *rev, int use_stdout,
                              int numbered, int numbered_files,
                              struct commit *origin,
 -                            int nr, struct commit **list, struct commit *head)
 +                            int nr, struct commit **list, struct commit *head,
 +                            int quiet)
  {
        const char *committer;
        const char *subject_start = NULL;
        struct commit *commit = NULL;
  
        if (rev->commit_format != CMIT_FMT_EMAIL)
 -              die("Cover letter needs email format");
 +              die(_("Cover letter needs email format"));
  
        committer = git_committer_info(0);
  
                        sha1_to_hex(head->object.sha1), committer, committer);
        }
  
 -      if (!use_stdout && reopen_stdout(commit, rev))
 +      if (!use_stdout && reopen_stdout(commit, rev, quiet))
                return;
  
        if (commit) {
@@@ -832,7 -836,7 +841,7 @@@ static const char *clean_message_id(con
                m++;
        }
        if (!z)
 -              die("insane in-reply-to: %s", msg_id);
 +              die(_("insane in-reply-to: %s"), msg_id);
        if (++z == m)
                return a;
        return xmemdupz(a, z - a);
@@@ -905,7 -909,7 +914,7 @@@ static int output_directory_callback(co
  {
        const char **dir = (const char **)opt->value;
        if (*dir)
 -              die("Two output directories?");
 +              die(_("Two output directories?"));
        *dir = arg;
        return 0;
  }
@@@ -1000,7 -1004,6 +1009,7 @@@ int cmd_format_patch(int argc, const ch
        char *add_signoff = NULL;
        struct strbuf buf = STRBUF_INIT;
        int use_patch_format = 0;
 +      int quiet = 0;
        const struct option builtin_format_patch_options[] = {
                { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
                            "use [PATCH n/m] even with a single patch",
                            PARSE_OPT_OPTARG, thread_callback },
                OPT_STRING(0, "signature", &signature, "signature",
                            "add a signature"),
 +              OPT_BOOLEAN(0, "quiet", &quiet,
 +                          "don't print the patch filenames"),
                OPT_END()
        };
  
        rev.commit_format = CMIT_FMT_EMAIL;
        rev.verbose_header = 1;
        rev.diff = 1;
 -      rev.no_merges = 1;
 +      rev.max_parents = 1;
        DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
        rev.subject_prefix = fmt_patch_subject_prefix;
        memset(&s_r_opt, 0, sizeof(s_r_opt));
                committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
                endpos = strchr(committer, '>');
                if (!endpos)
 -                      die("bogus committer info %s", committer);
 +                      die(_("bogus committer info %s"), committer);
                add_signoff = xmemdupz(committer, endpos - committer + 1);
        }
  
                numbered = 0;
  
        if (numbered && keep_subject)
 -              die ("-n and -k are mutually exclusive.");
 +              die (_("-n and -k are mutually exclusive."));
        if (keep_subject && subject_prefix)
 -              die ("--subject-prefix and -k are mutually exclusive.");
 +              die (_("--subject-prefix and -k are mutually exclusive."));
  
        argc = setup_revisions(argc, argv, &rev, &s_r_opt);
        if (argc > 1)
 -              die ("unrecognized argument: %s", argv[1]);
 +              die (_("unrecognized argument: %s"), argv[1]);
  
        if (rev.diffopt.output_format & DIFF_FORMAT_NAME)
 -              die("--name-only does not make sense");
 +              die(_("--name-only does not make sense"));
        if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS)
 -              die("--name-status does not make sense");
 +              die(_("--name-status does not make sense"));
        if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
 -              die("--check does not make sense");
 +              die(_("--check does not make sense"));
  
        if (!use_patch_format &&
                (!rev.diffopt.output_format ||
  
        if (output_directory) {
                if (use_stdout)
 -                      die("standard output, or directory, which one?");
 +                      die(_("standard output, or directory, which one?"));
                if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
 -                      die_errno("Could not create directory '%s'",
 +                      die_errno(_("Could not create directory '%s'"),
                                  output_directory);
        }
  
                realstdout = xfdopen(xdup(1), "w");
  
        if (prepare_revision_walk(&rev))
 -              die("revision walk setup failed");
 +              die(_("revision walk setup failed"));
        rev.boundary = 1;
        while ((commit = get_revision(&rev)) != NULL) {
                if (commit->object.flags & BOUNDARY) {
                if (thread)
                        gen_message_id(&rev, "cover");
                make_cover_letter(&rev, use_stdout, numbered, numbered_files,
 -                                origin, nr, list, head);
 +                                origin, nr, list, head, quiet);
                total++;
                start_number--;
        }
                }
  
                if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
 -                                               &rev))
 -                      die("Failed to create output files");
 +                                               &rev, quiet))
 +                      die(_("Failed to create output files"));
                shown = log_tree_commit(&rev, commit);
                free(commit->buffer);
                commit->buffer = NULL;
@@@ -1418,9 -1419,9 +1427,9 @@@ int cmd_cherry(int argc, const char **a
                if (!current_branch || !current_branch->merge
                                        || !current_branch->merge[0]
                                        || !current_branch->merge[0]->dst) {
 -                      fprintf(stderr, "Could not find a tracked"
 +                      fprintf(stderr, _("Could not find a tracked"
                                        " remote branch, please"
 -                                      " specify <upstream> manually.\n");
 +                                      " specify <upstream> manually.\n"));
                        usage_with_options(cherry_usage, options);
                }
  
        DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
  
        if (add_pending_commit(head, &revs, 0))
 -              die("Unknown commit %s", head);
 +              die(_("Unknown commit %s"), head);
        if (add_pending_commit(upstream, &revs, UNINTERESTING))
 -              die("Unknown commit %s", upstream);
 +              die(_("Unknown commit %s"), upstream);
  
        /* Don't say anything if head and upstream are the same. */
        if (revs.pending.nr == 2) {
        get_patch_ids(&revs, &ids, prefix);
  
        if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
 -              die("Unknown commit %s", limit);
 +              die(_("Unknown commit %s"), limit);
  
        /* reverse the list of commits */
        if (prepare_revision_walk(&revs))
 -              die("revision walk setup failed");
 +              die(_("revision walk setup failed"));
        while ((commit = get_revision(&revs)) != NULL) {
                /* ignore merges */
                if (commit->parents && commit->parents->next)
diff --combined diff.c
index 9a8012e362fe2bc08d4ecce21e68972579c3693b,13f322bc5f8d12f3a11f62db3744f0648ba8ace6..559bf574a88f97402f32e1bd4ab51f6f0c9d5026
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -1050,16 -1050,8 +1050,16 @@@ static void fn_out_consume(void *priv, 
                        emit_line(ecbdata->opt, plain, reset, line, len);
                        fputs("~\n", ecbdata->opt->file);
                } else {
 -                      /* don't print the prefix character */
 -                      emit_line(ecbdata->opt, plain, reset, line+1, len-1);
 +                      /*
 +                       * Skip the prefix character, if any.  With
 +                       * diff_suppress_blank_empty, there may be
 +                       * none.
 +                       */
 +                      if (line[0] != '\n') {
 +                            line++;
 +                            len--;
 +                      }
 +                      emit_line(ecbdata->opt, plain, reset, line, len);
                }
                return;
        }
@@@ -1250,7 -1242,7 +1250,7 @@@ static void show_stats(struct diffstat_
        uintmax_t max_change = 0, max_len = 0;
        int total_files = data->nr;
        int width, name_width;
 -      const char *reset, *set, *add_c, *del_c;
 +      const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
        struct strbuf *msg = NULL;
  
  
        /* Find the longest filename and max number of changes */
        reset = diff_get_color_opt(options, DIFF_RESET);
 -      set   = diff_get_color_opt(options, DIFF_PLAIN);
        add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
        del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
  
@@@ -1546,36 -1539,8 +1546,36 @@@ static void show_dirstat(struct diff_op
                struct diff_filepair *p = q->queue[i];
                const char *name;
                unsigned long copied, added, damage;
 +              int content_changed;
  
 -              name = p->one->path ? p->one->path : p->two->path;
 +              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);
 +              else
 +                      content_changed = 1;
 +
 +              if (!content_changed) {
 +                      /*
 +                       * The SHA1 has not changed, so pre-/post-content is
 +                       * identical. We can therefore skip looking at the
 +                       * file contents altogether.
 +                       */
 +                      damage = 0;
 +                      goto found_damage;
 +              }
 +
 +              if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE)) {
 +                      /*
 +                       * In --dirstat-by-file mode, we don't really need to
 +                       * look at the actual file contents at all.
 +                       * The fact that the SHA1 changed is enough for us to
 +                       * add this file to the list of results
 +                       * (with each file contributing equal damage).
 +                       */
 +                      damage = 1;
 +                      goto found_damage;
 +              }
  
                if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
                        diff_populate_filespec(p->one, 0);
                /*
                 * Original minus copied is the removed material,
                 * added is the new material.  They are both damages
 -               * made to the preimage. In --dirstat-by-file mode, count
 -               * damaged files, not damaged lines. This is done by
 -               * counting only a single damaged line per file.
 +               * made to the preimage.
 +               * If the resulting damage is zero, we know that
 +               * diffcore_count_changes() considers the two entries to
 +               * be identical, but since content_changed is true, we
 +               * know that there must have been _some_ kind of change,
 +               * so we force all entries to have damage > 0.
                 */
                damage = (p->one->size - copied) + added;
 -              if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE) && damage > 0)
 +              if (!damage)
                        damage = 1;
  
 +found_damage:
                ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
                dir.files[dir.nr].name = name;
                dir.files[dir.nr].changed = damage;
@@@ -3995,6 -3956,28 +3995,28 @@@ static int is_summary_empty(const struc
        return 1;
  }
  
+ static const char rename_limit_warning[] =
+ "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.";
+ static const char rename_limit_advice[] =
+ "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);
+       else if (needed)
+               warning(rename_limit_warning);
+       else
+               return;
+       if (0 < needed && needed < 32767)
+               warning(rename_limit_advice, varname, needed);
+ }
  void diff_flush(struct diff_options *options)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
@@@ -4276,6 -4259,10 +4298,10 @@@ void diffcore_std(struct diff_options *
  int diff_result_code(struct diff_options *opt, int status)
  {
        int result = 0;
+       diff_warn_rename_limit("diff.renamelimit",
+                              opt->needed_rename_limit,
+                              opt->degraded_cc_to_c);
        if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
            !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
                return status;
@@@ -4389,20 -4376,20 +4415,20 @@@ void diff_change(struct diff_options *o
                DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
 -void diff_unmerge(struct diff_options *options,
 -                const char *path,
 -                unsigned mode, const unsigned char *sha1)
 +struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
  {
 +      struct diff_filepair *pair;
        struct diff_filespec *one, *two;
  
        if (options->prefix &&
            strncmp(path, options->prefix, options->prefix_length))
 -              return;
 +              return NULL;
  
        one = alloc_filespec(path);
        two = alloc_filespec(path);
 -      fill_filespec(one, sha1, mode);
 -      diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1;
 +      pair = diff_queue(&diff_queued_diff, one, two);
 +      pair->is_unmerged = 1;
 +      return pair;
  }
  
  static char *run_textconv(const char *pgm, struct diff_filespec *spec,
diff --combined diff.h
index 42b49d8f228fd2cc680ab9a2e332819d46f2f875,b8dde8a750b17730f2e65da395700e5bd3ee2210..6e4d436e0ca21f2243c1b8a98319c878db3c1909
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -111,6 -111,7 +111,7 @@@ struct diff_options 
        int rename_score;
        int rename_limit;
        int needed_rename_limit;
+       int degraded_cc_to_c;
        int show_rename_progress;
        int dirstat_percent;
        int setup;
@@@ -208,7 -209,10 +209,7 @@@ extern void diff_change(struct diff_opt
                        const char *fullpath,
                        unsigned dirty_submodule1, unsigned dirty_submodule2);
  
 -extern void diff_unmerge(struct diff_options *,
 -                       const char *path,
 -                       unsigned mode,
 -                       const unsigned char *sha1);
 +extern struct diff_filepair *diff_unmerge(struct diff_options *, const char *path);
  
  #define DIFF_SETUP_REVERSE            1
  #define DIFF_SETUP_USE_CACHE          2
@@@ -270,6 -274,7 +271,7 @@@ extern void diffcore_fix_diff_index(str
  
  extern int diff_queue_is_empty(void);
  extern void diff_flush(struct diff_options*);
+ extern void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
  
  /* diff-raw status letters */
  #define DIFF_STATUS_ADDED             'A'
diff --combined diffcore-rename.c
index 0b9e99a04ee62d0d14900888f5b8b9ab3d957b5f,f62587e523e1b38c2fd440780d2635677e734c4d..d0259be412e8e262fe8661bd964145f5957315de
@@@ -55,22 -55,23 +55,23 @@@ static struct diff_rename_dst *locate_r
  
  /* Table of rename/copy src files */
  static struct diff_rename_src {
-       struct diff_filespec *one;
+       struct diff_filepair *p;
        unsigned short score; /* to remember the break score */
  } *rename_src;
  static int rename_src_nr, rename_src_alloc;
  
- static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
-                                                  unsigned short score)
+ static struct diff_rename_src *register_rename_src(struct diff_filepair *p)
  {
        int first, last;
+       struct diff_filespec *one = p->one;
+       unsigned short score = p->score;
  
        first = 0;
        last = rename_src_nr;
        while (last > first) {
                int next = (last + first) >> 1;
                struct diff_rename_src *src = &(rename_src[next]);
-               int cmp = strcmp(one->path, src->one->path);
+               int cmp = strcmp(one->path, src->p->one->path);
                if (!cmp)
                        return src;
                if (cmp < 0) {
@@@ -90,7 -91,7 +91,7 @@@
        if (first < rename_src_nr)
                memmove(rename_src + first + 1, rename_src + first,
                        (rename_src_nr - first - 1) * sizeof(*rename_src));
-       rename_src[first].one = one;
+       rename_src[first].p = p;
        rename_src[first].score = score;
        return &(rename_src[first]);
  }
@@@ -205,7 -206,7 +206,7 @@@ static void record_rename_pair(int dst_
        if (rename_dst[dst_index].pair)
                die("internal error: dst already matched.");
  
-       src = rename_src[src_index].one;
+       src = rename_src[src_index].p->one;
        src->rename_used++;
        src->count++;
  
@@@ -389,7 -390,7 +390,7 @@@ static int find_exact_renames(struct di
  
        init_hash(&file_table);
        for (i = 0; i < rename_src_nr; i++)
-               insert_file_table(&file_table, -1, i, rename_src[i].one);
+               insert_file_table(&file_table, -1, i, rename_src[i].p->one);
  
        for (i = 0; i < rename_dst_nr; i++)
                insert_file_table(&file_table, 1, i, rename_dst[i].two);
@@@ -419,6 -420,55 +420,55 @@@ static void record_if_better(struct dif
                m[worst] = *o;
  }
  
+ /*
+  * Returns:
+  * 0 if we are under the limit;
+  * 1 if we need to disable inexact rename detection;
+  * 2 if we would be under the limit if we were given -C instead of -C -C.
+  */
+ static int too_many_rename_candidates(int num_create,
+                                     struct diff_options *options)
+ {
+       int rename_limit = options->rename_limit;
+       int num_src = rename_src_nr;
+       int i;
+       options->needed_rename_limit = 0;
+       /*
+        * This basically does a test for the rename matrix not
+        * growing larger than a "rename_limit" square matrix, ie:
+        *
+        *    num_create * num_src > rename_limit * rename_limit
+        *
+        * but handles the potential overflow case specially (and we
+        * assume at least 32-bit integers)
+        */
+       if (rename_limit <= 0 || rename_limit > 32767)
+               rename_limit = 32767;
+       if ((num_create <= rename_limit || num_src <= rename_limit) &&
+           (num_create * num_src <= rename_limit * rename_limit))
+               return 0;
+       options->needed_rename_limit =
+               num_src > num_create ? num_src : num_create;
+       /* Are we running under -C -C? */
+       if (!DIFF_OPT_TST(options, FIND_COPIES_HARDER))
+               return 1;
+       /* Would we bust the limit if we were running under -C? */
+       for (num_src = i = 0; i < rename_src_nr; i++) {
+               if (diff_unmodified_pair(rename_src[i].p))
+                       continue;
+               num_src++;
+       }
+       if ((num_create <= rename_limit || num_src <= rename_limit) &&
+           (num_create * num_src <= rename_limit * rename_limit))
+               return 2;
+       return 1;
+ }
  static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, int copies)
  {
        int count = 0, i;
                dst = &rename_dst[mx[i].dst];
                if (dst->pair)
                        continue; /* already done, either exact or fuzzy. */
-               if (!copies && rename_src[mx[i].src].one->rename_used)
+               if (!copies && rename_src[mx[i].src].p->one->rename_used)
                        continue;
                record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
                count++;
@@@ -444,11 -494,10 +494,10 @@@ void diffcore_rename(struct diff_option
  {
        int detect_rename = options->detect_rename;
        int minimum_score = options->rename_score;
-       int rename_limit = options->rename_limit;
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
        struct diff_score *mx;
-       int i, j, rename_count;
+       int i, j, rename_count, skip_unmodified = 0;
        int num_create, num_src, dst_cnt;
        struct progress *progress = NULL;
  
                        else
                                locate_rename_dst(p->two, 1);
                }
 -              else if (!DIFF_FILE_VALID(p->two)) {
 +              else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) {
                        /*
                         * If the source is a broken "delete", and
                         * they did not really want to get broken,
                         */
                        if (p->broken_pair && !p->score)
                                p->one->rename_used++;
-                       register_rename_src(p->one, p->score);
+                       register_rename_src(p);
                }
                else if (detect_rename == DIFF_DETECT_COPY) {
                        /*
                         * one, to indicate ourselves as a user.
                         */
                        p->one->rename_used++;
-                       register_rename_src(p->one, p->score);
+                       register_rename_src(p);
                }
        }
        if (rename_dst_nr == 0 || rename_src_nr == 0)
        if (!num_create)
                goto cleanup;
  
-       /*
-        * This basically does a test for the rename matrix not
-        * growing larger than a "rename_limit" square matrix, ie:
-        *
-        *    num_create * num_src > rename_limit * rename_limit
-        *
-        * but handles the potential overflow case specially (and we
-        * assume at least 32-bit integers)
-        */
-       options->needed_rename_limit = 0;
-       if (rename_limit <= 0 || rename_limit > 32767)
-               rename_limit = 32767;
-       if ((num_create > rename_limit && num_src > rename_limit) ||
-           (num_create * num_src > rename_limit * rename_limit)) {
-               options->needed_rename_limit =
-                       num_src > num_create ? num_src : num_create;
+       switch (too_many_rename_candidates(num_create, options)) {
+       case 1:
                goto cleanup;
+       case 2:
+               options->degraded_cc_to_c = 1;
+               skip_unmodified = 1;
+               break;
+       default:
+               break;
        }
  
        if (options->show_rename_progress) {
                        m[j].dst = -1;
  
                for (j = 0; j < rename_src_nr; j++) {
-                       struct diff_filespec *one = rename_src[j].one;
+                       struct diff_filespec *one = rename_src[j].p->one;
                        struct diff_score this_src;
+                       if (skip_unmodified &&
+                           diff_unmodified_pair(rename_src[j].p))
+                               continue;
                        this_src.score = estimate_similarity(one, two,
                                                             minimum_score);
                        this_src.name_score = basename_same(one, two);
                struct diff_filepair *p = q->queue[i];
                struct diff_filepair *pair_to_free = NULL;
  
 -              if (!DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
 +              if (DIFF_PAIR_UNMERGED(p)) {
 +                      diff_q(&outq, p);
 +              }
 +              else if (!DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
                        /*
                         * Creation
                         *
diff --combined merge-recursive.c
index dba27643bd30768270d5c33753d6b26dea65face,3ae4d53cc552fb837cb50edddbcc993c460aa941..ae6ade4ecbbcc77bc952d223f0ae90ae2c77a0c7
  #include "dir.h"
  #include "submodule.h"
  
- static const char rename_limit_advice[] =
- "inexact rename detection was skipped because there were too many\n"
- "  files. You may want to set your merge.renamelimit variable to at least\n"
- "  %d and retry this merge.";
  static struct tree *shift_tree_object(struct tree *one, struct tree *two,
                                      const char *subtree_shift)
  {
@@@ -356,6 -351,7 +351,6 @@@ static void make_room_for_directories_o
         */
        const char *last_file = NULL;
        int last_len = 0;
 -      struct stage_data *last_e;
        int i;
  
        for (i = 0; i < entries->nr; i++) {
                if (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) {
                        last_file = path;
                        last_len = len;
 -                      last_e = e;
                } else {
                        last_file = NULL;
                }
@@@ -959,6 -956,7 +954,6 @@@ static int process_renames(struct merge
        }
  
        for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
 -              char *src;
                struct string_list *renames1, *renames2Dst;
                struct rename *ren1 = NULL, *ren2 = NULL;
                const char *branch1, *branch2;
                        ren2 = ren1;
                        ren1 = tmp;
                }
 -              src = ren1->pair->one->path;
  
                ren1->dst_entry->processed = 1;
                ren1->src_entry->processed = 1;
@@@ -1652,8 -1651,9 +1647,9 @@@ int merge_recursive(struct merge_option
                commit_list_insert(h2, &(*result)->parents->next);
        }
        flush_output(o);
-       if (o->needed_rename_limit)
-               warning(rename_limit_advice, o->needed_rename_limit);
+       if (show(o, 2))
+               diff_warn_rename_limit("merge.renamelimit",
+                                      o->needed_rename_limit, 0);
        return clean;
  }
  
@@@ -1711,15 -1711,15 +1707,15 @@@ int merge_recursive_generic(struct merg
  static int merge_recursive_config(const char *var, const char *value, void *cb)
  {
        struct merge_options *o = cb;
 -      if (!strcasecmp(var, "merge.verbosity")) {
 +      if (!strcmp(var, "merge.verbosity")) {
                o->verbosity = git_config_int(var, value);
                return 0;
        }
 -      if (!strcasecmp(var, "diff.renamelimit")) {
 +      if (!strcmp(var, "diff.renamelimit")) {
                o->diff_rename_limit = git_config_int(var, value);
                return 0;
        }
 -      if (!strcasecmp(var, "merge.renamelimit")) {
 +      if (!strcmp(var, "merge.renamelimit")) {
                o->merge_rename_limit = git_config_int(var, value);
                return 0;
        }
diff --combined t/t4001-diff-rename.sh
index cad85450b78891a98a0b60af1422c62c7288265c,301f3a0e545be02b071fcf43ea690a1a6d6e044a..3dadf9b31636b862b2ab7377ce7100ebebd397e2
@@@ -64,7 -64,7 +64,7 @@@ test_expect_success 
      'validate the output.' \
      'compare_diff_patch current expected'
  
 -test_expect_success 'favour same basenames over different ones' '
 +test_expect_success C_LOCALE_OUTPUT 'favour same basenames over different ones' '
        cp path1 another-path &&
        git add another-path &&
        git commit -m 1 &&
        git mv another-path subdir/path1 &&
        git status | grep "renamed: .*path1 -> subdir/path1"'
  
 -test_expect_success  'favour same basenames even with minor differences' '
 +test_expect_success C_LOCALE_OUTPUT  'favour same basenames even with minor differences' '
        git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
        git status | grep "renamed: .*path1 -> subdir/path1"'
  
+ test_expect_success 'setup for many rename source candidates' '
+       git reset --hard &&
+       for i in 0 1 2 3 4 5 6 7 8 9;
+       do
+               for j in 0 1 2 3 4 5 6 7 8 9;
+               do
+                       echo "$i$j" >"path$i$j"
+               done
+       done &&
+       git add "path??" &&
+       test_tick &&
+       git commit -m "hundred" &&
+       (cat path1; echo new) >new-path &&
+       echo old >>path1 &&
+       git add new-path path1 &&
+       git diff -l 4 -C -C --cached --name-status >actual 2>actual.err &&
+       sed -e "s/^\([CM]\)[0-9]*       /\1     /" actual >actual.munged &&
+       cat >expect <<-EOF &&
+       C       path1   new-path
+       M       path1
+       EOF
+       test_cmp expect actual.munged &&
+       grep warning actual.err
+ '
  test_done