summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
authorLibravatar Jeff King <peff@peff.net>2020-09-27 04:40:15 -0400
committerLibravatar Junio C Hamano <gitster@pobox.com>2020-09-27 12:21:05 -0700
commit63d24fa0b055d3f0386ee3686c07428450add708 (patch)
treeff5aae46b1399633020c6f584bdee8de5a6dc371 /builtin
parentshortlog: parse trailer idents (diff)
downloadtgif-63d24fa0b055d3f0386ee3686c07428450add708.tar.xz
shortlog: allow multiple groups to be specified
Now that shortlog supports reading from trailers, it can be useful to combine counts from multiple trailers, or between trailers and authors. This can be done manually by post-processing the output from multiple runs, but it's non-trivial to make sure that each name/commit pair is counted only once. This patch teaches shortlog to accept multiple --group options on the command line, and pull data from all of them. That makes it possible to run: git shortlog -ns --group=author --group=trailer:co-authored-by to get a shortlog that counts authors and co-authors equally. The implementation is mostly straightforward. The "group" enum becomes a bitfield, and the trailer key becomes a list. I didn't bother implementing the multi-group semantics for reading from stdin. It would be possible to do, but the existing matching code makes it awkward, and I doubt anybody cares. The duplicate suppression we used for trailers now covers authors and committers as well (though in non-trailer single-group mode we can skip the hash insertion and lookup, since we only see one value per commit). There is one subtlety: we now care about the case when no group bit is set (in which case we default to showing the author). The caller in builtin/log.c needs to be adapted to ask explicitly for authors, rather than relying on shortlog_init(). It would be possible with some gymnastics to make this keep working as-is, but it's not worth it for a single caller. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'builtin')
-rw-r--r--builtin/log.c1
-rw-r--r--builtin/shortlog.c64
2 files changed, 41 insertions, 24 deletions
diff --git a/builtin/log.c b/builtin/log.c
index d104d5c688..ad0c5230cf 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1197,6 +1197,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
log.in1 = 2;
log.in2 = 4;
log.file = rev->diffopt.file;
+ log.groups = SHORTLOG_GROUP_AUTHOR;
for (i = 0; i < nr; i++)
shortlog_add_commit(&log, list[i]);
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 28133aec68..0a5c4968f6 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -130,7 +130,10 @@ static void read_from_stdin(struct shortlog *log)
static const char *committer_match[2] = { "Commit: ", "committer " };
const char **match;
- switch (log->group) {
+ if (HAS_MULTI_BITS(log->groups))
+ die(_("using multiple --group options with stdin is not supported"));
+
+ switch (log->groups) {
case SHORTLOG_GROUP_AUTHOR:
match = author_match;
break;
@@ -221,13 +224,13 @@ static void strset_clear(struct strset *ss)
}
static void insert_records_from_trailers(struct shortlog *log,
+ struct strset *dups,
struct commit *commit,
struct pretty_print_context *ctx,
const char *oneline)
{
struct trailer_iterator iter;
const char *commit_buffer, *body;
- struct strset dups = STRSET_INIT;
struct strbuf ident = STRBUF_INIT;
/*
@@ -243,21 +246,20 @@ static void insert_records_from_trailers(struct shortlog *log,
while (trailer_iterator_advance(&iter)) {
const char *value = iter.val.buf;
- if (strcasecmp(iter.key.buf, log->trailer))
+ if (!string_list_has_string(&log->trailers, iter.key.buf))
continue;
strbuf_reset(&ident);
if (!parse_ident(log, &ident, value))
value = ident.buf;
- if (strset_check_and_add(&dups, value))
+ if (strset_check_and_add(dups, value))
continue;
insert_one_record(log, value, oneline);
}
trailer_iterator_release(&iter);
strbuf_release(&ident);
- strset_clear(&dups);
unuse_commit_buffer(commit, commit_buffer);
}
@@ -265,6 +267,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
{
struct strbuf ident = STRBUF_INIT;
struct strbuf oneline = STRBUF_INIT;
+ struct strset dups = STRSET_INIT;
struct pretty_print_context ctx = {0};
const char *oneline_str;
@@ -282,24 +285,29 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
}
oneline_str = oneline.len ? oneline.buf : "<none>";
- switch (log->group) {
- case SHORTLOG_GROUP_AUTHOR:
+ if (log->groups & SHORTLOG_GROUP_AUTHOR) {
+ strbuf_reset(&ident);
format_commit_message(commit,
log->email ? "%aN <%aE>" : "%aN",
&ident, &ctx);
- insert_one_record(log, ident.buf, oneline_str);
- break;
- case SHORTLOG_GROUP_COMMITTER:
+ if (!HAS_MULTI_BITS(log->groups) ||
+ !strset_check_and_add(&dups, ident.buf))
+ insert_one_record(log, ident.buf, oneline_str);
+ }
+ if (log->groups & SHORTLOG_GROUP_COMMITTER) {
+ strbuf_reset(&ident);
format_commit_message(commit,
log->email ? "%cN <%cE>" : "%cN",
&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;
+ if (!HAS_MULTI_BITS(log->groups) ||
+ !strset_check_and_add(&dups, ident.buf))
+ insert_one_record(log, ident.buf, oneline_str);
+ }
+ if (log->groups & SHORTLOG_GROUP_TRAILER) {
+ insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str);
}
+ strset_clear(&dups);
strbuf_release(&ident);
strbuf_release(&oneline);
}
@@ -366,14 +374,16 @@ static int parse_group_option(const struct option *opt, const char *arg, int uns
struct shortlog *log = opt->value;
const char *field;
- if (unset || !strcasecmp(arg, "author"))
- log->group = SHORTLOG_GROUP_AUTHOR;
+ if (unset) {
+ log->groups = 0;
+ string_list_clear(&log->trailers, 0);
+ } else if (!strcasecmp(arg, "author"))
+ log->groups |= SHORTLOG_GROUP_AUTHOR;
else if (!strcasecmp(arg, "committer"))
- log->group = SHORTLOG_GROUP_COMMITTER;
+ log->groups |= SHORTLOG_GROUP_COMMITTER;
else if (skip_prefix(arg, "trailer:", &field)) {
- log->group = SHORTLOG_GROUP_TRAILER;
- free(log->trailer);
- log->trailer = xstrdup(field);
+ log->groups |= SHORTLOG_GROUP_TRAILER;
+ string_list_append(&log->trailers, field);
} else
return error(_("unknown group type: %s"), arg);
@@ -391,6 +401,8 @@ void shortlog_init(struct shortlog *log)
log->wrap = DEFAULT_WRAPLEN;
log->in1 = DEFAULT_INDENT1;
log->in2 = DEFAULT_INDENT2;
+ log->trailers.strdup_strings = 1;
+ log->trailers.cmp = strcasecmp;
}
int cmd_shortlog(int argc, const char **argv, const char *prefix)
@@ -400,9 +412,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
int nongit = !startup_info->have_repository;
const struct option options[] = {
- OPT_SET_INT('c', "committer", &log.group,
- N_("Group by committer rather than author"),
- SHORTLOG_GROUP_COMMITTER),
+ OPT_BIT('c', "committer", &log.groups,
+ N_("Group by committer rather than author"),
+ SHORTLOG_GROUP_COMMITTER),
OPT_BOOL('n', "numbered", &log.sort_by_number,
N_("sort output according to the number of commits per author")),
OPT_BOOL('s', "summary", &log.summary,
@@ -454,6 +466,10 @@ parse_done:
log.abbrev = rev.abbrev;
log.file = rev.diffopt.file;
+ if (!log.groups)
+ log.groups = SHORTLOG_GROUP_AUTHOR;
+ string_list_sort(&log.trailers);
+
/* assume HEAD if from a tty */
if (!nongit && !rev.pending.nr && isatty(0))
add_head_to_pending(&rev);