Skip to content

Commit 8934ac8

Browse files
Barret Rhodengitster
authored andcommitted
blame: add config options for the output of ignored or unblamable lines
When ignoring commits, the commit that is blamed might not be responsible for the change, due to the inaccuracy of our heuristic. Users might want to know when a particular line has a potentially inaccurate blame. Furthermore, guess_line_blames() may fail to find any parent commit for a given line touched by an ignored commit. Those 'unblamable' lines remain blamed on an ignored commit. Users might want to know if a line is unblamable so that they do not spend time investigating a commit they know is uninteresting. This patch adds two config options to mark these two types of lines in the output of blame. The first option can identify ignored lines by specifying blame.markIgnoredLines. When this option is set, each blame line that was blamed on a commit other than the ignored commit is marked with a '?'. 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. Sometimes we are unable to even guess at what ancestor commit touched a line. These lines are 'unblamable.' The second option, blame.markUnblamableLines, will mark the line with '*'. For example, say we ignore e5e8d36d04cbe, yet we are unable to blame this line on another commit: e5e8d36d04cbe (Barret Rhoden 2016-04-11 13:57:54 -0400 26) appears as: *e5e8d36d04cb (Barret Rhoden 2016-04-11 13:57:54 -0400 26) When these config options are used together, every line touched by an ignored commit will be marked with either a '?' or a '*'. Signed-off-by: Barret Rhoden <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent ae3f36d commit 8934ac8

File tree

6 files changed

+119
-2
lines changed

6 files changed

+119
-2
lines changed

Documentation/blame-options.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,12 @@ take effect.
115115
change never happened. Lines that were changed or added by an ignored
116116
commit will be blamed on the previous commit that changed that line or
117117
nearby lines. This option may be specified multiple times to ignore
118-
more than one revision.
118+
more than one revision. If the `blame.markIgnoredLines` config option
119+
is set, then lines that were changed by an ignored commit and attributed to
120+
another commit will be marked with a `?` in the blame output. If the
121+
`blame.markUnblamableLines` config option is set, then those lines touched
122+
by an ignored commit that we could not attribute to another revision are
123+
marked with a '*'.
119124

120125
--ignore-revs-file <file>::
121126
Ignore revisions listed in `file`, which must be in the same format as an

Documentation/config/blame.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,12 @@ blame.ignoreRevsFile::
2626
`#` are ignored. This option may be repeated multiple times. Empty
2727
file names will reset the list of ignored revisions. This option will
2828
be handled before the command line option `--ignore-revs-file`.
29+
30+
blame.markUnblamables::
31+
Mark lines that were changed by an ignored revision that we could not
32+
attribute to another commit with a '*' in the output of
33+
linkgit:git-blame[1].
34+
35+
blame.markIgnoredLines::
36+
Mark lines that were changed by an ignored revision that we attributed to
37+
another commit with a '?' in the output of linkgit:git-blame[1].

blame.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,9 @@ void blame_coalesce(struct blame_scoreboard *sb)
479479

480480
for (ent = sb->ent; ent && (next = ent->next); ent = next) {
481481
if (ent->suspect == next->suspect &&
482-
ent->s_lno + ent->num_lines == next->s_lno) {
482+
ent->s_lno + ent->num_lines == next->s_lno &&
483+
ent->ignored == next->ignored &&
484+
ent->unblamable == next->unblamable) {
483485
ent->num_lines += next->num_lines;
484486
ent->next = next->next;
485487
blame_origin_decref(next->suspect);
@@ -729,8 +731,14 @@ static void split_overlap(struct blame_entry *split,
729731
struct blame_origin *parent)
730732
{
731733
int chunk_end_lno;
734+
int i;
732735
memset(split, 0, sizeof(struct blame_entry [3]));
733736

737+
for (i = 0; i < 3; i++) {
738+
split[i].ignored = e->ignored;
739+
split[i].unblamable = e->unblamable;
740+
}
741+
734742
if (e->s_lno < tlno) {
735743
/* there is a pre-chunk part not blamed on parent */
736744
split[0].suspect = blame_origin_incref(e->suspect);
@@ -851,6 +859,8 @@ static struct blame_entry *split_blame_at(struct blame_entry *e, int len,
851859
struct blame_entry *n = xcalloc(1, sizeof(struct blame_entry));
852860

853861
n->suspect = new_suspect;
862+
n->ignored = e->ignored;
863+
n->unblamable = e->unblamable;
854864
n->lno = e->lno + len;
855865
n->s_lno = e->s_lno + len;
856866
n->num_lines = e->num_lines - len;
@@ -939,12 +949,14 @@ static void ignore_blame_entry(struct blame_entry *e,
939949
blame_origin_incref(e->suspect));
940950
}
941951
if (line_blames[i].is_parent) {
952+
e->ignored = 1;
942953
blame_origin_decref(e->suspect);
943954
e->suspect = blame_origin_incref(parent);
944955
e->s_lno = line_blames[i - entry_len + 1].s_lno;
945956
e->next = *ignoredp;
946957
*ignoredp = e;
947958
} else {
959+
e->unblamable = 1;
948960
/* e->s_lno is already in the target's address space. */
949961
e->next = *diffp;
950962
*diffp = e;

blame.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ struct blame_entry {
9292
* scanning the lines over and over.
9393
*/
9494
unsigned score;
95+
int ignored;
96+
int unblamable;
9597
};
9698

9799
/*

builtin/blame.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ static int show_progress;
5353
static char repeated_meta_color[COLOR_MAXLEN];
5454
static int coloring_mode;
5555
static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;
56+
static int mark_unblamable_lines;
57+
static int mark_ignored_lines;
5658

5759
static struct date_mode blame_date_mode = { DATE_ISO8601 };
5860
static size_t blame_date_width;
@@ -480,6 +482,14 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
480482
}
481483
}
482484

485+
if (mark_unblamable_lines && ent->unblamable) {
486+
length--;
487+
putchar('*');
488+
}
489+
if (mark_ignored_lines && ent->ignored) {
490+
length--;
491+
putchar('?');
492+
}
483493
printf("%.*s", length, hex);
484494
if (opt & OUTPUT_ANNOTATE_COMPAT) {
485495
const char *name;
@@ -706,6 +716,14 @@ static int git_blame_config(const char *var, const char *value, void *cb)
706716
string_list_insert(&ignore_revs_file_list, str);
707717
return 0;
708718
}
719+
if (!strcmp(var, "blame.markunblamablelines")) {
720+
mark_unblamable_lines = git_config_bool(var, value);
721+
return 0;
722+
}
723+
if (!strcmp(var, "blame.markignoredlines")) {
724+
mark_ignored_lines = git_config_bool(var, value);
725+
return 0;
726+
}
709727
if (!strcmp(var, "color.blame.repeatedlines")) {
710728
if (color_parse_mem(value, strlen(value), repeated_meta_color))
711729
warning(_("invalid color '%s' in color.blame.repeatedLines"),

t/t8013-blame-ignore-revs.sh

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,77 @@ test_expect_success bad_files_and_revs '
121121
test_must_fail git blame file --ignore-revs-file ignore_norev 2>err &&
122122
test_i18ngrep "invalid object name: NOREV" err
123123
'
124+
125+
# For ignored revs that have added 'unblamable' lines, mark those lines with a
126+
# '*'
127+
# A--B--X--Y
128+
# Lines 3 and 4 are from Y and unblamable. This was set up in
129+
# ignore_rev_adding_unblamable_lines.
130+
test_expect_success mark_unblamable_lines '
131+
git config --add blame.markUnblamableLines true &&
132+
133+
git blame --ignore-rev Y file >blame_raw &&
134+
echo "*" >expect &&
135+
136+
sed -n "3p" blame_raw | cut -c1 >actual &&
137+
test_cmp expect actual &&
138+
139+
sed -n "4p" blame_raw | cut -c1 >actual &&
140+
test_cmp expect actual
141+
'
142+
143+
# Commit Z will touch the first two lines. Y touched all four.
144+
# A--B--X--Y--Z
145+
# The blame output when ignoring Z should be:
146+
# ?Y ... 1)
147+
# ?Y ... 2)
148+
# Y ... 3)
149+
# Y ... 4)
150+
# We're checking only the first character
151+
test_expect_success mark_ignored_lines '
152+
git config --add blame.markIgnoredLines true &&
153+
154+
test_write_lines line-one-Z line-two-Z y3 y4 >file &&
155+
git add file &&
156+
test_tick &&
157+
git commit -m Z &&
158+
git tag Z &&
159+
160+
git blame --ignore-rev Z file >blame_raw &&
161+
echo "?" >expect &&
162+
163+
sed -n "1p" blame_raw | cut -c1 >actual &&
164+
test_cmp expect actual &&
165+
166+
sed -n "2p" blame_raw | cut -c1 >actual &&
167+
test_cmp expect actual &&
168+
169+
sed -n "3p" blame_raw | cut -c1 >actual &&
170+
! test_cmp expect actual &&
171+
172+
sed -n "4p" blame_raw | cut -c1 >actual &&
173+
! test_cmp expect actual
174+
'
175+
176+
# For ignored revs that added 'unblamable' lines and more recent commits changed
177+
# the blamable lines, mark the unblamable lines with a
178+
# '*'
179+
# A--B--X--Y--Z
180+
# Lines 3 and 4 are from Y and unblamable, as set up in
181+
# ignore_rev_adding_unblamable_lines. Z changed lines 1 and 2.
182+
test_expect_success mark_unblamable_lines_intermediate '
183+
git config --add blame.markUnblamableLines true &&
184+
185+
git blame --ignore-rev Y file >blame_raw 2>stderr &&
186+
echo "*" >expect &&
187+
188+
sed -n "3p" blame_raw | cut -c1 >actual &&
189+
test_cmp expect actual &&
190+
191+
sed -n "4p" blame_raw | cut -c1 >actual &&
192+
test_cmp expect actual
193+
'
194+
124195
# The heuristic called by guess_line_blames() tries to find the size of a
125196
# blame_entry 'e' in the parent's address space. Those calculations need to
126197
# check for negative or zero values for when a blame entry is completely outside

0 commit comments

Comments
 (0)