]> git.ipfire.org Git - thirdparty/git.git/commitdiff
shortlog: match commit trailers with --group
authorJeff King <peff@peff.net>
Sun, 27 Sep 2020 08:40:04 +0000 (04:40 -0400)
committerJunio C Hamano <gitster@pobox.com>
Sun, 27 Sep 2020 19:21:05 +0000 (12:21 -0700)
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>
Documentation/git-shortlog.txt
builtin/shortlog.c
shortlog.h
t/t4201-shortlog.sh

index 6496d313c10d6a05bb698ee9bc0ed4752b2ebee4..edd6cda58a7bec84b457d51433b60d91371b0374 100644 (file)
@@ -53,6 +53,19 @@ OPTIONS
 +
  - `author`, commits are grouped by author
  - `committer`, commits are grouped by committer (the same as `-c`)
+ - `trailer:<field>`, the `<field>` is interpreted as a case-insensitive
+   commit message trailer (see linkgit:git-interpret-trailers[1]). For
+   example, if your project uses `Reviewed-by` trailers, you might want
+   to see who has been reviewing with
+   `git shortlog -ns --group=trailer:reviewed-by`.
++
+Note that commits that do not include the trailer will not be counted.
+Likewise, commits with multiple trailers (e.g., multiple signoffs) may
+be counted more than once.
++
+The contents of each trailer value are taken literally and completely.
+No mailmap is applied, and the `-e` option has no effect (if the trailer
+contains a username and email, they are both always shown).
 
 -c::
 --committer::
index 880ce19304fd653c7b945d41d4ac43af0b39c499..e1d9ee909f127dbad27318e333ceb40794d6ed5d 100644 (file)
@@ -9,6 +9,7 @@
 #include "mailmap.h"
 #include "shortlog.h"
 #include "parse-options.h"
+#include "trailer.h"
 
 static char const * const shortlog_usage[] = {
        N_("git shortlog [<options>] [<revision-range>] [[--] <path>...]"),
@@ -136,6 +137,8 @@ static void read_from_stdin(struct shortlog *log)
        case SHORTLOG_GROUP_COMMITTER:
                match = committer_match;
                break;
+       case SHORTLOG_GROUP_TRAILER:
+               die(_("using --group=trailer with stdin is not supported"));
        default:
                BUG("unhandled shortlog group");
        }
@@ -163,6 +166,37 @@ static void read_from_stdin(struct shortlog *log)
        strbuf_release(&oneline);
 }
 
+static void insert_records_from_trailers(struct shortlog *log,
+                                        struct commit *commit,
+                                        struct pretty_print_context *ctx,
+                                        const char *oneline)
+{
+       struct trailer_iterator iter;
+       const char *commit_buffer, *body;
+
+       /*
+        * Using format_commit_message("%B") would be simpler here, but
+        * this saves us copying the message.
+        */
+       commit_buffer = logmsg_reencode(commit, NULL, ctx->output_encoding);
+       body = strstr(commit_buffer, "\n\n");
+       if (!body)
+               return;
+
+       trailer_iterator_init(&iter, body);
+       while (trailer_iterator_advance(&iter)) {
+               const char *value = iter.val.buf;
+
+               if (strcasecmp(iter.key.buf, log->trailer))
+                       continue;
+
+               insert_one_record(log, value, oneline);
+       }
+       trailer_iterator_release(&iter);
+
+       unuse_commit_buffer(commit, commit_buffer);
+}
+
 void shortlog_add_commit(struct shortlog *log, struct commit *commit)
 {
        struct strbuf ident = STRBUF_INIT;
@@ -197,6 +231,9 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
                                      &ident, &ctx);
                insert_one_record(log, ident.buf, oneline_str);
                break;
+       case SHORTLOG_GROUP_TRAILER:
+               insert_records_from_trailers(log, commit, &ctx, oneline_str);
+               break;
        }
 
        strbuf_release(&ident);
@@ -263,12 +300,17 @@ static int parse_wrap_args(const struct option *opt, const char *arg, int unset)
 static int parse_group_option(const struct option *opt, const char *arg, int unset)
 {
        struct shortlog *log = opt->value;
+       const char *field;
 
        if (unset || !strcasecmp(arg, "author"))
                log->group = SHORTLOG_GROUP_AUTHOR;
        else if (!strcasecmp(arg, "committer"))
                log->group = SHORTLOG_GROUP_COMMITTER;
-       else
+       else if (skip_prefix(arg, "trailer:", &field)) {
+               log->group = SHORTLOG_GROUP_TRAILER;
+               free(log->trailer);
+               log->trailer = xstrdup(field);
+       } else
                return error(_("unknown group type: %s"), arg);
 
        return 0;
index 876a52158dabbf814652c7809807b2fbceea0f62..89c2dbc5e66b4a5bb56a6825cfe06bf1d856ab77 100644 (file)
@@ -19,7 +19,9 @@ struct shortlog {
        enum {
                SHORTLOG_GROUP_AUTHOR = 0,
                SHORTLOG_GROUP_COMMITTER,
+               SHORTLOG_GROUP_TRAILER,
        } group;
+       char *trailer;
 
        char *common_repo_prefix;
        int email;
index 65e4468746526d7382722139bda9c8b79a761713..e97d891a7188c376daea6e8a390d5549f2525158 100755 (executable)
@@ -220,4 +220,18 @@ test_expect_success '--group=committer is the same as --committer' '
        test_cmp expect actual
 '
 
+test_expect_success 'shortlog --group=trailer:signed-off-by' '
+       git commit --allow-empty -m foo -s &&
+       GIT_COMMITTER_NAME="SOB One" \
+       GIT_COMMITTER_EMAIL=sob@example.com \
+               git commit --allow-empty -m foo -s &&
+       git commit --allow-empty --amend --no-edit -s &&
+       cat >expect <<-\EOF &&
+            2  C O Mitter <committer@example.com>
+            1  SOB One <sob@example.com>
+       EOF
+       git shortlog -ns --group=trailer:signed-off-by HEAD >actual &&
+       test_cmp expect actual
+'
+
 test_done