When ignoring commits, the commit that is blamed might not be responsible for the change. Users might want to know when a particular line has a potentially inaccurate blame. This patch adds a config option to identify these lines by specifying blame.markIgnoredFiles. When this option is set, each blame line is marked with an '*'. For example: 278b6158d6fdb (Barret Rhoden 2016-04-11 13:57:54 -0400 26) appears as: *278b6158d6fd (Barret Rhoden 2016-04-11 13:57:54 -0400 26) where the '*' is placed before the commit, and the hash has one fewer characters. Signed-off-by: Barret Rhoden <brho@xxxxxxxxxx> --- Documentation/config/blame.txt | 4 ++++ blame.c | 4 ++++ blame.h | 1 + builtin/blame.c | 9 +++++++++ t/t8013-blame-ignore-revs.sh | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 51 insertions(+) diff --git a/Documentation/config/blame.txt b/Documentation/config/blame.txt index 4da2788f306d..9f7f0fcf42c9 100644 --- a/Documentation/config/blame.txt +++ b/Documentation/config/blame.txt @@ -26,3 +26,7 @@ blame.ignoreRevsFile:: `#` are ignored. This option may be repeated multiple times. Empty file names will reset the list of ignored revisions. This option will be handled before the command line option `--ignore-revs-file`. + +blame.markIgnoredLines:: + Mark lines that were changed by an ignored revision with a '*' in the + output of linkgit:git-blame[1]. diff --git a/blame.c b/blame.c index 4427cde95dc5..34debdba1a7f 100644 --- a/blame.c +++ b/blame.c @@ -480,6 +480,7 @@ void blame_coalesce(struct blame_scoreboard *sb) for (ent = sb->ent; ent && (next = ent->next); ent = next) { if (ent->suspect == next->suspect && ent->s_lno + ent->num_lines == next->s_lno && + ent->ignored == next->ignored && ent->unblamable == next->unblamable) { ent->num_lines += next->num_lines; ent->next = next->next; @@ -732,6 +733,7 @@ static void split_overlap(struct blame_entry *split, int chunk_end_lno; memset(split, 0, sizeof(struct blame_entry [3])); + split[0].ignored = split[1].ignored = split[2].ignored = e->ignored; split[0].unblamable = e->unblamable; split[1].unblamable = e->unblamable; split[2].unblamable = e->unblamable; @@ -854,6 +856,7 @@ static struct blame_entry *split_blame_at(struct blame_entry *e, int len, struct blame_entry *n = xcalloc(1, sizeof(struct blame_entry)); n->suspect = new_suspect; + n->ignored = e->ignored; n->unblamable = e->unblamable; n->lno = e->lno + len; n->s_lno = e->s_lno + len; @@ -951,6 +954,7 @@ static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq, blame_origin_decref(e->suspect); e->suspect = blame_origin_incref(parent); e->s_lno += offset; + e->ignored = 1; /* The top part of any ignored diff will not exist in * the parent, and we will never be able to accurately * blame it. We'll keep it on the blame list for the diff --git a/blame.h b/blame.h index 91664913d7c4..53df8b4c5b3f 100644 --- a/blame.h +++ b/blame.h @@ -92,6 +92,7 @@ struct blame_entry { * scanning the lines over and over. */ unsigned score; + int ignored; int unblamable; }; diff --git a/builtin/blame.c b/builtin/blame.c index 5f38e9dccddd..46d96905de75 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -53,6 +53,7 @@ static int show_progress; static char repeated_meta_color[COLOR_MAXLEN]; static int coloring_mode; static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP; +static int mark_ignored_lines; static struct date_mode blame_date_mode = { DATE_ISO8601 }; static size_t blame_date_width; @@ -482,6 +483,10 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int } } + if (mark_ignored_lines && ent->ignored) { + length--; + putchar('*'); + } if (ent->unblamable) memset(hex, '0', length); printf("%.*s", length, hex); @@ -710,6 +715,10 @@ static int git_blame_config(const char *var, const char *value, void *cb) string_list_insert(&ignore_revs_file_list, str); return 0; } + if (!strcmp(var, "blame.markignoredlines")) { + mark_ignored_lines = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "color.blame.repeatedlines")) { if (color_parse_mem(value, strlen(value), repeated_meta_color)) warning(_("invalid color '%s' in color.blame.repeatedLines"), diff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh index c5ef1e4b1b00..6bd17febec3a 100755 --- a/t/t8013-blame-ignore-revs.sh +++ b/t/t8013-blame-ignore-revs.sh @@ -121,6 +121,39 @@ test_expect_success bad_files_and_revs ' test_i18ngrep "Invalid object name: NOREV" err ' +# Commit Z will touch the first two lines. Y touched all four. +# A--B--X--Y--Z +# The blame output when ignoring Z should be: +# ^Y ... 1) +# ^Y ... 2) +# Y ... 3) +# Y ... 4) +# We're checking only the first character +test_expect_success mark_ignored_lines ' + git config --add blame.markIgnoredLines true && + + test_write_lines line-one-Z line-two-Z new_line3 new_line4 >file && + git add file && + test_tick && + git commit -m Z && + git tag Z && + + git blame --ignore-rev Z file >blame_raw && + echo "*" >expect && + + sed -n "1p" blame_raw | cut -c1 >actual && + test_cmp expect actual && + + sed -n "2p" blame_raw | cut -c1 >actual && + test_cmp expect actual && + + sed -n "3p" blame_raw | cut -c1 >actual && + ! test_cmp expect actual && + + sed -n "4p" blame_raw | cut -c1 >actual && + ! test_cmp expect actual + ' + # Resetting the repo and creating: # # A--B--M -- 2.21.0.rc2.261.ga7da99ff1b-goog