Merge branch 'jc/refactor-diff-stdin' into maint
authorJunio C Hamano <gitster@pobox.com>
Sun, 22 Jul 2012 20:01:22 +0000 (13:01 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 22 Jul 2012 20:01:23 +0000 (13:01 -0700)
"git diff", "git status" and anything that internally uses the
comparison machinery was utterly broken when the difference
involved a file with "-" as its name. This was due to the way "git
diff --no-index" was incorrectly bolted on to the system, making
any comparison that involves a file "-" at the root level
incorrectly read from the standard input.

* jc/refactor-diff-stdin:
diff-index.c: "git diff" has no need to read blob from the standard input
diff-index.c: unify handling of command line paths
diff-index.c: do not pretend paths are pathspecs

1  2 
diff-no-index.c
diff.c
diffcore.h
t/t7501-commit.sh
diff --combined diff-no-index.c
index beec49b5ac98c6d3ea895adcf66265f3a2fc7875,09656bca1d192f21f36ff56f2446c6bcc73442f4..7d805a06afacae7eaa36a192e3a16406ef0fb41f
@@@ -32,6 -32,13 +32,13 @@@ static int read_directory(const char *p
        return 0;
  }
  
+ /*
+  * This should be "(standard input)" or something, but it will
+  * probably expose many more breakages in the way no-index code
+  * is bolted onto the diff callchain.
+  */
+ static const char file_from_standard_input[] = "-";
  static int get_mode(const char *path, int *mode)
  {
        struct stat st;
@@@ -42,7 -49,7 +49,7 @@@
        else if (!strcasecmp(path, "nul"))
                *mode = 0;
  #endif
-       else if (!strcmp(path, "-"))
+       else if (path == file_from_standard_input)
                *mode = create_ce_mode(0666);
        else if (lstat(path, &st))
                return error("Could not access '%s'", path);
        return 0;
  }
  
+ static int populate_from_stdin(struct diff_filespec *s)
+ {
+       struct strbuf buf = STRBUF_INIT;
+       size_t size = 0;
+       if (strbuf_read(&buf, 0, 0) < 0)
+               return error("error while reading from stdin %s",
+                                    strerror(errno));
+       s->should_munmap = 0;
+       s->data = strbuf_detach(&buf, &size);
+       s->size = size;
+       s->should_free = 1;
+       s->is_stdin = 1;
+       return 0;
+ }
+ static struct diff_filespec *noindex_filespec(const char *name, int mode)
+ {
+       struct diff_filespec *s;
+       if (!name)
+               name = "/dev/null";
+       s = alloc_filespec(name);
+       fill_filespec(s, null_sha1, mode);
+       if (name == file_from_standard_input)
+               populate_from_stdin(s);
+       return s;
+ }
  static int queue_diff(struct diff_options *o,
 -              const char *name1, const char *name2)
 +                    const char *name1, const char *name2)
  {
        int mode1 = 0, mode2 = 0;
  
                return error("file/directory conflict: %s, %s", name1, name2);
  
        if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
 -              char buffer1[PATH_MAX], buffer2[PATH_MAX];
 +              struct strbuf buffer1 = STRBUF_INIT;
 +              struct strbuf buffer2 = STRBUF_INIT;
                struct string_list p1 = STRING_LIST_INIT_DUP;
                struct string_list p2 = STRING_LIST_INIT_DUP;
 -              int len1 = 0, len2 = 0, i1, i2, ret = 0;
 +              int i1, i2, ret = 0;
 +              size_t len1 = 0, len2 = 0;
  
                if (name1 && read_directory(name1, &p1))
                        return -1;
                }
  
                if (name1) {
 -                      len1 = strlen(name1);
 -                      if (len1 > 0 && name1[len1 - 1] == '/')
 -                              len1--;
 -                      memcpy(buffer1, name1, len1);
 -                      buffer1[len1++] = '/';
 +                      strbuf_addstr(&buffer1, name1);
 +                      if (buffer1.len && buffer1.buf[buffer1.len - 1] != '/')
 +                              strbuf_addch(&buffer1, '/');
 +                      len1 = buffer1.len;
                }
  
                if (name2) {
 -                      len2 = strlen(name2);
 -                      if (len2 > 0 && name2[len2 - 1] == '/')
 -                              len2--;
 -                      memcpy(buffer2, name2, len2);
 -                      buffer2[len2++] = '/';
 +                      strbuf_addstr(&buffer2, name2);
 +                      if (buffer2.len && buffer2.buf[buffer2.len - 1] != '/')
 +                              strbuf_addch(&buffer2, '/');
 +                      len2 = buffer2.len;
                }
  
                for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
                        const char *n1, *n2;
                        int comp;
  
 +                      strbuf_setlen(&buffer1, len1);
 +                      strbuf_setlen(&buffer2, len2);
 +
                        if (i1 == p1.nr)
                                comp = 1;
                        else if (i2 == p2.nr)
                                comp = -1;
                        else
 -                              comp = strcmp(p1.items[i1].string,
 -                                      p2.items[i2].string);
 +                              comp = strcmp(p1.items[i1].string, p2.items[i2].string);
  
                        if (comp > 0)
                                n1 = NULL;
                        else {
 -                              n1 = buffer1;
 -                              strncpy(buffer1 + len1, p1.items[i1++].string,
 -                                              PATH_MAX - len1);
 +                              strbuf_addstr(&buffer1, p1.items[i1++].string);
 +                              n1 = buffer1.buf;
                        }
  
                        if (comp < 0)
                                n2 = NULL;
                        else {
 -                              n2 = buffer2;
 -                              strncpy(buffer2 + len2, p2.items[i2++].string,
 -                                              PATH_MAX - len2);
 +                              strbuf_addstr(&buffer2, p2.items[i2++].string);
 +                              n2 = buffer2.buf;
                        }
  
                        ret = queue_diff(o, n1, n2);
                }
                string_list_clear(&p1, 0);
                string_list_clear(&p2, 0);
 +              strbuf_release(&buffer1);
 +              strbuf_release(&buffer2);
  
                return ret;
        } else {
                        tmp_c = name1; name1 = name2; name2 = tmp_c;
                }
  
-               if (!name1)
-                       name1 = "/dev/null";
-               if (!name2)
-                       name2 = "/dev/null";
-               d1 = alloc_filespec(name1);
-               d2 = alloc_filespec(name2);
-               fill_filespec(d1, null_sha1, mode1);
-               fill_filespec(d2, null_sha1, mode2);
+               d1 = noindex_filespec(name1, mode1);
+               d2 = noindex_filespec(name2, mode2);
                diff_queue(&diff_queued_diff, d1, d2);
                return 0;
        }
  }
  
 -static int path_outside_repo(const char *path)
 -{
 -      const char *work_tree;
 -      size_t len;
 -
 -      if (!is_absolute_path(path))
 -              return 0;
 -      work_tree = get_git_work_tree();
 -      if (!work_tree)
 -              return 1;
 -      len = strlen(work_tree);
 -      if (strncmp(path, work_tree, len) ||
 -          (path[len] != '\0' && path[len] != '/'))
 -              return 1;
 -      return 0;
 -}
 -
  void diff_no_index(struct rev_info *revs,
                   int argc, const char **argv,
                   int nongit, const char *prefix)
  {
-       int i;
+       int i, prefixlen;
        int no_index = 0;
        unsigned options = 0;
+       const char *paths[2];
  
        /* Were we asked to do --no-index explicitly? */
        for (i = 1; i < argc; i++) {
                 * a colourful "diff" replacement.
                 */
                if ((argc != i + 2) ||
 -                  (!path_outside_repo(argv[i]) &&
 -                   !path_outside_repo(argv[i+1])))
 +                  (path_inside_repo(prefix, argv[i]) &&
 +                   path_inside_repo(prefix, argv[i+1])))
                        return;
        }
        if (argc != i + 2)
                }
        }
  
-       if (prefix) {
-               int len = strlen(prefix);
-               const char *paths[3];
-               memset(paths, 0, sizeof(paths));
 -      /*
 -       * If the user asked for our exit code then don't start a
 -       * pager or we would end up reporting its exit code instead.
 -       */
 -      if (!DIFF_OPT_TST(&revs->diffopt, EXIT_WITH_STATUS))
 -              setup_pager();
--
-               for (i = 0; i < 2; i++) {
-                       const char *p = argv[argc - 2 + i];
+       prefixlen = prefix ? strlen(prefix) : 0;
+       for (i = 0; i < 2; i++) {
+               const char *p = argv[argc - 2 + i];
+               if (!strcmp(p, "-"))
                        /*
-                        * stdin should be spelled as '-'; if you have
-                        * path that is '-', spell it as ./-.
+                        * stdin should be spelled as "-"; if you have
+                        * path that is "-", spell it as "./-".
                         */
-                       p = (strcmp(p, "-")
-                            ? xstrdup(prefix_filename(prefix, len, p))
-                            : p);
-                       paths[i] = p;
-               }
-               diff_tree_setup_paths(paths, &revs->diffopt);
+                       p = file_from_standard_input;
+               else if (prefixlen)
+                       p = xstrdup(prefix_filename(prefix, prefixlen, p));
+               paths[i] = p;
        }
-       else
-               diff_tree_setup_paths(argv + argc - 2, &revs->diffopt);
        revs->diffopt.skip_stat_unmatch = 1;
        if (!revs->diffopt.output_format)
                revs->diffopt.output_format = DIFF_FORMAT_PATCH;
  
 -      DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
        DIFF_OPT_SET(&revs->diffopt, NO_INDEX);
  
        revs->max_count = -2;
        if (diff_setup_done(&revs->diffopt) < 0)
                die("diff_setup_done failed");
  
-       if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0],
-                      revs->diffopt.pathspec.raw[1]))
 +      setup_diff_pager(&revs->diffopt);
 +      DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);
 +
+       if (queue_diff(&revs->diffopt, paths[0], paths[1]))
                exit(1);
        diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
        diffcore_std(&revs->diffopt);
         * The return code for --no-index imitates diff(1):
         * 0 = no changes, 1 = changes, else error
         */
 -      exit(revs->diffopt.found_changes);
 +      exit(diff_result_code(&revs->diffopt, 0));
  }
diff --combined diff.c
index 1a594df4f45bf0bc8409e871535f7ebd34684c6c,d6fdb8e31fd24d3a6479d1c5e1f5c7fe19b03c81..208096fe461b483a0646bb583fd2158c3179823f
--- 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 int diff_stat_graph_width;
  static int diff_dirstat_permille_default = 30;
  static struct diff_options default_diff_options;
  
@@@ -138,7 -137,7 +138,7 @@@ static int git_config_rename(const cha
  int git_diff_ui_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
 -              diff_use_color_default = git_config_colorbool(var, value, -1);
 +              diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
        if (!strcmp(var, "diff.renames")) {
                diff_no_prefix = git_config_bool(var, value);
                return 0;
        }
 +      if (!strcmp(var, "diff.statgraphwidth")) {
 +              diff_stat_graph_width = git_config_int(var, value);
 +              return 0;
 +      }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
        if (!strcmp(var, "diff.wordregex"))
        if (!strcmp(var, "diff.ignoresubmodules"))
                handle_ignore_submodules_arg(&default_diff_options, value);
  
 +      if (git_color_config(var, value, cb) < 0)
 +              return -1;
 +
        return git_diff_basic_config(var, value, cb);
  }
  
@@@ -182,8 -174,11 +182,8 @@@ int git_diff_basic_config(const char *v
                return 0;
        }
  
 -      switch (userdiff_config(var, value)) {
 -              case 0: break;
 -              case -1: return -1;
 -              default: return 0;
 -      }
 +      if (userdiff_config(var, value) < 0)
 +              return -1;
  
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
        if (!prefixcmp(var, "submodule."))
                return parse_submodule_config_option(var, value);
  
 -      return git_color_default_config(var, value, cb);
 +      return git_default_config(var, value, cb);
  }
  
  static char *quote_two(const char *one, const char *two)
@@@ -588,10 -583,11 +588,10 @@@ static void emit_rewrite_diff(const cha
                              struct diff_options *o)
  {
        int lc_a, lc_b;
 -      int color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
        const char *name_a_tab, *name_b_tab;
 -      const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
 -      const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
 -      const char *reset = diff_get_color(color_diff, DIFF_RESET);
 +      const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
 +      const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
 +      const char *reset = diff_get_color(o->use_color, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        char *data_one, *data_two;
        size_two = fill_textconv(textconv_two, two, &data_two);
  
        memset(&ecbdata, 0, sizeof(ecbdata));
 -      ecbdata.color_diff = color_diff;
 +      ecbdata.color_diff = want_color(o->use_color);
        ecbdata.found_changesp = &o->found_changes;
        ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
        ecbdata.opt = o;
@@@ -989,74 -985,10 +989,74 @@@ static void diff_words_flush(struct emi
                diff_words_show(ecbdata->diff_words);
  }
  
 +static void diff_filespec_load_driver(struct diff_filespec *one)
 +{
 +      /* 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");
 +}
 +
 +static const char *userdiff_word_regex(struct diff_filespec *one)
 +{
 +      diff_filespec_load_driver(one);
 +      return one->driver->word_regex;
 +}
 +
 +static void init_diff_words_data(struct emit_callback *ecbdata,
 +                               struct diff_options *orig_opts,
 +                               struct diff_filespec *one,
 +                               struct diff_filespec *two)
 +{
 +      int i;
 +      struct diff_options *o = xmalloc(sizeof(struct diff_options));
 +      memcpy(o, orig_opts, sizeof(struct diff_options));
 +
 +      ecbdata->diff_words =
 +              xcalloc(1, sizeof(struct diff_words_data));
 +      ecbdata->diff_words->type = o->word_diff;
 +      ecbdata->diff_words->opt = o;
 +      if (!o->word_regex)
 +              o->word_regex = userdiff_word_regex(one);
 +      if (!o->word_regex)
 +              o->word_regex = userdiff_word_regex(two);
 +      if (!o->word_regex)
 +              o->word_regex = diff_word_regex_cfg;
 +      if (o->word_regex) {
 +              ecbdata->diff_words->word_regex = (regex_t *)
 +                      xmalloc(sizeof(regex_t));
 +              if (regcomp(ecbdata->diff_words->word_regex,
 +                          o->word_regex,
 +                          REG_EXTENDED | REG_NEWLINE))
 +                      die ("Invalid regular expression: %s",
 +                           o->word_regex);
 +      }
 +      for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
 +              if (o->word_diff == diff_words_styles[i].type) {
 +                      ecbdata->diff_words->style =
 +                              &diff_words_styles[i];
 +                      break;
 +              }
 +      }
 +      if (want_color(o->use_color)) {
 +              struct diff_words_style *st = ecbdata->diff_words->style;
 +              st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
 +              st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
 +              st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
 +      }
 +}
 +
  static void free_diff_words_data(struct emit_callback *ecbdata)
  {
        if (ecbdata->diff_words) {
                diff_words_flush(ecbdata);
 +              free (ecbdata->diff_words->opt);
                free (ecbdata->diff_words->minus.text.ptr);
                free (ecbdata->diff_words->minus.orig);
                free (ecbdata->diff_words->plus.text.ptr);
  
  const char *diff_get_color(int diff_use_color, enum color_diff ix)
  {
 -      if (diff_use_color)
 +      if (want_color(diff_use_color))
                return diff_colors[ix];
        return "";
  }
@@@ -1179,15 -1111,6 +1179,15 @@@ static void fn_out_consume(void *priv, 
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->plus);
                        return;
 +              } else if (!prefixcmp(line, "\\ ")) {
 +                      /*
 +                       * Eat the "no newline at eof" marker as if we
 +                       * saw a "+" or "-" line with nothing on it,
 +                       * and return without diff_words_flush() to
 +                       * defer processing. If this is the end of
 +                       * preimage, more "+" lines may come after it.
 +                       */
 +                      return;
                }
                diff_words_flush(ecbdata);
                if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
@@@ -1342,15 -1265,13 +1342,15 @@@ const char mime_boundary_leader[] = "--
  
  static int scale_linear(int it, int width, int max_change)
  {
 +      if (!it)
 +              return 0;
        /*
 -       * make sure that at least one '-' is printed if there were deletions,
 -       * and likewise for '+'.
 +       * make sure that at least one '-' or '+' is printed if
 +       * there is any change to this path. The easiest way is to
 +       * scale linearly as if the alloted width is one column shorter
 +       * than it is, and then add 1 to the result.
         */
 -      if (max_change < 2)
 -              return it;
 -      return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
 +      return 1 + (it * (width - 1) / max_change);
  }
  
  static void show_name(FILE *file,
@@@ -1390,64 -1311,14 +1390,64 @@@ static void fill_print_name(struct diff
        file->print_name = pname;
  }
  
 +int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      int ret;
 +
 +      if (!files) {
 +              assert(insertions == 0 && deletions == 0);
 +              return fputs(_(" 0 files changed\n"), fp);
 +      }
 +
 +      strbuf_addf(&sb,
 +                  Q_(" %d file changed", " %d files changed", files),
 +                  files);
 +
 +      /*
 +       * For binary diff, the caller may want to print "x files
 +       * changed" with insertions == 0 && deletions == 0.
 +       *
 +       * Not omitting "0 insertions(+), 0 deletions(-)" in this case
 +       * is probably less confusing (i.e skip over "2 files changed
 +       * but nothing about added/removed lines? Is this a bug in Git?").
 +       */
 +      if (insertions || deletions == 0) {
 +              /*
 +               * TRANSLATORS: "+" in (+) is a line addition marker;
 +               * do not translate it.
 +               */
 +              strbuf_addf(&sb,
 +                          Q_(", %d insertion(+)", ", %d insertions(+)",
 +                             insertions),
 +                          insertions);
 +      }
 +
 +      if (deletions || insertions == 0) {
 +              /*
 +               * TRANSLATORS: "-" in (-) is a line removal marker;
 +               * do not translate it.
 +               */
 +              strbuf_addf(&sb,
 +                          Q_(", %d deletion(-)", ", %d deletions(-)",
 +                             deletions),
 +                          deletions);
 +      }
 +      strbuf_addch(&sb, '\n');
 +      ret = fputs(sb.buf, fp);
 +      strbuf_release(&sb);
 +      return ret;
 +}
 +
  static void show_stats(struct diffstat_t *data, struct diff_options *options)
  {
        int i, len, add, del, adds = 0, dels = 0;
        uintmax_t max_change = 0, max_len = 0;
 -      int total_files = data->nr;
 -      int width, name_width;
 +      int total_files = data->nr, count;
 +      int width, name_width, graph_width, number_width = 0, bin_width = 0;
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
 +      int extra_shown = 0;
        struct strbuf *msg = NULL;
  
        if (data->nr == 0)
                line_prefix = msg->buf;
        }
  
 -      width = options->stat_width ? options->stat_width : 80;
 -      name_width = options->stat_name_width ? options->stat_name_width : 50;
 +      count = options->stat_count ? options->stat_count : data->nr;
  
 -      /* Sanity: give at least 5 columns to the graph,
 -       * but leave at least 10 columns for the name.
 -       */
 -      if (width < 25)
 -              width = 25;
 -      if (name_width < 10)
 -              name_width = 10;
 -      else if (width < name_width + 15)
 -              name_width = width - 15;
 -
 -      /* Find the longest filename and max number of changes */
        reset = diff_get_color_opt(options, DIFF_RESET);
        add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
        del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
  
 -      for (i = 0; i < data->nr; i++) {
 +      /*
 +       * Find the longest filename and max number of changes
 +       */
 +      for (i = 0; (i < count) && (i < data->nr); i++) {
                struct diffstat_file *file = data->files[i];
                uintmax_t change = file->added + file->deleted;
 +              if (!data->files[i]->is_renamed &&
 +                       (change == 0)) {
 +                      count++; /* not shown == room for one more */
 +                      continue;
 +              }
                fill_print_name(file);
                len = strlen(file->print_name);
                if (max_len < len)
                        max_len = len;
  
 -              if (file->is_binary || file->is_unmerged)
 +              if (file->is_unmerged) {
 +                      /* "Unmerged" is 8 characters */
 +                      bin_width = bin_width < 8 ? 8 : bin_width;
 +                      continue;
 +              }
 +              if (file->is_binary) {
 +                      /* "Bin XXX -> YYY bytes" */
 +                      int w = 14 + decimal_width(file->added)
 +                              + decimal_width(file->deleted);
 +                      bin_width = bin_width < w ? w : bin_width;
 +                      /* Display change counts aligned with "Bin" */
 +                      number_width = 3;
                        continue;
 +              }
 +
                if (max_change < change)
                        max_change = change;
        }
 +      count = i; /* min(count, data->nr) */
  
 -      /* Compute the width of the graph part;
 -       * 10 is for one blank at the beginning of the line plus
 -       * " | count " between the name and the graph.
 +      /*
 +       * We have width = stat_width or term_columns() columns total.
 +       * We want a maximum of min(max_len, stat_name_width) for the name part.
 +       * We want a maximum of min(max_change, stat_graph_width) for the +- part.
 +       * We also need 1 for " " and 4 + decimal_width(max_change)
 +       * for " | NNNN " and one the empty column at the end, altogether
 +       * 6 + decimal_width(max_change).
 +       *
 +       * If there's not enough space, we will use the smaller of
 +       * stat_name_width (if set) and 5/8*width for the filename,
 +       * and the rest for constant elements + graph part, but no more
 +       * than stat_graph_width for the graph part.
 +       * (5/8 gives 50 for filename and 30 for the constant parts + graph
 +       * for the standard terminal size).
 +       *
 +       * In other words: stat_width limits the maximum width, and
 +       * stat_name_width fixes the maximum width of the filename,
 +       * and is also used to divide available columns if there
 +       * aren't enough.
         *
 -       * From here on, name_width is the width of the name area,
 -       * and width is the width of the graph area.
 +       * Binary files are displayed with "Bin XXX -> YYY bytes"
 +       * instead of the change count and graph. This part is treated
 +       * similarly to the graph part, except that it is not
 +       * "scaled". If total width is too small to accomodate the
 +       * guaranteed minimum width of the filename part and the
 +       * separators and this message, this message will "overflow"
 +       * making the line longer than the maximum width.
         */
 -      name_width = (name_width < max_len) ? name_width : max_len;
 -      if (width < (name_width + 10) + max_change)
 -              width = width - (name_width + 10);
 +
 +      if (options->stat_width == -1)
 +              width = term_columns() - options->output_prefix_length;
        else
 -              width = max_change;
 +              width = options->stat_width ? options->stat_width : 80;
 +      number_width = decimal_width(max_change) > number_width ?
 +              decimal_width(max_change) : number_width;
  
 -      for (i = 0; i < data->nr; i++) {
 +      if (options->stat_graph_width == -1)
 +              options->stat_graph_width = diff_stat_graph_width;
 +
 +      /*
 +       * Guarantee 3/8*16==6 for the graph part
 +       * and 5/8*16==10 for the filename part
 +       */
 +      if (width < 16 + 6 + number_width)
 +              width = 16 + 6 + number_width;
 +
 +      /*
 +       * First assign sizes that are wanted, ignoring available width.
 +       * strlen("Bin XXX -> YYY bytes") == bin_width, and the part
 +       * starting from "XXX" should fit in graph_width.
 +       */
 +      graph_width = max_change + 4 > bin_width ? max_change : bin_width - 4;
 +      if (options->stat_graph_width &&
 +          options->stat_graph_width < graph_width)
 +              graph_width = options->stat_graph_width;
 +
 +      name_width = (options->stat_name_width > 0 &&
 +                    options->stat_name_width < max_len) ?
 +              options->stat_name_width : max_len;
 +
 +      /*
 +       * Adjust adjustable widths not to exceed maximum width
 +       */
 +      if (name_width + number_width + 6 + graph_width > width) {
 +              if (graph_width > width * 3/8 - number_width - 6) {
 +                      graph_width = width * 3/8 - number_width - 6;
 +                      if (graph_width < 6)
 +                              graph_width = 6;
 +              }
 +
 +              if (options->stat_graph_width &&
 +                  graph_width > options->stat_graph_width)
 +                      graph_width = options->stat_graph_width;
 +              if (name_width > width - number_width - 6 - graph_width)
 +                      name_width = width - number_width - 6 - graph_width;
 +              else
 +                      graph_width = width - number_width - 6 - name_width;
 +      }
 +
 +      /*
 +       * From here name_width is the width of the name area,
 +       * and graph_width is the width of the graph area.
 +       * max_change is used to scale graph properly.
 +       */
 +      for (i = 0; i < count; i++) {
                const char *prefix = "";
                char *name = data->files[i]->print_name;
                uintmax_t added = data->files[i]->added;
                uintmax_t deleted = data->files[i]->deleted;
                int name_len;
  
 +              if (!data->files[i]->is_renamed &&
 +                       (added + deleted == 0)) {
 +                      total_files--;
 +                      continue;
 +              }
                /*
                 * "scale" the filename
                 */
                if (data->files[i]->is_binary) {
                        fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
 -                      fprintf(options->file, "  Bin ");
 -                      fprintf(options->file, "%s%"PRIuMAX"%s",
 +                      fprintf(options->file, " %*s", number_width, "Bin");
 +                      if (!added && !deleted) {
 +                              putc('\n', options->file);
 +                              continue;
 +                      }
 +                      fprintf(options->file, " %s%"PRIuMAX"%s",
                                del_c, deleted, reset);
                        fprintf(options->file, " -> ");
                        fprintf(options->file, "%s%"PRIuMAX"%s",
                else if (data->files[i]->is_unmerged) {
                        fprintf(options->file, "%s", line_prefix);
                        show_name(options->file, prefix, name, len);
 -                      fprintf(options->file, "  Unmerged\n");
 -                      continue;
 -              }
 -              else if (!data->files[i]->is_renamed &&
 -                       (added + deleted == 0)) {
 -                      total_files--;
 +                      fprintf(options->file, " Unmerged\n");
                        continue;
                }
  
                adds += add;
                dels += del;
  
 -              if (width <= max_change) {
 -                      add = scale_linear(add, width, max_change);
 -                      del = scale_linear(del, width, max_change);
 +              if (graph_width <= max_change) {
 +                      int total = add + del;
 +
 +                      total = scale_linear(add + del, graph_width, max_change);
 +                      if (total < 2 && add && del)
 +                              /* width >= 2 due to the sanity check */
 +                              total = 2;
 +                      if (add < del) {
 +                              add = scale_linear(add, graph_width, max_change);
 +                              del = total - add;
 +                      } else {
 +                              del = scale_linear(del, graph_width, max_change);
 +                              add = total - del;
 +                      }
                }
                fprintf(options->file, "%s", line_prefix);
                show_name(options->file, prefix, name, len);
 -              fprintf(options->file, "%5"PRIuMAX"%s", added + deleted,
 -                              added + deleted ? " " : "");
 +              fprintf(options->file, " %*"PRIuMAX"%s",
 +                      number_width, added + deleted,
 +                      added + deleted ? " " : "");
                show_graph(options->file, '+', add, add_c, reset);
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
 +      for (i = count; i < data->nr; i++) {
 +              uintmax_t added = data->files[i]->added;
 +              uintmax_t deleted = data->files[i]->deleted;
 +              if (!data->files[i]->is_renamed &&
 +                       (added + deleted == 0)) {
 +                      total_files--;
 +                      continue;
 +              }
 +              adds += added;
 +              dels += deleted;
 +              if (!extra_shown)
 +                      fprintf(options->file, "%s ...\n", line_prefix);
 +              extra_shown = 1;
 +      }
        fprintf(options->file, "%s", line_prefix);
 -      fprintf(options->file,
 -             " %d files changed, %d insertions(+), %d deletions(-)\n",
 -             total_files, adds, dels);
 +      print_stat_summary(options->file, total_files, adds, dels);
  }
  
  static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
                return;
  
        for (i = 0; i < data->nr; i++) {
 -              if (!data->files[i]->is_binary &&
 -                  !data->files[i]->is_unmerged) {
 -                      int added = data->files[i]->added;
 -                      int deleted= data->files[i]->deleted;
 -                      if (!data->files[i]->is_renamed &&
 -                          (added + deleted == 0)) {
 -                              total_files--;
 -                      } else {
 -                              adds += added;
 -                              dels += deleted;
 -                      }
 +              int added = data->files[i]->added;
 +              int deleted= data->files[i]->deleted;
 +
 +              if (data->files[i]->is_unmerged)
 +                      continue;
 +              if (!data->files[i]->is_renamed && (added + deleted == 0)) {
 +                      total_files--;
 +              } else if (!data->files[i]->is_binary) { /* don't count bytes */
 +                      adds += added;
 +                      dels += deleted;
                }
        }
        if (options->output_prefix) {
                                options->output_prefix_data);
                fprintf(options->file, "%s", msg->buf);
        }
 -      fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n",
 -             total_files, adds, dels);
 +      print_stat_summary(options->file, total_files, adds, dels);
  }
  
  static void show_numstat(struct diffstat_t *data, struct diff_options *options)
@@@ -2022,10 -1786,11 +2022,10 @@@ static int is_conflict_marker(const cha
  static void checkdiff_consume(void *priv, char *line, unsigned long len)
  {
        struct checkdiff_t *data = priv;
 -      int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
        int marker_size = data->conflict_marker_size;
 -      const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
 -      const char *reset = diff_get_color(color_diff, DIFF_RESET);
 -      const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
 +      const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
 +      const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
 +      const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
        char *err;
        char *line_prefix = "";
        struct strbuf *msgbuf;
@@@ -2160,6 -1925,20 +2160,6 @@@ static void emit_binary_diff(FILE *file
        emit_binary_diff_body(file, two, one, prefix);
  }
  
 -static void diff_filespec_load_driver(struct diff_filespec *one)
 -{
 -      /* 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");
 -}
 -
  int diff_filespec_is_binary(struct diff_filespec *one)
  {
        if (one->is_binary == -1) {
@@@ -2185,6 -1964,12 +2185,6 @@@ static const struct userdiff_funcname *
        return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
  }
  
 -static const char *userdiff_word_regex(struct diff_filespec *one)
 -{
 -      diff_filespec_load_driver(one);
 -      return one->driver->word_regex;
 -}
 -
  void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
  {
        if (!options->a_prefix)
@@@ -2334,7 -2119,7 +2334,7 @@@ static void builtin_diff(const char *na
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
  
 -              if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS) || must_show_header) {
 +              if (must_show_header) {
                        fprintf(o->file, "%s", header.buf);
                        strbuf_reset(&header);
                }
                memset(&xecfg, 0, sizeof(xecfg));
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
 -              ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
 +              ecbdata.color_diff = want_color(o->use_color);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
 +              if (DIFF_OPT_TST(o, FUNCCONTEXT))
 +                      xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
                if (!diffopts)
                        xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
                else if (!prefixcmp(diffopts, "-u"))
                        xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
 -              if (o->word_diff) {
 -                      int i;
 -
 -                      ecbdata.diff_words =
 -                              xcalloc(1, sizeof(struct diff_words_data));
 -                      ecbdata.diff_words->type = o->word_diff;
 -                      ecbdata.diff_words->opt = o;
 -                      if (!o->word_regex)
 -                              o->word_regex = userdiff_word_regex(one);
 -                      if (!o->word_regex)
 -                              o->word_regex = userdiff_word_regex(two);
 -                      if (!o->word_regex)
 -                              o->word_regex = diff_word_regex_cfg;
 -                      if (o->word_regex) {
 -                              ecbdata.diff_words->word_regex = (regex_t *)
 -                                      xmalloc(sizeof(regex_t));
 -                              if (regcomp(ecbdata.diff_words->word_regex,
 -                                              o->word_regex,
 -                                              REG_EXTENDED | REG_NEWLINE))
 -                                      die ("Invalid regular expression: %s",
 -                                                      o->word_regex);
 -                      }
 -                      for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
 -                              if (o->word_diff == diff_words_styles[i].type) {
 -                                      ecbdata.diff_words->style =
 -                                              &diff_words_styles[i];
 -                                      break;
 -                              }
 -                      }
 -                      if (DIFF_OPT_TST(o, COLOR_DIFF)) {
 -                              struct diff_words_style *st = ecbdata.diff_words->style;
 -                              st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
 -                              st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
 -                              st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
 -                      }
 -              }
 +              if (o->word_diff)
 +                      init_diff_words_data(&ecbdata, o, one, two);
                xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
                              &xpp, &xecfg);
                if (o->word_diff)
@@@ -2402,7 -2219,6 +2402,7 @@@ static void builtin_diffstat(const cha
  {
        mmfile_t mf1, mf2;
        struct diffstat_file *data;
 +      int same_contents;
  
        data = diffstat_add(diffstat, name_a, name_b);
  
                return;
        }
  
 +      same_contents = !hashcmp(one->sha1, two->sha1);
 +
        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);
 +              if (same_contents) {
 +                      data->added = 0;
 +                      data->deleted = 0;
 +              } else {
 +                      data->added = diff_filespec_size(two);
 +                      data->deleted = diff_filespec_size(one);
 +              }
        }
  
        else if (complete_rewrite) {
                data->added = count_lines(two->data, two->size);
        }
  
 -      else {
 +      else if (!same_contents) {
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = o->xdl_opts;
 +              xecfg.ctxlen = o->context;
 +              xecfg.interhunkctxlen = o->interhunkcontext;
                xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
                              &xpp, &xecfg);
        }
@@@ -2619,22 -2426,6 +2619,6 @@@ static int reuse_worktree_file(const ch
        return 0;
  }
  
- static int populate_from_stdin(struct diff_filespec *s)
- {
-       struct strbuf buf = STRBUF_INIT;
-       size_t size = 0;
-       if (strbuf_read(&buf, 0, 0) < 0)
-               return error("error while reading from stdin %s",
-                                    strerror(errno));
-       s->should_munmap = 0;
-       s->data = strbuf_detach(&buf, &size);
-       s->size = size;
-       s->should_free = 1;
-       return 0;
- }
  static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
  {
        int len;
@@@ -2684,9 -2475,6 +2668,6 @@@ int diff_populate_filespec(struct diff_
                struct stat st;
                int fd;
  
-               if (!strcmp(s->path, "-"))
-                       return populate_from_stdin(s);
                if (lstat(s->path, &st) < 0) {
                        if (errno == ENOENT) {
                        err_empty:
@@@ -3026,7 -2814,7 +3007,7 @@@ static void run_diff_cmd(const char *pg
                 */
                fill_metainfo(msg, name, other, one, two, o, p,
                              &must_show_header,
 -                            DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
 +                            want_color(o->use_color) && !pgm);
                xfrm_msg = msg->len ? msg->buf : NULL;
        }
  
@@@ -3048,7 -2836,7 +3029,7 @@@ static void diff_fill_sha1_info(struct 
        if (DIFF_FILE_VALID(one)) {
                if (!one->sha1_valid) {
                        struct stat st;
-                       if (!strcmp(one->path, "-")) {
+                       if (one->is_stdin) {
                                hashcpy(one->sha1, null_sha1);
                                return;
                        }
@@@ -3189,11 -2977,11 +3170,11 @@@ void diff_setup(struct diff_options *op
        options->rename_limit = -1;
        options->dirstat_permille = diff_dirstat_permille_default;
        options->context = 3;
 +      DIFF_OPT_SET(options, RENAME_EMPTY);
  
        options->change = diff_change;
        options->add_remove = diff_addremove;
 -      if (diff_use_color_default > 0)
 -              DIFF_OPT_SET(options, COLOR_DIFF);
 +      options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
  
        if (diff_no_prefix) {
@@@ -3401,8 -3189,6 +3382,8 @@@ static int stat_opt(struct diff_option
        char *end;
        int width = options->stat_width;
        int name_width = options->stat_name_width;
 +      int graph_width = options->stat_graph_width;
 +      int count = options->stat_count;
        int argcount = 1;
  
        arg += strlen("--stat");
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
 +              } else if (!prefixcmp(arg, "-graph-width")) {
 +                      arg += strlen("-graph-width");
 +                      if (*arg == '=')
 +                              graph_width = strtoul(arg + 1, &end, 10);
 +                      else if (!*arg && !av[1])
 +                              die("Option '--stat-graph-width' requires a value");
 +                      else if (!*arg) {
 +                              graph_width = strtoul(av[1], &end, 10);
 +                              argcount = 2;
 +                      }
 +              } else if (!prefixcmp(arg, "-count")) {
 +                      arg += strlen("-count");
 +                      if (*arg == '=')
 +                              count = strtoul(arg + 1, &end, 10);
 +                      else if (!*arg && !av[1])
 +                              die("Option '--stat-count' requires a value");
 +                      else if (!*arg) {
 +                              count = strtoul(av[1], &end, 10);
 +                              argcount = 2;
 +                      }
                }
                break;
        case '=':
                width = strtoul(arg+1, &end, 10);
                if (*end == ',')
                        name_width = strtoul(end+1, &end, 10);
 +              if (*end == ',')
 +                      count = strtoul(end+1, &end, 10);
        }
  
        /* Important! This checks all the error cases! */
                return 0;
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
 +      options->stat_graph_width = graph_width;
        options->stat_width = width;
 +      options->stat_count = count;
        return argcount;
  }
  
@@@ -3532,7 -3294,7 +3513,7 @@@ int diff_opt_parse(struct diff_options 
        else if (!strcmp(arg, "-s"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
        else if (!prefixcmp(arg, "--stat"))
 -              /* --stat, --stat-width, or --stat-name-width */
 +              /* --stat, --stat-width, --stat-name-width, or --stat-count */
                return stat_opt(options, av);
  
        /* renames options */
        }
        else if (!strcmp(arg, "--no-renames"))
                options->detect_rename = 0;
 +      else if (!strcmp(arg, "--rename-empty"))
 +              DIFF_OPT_SET(options, RENAME_EMPTY);
 +      else if (!strcmp(arg, "--no-rename-empty"))
 +              DIFF_OPT_CLR(options, RENAME_EMPTY);
        else if (!strcmp(arg, "--relative"))
                DIFF_OPT_SET(options, RELATIVE_NAME);
        else if (!prefixcmp(arg, "--relative=")) {
        }
  
        /* xdiff options */
 +      else if (!strcmp(arg, "--minimal"))
 +              DIFF_XDL_SET(options, NEED_MINIMAL);
 +      else if (!strcmp(arg, "--no-minimal"))
 +              DIFF_XDL_CLR(options, NEED_MINIMAL);
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE);
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
        else if (!strcmp(arg, "--ignore-space-at-eol"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
        else if (!strcmp(arg, "--patience"))
 -              DIFF_XDL_SET(options, PATIENCE_DIFF);
 +              options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
 +      else if (!strcmp(arg, "--histogram"))
 +              options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF);
  
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
        else if (!strcmp(arg, "--color"))
 -              DIFF_OPT_SET(options, COLOR_DIFF);
 +              options->use_color = 1;
        else if (!prefixcmp(arg, "--color=")) {
 -              int value = git_config_colorbool(NULL, arg+8, -1);
 -              if (value == 0)
 -                      DIFF_OPT_CLR(options, COLOR_DIFF);
 -              else if (value > 0)
 -                      DIFF_OPT_SET(options, COLOR_DIFF);
 -              else
 +              int value = git_config_colorbool(NULL, arg+8);
 +              if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
 +              options->use_color = value;
        }
        else if (!strcmp(arg, "--no-color"))
 -              DIFF_OPT_CLR(options, COLOR_DIFF);
 +              options->use_color = 0;
        else if (!strcmp(arg, "--color-words")) {
 -              DIFF_OPT_SET(options, COLOR_DIFF);
 +              options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
        else if (!prefixcmp(arg, "--color-words=")) {
 -              DIFF_OPT_SET(options, COLOR_DIFF);
 +              options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
                options->word_regex = arg + 14;
        }
                if (!strcmp(type, "plain"))
                        options->word_diff = DIFF_WORDS_PLAIN;
                else if (!strcmp(type, "color")) {
 -                      DIFF_OPT_SET(options, COLOR_DIFF);
 +                      options->use_color = 1;
                        options->word_diff = DIFF_WORDS_COLOR;
                }
                else if (!strcmp(type, "porcelain"))
        else if (opt_arg(arg, '\0', "inter-hunk-context",
                         &options->interhunkcontext))
                ;
 +      else if (!strcmp(arg, "-W"))
 +              DIFF_OPT_SET(options, FUNCCONTEXT);
 +      else if (!strcmp(arg, "--function-context"))
 +              DIFF_OPT_SET(options, FUNCCONTEXT);
 +      else if (!strcmp(arg, "--no-function-context"))
 +              DIFF_OPT_CLR(options, FUNCCONTEXT);
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
                options->file = fopen(optarg, "w");
                if (!options->file)
@@@ -4457,12 -4206,6 +4438,12 @@@ void diff_flush(struct diff_options *op
  
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
 +                      if (options->output_prefix) {
 +                              struct strbuf *msg = NULL;
 +                              msg = options->output_prefix(options,
 +                                      options->output_prefix_data);
 +                              fwrite(msg->buf, msg->len, 1, stdout);
 +                      }
                        putc(options->line_termination, options->file);
                        if (options->stat_sep) {
                                /* attach patch instead of inline */
diff --combined diffcore.h
index 8f32b824cdc1bac1f094d743f3bee9f32890edca,ae43149620ee269ddddfcec06d618dcc76c52895..be0739c5c401c059a0d3030acbc99a7744f17065
@@@ -43,9 -43,10 +43,10 @@@ struct diff_filespec 
        unsigned should_free : 1; /* data should be free()'ed */
        unsigned should_munmap : 1; /* data should be munmap()'ed */
        unsigned dirty_submodule : 2;  /* For submodules: its work tree is dirty */
+       unsigned is_stdin : 1;
  #define DIRTY_SUBMODULE_UNTRACKED 1
  #define DIRTY_SUBMODULE_MODIFIED  2
 -
 +      unsigned has_more_entries : 1; /* only appear in combined diff */
        struct userdiff_driver *driver;
        /* data should be considered "binary"; -1 means "don't know yet" */
        int is_binary;
diff --combined t/t7501-commit.sh
index b20ca0eace9dd8f9a11227ebfb932e0446278ea1,e6cf6ee9e76ec4f2f56dd2c66460756dcc02558d..676da85b52cfd635dc2e08385b568260341f4d04
@@@ -8,41 -8,39 +8,41 @@@
  
  test_description='git commit'
  . ./test-lib.sh
 +. "$TEST_DIRECTORY/diff-lib.sh"
  
 -test_tick
 +author='The Real Author <someguy@his.email.org>'
  
 -test_expect_success \
 -      "initial status" \
 -      "echo 'bongo bongo' >file &&
 -       git add file"
 +test_tick
  
 -test_expect_success "Constructing initial commit" '
 +test_expect_success 'initial status' '
 +      echo bongo bongo >file &&
 +      git add file &&
        git status >actual &&
        test_i18ngrep "Initial commit" actual
  '
  
 -test_expect_success \
 -      "fail initial amend" \
 -      "test_must_fail git commit --amend"
 +test_expect_success 'fail initial amend' '
 +      test_must_fail git commit --amend
 +'
  
 -test_expect_success \
 -      "initial commit" \
 -      "git commit -m initial"
 +test_expect_success 'setup: initial commit' '
 +      git commit -m initial
 +'
  
 -test_expect_success \
 -      "invalid options 1" \
 -      "test_must_fail git commit -m foo -m bar -F file"
 +test_expect_success '-m and -F do not mix' '
 +      git checkout HEAD file && echo >>file && git add file &&
 +      test_must_fail git commit -m foo -m bar -F file
 +'
  
 -test_expect_success \
 -      "invalid options 2" \
 -      "test_must_fail git commit -C HEAD -m illegal"
 +test_expect_success '-m and -C do not mix' '
 +      git checkout HEAD file && echo >>file && git add file &&
 +      test_must_fail git commit -C HEAD -m illegal
 +'
  
 -test_expect_success \
 -      "using paths with -a" \
 -      "echo King of the bongo >file &&
 -      test_must_fail git commit -m foo -a file"
 +test_expect_success 'paths and -a do not mix' '
 +      echo King of the bongo >file &&
 +      test_must_fail git commit -m foo -a file
 +'
  
  test_expect_success PERL 'can use paths with --interactive' '
        echo bong-o-bong >file &&
        git reset --hard HEAD^
  '
  
 -test_expect_success \
 -      "using invalid commit with -C" \
 -      "test_must_fail git commit -C bogus"
 +test_expect_success 'using invalid commit with -C' '
 +      test_must_fail git commit -C bogus
 +'
  
 -test_expect_success \
 -      "testing nothing to commit" \
 -      "test_must_fail git commit -m initial"
 +test_expect_success 'nothing to commit' '
 +      test_must_fail git commit -m initial
 +'
 +
 +test_expect_success 'setup: non-initial commit' '
 +      echo bongo bongo bongo >file &&
 +      git commit -m next -a
 +'
  
 -test_expect_success \
 -      "next commit" \
 -      "echo 'bongo bongo bongo' >file \
 -       git commit -m next -a"
 +test_expect_success 'commit message from non-existing file' '
 +      echo more bongo: bongo bongo bongo bongo >file &&
 +      test_must_fail git commit -F gah -a
 +'
  
 -test_expect_success \
 -      "commit message from non-existing file" \
 -      "echo 'more bongo: bongo bongo bongo bongo' >file && \
 -       test_must_fail git commit -F gah -a"
 +test_expect_success 'empty commit message' '
 +      # Empty except stray tabs and spaces on a few lines.
 +      sed -e "s/@//g" >msg <<-\EOF &&
 +              @               @
 +              @@
 +              @  @
 +              @Signed-off-by: hula@
 +      EOF
 +      test_must_fail git commit -F msg -a
 +'
  
 -# Empty except stray tabs and spaces on a few lines.
 -sed -e 's/@$//' >msg <<EOF
 -              @
 +test_expect_success 'template "emptyness" check does not kick in with -F' '
 +      git checkout HEAD file && echo >>file && git add file &&
 +      git commit -t file -F file
 +'
  
 -  @
 -Signed-off-by: hula
 -EOF
 -test_expect_success \
 -      "empty commit message" \
 -      "test_must_fail git commit -F msg -a"
 +test_expect_success 'template "emptyness" check' '
 +      git checkout HEAD file && echo >>file && git add file &&
 +      test_must_fail git commit -t file 2>err &&
 +      test_i18ngrep "did not edit" err
 +'
  
 -test_expect_success \
 -      "commit message from file" \
 -      "echo 'this is the commit message, coming from a file' >msg && \
 -       git commit -F msg -a"
 +test_expect_success 'setup: commit message from file' '
 +      git checkout HEAD file && echo >>file && git add file &&
 +      echo this is the commit message, coming from a file >msg &&
 +      git commit -F msg -a
 +'
  
 -cat >editor <<\EOF
 -#!/bin/sh
 -sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
 -mv "$1-" "$1"
 -EOF
 -chmod 755 editor
 +test_expect_success 'amend commit' '
 +      cat >editor <<-\EOF &&
 +      #!/bin/sh
 +      sed -e "s/a file/an amend commit/g" < "$1" > "$1-"
 +      mv "$1-" "$1"
 +      EOF
 +      chmod 755 editor &&
 +      EDITOR=./editor git commit --amend
 +'
  
 -test_expect_success \
 -      "amend commit" \
 -      "EDITOR=./editor git commit --amend"
 +test_expect_success 'set up editor' '
 +      cat >editor <<-\EOF &&
 +      #!/bin/sh
 +      sed -e "s/unamended/amended/g" <"$1" >"$1-"
 +      mv "$1-" "$1"
 +      EOF
 +      chmod 755 editor
 +'
  
 -test_expect_success \
 -      "passing -m and -F" \
 -      "echo 'enough with the bongos' >file && \
 -       test_must_fail git commit -F msg -m amending ."
 +test_expect_success 'amend without launching editor' '
 +      echo unamended >expect &&
 +      git commit --allow-empty -m "unamended" &&
 +      echo needs more bongo >file &&
 +      git add file &&
 +      EDITOR=./editor git commit --no-edit --amend &&
 +      git diff --exit-code HEAD -- file &&
 +      git diff-tree -s --format=%s HEAD >msg &&
 +      test_cmp expect msg
 +'
  
 -test_expect_success \
 -      "using message from other commit" \
 -      "git commit -C HEAD^ ."
 +test_expect_success '--amend --edit' '
 +      echo amended >expect &&
 +      git commit --allow-empty -m "unamended" &&
 +      echo bongo again >file &&
 +      git add file &&
 +      EDITOR=./editor git commit --edit --amend &&
 +      git diff-tree -s --format=%s HEAD >msg &&
 +      test_cmp expect msg
 +'
  
 -cat >editor <<\EOF
 -#!/bin/sh
 -sed -e "s/amend/older/g"  < "$1" > "$1-"
 -mv "$1-" "$1"
 -EOF
 -chmod 755 editor
 -
 -test_expect_success \
 -      "editing message from other commit" \
 -      "echo 'hula hula' >file && \
 -       EDITOR=./editor git commit -c HEAD^ -a"
 -
 -test_expect_success \
 -      "message from stdin" \
 -      "echo 'silly new contents' >file && \
 -       echo commit message from stdin | git commit -F - -a"
 -
 -test_expect_success \
 -      "overriding author from command line" \
 -      "echo 'gak' >file && \
 -       git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a >output 2>&1"
 -
 -test_expect_success \
 -      "commit --author output mentions author" \
 -      "grep Rubber.Duck output"
 -
 -test_expect_success PERL \
 -      "interactive add" \
 -      "echo 7 | git commit --interactive | grep 'What now'"
 -
 -test_expect_success PERL \
 -      "commit --interactive doesn't change index if editor aborts" \
 -      "echo zoo >file &&
 +test_expect_success '-m --edit' '
 +      echo amended >expect &&
 +      git commit --allow-empty -m buffer &&
 +      echo bongo bongo >file &&
 +      git add file &&
 +      EDITOR=./editor git commit -m unamended --edit &&
 +      git diff-tree -s  --format=%s HEAD >msg &&
 +      test_cmp expect msg
 +'
 +
 +test_expect_success '-m and -F do not mix' '
 +      echo enough with the bongos >file &&
 +      test_must_fail git commit -F msg -m amending .
 +'
 +
 +test_expect_success 'using message from other commit' '
 +      git commit -C HEAD^ .
 +'
 +
 +test_expect_success 'editing message from other commit' '
 +      cat >editor <<-\EOF &&
 +      #!/bin/sh
 +      sed -e "s/amend/older/g"  < "$1" > "$1-"
 +      mv "$1-" "$1"
 +      EOF
 +      chmod 755 editor &&
 +      echo hula hula >file &&
 +      EDITOR=./editor git commit -c HEAD^ -a
 +'
 +
 +test_expect_success 'message from stdin' '
 +      echo silly new contents >file &&
 +      echo commit message from stdin |
 +      git commit -F - -a
 +'
 +
 +test_expect_success 'overriding author from command line' '
 +      echo gak >file &&
 +      git commit -m author \
 +              --author "Rubber Duck <rduck@convoy.org>" -a >output 2>&1 &&
 +      grep Rubber.Duck output
 +'
 +
 +test_expect_success PERL 'interactive add' '
 +      echo 7 |
 +      git commit --interactive |
 +      grep "What now"
 +'
 +
 +test_expect_success PERL "commit --interactive doesn't change index if editor aborts" '
 +      echo zoo >file &&
        test_must_fail git diff --exit-code >diff1 &&
 -      (echo u ; echo '*' ; echo q) |
 -      (EDITOR=: && export EDITOR &&
 -       test_must_fail git commit --interactive) &&
 +      (echo u ; echo "*" ; echo q) |
 +      (
 +              EDITOR=: &&
 +              export EDITOR &&
 +              test_must_fail git commit --interactive
 +      ) &&
        git diff >diff2 &&
 -      test_cmp diff1 diff2"
 -
 -test_expect_success \
 -      "showing committed revisions" \
 -      "git rev-list HEAD >current"
 +      compare_diff_patch diff1 diff2
 +'
  
 -cat >editor <<\EOF
 -#!/bin/sh
 -sed -e "s/good/bad/g" < "$1" > "$1-"
 -mv "$1-" "$1"
 -EOF
 -chmod 755 editor
 -
 -cat >msg <<EOF
 -A good commit message.
 -EOF
 -
 -test_expect_success \
 -      'editor not invoked if -F is given' '
 -       echo "moo" >file &&
 -       EDITOR=./editor git commit -a -F msg &&
 -       git show -s --pretty=format:"%s" | grep -q good &&
 -       echo "quack" >file &&
 -       echo "Another good message." | EDITOR=./editor git commit -a -F - &&
 -       git show -s --pretty=format:"%s" | grep -q good
 -       '
 -# We could just check the head sha1, but checking each commit makes it
 -# easier to isolate bugs.
 -
 -cat >expected <<\EOF
 -72c0dc9855b0c9dadcbfd5a31cab072e0cb774ca
 -9b88fc14ce6b32e3d9ee021531a54f18a5cf38a2
 -3536bbb352c3a1ef9a420f5b4242d48578b92aa7
 -d381ac431806e53f3dd7ac2f1ae0534f36d738b9
 -4fd44095ad6334f3ef72e4c5ec8ddf108174b54a
 -402702b49136e7587daa9280e91e4bb7cb2179f7
 -EOF
 -
 -test_expect_success \
 -    'validate git rev-list output.' \
 -    'test_cmp expected current'
 +test_expect_success 'editor not invoked if -F is given' '
 +      cat >editor <<-\EOF &&
 +      #!/bin/sh
 +      sed -e s/good/bad/g <"$1" >"$1-"
 +      mv "$1-" "$1"
 +      EOF
 +      chmod 755 editor &&
 +
 +      echo A good commit message. >msg &&
 +      echo moo >file &&
 +
 +      EDITOR=./editor git commit -a -F msg &&
 +      git show -s --pretty=format:%s >subject &&
 +      grep -q good subject &&
 +
 +      echo quack >file &&
 +      echo Another good message. |
 +      EDITOR=./editor git commit -a -F - &&
 +      git show -s --pretty=format:%s >subject &&
 +      grep -q good subject
 +'
  
  test_expect_success 'partial commit that involves removal (1)' '
  
@@@ -254,6 -216,7 +254,6 @@@ test_expect_success 'partial commit tha
  
  '
  
 -author="The Real Author <someguy@his.email.org>"
  test_expect_success 'amend commit to fix author' '
  
        oldtick=$GIT_AUTHOR_DATE &&
@@@ -382,6 -345,7 +382,6 @@@ test_expect_success 'multiple -m' 
  
  '
  
 -author="The Real Author <someguy@his.email.org>"
  test_expect_success 'amend commit to fix author' '
  
        oldtick=$GIT_AUTHOR_DATE &&
@@@ -408,8 -372,15 +408,8 @@@ test_expect_success 'git commit <file> 
  
  test_expect_success 'same tree (single parent)' '
  
 -      git reset --hard
 -
 -      if git commit -m empty
 -      then
 -              echo oops -- should have complained
 -              false
 -      else
 -              : happy
 -      fi
 +      git reset --hard &&
 +      test_must_fail git commit -m empty
  
  '
  
@@@ -487,4 -458,16 +487,16 @@@ test_expect_success 'amend can copy not
  
  '
  
+ test_expect_success 'commit a file whose name is a dash' '
+       git reset --hard &&
+       for i in 1 2 3 4 5
+       do
+               echo $i
+       done >./- &&
+       git add ./- &&
+       test_tick &&
+       git commit -m "add dash" >output </dev/null &&
+       test_i18ngrep " changed, 5 insertions" output
+ '
  test_done