Skip to content

Commit b7aba2e

Browse files
committed
Merge branch 'jk/blame-line-porcelain'
* jk/blame-line-porcelain: blame: add --line-porcelain output format blame: refactor porcelain output add tests for various blame formats
2 parents 391b142 + ed747dd commit b7aba2e

4 files changed

Lines changed: 131 additions & 10 deletions

File tree

Documentation/blame-options.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ of lines before or after the line given by <start>.
5252
--porcelain::
5353
Show in a format designed for machine consumption.
5454

55+
--line-porcelain::
56+
Show the porcelain format, but output commit information for
57+
each line, not just the first time a commit is referenced.
58+
Implies --porcelain.
59+
5560
--incremental::
5661
Show the result incrementally in a format designed for
5762
machine consumption.

Documentation/git-blame.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ The contents of the actual line is output after the above
105105
header, prefixed by a TAB. This is to allow adding more
106106
header elements later.
107107

108+
The porcelain format generally suppresses commit information that has
109+
already been seen. For example, two lines that are blamed to the same
110+
commit will both be shown, but the details for that commit will be shown
111+
only once. This is more efficient, but may require more state be kept by
112+
the reader. The `--line-porcelain` option can be used to output full
113+
commit information for each line, allowing simpler (but less efficient)
114+
usage like:
115+
116+
# count the number of lines attributed to each author
117+
git blame --line-porcelain file |
118+
sed -n 's/^author //p' |
119+
sort | uniq -c | sort -rn
120+
108121

109122
SPECIFYING RANGES
110123
-----------------

builtin/blame.c

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,13 +1484,14 @@ static void write_filename_info(const char *path)
14841484
/*
14851485
* Porcelain/Incremental format wants to show a lot of details per
14861486
* commit. Instead of repeating this every line, emit it only once,
1487-
* the first time each commit appears in the output.
1487+
* the first time each commit appears in the output (unless the
1488+
* user has specifically asked for us to repeat).
14881489
*/
1489-
static int emit_one_suspect_detail(struct origin *suspect)
1490+
static int emit_one_suspect_detail(struct origin *suspect, int repeat)
14901491
{
14911492
struct commit_info ci;
14921493

1493-
if (suspect->commit->object.flags & METAINFO_SHOWN)
1494+
if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN))
14941495
return 0;
14951496

14961497
suspect->commit->object.flags |= METAINFO_SHOWN;
@@ -1529,7 +1530,7 @@ static void found_guilty_entry(struct blame_entry *ent)
15291530
printf("%s %d %d %d\n",
15301531
sha1_to_hex(suspect->commit->object.sha1),
15311532
ent->s_lno + 1, ent->lno + 1, ent->num_lines);
1532-
emit_one_suspect_detail(suspect);
1533+
emit_one_suspect_detail(suspect, 0);
15331534
write_filename_info(suspect->path);
15341535
maybe_flush_or_die(stdout, "stdout");
15351536
}
@@ -1618,9 +1619,19 @@ static const char *format_time(unsigned long time, const char *tz_str,
16181619
#define OUTPUT_SHOW_SCORE 0100
16191620
#define OUTPUT_NO_AUTHOR 0200
16201621
#define OUTPUT_SHOW_EMAIL 0400
1622+
#define OUTPUT_LINE_PORCELAIN 01000
16211623

1622-
static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
1624+
static void emit_porcelain_details(struct origin *suspect, int repeat)
16231625
{
1626+
if (emit_one_suspect_detail(suspect, repeat) ||
1627+
(suspect->commit->object.flags & MORE_THAN_ONE_PATH))
1628+
write_filename_info(suspect->path);
1629+
}
1630+
1631+
static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
1632+
int opt)
1633+
{
1634+
int repeat = opt & OUTPUT_LINE_PORCELAIN;
16241635
int cnt;
16251636
const char *cp;
16261637
struct origin *suspect = ent->suspect;
@@ -1633,17 +1644,18 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
16331644
ent->s_lno + 1,
16341645
ent->lno + 1,
16351646
ent->num_lines);
1636-
if (emit_one_suspect_detail(suspect) ||
1637-
(suspect->commit->object.flags & MORE_THAN_ONE_PATH))
1638-
write_filename_info(suspect->path);
1647+
emit_porcelain_details(suspect, repeat);
16391648

16401649
cp = nth_line(sb, ent->lno);
16411650
for (cnt = 0; cnt < ent->num_lines; cnt++) {
16421651
char ch;
1643-
if (cnt)
1652+
if (cnt) {
16441653
printf("%s %d %d\n", hex,
16451654
ent->s_lno + 1 + cnt,
16461655
ent->lno + 1 + cnt);
1656+
if (repeat)
1657+
emit_porcelain_details(suspect, 1);
1658+
}
16471659
putchar('\t');
16481660
do {
16491661
ch = *cp++;
@@ -1756,7 +1768,7 @@ static void output(struct scoreboard *sb, int option)
17561768

17571769
for (ent = sb->ent; ent; ent = ent->next) {
17581770
if (option & OUTPUT_PORCELAIN)
1759-
emit_porcelain(sb, ent);
1771+
emit_porcelain(sb, ent, option);
17601772
else {
17611773
emit_other(sb, ent, option);
17621774
}
@@ -2300,6 +2312,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
23002312
OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
23012313
OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
23022314
OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
2315+
OPT_BIT(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
23032316
OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
23042317
OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
23052318
OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),

t/t8008-blame-formats.sh

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/bin/sh
2+
3+
test_description='blame output in various formats on a simple case'
4+
. ./test-lib.sh
5+
6+
test_expect_success 'setup' '
7+
echo a >file &&
8+
git add file
9+
test_tick &&
10+
git commit -m one &&
11+
echo b >>file &&
12+
echo c >>file &&
13+
echo d >>file &&
14+
test_tick &&
15+
git commit -a -m two
16+
'
17+
18+
cat >expect <<'EOF'
19+
^baf5e0b (A U Thor 2005-04-07 15:13:13 -0700 1) a
20+
8825379d (A U Thor 2005-04-07 15:14:13 -0700 2) b
21+
8825379d (A U Thor 2005-04-07 15:14:13 -0700 3) c
22+
8825379d (A U Thor 2005-04-07 15:14:13 -0700 4) d
23+
EOF
24+
test_expect_success 'normal blame output' '
25+
git blame file >actual &&
26+
test_cmp expect actual
27+
'
28+
29+
ID1=baf5e0b3869e0b2b2beb395a3720c7b51eac94fc
30+
COMMIT1='author A U Thor
31+
author-mail <author@example.com>
32+
author-time 1112911993
33+
author-tz -0700
34+
committer C O Mitter
35+
committer-mail <committer@example.com>
36+
committer-time 1112911993
37+
committer-tz -0700
38+
summary one
39+
boundary
40+
filename file'
41+
ID2=8825379dfb8a1267b58e8e5bcf69eec838f685ec
42+
COMMIT2='author A U Thor
43+
author-mail <author@example.com>
44+
author-time 1112912053
45+
author-tz -0700
46+
committer C O Mitter
47+
committer-mail <committer@example.com>
48+
committer-time 1112912053
49+
committer-tz -0700
50+
summary two
51+
previous baf5e0b3869e0b2b2beb395a3720c7b51eac94fc file
52+
filename file'
53+
54+
cat >expect <<EOF
55+
$ID1 1 1 1
56+
$COMMIT1
57+
a
58+
$ID2 2 2 3
59+
$COMMIT2
60+
b
61+
$ID2 3 3
62+
c
63+
$ID2 4 4
64+
d
65+
EOF
66+
test_expect_success 'blame --porcelain output' '
67+
git blame --porcelain file >actual &&
68+
test_cmp expect actual
69+
'
70+
71+
cat >expect <<EOF
72+
$ID1 1 1 1
73+
$COMMIT1
74+
a
75+
$ID2 2 2 3
76+
$COMMIT2
77+
b
78+
$ID2 3 3
79+
$COMMIT2
80+
c
81+
$ID2 4 4
82+
$COMMIT2
83+
d
84+
EOF
85+
test_expect_success 'blame --line-porcelain output' '
86+
git blame --line-porcelain file >actual &&
87+
test_cmp expect actual
88+
'
89+
90+
test_done

0 commit comments

Comments
 (0)