Merge branch 'jk/log-graph-name-only'
authorJunio C Hamano <gitster@pobox.com>
Fri, 10 Feb 2017 20:52:26 +0000 (12:52 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 10 Feb 2017 20:52:27 +0000 (12:52 -0800)
"git log --graph" did not work well with "--name-only", even though
other forms of "diff" output were handled correctly.

* jk/log-graph-name-only:
diff: print line prefix for --name-only output

1  2 
diff.c
t/t4202-log.sh
diff --combined diff.c
index d91a344908abe7335eaffef4157fc59fefdf3815,1205bac3fff5968360d3fe6bdfe9e4a51ab0286c..a79f3408da44a085f97d5cc1b29c200a2e1defbf
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -18,7 -18,6 +18,7 @@@
  #include "ll-merge.h"
  #include "string-list.h"
  #include "argv-array.h"
 +#include "graph.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
  #endif
  
  static int diff_detect_rename_default;
 -static int diff_compaction_heuristic; /* experimental */
 +static int diff_indent_heuristic; /* experimental */
  static int diff_rename_limit_default = 400;
  static int diff_suppress_blank_empty;
  static int diff_use_color_default = -1;
  static int diff_context_default = 3;
 +static int diff_interhunk_context_default;
  static const char *diff_word_regex_cfg;
  static const char *external_diff_cmd_cfg;
  static const char *diff_order_file_cfg;
@@@ -43,7 -41,6 +43,7 @@@ static int diff_stat_graph_width
  static int diff_dirstat_permille_default = 30;
  static struct diff_options default_diff_options;
  static long diff_algorithm;
 +static unsigned ws_error_highlight_default = WSEH_NEW;
  
  static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
        GIT_COLOR_NORMAL,       /* FUNCINFO */
  };
  
 +static NORETURN void die_want_option(const char *option_name)
 +{
 +      die(_("option '%s' requires a value"), option_name);
 +}
 +
  static int parse_diff_color_slot(const char *var)
  {
        if (!strcasecmp(var, "context") || !strcasecmp(var, "plain"))
@@@ -139,11 -131,9 +139,11 @@@ static int parse_dirstat_params(struct 
  static int parse_submodule_params(struct diff_options *options, const char *value)
  {
        if (!strcmp(value, "log"))
 -              DIFF_OPT_SET(options, SUBMODULE_LOG);
 +              options->submodule_format = DIFF_SUBMODULE_LOG;
        else if (!strcmp(value, "short"))
 -              DIFF_OPT_CLR(options, SUBMODULE_LOG);
 +              options->submodule_format = DIFF_SUBMODULE_SHORT;
 +      else if (!strcmp(value, "diff"))
 +              options->submodule_format = DIFF_SUBMODULE_INLINE_DIFF;
        else
                return -1;
        return 0;
@@@ -173,43 -163,6 +173,43 @@@ long parse_algorithm_value(const char *
        return -1;
  }
  
 +static int parse_one_token(const char **arg, const char *token)
 +{
 +      const char *rest;
 +      if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
 +              *arg = rest;
 +              return 1;
 +      }
 +      return 0;
 +}
 +
 +static int parse_ws_error_highlight(const char *arg)
 +{
 +      const char *orig_arg = arg;
 +      unsigned val = 0;
 +
 +      while (*arg) {
 +              if (parse_one_token(&arg, "none"))
 +                      val = 0;
 +              else if (parse_one_token(&arg, "default"))
 +                      val = WSEH_NEW;
 +              else if (parse_one_token(&arg, "all"))
 +                      val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
 +              else if (parse_one_token(&arg, "new"))
 +                      val |= WSEH_NEW;
 +              else if (parse_one_token(&arg, "old"))
 +                      val |= WSEH_OLD;
 +              else if (parse_one_token(&arg, "context"))
 +                      val |= WSEH_CONTEXT;
 +              else {
 +                      return -1 - (int)(arg - orig_arg);
 +              }
 +              if (*arg)
 +                      arg++;
 +      }
 +      return val;
 +}
 +
  /*
   * These are to give UI layer defaults.
   * The core-level commands such as git-diff-files should
@@@ -221,13 -174,6 +221,13 @@@ void init_diff_ui_defaults(void
        diff_detect_rename_default = 1;
  }
  
 +int git_diff_heuristic_config(const char *var, const char *value, void *cb)
 +{
 +      if (!strcmp(var, "diff.indentheuristic"))
 +              diff_indent_heuristic = git_config_bool(var, value);
 +      return 0;
 +}
 +
  int git_diff_ui_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                        return -1;
                return 0;
        }
 -      if (!strcmp(var, "diff.renames")) {
 -              diff_detect_rename_default = git_config_rename(var, value);
 +      if (!strcmp(var, "diff.interhunkcontext")) {
 +              diff_interhunk_context_default = git_config_int(var, value);
 +              if (diff_interhunk_context_default < 0)
 +                      return -1;
                return 0;
        }
 -      if (!strcmp(var, "diff.compactionheuristic")) {
 -              diff_compaction_heuristic = git_config_bool(var, value);
 +      if (!strcmp(var, "diff.renames")) {
 +              diff_detect_rename_default = git_config_rename(var, value);
                return 0;
        }
        if (!strcmp(var, "diff.autorefreshindex")) {
                return 0;
        }
  
 +      if (git_diff_heuristic_config(var, value, cb) < 0)
 +              return -1;
 +
 +      if (!strcmp(var, "diff.wserrorhighlight")) {
 +              int val = parse_ws_error_highlight(value);
 +              if (val < 0)
 +                      return -1;
 +              ws_error_highlight_default = val;
 +              return 0;
 +      }
 +
        if (git_color_config(var, value, cb) < 0)
                return -1;
  
@@@ -421,6 -354,7 +421,6 @@@ struct emit_callback 
        const char **label_path;
        struct diff_words_data *diff_words;
        struct diff_options *opt;
 -      int *found_changesp;
        struct strbuf *header;
  };
  
@@@ -788,6 -722,7 +788,6 @@@ static void emit_rewrite_diff(const cha
  
        memset(&ecbdata, 0, sizeof(ecbdata));
        ecbdata.color_diff = want_color(o->use_color);
 -      ecbdata.found_changesp = &o->found_changes;
        ecbdata.ws_rule = whitespace_rule(name_b);
        ecbdata.opt = o;
        if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
@@@ -1016,8 -951,7 +1016,8 @@@ static int find_word_boundaries(mmfile_
  {
        if (word_regex && *begin < buffer->size) {
                regmatch_t match[1];
 -              if (!regexec(word_regex, buffer->ptr + *begin, 1, match, 0)) {
 +              if (!regexec_buf(word_regex, buffer->ptr + *begin,
 +                               buffer->size - *begin, 1, match, 0)) {
                        char *p = memchr(buffer->ptr + *begin + match[0].rm_so,
                                        '\n', match[0].rm_eo - match[0].rm_so);
                        *end = p ? p - buffer->ptr : match[0].rm_eo + *begin;
@@@ -1282,13 -1216,12 +1282,13 @@@ static void fn_out_consume(void *priv, 
        struct diff_options *o = ecbdata->opt;
        const char *line_prefix = diff_line_prefix(o);
  
 +      o->found_changes = 1;
 +
        if (ecbdata->header) {
 -              fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf);
 +              fprintf(o->file, "%s", ecbdata->header->buf);
                strbuf_reset(ecbdata->header);
                ecbdata->header = NULL;
        }
 -      *(ecbdata->found_changesp) = 1;
  
        if (ecbdata->label_path[0]) {
                const char *name_a_tab, *name_b_tab;
                name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : "";
                name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
  
 -              fprintf(ecbdata->opt->file, "%s%s--- %s%s%s\n",
 +              fprintf(o->file, "%s%s--- %s%s%s\n",
                        line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab);
 -              fprintf(ecbdata->opt->file, "%s%s+++ %s%s%s\n",
 +              fprintf(o->file, "%s%s+++ %s%s%s\n",
                        line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab);
                ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
        }
                find_lno(line, ecbdata);
                emit_hunk_header(ecbdata, line, len);
                if (line[len-1] != '\n')
 -                      putc('\n', ecbdata->opt->file);
 -              return;
 -      }
 -
 -      if (len < 1) {
 -              emit_line(ecbdata->opt, reset, reset, line, len);
 -              if (ecbdata->diff_words
 -                  && ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN)
 -                      fputs("~\n", ecbdata->opt->file);
 +                      putc('\n', o->file);
                return;
        }
  
                }
                diff_words_flush(ecbdata);
                if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
 -                      emit_line(ecbdata->opt, context, reset, line, len);
 -                      fputs("~\n", ecbdata->opt->file);
 +                      emit_line(o, context, reset, line, len);
 +                      fputs("~\n", o->file);
                } else {
                        /*
                         * Skip the prefix character, if any.  With
                              line++;
                              len--;
                        }
 -                      emit_line(ecbdata->opt, context, reset, line, len);
 +                      emit_line(o, context, reset, line, len);
                }
                return;
        }
        default:
                /* incomplete line at the end */
                ecbdata->lno_in_preimage++;
 -              emit_line(ecbdata->opt,
 -                        diff_get_color(ecbdata->color_diff, DIFF_CONTEXT),
 +              emit_line(o, diff_get_color(ecbdata->color_diff, DIFF_CONTEXT),
                          reset, line, len);
                break;
        }
@@@ -1683,7 -1625,7 +1683,7 @@@ static void show_stats(struct diffstat_
         */
  
        if (options->stat_width == -1)
 -              width = term_columns() - options->output_prefix_length;
 +              width = term_columns() - strlen(line_prefix);
        else
                width = options->stat_width ? options->stat_width : 80;
        number_width = decimal_width(max_change) > number_width ?
@@@ -1991,8 -1933,8 +1991,8 @@@ static void show_dirstat(struct diff_op
  
                name = p->two->path ? p->two->path : p->one->path;
  
 -              if (p->one->sha1_valid && p->two->sha1_valid)
 -                      content_changed = hashcmp(p->one->sha1, p->two->sha1);
 +              if (p->one->oid_valid && p->two->oid_valid)
 +                      content_changed = oidcmp(&p->one->oid, &p->two->oid);
                else
                        content_changed = 1;
  
                if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
                        diff_populate_filespec(p->one, 0);
                        diff_populate_filespec(p->two, 0);
 -                      diffcore_count_changes(p->one, p->two, NULL, NULL, 0,
 +                      diffcore_count_changes(p->one, p->two, NULL, NULL,
                                               &copied, &added);
                        diff_free_filespec_data(p->one);
                        diff_free_filespec_data(p->two);
@@@ -2064,7 -2006,7 +2064,7 @@@ found_damage
                return;
  
        /* Show all directories with more than x% of the changes */
 -      qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
 +      QSORT(dir.files, dir.nr, dirstat_compare);
        gather_dirstat(options, &dir, changed, "", 0);
  }
  
@@@ -2108,7 -2050,7 +2108,7 @@@ static void show_dirstat_by_line(struc
                return;
  
        /* Show all directories with more than x% of the changes */
 -      qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
 +      QSORT(dir.files, dir.nr, dirstat_compare);
        gather_dirstat(options, &dir, changed, "", 0);
  }
  
@@@ -2357,37 -2299,16 +2357,37 @@@ static void builtin_diff(const char *na
        struct strbuf header = STRBUF_INIT;
        const char *line_prefix = diff_line_prefix(o);
  
 -      if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
 -                      (!one->mode || S_ISGITLINK(one->mode)) &&
 -                      (!two->mode || S_ISGITLINK(two->mode))) {
 +      diff_set_mnemonic_prefix(o, "a/", "b/");
 +      if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
 +              a_prefix = o->b_prefix;
 +              b_prefix = o->a_prefix;
 +      } else {
 +              a_prefix = o->a_prefix;
 +              b_prefix = o->b_prefix;
 +      }
 +
 +      if (o->submodule_format == DIFF_SUBMODULE_LOG &&
 +          (!one->mode || S_ISGITLINK(one->mode)) &&
 +          (!two->mode || S_ISGITLINK(two->mode))) {
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
                show_submodule_summary(o->file, one->path ? one->path : two->path,
                                line_prefix,
 -                              one->sha1, two->sha1, two->dirty_submodule,
 +                              &one->oid, &two->oid,
 +                              two->dirty_submodule,
                                meta, del, add, reset);
                return;
 +      } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF &&
 +                 (!one->mode || S_ISGITLINK(one->mode)) &&
 +                 (!two->mode || S_ISGITLINK(two->mode))) {
 +              const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
 +              const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
 +              show_submodule_inline_diff(o->file, one->path ? one->path : two->path,
 +                              line_prefix,
 +                              &one->oid, &two->oid,
 +                              two->dirty_submodule,
 +                              meta, del, add, reset, o);
 +              return;
        }
  
        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
                textconv_two = get_textconv(two);
        }
  
 -      diff_set_mnemonic_prefix(o, "a/", "b/");
 -      if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
 -              a_prefix = o->b_prefix;
 -              b_prefix = o->a_prefix;
 -      } else {
 -              a_prefix = o->a_prefix;
 -              b_prefix = o->b_prefix;
 -      }
 -
        /* Never use a non-valid filename anywhere if at all possible */
        name_a = DIFF_FILE_VALID(one) ? name_a : name_b;
        name_b = DIFF_FILE_VALID(two) ? name_b : name_a;
                if (!one->data && !two->data &&
                    S_ISREG(one->mode) && S_ISREG(two->mode) &&
                    !DIFF_OPT_TST(o, BINARY)) {
 -                      if (!hashcmp(one->sha1, two->sha1)) {
 +                      if (!oidcmp(&one->oid, &two->oid)) {
                                if (must_show_header)
                                        fprintf(o->file, "%s", header.buf);
                                goto free_ab_and_return;
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
                ecbdata.color_diff = want_color(o->use_color);
 -              ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
@@@ -2574,7 -2505,7 +2574,7 @@@ static void builtin_diffstat(const cha
                return;
        }
  
 -      same_contents = !hashcmp(one->sha1, two->sha1);
 +      same_contents = !oidcmp(&one->oid, &two->oid);
  
        if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
                data->is_binary = 1;
@@@ -2707,8 -2638,8 +2707,8 @@@ void fill_filespec(struct diff_filespe
  {
        if (mode) {
                spec->mode = canon_mode(mode);
 -              hashcpy(spec->sha1, sha1);
 -              spec->sha1_valid = sha1_valid;
 +              hashcpy(spec->oid.hash, sha1);
 +              spec->oid_valid = sha1_valid;
        }
  }
  
@@@ -2768,7 -2699,7 +2768,7 @@@ static int reuse_worktree_file(const ch
         * This is not the sha1 we are looking for, or
         * unreusable because it is not a regular file.
         */
 -      if (hashcmp(sha1, ce->sha1) || !S_ISREG(ce->ce_mode))
 +      if (hashcmp(sha1, ce->oid.hash) || !S_ISREG(ce->ce_mode))
                return 0;
  
        /*
@@@ -2797,8 -2728,7 +2797,8 @@@ static int diff_populate_gitlink(struc
        if (s->dirty_submodule)
                dirty = "-dirty";
  
 -      strbuf_addf(&buf, "Subproject commit %s%s\n", sha1_to_hex(s->sha1), dirty);
 +      strbuf_addf(&buf, "Subproject commit %s%s\n",
 +                  oid_to_hex(&s->oid), dirty);
        s->size = buf.len;
        if (size_only) {
                s->data = NULL;
@@@ -2841,8 -2771,8 +2841,8 @@@ int diff_populate_filespec(struct diff_
        if (S_ISGITLINK(s->mode))
                return diff_populate_gitlink(s, size_only);
  
 -      if (!s->sha1_valid ||
 -          reuse_worktree_file(s->path, s->sha1, 0)) {
 +      if (!s->oid_valid ||
 +          reuse_worktree_file(s->path, s->oid.hash, 0)) {
                struct strbuf buf = STRBUF_INIT;
                struct stat st;
                int fd;
        else {
                enum object_type type;
                if (size_only || (flags & CHECK_BINARY)) {
 -                      type = sha1_object_info(s->sha1, &s->size);
 +                      type = sha1_object_info(s->oid.hash, &s->size);
                        if (type < 0)
 -                              die("unable to read %s", sha1_to_hex(s->sha1));
 +                              die("unable to read %s",
 +                                  oid_to_hex(&s->oid));
                        if (size_only)
                                return 0;
                        if (s->size > big_file_threshold && s->is_binary == -1) {
                                return 0;
                        }
                }
 -              s->data = read_sha1_file(s->sha1, &type, &s->size);
 +              s->data = read_sha1_file(s->oid.hash, &type, &s->size);
                if (!s->data)
 -                      die("unable to read %s", sha1_to_hex(s->sha1));
 +                      die("unable to read %s", oid_to_hex(&s->oid));
                s->should_free = 1;
        }
        return 0;
@@@ -2941,7 -2870,7 +2941,7 @@@ void diff_free_filespec_data(struct dif
  static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
                           void *blob,
                           unsigned long size,
 -                         const unsigned char *sha1,
 +                         const struct object_id *oid,
                           int mode)
  {
        int fd;
                die_errno("unable to write temp-file");
        close_tempfile(&temp->tempfile);
        temp->name = get_tempfile_path(&temp->tempfile);
 -      sha1_to_hex_r(temp->hex, sha1);
 +      oid_to_hex_r(temp->hex, oid);
        xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
        strbuf_release(&buf);
        strbuf_release(&template);
@@@ -2990,8 -2919,8 +2990,8 @@@ static struct diff_tempfile *prepare_te
        }
  
        if (!S_ISGITLINK(one->mode) &&
 -          (!one->sha1_valid ||
 -           reuse_worktree_file(name, one->sha1, 1))) {
 +          (!one->oid_valid ||
 +           reuse_worktree_file(name, one->oid.hash, 1))) {
                struct stat st;
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
                        if (strbuf_readlink(&sb, name, st.st_size) < 0)
                                die_errno("readlink(%s)", name);
                        prep_temp_blob(name, temp, sb.buf, sb.len,
 -                                     (one->sha1_valid ?
 -                                      one->sha1 : null_sha1),
 -                                     (one->sha1_valid ?
 +                                     (one->oid_valid ?
 +                                      &one->oid : &null_oid),
 +                                     (one->oid_valid ?
                                        one->mode : S_IFLNK));
                        strbuf_release(&sb);
                }
                else {
                        /* we can borrow from the file in the work tree */
                        temp->name = name;
 -                      if (!one->sha1_valid)
 +                      if (!one->oid_valid)
                                sha1_to_hex_r(temp->hex, null_sha1);
                        else
 -                              sha1_to_hex_r(temp->hex, one->sha1);
 +                              oid_to_hex_r(temp->hex, &one->oid);
                        /* Even though we may sometimes borrow the
                         * contents from the work tree, we always want
                         * one->mode.  mode is trustworthy even when
                if (diff_populate_filespec(one, 0))
                        die("cannot read data blob for %s", one->path);
                prep_temp_blob(name, temp, one->data, one->size,
 -                             one->sha1, one->mode);
 +                             &one->oid, one->mode);
        }
        return temp;
  }
@@@ -3094,22 -3023,6 +3094,22 @@@ static int similarity_index(struct diff
        return p->score * 100 / MAX_SCORE;
  }
  
 +static const char *diff_abbrev_oid(const struct object_id *oid, int abbrev)
 +{
 +      if (startup_info->have_repository)
 +              return find_unique_abbrev(oid->hash, abbrev);
 +      else {
 +              char *hex = oid_to_hex(oid);
 +              if (abbrev < 0)
 +                      abbrev = FALLBACK_DEFAULT_ABBREV;
 +              if (abbrev > GIT_SHA1_HEXSZ)
 +                      die("BUG: oid abbreviation out of range: %d", abbrev);
 +              if (abbrev)
 +                      hex[abbrev] = '\0';
 +              return hex;
 +      }
 +}
 +
  static void fill_metainfo(struct strbuf *msg,
                          const char *name,
                          const char *other,
        default:
                *must_show_header = 0;
        }
 -      if (one && two && hashcmp(one->sha1, two->sha1)) {
 +      if (one && two && oidcmp(&one->oid, &two->oid)) {
                int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
  
                if (DIFF_OPT_TST(o, BINARY)) {
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
                                abbrev = 40;
                }
 -              strbuf_addf(msg, "%s%sindex %s..", line_prefix, set,
 -                          find_unique_abbrev(one->sha1, abbrev));
 -              strbuf_addstr(msg, find_unique_abbrev(two->sha1, abbrev));
 +              strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set,
 +                          diff_abbrev_oid(&one->oid, abbrev),
 +                          diff_abbrev_oid(&two->oid, abbrev));
                if (one->mode == two->mode)
                        strbuf_addf(msg, " %06o", one->mode);
                strbuf_addf(msg, "%s\n", reset);
@@@ -3225,20 -3138,20 +3225,20 @@@ static void run_diff_cmd(const char *pg
  static void diff_fill_sha1_info(struct diff_filespec *one)
  {
        if (DIFF_FILE_VALID(one)) {
 -              if (!one->sha1_valid) {
 +              if (!one->oid_valid) {
                        struct stat st;
                        if (one->is_stdin) {
 -                              hashcpy(one->sha1, null_sha1);
 +                              oidclr(&one->oid);
                                return;
                        }
                        if (lstat(one->path, &st) < 0)
                                die_errno("stat '%s'", one->path);
 -                      if (index_path(one->sha1, one->path, &st, 0))
 +                      if (index_path(one->oid.hash, one->path, &st, 0))
                                die("cannot hash %s", one->path);
                }
        }
        else
 -              hashclr(one->sha1);
 +              oidclr(&one->oid);
  }
  
  static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
@@@ -3363,14 -3276,12 +3363,14 @@@ void diff_setup(struct diff_options *op
  
        options->file = stdout;
  
 +      options->abbrev = DEFAULT_ABBREV;
        options->line_termination = '\n';
        options->break_opt = -1;
        options->rename_limit = -1;
        options->dirstat_permille = diff_dirstat_permille_default;
        options->context = diff_context_default;
 -      options->ws_error_highlight = WSEH_NEW;
 +      options->interhunkcontext = diff_interhunk_context_default;
 +      options->ws_error_highlight = ws_error_highlight_default;
        DIFF_OPT_SET(options, RENAME_EMPTY);
  
        /* pathchange left =NULL by default */
        options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
        options->xdl_opts |= diff_algorithm;
 -      if (diff_compaction_heuristic)
 -              DIFF_XDL_SET(options, COMPACTION_HEURISTIC);
 +      if (diff_indent_heuristic)
 +              DIFF_XDL_SET(options, INDENT_HEURISTIC);
  
        options->orderfile = diff_order_file_cfg;
  
@@@ -3408,7 -3319,7 +3408,7 @@@ void diff_setup_done(struct diff_option
        if (options->output_format & DIFF_FORMAT_NO_OUTPUT)
                count++;
        if (count > 1)
 -              die("--name-only, --name-status, --check and -s are mutually exclusive");
 +              die(_("--name-only, --name-status, --check and -s are mutually exclusive"));
  
        /*
         * Most of the time we can say "there are changes"
                         */
                        read_cache();
        }
 -      if (options->abbrev <= 0 || 40 < options->abbrev)
 +      if (40 < options->abbrev)
                options->abbrev = 40; /* full */
  
        /*
@@@ -3604,7 -3515,7 +3604,7 @@@ static int stat_opt(struct diff_option
                        if (*arg == '=')
                                width = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
 -                              die("Option '--stat-width' requires a value");
 +                              die_want_option("--stat-width");
                        else if (!*arg) {
                                width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        if (*arg == '=')
                                name_width = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
 -                              die("Option '--stat-name-width' requires a value");
 +                              die_want_option("--stat-name-width");
                        else if (!*arg) {
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        if (*arg == '=')
                                graph_width = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
 -                              die("Option '--stat-graph-width' requires a value");
 +                              die_want_option("--stat-graph-width");
                        else if (!*arg) {
                                graph_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        if (*arg == '=')
                                count = strtoul(arg + 1, &end, 10);
                        else if (!*arg && !av[1])
 -                              die("Option '--stat-count' requires a value");
 +                              die_want_option("--stat-count");
                        else if (!*arg) {
                                count = strtoul(av[1], &end, 10);
                                argcount = 2;
@@@ -3759,14 -3670,40 +3759,14 @@@ static void enable_patch_output(int *fm
        *fmt |= DIFF_FORMAT_PATCH;
  }
  
 -static int parse_one_token(const char **arg, const char *token)
 +static int parse_ws_error_highlight_opt(struct diff_options *opt, const char *arg)
  {
 -      const char *rest;
 -      if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
 -              *arg = rest;
 -              return 1;
 -      }
 -      return 0;
 -}
 +      int val = parse_ws_error_highlight(arg);
  
 -static int parse_ws_error_highlight(struct diff_options *opt, const char *arg)
 -{
 -      const char *orig_arg = arg;
 -      unsigned val = 0;
 -      while (*arg) {
 -              if (parse_one_token(&arg, "none"))
 -                      val = 0;
 -              else if (parse_one_token(&arg, "default"))
 -                      val = WSEH_NEW;
 -              else if (parse_one_token(&arg, "all"))
 -                      val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
 -              else if (parse_one_token(&arg, "new"))
 -                      val |= WSEH_NEW;
 -              else if (parse_one_token(&arg, "old"))
 -                      val |= WSEH_OLD;
 -              else if (parse_one_token(&arg, "context"))
 -                      val |= WSEH_CONTEXT;
 -              else {
 -                      error("unknown value after ws-error-highlight=%.*s",
 -                            (int)(arg - orig_arg), orig_arg);
 -                      return 0;
 -              }
 -              if (*arg)
 -                      arg++;
 +      if (val < 0) {
 +              error("unknown value after ws-error-highlight=%.*s",
 +                    -1 - val, arg);
 +              return 0;
        }
        opt->ws_error_highlight = val;
        return 1;
@@@ -3875,10 -3812,10 +3875,10 @@@ int diff_opt_parse(struct diff_options 
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
        else if (!strcmp(arg, "--ignore-blank-lines"))
                DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
 -      else if (!strcmp(arg, "--compaction-heuristic"))
 -              DIFF_XDL_SET(options, COMPACTION_HEURISTIC);
 -      else if (!strcmp(arg, "--no-compaction-heuristic"))
 -              DIFF_XDL_CLR(options, COMPACTION_HEURISTIC);
 +      else if (!strcmp(arg, "--indent-heuristic"))
 +              DIFF_XDL_SET(options, INDENT_HEURISTIC);
 +      else if (!strcmp(arg, "--no-indent-heuristic"))
 +              DIFF_XDL_CLR(options, INDENT_HEURISTIC);
        else if (!strcmp(arg, "--patience"))
                options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
        else if (!strcmp(arg, "--histogram"))
                DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(options, arg);
        } else if (!strcmp(arg, "--submodule"))
 -              DIFF_OPT_SET(options, SUBMODULE_LOG);
 +              options->submodule_format = DIFF_SUBMODULE_LOG;
        else if (skip_prefix(arg, "--submodule=", &arg))
                return parse_submodule_opt(options, arg);
        else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
 -              return parse_ws_error_highlight(options, arg);
 +              return parse_ws_error_highlight_opt(options, arg);
 +      else if (!strcmp(arg, "--ita-invisible-in-index"))
 +              options->ita_invisible_in_index = 1;
 +      else if (!strcmp(arg, "--ita-visible-in-index"))
 +              options->ita_invisible_in_index = 0;
  
        /* misc options */
        else if (!strcmp(arg, "-z"))
                            offending, optarg);
                return argcount;
        }
 +      else if (!strcmp(arg, "--no-abbrev"))
 +              options->abbrev = 0;
        else if (!strcmp(arg, "--abbrev"))
                options->abbrev = DEFAULT_ABBREV;
        else if (skip_prefix(arg, "--abbrev=", &arg)) {
                options->a_prefix = optarg;
                return argcount;
        }
 +      else if ((argcount = parse_long_opt("line-prefix", av, &optarg))) {
 +              options->line_prefix = optarg;
 +              options->line_prefix_length = strlen(options->line_prefix);
 +              graph_setup_line_prefix(options);
 +              return argcount;
 +      }
        else if ((argcount = parse_long_opt("dst-prefix", av, &optarg))) {
                options->b_prefix = optarg;
                return argcount;
                if (!options->file)
                        die_errno("Could not open '%s'", path);
                options->close_file = 1;
 +              if (options->use_color != GIT_COLOR_ALWAYS)
 +                      options->use_color = GIT_COLOR_NEVER;
                return argcount;
        } else
                return 0;
@@@ -4171,46 -4094,27 +4171,46 @@@ void diff_free_filepair(struct diff_fil
        free(p);
  }
  
 -/* This is different from find_unique_abbrev() in that
 - * it stuffs the result with dots for alignment.
 - */
 -const char *diff_unique_abbrev(const unsigned char *sha1, int len)
 +const char *diff_aligned_abbrev(const struct object_id *oid, int len)
  {
        int abblen;
        const char *abbrev;
 -      if (len == 40)
 -              return sha1_to_hex(sha1);
  
 -      abbrev = find_unique_abbrev(sha1, len);
 +      if (len == GIT_SHA1_HEXSZ)
 +              return oid_to_hex(oid);
 +
 +      abbrev = diff_abbrev_oid(oid, len);
        abblen = strlen(abbrev);
 -      if (abblen < 37) {
 -              static char hex[41];
 +
 +      /*
 +       * In well-behaved cases, where the abbbreviated result is the
 +       * same as the requested length, append three dots after the
 +       * abbreviation (hence the whole logic is limited to the case
 +       * where abblen < 37); when the actual abbreviated result is a
 +       * bit longer than the requested length, we reduce the number
 +       * of dots so that they match the well-behaved ones.  However,
 +       * if the actual abbreviation is longer than the requested
 +       * length by more than three, we give up on aligning, and add
 +       * three dots anyway, to indicate that the output is not the
 +       * full object name.  Yes, this may be suboptimal, but this
 +       * appears only in "diff --raw --abbrev" output and it is not
 +       * worth the effort to change it now.  Note that this would
 +       * likely to work fine when the automatic sizing of default
 +       * abbreviation length is used--we would be fed -1 in "len" in
 +       * that case, and will end up always appending three-dots, but
 +       * the automatic sizing is supposed to give abblen that ensures
 +       * uniqueness across all objects (statistically speaking).
 +       */
 +      if (abblen < GIT_SHA1_HEXSZ - 3) {
 +              static char hex[GIT_SHA1_HEXSZ + 1];
                if (len < abblen && abblen <= len + 2)
                        xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, "..");
                else
                        xsnprintf(hex, sizeof(hex), "%s...", abbrev);
                return hex;
        }
 -      return sha1_to_hex(sha1);
 +
 +      return oid_to_hex(oid);
  }
  
  static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
        fprintf(opt->file, "%s", diff_line_prefix(opt));
        if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) {
                fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode,
 -                      diff_unique_abbrev(p->one->sha1, opt->abbrev));
 -              fprintf(opt->file, "%s ", diff_unique_abbrev(p->two->sha1, opt->abbrev));
 +                      diff_aligned_abbrev(&p->one->oid, opt->abbrev));
 +              fprintf(opt->file, "%s ",
 +                      diff_aligned_abbrev(&p->two->oid, opt->abbrev));
        }
        if (p->score) {
                fprintf(opt->file, "%c%03d%c", p->status, similarity_index(p),
@@@ -4272,11 -4175,11 +4272,11 @@@ int diff_unmodified_pair(struct diff_fi
        /* both are valid and point at the same path.  that is, we are
         * dealing with a change.
         */
 -      if (one->sha1_valid && two->sha1_valid &&
 -          !hashcmp(one->sha1, two->sha1) &&
 +      if (one->oid_valid && two->oid_valid &&
 +          !oidcmp(&one->oid, &two->oid) &&
            !one->dirty_submodule && !two->dirty_submodule)
                return 1; /* no change */
 -      if (!one->sha1_valid && !two->sha1_valid)
 +      if (!one->oid_valid && !two->oid_valid)
                return 1; /* both look at the same file on the filesystem. */
        return 0;
  }
@@@ -4337,7 -4240,7 +4337,7 @@@ void diff_debug_filespec(struct diff_fi
                s->path,
                DIFF_FILE_VALID(s) ? "valid" : "invalid",
                s->mode,
 -              s->sha1_valid ? sha1_to_hex(s->sha1) : "");
 +              s->oid_valid ? oid_to_hex(&s->oid) : "");
        fprintf(stderr, "queue[%d] %s size %lu\n",
                x, one ? one : "",
                s->size);
@@@ -4407,11 -4310,11 +4407,11 @@@ static void diff_resolve_rename_copy(vo
                        else
                                p->status = DIFF_STATUS_RENAMED;
                }
 -              else if (hashcmp(p->one->sha1, p->two->sha1) ||
 +              else if (oidcmp(&p->one->oid, &p->two->oid) ||
                         p->one->mode != p->two->mode ||
                         p->one->dirty_submodule ||
                         p->two->dirty_submodule ||
 -                       is_null_sha1(p->one->sha1))
 +                       is_null_oid(&p->one->oid))
                        p->status = DIFF_STATUS_MODIFIED;
                else {
                        /* This is a "no-change" entry and should not
@@@ -4450,6 -4353,7 +4450,7 @@@ static void flush_one_pair(struct diff_
                name_a = p->two->path;
                name_b = NULL;
                strip_prefix(opt->prefix_length, &name_a, &name_b);
+               fprintf(opt->file, "%s", diff_line_prefix(opt));
                write_name_quoted(name_a, opt->file, opt->line_termination);
        }
  }
@@@ -4553,7 -4457,7 +4554,7 @@@ static void patch_id_consume(void *priv
  }
  
  /* returns 0 upon success, and writes result into sha1 */
 -static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
 +static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1, int diff_header_only)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
  
                diff_fill_sha1_info(p->one);
                diff_fill_sha1_info(p->two);
 -              if (fill_mmfile(&mf1, p->one) < 0 ||
 -                              fill_mmfile(&mf2, p->two) < 0)
 -                      return error("unable to read files to diff");
  
                len1 = remove_space(p->one->path, strlen(p->one->path));
                len2 = remove_space(p->two->path, strlen(p->two->path));
                                        len2, p->two->path);
                git_SHA1_Update(&ctx, buffer, len1);
  
 +              if (diff_header_only)
 +                      continue;
 +
 +              if (fill_mmfile(&mf1, p->one) < 0 ||
 +                  fill_mmfile(&mf2, p->two) < 0)
 +                      return error("unable to read files to diff");
 +
                if (diff_filespec_is_binary(p->one) ||
                    diff_filespec_is_binary(p->two)) {
 -                      git_SHA1_Update(&ctx, sha1_to_hex(p->one->sha1), 40);
 -                      git_SHA1_Update(&ctx, sha1_to_hex(p->two->sha1), 40);
 +                      git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid),
 +                                      40);
 +                      git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid),
 +                                      40);
                        continue;
                }
  
        return 0;
  }
  
 -int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1)
 +int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1, int diff_header_only)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
 -      int result = diff_get_patch_id(options, sha1);
 +      int result = diff_get_patch_id(options, sha1, diff_header_only);
  
        for (i = 0; i < q->nr; i++)
                diff_free_filepair(q->queue[i]);
@@@ -4692,25 -4590,25 +4693,25 @@@ static int is_summary_empty(const struc
  }
  
  static const char rename_limit_warning[] =
 -"inexact rename detection was skipped due to too many files.";
 +N_("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.";
 +N_("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.";
 +N_("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);
 +              warning(_(degrade_cc_to_c_warning));
        else if (needed)
 -              warning(rename_limit_warning);
 +              warning(_(rename_limit_warning));
        else
                return;
        if (0 < needed && needed < 32767)
 -              warning(rename_limit_advice, varname, needed);
 +              warning(_(rename_limit_advice), varname, needed);
  }
  
  void diff_flush(struct diff_options *options)
@@@ -4926,7 -4824,7 +4927,7 @@@ static int diff_filespec_check_stat_unm
         */
        if (!DIFF_FILE_VALID(p->one) || /* (1) */
            !DIFF_FILE_VALID(p->two) ||
 -          (p->one->sha1_valid && p->two->sha1_valid) ||
 +          (p->one->oid_valid && p->two->oid_valid) ||
            (p->one->mode != p->two->mode) ||
            diff_populate_filespec(p->one, CHECK_SIZE_ONLY) ||
            diff_populate_filespec(p->two, CHECK_SIZE_ONLY) ||
@@@ -4977,7 -4875,7 +4978,7 @@@ static int diffnamecmp(const void *a_, 
  void diffcore_fix_diff_index(struct diff_options *options)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
 -      qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
 +      QSORT(q->queue, q->nr, diffnamecmp);
  }
  
  void diffcore_std(struct diff_options *options)
@@@ -5222,9 -5120,8 +5223,9 @@@ size_t fill_textconv(struct userdiff_dr
        if (!driver->textconv)
                die("BUG: fill_textconv called with non-textconv driver");
  
 -      if (driver->textconv_cache && df->sha1_valid) {
 -              *outbuf = notes_cache_get(driver->textconv_cache, df->sha1,
 +      if (driver->textconv_cache && df->oid_valid) {
 +              *outbuf = notes_cache_get(driver->textconv_cache,
 +                                        df->oid.hash,
                                          &size);
                if (*outbuf)
                        return size;
        if (!*outbuf)
                die("unable to read files to diff");
  
 -      if (driver->textconv_cache && df->sha1_valid) {
 +      if (driver->textconv_cache && df->oid_valid) {
                /* ignore errors, as we might be in a readonly repository */
 -              notes_cache_put(driver->textconv_cache, df->sha1, *outbuf,
 +              notes_cache_put(driver->textconv_cache, df->oid.hash, *outbuf,
                                size);
                /*
                 * we could save up changes and flush them all at the end,
diff --combined t/t4202-log.sh
index 08ea725de3307c886d8f341b49c61c0d3436f91c,2f8fa4f6a37ccc7a44709878dc2e0b0c06796d7f..48b55bfd274a7adffe18af13489a54a7d92707a4
@@@ -187,16 -187,6 +187,16 @@@ test_expect_success 'git log --no-walk=
        test_cmp expect actual
  '
  
 +cat > expect << EOF
 +=== 804a787 sixth
 +=== 394ef78 fifth
 +=== 5d31159 fourth
 +EOF
 +test_expect_success 'git log --line-prefix="=== " --no-walk <commits> sorts by commit time' '
 +      git log --line-prefix="=== " --no-walk --oneline 5d31159 804a787 394ef78 > actual &&
 +      test_cmp expect actual
 +'
 +
  cat > expect << EOF
  5d31159 fourth
  804a787 sixth
@@@ -294,21 -284,6 +294,21 @@@ test_expect_success 'simple log --graph
        test_cmp expect actual
  '
  
 +cat > expect <<EOF
 +123 * Second
 +123 * sixth
 +123 * fifth
 +123 * fourth
 +123 * third
 +123 * second
 +123 * initial
 +EOF
 +
 +test_expect_success 'simple log --graph --line-prefix="123 "' '
 +      git log --graph --line-prefix="123 " --pretty=tformat:%s >actual &&
 +      test_cmp expect actual
 +'
 +
  test_expect_success 'set up merge history' '
        git checkout -b side HEAD~4 &&
        test_commit side-1 1 1 &&
@@@ -338,49 -313,6 +338,49 @@@ test_expect_success 'log --graph with m
        test_cmp expect actual
  '
  
 +cat > expect <<\EOF
 +| | | *   Merge branch 'side'
 +| | | |\
 +| | | | * side-2
 +| | | | * side-1
 +| | | * | Second
 +| | | * | sixth
 +| | | * | fifth
 +| | | * | fourth
 +| | | |/
 +| | | * third
 +| | | * second
 +| | | * initial
 +EOF
 +
 +test_expect_success 'log --graph --line-prefix="| | | " with merge' '
 +      git log --line-prefix="| | | " --graph --date-order --pretty=tformat:%s |
 +              sed "s/ *\$//" >actual &&
 +      test_cmp expect actual
 +'
 +
 +cat > expect.colors <<\EOF
 +*   Merge branch 'side'
 +<BLUE>|<RESET><CYAN>\<RESET>
 +<BLUE>|<RESET> * side-2
 +<BLUE>|<RESET> * side-1
 +* <CYAN>|<RESET> Second
 +* <CYAN>|<RESET> sixth
 +* <CYAN>|<RESET> fifth
 +* <CYAN>|<RESET> fourth
 +<CYAN>|<RESET><CYAN>/<RESET>
 +* third
 +* second
 +* initial
 +EOF
 +
 +test_expect_success 'log --graph with merge with log.graphColors' '
 +      test_config log.graphColors " blue,invalid-color, cyan, red  , " &&
 +      git log --color=always --graph --date-order --pretty=tformat:%s |
 +              test_decode_color | sed "s/ *\$//" >actual &&
 +      test_cmp expect.colors actual
 +'
 +
  test_expect_success 'log --raw --graph -m with merge' '
        git log --raw --graph --oneline -m master | head -n 500 >actual &&
        grep "initial" actual
@@@ -935,283 -867,54 +935,331 @@@ test_expect_success 'log --graph with d
        test_i18ncmp expect actual.sanitized
  '
  
 +cat >expect <<\EOF
 +*** *   commit COMMIT_OBJECT_NAME
 +*** |\  Merge: MERGE_PARENTS
 +*** | | Author: A U Thor <author@example.com>
 +*** | |
 +*** | |     Merge HEADS DESCRIPTION
 +*** | |
 +*** | * commit COMMIT_OBJECT_NAME
 +*** | | Author: A U Thor <author@example.com>
 +*** | |
 +*** | |     reach
 +*** | | ---
 +*** | |  reach.t | 1 +
 +*** | |  1 file changed, 1 insertion(+)
 +*** | |
 +*** | | diff --git a/reach.t b/reach.t
 +*** | | new file mode 100644
 +*** | | index 0000000..10c9591
 +*** | | --- /dev/null
 +*** | | +++ b/reach.t
 +*** | | @@ -0,0 +1 @@
 +*** | | +reach
 +*** | |
 +*** |  \
 +*** *-. \   commit COMMIT_OBJECT_NAME
 +*** |\ \ \  Merge: MERGE_PARENTS
 +*** | | | | Author: A U Thor <author@example.com>
 +*** | | | |
 +*** | | | |     Merge HEADS DESCRIPTION
 +*** | | | |
 +*** | | * | commit COMMIT_OBJECT_NAME
 +*** | | |/  Author: A U Thor <author@example.com>
 +*** | | |
 +*** | | |       octopus-b
 +*** | | |   ---
 +*** | | |    octopus-b.t | 1 +
 +*** | | |    1 file changed, 1 insertion(+)
 +*** | | |
 +*** | | |   diff --git a/octopus-b.t b/octopus-b.t
 +*** | | |   new file mode 100644
 +*** | | |   index 0000000..d5fcad0
 +*** | | |   --- /dev/null
 +*** | | |   +++ b/octopus-b.t
 +*** | | |   @@ -0,0 +1 @@
 +*** | | |   +octopus-b
 +*** | | |
 +*** | * | commit COMMIT_OBJECT_NAME
 +*** | |/  Author: A U Thor <author@example.com>
 +*** | |
 +*** | |       octopus-a
 +*** | |   ---
 +*** | |    octopus-a.t | 1 +
 +*** | |    1 file changed, 1 insertion(+)
 +*** | |
 +*** | |   diff --git a/octopus-a.t b/octopus-a.t
 +*** | |   new file mode 100644
 +*** | |   index 0000000..11ee015
 +*** | |   --- /dev/null
 +*** | |   +++ b/octopus-a.t
 +*** | |   @@ -0,0 +1 @@
 +*** | |   +octopus-a
 +*** | |
 +*** * | commit COMMIT_OBJECT_NAME
 +*** |/  Author: A U Thor <author@example.com>
 +*** |
 +*** |       seventh
 +*** |   ---
 +*** |    seventh.t | 1 +
 +*** |    1 file changed, 1 insertion(+)
 +*** |
 +*** |   diff --git a/seventh.t b/seventh.t
 +*** |   new file mode 100644
 +*** |   index 0000000..9744ffc
 +*** |   --- /dev/null
 +*** |   +++ b/seventh.t
 +*** |   @@ -0,0 +1 @@
 +*** |   +seventh
 +*** |
 +*** *   commit COMMIT_OBJECT_NAME
 +*** |\  Merge: MERGE_PARENTS
 +*** | | Author: A U Thor <author@example.com>
 +*** | |
 +*** | |     Merge branch 'tangle'
 +*** | |
 +*** | *   commit COMMIT_OBJECT_NAME
 +*** | |\  Merge: MERGE_PARENTS
 +*** | | | Author: A U Thor <author@example.com>
 +*** | | |
 +*** | | |     Merge branch 'side' (early part) into tangle
 +*** | | |
 +*** | * |   commit COMMIT_OBJECT_NAME
 +*** | |\ \  Merge: MERGE_PARENTS
 +*** | | | | Author: A U Thor <author@example.com>
 +*** | | | |
 +*** | | | |     Merge branch 'master' (early part) into tangle
 +*** | | | |
 +*** | * | | commit COMMIT_OBJECT_NAME
 +*** | | | | Author: A U Thor <author@example.com>
 +*** | | | |
 +*** | | | |     tangle-a
 +*** | | | | ---
 +*** | | | |  tangle-a | 1 +
 +*** | | | |  1 file changed, 1 insertion(+)
 +*** | | | |
 +*** | | | | diff --git a/tangle-a b/tangle-a
 +*** | | | | new file mode 100644
 +*** | | | | index 0000000..7898192
 +*** | | | | --- /dev/null
 +*** | | | | +++ b/tangle-a
 +*** | | | | @@ -0,0 +1 @@
 +*** | | | | +a
 +*** | | | |
 +*** * | | |   commit COMMIT_OBJECT_NAME
 +*** |\ \ \ \  Merge: MERGE_PARENTS
 +*** | | | | | Author: A U Thor <author@example.com>
 +*** | | | | |
 +*** | | | | |     Merge branch 'side'
 +*** | | | | |
 +*** | * | | | commit COMMIT_OBJECT_NAME
 +*** | | |_|/  Author: A U Thor <author@example.com>
 +*** | |/| |
 +*** | | | |       side-2
 +*** | | | |   ---
 +*** | | | |    2 | 1 +
 +*** | | | |    1 file changed, 1 insertion(+)
 +*** | | | |
 +*** | | | |   diff --git a/2 b/2
 +*** | | | |   new file mode 100644
 +*** | | | |   index 0000000..0cfbf08
 +*** | | | |   --- /dev/null
 +*** | | | |   +++ b/2
 +*** | | | |   @@ -0,0 +1 @@
 +*** | | | |   +2
 +*** | | | |
 +*** | * | | commit COMMIT_OBJECT_NAME
 +*** | | | | Author: A U Thor <author@example.com>
 +*** | | | |
 +*** | | | |     side-1
 +*** | | | | ---
 +*** | | | |  1 | 1 +
 +*** | | | |  1 file changed, 1 insertion(+)
 +*** | | | |
 +*** | | | | diff --git a/1 b/1
 +*** | | | | new file mode 100644
 +*** | | | | index 0000000..d00491f
 +*** | | | | --- /dev/null
 +*** | | | | +++ b/1
 +*** | | | | @@ -0,0 +1 @@
 +*** | | | | +1
 +*** | | | |
 +*** * | | | commit COMMIT_OBJECT_NAME
 +*** | | | | Author: A U Thor <author@example.com>
 +*** | | | |
 +*** | | | |     Second
 +*** | | | | ---
 +*** | | | |  one | 1 +
 +*** | | | |  1 file changed, 1 insertion(+)
 +*** | | | |
 +*** | | | | diff --git a/one b/one
 +*** | | | | new file mode 100644
 +*** | | | | index 0000000..9a33383
 +*** | | | | --- /dev/null
 +*** | | | | +++ b/one
 +*** | | | | @@ -0,0 +1 @@
 +*** | | | | +case
 +*** | | | |
 +*** * | | | commit COMMIT_OBJECT_NAME
 +*** | |_|/  Author: A U Thor <author@example.com>
 +*** |/| |
 +*** | | |       sixth
 +*** | | |   ---
 +*** | | |    a/two | 1 -
 +*** | | |    1 file changed, 1 deletion(-)
 +*** | | |
 +*** | | |   diff --git a/a/two b/a/two
 +*** | | |   deleted file mode 100644
 +*** | | |   index 9245af5..0000000
 +*** | | |   --- a/a/two
 +*** | | |   +++ /dev/null
 +*** | | |   @@ -1 +0,0 @@
 +*** | | |   -ni
 +*** | | |
 +*** * | | commit COMMIT_OBJECT_NAME
 +*** | | | Author: A U Thor <author@example.com>
 +*** | | |
 +*** | | |     fifth
 +*** | | | ---
 +*** | | |  a/two | 1 +
 +*** | | |  1 file changed, 1 insertion(+)
 +*** | | |
 +*** | | | diff --git a/a/two b/a/two
 +*** | | | new file mode 100644
 +*** | | | index 0000000..9245af5
 +*** | | | --- /dev/null
 +*** | | | +++ b/a/two
 +*** | | | @@ -0,0 +1 @@
 +*** | | | +ni
 +*** | | |
 +*** * | | commit COMMIT_OBJECT_NAME
 +*** |/ /  Author: A U Thor <author@example.com>
 +*** | |
 +*** | |       fourth
 +*** | |   ---
 +*** | |    ein | 1 +
 +*** | |    1 file changed, 1 insertion(+)
 +*** | |
 +*** | |   diff --git a/ein b/ein
 +*** | |   new file mode 100644
 +*** | |   index 0000000..9d7e69f
 +*** | |   --- /dev/null
 +*** | |   +++ b/ein
 +*** | |   @@ -0,0 +1 @@
 +*** | |   +ichi
 +*** | |
 +*** * | commit COMMIT_OBJECT_NAME
 +*** |/  Author: A U Thor <author@example.com>
 +*** |
 +*** |       third
 +*** |   ---
 +*** |    ichi | 1 +
 +*** |    one  | 1 -
 +*** |    2 files changed, 1 insertion(+), 1 deletion(-)
 +*** |
 +*** |   diff --git a/ichi b/ichi
 +*** |   new file mode 100644
 +*** |   index 0000000..9d7e69f
 +*** |   --- /dev/null
 +*** |   +++ b/ichi
 +*** |   @@ -0,0 +1 @@
 +*** |   +ichi
 +*** |   diff --git a/one b/one
 +*** |   deleted file mode 100644
 +*** |   index 9d7e69f..0000000
 +*** |   --- a/one
 +*** |   +++ /dev/null
 +*** |   @@ -1 +0,0 @@
 +*** |   -ichi
 +*** |
 +*** * commit COMMIT_OBJECT_NAME
 +*** | Author: A U Thor <author@example.com>
 +*** |
 +*** |     second
 +*** | ---
 +*** |  one | 2 +-
 +*** |  1 file changed, 1 insertion(+), 1 deletion(-)
 +*** |
 +*** | diff --git a/one b/one
 +*** | index 5626abf..9d7e69f 100644
 +*** | --- a/one
 +*** | +++ b/one
 +*** | @@ -1 +1 @@
 +*** | -one
 +*** | +ichi
 +*** |
 +*** * commit COMMIT_OBJECT_NAME
 +***   Author: A U Thor <author@example.com>
 +***
 +***       initial
 +***   ---
 +***    one | 1 +
 +***    1 file changed, 1 insertion(+)
 +***
 +***   diff --git a/one b/one
 +***   new file mode 100644
 +***   index 0000000..5626abf
 +***   --- /dev/null
 +***   +++ b/one
 +***   @@ -0,0 +1 @@
 +***   +one
 +EOF
 +
 +test_expect_success 'log --line-prefix="*** " --graph with diff and stats' '
 +      git log --line-prefix="*** " --no-renames --graph --pretty=short --stat -p >actual &&
 +      sanitize_output >actual.sanitized <actual &&
 +      test_i18ncmp expect actual.sanitized
 +'
 +
+ cat >expect <<-\EOF
+ * reach
+ |
+ | A   reach.t
+ * Merge branch 'tangle'
+ *   Merge branch 'side'
+ |\
+ | * side-2
+ |
+ |   A 2
+ * Second
+ |
+ | A   one
+ * sixth
+   D   a/two
+ EOF
+ test_expect_success 'log --graph with --name-status' '
+       git log --graph --format=%s --name-status tangle..reach >actual &&
+       sanitize_output <actual >actual.sanitized &&
+       test_cmp expect actual.sanitized
+ '
+ cat >expect <<-\EOF
+ * reach
+ |
+ | reach.t
+ * Merge branch 'tangle'
+ *   Merge branch 'side'
+ |\
+ | * side-2
+ |
+ |   2
+ * Second
+ |
+ | one
+ * sixth
+   a/two
+ EOF
+ test_expect_success 'log --graph with --name-only' '
+       git log --graph --format=%s --name-only tangle..reach >actual &&
+       sanitize_output <actual >actual.sanitized &&
+       test_cmp expect actual.sanitized
+ '
  test_expect_success 'dotdot is a parent directory' '
        mkdir -p a/b &&
        ( echo sixth && echo fifth ) >expect &&
        test_cmp expect actual
  '
  
 -test_expect_success GPG 'log --graph --show-signature' '
 +test_expect_success GPG 'setup signed branch' '
        test_when_finished "git reset --hard && git checkout master" &&
        git checkout -b signed master &&
        echo foo >foo &&
        git add foo &&
 -      git commit -S -m signed_commit &&
 +      git commit -S -m signed_commit
 +'
 +
 +test_expect_success GPG 'log --graph --show-signature' '
        git log --graph --show-signature -n1 signed >actual &&
        grep "^| gpg: Signature made" actual &&
        grep "^| gpg: Good signature" actual
@@@ -1252,31 -952,6 +1300,31 @@@ test_expect_success GPG 'log --graph --
        grep "^| | gpg: Good signature" actual
  '
  
 +test_expect_success GPG '--no-show-signature overrides --show-signature' '
 +      git log -1 --show-signature --no-show-signature signed >actual &&
 +      ! grep "^gpg:" actual
 +'
 +
 +test_expect_success GPG 'log.showsignature=true behaves like --show-signature' '
 +      test_config log.showsignature true &&
 +      git log -1 signed >actual &&
 +      grep "gpg: Signature made" actual &&
 +      grep "gpg: Good signature" actual
 +'
 +
 +test_expect_success GPG '--no-show-signature overrides log.showsignature=true' '
 +      test_config log.showsignature true &&
 +      git log -1 --no-show-signature signed >actual &&
 +      ! grep "^gpg:" actual
 +'
 +
 +test_expect_success GPG '--show-signature overrides log.showsignature=false' '
 +      test_config log.showsignature false &&
 +      git log -1 --show-signature signed >actual &&
 +      grep "gpg: Signature made" actual &&
 +      grep "gpg: Good signature" actual
 +'
 +
  test_expect_success 'log --graph --no-walk is forbidden' '
        test_must_fail git log --graph --no-walk
  '