Merge branch 'nd/log-graph-configurable-colors'
authorJunio C Hamano <gitster@pobox.com>
Thu, 2 Feb 2017 21:36:58 +0000 (13:36 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 2 Feb 2017 21:36:58 +0000 (13:36 -0800)
Some people feel the default set of colors used by "git log --graph"
rather limiting. A mechanism to customize the set of colors has
been introduced.

* nd/log-graph-configurable-colors:
document behavior of empty color name
color_parse_mem: allow empty color spec
log --graph: customize the graph lines with config log.graphColors
color.c: trim leading spaces in color_parse_mem()
color.c: fix color_parse_mem() with value_len == 0

1  2 
Documentation/config.txt
color.c
graph.c
t/t4202-log.sh
diff --combined Documentation/config.txt
index af2ae4cc02af75c5cf9395e228ff8bbc6e390181,49b264566c6852b044f445888e163c270ed2aec4..fc78139c21009f6991b8bed4c56509a8a2ab2c43
@@@ -170,6 -170,9 +170,9 @@@ The position of any attributes with res
  be turned off by prefixing them with `no` or `no-` (e.g., `noreverse`,
  `no-ul`, etc).
  +
+ An empty color string produces no color effect at all. This can be used
+ to avoid coloring specific elements without disabling color entirely.
+ +
  For git's pre-defined color slots, the attributes are meant to be reset
  at the beginning of each item in the colored output. So setting
  `color.decorate.branch` to `black` will paint that branch name in a
@@@ -783,11 -786,10 +786,11 @@@ core.sparseCheckout:
        linkgit:git-read-tree[1] for more information.
  
  core.abbrev::
 -      Set the length object names are abbreviated to.  If unspecified,
 -      many commands abbreviate to 7 hexdigits, which may not be enough
 -      for abbreviated object names to stay unique for sufficiently long
 -      time.
 +      Set the length object names are abbreviated to.  If
 +      unspecified or set to "auto", an appropriate value is
 +      computed based on the approximate number of packed objects
 +      in your repository, which hopefully is enough for
 +      abbreviated object names to stay unique for some time.
  
  add.ignoreErrors::
  add.ignore-errors (deprecated)::
@@@ -954,8 -956,7 +957,8 @@@ color.branch:
        A boolean to enable/disable color in the output of
        linkgit:git-branch[1]. May be set to `always`,
        `false` (or `never`) or `auto` (or `true`), in which case colors are used
 -      only when the output is to a terminal. Defaults to false.
 +      only when the output is to a terminal. If unset, then the
 +      value of `color.ui` is used (`auto` by default).
  
  color.branch.<slot>::
        Use customized color for branch coloration. `<slot>` is one of
@@@ -970,8 -971,7 +973,8 @@@ color.diff:
        linkgit:git-log[1], and linkgit:git-show[1] will use color
        for all patches.  If it is set to `true` or `auto`, those
        commands will only use color when output is to the terminal.
 -      Defaults to false.
 +      If unset, then the value of `color.ui` is used (`auto` by
 +      default).
  +
  This does not affect linkgit:git-format-patch[1] or the
  'git-diff-{asterisk}' plumbing commands.  Can be overridden on the
@@@ -994,8 -994,7 +997,8 @@@ color.decorate.<slot>:
  color.grep::
        When set to `always`, always highlight matches.  When `false` (or
        `never`), never.  When set to `true` or `auto`, use color only
 -      when the output is written to the terminal.  Defaults to `false`.
 +      when the output is written to the terminal.  If unset, then the
 +      value of `color.ui` is used (`auto` by default).
  
  color.grep.<slot>::
        Use customized color for grep colorization.  `<slot>` specifies which
@@@ -1028,8 -1027,7 +1031,8 @@@ color.interactive:
        and displays (such as those used by "git-add --interactive" and
        "git-clean --interactive"). When false (or `never`), never.
        When set to `true` or `auto`, use colors only when the output is
 -      to the terminal. Defaults to false.
 +      to the terminal. If unset, then the value of `color.ui` is
 +      used (`auto` by default).
  
  color.interactive.<slot>::
        Use customized color for 'git add --interactive' and 'git clean
@@@ -1045,15 -1043,13 +1048,15 @@@ color.showBranch:
        A boolean to enable/disable color in the output of
        linkgit:git-show-branch[1]. May be set to `always`,
        `false` (or `never`) or `auto` (or `true`), in which case colors are used
 -      only when the output is to a terminal. Defaults to false.
 +      only when the output is to a terminal. If unset, then the
 +      value of `color.ui` is used (`auto` by default).
  
  color.status::
        A boolean to enable/disable color in the output of
        linkgit:git-status[1]. May be set to `always`,
        `false` (or `never`) or `auto` (or `true`), in which case colors are used
 -      only when the output is to a terminal. Defaults to false.
 +      only when the output is to a terminal. If unset, then the
 +      value of `color.ui` is used (`auto` by default).
  
  color.status.<slot>::
        Use customized color for status colorization. `<slot>` is
@@@ -1373,7 -1369,7 +1376,7 @@@ fsck.skipList:
  gc.aggressiveDepth::
        The depth parameter used in the delta compression
        algorithm used by 'git gc --aggressive'.  This defaults
 -      to 250.
 +      to 50.
  
  gc.aggressiveWindow::
        The window size parameter used in the delta compression
@@@ -1410,9 -1406,7 +1413,9 @@@ gc.pruneExpire:
        Override the grace period with this config variable.  The value
        "now" may be used to disable this grace period and always prune
        unreachable objects immediately, or "never" may be used to
 -      suppress pruning.
 +      suppress pruning.  This feature helps prevent corruption when
 +      'git gc' runs concurrently with another process writing to the
 +      repository; see the "NOTES" section of linkgit:git-gc[1].
  
  gc.worktreePruneExpire::
        When 'git gc' is run, it calls
@@@ -1739,20 -1733,6 +1742,20 @@@ http.emptyAuth:
        a username in the URL, as libcurl normally requires a username for
        authentication.
  
 +http.delegation::
 +      Control GSSAPI credential delegation. The delegation is disabled
 +      by default in libcurl since version 7.21.7. Set parameter to tell
 +      the server what it is allowed to delegate when it comes to user
 +      credentials. Used with GSS/kerberos. Possible values are:
 ++
 +--
 +* `none` - Don't allow any delegation.
 +* `policy` - Delegates if and only if the OK-AS-DELEGATE flag is set in the
 +  Kerberos service ticket, which is a matter of realm policy.
 +* `always` - Unconditionally allow the server to delegate.
 +--
 +
 +
  http.extraHeader::
        Pass an additional HTTP header when communicating with a server.  If
        more than one such entry exists, all of them are added as extra
@@@ -1894,16 -1874,6 +1897,16 @@@ http.userAgent:
        of common USER_AGENT strings (but not including those like git/1.7.1).
        Can be overridden by the `GIT_HTTP_USER_AGENT` environment variable.
  
 +http.followRedirects::
 +      Whether git should follow HTTP redirects. If set to `true`, git
 +      will transparently follow any redirect issued by a server it
 +      encounters. If set to `false`, git will treat all redirects as
 +      errors. If set to `initial`, git will follow redirects only for
 +      the initial request to a remote, but not for subsequent
 +      follow-up HTTP requests. Since git uses the redirected URL as
 +      the base for the follow-up requests, this is generally
 +      sufficient. The default is `initial`.
 +
  http.<url>.*::
        Any of the http.* options above can be applied selectively to some URLs.
        For a config key to match a URL, each element of the config key is
@@@ -2036,6 -2006,10 +2039,10 @@@ log.follow:
        i.e. it cannot be used to follow multiple files and does not work well
        on non-linear history.
  
+ log.graphColors::
+       A list of colors, separated by commas, that can be used to draw
+       history lines in `git log --graph`.
  log.showRoot::
        If true, the initial commit will be shown as a big creation event.
        This is equivalent to a diff against an empty tree.
@@@ -2321,52 -2295,6 +2328,52 @@@ pretty.<name>:
        Note that an alias with the same name as a built-in format
        will be silently ignored.
  
 +protocol.allow::
 +      If set, provide a user defined default policy for all protocols which
 +      don't explicitly have a policy (`protocol.<name>.allow`).  By default,
 +      if unset, known-safe protocols (http, https, git, ssh, file) have a
 +      default policy of `always`, known-dangerous protocols (ext) have a
 +      default policy of `never`, and all other protocols have a default
 +      policy of `user`.  Supported policies:
 ++
 +--
 +
 +* `always` - protocol is always able to be used.
 +
 +* `never` - protocol is never able to be used.
 +
 +* `user` - protocol is only able to be used when `GIT_PROTOCOL_FROM_USER` is
 +  either unset or has a value of 1.  This policy should be used when you want a
 +  protocol to be directly usable by the user but don't want it used by commands which
 +  execute clone/fetch/push commands without user input, e.g. recursive
 +  submodule initialization.
 +
 +--
 +
 +protocol.<name>.allow::
 +      Set a policy to be used by protocol `<name>` with clone/fetch/push
 +      commands. See `protocol.allow` above for the available policies.
 ++
 +The protocol names currently used by git are:
 ++
 +--
 +  - `file`: any local file-based path (including `file://` URLs,
 +    or local paths)
 +
 +  - `git`: the anonymous git protocol over a direct TCP
 +    connection (or proxy, if configured)
 +
 +  - `ssh`: git over ssh (including `host:path` syntax,
 +    `ssh://`, etc).
 +
 +  - `http`: git over http, both "smart http" and "dumb http".
 +    Note that this does _not_ include `https`; if you want to configure
 +    both, you must do so individually.
 +
 +  - any external helpers are named by their protocol (e.g., use
 +    `hg` to allow the `git-remote-hg` helper)
 +--
 +
  pull.ff::
        By default, Git does not create an extra merge commit when merging
        a commit that is a descendant of the current commit. Instead, the
@@@ -2509,7 -2437,7 +2516,7 @@@ rebase.missingCommitsCheck:
        command in the todo-list.
        Defaults to "ignore".
  
 -rebase.instructionFormat
 +rebase.instructionFormat::
        A format string, as specified in linkgit:git-log[1], to be used for
        the instruction list during an interactive rebase.  The format will automatically
        have the long commit hash prepended to the format.
@@@ -2596,12 -2524,6 +2603,12 @@@ receive.unpackLimit:
        especially on slow filesystems.  If not set, the value of
        `transfer.unpackLimit` is used instead.
  
 +receive.maxInputSize::
 +      If the size of the incoming pack stream is larger than this
 +      limit, then git-receive-pack will error out, instead of
 +      accepting the pack file. If not set or set to 0, then the size
 +      is unlimited.
 +
  receive.denyDeletes::
        If set to true, git-receive-pack will deny a ref update that deletes
        the ref. Use this to prevent such a ref deletion via a push.
@@@ -2884,13 -2806,12 +2891,13 @@@ stash.showStat:
        option will show diffstat of the stash.  Defaults to true.
        See description of 'show' command in linkgit:git-stash[1].
  
 -submodule.<name>.path::
  submodule.<name>.url::
 -      The path within this project and URL for a submodule. These
 -      variables are initially populated by 'git submodule init'. See
 -      linkgit:git-submodule[1] and linkgit:gitmodules[5] for
 -      details.
 +      The URL for a submodule. This variable is copied from the .gitmodules
 +      file to the git config via 'git submodule init'. The user can change
 +      the configured URL before obtaining the submodule via 'git submodule
 +      update'. After obtaining the submodule, the presence of this variable
 +      is used as a sign whether the submodule is of interest to git commands.
 +      See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
  
  submodule.<name>.update::
        The default update procedure for a submodule. This variable
@@@ -2933,18 -2854,6 +2940,18 @@@ submodule.fetchJobs:
        in parallel. A value of 0 will give some reasonable default.
        If unset, it defaults to 1.
  
 +submodule.alternateLocation::
 +      Specifies how the submodules obtain alternates when submodules are
 +      cloned. Possible values are `no`, `superproject`.
 +      By default `no` is assumed, which doesn't add references. When the
 +      value is set to `superproject` the submodule to be cloned computes
 +      its alternates location relative to the superprojects alternate.
 +
 +submodule.alternateErrorStrategy
 +      Specifies how to treat errors with the alternates for a submodule
 +      as computed via `submodule.alternateLocation`. Possible values are
 +      `ignore`, `info`, `die`. Default is `die`.
 +
  tag.forceSignAnnotated::
        A boolean to specify whether annotated tags created should be GPG signed.
        If `--annotate` is specified on the command line, it takes
@@@ -2989,11 -2898,6 +2996,11 @@@ is omitted from the advertisements but 
  `refs/namespaces/bar/refs/heads/master` are still advertised as so-called
  "have" lines. In order to match refs before stripping, add a `^` in front of
  the ref name. If you combine `!` and `^`, `!` must be specified first.
 ++
 +Even if you hide refs, a client may still be able to steal the target
 +objects via the techniques described in the "SECURITY" section of the
 +linkgit:gitnamespaces[7] man page; it's best to keep private data in a
 +separate repository.
  
  transfer.unpackLimit::
        When `fetch.unpackLimit` or `receive.unpackLimit` are
  uploadarchive.allowUnreachable::
        If true, allow clients to use `git archive --remote` to request
        any tree, whether reachable from the ref tips or not. See the
 -      discussion in the `SECURITY` section of
 +      discussion in the "SECURITY" section of
        linkgit:git-upload-archive[1] for more details. Defaults to
        `false`.
  
@@@ -3017,23 -2921,12 +3024,23 @@@ uploadpack.allowTipSHA1InWant:
        When `uploadpack.hideRefs` is in effect, allow `upload-pack`
        to accept a fetch request that asks for an object at the tip
        of a hidden ref (by default, such a request is rejected).
 -      see also `uploadpack.hideRefs`.
 +      See also `uploadpack.hideRefs`.  Even if this is false, a client
 +      may be able to steal objects via the techniques described in the
 +      "SECURITY" section of the linkgit:gitnamespaces[7] man page; it's
 +      best to keep private data in a separate repository.
  
  uploadpack.allowReachableSHA1InWant::
        Allow `upload-pack` to accept a fetch request that asks for an
        object that is reachable from any ref tip. However, note that
        calculating object reachability is computationally expensive.
 +      Defaults to `false`.  Even if this is false, a client may be able
 +      to steal objects via the techniques described in the "SECURITY"
 +      section of the linkgit:gitnamespaces[7] man page; it's best to
 +      keep private data in a separate repository.
 +
 +uploadpack.allowAnySHA1InWant::
 +      Allow `upload-pack` to accept a fetch request that asks for any
 +      object at all.
        Defaults to `false`.
  
  uploadpack.keepAlive::
@@@ -3113,39 -3006,17 +3120,39 @@@ user.signingKey:
        This option is passed unchanged to gpg's --local-user parameter,
        so you may specify a key using any method that gpg supports.
  
 -versionsort.prereleaseSuffix::
 -      When version sort is used in linkgit:git-tag[1], prerelease
 -      tags (e.g. "1.0-rc1") may appear after the main release
 -      "1.0". By specifying the suffix "-rc" in this variable,
 -      "1.0-rc1" will appear before "1.0".
 -+
 -This variable can be specified multiple times, once per suffix. The
 -order of suffixes in the config file determines the sorting order
 -(e.g. if "-pre" appears before "-rc" in the config file then 1.0-preXX
 -is sorted before 1.0-rcXX). The sorting order between different
 -suffixes is undefined if they are in multiple config files.
 +versionsort.prereleaseSuffix (deprecated)::
 +      Deprecated alias for `versionsort.suffix`.  Ignored if
 +      `versionsort.suffix` is set.
 +
 +versionsort.suffix::
 +      Even when version sort is used in linkgit:git-tag[1], tagnames
 +      with the same base version but different suffixes are still sorted
 +      lexicographically, resulting e.g. in prerelease tags appearing
 +      after the main release (e.g. "1.0-rc1" after "1.0").  This
 +      variable can be specified to determine the sorting order of tags
 +      with different suffixes.
 ++
 +By specifying a single suffix in this variable, any tagname containing
 +that suffix will appear before the corresponding main release.  E.g. if
 +the variable is set to "-rc", then all "1.0-rcX" tags will appear before
 +"1.0".  If specified multiple times, once per suffix, then the order of
 +suffixes in the configuration will determine the sorting order of tagnames
 +with those suffixes.  E.g. if "-pre" appears before "-rc" in the
 +configuration, then all "1.0-preX" tags will be listed before any
 +"1.0-rcX" tags.  The placement of the main release tag relative to tags
 +with various suffixes can be determined by specifying the empty suffix
 +among those other suffixes.  E.g. if the suffixes "-rc", "", "-ck" and
 +"-bfs" appear in the configuration in this order, then all "v4.8-rcX" tags
 +are listed first, followed by "v4.8", then "v4.8-ckX" and finally
 +"v4.8-bfsX".
 ++
 +If more than one suffixes match the same tagname, then that tagname will
 +be sorted according to the suffix which starts at the earliest position in
 +the tagname.  If more than one different matching suffixes start at
 +that earliest position, then that tagname will be sorted according to the
 +longest of those suffixes.
 +The sorting order between different suffixes is undefined if they are
 +in multiple config files.
  
  web.browser::
        Specify a web browser that may be used by some commands.
diff --combined color.c
index 1b95e6b2a7bb1601fa5862de8989775e197c7b41,2925a819b219453a5f5be974c034babccdfa8417..dee61557e03f452f1e20ce6c8c467203aa8aed60
+++ b/color.c
@@@ -207,7 -207,17 +207,17 @@@ int color_parse_mem(const char *value, 
        struct color fg = { COLOR_UNSPECIFIED };
        struct color bg = { COLOR_UNSPECIFIED };
  
-       if (!strncasecmp(value, "reset", len)) {
+       while (len > 0 && isspace(*ptr)) {
+               ptr++;
+               len--;
+       }
+       if (!len) {
+               dst[0] = '\0';
+               return 0;
+       }
+       if (!strncasecmp(ptr, "reset", len)) {
                xsnprintf(dst, end - dst, GIT_COLOR_RESET);
                return 0;
        }
        /* [fg [bg]] [attr]... */
        while (len > 0) {
                const char *word = ptr;
 -              struct color c;
 +              struct color c = { COLOR_UNSPECIFIED };
                int val, wordlen = 0;
  
                while (len > 0 && !isspace(word[wordlen])) {
diff --combined graph.c
index d4e8519c904df6e18869de527f7fe6d57adf2a26,00aeee36d8a048d56ab686b136cc68bc111f9929..0649007704ac635af163a4e1016121744a951d1c
+++ b/graph.c
@@@ -2,7 -2,9 +2,8 @@@
  #include "commit.h"
  #include "color.h"
  #include "graph.h"
 -#include "diff.h"
  #include "revision.h"
+ #include "argv-array.h"
  
  /* Internal API */
  
@@@ -27,15 -29,8 +28,15 @@@ static void graph_padding_line(struct g
   * responsible for printing this line's graph (perhaps via
   * graph_show_commit() or graph_show_oneline()) before calling
   * graph_show_strbuf().
 + *
 + * Note that unlike some other graph display functions, you must pass the file
 + * handle directly. It is assumed that this is the same file handle as the
 + * file specified by the graph diff options. This is necessary so that
 + * graph_show_strbuf can be called even with a NULL graph.
   */
 -static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
 +static void graph_show_strbuf(struct git_graph *graph,
 +                            FILE *file,
 +                            struct strbuf const *sb);
  
  /*
   * TODO:
@@@ -65,20 -60,29 +66,40 @@@ enum graph_state 
        GRAPH_COLLAPSING
  };
  
 +static void graph_show_line_prefix(const struct diff_options *diffopt)
 +{
 +      if (!diffopt || !diffopt->line_prefix)
 +              return;
 +
 +      fwrite(diffopt->line_prefix,
 +             sizeof(char),
 +             diffopt->line_prefix_length,
 +             diffopt->file);
 +}
 +
  static const char **column_colors;
  static unsigned short column_colors_max;
  
+ static void parse_graph_colors_config(struct argv_array *colors, const char *string)
+ {
+       const char *end, *start;
+       start = string;
+       end = string + strlen(string);
+       while (start < end) {
+               const char *comma = strchrnul(start, ',');
+               char color[COLOR_MAXLEN];
+               if (!color_parse_mem(start, comma - start, color))
+                       argv_array_push(colors, color);
+               else
+                       warning(_("ignore invalid color '%.*s' in log.graphColors"),
+                               (int)(comma - start), start);
+               start = comma + 1;
+       }
+       argv_array_push(colors, GIT_COLOR_RESET);
+ }
  void graph_set_column_colors(const char **colors, unsigned short colors_max)
  {
        column_colors = colors;
@@@ -212,35 -216,34 +233,48 @@@ static struct strbuf *diff_output_prefi
        static struct strbuf msgbuf = STRBUF_INIT;
  
        assert(opt);
 -      assert(graph);
  
 -      opt->output_prefix_length = graph->width;
        strbuf_reset(&msgbuf);
 -      graph_padding_line(graph, &msgbuf);
 +      if (opt->line_prefix)
 +              strbuf_add(&msgbuf, opt->line_prefix,
 +                         opt->line_prefix_length);
 +      if (graph)
 +              graph_padding_line(graph, &msgbuf);
        return &msgbuf;
  }
  
 +static const struct diff_options *default_diffopt;
 +
 +void graph_setup_line_prefix(struct diff_options *diffopt)
 +{
 +      default_diffopt = diffopt;
 +
 +      /* setup an output prefix callback if necessary */
 +      if (diffopt && !diffopt->output_prefix)
 +              diffopt->output_prefix = diff_output_prefix_callback;
 +}
 +
 +
  struct git_graph *graph_init(struct rev_info *opt)
  {
        struct git_graph *graph = xmalloc(sizeof(struct git_graph));
  
-       if (!column_colors)
-               graph_set_column_colors(column_colors_ansi,
-                                       column_colors_ansi_max);
+       if (!column_colors) {
+               char *string;
+               if (git_config_get_string("log.graphcolors", &string)) {
+                       /* not configured -- use default */
+                       graph_set_column_colors(column_colors_ansi,
+                                               column_colors_ansi_max);
+               } else {
+                       static struct argv_array custom_colors = ARGV_ARRAY_INIT;
+                       argv_array_clear(&custom_colors);
+                       parse_graph_colors_config(&custom_colors, string);
+                       free(string);
+                       /* graph_set_column_colors takes a max-index, not a count */
+                       graph_set_column_colors(custom_colors.argv,
+                                               custom_colors.argc - 1);
+               }
+       }
  
        graph->commit = NULL;
        graph->revs = opt;
         */
        opt->diffopt.output_prefix = diff_output_prefix_callback;
        opt->diffopt.output_prefix_data = graph;
 -      opt->diffopt.output_prefix_length = 0;
  
        return graph;
  }
@@@ -1175,7 -1179,6 +1209,7 @@@ int graph_next_line(struct git_graph *g
  static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
  {
        int i;
 +      int chars_written = 0;
  
        if (graph->state != GRAPH_COMMIT) {
                graph_next_line(graph, sb);
         */
        for (i = 0; i < graph->num_columns; i++) {
                struct column *col = &graph->columns[i];
 +
                strbuf_write_column(sb, col, '|');
 -              if (col->commit == graph->commit && graph->num_parents > 2)
 -                      strbuf_addchars(sb, ' ', (graph->num_parents - 2) * 2);
 -              else
 +              chars_written++;
 +
 +              if (col->commit == graph->commit && graph->num_parents > 2) {
 +                      int len = (graph->num_parents - 2) * 2;
 +                      strbuf_addchars(sb, ' ', len);
 +                      chars_written += len;
 +              } else {
                        strbuf_addch(sb, ' ');
 +                      chars_written++;
 +              }
        }
  
 -      graph_pad_horizontally(graph, sb, graph->num_columns);
 +      graph_pad_horizontally(graph, sb, chars_written);
  
        /*
         * Update graph->prev_state since we have output a padding line
@@@ -1223,8 -1219,6 +1257,8 @@@ void graph_show_commit(struct git_grap
        struct strbuf msgbuf = STRBUF_INIT;
        int shown_commit_line = 0;
  
 +      graph_show_line_prefix(default_diffopt);
 +
        if (!graph)
                return;
  
                shown_commit_line = graph_next_line(graph, &msgbuf);
                fwrite(msgbuf.buf, sizeof(char), msgbuf.len,
                        graph->revs->diffopt.file);
 -              if (!shown_commit_line)
 +              if (!shown_commit_line) {
                        putc('\n', graph->revs->diffopt.file);
 +                      graph_show_line_prefix(&graph->revs->diffopt);
 +              }
                strbuf_setlen(&msgbuf, 0);
        }
  
@@@ -1256,8 -1248,6 +1290,8 @@@ void graph_show_oneline(struct git_grap
  {
        struct strbuf msgbuf = STRBUF_INIT;
  
 +      graph_show_line_prefix(default_diffopt);
 +
        if (!graph)
                return;
  
@@@ -1270,8 -1260,6 +1304,8 @@@ void graph_show_padding(struct git_grap
  {
        struct strbuf msgbuf = STRBUF_INIT;
  
 +      graph_show_line_prefix(default_diffopt);
 +
        if (!graph)
                return;
  
@@@ -1285,8 -1273,6 +1319,8 @@@ int graph_show_remainder(struct git_gra
        struct strbuf msgbuf = STRBUF_INIT;
        int shown = 0;
  
 +      graph_show_line_prefix(default_diffopt);
 +
        if (!graph)
                return 0;
  
                strbuf_setlen(&msgbuf, 0);
                shown = 1;
  
 -              if (!graph_is_commit_finished(graph))
 +              if (!graph_is_commit_finished(graph)) {
                        putc('\n', graph->revs->diffopt.file);
 -              else
 +                      graph_show_line_prefix(&graph->revs->diffopt);
 +              } else {
                        break;
 +              }
        }
        strbuf_release(&msgbuf);
  
        return shown;
  }
  
 -
 -static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
 +static void graph_show_strbuf(struct git_graph *graph,
 +                            FILE *file,
 +                            struct strbuf const *sb)
  {
        char *p;
  
 -      if (!graph) {
 -              fwrite(sb->buf, sizeof(char), sb->len,
 -                      graph->revs->diffopt.file);
 -              return;
 -      }
 -
        /*
         * Print the strbuf line by line,
         * and display the graph info before each line but the first.
                } else {
                        len = (sb->buf + sb->len) - p;
                }
 -              fwrite(p, sizeof(char), len, graph->revs->diffopt.file);
 +              fwrite(p, sizeof(char), len, file);
                if (next_p && *next_p != '\0')
                        graph_show_oneline(graph);
                p = next_p;
  }
  
  void graph_show_commit_msg(struct git_graph *graph,
 +                         FILE *file,
                           struct strbuf const *sb)
  {
        int newline_terminated;
  
 -      if (!graph) {
 -              /*
 -               * If there's no graph, just print the message buffer.
 -               *
 -               * The message buffer for CMIT_FMT_ONELINE and
 -               * CMIT_FMT_USERFORMAT are already missing a terminating
 -               * newline.  All of the other formats should have it.
 -               */
 -              fwrite(sb->buf, sizeof(char), sb->len,
 -                      graph->revs->diffopt.file);
 -              return;
 -      }
 -
 -      newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');
 -
        /*
         * Show the commit message
         */
 -      graph_show_strbuf(graph, sb);
 +      graph_show_strbuf(graph, file, sb);
 +
 +      if (!graph)
 +              return;
 +
 +      newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');
  
        /*
         * If there is more output needed for this commit, show it now
                 * new line.
                 */
                if (!newline_terminated)
 -                      putc('\n', graph->revs->diffopt.file);
 +                      putc('\n', file);
  
                graph_show_remainder(graph);
  
                 * If sb ends with a newline, our output should too.
                 */
                if (newline_terminated)
 -                      putc('\n', graph->revs->diffopt.file);
 +                      putc('\n', file);
        }
  }
diff --combined t/t4202-log.sh
index 1ccbd5948a735f692469e181933c97e8632404b4,1edbb1e7f1d51778193dbeb071fdc94d85e283a5..08ea725de3307c886d8f341b49c61c0d3436f91c
@@@ -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,27 -313,28 +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
@@@ -913,283 -889,6 +935,283 @@@ 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
 +'
 +
  test_expect_success 'dotdot is a parent directory' '
        mkdir -p a/b &&
        ( echo sixth && echo fifth ) >expect &&