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;
}
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;
/*
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);
}
{
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;
}
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);
}
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);
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)
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,
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);
test_cmp expect actual
'
+test_expect_success 'shortlog can match multiple groups' '
+ git commit --allow-empty -F - <<-\EOF &&
+ subject one
+
+ this has two trailers that are distinct from the author; it will count
+ 3 times in the output
+
+ Some-trailer: User A <a@example.com>
+ Another-trailer: User B <b@example.com>
+ EOF
+
+ git commit --allow-empty -F - <<-\EOF &&
+ subject two
+
+ this one has two trailers, one of which is a duplicate with the author;
+ it will only be counted once for them
+
+ Another-trailer: A U Thor <author@example.com>
+ Some-trailer: User B <b@example.com>
+ EOF
+
+ cat >expect <<-\EOF &&
+ 2 A U Thor
+ 2 User B
+ 1 User A
+ EOF
+ git shortlog -ns \
+ --group=author \
+ --group=trailer:some-trailer \
+ --group=trailer:another-trailer \
+ -2 HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'set up option selection tests' '
+ git commit --allow-empty -F - <<-\EOF
+ subject
+
+ body
+
+ Trailer-one: value-one
+ Trailer-two: value-two
+ EOF
+'
+
+test_expect_success '--no-group resets group list to author' '
+ cat >expect <<-\EOF &&
+ 1 A U Thor
+ EOF
+ git shortlog -ns \
+ --group=committer \
+ --group=trailer:trailer-one \
+ --no-group \
+ -1 HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--no-group resets trailer list' '
+ cat >expect <<-\EOF &&
+ 1 value-two
+ EOF
+ git shortlog -ns \
+ --group=trailer:trailer-one \
+ --no-group \
+ --group=trailer:trailer-two \
+ -1 HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'stdin with multiple groups reports error' '
+ git log >log &&
+ test_must_fail git shortlog --group=author --group=committer <log
+'
+
test_done