@matheusbsbs / matheus-bagliano / commits / 47beb37bc6
Fork 0

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