shortlog: match commit trailers with --group
If a project uses commit trailers, this patch lets you use shortlog to see who is performing each action. For example, running: git shortlog -ns --group=trailer:reviewed-by in git.git shows who has reviewed. You can even use a custom format to see things like who has helped whom: git shortlog --format="...helped %an (%ad)" \ --group=trailer:helped-by Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jeff King committed
Sep 27, 2020 at 04:40 UTC
47beb37bc60b690e83b19aa7ff646255fb643ad9
4 files changed
+72
-1
Documentation/git-shortlog.txt
+13
@@ -53,6 +53,19 @@ OPTIONS
53
+
54
- `author`, commits are grouped by author
55
- `committer`, commits are grouped by committer (the same as `-c`)
56
+ - `trailer:<field>`, the `<field>` is interpreted as a case-insensitive
57
+ commit message trailer (see linkgit:git-interpret-trailers[1]). For
58
+ example, if your project uses `Reviewed-by` trailers, you might want
59
+ to see who has been reviewing with
60
+ `git shortlog -ns --group=trailer:reviewed-by`.
61
++
62
+Note that commits that do not include the trailer will not be counted.
63
+Likewise, commits with multiple trailers (e.g., multiple signoffs) may
64
+be counted more than once.
65
++
66
+The contents of each trailer value are taken literally and completely.
67
+No mailmap is applied, and the `-e` option has no effect (if the trailer
68
+contains a username and email, they are both always shown).
69
70
-c::
71
--committer::
builtin/shortlog.c
+43
-1
@@ -9,6 +9,7 @@
9
#include "mailmap.h"
10
#include "shortlog.h"
11
#include "parse-options.h"
12
+#include "trailer.h"
13
14
static char const * const shortlog_usage[] = {
15
N_("git shortlog [<options>] [<revision-range>] [[--] <path>...]"),
@@ -136,6 +137,8 @@ static void read_from_stdin(struct shortlog *log)
137
case SHORTLOG_GROUP_COMMITTER:
138
match = committer_match;
139
break;
140
+ case SHORTLOG_GROUP_TRAILER:
141
+ die(_("using --group=trailer with stdin is not supported"));
142
default:
143
BUG("unhandled shortlog group");
144
}
@@ -163,6 +166,37 @@ static void read_from_stdin(struct shortlog *log)
166
strbuf_release(&oneline);
167
}
168
169
+static void insert_records_from_trailers(struct shortlog *log,
170
+ struct commit *commit,
171
+ struct pretty_print_context *ctx,
172
+ const char *oneline)
173
+{
174
+ struct trailer_iterator iter;
175
+ const char *commit_buffer, *body;
176
+
177
+ /*
178
+ * Using format_commit_message("%B") would be simpler here, but
179
+ * this saves us copying the message.
180
+ */
181
+ commit_buffer = logmsg_reencode(commit, NULL, ctx->output_encoding);
182
+ body = strstr(commit_buffer, "\n\n");
183
+ if (!body)
184
+ return;
185
+
186
+ trailer_iterator_init(&iter, body);
187
+ while (trailer_iterator_advance(&iter)) {
188
+ const char *value = iter.val.buf;
189
+
190
+ if (strcasecmp(iter.key.buf, log->trailer))
191
+ continue;
192
+
193
+ insert_one_record(log, value, oneline);
194
+ }
195
+ trailer_iterator_release(&iter);
196
+
197
+ unuse_commit_buffer(commit, commit_buffer);
198
+}
199
+
200
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
201
{
202
struct strbuf ident = STRBUF_INIT;
@@ -197,6 +231,9 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
231
&ident, &ctx);
232
insert_one_record(log, ident.buf, oneline_str);
233
break;
234
+ case SHORTLOG_GROUP_TRAILER:
235
+ insert_records_from_trailers(log, commit, &ctx, oneline_str);
236
+ break;
237
}
238
239
strbuf_release(&ident);
@@ -263,12 +300,17 @@ static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
300
static int parse_group_option(const struct option *opt, const char *arg, int unset)
301
{
302
struct shortlog *log = opt->value;
303
+ const char *field;
304
305
if (unset || !strcasecmp(arg, "author"))
306
log->group = SHORTLOG_GROUP_AUTHOR;
307
else if (!strcasecmp(arg, "committer"))
308
log->group = SHORTLOG_GROUP_COMMITTER;
271
- else
309
+ else if (skip_prefix(arg, "trailer:", &field)) {
310
+ log->group = SHORTLOG_GROUP_TRAILER;
311
+ free(log->trailer);
312
+ log->trailer = xstrdup(field);
313
+ } else
314
return error(_("unknown group type: %s"), arg);
315
316
return 0;
shortlog.h
+2
@@ -19,7 +19,9 @@ struct shortlog {
19
enum {
20
SHORTLOG_GROUP_AUTHOR = 0,
21
SHORTLOG_GROUP_COMMITTER,
22
+ SHORTLOG_GROUP_TRAILER,
23
} group;
24
+ char *trailer;
25
26
char *common_repo_prefix;
27
int email;
t/t4201-shortlog.sh
+14
@@ -220,4 +220,18 @@ test_expect_success '--group=committer is the same as --committer' '
220
test_cmp expect actual
221
'
222
223
+test_expect_success 'shortlog --group=trailer:signed-off-by' '
224
+ git commit --allow-empty -m foo -s &&
225
+ GIT_COMMITTER_NAME="SOB One" \
226
+ GIT_COMMITTER_EMAIL=sob@example.com \
227
+ git commit --allow-empty -m foo -s &&
228
+ git commit --allow-empty --amend --no-edit -s &&
229
+ cat >expect <<-\EOF &&
230
+ 2 C O Mitter <committer@example.com>
231
+ 1 SOB One <sob@example.com>
232
+ EOF
233
+ git shortlog -ns --group=trailer:signed-off-by HEAD >actual &&
234
+ test_cmp expect actual
235
+'
236
+
237
test_done