Merge branch 'jc/fix-diff-files-unmerged'
authorJunio C Hamano <gitster@pobox.com>
Fri, 6 May 2011 17:52:58 +0000 (10:52 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 6 May 2011 17:52:58 +0000 (10:52 -0700)
* jc/fix-diff-files-unmerged:
diff-files: show unmerged entries correctly
diff: remove often unused parameters from diff_unmerge()
diff.c: return filepair from diff_unmerge()
test: use $_z40 from test-lib

1  2 
diff-lib.c
diff.c
diff.h
t/t1400-update-ref.sh
t/t1501-worktree.sh
t/t3200-branch.sh
t/t3600-rm.sh
t/t4002-diff-basic.sh
t/t4027-diff-submodule.sh
t/t7012-skip-worktree-writing.sh
t/test-lib.sh
diff --combined diff-lib.c
index 2870de400ed533d83c77269ee1654af212c6510c,b782476e4ee7769854d06ad0bbafd1df99ae19b4..3b5f2242a597ff1b44a3af6be72cb14e6e0d5455
@@@ -103,15 -103,16 +103,17 @@@ int run_diff_files(struct rev_info *rev
                unsigned dirty_submodule = 0;
  
                if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
 -                      DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
 +                  !revs->diffopt.filter &&
 +                  DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
                        break;
  
 -              if (!ce_path_match(ce, revs->prune_data))
 +              if (!ce_path_match(ce, &revs->prune_data))
                        continue;
  
                if (ce_stage(ce)) {
                        struct combine_diff_path *dpath;
+                       struct diff_filepair *pair;
+                       unsigned int wt_mode = 0;
                        int num_compare_stages = 0;
                        size_t path_len;
  
  
                        changed = check_removed(ce, &st);
                        if (!changed)
-                               dpath->mode = ce_mode_from_stat(ce, st.st_mode);
+                               wt_mode = ce_mode_from_stat(ce, st.st_mode);
                        else {
                                if (changed < 0) {
                                        perror(ce->name);
                                }
                                if (silent_on_removed)
                                        continue;
+                               wt_mode = 0;
                        }
+                       dpath->mode = wt_mode;
  
                        while (i < entries) {
                                struct cache_entry *nce = active_cache[i];
                         * Show the diff for the 'ce' if we found the one
                         * from the desired stage.
                         */
-                       diff_unmerge(&revs->diffopt, ce->name, 0, null_sha1);
+                       pair = diff_unmerge(&revs->diffopt, ce->name);
+                       if (wt_mode)
+                               pair->two->mode = wt_mode;
                        if (ce_stage(ce) != diff_unmerged_stage)
                                continue;
                }
@@@ -373,8 -378,9 +379,9 @@@ static void do_oneway_diff(struct unpac
        match_missing = !revs->ignore_merges;
  
        if (cached && idx && ce_stage(idx)) {
-               diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode,
-                            idx->sha1);
+               struct diff_filepair *pair;
+               pair = diff_unmerge(&revs->diffopt, idx->name);
+               fill_filespec(pair->one, idx->sha1, idx->ce_mode);
                return;
        }
  
@@@ -428,7 -434,7 +435,7 @@@ static int oneway_diff(struct cache_ent
        if (tree == o->df_conflict_entry)
                tree = NULL;
  
 -      if (ce_path_match(idx ? idx : tree, revs->prune_data))
 +      if (ce_path_match(idx ? idx : tree, &revs->prune_data))
                do_oneway_diff(o, idx, tree);
  
        return 0;
@@@ -502,7 -508,7 +509,7 @@@ int do_diff_cache(const unsigned char *
        active_nr = dst - active_cache;
  
        init_revisions(&revs, NULL);
 -      revs.prune_data = opt->paths;
 +      init_pathspec(&revs.prune_data, opt->pathspec.raw);
        tree = parse_tree_indirect(tree_sha1);
        if (!tree)
                die("bad tree object %s", sha1_to_hex(tree_sha1));
diff --combined diff.c
index 3b40e597d5793d13944f9604e5197ac216eb3a3e,d2c5c563bc84ba048ebccdc692a84663b8a74e9a..feced343433f41c2138f09e10e256b2020c89137
--- 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;
@@@ -581,14 -572,11 +581,14 @@@ static void emit_rewrite_diff(const cha
                line_prefix, metainfo, a_name.buf, name_a_tab, reset,
                line_prefix, metainfo, b_name.buf, name_b_tab, reset,
                line_prefix, fraginfo);
 -      print_line_count(o->file, lc_a);
 +      if (!o->irreversible_delete)
 +              print_line_count(o->file, lc_a);
 +      else
 +              fprintf(o->file, "?,?");
        fprintf(o->file, " +");
        print_line_count(o->file, lc_b);
        fprintf(o->file, " @@%s\n", reset);
 -      if (lc_a)
 +      if (lc_a && !o->irreversible_delete)
                emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
        if (lc_b)
                emit_rewrite_lines(&ecbdata, '+', data_two, size_two);
@@@ -618,20 -606,22 +618,20 @@@ 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;
  };
  
 -struct diff_words_style diff_words_styles[] = {
 +static struct diff_words_style diff_words_styles[] = {
        { DIFF_WORDS_PORCELAIN, {"+", "\n"}, {"-", "\n"}, {" ", "\n"}, "~\n" },
        { DIFF_WORDS_PLAIN, {"{+", "+}"}, {"[-", "-]"}, {"", ""}, "\n" },
        { DIFF_WORDS_COLOR, {"", ""}, {"", ""}, {"", ""}, "\n" }
@@@ -1245,7 -1235,7 +1245,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);
  
@@@ -1541,36 -1532,8 +1541,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->two->path ? p->two->path : p->one->path;
  
 -              name = p->one->path ? p->one->path : p->two->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;
@@@ -1812,14 -1771,8 +1812,14 @@@ static void emit_binary_diff(FILE *file
  
  static void diff_filespec_load_driver(struct diff_filespec *one)
  {
 -      if (!one->driver)
 +      /* Use already-loaded driver */
 +      if (one->driver)
 +              return;
 +
 +      if (S_ISREG(one->mode))
                one->driver = userdiff_find_by_path(one->path);
 +
 +      /* Fallback to default settings */
        if (!one->driver)
                one->driver = userdiff_find_by_name("default");
  }
@@@ -1867,7 -1820,8 +1867,7 @@@ struct userdiff_driver *get_textconv(st
  {
        if (!DIFF_FILE_VALID(one))
                return NULL;
 -      if (!S_ISREG(one->mode))
 -              return NULL;
 +
        diff_filespec_load_driver(one);
        if (!one->driver->textconv)
                return NULL;
@@@ -1984,11 -1938,7 +1984,11 @@@ static void builtin_diff(const char *na
                }
        }
  
 -      if (!DIFF_OPT_TST(o, TEXT) &&
 +      if (o->irreversible_delete && lbl[1][0] == '/') {
 +              fprintf(o->file, "%s", header.buf);
 +              strbuf_reset(&header);
 +              goto free_ab_and_return;
 +      } else if (!DIFF_OPT_TST(o, TEXT) &&
            ( (!textconv_one && diff_filespec_is_binary(one)) ||
              (!textconv_two && diff_filespec_is_binary(two)) )) {
                if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                        fprintf(o->file, "%sBinary files %s and %s differ\n",
                                line_prefix, lbl[0], lbl[1]);
                o->found_changes = 1;
 -      }
 -      else {
 +      } else {
                /* Crazy xdl interfaces.. */
                const char *diffopts = getenv("GIT_DIFF_OPTS");
                xpparam_t xpp;
@@@ -2123,28 -2074,25 +2123,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);
  }
@@@ -2204,7 -2153,7 +2204,7 @@@ static void builtin_checkdiff(const cha
  
                        ecbdata.ws_rule = data.ws_rule;
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
 -                      blank_at_eof = ecbdata.blank_at_eof_in_preimage;
 +                      blank_at_eof = ecbdata.blank_at_eof_in_postimage;
  
                        if (blank_at_eof) {
                                static char *err;
@@@ -2437,14 -2386,10 +2437,14 @@@ int diff_populate_filespec(struct diff_
        }
        else {
                enum object_type type;
 -              if (size_only)
 +              if (size_only) {
                        type = sha1_object_info(s->sha1, &s->size);
 -              else {
 +                      if (type < 0)
 +                              die("unable to read %s", sha1_to_hex(s->sha1));
 +              } else {
                        s->data = read_sha1_file(s->sha1, &type, &s->size);
 +                      if (!s->data)
 +                              die("unable to read %s", sha1_to_hex(s->sha1));
                        s->should_free = 1;
                }
        }
@@@ -3195,26 -3140,20 +3195,26 @@@ int diff_opt_parse(struct diff_options 
                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;
 +                      return error("invalid argument to -B: %s", arg+2);
        }
 -      else if (!prefixcmp(arg, "-M")) {
 +      else if (!prefixcmp(arg, "-M") || !prefixcmp(arg, "--find-renames=") ||
 +               !strcmp(arg, "--find-renames")) {
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
 -                      return -1;
 +                      return error("invalid argument to -M: %s", arg+2);
                options->detect_rename = DIFF_DETECT_RENAME;
        }
 -      else if (!prefixcmp(arg, "-C")) {
 +      else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) {
 +              options->irreversible_delete = 1;
 +      }
 +      else if (!prefixcmp(arg, "-C") || !prefixcmp(arg, "--find-copies=") ||
 +               !strcmp(arg, "--find-copies")) {
                if (options->detect_rename == DIFF_DETECT_COPY)
                        DIFF_OPT_SET(options, FIND_COPIES_HARDER);
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
 -                      return -1;
 +                      return error("invalid argument to -C: %s", arg+2);
                options->detect_rename = DIFF_DETECT_COPY;
        }
        else if (!strcmp(arg, "--no-renames"))
        }
        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;
 +              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("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 1;
  }
  
 -static int parse_num(const char **cp_p)
 +int parse_rename_score(const char **cp_p)
  {
        unsigned long num, scale;
        int ch, dot;
@@@ -3427,26 -3361,10 +3427,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, "find-copies")) {
 +                      opt += strlen("find-copies");
 +                      if (*opt == 0 || *opt++ == '=')
 +                              cmd = 'C';
 +              } else if (!prefixcmp(opt, "find-renames")) {
 +                      opt += strlen("find-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)
@@@ -3609,7 -3527,7 +3609,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);
  }
@@@ -3622,7 -3540,7 +3622,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);
  }
@@@ -3947,7 -3865,7 +3947,7 @@@ static int diff_get_patch_id(struct dif
  
                xpp.flags = 0;
                xecfg.ctxlen = 3;
 -              xecfg.flags = XDL_EMIT_FUNCNAMES;
 +              xecfg.flags = 0;
                xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
                              &xpp, &xecfg);
        }
@@@ -3996,28 -3914,6 +3996,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;
@@@ -4280,7 -4176,7 +4280,7 @@@ void diffcore_std(struct diff_options *
                        diffcore_merge_broken();
        }
        if (options->pickaxe)
 -              diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
 +              diffcore_pickaxe(options);
        if (options->orderfile)
                diffcore_order(options->orderfile);
        if (!options->found_follow)
  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;
@@@ -4416,20 -4308,20 +4416,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,
@@@ -4487,7 -4379,7 +4487,7 @@@ size_t fill_textconv(struct userdiff_dr
                return df->size;
        }
  
 -      if (driver->textconv_cache) {
 +      if (driver->textconv_cache && df->sha1_valid) {
                *outbuf = notes_cache_get(driver->textconv_cache, df->sha1,
                                          &size);
                if (*outbuf)
        if (!*outbuf)
                die("unable to read files to diff");
  
 -      if (driver->textconv_cache) {
 +      if (driver->textconv_cache && df->sha1_valid) {
                /* ignore errors, as we might be in a readonly repository */
                notes_cache_put(driver->textconv_cache, df->sha1, *outbuf,
                                size);
diff --combined diff.h
index 6fe1597785761b0e6961910214a129a2dde5197f,3edb705b4dc6c7f8bb1febdb6440c96141232c01..d83e53e9d465ba73808ff5071b17b7f13ad27b09
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -104,16 -104,13 +104,16 @@@ struct diff_options 
        int interhunkcontext;
        int break_opt;
        int detect_rename;
 +      int irreversible_delete;
        int skip_stat_unmatch;
        int line_termination;
        int output_format;
        int pickaxe_opts;
        int rename_score;
        int rename_limit;
 -      int warn_on_too_large_rename;
 +      int needed_rename_limit;
 +      int degraded_cc_to_c;
 +      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;
@@@ -210,10 -209,7 +210,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
@@@ -239,9 -235,6 +236,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 *);
  
  
  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'
@@@ -317,6 -309,4 +314,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 t/t1400-update-ref.sh
index ff747f8229bb46df070ea9aad2702f4d1c703420,6b3991576538034b0ba2907130a1a35ced71610e..4fd83a667ac8b08bef090d303bc4434969874780
@@@ -6,7 -6,7 +6,7 @@@
  test_description='Test git update-ref and basic ref logging'
  . ./test-lib.sh
  
- Z=0000000000000000000000000000000000000000
+ Z=$_z40
  
  test_expect_success setup '
  
@@@ -52,8 -52,9 +52,8 @@@ rm -f .git/$
  
  test_expect_success \
        "fail to create $n" \
 -      "touch .git/$n_dir
 -       git update-ref $n $A >out 2>err"'
 -       test $? != 0'
 +      "touch .git/$n_dir &&
 +       test_must_fail git update-ref $n $A >out 2>err"
  rm -f .git/$n_dir out err
  
  test_expect_success \
@@@ -184,55 -185,55 +184,55 @@@ gd="Thu, 26 May 2005 18:33:00 -0500
  ld="Thu, 26 May 2005 18:43:00 -0500"
  test_expect_success \
        'Query "master@{May 25 2005}" (before history)' \
 -      'rm -f o e
 +      'rm -f o e &&
         git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
         test '"$C"' = $(cat o) &&
         test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
  test_expect_success \
        "Query master@{2005-05-25} (before history)" \
 -      'rm -f o e
 +      'rm -f o e &&
         git rev-parse --verify master@{2005-05-25} >o 2>e &&
         test '"$C"' = $(cat o) &&
         echo test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
  test_expect_success \
        'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
 -      'rm -f o e
 +      'rm -f o e &&
         git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
         test '"$C"' = $(cat o) &&
         test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"'
  test_expect_success \
        'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
 -      'rm -f o e
 +      'rm -f o e &&
         git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
         test '"$C"' = $(cat o) &&
         test "" = "$(cat e)"'
  test_expect_success \
        'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' \
 -      'rm -f o e
 +      'rm -f o e &&
         git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e &&
         test '"$A"' = $(cat o) &&
         test "" = "$(cat e)"'
  test_expect_success \
        'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
 -      'rm -f o e
 +      'rm -f o e &&
         git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
         test '"$B"' = $(cat o) &&
         test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"'
  test_expect_success \
        'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
 -      'rm -f o e
 +      'rm -f o e &&
         git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
         test '"$Z"' = $(cat o) &&
         test "" = "$(cat e)"'
  test_expect_success \
        'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
 -      'rm -f o e
 +      'rm -f o e &&
         git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
         test '"$E"' = $(cat o) &&
         test "" = "$(cat e)"'
  test_expect_success \
        'Query "master@{2005-05-28}" (past end of history)' \
 -      'rm -f o e
 +      'rm -f o e &&
         git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
         test '"$D"' = $(cat o) &&
         test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"'
@@@ -246,7 -247,7 +246,7 @@@ test_expect_success 
       git add F &&
         GIT_AUTHOR_DATE="2005-05-26 23:30" \
         GIT_COMMITTER_DATE="2005-05-26 23:30" git commit -m add -a &&
 -       h_TEST=$(git rev-parse --verify HEAD)
 +       h_TEST=$(git rev-parse --verify HEAD) &&
         echo The other day this did not work. >M &&
         echo And then Bob told me how to fix it. >>M &&
         echo OTHER >F &&
diff --combined t/t1501-worktree.sh
index da6252b1179c47d39393c983d88c75d84a507cb7,dc7c99257ce15102a8f5c9986e17599aeb9d1ca0..63849836c8b088eb495dc565ff909c367c868301
@@@ -7,7 -7,6 +7,6 @@@ test_expect_success 'setup' 
        EMPTY_TREE=$(git write-tree) &&
        EMPTY_BLOB=$(git hash-object -t blob --stdin </dev/null) &&
        CHANGED_BLOB=$(echo changed | git hash-object -t blob --stdin) &&
-       ZEROES=0000000000000000000000000000000000000000 &&
        EMPTY_BLOB7=$(echo $EMPTY_BLOB | sed "s/\(.......\).*/\1/") &&
        CHANGED_BLOB7=$(echo $CHANGED_BLOB | sed "s/\(.......\).*/\1/") &&
  
@@@ -239,10 -238,10 +238,10 @@@ test_expect_success '_gently() groks re
  
  test_expect_success 'diff-index respects work tree under .git dir' '
        cat >diff-index-cached.expected <<-EOF &&
-       :000000 100644 $ZEROES $EMPTY_BLOB A    sub/dir/tracked
+       :000000 100644 $_z40 $EMPTY_BLOB A      sub/dir/tracked
        EOF
        cat >diff-index.expected <<-EOF &&
-       :000000 100644 $ZEROES $ZEROES A        sub/dir/tracked
+       :000000 100644 $_z40 $_z40 A    sub/dir/tracked
        EOF
  
        (
  
  test_expect_success 'diff-files respects work tree under .git dir' '
        cat >diff-files.expected <<-EOF &&
-       :100644 100644 $EMPTY_BLOB $ZEROES M    sub/dir/tracked
+       :100644 100644 $EMPTY_BLOB $_z40 M      sub/dir/tracked
        EOF
  
        (
@@@ -340,11 -339,4 +339,11 @@@ test_expect_success 'make_relative_pat
        git --git-dir="$(pwd)//repo.git" --work-tree="$(pwd)" add dummy_file
  '
  
 +test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' '
 +      GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work \
 +      test-subprocess --setup-work-tree rev-parse --show-toplevel >actual &&
 +      echo "$(pwd)/repo.git/work" >expected &&
 +      test_cmp expected actual
 +'
 +
  test_done
diff --combined t/t3200-branch.sh
index 0ce95c04e50f4662f87600bd3af48da0492a72a2,4c4cff19634546d37b956dadbe9306db15ecbde4..9e69c8c926620f06343e64e7b3aa3e4ada5a6b69
@@@ -26,17 -26,6 +26,17 @@@ test_expect_success 
       ! test -f .git/refs/heads/--help
  '
  
 +test_expect_success 'branch -h in broken repository' '
 +      mkdir broken &&
 +      (
 +              cd broken &&
 +              git init &&
 +              >.git/refs/heads/master &&
 +              test_expect_code 129 git branch -h >usage 2>&1
 +      ) &&
 +      grep "[Uu]sage" broken/usage
 +'
 +
  test_expect_success \
      'git branch abc should create a branch' \
      'git branch abc && test -f .git/refs/heads/abc'
@@@ -46,7 -35,7 +46,7 @@@ test_expect_success 
      'git branch a/b/c && test -f .git/refs/heads/a/b/c'
  
  cat >expect <<EOF
0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000    branch: Created from master
$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000       branch: Created from master
  EOF
  test_expect_success \
      'git branch -l d/e/f should create a branch and a log' \
@@@ -206,9 -195,7 +206,9 @@@ test_expect_success 'test deleting bran
  test_expect_success 'test deleting branch without config' \
      'git branch my7 s &&
       sha1=$(git rev-parse my7 | cut -c 1-7) &&
 -     test "$(git branch -d my7 2>&1)" = "Deleted branch my7 (was $sha1)."'
 +     echo "Deleted branch my7 (was $sha1)." >expect &&
 +     git branch -d my7 >actual 2>&1 &&
 +     test_i18ncmp expect actual'
  
  test_expect_success 'test --track without .fetch entries' \
      'git branch --track my8 &&
@@@ -225,14 -212,9 +225,14 @@@ test_expect_success 
      'branch from non-branch HEAD w/--track causes failure' \
      'test_must_fail git branch --track my10 HEAD^'
  
 +test_expect_success \
 +    'branch from tag w/--track causes failure' \
 +    'git tag foobar &&
 +     test_must_fail git branch --track my11 foobar'
 +
  # Keep this test last, as it changes the current branch
  cat >expect <<EOF
0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000    branch: Created from master
$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000       branch: Created from master
  EOF
  test_expect_success \
      'git checkout -b g/h/i -l should create a branch and a log' \
@@@ -495,15 -477,6 +495,15 @@@ test_expect_success 'autosetuprebase al
        test "z$(git config branch.myr20.rebase)" = z
  '
  
 +test_expect_success 'autosetuprebase always on detached HEAD' '
 +      git config branch.autosetupmerge always &&
 +      test_when_finished git checkout master &&
 +      git checkout HEAD^0 &&
 +      git branch my11 &&
 +      test -z "$(git config branch.my11.remote)" &&
 +      test -z "$(git config branch.my11.merge)"
 +'
 +
  test_expect_success 'detect misconfigured autosetuprebase (bad value)' '
        git config branch.autosetuprebase garbage &&
        test_must_fail git branch
diff --combined t/t3600-rm.sh
index cd093bd34730a3dba6eec7719d3b0170e517fb5e,66523bd8ba95b6075a06cb668e1eb5b0a0eff9f1..9fd28bcf3435f831fa24285dd703c82b8396d635
@@@ -96,7 -96,7 +96,7 @@@ test_expect_success FUNNYNAMES 
      "git rm -f 'space embedded' 'tab  embedded' 'newline
  embedded'"
  
 -test_expect_success RO_DIR 'Test that "git rm -f" fails if its rm fails' '
 +test_expect_success SANITY 'Test that "git rm -f" fails if its rm fails' '
        chmod a-w . &&
        test_must_fail git rm -f baz &&
        chmod 775 .
@@@ -240,11 -240,10 +240,10 @@@ test_expect_success 'refresh index befo
  
  test_expect_success 'choking "git rm" should not let it die with cruft' '
        git reset -q --hard &&
-       H=0000000000000000000000000000000000000000 &&
        i=0 &&
        while test $i -lt 12000
        do
-           echo "100644 $H 0   some-file-$i"
+           echo "100644 $_z40 0        some-file-$i"
            i=$(( $i + 1 ))
        done | git update-index --index-info &&
        git rm -n "some-file-*" | :;
diff --combined t/t4002-diff-basic.sh
index 9fb8ca06a84b3f3e60f466cc33bc2de786a9fc90,66e1a52c6c2fe351c0e30bfc07c7adeb4f0ff3ba..a5e8b830834f4b5feb531f8f4f4d08462325b1de
@@@ -126,15 -126,12 +126,12 @@@ cat >.test-recursive-AB <<\EO
  :100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M    Z/NM
  EOF
  
- x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
- x40="$x40$x40$x40$x40$x40$x40$x40$x40"
- z40='0000000000000000000000000000000000000000'
  cmp_diff_files_output () {
      # diff-files never reports additions.  Also it does not fill in the
      # object ID for the changed files because it wants you to look at the
      # filesystem.
      sed <"$2" >.test-tmp \
-       -e '/^:000000 /d;s/'$x40'\( [MCRNDU][0-9]*\)    /'$z40'\1       /' &&
+       -e '/^:000000 /d;s/'$_x40'\( [MCRNDU][0-9]*\)   /'$_z40'\1      /' &&
      test_cmp "$1" .test-tmp
  }
  
@@@ -205,8 -202,8 +202,8 @@@ test_expect_success 
      'rm -fr Z [A-Z][A-Z] &&
       git read-tree $tree_A &&
       git checkout-index -f -a &&
 -     git read-tree --reset $tree_O || return 1
 -     git update-index --refresh >/dev/null ;# this can exit non-zero
 +     git read-tree --reset $tree_O &&
 +     test_must_fail git update-index --refresh -q &&
       git diff-files >.test-a &&
       cmp_diff_files_output .test-a .test-recursive-OA'
  
@@@ -215,8 -212,8 +212,8 @@@ test_expect_success 
      'rm -fr Z [A-Z][A-Z] &&
       git read-tree $tree_B &&
       git checkout-index -f -a &&
 -     git read-tree --reset $tree_O || return 1
 -     git update-index --refresh >/dev/null ;# this can exit non-zero
 +     git read-tree --reset $tree_O &&
 +     test_must_fail git update-index --refresh -q &&
       git diff-files >.test-a &&
       cmp_diff_files_output .test-a .test-recursive-OB'
  
@@@ -225,8 -222,8 +222,8 @@@ test_expect_success 
      'rm -fr Z [A-Z][A-Z] &&
       git read-tree $tree_B &&
       git checkout-index -f -a &&
 -     git read-tree --reset $tree_A || return 1
 -     git update-index --refresh >/dev/null ;# this can exit non-zero
 +     git read-tree --reset $tree_A &&
 +     test_must_fail git update-index --refresh -q &&
       git diff-files >.test-a &&
       cmp_diff_files_output .test-a .test-recursive-AB'
  
index 241a74d2a20276d711944d3e203083b515486e77,fbe44a3271acb1a82eb9a62640d0e221b417d608..518bf9524e0b55181f3c440d202b69c12a0b314f
@@@ -5,7 -5,6 +5,6 @@@ test_description='difference in submodu
  . ./test-lib.sh
  . "$TEST_DIRECTORY"/diff-lib.sh
  
- _z40=0000000000000000000000000000000000000000
  test_expect_success setup '
        test_tick &&
        test_create_repo sub &&
@@@ -316,11 -315,11 +315,11 @@@ test_expect_success 'git diff (empty su
  test_expect_success 'conflicted submodule setup' '
  
        # 39 efs
 -      c=fffffffffffffffffffffffffffffffffffffff
 +      c=fffffffffffffffffffffffffffffffffffffff &&
        (
 -              echo "000000 $_z40 0    sub"
 -              echo "160000 1$c 1      sub"
 -              echo "160000 2$c 2      sub"
 +              echo "000000 $_z40 0    sub" &&
 +              echo "160000 1$c 1      sub" &&
 +              echo "160000 2$c 2      sub" &&
                echo "160000 3$c 3      sub"
        ) | git update-index --index-info &&
        echo >expect.nosub '\''diff --cc sub
index cffb2696d4b1a1ca7bdc962590dc7cedde1e5e2a,d70fe2fe303c083f5cd0328758db9602e3af5381..9ceaa4049f960f20ca37d58e58e9e6c4e9ff4398
@@@ -54,7 -54,7 +54,7 @@@ test_expect_success 'read-tree removes 
  '
  
  NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
- ZERO_SHA0=0000000000000000000000000000000000000000
  setup_absent() {
        test -f 1 && rm 1
        git update-index --remove 1 &&
@@@ -127,13 -127,13 +127,13 @@@ EO
  test_expect_success 'git-clean, absent case' '
        setup_absent &&
        git clean -n > result &&
 -      test_cmp expected result
 +      test_i18ncmp expected result
  '
  
  test_expect_success 'git-clean, dirty case' '
        setup_dirty &&
        git clean -n > result &&
 -      test_cmp expected result
 +      test_i18ncmp expected result
  '
  
  #TODO test_expect_failure 'git-apply adds file' false
diff --combined t/test-lib.sh
index c5b18e282a7ef4246fb7d725cf94bfc5e7e72dc3,7afa25fa835f4f5b0e1a18e3cf110296ea8ce948..b2ce2bc4b22f43c0b324ab3f596a7f865fa58cc1
@@@ -43,25 -43,33 +43,25 @@@ TERM=dum
  export LANG LC_ALL PAGER TERM TZ
  EDITOR=:
  unset VISUAL
 -unset GIT_EDITOR
 -unset AUTHOR_DATE
 -unset AUTHOR_EMAIL
 -unset AUTHOR_NAME
 -unset COMMIT_AUTHOR_EMAIL
 -unset COMMIT_AUTHOR_NAME
  unset EMAIL
 -unset GIT_ALTERNATE_OBJECT_DIRECTORIES
 -unset GIT_AUTHOR_DATE
 +unset $(perl -e '
 +      my @env = keys %ENV;
 +      my $ok = join("|", qw(
 +              TRACE
 +              DEBUG
 +              USE_LOOKUP
 +              TEST
 +              .*_TEST
 +              PROVE
 +              VALGRIND
 +      ));
 +      my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
 +      print join("\n", @vars);
 +')
  GIT_AUTHOR_EMAIL=author@example.com
  GIT_AUTHOR_NAME='A U Thor'
 -unset GIT_COMMITTER_DATE
  GIT_COMMITTER_EMAIL=committer@example.com
  GIT_COMMITTER_NAME='C O Mitter'
 -unset GIT_DIFF_OPTS
 -unset GIT_DIR
 -unset GIT_WORK_TREE
 -unset GIT_EXTERNAL_DIFF
 -unset GIT_INDEX_FILE
 -unset GIT_OBJECT_DIRECTORY
 -unset GIT_CEILING_DIRECTORIES
 -unset SHA1_FILE_DIRECTORIES
 -unset SHA1_FILE_DIRECTORY
 -unset GIT_NOTES_REF
 -unset GIT_NOTES_DISPLAY_REF
 -unset GIT_NOTES_REWRITE_REF
 -unset GIT_NOTES_REWRITE_MODE
  GIT_MERGE_VERBOSITY=5
  export GIT_MERGE_VERBOSITY
  export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
@@@ -89,6 -97,9 +89,9 @@@ esa
  _x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
  _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
  
+ # Zero SHA-1
+ _z40=0000000000000000000000000000000000000000
  # Each test should start with something like this, after copyright notices:
  #
  # test_description='Description of this test...
@@@ -230,51 -241,14 +233,51 @@@ test_set_editor () 
  }
  
  test_decode_color () {
 -      sed     -e 's/.\[1m/<WHITE>/g' \
 -              -e 's/.\[31m/<RED>/g' \
 -              -e 's/.\[32m/<GREEN>/g' \
 -              -e 's/.\[33m/<YELLOW>/g' \
 -              -e 's/.\[34m/<BLUE>/g' \
 -              -e 's/.\[35m/<MAGENTA>/g' \
 -              -e 's/.\[36m/<CYAN>/g' \
 -              -e 's/.\[m/<RESET>/g'
 +      awk '
 +              function name(n) {
 +                      if (n == 0) return "RESET";
 +                      if (n == 1) return "BOLD";
 +                      if (n == 30) return "BLACK";
 +                      if (n == 31) return "RED";
 +                      if (n == 32) return "GREEN";
 +                      if (n == 33) return "YELLOW";
 +                      if (n == 34) return "BLUE";
 +                      if (n == 35) return "MAGENTA";
 +                      if (n == 36) return "CYAN";
 +                      if (n == 37) return "WHITE";
 +                      if (n == 40) return "BLACK";
 +                      if (n == 41) return "BRED";
 +                      if (n == 42) return "BGREEN";
 +                      if (n == 43) return "BYELLOW";
 +                      if (n == 44) return "BBLUE";
 +                      if (n == 45) return "BMAGENTA";
 +                      if (n == 46) return "BCYAN";
 +                      if (n == 47) return "BWHITE";
 +              }
 +              {
 +                      while (match($0, /\033\[[0-9;]*m/) != 0) {
 +                              printf "%s<", substr($0, 1, RSTART-1);
 +                              codes = substr($0, RSTART+2, RLENGTH-3);
 +                              if (length(codes) == 0)
 +                                      printf "%s", name(0)
 +                              else {
 +                                      n = split(codes, ary, ";");
 +                                      sep = "";
 +                                      for (i = 1; i <= n; i++) {
 +                                              printf "%s%s", sep, name(ary[i]);
 +                                              sep = ";"
 +                                      }
 +                              }
 +                              printf ">";
 +                              $0 = substr($0, RSTART + RLENGTH, length($0) - RSTART - RLENGTH + 1);
 +                      }
 +                      print
 +              }
 +      '
 +}
 +
 +nul_to_q () {
 +      perl -pe 'y/\000/Q/'
  }
  
  q_to_nul () {
@@@ -297,17 -271,6 +300,17 @@@ remove_cr () 
        tr '\015' Q | sed -e 's/Q$//'
  }
  
 +# In some bourne shell implementations, the "unset" builtin returns
 +# nonzero status when a variable to be unset was not set in the first
 +# place.
 +#
 +# Use sane_unset when that should not be considered an error.
 +
 +sane_unset () {
 +      unset "$@"
 +      return 0
 +}
 +
  test_tick () {
        if test -z "${test_tick+set}"
        then
@@@ -402,15 -365,6 +405,15 @@@ test_have_prereq () 
        test $total_prereq = $ok_prereq
  }
  
 +test_declared_prereq () {
 +      case ",$test_prereq," in
 +      *,$1,*)
 +              return 0
 +              ;;
 +      esac
 +      return 1
 +}
 +
  # You are not expected to call test_ok_ and test_failure_ directly, use
  # the text_expect_* functions instead.
  
@@@ -463,17 -417,17 +466,17 @@@ test_skip () 
                        break
                esac
        done
 -      if test -z "$to_skip" && test -n "$prereq" &&
 -         ! test_have_prereq "$prereq"
 +      if test -z "$to_skip" && test -n "$test_prereq" &&
 +         ! test_have_prereq "$test_prereq"
        then
                to_skip=t
        fi
        case "$to_skip" in
        t)
                of_prereq=
 -              if test "$missing_prereq" != "$prereq"
 +              if test "$missing_prereq" != "$test_prereq"
                then
 -                      of_prereq=" of $prereq"
 +                      of_prereq=" of $test_prereq"
                fi
  
                say_color skip >&3 "skipping test: $@"
  }
  
  test_expect_failure () {
 -      test "$#" = 3 && { prereq=$1; shift; } || prereq=
 +      test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
        test "$#" = 2 ||
        error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
 +      export test_prereq
        if ! test_skip "$@"
        then
                say >&3 "checking known breakage: $2"
  }
  
  test_expect_success () {
 -      test "$#" = 3 && { prereq=$1; shift; } || prereq=
 +      test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
        test "$#" = 2 ||
        error "bug in the test script: not 2 or 3 parameters to test-expect-success"
 +      export test_prereq
        if ! test_skip "$@"
        then
                say >&3 "expecting success: $2"
        echo >&3 ""
  }
  
 -test_expect_code () {
 -      test "$#" = 4 && { prereq=$1; shift; } || prereq=
 -      test "$#" = 3 ||
 -      error "bug in the test script: not 3 or 4 parameters to test-expect-code"
 -      if ! test_skip "$@"
 -      then
 -              say >&3 "expecting exit code $1: $3"
 -              test_run_ "$3"
 -              if [ "$?" = 0 -a "$eval_ret" = "$1" ]
 -              then
 -                      test_ok_ "$2"
 -              else
 -                      test_failure_ "$@"
 -              fi
 -      fi
 -      echo >&3 ""
 -}
 -
  # test_external runs external test scripts that provide continuous
  # test output about their progress, and succeeds/fails on
  # zero/non-zero exit code.  It outputs the test output on stdout even
  # Usage: test_external description command arguments...
  # Example: test_external 'Perl API' perl ../path/to/test.pl
  test_external () {
 -      test "$#" = 4 && { prereq=$1; shift; } || prereq=
 +      test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq=
        test "$#" = 3 ||
        error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
        descr="$1"
        shift
 +      export test_prereq
        if ! test_skip "$descr" "$@"
        then
                # Announce the script to reduce confusion about the
  test_external_without_stderr () {
        # The temporary file has no (and must have no) security
        # implications.
 -      tmp="$TMPDIR"; if [ -z "$tmp" ]; then tmp=/tmp; fi
 +      tmp=${TMPDIR:-/tmp}
        stderr="$tmp/git-external-stderr.$$.tmp"
        test_external "$@" 4> "$stderr"
        [ -f "$stderr" ] || error "Internal error: $stderr disappeared."
@@@ -639,28 -608,6 +642,28 @@@ test_path_is_missing () 
        fi
  }
  
 +# test_line_count checks that a file has the number of lines it
 +# ought to. For example:
 +#
 +#     test_expect_success 'produce exactly one line of output' '
 +#             do something >output &&
 +#             test_line_count = 1 output
 +#     '
 +#
 +# is like "test $(wc -l <output) = 1" except that it passes the
 +# output through when the number of lines is wrong.
 +
 +test_line_count () {
 +      if test $# != 3
 +      then
 +              error "bug in the test script: not 3 parameters to test_line_count"
 +      elif ! test $(wc -l <"$3") "$1" "$2"
 +      then
 +              echo "test_line_count: line count for $3 !$1 $2"
 +              cat "$3"
 +              return 1
 +      fi
 +}
  
  # This is not among top-level (test_expect_success | test_expect_failure)
  # but is a prefix that can be used in the test script, like:
@@@ -714,28 -661,6 +717,28 @@@ test_might_fail () 
        return 0
  }
  
 +# Similar to test_must_fail and test_might_fail, but check that a
 +# given command exited with a given exit code. Meant to be used as:
 +#
 +#     test_expect_success 'Merge with d/f conflicts' '
 +#             test_expect_code 1 git merge "merge msg" B master
 +#     '
 +
 +test_expect_code () {
 +      want_code=$1
 +      shift
 +      "$@"
 +      exit_code=$?
 +      if test $exit_code = $want_code
 +      then
 +              echo >&2 "test_expect_code: command exited with $exit_code: $*"
 +              return 0
 +      else
 +              echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
 +              return 1
 +      fi
 +}
 +
  # test_cmp is a helper function to compare actual and expected output.
  # You can use it like:
  #
@@@ -801,14 -726,12 +804,14 @@@ test_done () 
                mkdir -p "$test_results_dir"
                test_results_path="$test_results_dir/${0%.sh}-$$.counts"
  
 -              echo "total $test_count" >> $test_results_path
 -              echo "success $test_success" >> $test_results_path
 -              echo "fixed $test_fixed" >> $test_results_path
 -              echo "broken $test_broken" >> $test_results_path
 -              echo "failed $test_failure" >> $test_results_path
 -              echo "" >> $test_results_path
 +              cat >>"$test_results_path" <<-EOF
 +              total $test_count
 +              success $test_success
 +              fixed $test_fixed
 +              broken $test_broken
 +              failed $test_failure
 +
 +              EOF
        fi
  
        if test "$test_fixed" != 0
@@@ -945,8 -868,8 +948,8 @@@ f
  GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt
  unset GIT_CONFIG
  GIT_CONFIG_NOSYSTEM=1
 -GIT_CONFIG_NOGLOBAL=1
 -export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
 +GIT_ATTR_NOSYSTEM=1
 +export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM
  
  . "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS
  
@@@ -995,14 -918,14 +998,14 @@@ rm -fr "$test" || 
        exit 1
  }
  
 +HOME="$TRASH_DIRECTORY"
 +export HOME
 +
  test_create_repo "$test"
  # Use -P to resolve symlinks in our working directory so that the cwd
  # in subprocesses like git equals our $PWD (for pathname comparisons).
  cd -P "$test" || exit 1
  
 -HOME=$(pwd)
 -export HOME
 -
  this_test=${0##*/}
  this_test=${this_test%%-*}
  for skp in $GIT_SKIP_TESTS
@@@ -1050,61 -973,17 +1053,61 @@@ case $(uname -s) i
        # no POSIX permissions
        # backslashes in pathspec are converted to '/'
        # exec does not inherit the PID
 +      test_set_prereq MINGW
 +      test_set_prereq SED_STRIPS_CR
 +      ;;
 +*CYGWIN*)
 +      test_set_prereq POSIXPERM
 +      test_set_prereq EXECKEEPSPID
 +      test_set_prereq NOT_MINGW
 +      test_set_prereq SED_STRIPS_CR
        ;;
  *)
        test_set_prereq POSIXPERM
        test_set_prereq BSLASHPSPEC
        test_set_prereq EXECKEEPSPID
 +      test_set_prereq NOT_MINGW
        ;;
  esac
  
  test -z "$NO_PERL" && test_set_prereq PERL
  test -z "$NO_PYTHON" && test_set_prereq PYTHON
  
 +# Can we rely on git's output in the C locale?
 +if test -n "$GETTEXT_POISON"
 +then
 +      GIT_GETTEXT_POISON=YesPlease
 +      export GIT_GETTEXT_POISON
 +else
 +      test_set_prereq C_LOCALE_OUTPUT
 +fi
 +
 +# Use this instead of test_cmp to compare files that contain expected and
 +# actual output from git commands that can be translated.  When running
 +# under GETTEXT_POISON this pretends that the command produced expected
 +# results.
 +test_i18ncmp () {
 +      test -n "$GETTEXT_POISON" || test_cmp "$@"
 +}
 +
 +# Use this instead of "grep expected-string actual" to see if the
 +# output from a git command that can be translated either contains an
 +# expected string, or does not contain an unwanted one.  When running
 +# under GETTEXT_POISON this pretends that the command produced expected
 +# results.
 +test_i18ngrep () {
 +      if test -n "$GETTEXT_POISON"
 +      then
 +          : # pretend success
 +      elif test "x!" = "x$1"
 +      then
 +              shift
 +              ! grep "$@"
 +      else
 +              grep "$@"
 +      fi
 +}
 +
  # test whether the filesystem supports symbolic links
  ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
  rm -f y