Code

Merge branch 'kb/merge-recursive-rename-threshold'
authorJunio C Hamano <gitster@pobox.com>
Wed, 27 Oct 2010 04:54:04 +0000 (21:54 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 27 Oct 2010 04:54:04 +0000 (21:54 -0700)
* kb/merge-recursive-rename-threshold:
  diff: add synonyms for -M, -C, -B
  merge-recursive: option to specify rename threshold

Conflicts:
Documentation/diff-options.txt
Documentation/merge-strategies.txt

1  2 
Documentation/diff-options.txt
Documentation/merge-strategies.txt
diff.c
diff.h
merge-recursive.c
merge-recursive.h

index e954af0ad6c20707c2bf950a021fa1296e2582ea,df37ccd36572ea682ccebd67c1e8dccc217add79..bfd0b571e23a6b6ee21d9616466acd82795eed6c
@@@ -48,9 -48,9 +48,9 @@@ endif::git-format-patch[
  --patience::
        Generate a diff using the "patience diff" algorithm.
  
 ---stat[=width[,name-width]]::
 +--stat[=<width>[,<name-width>]]::
        Generate a diffstat.  You can override the default
 -      output width for 80-column terminal by `--stat=width`.
 +      output width for 80-column terminal by `--stat=<width>`.
        The width of the filename part can be controlled by
        giving another width to it separated by a comma.
  
        number of modified files, as well as number of added and deleted
        lines.
  
 ---dirstat[=limit]::
 +--dirstat[=<limit>]::
        Output the distribution of relative amount of changes (number of lines added or
        removed) for each sub-directory. Directories with changes below
        a cut-off percent (3% by default) are not shown. The cut-off percent
 -      can be set with `--dirstat=limit`. Changes in a child directory is not
 +      can be set with `--dirstat=<limit>`. Changes in a child directory are not
        counted for the parent directory, unless `--cumulative` is used.
  
 ---dirstat-by-file[=limit]::
 +--dirstat-by-file[=<limit>]::
        Same as `--dirstat`, but counts changed files instead of lines.
  
  --summary::
@@@ -206,29 -206,12 +206,31 @@@ endif::git-format-patch[
        the diff-patch output format.  Non default number of
        digits can be specified with `--abbrev=<n>`.
  
 --B::
 +-B[<n>][/<m>]::
+ --break-rewrites[=[<n>][/<m>]]::
 -      Break complete rewrite changes into pairs of delete and create.
 -
 --M::
 +      Break complete rewrite changes into pairs of delete and
 +      create. This serves two purposes:
 ++
 +It affects the way a change that amounts to a total rewrite of a file
 +not as a series of deletion and insertion mixed together with a very
 +few lines that happen to match textually as the context, but as a
 +single deletion of everything old followed by a single insertion of
 +everything new, and the number `m` controls this aspect of the -B
 +option (defaults to 60%). `-B/70%` specifies that less than 30% of the
 +original should remain in the result for git to consider it a total
 +rewrite (i.e. otherwise the resulting patch will be a series of
 +deletion and insertion mixed together with context lines).
 ++
 +When used with -M, a totally-rewritten file is also considered as the
 +source of a rename (usually -M only considers a file that disappeared
 +as the source of a rename), and the number `n` controls this aspect of
 +the -B option (defaults to 50%). `-B20%` specifies that a change with
 +addition and deletion compared to 20% or more of the file's size are
 +eligible for being picked up as a possible source of a rename to
 +another file.
 +
 +-M[<n>]::
+ --detect-renames[=<n>]::
  ifndef::git-log[]
        Detect renames.
  endif::git-log[]
@@@ -237,24 -220,19 +239,25 @@@ ifdef::git-log[
        For following files across renames while traversing history, see
        `--follow`.
  endif::git-log[]
 +      If `n` is specified, it is a is a threshold on the similarity
 +      index (i.e. amount of addition/deletions compared to the
 +      file's size). For example, `-M90%` means git should consider a
 +      delete/add pair to be a rename if more than 90% of the file
 +      hasn't changed.
  
 --C::
 +-C[<n>]::
+ --detect-copies[=<n>]::
        Detect copies as well as renames.  See also `--find-copies-harder`.
 +      If `n` is specified, it has the same meaning as for `-M<n>`.
  
  ifndef::git-format-patch[]
 ---diff-filter=[ACDMRTUXB*]::
 +--diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]]::
        Select only files that are Added (`A`), Copied (`C`),
        Deleted (`D`), Modified (`M`), Renamed (`R`), have their
        type (i.e. regular file, symlink, submodule, ...) changed (`T`),
        are Unmerged (`U`), are
        Unknown (`X`), or have had their pairing Broken (`B`).
 -      Any combination of the filter characters may be used.
 +      Any combination of the filter characters (including none) can be used.
        When `*` (All-or-none) is added to the combination, all
        paths are selected if there is any file that matches
        other criteria in the comparison; if there is no file
@@@ -284,12 -262,8 +287,12 @@@ ifndef::git-format-patch[
        appearing in diff output; see the 'pickaxe' entry in
        linkgit:gitdiffcore[7] for more details.
  
 +-G<regex>::
 +      Look for differences whose added or removed line matches
 +      the given <regex>.
 +
  --pickaxe-all::
 -      When `-S` finds a change, show all the changes in that
 +      When `-S` or `-G` finds a change, show all the changes in that
        changeset, not just the files that contain the change
        in <string>.
  
@@@ -357,18 -331,8 +360,18 @@@ endif::git-format-patch[
  --no-ext-diff::
        Disallow external diff drivers.
  
 ---ignore-submodules::
 -      Ignore changes to submodules in the diff generation.
 +--ignore-submodules[=<when>]::
 +      Ignore changes to submodules in the diff generation. <when> can be
 +      either "none", "untracked", "dirty" or "all", which is the default
 +      Using "none" will consider the submodule modified when it either contains
 +      untracked or modified files or its HEAD differs from the commit recorded
 +      in the superproject and can be used to override any settings of the
 +      'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When
 +      "untracked" is used submodules are not considered dirty when they only
 +      contain untracked content (but they are still scanned for modified
 +      content). Using "dirty" ignores all changes to the work tree of submodules,
 +      only changes to the commits stored in the superproject are shown (this was
 +      the behavior until 1.7.0). Using "all" hides all changes to submodules.
  
  --src-prefix=<prefix>::
        Show the given source prefix instead of "a/".
index 9cf88e2bb5baa51c334e6d7232f926b49aef3681,77f26061d66fa24c57e169e75cff542e1223e12e..595a3cf1a7118ba29a1d57d7fc17d233d89cd3d0
@@@ -74,7 -74,11 +74,11 @@@ no-renormalize;
        Disables the `renormalize` option.  This overrides the
        `merge.renormalize` configuration variable.
  
 -subtree[=path];;
+ rename-threshold=<n>;;
+       Controls the similarity threshold used for rename detection.
+       See also linkgit:git-diff[1] `-M`.
 +subtree[=<path>];;
        This option is a more advanced form of 'subtree' strategy, where
        the strategy makes a guess on how two trees must be shifted to
        match with each other when merging.  Instead, the specified path
diff --combined diff.c
index ba57bfab1748ea6a6401105421d298c236bc7ef3,85a7fb02f0777692151cad28f4e02f3f26918591..d1c6b91982ccbb6489c57c9bbddb692c7b4b7ca3
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -31,7 -31,6 +31,7 @@@ static const char *external_diff_cmd_cf
  int diff_auto_refresh_index = 1;
  static int diff_mnemonic_prefix;
  static int diff_no_prefix;
 +static struct diff_options default_diff_options;
  
  static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@@ -108,9 -107,6 +108,9 @@@ int git_diff_ui_config(const char *var
        if (!strcmp(var, "diff.wordregex"))
                return git_config_string(&diff_word_regex_cfg, var, value);
  
 +      if (!strcmp(var, "diff.ignoresubmodules"))
 +              handle_ignore_submodules_arg(&default_diff_options, value);
 +
        return git_diff_basic_config(var, value, cb);
  }
  
@@@ -145,9 -141,6 +145,9 @@@ int git_diff_basic_config(const char *v
                return 0;
        }
  
 +      if (!prefixcmp(var, "submodule."))
 +              return parse_submodule_config_option(var, value);
 +
        return git_color_default_config(var, value, cb);
  }
  
@@@ -919,10 -912,7 +919,10 @@@ static void free_diff_words_data(struc
                free (ecbdata->diff_words->minus.orig);
                free (ecbdata->diff_words->plus.text.ptr);
                free (ecbdata->diff_words->plus.orig);
 -              free(ecbdata->diff_words->word_regex);
 +              if (ecbdata->diff_words->word_regex) {
 +                      regfree(ecbdata->diff_words->word_regex);
 +                      free(ecbdata->diff_words->word_regex);
 +              }
                free(ecbdata->diff_words);
                ecbdata->diff_words = NULL;
        }
@@@ -2637,7 -2627,8 +2637,7 @@@ static void fill_metainfo(struct strbu
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
                                abbrev = 40;
                }
 -              strbuf_addf(msg, "%s%sindex %s..", set,
 -                          line_prefix,
 +              strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
                            find_unique_abbrev(one->sha1, abbrev));
                strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev));
                if (one->mode == two->mode)
@@@ -2714,16 -2705,10 +2714,16 @@@ static void diff_fill_sha1_info(struct 
  static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
  {
        /* Strip the prefix but do not molest /dev/null and absolute paths */
 -      if (*namep && **namep != '/')
 +      if (*namep && **namep != '/') {
                *namep += prefix_length;
 -      if (*otherp && **otherp != '/')
 +              if (**namep == '/')
 +                      ++*namep;
 +      }
 +      if (*otherp && **otherp != '/') {
                *otherp += prefix_length;
 +              if (**otherp == '/')
 +                      ++*otherp;
 +      }
  }
  
  static void run_diff(struct diff_filepair *p, struct diff_options *o)
@@@ -2829,7 -2814,8 +2829,7 @@@ static void run_checkdiff(struct diff_f
  
  void diff_setup(struct diff_options *options)
  {
 -      memset(options, 0, sizeof(*options));
 -      memset(&diff_queued_diff, 0, sizeof(diff_queued_diff));
 +      memcpy(options, &default_diff_options, sizeof(*options));
  
        options->file = stdout;
  
@@@ -3005,100 -2991,9 +3005,100 @@@ static int opt_arg(const char *arg, in
  
  static int diff_scoreopt_parse(const char *opt);
  
 +static inline int short_opt(char opt, const char **argv,
 +                          const char **optarg)
 +{
 +      const char *arg = argv[0];
 +      if (arg[0] != '-' || arg[1] != opt)
 +              return 0;
 +      if (arg[2] != '\0') {
 +              *optarg = arg + 2;
 +              return 1;
 +      }
 +      if (!argv[1])
 +              die("Option '%c' requires a value", opt);
 +      *optarg = argv[1];
 +      return 2;
 +}
 +
 +int parse_long_opt(const char *opt, const char **argv,
 +                 const char **optarg)
 +{
 +      const char *arg = argv[0];
 +      if (arg[0] != '-' || arg[1] != '-')
 +              return 0;
 +      arg += strlen("--");
 +      if (prefixcmp(arg, opt))
 +              return 0;
 +      arg += strlen(opt);
 +      if (*arg == '=') { /* sticked form: --option=value */
 +              *optarg = arg + 1;
 +              return 1;
 +      }
 +      if (*arg != '\0')
 +              return 0;
 +      /* separate form: --option value */
 +      if (!argv[1])
 +              die("Option '--%s' requires a value", opt);
 +      *optarg = argv[1];
 +      return 2;
 +}
 +
 +static int stat_opt(struct diff_options *options, const char **av)
 +{
 +      const char *arg = av[0];
 +      char *end;
 +      int width = options->stat_width;
 +      int name_width = options->stat_name_width;
 +      int argcount = 1;
 +
 +      arg += strlen("--stat");
 +      end = (char *)arg;
 +
 +      switch (*arg) {
 +      case '-':
 +              if (!prefixcmp(arg, "-width")) {
 +                      arg += strlen("-width");
 +                      if (*arg == '=')
 +                              width = strtoul(arg + 1, &end, 10);
 +                      else if (!*arg && !av[1])
 +                              die("Option '--stat-width' requires a value");
 +                      else if (!*arg) {
 +                              width = strtoul(av[1], &end, 10);
 +                              argcount = 2;
 +                      }
 +              } else if (!prefixcmp(arg, "-name-width")) {
 +                      arg += strlen("-name-width");
 +                      if (*arg == '=')
 +                              name_width = strtoul(arg + 1, &end, 10);
 +                      else if (!*arg && !av[1])
 +                              die("Option '--stat-name-width' requires a value");
 +                      else if (!*arg) {
 +                              name_width = strtoul(av[1], &end, 10);
 +                              argcount = 2;
 +                      }
 +              }
 +              break;
 +      case '=':
 +              width = strtoul(arg+1, &end, 10);
 +              if (*end == ',')
 +                      name_width = strtoul(end+1, &end, 10);
 +      }
 +
 +      /* Important! This checks all the error cases! */
 +      if (*end)
 +              return 0;
 +      options->output_format |= DIFF_FORMAT_DIFFSTAT;
 +      options->stat_name_width = name_width;
 +      options->stat_width = width;
 +      return argcount;
 +}
 +
  int diff_opt_parse(struct diff_options *options, const char **av, int ac)
  {
        const char *arg = av[0];
 +      const char *optarg;
 +      int argcount;
  
        /* Output format options */
        if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch"))
                options->output_format |= DIFF_FORMAT_NAME_STATUS;
        else if (!strcmp(arg, "-s"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
 -      else if (!prefixcmp(arg, "--stat")) {
 -              char *end;
 -              int width = options->stat_width;
 -              int name_width = options->stat_name_width;
 -              arg += 6;
 -              end = (char *)arg;
 -
 -              switch (*arg) {
 -              case '-':
 -                      if (!prefixcmp(arg, "-width="))
 -                              width = strtoul(arg + 7, &end, 10);
 -                      else if (!prefixcmp(arg, "-name-width="))
 -                              name_width = strtoul(arg + 12, &end, 10);
 -                      break;
 -              case '=':
 -                      width = strtoul(arg+1, &end, 10);
 -                      if (*end == ',')
 -                              name_width = strtoul(end+1, &end, 10);
 -              }
 -
 -              /* Important! This checks all the error cases! */
 -              if (*end)
 -                      return 0;
 -              options->output_format |= DIFF_FORMAT_DIFFSTAT;
 -              options->stat_name_width = name_width;
 -              options->stat_width = width;
 -      }
 +      else if (!prefixcmp(arg, "--stat"))
 +              /* --stat, --stat-width, or --stat-name-width */
 +              return stat_opt(options, av);
  
        /* renames options */
-       else if (!prefixcmp(arg, "-B")) {
+       else if (!prefixcmp(arg, "-B") || !prefixcmp(arg, "--break-rewrites=") ||
+                !strcmp(arg, "--break-rewrites")) {
                if ((options->break_opt = diff_scoreopt_parse(arg)) == -1)
                        return -1;
        }
-       else if (!prefixcmp(arg, "-M")) {
+       else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--detect-renames=") ||
+                !strcmp(arg, "--detect-renames")) {
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
                        return -1;
                options->detect_rename = DIFF_DETECT_RENAME;
        }
-       else if (!prefixcmp(arg, "-C")) {
+       else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--detect-copies=") ||
+                !strcmp(arg, "--detect-copies")) {
                if (options->detect_rename == DIFF_DETECT_COPY)
                        DIFF_OPT_SET(options, FIND_COPIES_HARDER);
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
                else
                        die("bad --word-diff argument: %s", type);
        }
 -      else if (!prefixcmp(arg, "--word-diff-regex=")) {
 +      else if ((argcount = parse_long_opt("word-diff-regex", av, &optarg))) {
                if (options->word_diff == DIFF_WORDS_NONE)
                        options->word_diff = DIFF_WORDS_PLAIN;
 -              options->word_regex = arg + 18;
 +              options->word_regex = optarg;
 +              return argcount;
        }
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
                DIFF_OPT_SET(options, ALLOW_TEXTCONV);
        else if (!strcmp(arg, "--no-textconv"))
                DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
 -      else if (!strcmp(arg, "--ignore-submodules"))
 -              DIFF_OPT_SET(options, IGNORE_SUBMODULES);
 -      else if (!strcmp(arg, "--submodule"))
 +      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=")) {
 +              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=")) {
                if (!strcmp(arg + 12, "log"))
        /* misc options */
        else if (!strcmp(arg, "-z"))
                options->line_termination = 0;
 -      else if (!prefixcmp(arg, "-l"))
 -              options->rename_limit = strtoul(arg+2, NULL, 10);
 -      else if (!prefixcmp(arg, "-S"))
 -              options->pickaxe = arg + 2;
 +      else if ((argcount = short_opt('l', av, &optarg))) {
 +              options->rename_limit = strtoul(optarg, NULL, 10);
 +              return argcount;
 +      }
 +      else if ((argcount = short_opt('S', av, &optarg))) {
 +              options->pickaxe = optarg;
 +              options->pickaxe_opts |= DIFF_PICKAXE_KIND_S;
 +              return argcount;
 +      } else if ((argcount = short_opt('G', av, &optarg))) {
 +              options->pickaxe = optarg;
 +              options->pickaxe_opts |= DIFF_PICKAXE_KIND_G;
 +              return argcount;
 +      }
        else if (!strcmp(arg, "--pickaxe-all"))
 -              options->pickaxe_opts = DIFF_PICKAXE_ALL;
 +              options->pickaxe_opts |= DIFF_PICKAXE_ALL;
        else if (!strcmp(arg, "--pickaxe-regex"))
 -              options->pickaxe_opts = DIFF_PICKAXE_REGEX;
 -      else if (!prefixcmp(arg, "-O"))
 -              options->orderfile = arg + 2;
 -      else if (!prefixcmp(arg, "--diff-filter="))
 -              options->filter = arg + 14;
 +              options->pickaxe_opts |= DIFF_PICKAXE_REGEX;
 +      else if ((argcount = short_opt('O', av, &optarg))) {
 +              options->orderfile = optarg;
 +              return argcount;
 +      }
 +      else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
 +              options->filter = optarg;
 +              return argcount;
 +      }
        else if (!strcmp(arg, "--abbrev"))
                options->abbrev = DEFAULT_ABBREV;
        else if (!prefixcmp(arg, "--abbrev=")) {
                else if (40 < options->abbrev)
                        options->abbrev = 40;
        }
 -      else if (!prefixcmp(arg, "--src-prefix="))
 -              options->a_prefix = arg + 13;
 -      else if (!prefixcmp(arg, "--dst-prefix="))
 -              options->b_prefix = arg + 13;
 +      else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) {
 +              options->a_prefix = optarg;
 +              return argcount;
 +      }
 +      else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
 +              options->b_prefix = optarg;
 +              return argcount;
 +      }
        else if (!strcmp(arg, "--no-prefix"))
                options->a_prefix = options->b_prefix = "";
        else if (opt_arg(arg, '\0', "inter-hunk-context",
                         &options->interhunkcontext))
                ;
 -      else if (!prefixcmp(arg, "--output=")) {
 -              options->file = fopen(arg + strlen("--output="), "w");
 +      else if ((argcount = parse_long_opt("output", av, &optarg))) {
 +              options->file = fopen(optarg, "w");
                if (!options->file)
 -                      die_errno("Could not open '%s'", arg + strlen("--output="));
 +                      die_errno("Could not open '%s'", optarg);
                options->close_file = 1;
 +              return argcount;
        } else
                return 0;
        return 1;
  }
  
static int parse_num(const char **cp_p)
int parse_rename_score(const char **cp_p)
  {
        unsigned long num, scale;
        int ch, dot;
@@@ -3366,10 -3265,26 +3369,26 @@@ static int diff_scoreopt_parse(const ch
        if (*opt++ != '-')
                return -1;
        cmd = *opt++;
+       if (cmd == '-') {
+               /* convert the long-form arguments into short-form versions */
+               if (!prefixcmp(opt, "break-rewrites")) {
+                       opt += strlen("break-rewrites");
+                       if (*opt == 0 || *opt++ == '=')
+                               cmd = 'B';
+               } else if (!prefixcmp(opt, "detect-copies")) {
+                       opt += strlen("detect-copies");
+                       if (*opt == 0 || *opt++ == '=')
+                               cmd = 'C';
+               } else if (!prefixcmp(opt, "detect-renames")) {
+                       opt += strlen("detect-renames");
+                       if (*opt == 0 || *opt++ == '=')
+                               cmd = 'M';
+               }
+       }
        if (cmd != 'M' && cmd != 'C' && cmd != 'B')
                return -1; /* that is not a -M, -C nor -B option */
  
-       opt1 = parse_num(&opt);
+       opt1 = parse_rename_score(&opt);
        if (cmd != 'B')
                opt2 = 0;
        else {
                        return -1; /* we expect -B80/99 or -B80 */
                else {
                        opt++;
-                       opt2 = parse_num(&opt);
+                       opt2 = parse_rename_score(&opt);
                }
        }
        if (*opt != 0)
@@@ -3532,7 -3447,7 +3551,7 @@@ static void diff_flush_stat(struct diff
  
        if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
            (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
 -              return; /* no tree diffs in patch format */
 +              return; /* no useful stat for tree diffs */
  
        run_diffstat(p, o, diffstat);
  }
@@@ -3545,7 -3460,7 +3564,7 @@@ static void diff_flush_checkdiff(struc
  
        if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
            (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
 -              return; /* no tree diffs in patch format */
 +              return; /* nothing to check in tree diffs */
  
        run_checkdiff(p, o);
  }
@@@ -3861,13 -3776,6 +3880,13 @@@ static int diff_get_patch_id(struct dif
                                        len2, p->two->path);
                git_SHA1_Update(&ctx, buffer, len1);
  
 +              if (diff_filespec_is_binary(p->one) ||
 +                  diff_filespec_is_binary(p->two)) {
 +                      git_SHA1_Update(&ctx, sha1_to_hex(p->one->sha1), 40);
 +                      git_SHA1_Update(&ctx, sha1_to_hex(p->two->sha1), 40);
 +                      continue;
 +              }
 +
                xpp.flags = 0;
                xecfg.ctxlen = 3;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
@@@ -4169,24 -4077,25 +4188,24 @@@ void diffcore_fix_diff_index(struct dif
  
  void diffcore_std(struct diff_options *options)
  {
 -      /* We never run this function more than one time, because the
 -       * rename/copy detection logic can only run once.
 -       */
 -      if (diff_queued_diff.run)
 -              return;
 -
        if (options->skip_stat_unmatch)
                diffcore_skip_stat_unmatch(options);
 -      if (options->break_opt != -1)
 -              diffcore_break(options->break_opt);
 -      if (options->detect_rename)
 -              diffcore_rename(options);
 -      if (options->break_opt != -1)
 -              diffcore_merge_broken();
 +      if (!options->found_follow) {
 +              /* See try_to_follow_renames() in tree-diff.c */
 +              if (options->break_opt != -1)
 +                      diffcore_break(options->break_opt);
 +              if (options->detect_rename)
 +                      diffcore_rename(options);
 +              if (options->break_opt != -1)
 +                      diffcore_merge_broken();
 +      }
        if (options->pickaxe)
 -              diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
 +              diffcore_pickaxe(options);
        if (options->orderfile)
                diffcore_order(options->orderfile);
 -      diff_resolve_rename_copy();
 +      if (!options->found_follow)
 +              /* See try_to_follow_renames() in tree-diff.c */
 +              diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
  
        if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
  
 -      diff_queued_diff.run = 1;
 +      options->found_follow = 0;
  }
  
  int diff_result_code(struct diff_options *opt, int status)
        return result;
  }
  
 +/*
 + * Shall changes to this submodule be ignored?
 + *
 + * Submodule changes can be configured to be ignored separately for each path,
 + * but that configuration can be overridden from the command line.
 + */
 +static int is_submodule_ignored(const char *path, struct diff_options *options)
 +{
 +      int ignored = 0;
 +      unsigned orig_flags = options->flags;
 +      if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG))
 +              set_diffopt_flags_from_submodule_config(options, path);
 +      if (DIFF_OPT_TST(options, IGNORE_SUBMODULES))
 +              ignored = 1;
 +      options->flags = orig_flags;
 +      return ignored;
 +}
 +
  void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
  {
        struct diff_filespec *one, *two;
  
 -      if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(mode))
 +      if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options))
                return;
  
        /* This may look odd, but it is a preparation for
@@@ -4284,8 -4175,8 +4303,8 @@@ void diff_change(struct diff_options *o
  {
        struct diff_filespec *one, *two;
  
 -      if (DIFF_OPT_TST(options, IGNORE_SUBMODULES) && S_ISGITLINK(old_mode)
 -                      && S_ISGITLINK(new_mode))
 +      if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) &&
 +          is_submodule_ignored(concatpath, options))
                return;
  
        if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
diff --combined diff.h
index 1fd44f5e47604b94c28a673e6cadc6ec870da9df,dbd50961f1b974c92587778dfb99295ec2e3804d..0083d92438916a8188656df140ba70d6acc8c6f6
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -77,7 -77,6 +77,7 @@@ typedef struct strbuf *(*diff_prefix_fn
  #define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)
  #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)
  #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
 +#define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
  
  #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
  #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
@@@ -127,9 -126,6 +127,9 @@@ struct diff_options 
        /* this is set by diffcore for DIFF_FORMAT_PATCH */
        int found_changes;
  
 +      /* to support internal diff recursion by --follow hack*/
 +      int found_follow;
 +
        FILE *file;
        int close_file;
  
@@@ -218,13 -214,6 +218,13 @@@ extern void diff_unmerge(struct diff_op
  #define DIFF_SETUP_USE_CACHE          2
  #define DIFF_SETUP_USE_SIZE_CACHE     4
  
 +/*
 + * Poor man's alternative to parse-option, to allow both sticked form
 + * (--option=value) and separate form (--option value).
 + */
 +extern int parse_long_opt(const char *opt, const char **argv,
 +                       const char **optarg);
 +
  extern int git_diff_basic_config(const char *var, const char *value, void *cb);
  extern int git_diff_ui_config(const char *var, const char *value, void *cb);
  extern int diff_use_color_default;
@@@ -238,9 -227,6 +238,9 @@@ extern int diff_setup_done(struct diff_
  #define DIFF_PICKAXE_ALL      1
  #define DIFF_PICKAXE_REGEX    2
  
 +#define DIFF_PICKAXE_KIND_S   4 /* traditional plumbing counter */
 +#define DIFF_PICKAXE_KIND_G   8 /* grep in the patch */
 +
  extern void diffcore_std(struct diff_options *);
  extern void diffcore_fix_diff_index(struct diff_options *);
  
@@@ -315,4 -301,6 +315,6 @@@ extern size_t fill_textconv(struct user
  
  extern struct userdiff_driver *get_textconv(struct diff_filespec *one);
  
+ extern int parse_rename_score(const char **cp_p);
  #endif /* DIFF_H */
diff --combined merge-recursive.c
index 325a97bf31c8b25871fe2c2eb4391f57beb02429,148c550fee4775be7c8b5f5a7faf61f3f05f890a..875859f68efaab5113785896326140c8d0543f45
@@@ -20,7 -20,6 +20,7 @@@
  #include "attr.h"
  #include "merge-recursive.h"
  #include "dir.h"
 +#include "submodule.h"
  
  static struct tree *shift_tree_object(struct tree *one, struct tree *two,
                                      const char *subtree_shift)
@@@ -137,10 -136,16 +137,10 @@@ static void output_commit_title(struct 
                if (parse_commit(commit) != 0)
                        printf("(bad commit)\n");
                else {
 -                      const char *s;
 -                      int len;
 -                      for (s = commit->buffer; *s; s++)
 -                              if (*s == '\n' && s[1] == '\n') {
 -                                      s += 2;
 -                                      break;
 -                              }
 -                      for (len = 0; s[len] && '\n' != s[len]; len++)
 -                              ; /* do nothing */
 -                      printf("%.*s\n", len, s);
 +                      const char *title;
 +                      int len = find_commit_subject(commit->buffer, &title);
 +                      if (len)
 +                              printf("%.*s\n", len, title);
                }
        }
  }
@@@ -180,7 -185,7 +180,7 @@@ static int git_merge_trees(int index_on
        opts.fn = threeway_merge;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
 -      opts.msgs = get_porcelain_error_msgs();
 +      setup_unpack_trees_porcelain(&opts, "merge");
  
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
@@@ -233,9 -238,9 +233,9 @@@ static int save_files_dirs(const unsign
        newpath[baselen + len] = '\0';
  
        if (S_ISDIR(mode))
 -              string_list_insert(newpath, &o->current_directory_set);
 +              string_list_insert(&o->current_directory_set, newpath);
        else
 -              string_list_insert(newpath, &o->current_file_set);
 +              string_list_insert(&o->current_file_set, newpath);
        free(newpath);
  
        return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
@@@ -266,7 -271,7 +266,7 @@@ static struct stage_data *insert_stage_
                        e->stages[2].sha, &e->stages[2].mode);
        get_tree_entry(b->object.sha1, path,
                        e->stages[3].sha, &e->stages[3].mode);
 -      item = string_list_insert(path, entries);
 +      item = string_list_insert(entries, path);
        item->util = e;
        return e;
  }
@@@ -289,9 -294,9 +289,9 @@@ static struct string_list *get_unmerged
                if (!ce_stage(ce))
                        continue;
  
 -              item = string_list_lookup(ce->name, unmerged);
 +              item = string_list_lookup(unmerged, ce->name);
                if (!item) {
 -                      item = string_list_insert(ce->name, unmerged);
 +                      item = string_list_insert(unmerged, ce->name);
                        item->util = xcalloc(1, sizeof(struct stage_data));
                }
                e = item->util;
@@@ -334,6 -339,7 +334,7 @@@ static struct string_list *get_renames(
        opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
                            o->diff_rename_limit >= 0 ? o->diff_rename_limit :
                            500;
+       opts.rename_score = o->rename_score;
        opts.warn_on_too_large_rename = 1;
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        if (diff_setup_done(&opts) < 0)
                re = xmalloc(sizeof(*re));
                re->processed = 0;
                re->pair = pair;
 -              item = string_list_lookup(re->pair->one->path, entries);
 +              item = string_list_lookup(entries, re->pair->one->path);
                if (!item)
                        re->src_entry = insert_stage_data(re->pair->one->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->src_entry = item->util;
  
 -              item = string_list_lookup(re->pair->two->path, entries);
 +              item = string_list_lookup(entries, re->pair->two->path);
                if (!item)
                        re->dst_entry = insert_stage_data(re->pair->two->path,
                                        o_tree, a_tree, b_tree, entries);
                else
                        re->dst_entry = item->util;
 -              item = string_list_insert(pair->one->path, renames);
 +              item = string_list_insert(renames, pair->one->path);
                item->util = re;
        }
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
@@@ -427,7 -433,7 +428,7 @@@ static char *unique_path(struct merge_o
               lstat(newpath, &st) == 0)
                sprintf(p, "_%d", suffix++);
  
 -      string_list_insert(newpath, &o->current_file_set);
 +      string_list_insert(&o->current_file_set, newpath);
        return newpath;
  }
  
@@@ -520,15 -526,13 +521,15 @@@ static void update_file_flags(struct me
                void *buf;
                unsigned long size;
  
 -              if (S_ISGITLINK(mode))
 +              if (S_ISGITLINK(mode)) {
                        /*
                         * We may later decide to recursively descend into
                         * the submodule directory and update its index
                         * and/or work tree, but we do not do that now.
                         */
 +                      update_wd = 0;
                        goto update_index;
 +              }
  
                buf = read_sha1_file(sha, &type, &size);
                if (!buf)
@@@ -716,8 -720,8 +717,8 @@@ static struct merge_file_info merge_fil
                        free(result_buf.ptr);
                        result.clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
 -                      result.clean = 0;
 -                      hashcpy(result.sha, a->sha1);
 +                      result.clean = merge_submodule(result.sha, one->path, one->sha1,
 +                                                     a->sha1, b->sha1);
                } else if (S_ISLNK(a->mode)) {
                        hashcpy(result.sha, a->sha1);
  
@@@ -806,18 -810,17 +807,18 @@@ static int process_renames(struct merge
                           struct string_list *b_renames)
  {
        int clean_merge = 1, i, j;
 -      struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
 +      struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
 +      struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
        const struct rename *sre;
  
        for (i = 0; i < a_renames->nr; i++) {
                sre = a_renames->items[i].util;
 -              string_list_insert(sre->pair->two->path, &a_by_dst)->util
 +              string_list_insert(&a_by_dst, sre->pair->two->path)->util
                        = sre->dst_entry;
        }
        for (i = 0; i < b_renames->nr; i++) {
                sre = b_renames->items[i].util;
 -              string_list_insert(sre->pair->two->path, &b_by_dst)->util
 +              string_list_insert(&b_by_dst, sre->pair->two->path)->util
                        = sre->dst_entry;
        }
  
                                                        ren1->pair->two : NULL,
                                                        branch1 == o->branch1 ?
                                                        NULL : ren1->pair->two, 1);
 +                      } else if ((dst_other.mode == ren1->pair->two->mode) &&
 +                                 sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {
 +                              /* Added file on the other side
 +                                 identical to the file being
 +                                 renamed: clean merge */
 +                              update_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
                        } else if (!sha_eq(dst_other.sha1, null_sha1)) {
                                const char *new_path;
                                clean_merge = 0;
                                        output(o, 1, "Adding as %s instead", new_path);
                                        update_file(o, 0, dst_other.sha1, dst_other.mode, new_path);
                                }
 -                      } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {
 +                      } else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {
                                ren2 = item->util;
                                clean_merge = 0;
                                ren2->processed = 1;
  
                                if (mfi.clean &&
                                    sha_eq(mfi.sha, ren1->pair->two->sha1) &&
 -                                  mfi.mode == ren1->pair->two->mode)
 +                                  mfi.mode == ren1->pair->two->mode) {
                                        /*
 -                                       * This messaged is part of
 +                                       * This message is part of
                                         * t6022 test. If you change
                                         * it update the test too.
                                         */
                                        output(o, 3, "Skipped %s (merged same as existing)", ren1_dst);
 -                              else {
 +
 +                                      /* There may be higher stage entries left
 +                                       * in the index (e.g. due to a D/F
 +                                       * conflict) that need to be resolved.
 +                                       */
 +                                      if (!ren1->dst_entry->stages[2].mode !=
 +                                          !ren1->dst_entry->stages[3].mode)
 +                                              ren1->dst_entry->processed = 0;
 +                              } else {
                                        if (mfi.merge || !mfi.clean)
                                                output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst);
                                        if (mfi.merge)
@@@ -1135,7 -1124,6 +1136,7 @@@ static int process_entry(struct merge_o
        unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
        unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
  
 +      entry->processed = 1;
        if (o_sha && (!a_sha || !b_sha)) {
                /* Case A: Deleted in one */
                if ((!a_sha && !b_sha) ||
        } else if ((!o_sha && a_sha && !b_sha) ||
                   (!o_sha && !a_sha && b_sha)) {
                /* Case B: Added in one. */
 -              const char *add_branch;
 -              const char *other_branch;
                unsigned mode;
                const unsigned char *sha;
 -              const char *conf;
  
                if (a_sha) {
 -                      add_branch = o->branch1;
 -                      other_branch = o->branch2;
                        mode = a_mode;
                        sha = a_sha;
 -                      conf = "file/directory";
                } else {
 -                      add_branch = o->branch2;
 -                      other_branch = o->branch1;
                        mode = b_mode;
                        sha = b_sha;
 -                      conf = "directory/file";
                }
                if (string_list_has_string(&o->current_directory_set, path)) {
 -                      const char *new_path = unique_path(o, path, add_branch);
 -                      clean_merge = 0;
 -                      output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
 -                             "Adding %s as %s",
 -                             conf, path, other_branch, path, new_path);
 -                      remove_file(o, 0, path, 0);
 -                      update_file(o, 0, sha, mode, new_path);
 +                      /* Handle D->F conflicts after all subfiles */
 +                      entry->processed = 0;
 +                      /* But get any file out of the way now, so conflicted
 +                       * entries below the directory of the same name can
 +                       * be put in the working directory.
 +                       */
 +                      if (a_sha)
 +                              output(o, 2, "Removing %s", path);
 +                      /* do not touch working file if it did not exist */
 +                      remove_file(o, 0, path, !a_sha);
 +                      return 1; /* Assume clean till processed */
                } else {
                        output(o, 2, "Adding %s", path);
                        update_file(o, 1, sha, mode, path);
        return clean_merge;
  }
  
 -struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
 +/*
 + * Per entry merge function for D/F conflicts, to be called only after
 + * all files below dir have been processed.  We do this because in the
 + * cases we can cleanly resolve D/F conflicts, process_entry() can clean
 + * out all the files below the directory for us.
 + */
 +static int process_df_entry(struct merge_options *o,
 +                       const char *path, struct stage_data *entry)
  {
 -      struct unpack_trees_error_msgs msgs = {
 -              /* would_overwrite */
 -              "Your local changes to '%s' would be overwritten by merge.  Aborting.",
 -              /* not_uptodate_file */
 -              "Your local changes to '%s' would be overwritten by merge.  Aborting.",
 -              /* not_uptodate_dir */
 -              "Updating '%s' would lose untracked files in it.  Aborting.",
 -              /* would_lose_untracked */
 -              "Untracked working tree file '%s' would be %s by merge.  Aborting",
 -              /* bind_overlap -- will not happen here */
 -              NULL,
 -      };
 -      if (advice_commit_before_merge) {
 -              msgs.would_overwrite = msgs.not_uptodate_file =
 -                      "Your local changes to '%s' would be overwritten by merge.  Aborting.\n"
 -                      "Please, commit your changes or stash them before you can merge.";
 +      int clean_merge = 1;
 +      unsigned o_mode = entry->stages[1].mode;
 +      unsigned a_mode = entry->stages[2].mode;
 +      unsigned b_mode = entry->stages[3].mode;
 +      unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);
 +      unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);
 +      unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);
 +      const char *add_branch;
 +      const char *other_branch;
 +      unsigned mode;
 +      const unsigned char *sha;
 +      const char *conf;
 +      struct stat st;
 +
 +      /* We currently only handle D->F cases */
 +      assert((!o_sha && a_sha && !b_sha) ||
 +             (!o_sha && !a_sha && b_sha));
 +
 +      entry->processed = 1;
 +
 +      if (a_sha) {
 +              add_branch = o->branch1;
 +              other_branch = o->branch2;
 +              mode = a_mode;
 +              sha = a_sha;
 +              conf = "file/directory";
 +      } else {
 +              add_branch = o->branch2;
 +              other_branch = o->branch1;
 +              mode = b_mode;
 +              sha = b_sha;
 +              conf = "directory/file";
        }
 -      return msgs;
 +      if (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
 +              const char *new_path = unique_path(o, path, add_branch);
 +              clean_merge = 0;
 +              output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. "
 +                     "Adding %s as %s",
 +                     conf, path, other_branch, path, new_path);
 +              remove_file(o, 0, path, 0);
 +              update_file(o, 0, sha, mode, new_path);
 +      } else {
 +              output(o, 2, "Adding %s", path);
 +              update_file(o, 1, sha, mode, path);
 +      }
 +
 +      return clean_merge;
  }
  
  int merge_trees(struct merge_options *o,
        }
  
        if (sha_eq(common->object.sha1, merge->object.sha1)) {
 -              output(o, 0, "Already uptodate!");
 +              output(o, 0, "Already up-to-date!");
                *result = head;
                return 1;
        }
                                && !process_entry(o, path, e))
                                clean = 0;
                }
 +              for (i = 0; i < entries->nr; i++) {
 +                      const char *path = entries->items[i].string;
 +                      struct stage_data *e = entries->items[i].util;
 +                      if (!e->processed
 +                              && !process_df_entry(o, path, e))
 +                              clean = 0;
 +              }
  
                string_list_clear(re_merge, 0);
                string_list_clear(re_head, 0);
@@@ -1576,6 -1526,11 +1577,11 @@@ int parse_merge_opt(struct merge_option
                o->renormalize = 1;
        else if (!strcmp(s, "no-renormalize"))
                o->renormalize = 0;
+       else if (!prefixcmp(s, "rename-threshold=")) {
+               const char *score = s + strlen("rename-threshold=");
+               if ((o->rename_score = parse_rename_score(&score)) == -1 || *score != 0)
+                       return -1;
+       }
        else
                return -1;
        return 0;
diff --combined merge-recursive.h
index 2eb5d1aad531a6ba822d74f1231859aec47c133b,54420f15560310cdaf695a0b89133465f2bc52ec..c8135b0ec70cc2eb92e5a6fd9353a134fda6b7bf
@@@ -19,12 -19,16 +19,13 @@@ struct merge_options 
        int verbosity;
        int diff_rename_limit;
        int merge_rename_limit;
+       int rename_score;
        int call_depth;
        struct strbuf obuf;
        struct string_list current_file_set;
        struct string_list current_directory_set;
  };
  
 -/* Return a list of user-friendly error messages to be used by merge */
 -struct unpack_trees_error_msgs get_porcelain_error_msgs(void);
 -
  /* merge_trees() but with recursive ancestor consolidation */
  int merge_recursive(struct merge_options *o,
                    struct commit *h1,