{"thread":{"id":"47149","subject":"[RFC PATCH 0/4] blame: (dim rep. metadata lines or fields, decay date coloring)","startedAt":"2017-11-10T01:10:15Z","lastAt":"2018-02-08T21:19:17Z","messageCount":17,"participants":["Stefan Beller","Junio C Hamano","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"332139","messageId":"20171110011002.10179-1-sbeller@google.com","threadId":"47149","inReplyTo":null,"subject":"[RFC PATCH 0/4] blame: (dim rep. metadata lines or fields, decay date coloring)","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-11-10T01:09:58Z","receivedAt":"2017-11-10T01:10:15Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"This is picking up [1], but presenting it in another approach,\nas I realized these are orthogonal features:\n* dimming repeated lines/fields of information\n* giving a quick visual information how old (as a proxy for 'well tested')\n  a line of code is.\n  \nBoth features are configurable.\n\nAny feedback welcome.\n\nThanks,\nStefan\n\n[1] https://public-inbox.org/git/20170613023151.9688-1-sbeller@google.com/\n\nStefan Beller (4):\n  color.h: modernize header\n  builtin/blame: dim uninteresting metadata\n  builtin/blame: add option to color metadata fields separately\n  builtin/blame: highlight recently changed lines\n\n Documentation/config.txt |  25 ++++++\n builtin/blame.c          | 216 ++++++++++++++++++++++++++++++++++++++++++-----\n color.c                  |   2 -\n color.h                  |  49 ++++++++---\n t/t8012-blame-colors.sh  |  56 ++++++++++++\n 5 files changed, 313 insertions(+), 35 deletions(-)\n create mode 100755 t/t8012-blame-colors.sh\n\n-- \n2.15.0.128.gcadd42da22\n\n"},{"id":"332140","messageId":"20171110011002.10179-2-sbeller@google.com","threadId":"47149","inReplyTo":"20171110011002.10179-1-sbeller@google.com","subject":"[PATCH 1/4] color.h: modernize header","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-11-10T01:09:59Z","receivedAt":"2017-11-10T01:10:17Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Signed-off-by: Stefan Beller <sbeller@google.com>\n---\n color.c |  2 --\n color.h | 48 +++++++++++++++++++++++++++++++++++-------------\n 2 files changed, 35 insertions(+), 15 deletions(-)\n\ndiff --git a/color.c b/color.c\nindex 9a9261ac16..e43da3ce53 100644\n--- a/color.c\n+++ b/color.c\n@@ -400,8 +400,6 @@ static int color_vfprintf(FILE *fp, const char *color, const char *fmt,\n \treturn r;\n }\n \n-\n-\n int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)\n {\n \tva_list args;\ndiff --git a/color.h b/color.h\nindex fd2b688dfb..6cd632c0d8 100644\n--- a/color.h\n+++ b/color.h\n@@ -72,26 +72,48 @@ extern int color_stdout_is_tty;\n  * Use the first one if you need only color config; the second is a convenience\n  * if you are just going to change to git_default_config, too.\n  */\n-int git_color_config(const char *var, const char *value, void *cb);\n-int git_color_default_config(const char *var, const char *value, void *cb);\n+extern int git_color_config(const char *var, const char *value, void *cb);\n+extern int git_color_default_config(const char *var, const char *value, void *cb);\n \n /*\n- * Set the color buffer (which must be COLOR_MAXLEN bytes)\n- * to the raw color bytes; this is useful for initializing\n+ * Set the color buffer `dst` (which must be COLOR_MAXLEN bytes)\n+ * to the raw color bytes `color_bytes`; this is useful for initializing\n  * default color variables.\n  */\n-void color_set(char *dst, const char *color_bytes);\n+extern void color_set(char *dst, const char *color_bytes);\n \n-int git_config_colorbool(const char *var, const char *value);\n-int want_color(int var);\n-int color_parse(const char *value, char *dst);\n-int color_parse_mem(const char *value, int len, char *dst);\n+/*\n+ * Parses a config option, which can be a boolean or one of\n+ * \"never\", \"auto\", \"always\". Returns the constant for the given setting.\n+ */\n+extern int git_config_colorbool(const char *var, const char *value);\n+\n+/* Is the output supposed to be colored? Resolve and cache the 'auto' setting */\n+extern int want_color(int var);\n+\n+/*\n+ * Translate the human readable color string from `value` and into\n+ * terminal color codes and store them in `dst`\n+ */\n+extern int color_parse(const char *value, char *dst);\n+extern int color_parse_mem(const char *value, int len, char *dst);\n+\n+/*\n+ * Print the format string `fmt`, encapsulated by setting and resetting the\n+ * color. Omits the color encapsulation if `color` is NULL.\n+ * The `color_fprintf_ln` prints a new line after resetting the color.\n+ * The `color_print_strbuf` prints the given pre-formatted strbuf instead.\n+ */\n __attribute__((format (printf, 3, 4)))\n-int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);\n+extern int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);\n __attribute__((format (printf, 3, 4)))\n-int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);\n-void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb);\n+extern int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);\n+extern void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb);\n \n-int color_is_nil(const char *color);\n+/*\n+ * Check if the given color is GIT_COLOR_NIL that means \"no color selected\".\n+ * The application needs to replace the color with the actual desired color.\n+ */\n+extern int color_is_nil(const char *color);\n \n #endif /* COLOR_H */\n-- \n2.15.0.128.gcadd42da22\n\n"},{"id":"332141","messageId":"20171110011002.10179-3-sbeller@google.com","threadId":"47149","inReplyTo":"20171110011002.10179-1-sbeller@google.com","subject":"[PATCH 2/4] builtin/blame: dim uninteresting metadata","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-11-10T01:10:00Z","receivedAt":"2017-11-10T01:10:22Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"When using git-blame lots of lines contain redundant information, for\nexample in hunks that consist of multiple lines, the metadata (commit\nname, author, date) are repeated. A reader may not be interested in those,\nso offer an option to color the information that is repeated from the\nprevious line differently.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n Documentation/config.txt |  5 ++++\n builtin/blame.c          | 68 ++++++++++++++++++++++++++++++++++++++----------\n color.h                  |  1 +\n t/t8012-blame-colors.sh  | 18 +++++++++++++\n 4 files changed, 78 insertions(+), 14 deletions(-)\n create mode 100755 t/t8012-blame-colors.sh\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 5f0d62753d..85dfdc6a9b 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1189,6 +1189,11 @@ color.status.<slot>::\n \tstatus short-format), or\n \t`unmerged` (files which have unmerged changes).\n \n+color.blame.repeatedMeta::\n+\tUse the customized color for the part of git-blame output that\n+\tis repeated meta information per line (such as commit id,\n+\tauthor name, date and timezone). Defaults to dark gray.\n+\n color.ui::\n \tThis variable determines the default value for variables such\n \tas `color.diff` and `color.grep` that control the use of color\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 67adaef4d8..d15e901357 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -7,6 +7,7 @@\n \n #include \"cache.h\"\n #include \"config.h\"\n+#include \"color.h\"\n #include \"builtin.h\"\n #include \"commit.h\"\n #include \"diff.h\"\n@@ -46,6 +47,7 @@ static int xdl_opts;\n static int abbrev = -1;\n static int no_whole_file_rename;\n static int show_progress;\n+static char *repeated_meta_color;\n \n static struct date_mode blame_date_mode = { DATE_ISO8601 };\n static size_t blame_date_width;\n@@ -316,10 +318,11 @@ static const char *format_time(timestamp_t time, const char *tz_str,\n #define OUTPUT_PORCELAIN\t010\n #define OUTPUT_SHOW_NAME\t020\n #define OUTPUT_SHOW_NUMBER\t040\n-#define OUTPUT_SHOW_SCORE      0100\n-#define OUTPUT_NO_AUTHOR       0200\n+#define OUTPUT_SHOW_SCORE\t0100\n+#define OUTPUT_NO_AUTHOR\t0200\n #define OUTPUT_SHOW_EMAIL\t0400\n-#define OUTPUT_LINE_PORCELAIN 01000\n+#define OUTPUT_LINE_PORCELAIN \t01000\n+#define OUTPUT_COLOR_LINE\t02000\n \n static void emit_porcelain_details(struct blame_origin *suspect, int repeat)\n {\n@@ -367,6 +370,28 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,\n \t\tputchar('\\n');\n }\n \n+static inline void colors_unset(const char **use_color, const char **reset_color)\n+{\n+\t*use_color = \"\";\n+\t*reset_color = \"\";\n+}\n+\n+static inline void colors_set(const char **use_color, const char **reset_color)\n+{\n+\t*use_color = repeated_meta_color;\n+\t*reset_color = GIT_COLOR_RESET;\n+}\n+\n+static void setup_line_color(int opt, int cnt,\n+\t\t\t     const char **use_color,\n+\t\t\t     const char **reset_color)\n+{\n+\tcolors_unset(use_color, reset_color);\n+\n+\tif ((opt & OUTPUT_COLOR_LINE) && cnt > 0)\n+\t\tcolors_set(use_color, reset_color);\n+}\n+\n static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt)\n {\n \tint cnt;\n@@ -383,6 +408,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \tfor (cnt = 0; cnt < ent->num_lines; cnt++) {\n \t\tchar ch;\n \t\tint length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;\n+\t\tconst char *col, *rcol;\n \n \t\tif (suspect->commit->object.flags & UNINTERESTING) {\n \t\t\tif (blank_boundary)\n@@ -393,7 +419,8 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t}\n \t\t}\n \n-\t\tprintf(\"%.*s\", length, hex);\n+\t\tsetup_line_color(opt, cnt, &col, &rcol);\n+\t\tprintf(\"%s%.*s%s\", col, length, hex, rcol);\n \t\tif (opt & OUTPUT_ANNOTATE_COMPAT) {\n \t\t\tconst char *name;\n \t\t\tif (opt & OUTPUT_SHOW_EMAIL)\n@@ -406,16 +433,17 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t       ent->lno + 1 + cnt);\n \t\t} else {\n \t\t\tif (opt & OUTPUT_SHOW_SCORE)\n-\t\t\t\tprintf(\" %*d %02d\",\n+\t\t\t\tprintf(\" %s%*d %02d%s\", col,\n \t\t\t\t       max_score_digits, ent->score,\n-\t\t\t\t       ent->suspect->refcnt);\n+\t\t\t\t       ent->suspect->refcnt, rcol);\n \t\t\tif (opt & OUTPUT_SHOW_NAME)\n-\t\t\t\tprintf(\" %-*.*s\", longest_file, longest_file,\n-\t\t\t\t       suspect->path);\n+\t\t\t\tprintf(\" %s%-*.*s%s\", col, longest_file,\n+\t\t\t\t\t\t      longest_file,\n+\t\t\t\t\t\t      suspect->path,\n+\t\t\t\t\t\t      rcol);\n \t\t\tif (opt & OUTPUT_SHOW_NUMBER)\n-\t\t\t\tprintf(\" %*d\", max_orig_digits,\n-\t\t\t\t       ent->s_lno + 1 + cnt);\n-\n+\t\t\t\tprintf(\" %s%*d%s\", col, max_orig_digits,\n+\t\t\t\t       ent->s_lno + 1 + cnt, rcol);\n \t\t\tif (!(opt & OUTPUT_NO_AUTHOR)) {\n \t\t\t\tconst char *name;\n \t\t\t\tint pad;\n@@ -424,11 +452,12 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t\telse\n \t\t\t\t\tname = ci.author.buf;\n \t\t\t\tpad = longest_author - utf8_strwidth(name);\n-\t\t\t\tprintf(\" (%s%*s %10s\",\n+\t\t\t\tprintf(\" %s(%s%*s %10s%s\", col,\n \t\t\t\t       name, pad, \"\",\n \t\t\t\t       format_time(ci.author_time,\n \t\t\t\t\t\t   ci.author_tz.buf,\n-\t\t\t\t\t\t   show_raw_time));\n+\t\t\t\t\t\t   show_raw_time),\n+\t\t\t\t       rcol);\n \t\t\t}\n \t\t\tprintf(\" %*d) \",\n \t\t\t       max_digits, ent->lno + 1 + cnt);\n@@ -607,6 +636,12 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \t\tparse_date_format(value, &blame_date_mode);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"color.blame.repeatedmeta\")) {\n+\t\tif (color_parse_mem(value, strlen(value), repeated_meta_color))\n+\t\t\twarning(_(\"ignore invalid color '%s' in color.blame.repeatedMeta\"),\n+\t\t\t\tvalue);\n+\t\treturn 0;\n+\t}\n \n \tif (git_diff_heuristic_config(var, value, cb) < 0)\n \t\treturn -1;\n@@ -681,6 +716,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('s', NULL, &output_option, N_(\"Suppress author name and timestamp (Default: off)\"), OUTPUT_NO_AUTHOR),\n \t\tOPT_BIT('e', \"show-email\", &output_option, N_(\"Show author email instead of name (Default: off)\"), OUTPUT_SHOW_EMAIL),\n \t\tOPT_BIT('w', NULL, &xdl_opts, N_(\"Ignore whitespace differences\"), XDF_IGNORE_WHITESPACE),\n+\t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line\"), OUTPUT_COLOR_LINE),\n \n \t\t/*\n \t\t * The following two options are parsed by parse_revision_opt()\n@@ -940,8 +976,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \n \tblame_coalesce(&sb);\n \n-\tif (!(output_option & OUTPUT_PORCELAIN))\n+\tif (!(output_option & OUTPUT_PORCELAIN)) {\n \t\tfind_alignment(&sb, &output_option);\n+\t\tif ((output_option & OUTPUT_COLOR_LINE) &&\n+\t\t    !repeated_meta_color)\n+\t\t\trepeated_meta_color = GIT_COLOR_DARK;\n+\t}\n \n \toutput(&sb, output_option);\n \tfree((void *)sb.final_buf);\ndiff --git a/color.h b/color.h\nindex 6cd632c0d8..fce5588df9 100644\n--- a/color.h\n+++ b/color.h\n@@ -30,6 +30,7 @@ struct strbuf;\n #define GIT_COLOR_BLUE\t\t\"\\033[34m\"\n #define GIT_COLOR_MAGENTA\t\"\\033[35m\"\n #define GIT_COLOR_CYAN\t\t\"\\033[36m\"\n+#define GIT_COLOR_DARK\t\t\"\\033[1;30m\"\n #define GIT_COLOR_BOLD_RED\t\"\\033[1;31m\"\n #define GIT_COLOR_BOLD_GREEN\t\"\\033[1;32m\"\n #define GIT_COLOR_BOLD_YELLOW\t\"\\033[1;33m\"\ndiff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh\nnew file mode 100755\nindex 0000000000..b2de03b777\n--- /dev/null\n+++ b/t/t8012-blame-colors.sh\n@@ -0,0 +1,18 @@\n+#!/bin/sh\n+\n+test_description='colored git blame'\n+. ./test-lib.sh\n+\n+PROG='git blame -c'\n+. \"$TEST_DIRECTORY\"/annotate-tests.sh\n+\n+test_expect_success 'colored blame colors continuous lines' '\n+\tgit blame --abbrev=12 --color-lines hello.c >actual.raw &&\n+\ttest_decode_color <actual.raw >actual &&\n+\tgrep \"<BOLD;BLACK>(F\" actual > F.expect &&\n+\tgrep \"<BOLD;BLACK>(H\" actual > H.expect &&\n+\ttest_line_count = 2 F.expect &&\n+\ttest_line_count = 3 H.expect\n+'\n+\n+test_done\n-- \n2.15.0.128.gcadd42da22\n\n"},{"id":"332142","messageId":"20171110011002.10179-4-sbeller@google.com","threadId":"47149","inReplyTo":"20171110011002.10179-1-sbeller@google.com","subject":"[PATCH 3/4] builtin/blame: add option to color metadata fields separately","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-11-10T01:10:01Z","receivedAt":"2017-11-10T01:10:24Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Unlike the previous commit, this dims colors for each\nmetadata field individually.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n builtin/blame.c         | 82 +++++++++++++++++++++++++++++++++++++++++++------\n t/t8012-blame-colors.sh | 38 +++++++++++++++++++++++\n 2 files changed, 111 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex d15e901357..9d460597a2 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -323,6 +323,7 @@ static const char *format_time(timestamp_t time, const char *tz_str,\n #define OUTPUT_SHOW_EMAIL\t0400\n #define OUTPUT_LINE_PORCELAIN \t01000\n #define OUTPUT_COLOR_LINE\t02000\n+#define OUTPUT_COLOR_FIELDS\t04000\n \n static void emit_porcelain_details(struct blame_origin *suspect, int repeat)\n {\n@@ -370,6 +371,33 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,\n \t\tputchar('\\n');\n }\n \n+static int had_same_field_previously(int opt, int field,\n+\t\t\t  struct blame_entry *ent,\n+\t\t\t  struct blame_entry *prev)\n+{\n+\tstruct commit_info ci, prev_ci;\n+\n+\tswitch (field) {\n+\tcase OUTPUT_SHOW_SCORE:\n+\t\treturn ent->score == prev->score;\n+\tcase OUTPUT_SHOW_NAME:\n+\t\treturn prev->suspect &&\n+\t\t\t!strcmp(ent->suspect->path, prev->suspect->path);\n+\tcase OUTPUT_SHOW_NUMBER:\n+\t\treturn ent->s_lno == prev->s_lno + prev->num_lines - 1;\n+\n+\tcase OUTPUT_NO_AUTHOR:\n+\t\tget_commit_info(ent->suspect->commit, &ci, 1);\n+\t\tget_commit_info(prev->suspect->commit, &prev_ci, 1);\n+\t\treturn ((opt & OUTPUT_SHOW_EMAIL) &&\n+\t\t\t!strcmp(ci.author_mail.buf, prev_ci.author_mail.buf)) ||\n+\t\t\t!strcmp(ci.author.buf, prev_ci.author.buf);\n+\tdefault:\n+\t\tBUG(\"unknown field\");\n+\t}\n+\treturn 0;\n+}\n+\n static inline void colors_unset(const char **use_color, const char **reset_color)\n {\n \t*use_color = \"\";\n@@ -382,17 +410,36 @@ static inline void colors_set(const char **use_color, const char **reset_color)\n \t*reset_color = GIT_COLOR_RESET;\n }\n \n+static void setup_field_color(int opt, int cnt, int field,\n+\t\t\t      struct blame_entry *ent,\n+\t\t\t      struct blame_entry *prev,\n+\t\t\t      const char **use_color,\n+\t\t\t      const char **reset_color)\n+{\n+\tif (!(opt & OUTPUT_COLOR_FIELDS))\n+\t\treturn;\n+\n+\tif ((cnt > 0 ||\n+\t     (prev && had_same_field_previously(opt, field, ent, prev))))\n+\t\tcolors_set(use_color, reset_color);\n+\telse\n+\t\tcolors_unset(use_color, reset_color);\n+}\n+\n static void setup_line_color(int opt, int cnt,\n \t\t\t     const char **use_color,\n \t\t\t     const char **reset_color)\n {\n \tcolors_unset(use_color, reset_color);\n \n-\tif ((opt & OUTPUT_COLOR_LINE) && cnt > 0)\n+\tif ((opt & (OUTPUT_COLOR_LINE | OUTPUT_COLOR_FIELDS)) && cnt > 0)\n \t\tcolors_set(use_color, reset_color);\n }\n \n-static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt)\n+static void emit_other(struct blame_scoreboard *sb,\n+\t\t       struct blame_entry *ent,\n+\t\t       struct blame_entry *prev,\n+\t\t       int opt)\n {\n \tint cnt;\n \tconst char *cp;\n@@ -432,18 +479,27 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t\t\t   show_raw_time),\n \t\t\t       ent->lno + 1 + cnt);\n \t\t} else {\n-\t\t\tif (opt & OUTPUT_SHOW_SCORE)\n+\t\t\tif (opt & OUTPUT_SHOW_SCORE) {\n+\t\t\t\tsetup_field_color(opt, cnt, OUTPUT_SHOW_SCORE,\n+\t\t\t\t\t\t  ent, prev, &col, &rcol);\n \t\t\t\tprintf(\" %s%*d %02d%s\", col,\n \t\t\t\t       max_score_digits, ent->score,\n \t\t\t\t       ent->suspect->refcnt, rcol);\n-\t\t\tif (opt & OUTPUT_SHOW_NAME)\n+\t\t\t}\n+\t\t\tif (opt & OUTPUT_SHOW_NAME) {\n+\t\t\t\tsetup_field_color(opt, cnt, OUTPUT_SHOW_NAME,\n+\t\t\t\t\t\t   ent, prev, &col, &rcol);\n \t\t\t\tprintf(\" %s%-*.*s%s\", col, longest_file,\n \t\t\t\t\t\t      longest_file,\n \t\t\t\t\t\t      suspect->path,\n \t\t\t\t\t\t      rcol);\n-\t\t\tif (opt & OUTPUT_SHOW_NUMBER)\n+\t\t\t}\n+\t\t\tif (opt & OUTPUT_SHOW_NUMBER) {\n+\t\t\t\tsetup_field_color(opt, cnt, OUTPUT_SHOW_NUMBER,\n+\t\t\t\t\t\t  ent, prev, &col, &rcol);\n \t\t\t\tprintf(\" %s%*d%s\", col, max_orig_digits,\n \t\t\t\t       ent->s_lno + 1 + cnt, rcol);\n+\t\t\t}\n \t\t\tif (!(opt & OUTPUT_NO_AUTHOR)) {\n \t\t\t\tconst char *name;\n \t\t\t\tint pad;\n@@ -452,6 +508,8 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t\telse\n \t\t\t\t\tname = ci.author.buf;\n \t\t\t\tpad = longest_author - utf8_strwidth(name);\n+\t\t\t\tsetup_field_color(opt, cnt, OUTPUT_NO_AUTHOR,\n+\t\t\t\t\t\t  ent, prev, &col, &rcol);\n \t\t\t\tprintf(\" %s(%s%*s %10s%s\", col,\n \t\t\t\t       name, pad, \"\",\n \t\t\t\t       format_time(ci.author_time,\n@@ -477,7 +535,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \n static void output(struct blame_scoreboard *sb, int option)\n {\n-\tstruct blame_entry *ent;\n+\tstruct blame_entry *ent, *prev = NULL;\n \n \tif (option & OUTPUT_PORCELAIN) {\n \t\tfor (ent = sb->ent; ent; ent = ent->next) {\n@@ -499,7 +557,8 @@ static void output(struct blame_scoreboard *sb, int option)\n \t\tif (option & OUTPUT_PORCELAIN)\n \t\t\temit_porcelain(sb, ent, option);\n \t\telse {\n-\t\t\temit_other(sb, ent, option);\n+\t\t\temit_other(sb, ent, prev, option);\n+\t\t\tprev = ent;\n \t\t}\n \t}\n }\n@@ -717,6 +776,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('e', \"show-email\", &output_option, N_(\"Show author email instead of name (Default: off)\"), OUTPUT_SHOW_EMAIL),\n \t\tOPT_BIT('w', NULL, &xdl_opts, N_(\"Ignore whitespace differences\"), XDF_IGNORE_WHITESPACE),\n \t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line\"), OUTPUT_COLOR_LINE),\n+\t\tOPT_BIT(0, \"color-fields\", &output_option, N_(\"color redundant metadata fields from previous line\"), OUTPUT_COLOR_FIELDS),\n \n \t\t/*\n \t\t * The following two options are parsed by parse_revision_opt()\n@@ -775,6 +835,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \tDIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES);\n \targc = parse_options_end(&ctx);\n \n+\tif ((output_option & OUTPUT_COLOR_LINE) &&\n+\t    (output_option & OUTPUT_COLOR_FIELDS))\n+\t\tdie(_(\"cannot ask for colored lines and fields at the same time\"));\n+\n \tif (incremental || (output_option & OUTPUT_PORCELAIN)) {\n \t\tif (show_progress > 0)\n \t\t\tdie(_(\"--progress can't be used with --incremental or porcelain formats\"));\n@@ -978,8 +1042,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \n \tif (!(output_option & OUTPUT_PORCELAIN)) {\n \t\tfind_alignment(&sb, &output_option);\n-\t\tif ((output_option & OUTPUT_COLOR_LINE) &&\n-\t\t    !repeated_meta_color)\n+\t\tif (!repeated_meta_color &&\n+\t\t    (output_option & (OUTPUT_COLOR_LINE | OUTPUT_COLOR_FIELDS)))\n \t\t\trepeated_meta_color = GIT_COLOR_DARK;\n \t}\n \ndiff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh\nindex b2de03b777..be491bde9a 100755\n--- a/t/t8012-blame-colors.sh\n+++ b/t/t8012-blame-colors.sh\n@@ -15,4 +15,42 @@ test_expect_success 'colored blame colors continuous lines' '\n \ttest_line_count = 3 H.expect\n '\n \n+test_expect_success 'colored blame colors continuous fields' '\n+\n+\tgit mv hello.c world.c &&\n+\tgit commit -a -m \"moved file\" &&\n+\tcat <<-EOF >> world.c &&\n+\tvoid world()\n+\t{\n+\t\tputs(\"world\");\n+\t}\n+\tEOF\n+\tgit add world.c &&\n+\tGIT_AUTHOR_NAME=\"F\" GIT_AUTHOR_EMAIL=\"F@test.git\" \\\n+\t\tgit commit -m \"forgot to add changes to moved file\" &&\n+\n+\tgit blame --abbrev=12 --color-fields world.c >actual.raw &&\n+\ttest_decode_color <actual.raw >actual &&\n+\n+\tgrep \"<BOLD;BLACK>hello.c\" actual > colored_hello.expect &&\n+\tgrep \"hello.c\" actual > all_hello.expect &&\n+\ttest_line_count = 9 colored_hello.expect &&\n+\ttest_line_count = 10 all_hello.expect &&\n+\n+\tgrep \"<BOLD;BLACK>world.c\" actual > colored_world.expect &&\n+\tgrep \"world.c\" actual > all_world.expect &&\n+\ttest_line_count = 3 colored_world.expect &&\n+\ttest_line_count = 4 all_world.expect &&\n+\n+\tgrep \"(F\" actual > all_F.expect &&\n+\tgrep \"<BOLD;BLACK>(F\" actual > colored_F.expect &&\n+\ttest_line_count = 8 all_F.expect &&\n+\ttest_line_count = 5 colored_F.expect &&\n+\n+\tgrep \"(H\" actual > all_H.expect &&\n+\tgrep \"<BOLD;BLACK>(H\" actual > colored_H.expect &&\n+\ttest_line_count = 5 all_H.expect &&\n+\ttest_line_count = 3 colored_H.expect\n+'\n+\n test_done\n-- \n2.15.0.128.gcadd42da22\n\n"},{"id":"332143","messageId":"20171110011002.10179-5-sbeller@google.com","threadId":"47149","inReplyTo":"20171110011002.10179-1-sbeller@google.com","subject":"[PATCH 4/4] builtin/blame: highlight recently changed lines","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2017-11-10T01:10:02Z","receivedAt":"2017-11-10T01:10:26Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Choose a different color for dates and imitate a 'temperature cool down'\nfor the dates.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n Documentation/config.txt | 20 +++++++++++++\n builtin/blame.c          | 74 +++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 93 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 85dfdc6a9b..7d2efc5ad6 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1194,6 +1194,26 @@ color.blame.repeatedMeta::\n \tis repeated meta information per line (such as commit id,\n \tauthor name, date and timezone). Defaults to dark gray.\n \n+color.blame.highlightRecent::\n+\tThis can be used to color the author and date of a blame line.\n+\tThis overrides `color.blame.repeatedMeta` setting, which colors\n+\trepetitions.\n++\n+\tThis setting should be set to a comma-separated list of\n+\tcolor and date settings, starting and ending with a color, the dates\n+\tshould be set from oldest to newest.\n+\tThe metadata will be colored given the colors if the the line was\n+\tintroduced before the given timestamp, overwriting older timestamped\n+\tcolors.\n++\n+\tInstead of an absolute timestamp relative timestamps work as\n+\twell, e.g. 2.weeks.ago is valid to address anything older than 2 weeks.\n++\n+\tIt defaults to \"blue,12 month ago,white,1 month ago,red\",\n+\twhich colors everything older than one year blue, recent changes between\n+\tone month and one year old are kept white, and lines introduced within\n+\tthe last month are colored red.\n+\n color.ui::\n \tThis variable determines the default value for variables such\n \tas `color.diff` and `color.grep` that control the use of color\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 9d460597a2..e157ee3864 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -24,6 +24,7 @@\n #include \"dir.h\"\n #include \"progress.h\"\n #include \"blame.h\"\n+#include \"string-list.h\"\n \n static char blame_usage[] = N_(\"git blame [<options>] [<rev-opts>] [<rev>] [--] <file>\");\n \n@@ -324,6 +325,7 @@ static const char *format_time(timestamp_t time, const char *tz_str,\n #define OUTPUT_LINE_PORCELAIN \t01000\n #define OUTPUT_COLOR_LINE\t02000\n #define OUTPUT_COLOR_FIELDS\t04000\n+#define OUTPUT_HEATED_LINES\t010000\n \n static void emit_porcelain_details(struct blame_origin *suspect, int repeat)\n {\n@@ -436,6 +438,61 @@ static void setup_line_color(int opt, int cnt,\n \t\tcolors_set(use_color, reset_color);\n }\n \n+static struct color_field {\n+\ttimestamp_t hop;\n+\tchar col[COLOR_MAXLEN];\n+} *colorfield;\n+static int colorfield_nr, colorfield_alloc;\n+\n+static void parse_color_fields(const char *s)\n+{\n+\tstruct string_list l = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tenum { EXPECT_DATE, EXPECT_COLOR } next = EXPECT_COLOR;\n+\n+\t/* Ideally this would be stripped and split at the same time? */\n+\tstring_list_split(&l, s, ',', -1);\n+\tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n+\n+\tfor_each_string_list_item(item, &l) {\n+\t\tswitch (next) {\n+\t\tcase EXPECT_DATE:\n+\t\t\tcolorfield[colorfield_nr].hop = approxidate(item->string);\n+\t\t\tnext = EXPECT_COLOR;\n+\t\t\tcolorfield_nr++;\n+\t\t\tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n+\t\t\tbreak;\n+\t\tcase EXPECT_COLOR:\n+\t\t\tif (color_parse(item->string, colorfield[colorfield_nr].col))\n+\t\t\t\tdie(_(\"expecting a color: %s\"), item->string);\n+\t\t\tnext = EXPECT_DATE;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (next == EXPECT_COLOR)\n+\t\tdie (_(\"must end with a color\"));\n+\n+\tcolorfield[colorfield_nr].hop = TIME_MAX;\n+}\n+\n+static void setup_default_colorfield(void)\n+{\n+\tparse_color_fields(\"blue,12 month ago,white,1 month ago,red\");\n+}\n+\n+static void determine_line_heat(struct blame_entry *ent, const char **dest_color)\n+{\n+\tint i = 0;\n+\tstruct commit_info ci;\n+\tget_commit_info(ent->suspect->commit, &ci, 1);\n+\n+\twhile (i < colorfield_nr && ci.author_time > colorfield[i].hop)\n+\t\ti++;\n+\n+\t*dest_color = colorfield[i].col;\n+}\n+\n static void emit_other(struct blame_scoreboard *sb,\n \t\t       struct blame_entry *ent,\n \t\t       struct blame_entry *prev,\n@@ -443,6 +500,7 @@ static void emit_other(struct blame_scoreboard *sb,\n {\n \tint cnt;\n \tconst char *cp;\n+\tconst char *heatcolor = NULL;\n \tstruct blame_origin *suspect = ent->suspect;\n \tstruct commit_info ci;\n \tchar hex[GIT_MAX_HEXSZ + 1];\n@@ -452,6 +510,10 @@ static void emit_other(struct blame_scoreboard *sb,\n \toid_to_hex_r(hex, &suspect->commit->object.oid);\n \n \tcp = blame_nth_line(sb, ent->lno);\n+\n+\tif (opt & OUTPUT_HEATED_LINES)\n+\t\tdetermine_line_heat(ent, &heatcolor);\n+\n \tfor (cnt = 0; cnt < ent->num_lines; cnt++) {\n \t\tchar ch;\n \t\tint length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;\n@@ -503,6 +565,7 @@ static void emit_other(struct blame_scoreboard *sb,\n \t\t\tif (!(opt & OUTPUT_NO_AUTHOR)) {\n \t\t\t\tconst char *name;\n \t\t\t\tint pad;\n+\t\t\t\tint hcolor = opt & OUTPUT_HEATED_LINES;\n \t\t\t\tif (opt & OUTPUT_SHOW_EMAIL)\n \t\t\t\t\tname = ci.author_mail.buf;\n \t\t\t\telse\n@@ -510,7 +573,10 @@ static void emit_other(struct blame_scoreboard *sb,\n \t\t\t\tpad = longest_author - utf8_strwidth(name);\n \t\t\t\tsetup_field_color(opt, cnt, OUTPUT_NO_AUTHOR,\n \t\t\t\t\t\t  ent, prev, &col, &rcol);\n-\t\t\t\tprintf(\" %s(%s%*s %10s%s\", col,\n+\t\t\t\tif (hcolor)\n+\t\t\t\t\trcol = GIT_COLOR_RESET;\n+\t\t\t\tprintf(\" %s(%s%*s %10s%s\",\n+\t\t\t\t       hcolor ? heatcolor : col,\n \t\t\t\t       name, pad, \"\",\n \t\t\t\t       format_time(ci.author_time,\n \t\t\t\t\t\t   ci.author_tz.buf,\n@@ -701,6 +767,10 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \t\t\t\tvalue);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"color.blame.highlightrecent\")) {\n+\t\tparse_color_fields(value);\n+\t\treturn 0;\n+\t}\n \n \tif (git_diff_heuristic_config(var, value, cb) < 0)\n \t\treturn -1;\n@@ -777,6 +847,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('w', NULL, &xdl_opts, N_(\"Ignore whitespace differences\"), XDF_IGNORE_WHITESPACE),\n \t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line\"), OUTPUT_COLOR_LINE),\n \t\tOPT_BIT(0, \"color-fields\", &output_option, N_(\"color redundant metadata fields from previous line\"), OUTPUT_COLOR_FIELDS),\n+\t\tOPT_BIT(0, \"heated-lines\", &output_option, N_(\"color lines by date\"), OUTPUT_HEATED_LINES),\n \n \t\t/*\n \t\t * The following two options are parsed by parse_revision_opt()\n@@ -801,6 +872,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \tunsigned int range_i;\n \tlong anchor;\n \n+\tsetup_default_colorfield();\n \tgit_config(git_blame_config, &output_option);\n \tinit_revisions(&revs, NULL);\n \trevs.date_mode = blame_date_mode;\n-- \n2.15.0.128.gcadd42da22\n\n"},{"id":"335878","messageId":"20180104224007.214548-1-sbeller@google.com","threadId":"47149","inReplyTo":"20171110011002.10179-1-sbeller@google.com","subject":"[PATCHv3 0/4] blame: (dim rep. metadata lines or fields, decay date coloring)","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-04T22:40:03Z","receivedAt":"2018-01-04T22:40:18Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"v3:\n\nThanks Eric for feedback, I implemented all of the suggestions.\nSpecifically I dropped the abstractions in patch2 but keep around a similar\nabstraction in patch 3 as that still looks like it benefits (the condition\nis just growing large).\n\nThanks,\nStefan\n\nv2:\nThis is picking up [1], but presenting it in another approach,\nas I realized these are orthogonal features:\n* dimming repeated lines/fields of information\n* giving a quick visual information how old (as a proxy for 'well tested')\n  a line of code is.\n\nBoth features are configurable.\n\nChanges from sending it out in November:\n* better commit messages\n* rebased on master\n\nAny feedback welcome.\n\nThanks,\nStefan\n\n[1] https://public-inbox.org/git/20171110011002.10179-1-sbeller@google.com/\n\nStefan Beller (4):\n  color.h: document and modernize header\n  builtin/blame: dim uninteresting metadata\n  builtin/blame: add option to color metadata fields separately\n  builtin/blame: highlight recently changed lines\n\n Documentation/config.txt |  23 ++++++\n builtin/blame.c          | 201 ++++++++++++++++++++++++++++++++++++++++++-----\n color.c                  |   2 -\n color.h                  |  59 ++++++++++----\n t/t8012-blame-colors.sh  |  56 +++++++++++++\n 5 files changed, 305 insertions(+), 36 deletions(-)\n create mode 100755 t/t8012-blame-colors.sh\n\n-- \n2.16.0.rc0.223.g4a4ac83678-goog\n\n"},{"id":"335879","messageId":"20180104224007.214548-2-sbeller@google.com","threadId":"47149","inReplyTo":"20180104224007.214548-1-sbeller@google.com","subject":"[PATCHv3 1/4] color.h: document and modernize header","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-04T22:40:04Z","receivedAt":"2018-01-04T22:40:20Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Add documentation explaining the functions in color.h.\nWhile at it, mark them extern.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n color.c |  2 --\n color.h | 58 ++++++++++++++++++++++++++++++++++++++++++++--------------\n 2 files changed, 44 insertions(+), 16 deletions(-)\n\ndiff --git a/color.c b/color.c\nindex d48dd947c9..c4dc1cb989 100644\n--- a/color.c\n+++ b/color.c\n@@ -399,8 +399,6 @@ static int color_vfprintf(FILE *fp, const char *color, const char *fmt,\n \treturn r;\n }\n \n-\n-\n int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)\n {\n \tva_list args;\ndiff --git a/color.h b/color.h\nindex fd2b688dfb..2e768a10c6 100644\n--- a/color.h\n+++ b/color.h\n@@ -72,26 +72,56 @@ extern int color_stdout_is_tty;\n  * Use the first one if you need only color config; the second is a convenience\n  * if you are just going to change to git_default_config, too.\n  */\n-int git_color_config(const char *var, const char *value, void *cb);\n-int git_color_default_config(const char *var, const char *value, void *cb);\n+extern int git_color_config(const char *var, const char *value, void *cb);\n+extern int git_color_default_config(const char *var, const char *value, void *cb);\n \n /*\n- * Set the color buffer (which must be COLOR_MAXLEN bytes)\n- * to the raw color bytes; this is useful for initializing\n- * default color variables.\n+ * NEEDSWWORK: document this function or refactor grep.c to stop using this\n+ * function.\n  */\n-void color_set(char *dst, const char *color_bytes);\n+extern void color_set(char *dst, const char *color_bytes);\n \n-int git_config_colorbool(const char *var, const char *value);\n-int want_color(int var);\n-int color_parse(const char *value, char *dst);\n-int color_parse_mem(const char *value, int len, char *dst);\n+/*\n+ * Parse a config option, which can be a boolean or one of\n+ * \"never\", \"auto\", \"always\". Return a constant of\n+ * GIT_COLOR_NEVER for \"never\" or negative boolean,\n+ * GIT_COLOR_ALWAYS for \"always\" or a positive boolean,\n+ * and GIT_COLOR_AUTO for \"auto\".\n+ */\n+extern int git_config_colorbool(const char *var, const char *value);\n+\n+/*\n+ * Resolve the constants as returned by git_config_colorbool()\n+ * (specifically \"auto\") to a boolean answer.\n+ */\n+extern int want_color(int var);\n+\n+/*\n+ * Translate a Git color from 'value' into a string that the terminal can\n+ * interpret and store it into 'dst'. The Git color values are of the form\n+ * \"foreground [background] [attr]\" where fore- and background can be a color\n+ * name (\"red\"), a RGB code (#0xFF0000) or a 256-color-mode from the terminal.\n+ */\n+extern int color_parse(const char *value, char *dst);\n+extern int color_parse_mem(const char *value, int len, char *dst);\n+\n+/*\n+ * Output the formatted string in the specified color (and then reset to normal\n+ * color so subsequent output is uncolored). Omits the color encapsulation if\n+ * `color` is NULL. The `color_fprintf_ln` prints a new line after resetting\n+ * the color. The `color_print_strbuf` prints the given pre-formatted strbuf\n+ * instead, up to its first NUL character.\n+ */\n __attribute__((format (printf, 3, 4)))\n-int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);\n+extern int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);\n __attribute__((format (printf, 3, 4)))\n-int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);\n-void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb);\n+extern int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);\n+extern void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb);\n \n-int color_is_nil(const char *color);\n+/*\n+ * Check if the given color is GIT_COLOR_NIL that means \"no color selected\".\n+ * The caller needs to replace the color with the actual desired color.\n+ */\n+extern int color_is_nil(const char *color);\n \n #endif /* COLOR_H */\n-- \n2.16.0.rc0.223.g4a4ac83678-goog\n\n"},{"id":"335880","messageId":"20180104224007.214548-3-sbeller@google.com","threadId":"47149","inReplyTo":"20180104224007.214548-1-sbeller@google.com","subject":"[PATCHv3 2/4] builtin/blame: dim uninteresting metadata","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-04T22:40:05Z","receivedAt":"2018-01-04T22:40:22Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"When using git-blame lots of lines contain redundant information, for\nexample in hunks that consist of multiple lines, the metadata (commit\nname, author, date) are repeated. A reader may not be interested in those,\nso offer an option to color the information that is repeated from the\nprevious line differently.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n Documentation/config.txt |  5 +++++\n builtin/blame.c          | 50 ++++++++++++++++++++++++++++++++++--------------\n color.h                  |  1 +\n t/t8012-blame-colors.sh  | 18 +++++++++++++++++\n 4 files changed, 60 insertions(+), 14 deletions(-)\n create mode 100755 t/t8012-blame-colors.sh\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 64c1dbba94..45749a574d 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1216,6 +1216,11 @@ color.status.<slot>::\n \tstatus short-format), or\n \t`unmerged` (files which have unmerged changes).\n \n+color.blame.repeatedMeta::\n+\tUse the customized color for the part of git-blame output that\n+\tis repeated meta information per line (such as commit id,\n+\tauthor name, date and timezone). Defaults to dark gray.\n+\n color.ui::\n \tThis variable determines the default value for variables such\n \tas `color.diff` and `color.grep` that control the use of color\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 005f55aaa2..7b9c6e8676 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -7,6 +7,7 @@\n \n #include \"cache.h\"\n #include \"config.h\"\n+#include \"color.h\"\n #include \"builtin.h\"\n #include \"commit.h\"\n #include \"diff.h\"\n@@ -46,6 +47,7 @@ static int xdl_opts;\n static int abbrev = -1;\n static int no_whole_file_rename;\n static int show_progress;\n+static char *repeated_meta_color;\n \n static struct date_mode blame_date_mode = { DATE_ISO8601 };\n static size_t blame_date_width;\n@@ -316,10 +318,11 @@ static const char *format_time(timestamp_t time, const char *tz_str,\n #define OUTPUT_PORCELAIN\t010\n #define OUTPUT_SHOW_NAME\t020\n #define OUTPUT_SHOW_NUMBER\t040\n-#define OUTPUT_SHOW_SCORE      0100\n-#define OUTPUT_NO_AUTHOR       0200\n+#define OUTPUT_SHOW_SCORE\t0100\n+#define OUTPUT_NO_AUTHOR\t0200\n #define OUTPUT_SHOW_EMAIL\t0400\n-#define OUTPUT_LINE_PORCELAIN 01000\n+#define OUTPUT_LINE_PORCELAIN \t01000\n+#define OUTPUT_COLOR_LINE\t02000\n \n static void emit_porcelain_details(struct blame_origin *suspect, int repeat)\n {\n@@ -383,6 +386,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \tfor (cnt = 0; cnt < ent->num_lines; cnt++) {\n \t\tchar ch;\n \t\tint length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;\n+\t\tconst char *color = \"\", *reset = \"\";\n \n \t\tif (suspect->commit->object.flags & UNINTERESTING) {\n \t\t\tif (blank_boundary)\n@@ -393,7 +397,12 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t}\n \t\t}\n \n-\t\tprintf(\"%.*s\", length, hex);\n+\t\tif ((opt & OUTPUT_COLOR_LINE) && cnt > 0) {\n+\t\t\tcolor = repeated_meta_color;\n+\t\t\treset = GIT_COLOR_RESET;\n+\t\t}\n+\n+\t\tprintf(\"%s%.*s%s\", color, length, hex, reset);\n \t\tif (opt & OUTPUT_ANNOTATE_COMPAT) {\n \t\t\tconst char *name;\n \t\t\tif (opt & OUTPUT_SHOW_EMAIL)\n@@ -406,16 +415,17 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t       ent->lno + 1 + cnt);\n \t\t} else {\n \t\t\tif (opt & OUTPUT_SHOW_SCORE)\n-\t\t\t\tprintf(\" %*d %02d\",\n+\t\t\t\tprintf(\" %s%*d %02d%s\", color,\n \t\t\t\t       max_score_digits, ent->score,\n-\t\t\t\t       ent->suspect->refcnt);\n+\t\t\t\t       ent->suspect->refcnt, reset);\n \t\t\tif (opt & OUTPUT_SHOW_NAME)\n-\t\t\t\tprintf(\" %-*.*s\", longest_file, longest_file,\n-\t\t\t\t       suspect->path);\n+\t\t\t\tprintf(\" %s%-*.*s%s\", color, longest_file,\n+\t\t\t\t\t\t      longest_file,\n+\t\t\t\t\t\t      suspect->path,\n+\t\t\t\t\t\t      reset);\n \t\t\tif (opt & OUTPUT_SHOW_NUMBER)\n-\t\t\t\tprintf(\" %*d\", max_orig_digits,\n-\t\t\t\t       ent->s_lno + 1 + cnt);\n-\n+\t\t\t\tprintf(\" %s%*d%s\", color, max_orig_digits,\n+\t\t\t\t       ent->s_lno + 1 + cnt, reset);\n \t\t\tif (!(opt & OUTPUT_NO_AUTHOR)) {\n \t\t\t\tconst char *name;\n \t\t\t\tint pad;\n@@ -424,11 +434,12 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t\telse\n \t\t\t\t\tname = ci.author.buf;\n \t\t\t\tpad = longest_author - utf8_strwidth(name);\n-\t\t\t\tprintf(\" (%s%*s %10s\",\n+\t\t\t\tprintf(\" %s(%s%*s %10s%s\", color,\n \t\t\t\t       name, pad, \"\",\n \t\t\t\t       format_time(ci.author_time,\n \t\t\t\t\t\t   ci.author_tz.buf,\n-\t\t\t\t\t\t   show_raw_time));\n+\t\t\t\t\t\t   show_raw_time),\n+\t\t\t\t       reset);\n \t\t\t}\n \t\t\tprintf(\" %*d) \",\n \t\t\t       max_digits, ent->lno + 1 + cnt);\n@@ -607,6 +618,12 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \t\tparse_date_format(value, &blame_date_mode);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"color.blame.repeatedmeta\")) {\n+\t\tif (color_parse_mem(value, strlen(value), repeated_meta_color))\n+\t\t\twarning(_(\"invalid color '%s' in color.blame.repeatedMeta\"),\n+\t\t\t\tvalue);\n+\t\treturn 0;\n+\t}\n \n \tif (git_diff_heuristic_config(var, value, cb) < 0)\n \t\treturn -1;\n@@ -681,6 +698,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('s', NULL, &output_option, N_(\"Suppress author name and timestamp (Default: off)\"), OUTPUT_NO_AUTHOR),\n \t\tOPT_BIT('e', \"show-email\", &output_option, N_(\"Show author email instead of name (Default: off)\"), OUTPUT_SHOW_EMAIL),\n \t\tOPT_BIT('w', NULL, &xdl_opts, N_(\"Ignore whitespace differences\"), XDF_IGNORE_WHITESPACE),\n+\t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line differently\"), OUTPUT_COLOR_LINE),\n \n \t\t/*\n \t\t * The following two options are parsed by parse_revision_opt()\n@@ -940,8 +958,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \n \tblame_coalesce(&sb);\n \n-\tif (!(output_option & OUTPUT_PORCELAIN))\n+\tif (!(output_option & OUTPUT_PORCELAIN)) {\n \t\tfind_alignment(&sb, &output_option);\n+\t\tif ((output_option & OUTPUT_COLOR_LINE) &&\n+\t\t    !repeated_meta_color)\n+\t\t\trepeated_meta_color = GIT_COLOR_DARK;\n+\t}\n \n \toutput(&sb, output_option);\n \tfree((void *)sb.final_buf);\ndiff --git a/color.h b/color.h\nindex 2e768a10c6..2df2f86698 100644\n--- a/color.h\n+++ b/color.h\n@@ -30,6 +30,7 @@ struct strbuf;\n #define GIT_COLOR_BLUE\t\t\"\\033[34m\"\n #define GIT_COLOR_MAGENTA\t\"\\033[35m\"\n #define GIT_COLOR_CYAN\t\t\"\\033[36m\"\n+#define GIT_COLOR_DARK\t\t\"\\033[1;30m\"\n #define GIT_COLOR_BOLD_RED\t\"\\033[1;31m\"\n #define GIT_COLOR_BOLD_GREEN\t\"\\033[1;32m\"\n #define GIT_COLOR_BOLD_YELLOW\t\"\\033[1;33m\"\ndiff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh\nnew file mode 100755\nindex 0000000000..a2b7090cef\n--- /dev/null\n+++ b/t/t8012-blame-colors.sh\n@@ -0,0 +1,18 @@\n+#!/bin/sh\n+\n+test_description='colored git blame'\n+. ./test-lib.sh\n+\n+PROG='git blame -c'\n+. \"$TEST_DIRECTORY\"/annotate-tests.sh\n+\n+test_expect_success 'colored blame colors contiguous lines' '\n+\tgit blame --abbrev=12 --color-lines hello.c >actual.raw &&\n+\ttest_decode_color <actual.raw >actual &&\n+\tgrep \"<BOLD;BLACK>(F\" actual > F.expect &&\n+\tgrep \"<BOLD;BLACK>(H\" actual > H.expect &&\n+\ttest_line_count = 2 F.expect &&\n+\ttest_line_count = 3 H.expect\n+'\n+\n+test_done\n-- \n2.16.0.rc0.223.g4a4ac83678-goog\n\n"},{"id":"335881","messageId":"20180104224007.214548-5-sbeller@google.com","threadId":"47149","inReplyTo":"20180104224007.214548-1-sbeller@google.com","subject":"[PATCHv3 4/4] builtin/blame: highlight recently changed lines","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-04T22:40:07Z","receivedAt":"2018-01-04T22:40:24Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Choose a different color for dates and imitate a 'temperature cool down'\ndepending upon age.\n\nOriginally I had planned to have the temperature cooldown dependent on\nthe age of the project or file for example, as that might scale better,\nbut that can be added on top of this commit, e.g. instead of giving a\ndate, you could imagine giving a percentage that would be the linearly\ninterpolated between now and the beginning of the file.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n Documentation/config.txt | 18 ++++++++++++\n builtin/blame.c          | 74 +++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 91 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 45749a574d..aa967ce3ed 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1221,6 +1221,24 @@ color.blame.repeatedMeta::\n \tis repeated meta information per line (such as commit id,\n \tauthor name, date and timezone). Defaults to dark gray.\n \n+color.blame.highlightRecent::\n+\tThis can be used to color the author and date of a blame line.\n+\tThis overrides `color.blame.repeatedMeta` setting, which colors\n+\trepetitions.\n++\n+This setting should be set to a comma-separated list of color and date settings,\n+starting and ending with a color, the dates should be set from oldest to newest.\n+The metadata will be colored given the colors if the the line was introduced\n+before the given timestamp, overwriting older timestamped colors.\n++\n+Instead of an absolute timestamp relative timestamps work as well, e.g.\n+2.weeks.ago is valid to address anything older than 2 weeks.\n++\n+It defaults to \"blue,12 month ago,white,1 month ago,red\", which colors\n+everything older than one year blue, recent changes between one month and\n+one year old are kept white, and lines introduced within the last month are\n+colored red.\n+\n color.ui::\n \tThis variable determines the default value for variables such\n \tas `color.diff` and `color.grep` that control the use of color\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 60c8dadf4b..3d444e66b3 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -24,6 +24,7 @@\n #include \"dir.h\"\n #include \"progress.h\"\n #include \"blame.h\"\n+#include \"string-list.h\"\n \n static char blame_usage[] = N_(\"git blame [<options>] [<rev-opts>] [<rev>] [--] <file>\");\n \n@@ -324,6 +325,7 @@ static const char *format_time(timestamp_t time, const char *tz_str,\n #define OUTPUT_LINE_PORCELAIN \t01000\n #define OUTPUT_COLOR_LINE\t02000\n #define OUTPUT_COLOR_FIELDS\t04000\n+#define OUTPUT_HEATED_LINES\t010000\n \n static void emit_porcelain_details(struct blame_origin *suspect, int repeat)\n {\n@@ -417,6 +419,61 @@ static void setup_field_color(int opt, int cnt, int field,\n \t}\n }\n \n+static struct color_field {\n+\ttimestamp_t hop;\n+\tchar col[COLOR_MAXLEN];\n+} *colorfield;\n+static int colorfield_nr, colorfield_alloc;\n+\n+static void parse_color_fields(const char *s)\n+{\n+\tstruct string_list l = STRING_LIST_INIT_DUP;\n+\tstruct string_list_item *item;\n+\tenum { EXPECT_DATE, EXPECT_COLOR } next = EXPECT_COLOR;\n+\n+\t/* Ideally this would be stripped and split at the same time? */\n+\tstring_list_split(&l, s, ',', -1);\n+\tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n+\n+\tfor_each_string_list_item(item, &l) {\n+\t\tswitch (next) {\n+\t\tcase EXPECT_DATE:\n+\t\t\tcolorfield[colorfield_nr].hop = approxidate(item->string);\n+\t\t\tnext = EXPECT_COLOR;\n+\t\t\tcolorfield_nr++;\n+\t\t\tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n+\t\t\tbreak;\n+\t\tcase EXPECT_COLOR:\n+\t\t\tif (color_parse(item->string, colorfield[colorfield_nr].col))\n+\t\t\t\tdie(_(\"expecting a color: %s\"), item->string);\n+\t\t\tnext = EXPECT_DATE;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (next == EXPECT_COLOR)\n+\t\tdie (_(\"must end with a color\"));\n+\n+\tcolorfield[colorfield_nr].hop = TIME_MAX;\n+}\n+\n+static void setup_default_colorfield(void)\n+{\n+\tparse_color_fields(\"blue,12 month ago,white,1 month ago,red\");\n+}\n+\n+static void determine_line_heat(struct blame_entry *ent, const char **dest_color)\n+{\n+\tint i = 0;\n+\tstruct commit_info ci;\n+\tget_commit_info(ent->suspect->commit, &ci, 1);\n+\n+\twhile (i < colorfield_nr && ci.author_time > colorfield[i].hop)\n+\t\ti++;\n+\n+\t*dest_color = colorfield[i].col;\n+}\n+\n static void emit_other(struct blame_scoreboard *sb,\n \t\t       struct blame_entry *ent,\n \t\t       struct blame_entry *prev,\n@@ -424,6 +481,7 @@ static void emit_other(struct blame_scoreboard *sb,\n {\n \tint cnt;\n \tconst char *cp;\n+\tconst char *heatcolor = NULL;\n \tstruct blame_origin *suspect = ent->suspect;\n \tstruct commit_info ci;\n \tchar hex[GIT_MAX_HEXSZ + 1];\n@@ -433,6 +491,10 @@ static void emit_other(struct blame_scoreboard *sb,\n \toid_to_hex_r(hex, &suspect->commit->object.oid);\n \n \tcp = blame_nth_line(sb, ent->lno);\n+\n+\tif (opt & OUTPUT_HEATED_LINES)\n+\t\tdetermine_line_heat(ent, &heatcolor);\n+\n \tfor (cnt = 0; cnt < ent->num_lines; cnt++) {\n \t\tchar ch;\n \t\tint length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev;\n@@ -488,6 +550,7 @@ static void emit_other(struct blame_scoreboard *sb,\n \t\t\tif (!(opt & OUTPUT_NO_AUTHOR)) {\n \t\t\t\tconst char *name;\n \t\t\t\tint pad;\n+\t\t\t\tint hcolor = opt & OUTPUT_HEATED_LINES;\n \t\t\t\tif (opt & OUTPUT_SHOW_EMAIL)\n \t\t\t\t\tname = ci.author_mail.buf;\n \t\t\t\telse\n@@ -495,7 +558,10 @@ static void emit_other(struct blame_scoreboard *sb,\n \t\t\t\tpad = longest_author - utf8_strwidth(name);\n \t\t\t\tsetup_field_color(opt, cnt, OUTPUT_NO_AUTHOR,\n \t\t\t\t\t\t  ent, prev, &color, &reset);\n-\t\t\t\tprintf(\" %s(%s%*s %10s%s\", color,\n+\t\t\t\tif (hcolor)\n+\t\t\t\t\treset = GIT_COLOR_RESET;\n+\t\t\t\tprintf(\" %s(%s%*s %10s%s\",\n+\t\t\t\t       hcolor ? heatcolor : color,\n \t\t\t\t       name, pad, \"\",\n \t\t\t\t       format_time(ci.author_time,\n \t\t\t\t\t\t   ci.author_tz.buf,\n@@ -686,6 +752,10 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \t\t\t\tvalue);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"color.blame.highlightrecent\")) {\n+\t\tparse_color_fields(value);\n+\t\treturn 0;\n+\t}\n \n \tif (git_diff_heuristic_config(var, value, cb) < 0)\n \t\treturn -1;\n@@ -762,6 +832,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('w', NULL, &xdl_opts, N_(\"Ignore whitespace differences\"), XDF_IGNORE_WHITESPACE),\n \t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line differently\"), OUTPUT_COLOR_LINE),\n \t\tOPT_BIT(0, \"color-fields\", &output_option, N_(\"color redundant metadata fields from previous line differently\"), OUTPUT_COLOR_FIELDS),\n+\t\tOPT_BIT(0, \"heated-lines\", &output_option, N_(\"color lines by age\"), OUTPUT_HEATED_LINES),\n \n \t\t/*\n \t\t * The following two options are parsed by parse_revision_opt()\n@@ -786,6 +857,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \tunsigned int range_i;\n \tlong anchor;\n \n+\tsetup_default_colorfield();\n \tgit_config(git_blame_config, &output_option);\n \tinit_revisions(&revs, NULL);\n \trevs.date_mode = blame_date_mode;\n-- \n2.16.0.rc0.223.g4a4ac83678-goog\n\n"},{"id":"335882","messageId":"20180104224007.214548-4-sbeller@google.com","threadId":"47149","inReplyTo":"20180104224007.214548-1-sbeller@google.com","subject":"[PATCHv3 3/4] builtin/blame: add option to color metadata fields separately","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-04T22:40:06Z","receivedAt":"2018-01-04T22:40:41Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Unlike the previous commit, this dims colors for each\nmetadata field individually.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n builtin/blame.c         | 83 ++++++++++++++++++++++++++++++++++++++++++++-----\n t/t8012-blame-colors.sh | 38 ++++++++++++++++++++++\n 2 files changed, 113 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 7b9c6e8676..60c8dadf4b 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -323,6 +323,7 @@ static const char *format_time(timestamp_t time, const char *tz_str,\n #define OUTPUT_SHOW_EMAIL\t0400\n #define OUTPUT_LINE_PORCELAIN \t01000\n #define OUTPUT_COLOR_LINE\t02000\n+#define OUTPUT_COLOR_FIELDS\t04000\n \n static void emit_porcelain_details(struct blame_origin *suspect, int repeat)\n {\n@@ -370,7 +371,56 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,\n \t\tputchar('\\n');\n }\n \n-static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt)\n+static int had_same_field_previously(int opt, int field,\n+\t\t\t  struct blame_entry *ent,\n+\t\t\t  struct blame_entry *prev)\n+{\n+\tstruct commit_info ci, prev_ci;\n+\n+\tswitch (field) {\n+\tcase OUTPUT_SHOW_SCORE:\n+\t\treturn ent->score == prev->score;\n+\tcase OUTPUT_SHOW_NAME:\n+\t\treturn prev->suspect &&\n+\t\t\t!strcmp(ent->suspect->path, prev->suspect->path);\n+\tcase OUTPUT_SHOW_NUMBER:\n+\t\treturn ent->s_lno == prev->s_lno + prev->num_lines - 1;\n+\n+\tcase OUTPUT_NO_AUTHOR:\n+\t\tget_commit_info(ent->suspect->commit, &ci, 1);\n+\t\tget_commit_info(prev->suspect->commit, &prev_ci, 1);\n+\t\treturn ((opt & OUTPUT_SHOW_EMAIL) &&\n+\t\t\t!strcmp(ci.author_mail.buf, prev_ci.author_mail.buf)) ||\n+\t\t\t!strcmp(ci.author.buf, prev_ci.author.buf);\n+\tdefault:\n+\t\tBUG(\"unknown field\");\n+\t}\n+\treturn 0;\n+}\n+\n+static void setup_field_color(int opt, int cnt, int field,\n+\t\t\t      struct blame_entry *ent,\n+\t\t\t      struct blame_entry *prev,\n+\t\t\t      const char **use_color,\n+\t\t\t      const char **reset_color)\n+{\n+\tif (!(opt & OUTPUT_COLOR_FIELDS))\n+\t\treturn;\n+\n+\tif ((cnt > 0 ||\n+\t     (prev && had_same_field_previously(opt, field, ent, prev)))) {\n+\t\t*use_color = repeated_meta_color;\n+\t\t*reset_color = GIT_COLOR_RESET;\n+\t} else {\n+\t\t*use_color = \"\";\n+\t\t*reset_color = \"\";\n+\t}\n+}\n+\n+static void emit_other(struct blame_scoreboard *sb,\n+\t\t       struct blame_entry *ent,\n+\t\t       struct blame_entry *prev,\n+\t\t       int opt)\n {\n \tint cnt;\n \tconst char *cp;\n@@ -414,18 +464,27 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t\t\t   show_raw_time),\n \t\t\t       ent->lno + 1 + cnt);\n \t\t} else {\n-\t\t\tif (opt & OUTPUT_SHOW_SCORE)\n+\t\t\tif (opt & OUTPUT_SHOW_SCORE) {\n+\t\t\t\tsetup_field_color(opt, cnt, OUTPUT_SHOW_SCORE,\n+\t\t\t\t\t\t  ent, prev, &color, &reset);\n \t\t\t\tprintf(\" %s%*d %02d%s\", color,\n \t\t\t\t       max_score_digits, ent->score,\n \t\t\t\t       ent->suspect->refcnt, reset);\n-\t\t\tif (opt & OUTPUT_SHOW_NAME)\n+\t\t\t}\n+\t\t\tif (opt & OUTPUT_SHOW_NAME) {\n+\t\t\t\tsetup_field_color(opt, cnt, OUTPUT_SHOW_NAME,\n+\t\t\t\t\t\t   ent, prev, &color, &reset);\n \t\t\t\tprintf(\" %s%-*.*s%s\", color, longest_file,\n \t\t\t\t\t\t      longest_file,\n \t\t\t\t\t\t      suspect->path,\n \t\t\t\t\t\t      reset);\n-\t\t\tif (opt & OUTPUT_SHOW_NUMBER)\n+\t\t\t}\n+\t\t\tif (opt & OUTPUT_SHOW_NUMBER) {\n+\t\t\t\tsetup_field_color(opt, cnt, OUTPUT_SHOW_NUMBER,\n+\t\t\t\t\t\t  ent, prev, &color, &reset);\n \t\t\t\tprintf(\" %s%*d%s\", color, max_orig_digits,\n \t\t\t\t       ent->s_lno + 1 + cnt, reset);\n+\t\t\t}\n \t\t\tif (!(opt & OUTPUT_NO_AUTHOR)) {\n \t\t\t\tconst char *name;\n \t\t\t\tint pad;\n@@ -434,6 +493,8 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\t\telse\n \t\t\t\t\tname = ci.author.buf;\n \t\t\t\tpad = longest_author - utf8_strwidth(name);\n+\t\t\t\tsetup_field_color(opt, cnt, OUTPUT_NO_AUTHOR,\n+\t\t\t\t\t\t  ent, prev, &color, &reset);\n \t\t\t\tprintf(\" %s(%s%*s %10s%s\", color,\n \t\t\t\t       name, pad, \"\",\n \t\t\t\t       format_time(ci.author_time,\n@@ -459,7 +520,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \n static void output(struct blame_scoreboard *sb, int option)\n {\n-\tstruct blame_entry *ent;\n+\tstruct blame_entry *ent, *prev = NULL;\n \n \tif (option & OUTPUT_PORCELAIN) {\n \t\tfor (ent = sb->ent; ent; ent = ent->next) {\n@@ -481,7 +542,8 @@ static void output(struct blame_scoreboard *sb, int option)\n \t\tif (option & OUTPUT_PORCELAIN)\n \t\t\temit_porcelain(sb, ent, option);\n \t\telse {\n-\t\t\temit_other(sb, ent, option);\n+\t\t\temit_other(sb, ent, prev, option);\n+\t\t\tprev = ent;\n \t\t}\n \t}\n }\n@@ -699,6 +761,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('e', \"show-email\", &output_option, N_(\"Show author email instead of name (Default: off)\"), OUTPUT_SHOW_EMAIL),\n \t\tOPT_BIT('w', NULL, &xdl_opts, N_(\"Ignore whitespace differences\"), XDF_IGNORE_WHITESPACE),\n \t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line differently\"), OUTPUT_COLOR_LINE),\n+\t\tOPT_BIT(0, \"color-fields\", &output_option, N_(\"color redundant metadata fields from previous line differently\"), OUTPUT_COLOR_FIELDS),\n \n \t\t/*\n \t\t * The following two options are parsed by parse_revision_opt()\n@@ -757,6 +820,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \trevs.diffopt.flags.follow_renames = 0;\n \targc = parse_options_end(&ctx);\n \n+\tif ((output_option & OUTPUT_COLOR_LINE) &&\n+\t    (output_option & OUTPUT_COLOR_FIELDS))\n+\t\tdie(_(\"cannot ask for colored lines and fields at the same time\"));\n+\n \tif (incremental || (output_option & OUTPUT_PORCELAIN)) {\n \t\tif (show_progress > 0)\n \t\t\tdie(_(\"--progress can't be used with --incremental or porcelain formats\"));\n@@ -960,8 +1027,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \n \tif (!(output_option & OUTPUT_PORCELAIN)) {\n \t\tfind_alignment(&sb, &output_option);\n-\t\tif ((output_option & OUTPUT_COLOR_LINE) &&\n-\t\t    !repeated_meta_color)\n+\t\tif (!repeated_meta_color &&\n+\t\t    (output_option & (OUTPUT_COLOR_LINE | OUTPUT_COLOR_FIELDS)))\n \t\t\trepeated_meta_color = GIT_COLOR_DARK;\n \t}\n \ndiff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh\nindex a2b7090cef..18f9c9a16d 100755\n--- a/t/t8012-blame-colors.sh\n+++ b/t/t8012-blame-colors.sh\n@@ -15,4 +15,42 @@ test_expect_success 'colored blame colors contiguous lines' '\n \ttest_line_count = 3 H.expect\n '\n \n+test_expect_success 'colored blame colors continuous fields' '\n+\n+\tgit mv hello.c world.c &&\n+\tgit commit -a -m \"moved file\" &&\n+\tcat <<-EOF >> world.c &&\n+\tvoid world()\n+\t{\n+\t\tputs(\"world\");\n+\t}\n+\tEOF\n+\tgit add world.c &&\n+\tGIT_AUTHOR_NAME=\"F\" GIT_AUTHOR_EMAIL=\"F@test.git\" \\\n+\t\tgit commit -m \"forgot to add changes to moved file\" &&\n+\n+\tgit blame --abbrev=12 --color-fields world.c >actual.raw &&\n+\ttest_decode_color <actual.raw >actual &&\n+\n+\tgrep \"<BOLD;BLACK>hello.c\" actual > colored_hello.expect &&\n+\tgrep \"hello.c\" actual > all_hello.expect &&\n+\ttest_line_count = 9 colored_hello.expect &&\n+\ttest_line_count = 10 all_hello.expect &&\n+\n+\tgrep \"<BOLD;BLACK>world.c\" actual > colored_world.expect &&\n+\tgrep \"world.c\" actual > all_world.expect &&\n+\ttest_line_count = 3 colored_world.expect &&\n+\ttest_line_count = 4 all_world.expect &&\n+\n+\tgrep \"(F\" actual > all_F.expect &&\n+\tgrep \"<BOLD;BLACK>(F\" actual > colored_F.expect &&\n+\ttest_line_count = 8 all_F.expect &&\n+\ttest_line_count = 5 colored_F.expect &&\n+\n+\tgrep \"(H\" actual > all_H.expect &&\n+\tgrep \"<BOLD;BLACK>(H\" actual > colored_H.expect &&\n+\ttest_line_count = 5 all_H.expect &&\n+\ttest_line_count = 3 colored_H.expect\n+'\n+\n test_done\n-- \n2.16.0.rc0.223.g4a4ac83678-goog\n\n"},{"id":"336172","messageId":"xmqqr2r088p3.fsf@gitster.mtv.corp.google.com","threadId":"47149","inReplyTo":"20180104224007.214548-2-sbeller@google.com","subject":"Re: [PATCHv3 1/4] color.h: document and modernize header","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-08T19:14:00Z","receivedAt":"2018-01-08T19:14:11Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n>  /*\n> - * Set the color buffer (which must be COLOR_MAXLEN bytes)\n> - * to the raw color bytes; this is useful for initializing\n> - * default color variables.\n> + * NEEDSWWORK: document this function or refactor grep.c to stop using this\n> + * function.\n>   */\n> -void color_set(char *dst, const char *color_bytes);\n> +extern void color_set(char *dst, const char *color_bytes);\n\nThe original that is removed by the patch documents the function\nwell enough; as long as the NEEDSWORK comment is followed through\nin a later step in the series, it's alright, though ;-)\n\n> -int git_config_colorbool(const char *var, const char *value);\n> -int want_color(int var);\n> -int color_parse(const char *value, char *dst);\n> -int color_parse_mem(const char *value, int len, char *dst);\n> +/*\n> + * Parse a config option, which can be a boolean or one of\n> + * \"never\", \"auto\", \"always\". Return a constant of\n> + * GIT_COLOR_NEVER for \"never\" or negative boolean,\n> + * GIT_COLOR_ALWAYS for \"always\" or a positive boolean,\n> + * and GIT_COLOR_AUTO for \"auto\".\n> + */\n> +extern int git_config_colorbool(const char *var, const char *value);\n\n\"never\" and \"always\" not being part of usual boolean vocabulary\nmakes it a bit awkward to explain.\n\n> +/*\n> + * Output the formatted string in the specified color (and then reset to normal\n> + * color so subsequent output is uncolored). Omits the color encapsulation if\n> + * `color` is NULL. The `color_fprintf_ln` prints a new line after resetting\n> + * the color. The `color_print_strbuf` prints the given pre-formatted strbuf\n> + * instead, up to its first NUL character.\n> + */\n\nObviously, it does not have to be part of this step nor series, but\nthe above observation makes us realize that color_print_strbuf()\nwould probably be an unreasonably narrow interface.  It is not too\nmuch to ask the caller to dereference and pass only the .buf\ncomponent of the strbuf to an alternative helper that takes \"const\nchar *\" and by doing so would allow us to let other callers that do\nnot have a strbuf but just a plain string use it, too.\n\nLooks good.\n"},{"id":"336176","messageId":"xmqqk1ws87qw.fsf@gitster.mtv.corp.google.com","threadId":"47149","inReplyTo":"20180104224007.214548-3-sbeller@google.com","subject":"Re: [PATCHv3 2/4] builtin/blame: dim uninteresting metadata","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-08T19:34:31Z","receivedAt":"2018-01-08T19:34:37Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> +color.blame.repeatedMeta::\n> +\tUse the customized color for the part of git-blame output that\n> +\tis repeated meta information per line (such as commit id,\n> +\tauthor name, date and timezone). Defaults to dark gray.\n> +\n> ...\n\n\"Dark gray on default background\" may alleviate worrries from those\nwho prefer black ink on white paper display by hinting that both\nforeground and background colors can be configured.\n\nDo we want to make this overridable from the command line,\ni.e. --color-repeated-meta=gray?\n\n\n> +#define OUTPUT_COLOR_LINE\t02000\n\nThe name of the macro implies that this is (or at least can be) a\nlot more generic UI request than merely \"paint the same metadata on\nadjacent lines in a different color\".\n\n> +\t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line differently\"), OUTPUT_COLOR_LINE),\n\nShould this eventually become \"--color=<yes,no,auto>\" that is more\nusual and generic, possibly defaulting to \"auto\" in the future, I\nwonder?\n\n> diff --git a/color.h b/color.h\n> index 2e768a10c6..2df2f86698 100644\n> --- a/color.h\n> +++ b/color.h\n> @@ -30,6 +30,7 @@ struct strbuf;\n>  #define GIT_COLOR_BLUE\t\t\"\\033[34m\"\n>  #define GIT_COLOR_MAGENTA\t\"\\033[35m\"\n>  #define GIT_COLOR_CYAN\t\t\"\\033[36m\"\n> +#define GIT_COLOR_DARK\t\t\"\\033[1;30m\"\n>  #define GIT_COLOR_BOLD_RED\t\"\\033[1;31m\"\n>  #define GIT_COLOR_BOLD_GREEN\t\"\\033[1;32m\"\n>  #define GIT_COLOR_BOLD_YELLOW\t\"\\033[1;33m\"\n\nHow about using CYAN just like \"diff\" output uses it to paint the\nleast interesting lines?  That way we will keep the \"uninteresting\nis cyan\" consistency for the default settings without adding a new\ncolor to the palette.\n"},{"id":"336180","messageId":"xmqqfu7g86qs.fsf@gitster.mtv.corp.google.com","threadId":"47149","inReplyTo":"20180104224007.214548-5-sbeller@google.com","subject":"Re: [PATCHv3 4/4] builtin/blame: highlight recently changed lines","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-01-08T19:56:11Z","receivedAt":"2018-01-08T19:56:20Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Stefan Beller <sbeller@google.com> writes:\n\n> +static struct color_field {\n> +\ttimestamp_t hop;\n> +\tchar col[COLOR_MAXLEN];\n> +} *colorfield;\n> +static int colorfield_nr, colorfield_alloc;\n> +\n> +static void parse_color_fields(const char *s)\n> +{\n> +\tstruct string_list l = STRING_LIST_INIT_DUP;\n> +\tstruct string_list_item *item;\n> +\tenum { EXPECT_DATE, EXPECT_COLOR } next = EXPECT_COLOR;\n> +\n> +\t/* Ideally this would be stripped and split at the same time? */\n> +\tstring_list_split(&l, s, ',', -1);\n> +\tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n> +\n> +\tfor_each_string_list_item(item, &l) {\n> +\t\tswitch (next) {\n> +\t\tcase EXPECT_DATE:\n> +\t\t\tcolorfield[colorfield_nr].hop = approxidate(item->string);\n> +\t\t\tnext = EXPECT_COLOR;\n> +\t\t\tcolorfield_nr++;\n> +\t\t\tALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n> +\t\t\tbreak;\n\nThis should make sure cf[i].hop is monotonically increasing to avoid\nend-user mistakes, I would think (what's 'hop' by the way?).\n\n> +\t\tcase EXPECT_COLOR:\n> +\t\t\tif (color_parse(item->string, colorfield[colorfield_nr].col))\n> +\t\t\t\tdie(_(\"expecting a color: %s\"), item->string);\n\nWhen you have a typo in one of your configuration files, say \"[color\n\"blame\"] highlightrecent = 1,week,blue,...\", you'd want to see a bit\nmore than just \"expecting a color: week\" to help you diagnose and\nresolve the issue.  Giving the name of the variable and the file the\nwrong definition was found in would be needed, givin that this is\ncalled from the config callback git_blame_config() below.\n\n> +\t\t\tnext = EXPECT_DATE;\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n> +\n> +\tif (next == EXPECT_COLOR)\n> +\t\tdie (_(\"must end with a color\"));\n\nSame here.\n\n>  \t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line differently\"), OUTPUT_COLOR_LINE),\n>  \t\tOPT_BIT(0, \"color-fields\", &output_option, N_(\"color redundant metadata fields from previous line differently\"), OUTPUT_COLOR_FIELDS),\n> +\t\tOPT_BIT(0, \"heated-lines\", &output_option, N_(\"color lines by age\"), OUTPUT_HEATED_LINES),\n\nThese options may be useful while having fun experimenting, but my\ngut feeling is that these are too fine-grained for end-users to\ntweak per invocation basis (which is what command line options are\nfor).  \n\nBut perhaps I am biased (as anybody else), as personally I find\nanything beyond step 2/4 uninteresting, and not adding too many of\nthese options is consistent with that viewpoint ;-)\n\nIn any case, thanks for a fun read.\n\n"},{"id":"336183","messageId":"CAGZ79kays4c89quf+8g7vCzydoWxUCnZ0GmC-SP7tgE1vd=fzg@mail.gmail.com","threadId":"47149","inReplyTo":"xmqqfu7g86qs.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv3 4/4] builtin/blame: highlight recently changed lines","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-01-08T20:09:12Z","receivedAt":"2018-01-08T20:09:19Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jan 8, 2018 at 11:56 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> +static struct color_field {\n>> +     timestamp_t hop;\n>> +     char col[COLOR_MAXLEN];\n>> +} *colorfield;\n>> +static int colorfield_nr, colorfield_alloc;\n>> +\n>> +static void parse_color_fields(const char *s)\n>> +{\n>> +     struct string_list l = STRING_LIST_INIT_DUP;\n>> +     struct string_list_item *item;\n>> +     enum { EXPECT_DATE, EXPECT_COLOR } next = EXPECT_COLOR;\n>> +\n>> +     /* Ideally this would be stripped and split at the same time? */\n>> +     string_list_split(&l, s, ',', -1);\n>> +     ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n>> +\n>> +     for_each_string_list_item(item, &l) {\n>> +             switch (next) {\n>> +             case EXPECT_DATE:\n>> +                     colorfield[colorfield_nr].hop = approxidate(item->string);\n>> +                     next = EXPECT_COLOR;\n>> +                     colorfield_nr++;\n>> +                     ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc);\n>> +                     break;\n>\n> This should make sure cf[i].hop is monotonically increasing to avoid\n> end-user mistakes, I would think (what's 'hop' by the way?).\n>\n>> +             case EXPECT_COLOR:\n>> +                     if (color_parse(item->string, colorfield[colorfield_nr].col))\n>> +                             die(_(\"expecting a color: %s\"), item->string);\n>\n> When you have a typo in one of your configuration files, say \"[color\n> \"blame\"] highlightrecent = 1,week,blue,...\", you'd want to see a bit\n> more than just \"expecting a color: week\" to help you diagnose and\n> resolve the issue.  Giving the name of the variable and the file the\n> wrong definition was found in would be needed, givin that this is\n> called from the config callback git_blame_config() below.\n>\n>> +                     next = EXPECT_DATE;\n>> +                     break;\n>> +             }\n>> +     }\n>> +\n>> +     if (next == EXPECT_COLOR)\n>> +             die (_(\"must end with a color\"));\n>\n> Same here.\n>\n>>               OPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line differently\"), OUTPUT_COLOR_LINE),\n>>               OPT_BIT(0, \"color-fields\", &output_option, N_(\"color redundant metadata fields from previous line differently\"), OUTPUT_COLOR_FIELDS),\n>> +             OPT_BIT(0, \"heated-lines\", &output_option, N_(\"color lines by age\"), OUTPUT_HEATED_LINES),\n>\n> These options may be useful while having fun experimenting, but my\n> gut feeling is that these are too fine-grained for end-users to\n> tweak per invocation basis (which is what command line options are\n> for).\n>\n> But perhaps I am biased (as anybody else), as personally I find\n> anything beyond step 2/4 uninteresting, and not adding too many of\n> these options is consistent with that viewpoint ;-)\n\nSee, I find 2 and 3 uninteresting and just did it 'because someone else\nhinted at that is what they want'. Maybe I was a bad listener.\n\n4 (maybe with 2 in combination) would be all I need as that allows me\nto quickly judge the trustworthiness of code (old code is better,\njust like most liquors? ;)\n\n> In any case, thanks for a fun read.\n\nThanks, I'll reread the comments and see if I can remove some\noptions to make it useful for upstream consumption.\n\nThanks,\nStefan\n"},{"id":"338040","messageId":"87inbgmrug.fsf@evledraar.gmail.com","threadId":"47149","inReplyTo":"20180104224007.214548-1-sbeller@google.com","subject":"Re: [PATCHv3 0/4] blame: (dim rep. metadata lines or fields, decay date coloring)","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-02-01T19:29:11Z","receivedAt":"2018-02-01T19:29:20Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 04 2018, Stefan Beller jotted:\n\n> Stefan Beller (4):\n>   color.h: document and modernize header\n>   builtin/blame: dim uninteresting metadata\n>   builtin/blame: add option to color metadata fields separately\n>   builtin/blame: highlight recently changed lines\n\nI like this feature in principle, but I can't get it to work. Building\npu and:\n\n    ./git -c color.ui=always --exec-path=$PWD blame Makefile\n\nShows no colors. Neither does:\n\n    ./git -c color.ui=always --exec-path=$PWD -c color.blame.highlightRecent=\"red,12 month ago,blue\" blame Makefile\n\nAnd there's a bug, it segfaults on any custom value to the other config\noption:\n\n    ./git -c color.ui=always --exec-path=$PWD -c color.blame.repeatedMeta=red blame Makefile\n\n    0x00000000004c312b in color_parse_mem (value=0x8f6520 \"red\", value_len=3, dst=0x1 <Address 0x1 out of bounds>) at color.c:272\n    272                     OUT('\\033');\n\nThe repeated_meta_color variable is NULL when passed to\ncolor_parse_mem(). Didn't dig further.\n"},{"id":"338045","messageId":"CAGZ79kZeDZD4pOSv-Bk3AspfMYrcjHEnJrQf9EZ3LevZx9bnGA@mail.gmail.com","threadId":"47149","inReplyTo":"87inbgmrug.fsf@evledraar.gmail.com","subject":"Re: [PATCHv3 0/4] blame: (dim rep. metadata lines or fields, decay date coloring)","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-02-01T20:24:33Z","receivedAt":"2018-02-01T20:24:39Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Thu, Feb 1, 2018 at 11:29 AM, Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n>\n> On Thu, Jan 04 2018, Stefan Beller jotted:\n>\n>> Stefan Beller (4):\n>>   color.h: document and modernize header\n>>   builtin/blame: dim uninteresting metadata\n>>   builtin/blame: add option to color metadata fields separately\n>>   builtin/blame: highlight recently changed lines\n>\n> I like this feature in principle, but I can't get it to work. Building\n> pu and:\n\nThanks for testing the feature!\nPlease use the flag `--color-lines`, `--color-fields` or `--heated-lines`\nfor experimentation.\n\nIn the future we may decide that one of them becomes the default\n(which one?) and is triggered by the color.ui=always setting as well.\n\n>     ./git -c color.ui=always --exec-path=$PWD blame Makefile\n>\n> Shows no colors. Neither does:\n>\n>     ./git -c color.ui=always --exec-path=$PWD -c color.blame.highlightRecent=\"red,12 month ago,blue\" blame Makefile\n>\n> And there's a bug, it segfaults on any custom value to the other config\n> option:\n>\n>     ./git -c color.ui=always --exec-path=$PWD -c color.blame.repeatedMeta=red blame Makefile\n>\n>     0x00000000004c312b in color_parse_mem (value=0x8f6520 \"red\", value_len=3, dst=0x1 <Address 0x1 out of bounds>) at color.c:272\n>     272                     OUT('\\033');\n>\n> The repeated_meta_color variable is NULL when passed to\n> color_parse_mem(). Didn't dig further.\n\nThanks for noticing.\nFix below (white space mangled)\n\n--- i/builtin/blame.c\n+++ w/builtin/blame.c\n@@ -48,7 +48,7 @@ static int xdl_opts;\n static int abbrev = -1;\n static int no_whole_file_rename;\n static int show_progress;\n-static char *repeated_meta_color;\n+static char repeated_meta_color[COLOR_MAXLEN];\n\n static struct date_mode blame_date_mode = { DATE_ISO8601 };\n static size_t blame_date_width;\n@@ -1099,9 +1099,9 @@ int cmd_blame(int argc, const char **argv, const\nchar *prefix)\n\n        if (!(output_option & OUTPUT_PORCELAIN)) {\n                find_alignment(&sb, &output_option);\n-               if (!repeated_meta_color &&\n+               if (!*repeated_meta_color &&\n                    (output_option & (OUTPUT_COLOR_LINE | OUTPUT_COLOR_FIELDS)))\n-                       repeated_meta_color = GIT_COLOR_DARK;\n+                       strcpy(repeated_meta_color, GIT_COLOR_DARK);\n        }\n"},{"id":"338776","messageId":"CAGZ79kZKaKuvF2mp2r7Vt_3r73m7BmFqc_VPctKezR7gXQTx_A@mail.gmail.com","threadId":"47149","inReplyTo":"xmqqk1ws87qw.fsf@gitster.mtv.corp.google.com","subject":"Re: [PATCHv3 2/4] builtin/blame: dim uninteresting metadata","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-02-08T21:19:09Z","receivedAt":"2018-02-08T21:19:17Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Mon, Jan 8, 2018 at 11:34 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Stefan Beller <sbeller@google.com> writes:\n>\n>> +color.blame.repeatedMeta::\n>> +     Use the customized color for the part of git-blame output that\n>> +     is repeated meta information per line (such as commit id,\n>> +     author name, date and timezone). Defaults to dark gray.\n>> +\n>> ...\n>\n> \"Dark gray on default background\" may alleviate worrries from those\n> who prefer black ink on white paper display by hinting that both\n> foreground and background colors can be configured.\n>\n> Do we want to make this overridable from the command line,\n> i.e. --color-repeated-meta=gray?\n>\n>> +#define OUTPUT_COLOR_LINE    02000\n>\n> The name of the macro implies that this is (or at least can be) a\n> lot more generic UI request than merely \"paint the same metadata on\n> adjacent lines in a different color\".\n>\n>> +             OPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line differently\"), OUTPUT_COLOR_LINE),\n>\n> Should this eventually become \"--color=<yes,no,auto>\" that is more\n> usual and generic, possibly defaulting to \"auto\" in the future, I\n> wonder?\n\nThis sounds like a good change to make, though what does \"yes\"\nmean here? The following patches introduce other modes of\ncolorization, such that --color=<no, auto, lines, fields, decayed>\nwould make sense to me, with \"lines\" as the mode of this patch,\n\"fields\" implemented in the next patch and \"decayed\" having the\nmeaning of the heating algorithm presented in the last patch.\n\nHowever each of these (except no and auto) would want to have\nextra parameters, which as mentioned above could go into its own\nseparate parameters, such that\n\n  git blame --color=line --color-repeated-meta=cyan\n\nwould seem like a good UI. This might look like it could be a good\nidea to have --color-repeated-meta imply --color=<line,auto> if no\n--color is given. But as different coloring modes will have different\narguments to provide extra color information, I wonder what we\nwould do with\n\n  git blame --color=\"mode1\" --extra-color-arg-for-mode-2\n\nSo I don't think the idea of having separate options would be a\ngood idea as we'd have to think about the implications as mentioned\nabove. So maybe\n\n  git blame \"--color=lines,cyan yellow\"\n\nwould work, if you want \"cyan yellow\" as the color configuration in\nthe \"lines\" mode? (bad choice of a background color btw)\n\n>> diff --git a/color.h b/color.h\n>> index 2e768a10c6..2df2f86698 100644\n>> --- a/color.h\n>> +++ b/color.h\n>> @@ -30,6 +30,7 @@ struct strbuf;\n>>  #define GIT_COLOR_BLUE               \"\\033[34m\"\n>>  #define GIT_COLOR_MAGENTA    \"\\033[35m\"\n>>  #define GIT_COLOR_CYAN               \"\\033[36m\"\n>> +#define GIT_COLOR_DARK               \"\\033[1;30m\"\n>>  #define GIT_COLOR_BOLD_RED   \"\\033[1;31m\"\n>>  #define GIT_COLOR_BOLD_GREEN \"\\033[1;32m\"\n>>  #define GIT_COLOR_BOLD_YELLOW        \"\\033[1;33m\"\n>\n> How about using CYAN just like \"diff\" output uses it to paint the\n> least interesting lines?  That way we will keep the \"uninteresting\n> is cyan\" consistency for the default settings without adding a new\n> color to the palette.\n\nsounds good, I'll take that suggestion once we reach consensus on\nthe UI for the user.\n\nThanks,\nStefan\n"}]}