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>
This commit is contained in:

committed by
Junio C Hamano

parent
f0939a0eb1
commit
47beb37bc6
@ -53,6 +53,19 @@ OPTIONS
|
|||||||
+
|
+
|
||||||
- `author`, commits are grouped by author
|
- `author`, commits are grouped by author
|
||||||
- `committer`, commits are grouped by committer (the same as `-c`)
|
- `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::
|
-c::
|
||||||
--committer::
|
--committer::
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "mailmap.h"
|
#include "mailmap.h"
|
||||||
#include "shortlog.h"
|
#include "shortlog.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
|
#include "trailer.h"
|
||||||
|
|
||||||
static char const * const shortlog_usage[] = {
|
static char const * const shortlog_usage[] = {
|
||||||
N_("git shortlog [<options>] [<revision-range>] [[--] <path>...]"),
|
N_("git shortlog [<options>] [<revision-range>] [[--] <path>...]"),
|
||||||
@ -136,6 +137,8 @@ static void read_from_stdin(struct shortlog *log)
|
|||||||
case SHORTLOG_GROUP_COMMITTER:
|
case SHORTLOG_GROUP_COMMITTER:
|
||||||
match = committer_match;
|
match = committer_match;
|
||||||
break;
|
break;
|
||||||
|
case SHORTLOG_GROUP_TRAILER:
|
||||||
|
die(_("using --group=trailer with stdin is not supported"));
|
||||||
default:
|
default:
|
||||||
BUG("unhandled shortlog group");
|
BUG("unhandled shortlog group");
|
||||||
}
|
}
|
||||||
@ -163,6 +166,37 @@ static void read_from_stdin(struct shortlog *log)
|
|||||||
strbuf_release(&oneline);
|
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)
|
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
|
||||||
{
|
{
|
||||||
struct strbuf ident = STRBUF_INIT;
|
struct strbuf ident = STRBUF_INIT;
|
||||||
@ -197,6 +231,9 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
|
|||||||
&ident, &ctx);
|
&ident, &ctx);
|
||||||
insert_one_record(log, ident.buf, oneline_str);
|
insert_one_record(log, ident.buf, oneline_str);
|
||||||
break;
|
break;
|
||||||
|
case SHORTLOG_GROUP_TRAILER:
|
||||||
|
insert_records_from_trailers(log, commit, &ctx, oneline_str);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
strbuf_release(&ident);
|
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)
|
static int parse_group_option(const struct option *opt, const char *arg, int unset)
|
||||||
{
|
{
|
||||||
struct shortlog *log = opt->value;
|
struct shortlog *log = opt->value;
|
||||||
|
const char *field;
|
||||||
|
|
||||||
if (unset || !strcasecmp(arg, "author"))
|
if (unset || !strcasecmp(arg, "author"))
|
||||||
log->group = SHORTLOG_GROUP_AUTHOR;
|
log->group = SHORTLOG_GROUP_AUTHOR;
|
||||||
else if (!strcasecmp(arg, "committer"))
|
else if (!strcasecmp(arg, "committer"))
|
||||||
log->group = SHORTLOG_GROUP_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 error(_("unknown group type: %s"), arg);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -19,7 +19,9 @@ struct shortlog {
|
|||||||
enum {
|
enum {
|
||||||
SHORTLOG_GROUP_AUTHOR = 0,
|
SHORTLOG_GROUP_AUTHOR = 0,
|
||||||
SHORTLOG_GROUP_COMMITTER,
|
SHORTLOG_GROUP_COMMITTER,
|
||||||
|
SHORTLOG_GROUP_TRAILER,
|
||||||
} group;
|
} group;
|
||||||
|
char *trailer;
|
||||||
|
|
||||||
char *common_repo_prefix;
|
char *common_repo_prefix;
|
||||||
int email;
|
int email;
|
||||||
|
@ -220,4 +220,18 @@ test_expect_success '--group=committer is the same as --committer' '
|
|||||||
test_cmp expect actual
|
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
|
test_done
|
||||||
|
Reference in New Issue
Block a user