Merge branch 'jk/merge-rename-ux'
authorJunio C Hamano <gitster@pobox.com>
Sun, 20 Mar 2011 06:23:56 +0000 (23:23 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 20 Mar 2011 06:23:56 +0000 (23:23 -0700)
* jk/merge-rename-ux:
pull: propagate --progress to merge
merge: enable progress reporting for rename detection
add inexact rename detection progress infrastructure
commit: stop setting rename limit
bump rename limit defaults (again)
merge: improve inexact rename limit warning

1  2 
builtin/commit.c
builtin/merge.c
diff.c
diff.h
diffcore-rename.c
git-pull.sh
merge-recursive.c
merge-recursive.h
diff --combined builtin/commit.c
index 82092e5c82cd48c7e3bfc97acadc7f7173b756bf,9f6b3cb82a33bd7aad2776d7eb25b276f7897507..de0e11137823fc514a6868c5679c56b3f91b0833
@@@ -54,17 -54,9 +54,17 @@@ static const char empty_amend_advice[] 
  "it empty. You can repeat your command with --allow-empty, or you can\n"
  "remove the commit entirely with \"git reset HEAD^\".\n";
  
 +static const char empty_cherry_pick_advice[] =
 +"The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
 +"If you wish to commit it anyway, use:\n"
 +"\n"
 +"    git commit --allow-empty\n"
 +"\n"
 +"Otherwise, please use 'git reset'\n";
 +
  static unsigned char head_sha1[20];
  
 -static char *use_message_buffer;
 +static const char *use_message_buffer;
  static const char commit_editmsg[] = "COMMIT_EDITMSG";
  static struct lock_file index_lock; /* real index */
  static struct lock_file false_lock; /* used only for partial commits */
@@@ -76,11 -68,6 +76,11 @@@ static enum 
  
  static const char *logfile, *force_author;
  static const char *template_file;
 +/*
 + * The _message variables are commit names from which to take
 + * the commit message and/or authorship.
 + */
 +static const char *author_message, *author_message_buffer;
  static char *edit_message, *use_message;
  static char *fixup_message, *squash_message;
  static int all, edit_flag, also, interactive, only, amend, signoff;
@@@ -101,8 -88,7 +101,8 @@@ static enum 
  } cleanup_mode;
  static char *cleanup_arg;
  
 -static int use_editor = 1, initial_commit, in_merge, include_status = 1;
 +static enum commit_whence whence;
 +static int use_editor = 1, initial_commit, include_status = 1;
  static int show_ignored_in_status;
  static const char *only_include_assumed;
  static struct strbuf message;
@@@ -132,14 -118,14 +132,14 @@@ static struct option builtin_commit_opt
        OPT__VERBOSE(&verbose, "show diff in commit message template"),
  
        OPT_GROUP("Commit message options"),
 -      OPT_FILENAME('F', "file", &logfile, "read log from file"),
 -      OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
 -      OPT_STRING(0, "date", &force_date, "DATE", "override date for commit"),
 -      OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
 -      OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
 -      OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
 -      OPT_STRING(0, "fixup", &fixup_message, "COMMIT", "use autosquash formatted message to fixup specified commit"),
 -      OPT_STRING(0, "squash", &squash_message, "COMMIT", "use autosquash formatted message to squash specified commit"),
 +      OPT_FILENAME('F', "file", &logfile, "read message from file"),
 +      OPT_STRING(0, "author", &force_author, "author", "override author for commit"),
 +      OPT_STRING(0, "date", &force_date, "date", "override date for commit"),
 +      OPT_CALLBACK('m', "message", &message, "message", "commit message", opt_parse_m),
 +      OPT_STRING('c', "reedit-message", &edit_message, "commit", "reuse and edit message from specified commit"),
 +      OPT_STRING('C', "reuse-message", &use_message, "commit", "reuse message from specified commit"),
 +      OPT_STRING(0, "fixup", &fixup_message, "commit", "use autosquash formatted message to fixup specified commit"),
 +      OPT_STRING(0, "squash", &squash_message, "commit", "use autosquash formatted message to squash specified commit"),
        OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
                    STATUS_FORMAT_SHORT),
        OPT_BOOLEAN(0, "branch", &status_show_branch, "show branch information"),
        OPT_SET_INT(0, "porcelain", &status_format,
 -                  "show porcelain output format", STATUS_FORMAT_PORCELAIN),
 +                  "machine-readable output", STATUS_FORMAT_PORCELAIN),
        OPT_BOOLEAN('z', "null", &null_termination,
                    "terminate entries with NUL"),
        OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
        OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
 -      { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 +      { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        /* end commit contents options */
  
        { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
        OPT_END()
  };
  
 +static void determine_whence(struct wt_status *s)
 +{
 +      if (file_exists(git_path("MERGE_HEAD")))
 +              whence = FROM_MERGE;
 +      else if (file_exists(git_path("CHERRY_PICK_HEAD")))
 +              whence = FROM_CHERRY_PICK;
 +      else
 +              whence = FROM_COMMIT;
 +      if (s)
 +              s->whence = whence;
 +}
 +
 +static const char *whence_s(void)
 +{
 +      char *s = "";
 +
 +      switch (whence) {
 +      case FROM_COMMIT:
 +              break;
 +      case FROM_MERGE:
 +              s = "merge";
 +              break;
 +      case FROM_CHERRY_PICK:
 +              s = "cherry-pick";
 +              break;
 +      }
 +
 +      return s;
 +}
 +
  static void rollback_index_files(void)
  {
        switch (commit_style) {
@@@ -422,8 -378,8 +422,8 @@@ static char *prepare_index(int argc, co
         */
        commit_style = COMMIT_PARTIAL;
  
 -      if (in_merge)
 -              die("cannot do a partial commit during a merge.");
 +      if (whence != FROM_COMMIT)
 +              die("cannot do a partial commit during a %s.", whence_s());
  
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
@@@ -513,18 -469,18 +513,18 @@@ static void determine_author_info(struc
        email = getenv("GIT_AUTHOR_EMAIL");
        date = getenv("GIT_AUTHOR_DATE");
  
 -      if (use_message && !renew_authorship) {
 +      if (author_message) {
                const char *a, *lb, *rb, *eol;
  
 -              a = strstr(use_message_buffer, "\nauthor ");
 +              a = strstr(author_message_buffer, "\nauthor ");
                if (!a)
 -                      die("invalid commit: %s", use_message);
 +                      die("invalid commit: %s", author_message);
  
                lb = strchrnul(a + strlen("\nauthor "), '<');
                rb = strchrnul(lb, '>');
                eol = strchrnul(rb, '\n');
                if (!*lb || !*rb || !*eol)
 -                      die("invalid commit: %s", use_message);
 +                      die("invalid commit: %s", author_message);
  
                if (lb == a + strlen("\nauthor "))
                        /* \nauthor <foo@example.com> */
@@@ -678,22 -634,18 +678,22 @@@ static int prepare_to_commit(const cha
                if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
                        die_errno("could not read SQUASH_MSG");
                hook_arg1 = "squash";
 -      } else if (template_file && !stat(template_file, &statbuf)) {
 +      } else if (template_file) {
                if (strbuf_read_file(&sb, template_file, 0) < 0)
                        die_errno("could not read '%s'", template_file);
                hook_arg1 = "template";
        }
  
        /*
 -       * This final case does not modify the template message,
 -       * it just sets the argument to the prepare-commit-msg hook.
 +       * The remaining cases don't modify the template message, but
 +       * just set the argument(s) to the prepare-commit-msg hook.
         */
 -      else if (in_merge)
 +      else if (whence == FROM_MERGE)
                hook_arg1 = "merge";
 +      else if (whence == FROM_CHERRY_PICK) {
 +              hook_arg1 = "commit";
 +              hook_arg2 = "CHERRY_PICK_HEAD";
 +      }
  
        if (squash_message) {
                /*
        strbuf_addstr(&committer_ident, git_committer_info(0));
        if (use_editor && include_status) {
                char *ai_tmp, *ci_tmp;
 -              if (in_merge)
 +              if (whence != FROM_COMMIT)
                        fprintf(fp,
                                "#\n"
 -                              "# It looks like you may be committing a MERGE.\n"
 +                              "# It looks like you may be committing a %s.\n"
                                "# If this is not correct, please remove the file\n"
                                "#      %s\n"
                                "# and try again.\n"
                                "#\n",
 -                              git_path("MERGE_HEAD"));
 -
 +                              whence_s(),
 +                              git_path(whence == FROM_MERGE
 +                                       ? "MERGE_HEAD"
 +                                       : "CHERRY_PICK_HEAD"));
                fprintf(fp,
                        "\n"
                        "# Please enter the commit message for your changes.");
  
        fclose(fp);
  
 -      if (!commitable && !in_merge && !allow_empty &&
 +      /*
 +       * Reject an attempt to record a non-merge empty commit without
 +       * explicit --allow-empty. In the cherry-pick case, it may be
 +       * empty due to conflict resolution, which the user should okay.
 +       */
 +      if (!commitable && whence != FROM_MERGE && !allow_empty &&
            !(amend && is_a_merge(head_sha1))) {
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(empty_amend_advice, stderr);
 +              else if (whence == FROM_CHERRY_PICK)
 +                      fputs(empty_cherry_pick_advice, stderr);
                return 0;
        }
  
@@@ -955,28 -898,6 +955,28 @@@ static void handle_untracked_files_arg(
                die("Invalid untracked files mode '%s'", untracked_files_arg);
  }
  
 +static const char *read_commit_message(const char *name)
 +{
 +      const char *out_enc, *out;
 +      struct commit *commit;
 +
 +      commit = lookup_commit_reference_by_name(name);
 +      if (!commit)
 +              die("could not lookup commit %s", name);
 +      out_enc = get_commit_output_encoding();
 +      out = logmsg_reencode(commit, out_enc);
 +
 +      /*
 +       * If we failed to reencode the buffer, just copy it
 +       * byte for byte so the user can try to fix it up.
 +       * This also handles the case where input and output
 +       * encodings are identical.
 +       */
 +      if (out == NULL)
 +              out = xstrdup(commit->buffer);
 +      return out;
 +}
 +
  static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix,
        /* Sanity check options */
        if (amend && initial_commit)
                die("You have nothing to amend.");
 -      if (amend && in_merge)
 -              die("You are in the middle of a merge -- cannot amend.");
 +      if (amend && whence != FROM_COMMIT)
 +              die("You are in the middle of a %s -- cannot amend.", whence_s());
        if (fixup_message && squash_message)
                die("Options --squash and --fixup cannot be used together");
        if (use_message)
                use_message = edit_message;
        if (amend && !use_message && !fixup_message)
                use_message = "HEAD";
 -      if (!use_message && renew_authorship)
 +      if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship)
                die("--reset-author can be used only with -C, -c or --amend.");
        if (use_message) {
 -              const char *out_enc;
 -              struct commit *commit;
 -
 -              commit = lookup_commit_reference_by_name(use_message);
 -              if (!commit)
 -                      die("could not lookup commit %s", use_message);
 -              out_enc = get_commit_output_encoding();
 -              use_message_buffer = logmsg_reencode(commit, out_enc);
 -
 -              /*
 -               * If we failed to reencode the buffer, just copy it
 -               * byte for byte so the user can try to fix it up.
 -               * This also handles the case where input and output
 -               * encodings are identical.
 -               */
 -              if (use_message_buffer == NULL)
 -                      use_message_buffer = xstrdup(commit->buffer);
 +              use_message_buffer = read_commit_message(use_message);
 +              if (!renew_authorship) {
 +                      author_message = use_message;
 +                      author_message_buffer = use_message_buffer;
 +              }
 +      }
 +      if (whence == FROM_CHERRY_PICK && !renew_authorship) {
 +              author_message = "CHERRY_PICK_HEAD";
 +              author_message_buffer = read_commit_message(author_message);
        }
  
        if (!!also + !!only + !!all + !!interactive > 1)
@@@ -1163,7 -1092,7 +1163,7 @@@ int cmd_status(int argc, const char **a
                OPT_BOOLEAN('b', "branch", &status_show_branch,
                            "show branch information"),
                OPT_SET_INT(0, "porcelain", &status_format,
 -                          "show porcelain output format",
 +                          "machine-readable output",
                            STATUS_FORMAT_PORCELAIN),
                OPT_BOOLEAN('z', "null", &null_termination,
                            "terminate entries with NUL"),
        wt_status_prepare(&s);
        gitmodules_config();
        git_config(git_status_config, &s);
 -      in_merge = file_exists(git_path("MERGE_HEAD"));
 +      determine_whence(&s);
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
        }
  
        s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
 -      s.in_merge = in_merge;
        s.ignore_submodule_arg = ignore_submodule_arg;
        wt_status_collect(&s);
  
@@@ -1285,7 -1215,6 +1285,6 @@@ static void print_summary(const char *p
        get_commit_format(format.buf, &rev);
        rev.always_show_header = 0;
        rev.diffopt.detect_rename = 1;
-       rev.diffopt.rename_limit = 100;
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
  
@@@ -1372,7 -1301,8 +1371,7 @@@ int cmd_commit(int argc, const char **a
  
        wt_status_prepare(&s);
        git_config(git_commit_config, &s);
 -      in_merge = file_exists(git_path("MERGE_HEAD"));
 -      s.in_merge = in_merge;
 +      determine_whence(&s);
  
        if (s.use_color == -1)
                s.use_color = git_use_color_default;
  
                for (c = commit->parents; c; c = c->next)
                        pptr = &commit_list_insert(c->item, pptr)->next;
 -      } else if (in_merge) {
 +      } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
                FILE *fp;
  
                        parents = reduce_heads(parents);
        } else {
                if (!reflog_msg)
 -                      reflog_msg = "commit";
 +                      reflog_msg = (whence == FROM_CHERRY_PICK)
 +                                      ? "commit (cherry-pick)"
 +                                      : "commit";
                pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
        }
  
                die("cannot update HEAD ref");
        }
  
 +      unlink(git_path("CHERRY_PICK_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
diff --combined builtin/merge.c
index b4746ee55b389d35a842c0471336f2e22a003430,6ce82105017a656609735805586566a638f4785c..aa3453c5e1c99c02d802bc77632ecedec4451c43
@@@ -58,6 -58,7 +58,7 @@@ static int option_renormalize
  static int verbosity;
  static int allow_rerere_auto;
  static int abort_current_merge;
+ static int show_progress = -1;
  
  static struct strategy all_strategy[] = {
        { "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@@ -195,11 -196,12 +196,12 @@@ static struct option builtin_merge_opti
        OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
                "option for selected merge strategy", option_parse_x),
        OPT_CALLBACK('m', "message", &merge_msg, "message",
 -              "message to be used for the merge commit (if any)",
 +              "merge commit message (for a non-fast-forward merge)",
                option_parse_message),
        OPT__VERBOSITY(&verbosity),
        OPT_BOOLEAN(0, "abort", &abort_current_merge,
                "abort the current in-progress merge"),
+       OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
        OPT_END()
  };
  
@@@ -582,8 -584,7 +584,8 @@@ static void write_tree_trivial(unsigne
                die("git write-tree failed to write a tree");
  }
  
 -int try_merge_command(const char *strategy, struct commit_list *common,
 +int try_merge_command(const char *strategy, size_t xopts_nr,
 +                    const char **xopts, struct commit_list *common,
                      const char *head_arg, struct commit_list *remotes)
  {
        const char **args;
@@@ -660,6 -661,8 +662,8 @@@ static int try_merge_strategy(const cha
                        o.subtree_shift = "";
  
                o.renormalize = option_renormalize;
+               o.show_rename_progress =
+                       show_progress == -1 ? isatty(2) : show_progress;
  
                for (x = 0; x < xopts_nr; x++)
                        if (parse_merge_opt(&o, xopts[x]))
                rollback_lock_file(lock);
                return clean ? 0 : 1;
        } else {
 -              return try_merge_command(strategy, common, head_arg, remoteheads);
 +              return try_merge_command(strategy, xopts_nr, xopts,
 +                                              common, head_arg, remoteheads);
        }
  }
  
@@@ -797,32 -799,6 +801,32 @@@ static void add_strategies(const char *
  
  }
  
 +static void write_merge_msg(void)
 +{
 +      int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
 +      if (fd < 0)
 +              die_errno("Could not open '%s' for writing",
 +                        git_path("MERGE_MSG"));
 +      if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
 +              die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
 +      close(fd);
 +}
 +
 +static void read_merge_msg(void)
 +{
 +      strbuf_reset(&merge_msg);
 +      if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
 +              die_errno("Could not read from '%s'", git_path("MERGE_MSG"));
 +}
 +
 +static void run_prepare_commit_msg(void)
 +{
 +      write_merge_msg();
 +      run_hook(get_index_file(), "prepare-commit-msg",
 +               git_path("MERGE_MSG"), "merge", NULL, NULL);
 +      read_merge_msg();
 +}
 +
  static int merge_trivial(void)
  {
        unsigned char result_tree[20], result_commit[20];
        parent->next = xmalloc(sizeof(*parent->next));
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
 +      run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
        finish(result_commit, "In-index merge");
        drop_save();
@@@ -864,7 -839,6 +868,7 @@@ static int finish_automerge(struct comm
        }
        free_commit_list(remoteheads);
        strbuf_addch(&merge_msg, '\n');
 +      run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
        strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
        finish(result_commit, buf.buf);
@@@ -974,6 -948,9 +978,9 @@@ int cmd_merge(int argc, const char **ar
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
                        builtin_merge_usage, 0);
  
+       if (verbosity < 0 && show_progress == -1)
+               show_progress = 0;
        if (abort_current_merge) {
                int nargc = 2;
                const char *nargv[] = {"reset", "--merge", NULL};
                else
                        die("You have not concluded your merge (MERGE_HEAD exists).");
        }
 +      if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
 +              if (advice_resolve_conflict)
 +                      die("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
 +                          "Please, commit your changes before you can merge.");
 +              else
 +                      die("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).");
 +      }
        resolve_undo_clear();
  
        if (verbosity < 0)
                        die_errno("Could not write to '%s'", git_path("MERGE_HEAD"));
                close(fd);
                strbuf_addch(&merge_msg, '\n');
 -              fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
 -              if (fd < 0)
 -                      die_errno("Could not open '%s' for writing",
 -                                git_path("MERGE_MSG"));
 -              if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
 -                      merge_msg.len)
 -                      die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
 -              close(fd);
 +              write_merge_msg();
                fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
                if (fd < 0)
                        die_errno("Could not open '%s' for writing",
diff --combined diff.c
index 3fd9e0c70315727f835f39d9a0230ee9f24e7ea8,869cca7536625976e958c48b3c04d1f4bfdcacbb..42a107c58ae6f81ffe514e573afdaec881c4532e
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -23,7 -23,7 +23,7 @@@
  #endif
  
  static int diff_detect_rename_default;
- static int diff_rename_limit_default = 200;
+ static int diff_rename_limit_default = 400;
  static int diff_suppress_blank_empty;
  int diff_use_color_default = -1;
  static const char *diff_word_regex_cfg;
@@@ -245,15 -245,6 +245,15 @@@ static int fill_mmfile(mmfile_t *mf, st
        return 0;
  }
  
 +/* like fill_mmfile, but only for size, so we can avoid retrieving blob */
 +static unsigned long diff_filespec_size(struct diff_filespec *one)
 +{
 +      if (!DIFF_FILE_VALID(one))
 +              return 0;
 +      diff_populate_filespec(one, 1);
 +      return one->size;
 +}
 +
  static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule)
  {
        char *ptr = mf->ptr;
@@@ -615,14 -606,16 +615,14 @@@ static void diff_words_append(char *lin
        buffer->text.ptr[buffer->text.size] = '\0';
  }
  
 -struct diff_words_style_elem
 -{
 +struct diff_words_style_elem {
        const char *prefix;
        const char *suffix;
        const char *color; /* NULL; filled in by the setup code if
                            * color is enabled */
  };
  
 -struct diff_words_style
 -{
 +struct diff_words_style {
        enum diff_words_type type;
        struct diff_words_style_elem new, old, ctx;
        const char *newline;
@@@ -2086,28 -2079,25 +2086,28 @@@ static void builtin_diffstat(const cha
                data->is_unmerged = 1;
                return;
        }
 -      if (complete_rewrite) {
 +
 +      if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
 +              data->is_binary = 1;
 +              data->added = diff_filespec_size(two);
 +              data->deleted = diff_filespec_size(one);
 +      }
 +
 +      else if (complete_rewrite) {
                diff_populate_filespec(one, 0);
                diff_populate_filespec(two, 0);
                data->deleted = count_lines(one->data, one->size);
                data->added = count_lines(two->data, two->size);
 -              goto free_and_return;
        }
 -      if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
 -              die("unable to read files to diff");
  
 -      if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
 -              data->is_binary = 1;
 -              data->added = mf2.size;
 -              data->deleted = mf1.size;
 -      } else {
 +      else {
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
  
 +              if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
 +                      die("unable to read files to diff");
 +
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = o->xdl_opts;
                              &xpp, &xecfg);
        }
  
 - free_and_return:
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
  }
diff --combined diff.h
index 310bd6b2832ce7f874aff735bb19b4813f117d4d,9585e41aea3ba32b1132a6137151b66b22908c32..007a0554d4b252e83e98f5578758d51ec0c6e120
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -110,7 -110,8 +110,8 @@@ struct diff_options 
        int pickaxe_opts;
        int rename_score;
        int rename_limit;
-       int warn_on_too_large_rename;
+       int needed_rename_limit;
+       int show_rename_progress;
        int dirstat_percent;
        int setup;
        int abbrev;
        FILE *file;
        int close_file;
  
 -      int nr_paths;
 -      const char **paths;
 -      int *pathlens;
 +      struct pathspec pathspec;
        change_fn_t change;
        add_remove_fn_t add_remove;
        diff_format_fn_t format_callback;
diff --combined diffcore-rename.c
index 0cd4c1305ba82b39bb8455dd6779fec2cd692e02,cb57f512726b891e609ca75d05344f529cc5e1ac..d40e40a3ac9c919475d6157464fa906e37b0dd80
@@@ -5,6 -5,7 +5,7 @@@
  #include "diff.h"
  #include "diffcore.h"
  #include "hash.h"
+ #include "progress.h"
  
  /* Table of rename/copy destinations */
  
@@@ -170,7 -171,7 +171,7 @@@ static int estimate_similarity(struct d
         * and the final score computation below would not have a
         * divide-by-zero issue.
         */
 -      if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
 +      if (max_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
                return 0;
  
        if (!src->cnt_data && diff_populate_filespec(src, 0))
@@@ -247,8 -248,7 +248,8 @@@ struct file_similarity 
  };
  
  static int find_identical_files(struct file_similarity *src,
 -                              struct file_similarity *dst)
 +                              struct file_similarity *dst,
 +                              struct diff_options *options)
  {
        int renames = 0;
  
                        }
                        /* Give higher scores to sources that haven't been used already */
                        score = !source->rename_used;
 +                      if (source->rename_used && options->detect_rename != DIFF_DETECT_COPY)
 +                              continue;
                        score += basename_same(source, target);
                        if (score > best_score) {
                                best = p;
@@@ -309,12 -307,11 +310,12 @@@ static void free_similarity_list(struc
        }
  }
  
 -static int find_same_files(void *ptr)
 +static int find_same_files(void *ptr, void *data)
  {
        int ret;
        struct file_similarity *p = ptr;
        struct file_similarity *src = NULL, *dst = NULL;
 +      struct diff_options *options = data;
  
        /* Split the hash list up into sources and destinations */
        do {
         * If we have both sources *and* destinations, see if
         * we can match them up
         */
 -      ret = (src && dst) ? find_identical_files(src, dst) : 0;
 +      ret = (src && dst) ? find_identical_files(src, dst, options) : 0;
  
        /* Free the hashes and return the number of renames found */
        free_similarity_list(src);
@@@ -381,7 -378,7 +382,7 @@@ static void insert_file_table(struct ha
   * and then during the second round we try to match
   * cache-dirty entries as well.
   */
 -static int find_exact_renames(void)
 +static int find_exact_renames(struct diff_options *options)
  {
        int i;
        struct hash_table file_table;
                insert_file_table(&file_table, 1, i, rename_dst[i].two);
  
        /* Find the renames */
 -      i = for_each_hash(&file_table, find_same_files);
 +      i = for_each_hash(&file_table, find_same_files, options);
  
        /* .. and free the hash data structure */
        free_hash(&file_table);
@@@ -418,27 -415,6 +419,27 @@@ static void record_if_better(struct dif
                m[worst] = *o;
  }
  
 +static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, int copies)
 +{
 +      int count = 0, i;
 +
 +      for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) {
 +              struct diff_rename_dst *dst;
 +
 +              if ((mx[i].dst < 0) ||
 +                  (mx[i].score < minimum_score))
 +                      break; /* there is no more usable pair. */
 +              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)
 +                      continue;
 +              record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
 +              count++;
 +      }
 +      return count;
 +}
 +
  void diffcore_rename(struct diff_options *options)
  {
        int detect_rename = options->detect_rename;
        struct diff_score *mx;
        int i, j, rename_count;
        int num_create, num_src, dst_cnt;
+       struct progress *progress = NULL;
  
        if (!minimum_score)
                minimum_score = DEFAULT_RENAME_SCORE;
         * We really want to cull the candidates list early
         * with cheap tests in order to avoid doing deltas.
         */
 -      rename_count = find_exact_renames();
 +      rename_count = find_exact_renames(options);
  
        /* Did we only want exact renames? */
        if (minimum_score == MAX_SCORE)
         * 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)) {
-               if (options->warn_on_too_large_rename)
-                       warning("too many files (created: %d deleted: %d), skipping inexact rename detection", num_create, num_src);
+               options->needed_rename_limit =
+                       num_src > num_create ? num_src : num_create;
                goto cleanup;
        }
  
+       if (options->show_rename_progress) {
+               progress = start_progress_delay(
+                               "Performing inexact rename detection",
+                               rename_dst_nr * rename_src_nr, 50, 1);
+       }
        mx = xcalloc(num_create * NUM_CANDIDATE_PER_DST, sizeof(*mx));
        for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
                struct diff_filespec *two = rename_dst[i].two;
                        diff_free_filespec_blob(two);
                }
                dst_cnt++;
+               display_progress(progress, (i+1)*rename_src_nr);
        }
+       stop_progress(&progress);
  
        /* cost matrix sorted by most to least similar pair */
        qsort(mx, dst_cnt * NUM_CANDIDATE_PER_DST, sizeof(*mx), score_compare);
  
 -      for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) {
 -              struct diff_rename_dst *dst;
 -
 -              if ((mx[i].dst < 0) ||
 -                  (mx[i].score < minimum_score))
 -                      break; /* there is no more usable pair. */
 -              dst = &rename_dst[mx[i].dst];
 -              if (dst->pair)
 -                      continue; /* already done, either exact or fuzzy. */
 -              if (rename_src[mx[i].src].one->rename_used)
 -                      continue;
 -              record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
 -              rename_count++;
 -      }
 -
 -      for (i = 0; i < dst_cnt * NUM_CANDIDATE_PER_DST; i++) {
 -              struct diff_rename_dst *dst;
 -
 -              if ((mx[i].dst < 0) ||
 -                  (mx[i].score < minimum_score))
 -                      break; /* there is no more usable pair. */
 -              dst = &rename_dst[mx[i].dst];
 -              if (dst->pair)
 -                      continue; /* already done, either exact or fuzzy. */
 -              record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
 -              rename_count++;
 -      }
 +      rename_count += find_renames(mx, dst_cnt, minimum_score, 0);
 +      if (detect_rename == DIFF_DETECT_COPY)
 +              rename_count += find_renames(mx, dst_cnt, minimum_score, 1);
        free(mx);
  
   cleanup:
diff --combined git-pull.sh
index f6b7b8404896c15487044bcfaa64f758faeeef06,5e8215ca74c01460f848f957253f2836ba5c1e9c..63b063a7b284bac5b10a9bd6fe879e5717bee2f3
@@@ -53,6 -53,8 +53,8 @@@ d
                verbosity="$verbosity -v" ;;
        --progress)
                progress=--progress ;;
+       --no-progress)
+               progress=--no-progress ;;
        -n|--no-stat|--no-summary)
                diffstat=--no-stat ;;
        --stat|--summary)
        --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
                dry_run=--dry-run
                ;;
 -      -h|--h|--he|--hel|--help)
 +      -h|--h|--he|--hel|--help|--help-|--help-a|--help-al|--help-all)
                usage
                ;;
        *)
@@@ -293,8 -295,8 +295,8 @@@ true
        ;;
  *)
        eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
-       eval="$eval  $log_arg $strategy_args $merge_args"
-       eval="$eval \"\$merge_name\" HEAD $merge_head $verbosity"
+       eval="$eval  $log_arg $strategy_args $merge_args $verbosity $progress"
+       eval="$eval \"\$merge_name\" HEAD $merge_head"
        ;;
  esac
  eval "exec $eval"
diff --combined merge-recursive.c
index 3debbc44a05246c24ce431ff3384bf710a9fbb59,6c8f95771231e0b0d5400e6c4b9af17fffeedbe7..8e82a8b1a5ab8a2f5af00910a8febda93b9e420d
  #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)
  {
@@@ -83,8 -88,10 +88,8 @@@ struct rename_df_conflict_info 
   * Since we want to write the index eventually, we cannot reuse the index
   * for these (temporary) data.
   */
 -struct stage_data
 -{
 -      struct
 -      {
 +struct stage_data {
 +      struct {
                unsigned mode;
                unsigned char sha[20];
        } stages[4];
@@@ -135,6 -142,7 +140,6 @@@ static void flush_output(struct merge_o
  __attribute__((format (printf, 3, 4)))
  static void output(struct merge_options *o, int v, const char *fmt, ...)
  {
 -      int len;
        va_list ap;
  
        if (!show(o, v))
        strbuf_setlen(&o->obuf, o->obuf.len + o->call_depth * 2);
  
        va_start(ap, fmt);
 -      len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
 +      strbuf_vaddf(&o->obuf, fmt, ap);
        va_end(ap);
  
 -      if (len < 0)
 -              len = 0;
 -      if (len >= strbuf_avail(&o->obuf)) {
 -              strbuf_grow(&o->obuf, len + 2);
 -              va_start(ap, fmt);
 -              len = vsnprintf(o->obuf.buf + o->obuf.len, strbuf_avail(&o->obuf), fmt, ap);
 -              va_end(ap);
 -              if (len >= strbuf_avail(&o->obuf)) {
 -                      die("this should not happen, your snprintf is broken");
 -              }
 -      }
 -      strbuf_setlen(&o->obuf, o->obuf.len + len);
        strbuf_add(&o->obuf, "\n", 1);
        if (!o->buffer_output)
                flush_output(o);
@@@ -388,7 -408,8 +393,7 @@@ static void make_room_for_directories_o
        }
  }
  
 -struct rename
 -{
 +struct rename {
        struct diff_filepair *pair;
        struct stage_data *src_entry;
        struct stage_data *dst_entry;
@@@ -418,14 -439,16 +423,16 @@@ static struct string_list *get_renames(
        opts.detect_rename = DIFF_DETECT_RENAME;
        opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
                            o->diff_rename_limit >= 0 ? o->diff_rename_limit :
-                           500;
+                           1000;
        opts.rename_score = o->rename_score;
-       opts.warn_on_too_large_rename = 1;
+       opts.show_rename_progress = o->show_rename_progress;
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        if (diff_setup_done(&opts) < 0)
                die("diff setup failed");
        diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts);
        diffcore_std(&opts);
+       if (opts.needed_rename_limit > o->needed_rename_limit)
+               o->needed_rename_limit = opts.needed_rename_limit;
        for (i = 0; i < diff_queued_diff.nr; ++i) {
                struct string_list_item *item;
                struct rename *re;
@@@ -701,7 -724,8 +708,7 @@@ static void update_file(struct merge_op
  
  /* Low level file merging, update and removal */
  
 -struct merge_file_info
 -{
 +struct merge_file_info {
        unsigned char sha[20];
        unsigned mode;
        unsigned clean:1,
@@@ -1649,6 -1673,8 +1656,8 @@@ 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);
        return clean;
  }
  
diff --combined merge-recursive.h
index 981ed6ac94b5ed73f15a3b0dca723586374ca0b8,59d1475be9239c57190620ed8c4df628dd24d4f8..7e1e972b13cb8a9f2fae084f769fffede24c8dbb
@@@ -20,6 -20,8 +20,8 @@@ struct merge_options 
        int diff_rename_limit;
        int merge_rename_limit;
        int rename_score;
+       int needed_rename_limit;
+       int show_rename_progress;
        int call_depth;
        struct strbuf obuf;
        struct string_list current_file_set;
@@@ -57,8 -59,6 +59,8 @@@ struct tree *write_tree_from_memory(str
  int parse_merge_opt(struct merge_options *out, const char *s);
  
  /* builtin/merge.c */
 -int try_merge_command(const char *strategy, struct commit_list *common, const char *head_arg, struct commit_list *remotes);
 +int try_merge_command(const char *strategy, size_t xopts_nr,
 +              const char **xopts, struct commit_list *common,
 +              const char *head_arg, struct commit_list *remotes);
  
  #endif