{"thread":{"id":"64894","subject":"[BUG] git-blame: --color-lines ignores --ignore-rev","startedAt":"2026-02-01T07:32:50Z","lastAt":"2026-02-02T17:56:30Z","messageCount":6,"participants":["Seth McDonald","René Scharfe","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"534938","messageId":"aX8BjoOGPIytGXjD@McDaDebianPC","threadId":"64894","inReplyTo":null,"subject":"[BUG] git-blame: --color-lines ignores --ignore-rev","fromName":"Seth McDonald","fromEmail":"sethmcmail@pm.me","sentAt":"2026-02-01T07:32:35Z","receivedAt":"2026-02-01T07:32:50Z","isPatch":false,"sender":{"key":"sethmcmail@pm.me","avatar":null},"body":"G'day all,\n\nI believe I've found a bug regarding how git-blame(1) colours its output\nwhen invoked with both --color-lines and --ignore-rev.\n\n== Summary ==\ngit-blame(1) has two options of interest.  '--color-lines' colours a\nline (in cyan by default) if it has the same blame as the preceding\nline.  And '--ignore-rev <rev>' prevents any line from being blamed on\n<rev>, instead blaming such lines on the most recent commit prior to\n<rev> that modified those lines.\n\nWhen used in combination, it is possible for consecutive lines to have\nthe same blame without the latter lines being coloured correctly.  I\nfirst observed this behaviour on Git 2.47.3, but have since compiled\nthe latest release candidate (Git 2.53.0.rc2) and have observed the same\nbehaviour.\n\n== Reproducibility ==\nI was able to construct a minimal working example as follows.\n\nFirst create and cd(1) into a temporary directory.  Let's name it\n'git-test'.\n\n$ mkdir git-test\n$ cd git-test\n\nMake git(1) ignore the system and global config files.  This is mainly\nto aid in reproducibility.\n\n$ export GIT_CONFIG_SYSTEM=/dev/null\n$ export GIT_CONFIG_GLOBAL=/dev/null\n\nInitialise a Git repository.\n\n$ git init --initial-branch main\nInitialized empty Git repository in /.../git-test/.git/\n\nNow set some default values.  In particular, set blame.coloring to\nrepeatedLines; equivalent to the --color-lines option for git-blame(1).\n\n$ git config set user.name 'John Git'\n$ git config set user.email 'johngit@for.real'\n$ git config set blame.coloring repeatedLines\n$ git config set blame.date short\n\nCreate a file (let's name it 'text'), give it some text, and commit it.\n\n$ echo Why hello there how are you | xargs -n1 > text\n$ git add text\n$ git commit --message 'init'\n[main (root-commit) xxxxxxx] init\n 1 file changed, 6 insertions(+)\n create mode 100644 text\n\nThen fixup some punctuation in the file and commit it.\n\n$ sed -i -e '3s/$/!/' -e '4s/h/H/' -e '6s/$/?/' text\n$ git add text\n$ git commit --message 'fix'\n[main yyyyyyy] fix\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\nIf we now blame the file, we get the following.\n\n$ git blame text\n^xxxxxxx (John Git YYYY-MM-DD 1) Why\n^xxxxxxx (John Git YYYY-MM-DD 2) hello\nyyyyyyyy (John Git YYYY-MM-DD 3) there!\nyyyyyyyy (John Git YYYY-MM-DD 4) How\n^xxxxxxx (John Git YYYY-MM-DD 5) are\nyyyyyyyy (John Git YYYY-MM-DD 6) you?\n\nWhere lines 2 and 4 are coloured cyan.  This is expected, since their\nlisted commits are the same as that on lines 1 and 3, respectively.\n\nNow consider the output of git-blame(1) if we ignore the second commit.\n\n$ git blame --ignore-rev HEAD text\n^xxxxxxx (John Git YYYY-MM-DD 1) Why\n^xxxxxxx (John Git YYYY-MM-DD 2) hello\n^xxxxxxx (John Git YYYY-MM-DD 3) there!\n^xxxxxxx (John Git YYYY-MM-DD 4) How\n^xxxxxxx (John Git YYYY-MM-DD 5) are\n^xxxxxxx (John Git YYYY-MM-DD 6) you?\n\nMy expected behaviour of git-blame(1) is to colour lines 2-6 cyan.  This\nis because lines 1-6 all blame the same commit, meaning lines 2-6 all\nhave the same blame as their respective preceding lines.  And as the man\npage for git-blame(1) states:\n\n$ MANWIDTH=72 man git-blame | sed -n '/--color-lines$/,/^$/p'\n       --color-lines\n           Color line annotations in the default format differently if\n           they come from the same commit as the preceding line. This\n           makes it easier to distinguish code blocks introduced by\n           different commits. The color defaults to cyan and can be\n           adjusted using the color.blame.repeatedLines config option.\n\nHowever, the actual behaviour of git-blame(1) is to colour only lines 2\nand 4 cyan.  That is, it colours the output as if the --ignore-rev\noption was not given.\n\n== Environment ==\nThe following info was added verbatim from git-bugreport(1).\n\n[System Info]\ngit version:\ngit version 2.53.0.rc2\ncpu: x86_64\nbuilt from commit: ab380cb80b0727f7f2d7f6b17592ae6783e9820c\nsizeof-long: 8\nsizeof-size_t: 8\nshell-path: /bin/sh\nrust: disabled\ngettext: enabled\nlibcurl: 8.14.1\nOpenSSL: OpenSSL 3.5.4 30 Sep 2025\nzlib: 1.3.1\nSHA-1: SHA1_DC\nSHA-256: SHA256_BLK\ndefault-ref-format: files\ndefault-hash: sha1\nuname: Linux 6.12.63+deb13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.63-1 (2025-12-30) x86_64\ncompiler info: gnuc: 14.2\nlibc info: glibc: 2.41\n$SHELL (typically, interactive shell): /bin/bash\n\n\n[Enabled Hooks]\n\n\nAnd here's some extra info, in case it helps:\n\nOS: Debian GNU/Linux 13 (trixie)\nTerminal: GNOME Terminal 3.56.2\nbash: (GNU bash) 5.2.37(1)-release (x86_64-pc-linux-gnu)\nsed: (GNU sed) 4.9\nxargs: (GNU findutils) 4.10.0\n\nI'd be happy to further elaborate or provide help if needed.\n\n-- \nTake care,\n\tSeth McDonald.\n\nOn-list:  2336 E8D2 FEB1 5300 692C  62A9 5839 6AD8 9243 D369\nOff-list: 82B9 620E 53D0 A1AE 2D69  6111 C267 B002 0A90 0289\n"},{"id":"534942","messageId":"28ac1ee6-f3e9-4789-92b7-903788430697@web.de","threadId":"64894","inReplyTo":"aX8BjoOGPIytGXjD@McDaDebianPC","subject":"[PATCH] blame: fix coloring for repeated suspects","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2026-02-01T11:47:53Z","receivedAt":"2026-02-01T11:48:02Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The option --ignore-rev passes the blame to an older commit.  This can\ncause adjacent scoreboard entries to blame the same commit.  Currently\nwe only look a the present entry when determining whether a line needs\nto be colored for --color-lines.  Check the previous entry as well.\n\nReported-by: Seth McDonald <sethmcmail@pm.me>\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n builtin/blame.c         | 13 +++++++++----\n t/t8012-blame-colors.sh | 14 ++++++++++++++\n 2 files changed, 23 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 6044973462..bb460346e6 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -454,7 +454,8 @@ static void determine_line_heat(struct commit_info *ci, const char **dest_color)\n \t*dest_color = colorfield[i].col;\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, struct blame_entry *ent,\n+\t\t       int opt, struct blame_entry *prev_ent)\n {\n \tint cnt;\n \tconst char *cp;\n@@ -485,7 +486,10 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int\n \t\t\tthe_hash_algo->hexsz : (size_t) abbrev;\n \n \t\tif (opt & OUTPUT_COLOR_LINE) {\n-\t\t\tif (cnt > 0) {\n+\t\t\tif (cnt > 0 ||\n+\t\t\t    (prev_ent &&\n+\t\t\t     oideq(&suspect->commit->object.oid,\n+\t\t\t\t   &prev_ent->suspect->commit->object.oid))) {\n \t\t\t\tcolor = repeated_meta_color;\n \t\t\t\treset = GIT_COLOR_RESET;\n \t\t\t} else  {\n@@ -571,7 +575,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_ent = NULL;\n \n \tif (option & OUTPUT_PORCELAIN) {\n \t\tfor (ent = sb->ent; ent; ent = ent->next) {\n@@ -593,7 +597,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, option, prev_ent);\n+\t\t\tprev_ent = ent;\n \t\t}\n \t}\n }\ndiff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh\nindex 3d77352650..5562eba436 100755\n--- a/t/t8012-blame-colors.sh\n+++ b/t/t8012-blame-colors.sh\n@@ -28,6 +28,20 @@ test_expect_success 'colored blame colors contiguous lines' '\n \ttest_line_count = 3 H.expect\n '\n \n+test_expect_success 'color lines becoming contiguous due to --ignore-rev' '\n+\tmv hello.c hello.orig &&\n+\tsed \"s/\t/    /g\" <hello.orig >hello.c &&\n+\tgit add hello.c &&\n+\tgit commit -m\"tabs to spaces\" &&\n+\tgit -c color.blame.repeatedLines=yellow blame --color-lines --ignore-rev=HEAD hello.c >actual.raw &&\n+\ttest_decode_color <actual.raw >actual &&\n+\tgrep \"<YELLOW>\" <actual >darkened &&\n+\tgrep \"(F\" darkened > F.expect &&\n+\tgrep \"(H\" darkened > H.expect &&\n+\ttest_line_count = 2 F.expect &&\n+\ttest_line_count = 3 H.expect\n+'\n+\n test_expect_success 'color by age consistently colors old code' '\n \tgit blame --color-by-age hello.c >actual.raw &&\n \tgit -c blame.coloring=highlightRecent blame hello.c >actual.raw.2 &&\n-- \n2.52.0\n"},{"id":"534951","messageId":"aYAFqZRz9EyI7jUQ@McDaDebianPC","threadId":"64894","inReplyTo":"28ac1ee6-f3e9-4789-92b7-903788430697@web.de","subject":"Re: [PATCH] blame: fix coloring for repeated suspects","fromName":"Seth McDonald","fromEmail":"sethmcmail@pm.me","sentAt":"2026-02-02T02:02:25Z","receivedAt":"2026-02-02T02:02:39Z","isPatch":true,"sender":{"key":"sethmcmail@pm.me","avatar":null},"body":"Hi René,\n\nOn Sun, 01 Feb 2026 at 12:47:53 +0100, René Scharfe wrote:\n[...]\n>  builtin/blame.c         | 13 +++++++++----\n>  t/t8012-blame-colors.sh | 14 ++++++++++++++\n>  2 files changed, 23 insertions(+), 4 deletions(-)\n\nI've compiled git(1) with your patch and it seems to fix the issue.\nThanks for the fast response!\n\n-- \nTake care,\n\tSeth McDonald.\n\nOn-list:  2336 E8D2 FEB1 5300 692C  62A9 5839 6AD8 9243 D369\nOff-list: 82B9 620E 53D0 A1AE 2D69  6111 C267 B002 0A90 0289\n"},{"id":"534971","messageId":"xmqqfr7j2u6q.fsf@gitster.g","threadId":"64894","inReplyTo":"28ac1ee6-f3e9-4789-92b7-903788430697@web.de","subject":"Re: [PATCH] blame: fix coloring for repeated suspects","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-02T12:42:21Z","receivedAt":"2026-02-02T12:42:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n> The option --ignore-rev passes the blame to an older commit.  This can\n> cause adjacent scoreboard entries to blame the same commit.  Currently\n> we only look a the present entry when determining whether a line needs\n\n\"look at\"?\n\n> to be colored for --color-lines.  Check the previous entry as well.\n\nWhile this should work, I am kind of surprised that this has to done\nas a sepecial case.  It often happens that two adjacent blocks may\nbe originally pass their blames to different parents of a merge, but\nthen the blame passes down through both branches down to the same\nancestor, at which point these two blocks need to be merged back\ninto the same source again, and I was hoping that a helper function\nfor it would be called to take care of this case as well.\n\nIn any case, thaks for a fix, and with a test, which is great.\n\n"},{"id":"534981","messageId":"62e3ab10-bfa4-4ec7-9838-0bad89d04edd@web.de","threadId":"64894","inReplyTo":"xmqqfr7j2u6q.fsf@gitster.g","subject":"Re: [PATCH] blame: fix coloring for repeated suspects","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2026-02-02T16:24:45Z","receivedAt":"2026-02-02T16:24:50Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"On 2/2/26 1:42 PM, Junio C Hamano wrote:\n> René Scharfe <l.s.r@web.de> writes:\n> \n>> The option --ignore-rev passes the blame to an older commit.  This can\n>> cause adjacent scoreboard entries to blame the same commit.  Currently\n>> we only look a the present entry when determining whether a line needs\n> \n> \"look at\"?\n\nYes.\n\n>> to be colored for --color-lines.  Check the previous entry as well.\n> \n> While this should work, I am kind of surprised that this has to done\n> as a sepecial case.  It often happens that two adjacent blocks may\n> be originally pass their blames to different parents of a merge, but\n> then the blame passes down through both branches down to the same\n> ancestor, at which point these two blocks need to be merged back\n> into the same source again, and I was hoping that a helper function\n> for it would be called to take care of this case as well.\nDo you mean blame_coalesce()?  It is called, but won't merge entries\nthat are not ignored with those that are.  And we do need to keep them\nseparate for blame.markignoredlines to work.\n\nRené\n\n"},{"id":"534990","messageId":"xmqqqzr3yqpf.fsf@gitster.g","threadId":"64894","inReplyTo":"62e3ab10-bfa4-4ec7-9838-0bad89d04edd@web.de","subject":"Re: [PATCH] blame: fix coloring for repeated suspects","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-02T17:56:28Z","receivedAt":"2026-02-02T17:56:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n>> While this should work, I am kind of surprised that this has to done\n>> as a sepecial case.  It often happens that two adjacent blocks may\n>> be originally pass their blames to different parents of a merge, but\n>> then the blame passes down through both branches down to the same\n>> ancestor, at which point these two blocks need to be merged back\n>> into the same source again, and I was hoping that a helper function\n>> for it would be called to take care of this case as well.\n>\n> Do you mean blame_coalesce()?  It is called, but won't merge entries\n> that are not ignored with those that are.  And we do need to keep them\n> separate for blame.markignoredlines to work.\n\nYes, and sigh.  I know \"ignore these commits\" came much later than\nthe main part of blame, and I am not surprised if the way it was\nbolted on was not designed to mesh well with existing framework like\nthe blame_coalesce() helper and what it tried to achieve.\n\nAnyway, thanks for a fix.  Will queue.\n"}]}