Skip to content

Commit 868a8f9

Browse files
committed
Merge branch 'br/blame-ignore' into pu
"git blame" learned to "ignore" commits in the history, whose effects (as well as their presence) get ignored. * br/blame-ignore: SQUASH??? blame: add tests for ignoring revisions blame: add a config option to mark ignored lines blame: add the ability to ignore commits and their changes blame: use a helper function in blame_chunk() Move init_skiplist() outside of fsck
2 parents e927886 + 43a290e commit 868a8f9

File tree

11 files changed

+422
-68
lines changed

11 files changed

+422
-68
lines changed

Documentation/blame-options.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,21 @@ commit. And the default value is 40. If there are more than one
110110
`-C` options given, the <num> argument of the last `-C` will
111111
take effect.
112112

113+
--ignore-rev <rev>::
114+
Ignore changes made by the revision when assigning blame, as if the
115+
change never happened. Lines that were changed or added by an ignored
116+
commit will be blamed on the previous commit that changed that line or
117+
nearby lines. This option may be specified multiple times to ignore
118+
more than one revision. If the `blame.markIgnoredLines` config option
119+
is set, then lines that were changed by an ignored commit will be
120+
marked with a `*` in the blame output.
121+
122+
--ignore-revs-file <file>::
123+
Ignore revisions listed in `file`, one unabbreviated object name per line.
124+
Whitespace and comments beginning with `#` are ignored. This option may be
125+
repeated, and these files will be processed after any files specified with
126+
the `blame.ignoreRevsFile` config option. An empty file name, `""`, will
127+
clear the list of revs from previously processed files.
128+
113129
-h::
114130
Show help message.

Documentation/config/blame.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,14 @@ blame.showEmail::
1919
blame.showRoot::
2020
Do not treat root commits as boundaries in linkgit:git-blame[1].
2121
This option defaults to false.
22+
23+
blame.ignoreRevsFile::
24+
Ignore revisions listed in the file, one unabbreviated object name per
25+
line, in linkgit:git-blame[1]. Whitespace and comments beginning with
26+
`#` are ignored. This option may be repeated multiple times. Empty
27+
file names will reset the list of ignored revisions. This option will
28+
be handled before the command line option `--ignore-revs-file`.
29+
30+
blame.markIgnoredLines::
31+
Mark lines that were changed by an ignored revision with a '*' in the
32+
output of linkgit:git-blame[1].

Documentation/git-blame.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ SYNOPSIS
1010
[verse]
1111
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
1212
[-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
13+
[--ignore-rev <rev>] [--ignore-revs-file <file>]
1314
[--progress] [--abbrev=<n>] [<rev> | --contents <file> | --reverse <rev>..<rev>]
1415
[--] <file>
1516

blame.c

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,9 @@ void blame_coalesce(struct blame_scoreboard *sb)
480480

481481
for (ent = sb->ent; ent && (next = ent->next); ent = next) {
482482
if (ent->suspect == next->suspect &&
483-
ent->s_lno + ent->num_lines == next->s_lno) {
483+
ent->s_lno + ent->num_lines == next->s_lno &&
484+
ent->ignored == next->ignored &&
485+
ent->unblamable == next->unblamable) {
484486
ent->num_lines += next->num_lines;
485487
ent->next = next->next;
486488
blame_origin_decref(next->suspect);
@@ -732,6 +734,11 @@ static void split_overlap(struct blame_entry *split,
732734
int chunk_end_lno;
733735
memset(split, 0, sizeof(struct blame_entry [3]));
734736

737+
split[0].ignored = split[1].ignored = split[2].ignored = e->ignored;
738+
split[0].unblamable = e->unblamable;
739+
split[1].unblamable = e->unblamable;
740+
split[2].unblamable = e->unblamable;
741+
735742
if (e->s_lno < tlno) {
736743
/* there is a pre-chunk part not blamed on parent */
737744
split[0].suspect = blame_origin_incref(e->suspect);
@@ -839,6 +846,27 @@ static struct blame_entry *reverse_blame(struct blame_entry *head,
839846
return tail;
840847
}
841848

849+
/* Splits a blame entry into two entries at 'len' lines. The original 'e'
850+
* consists of len lines, i.e. [e->lno, e->lno + len), and the second part,
851+
* which is returned, consists of the remainder: [e->lno + len, e->lno +
852+
* e->num_lines). The caller needs to sort out the reference counting for the
853+
* new entry's suspect. */
854+
static struct blame_entry *split_blame_at(struct blame_entry *e, int len,
855+
struct blame_origin *new_suspect)
856+
{
857+
struct blame_entry *n = xcalloc(1, sizeof(struct blame_entry));
858+
859+
n->suspect = new_suspect;
860+
n->ignored = e->ignored;
861+
n->unblamable = e->unblamable;
862+
n->lno = e->lno + len;
863+
n->s_lno = e->s_lno + len;
864+
n->num_lines = e->num_lines - len;
865+
e->num_lines = len;
866+
e->score = 0;
867+
return n;
868+
}
869+
842870
/*
843871
* Process one hunk from the patch between the current suspect for
844872
* blame_entry e and its parent. This first blames any unfinished
@@ -851,10 +879,10 @@ static struct blame_entry *reverse_blame(struct blame_entry *head,
851879
*/
852880
static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
853881
int tlno, int offset, int same,
854-
struct blame_origin *parent)
882+
struct blame_origin *parent, int ignore_diffs, int delta)
855883
{
856884
struct blame_entry *e = **srcq;
857-
struct blame_entry *samep = NULL, *diffp = NULL;
885+
struct blame_entry *samep = NULL, *diffp = NULL, *ignoredp = NULL;
858886

859887
while (e && e->s_lno < tlno) {
860888
struct blame_entry *next = e->next;
@@ -865,14 +893,9 @@ static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
865893
*/
866894
if (e->s_lno + e->num_lines > tlno) {
867895
/* Move second half to a new record */
868-
int len = tlno - e->s_lno;
869-
struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
870-
n->suspect = e->suspect;
871-
n->lno = e->lno + len;
872-
n->s_lno = e->s_lno + len;
873-
n->num_lines = e->num_lines - len;
874-
e->num_lines = len;
875-
e->score = 0;
896+
struct blame_entry *n;
897+
898+
n = split_blame_at(e, tlno - e->s_lno, e->suspect);
876899
/* Push new record to diffp */
877900
n->next = diffp;
878901
diffp = n;
@@ -919,22 +942,45 @@ static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
919942
* Move second half to a new record to be
920943
* processed by later chunks
921944
*/
922-
int len = same - e->s_lno;
923-
struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry));
924-
n->suspect = blame_origin_incref(e->suspect);
925-
n->lno = e->lno + len;
926-
n->s_lno = e->s_lno + len;
927-
n->num_lines = e->num_lines - len;
928-
e->num_lines = len;
929-
e->score = 0;
945+
struct blame_entry *n;
946+
947+
n = split_blame_at(e, same - e->s_lno,
948+
blame_origin_incref(e->suspect));
930949
/* Push new record to samep */
931950
n->next = samep;
932951
samep = n;
933952
}
934-
e->next = diffp;
935-
diffp = e;
953+
if (ignore_diffs) {
954+
/* These go to the parent, like the ones before tlno. */
955+
blame_origin_decref(e->suspect);
956+
e->suspect = blame_origin_incref(parent);
957+
e->s_lno += offset;
958+
e->ignored = 1;
959+
/* The top part of any ignored diff will not exist in
960+
* the parent, and we will never be able to accurately
961+
* blame it. We'll keep it on the blame list for the
962+
* target and mark it during the output. */
963+
if (delta > 0) {
964+
struct blame_entry *n;
965+
966+
n = split_blame_at(e, e->num_lines - delta,
967+
blame_origin_incref(e->suspect));
968+
n->unblamable = 1;
969+
n->next = diffp;
970+
diffp = n;
971+
}
972+
e->next = ignoredp;
973+
ignoredp = e;
974+
} else {
975+
e->next = diffp;
976+
diffp = e;
977+
}
936978
e = next;
937979
}
980+
if (ignoredp) {
981+
**dstq = reverse_blame(ignoredp, **dstq);
982+
*dstq = &ignoredp->next;
983+
}
938984
**srcq = reverse_blame(diffp, reverse_blame(samep, e));
939985
/* Move across elements that are in the unblamable portion */
940986
if (diffp)
@@ -944,6 +990,7 @@ static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq,
944990
struct blame_chunk_cb_data {
945991
struct blame_origin *parent;
946992
long offset;
993+
int ignore_diffs;
947994
struct blame_entry **dstq;
948995
struct blame_entry **srcq;
949996
};
@@ -956,7 +1003,7 @@ static int blame_chunk_cb(long start_a, long count_a,
9561003
if (start_a - start_b != d->offset)
9571004
die("internal error in blame::blame_chunk_cb");
9581005
blame_chunk(&d->dstq, &d->srcq, start_b, start_a - start_b,
959-
start_b + count_b, d->parent);
1006+
start_b + count_b, d->parent, d->ignore_diffs, count_b - count_a);
9601007
d->offset = start_a + count_a - (start_b + count_b);
9611008
return 0;
9621009
}
@@ -968,7 +1015,7 @@ static int blame_chunk_cb(long start_a, long count_a,
9681015
*/
9691016
static void pass_blame_to_parent(struct blame_scoreboard *sb,
9701017
struct blame_origin *target,
971-
struct blame_origin *parent)
1018+
struct blame_origin *parent, int ignore_diffs)
9721019
{
9731020
mmfile_t file_p, file_o;
9741021
struct blame_chunk_cb_data d;
@@ -979,6 +1026,7 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb,
9791026

9801027
d.parent = parent;
9811028
d.offset = 0;
1029+
d.ignore_diffs = ignore_diffs;
9821030
d.dstq = &newdest; d.srcq = &target->suspects;
9831031

9841032
fill_origin_blob(&sb->revs->diffopt, parent, &file_p, &sb->num_read_blob);
@@ -990,7 +1038,7 @@ static void pass_blame_to_parent(struct blame_scoreboard *sb,
9901038
oid_to_hex(&parent->commit->object.oid),
9911039
oid_to_hex(&target->commit->object.oid));
9921040
/* The rest are the same as the parent */
993-
blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
1041+
blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent, 0, 0);
9941042
*d.dstq = NULL;
9951043
queue_blames(sb, parent, newdest);
9961044

@@ -1495,11 +1543,28 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin,
14951543
blame_origin_incref(porigin);
14961544
origin->previous = porigin;
14971545
}
1498-
pass_blame_to_parent(sb, origin, porigin);
1546+
pass_blame_to_parent(sb, origin, porigin, 0);
14991547
if (!origin->suspects)
15001548
goto finish;
15011549
}
15021550

1551+
/*
1552+
* Pass remaining suspects for ignored commits to their parents.
1553+
*/
1554+
if (oidset_contains(&sb->ignore_list, &commit->object.oid)) {
1555+
for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse);
1556+
i < num_sg && sg;
1557+
sg = sg->next, i++) {
1558+
struct blame_origin *porigin = sg_origin[i];
1559+
1560+
if (!porigin)
1561+
continue;
1562+
pass_blame_to_parent(sb, origin, porigin, 1);
1563+
if (!origin->suspects)
1564+
goto finish;
1565+
}
1566+
}
1567+
15031568
/*
15041569
* Optionally find moves in parents' files.
15051570
*/

blame.h

Lines changed: 4 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
/*
@@ -117,6 +119,8 @@ struct blame_scoreboard {
117119
/* linked list of blames */
118120
struct blame_entry *ent;
119121

122+
struct oidset ignore_list;
123+
120124
/* look-up a line in the final buffer */
121125
int num_lines;
122126
int *lineno;

builtin/blame.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ static int no_whole_file_rename;
5252
static int show_progress;
5353
static char repeated_meta_color[COLOR_MAXLEN];
5454
static int coloring_mode;
55+
static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;
56+
static int mark_ignored_lines;
5557

5658
static struct date_mode blame_date_mode = { DATE_ISO8601 };
5759
static size_t blame_date_width;
@@ -346,6 +348,8 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent,
346348
char hex[GIT_MAX_HEXSZ + 1];
347349

348350
oid_to_hex_r(hex, &suspect->commit->object.oid);
351+
if (ent->unblamable)
352+
memset(hex, '0', strlen(hex));
349353
printf("%s %d %d %d\n",
350354
hex,
351355
ent->s_lno + 1,
@@ -479,6 +483,12 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
479483
}
480484
}
481485

486+
if (mark_ignored_lines && ent->ignored) {
487+
length--;
488+
putchar('*');
489+
}
490+
if (ent->unblamable)
491+
memset(hex, '0', length);
482492
printf("%.*s", length, hex);
483493
if (opt & OUTPUT_ANNOTATE_COMPAT) {
484494
const char *name;
@@ -695,6 +705,20 @@ static int git_blame_config(const char *var, const char *value, void *cb)
695705
parse_date_format(value, &blame_date_mode);
696706
return 0;
697707
}
708+
if (!strcmp(var, "blame.ignorerevsfile")) {
709+
const char *str;
710+
int ret;
711+
712+
ret = git_config_pathname(&str, var, value);
713+
if (ret)
714+
return ret;
715+
string_list_insert(&ignore_revs_file_list, str);
716+
return 0;
717+
}
718+
if (!strcmp(var, "blame.markignoredlines")) {
719+
mark_ignored_lines = git_config_bool(var, value);
720+
return 0;
721+
}
698722
if (!strcmp(var, "color.blame.repeatedlines")) {
699723
if (color_parse_mem(value, strlen(value), repeated_meta_color))
700724
warning(_("invalid color '%s' in color.blame.repeatedLines"),
@@ -774,6 +798,27 @@ static int is_a_rev(const char *name)
774798
return OBJ_NONE < oid_object_info(the_repository, &oid, NULL);
775799
}
776800

801+
static void build_ignorelist(struct blame_scoreboard *sb,
802+
struct string_list *ignore_revs_file_list,
803+
struct string_list *ignore_rev_list)
804+
{
805+
struct string_list_item *i;
806+
struct object_id oid;
807+
808+
oidset_init(&sb->ignore_list, 0);
809+
for_each_string_list_item(i, ignore_revs_file_list) {
810+
if (!strcmp(i->string, ""))
811+
oidset_clear(&sb->ignore_list);
812+
else
813+
oidset_parse_file(&sb->ignore_list, i->string);
814+
}
815+
for_each_string_list_item(i, ignore_rev_list) {
816+
if (get_oid_committish(i->string, &oid))
817+
die(_("Cannot find revision %s to ignore"), i->string);
818+
oidset_insert(&sb->ignore_list, &oid);
819+
}
820+
}
821+
777822
int cmd_blame(int argc, const char **argv, const char *prefix)
778823
{
779824
struct rev_info revs;
@@ -785,6 +830,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
785830
struct progress_info pi = { NULL, 0 };
786831

787832
struct string_list range_list = STRING_LIST_INIT_NODUP;
833+
struct string_list ignore_rev_list = STRING_LIST_INIT_NODUP;
788834
int output_option = 0, opt = 0;
789835
int show_stats = 0;
790836
const char *revs_file = NULL;
@@ -806,6 +852,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
806852
OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
807853
OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
808854
OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
855+
OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("Ignore <rev> when blaming")),
856+
OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")),
809857
OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE),
810858
OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR),
811859

@@ -999,6 +1047,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
9991047
sb.contents_from = contents_from;
10001048
sb.reverse = reverse;
10011049
sb.repo = the_repository;
1050+
build_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list);
1051+
string_list_clear(&ignore_revs_file_list, 0);
1052+
string_list_clear(&ignore_rev_list, 0);
10021053
setup_scoreboard(&sb, path, &o);
10031054
lno = sb.num_lines;
10041055

0 commit comments

Comments
 (0)