From 45d93eb82403a35d248d17dd0ea7dc3d690b1323 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 25 Sep 2020 03:01:50 -0400 Subject: shortlog: change "author" variables to "ident" We already match "committer", and we're about to start matching more things. Let's use a more neutral variable to avoid confusion. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/shortlog.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'builtin') diff --git a/builtin/shortlog.c b/builtin/shortlog.c index c856c58bb5..edcf2e0d54 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -49,12 +49,12 @@ static int compare_by_list(const void *a1, const void *a2) } static void insert_one_record(struct shortlog *log, - const char *author, + const char *ident, const char *oneline) { struct string_list_item *item; - item = string_list_insert(&log->list, author); + item = string_list_insert(&log->list, ident); if (log->summary) item->util = (void *)(UTIL_TO_INT(item) + 1); @@ -97,8 +97,8 @@ static void insert_one_record(struct shortlog *log, } } -static int parse_stdin_author(struct shortlog *log, - struct strbuf *out, const char *in) +static int parse_stdin_ident(struct shortlog *log, + struct strbuf *out, const char *in) { const char *mailbuf, *namebuf; size_t namelen, maillen; @@ -122,18 +122,18 @@ static int parse_stdin_author(struct shortlog *log, static void read_from_stdin(struct shortlog *log) { - struct strbuf author = STRBUF_INIT; - struct strbuf mapped_author = STRBUF_INIT; + struct strbuf ident = STRBUF_INIT; + struct strbuf mapped_ident = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; static const char *author_match[2] = { "Author: ", "author " }; static const char *committer_match[2] = { "Commit: ", "committer " }; const char **match; match = log->committer ? committer_match : author_match; - while (strbuf_getline_lf(&author, stdin) != EOF) { + while (strbuf_getline_lf(&ident, stdin) != EOF) { const char *v; - if (!skip_prefix(author.buf, match[0], &v) && - !skip_prefix(author.buf, match[1], &v)) + if (!skip_prefix(ident.buf, match[0], &v) && + !skip_prefix(ident.buf, match[1], &v)) continue; while (strbuf_getline_lf(&oneline, stdin) != EOF && oneline.len) @@ -142,20 +142,20 @@ static void read_from_stdin(struct shortlog *log) !oneline.len) ; /* discard blanks */ - strbuf_reset(&mapped_author); - if (parse_stdin_author(log, &mapped_author, v) < 0) + strbuf_reset(&mapped_ident); + if (parse_stdin_ident(log, &mapped_ident, v) < 0) continue; - insert_one_record(log, mapped_author.buf, oneline.buf); + insert_one_record(log, mapped_ident.buf, oneline.buf); } - strbuf_release(&author); - strbuf_release(&mapped_author); + strbuf_release(&ident); + strbuf_release(&mapped_ident); strbuf_release(&oneline); } void shortlog_add_commit(struct shortlog *log, struct commit *commit) { - struct strbuf author = STRBUF_INIT; + struct strbuf ident = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; struct pretty_print_context ctx = {0}; const char *fmt; @@ -170,7 +170,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) (log->email ? "%cN <%cE>" : "%cN") : (log->email ? "%aN <%aE>" : "%aN"); - format_commit_message(commit, fmt, &author, &ctx); + format_commit_message(commit, fmt, &ident, &ctx); if (!log->summary) { if (log->user_format) pretty_print_commit(&ctx, commit, &oneline); @@ -178,9 +178,9 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) format_commit_message(commit, "%s", &oneline, &ctx); } - insert_one_record(log, author.buf, oneline.len ? oneline.buf : ""); + insert_one_record(log, ident.buf, oneline.len ? oneline.buf : ""); - strbuf_release(&author); + strbuf_release(&ident); strbuf_release(&oneline); } -- cgit v1.2.3 From 92338c450bc41e8e3da31a10d2ab73844e8b0634 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 27 Sep 2020 04:39:59 -0400 Subject: shortlog: add grouping option In preparation for adding more grouping types, let's refactor the committer/author grouping code and add a user-facing option that binds them together. In particular: - the main option is now "--group", to make it clear that the various group types are mutually exclusive. The "--committer" option is an alias for "--group=committer". - we keep an enum rather than a binary flag, to prepare for more values - we prefer switch statements to ternary assignment, since other group types will need more custom code Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/shortlog.c | 59 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 11 deletions(-) (limited to 'builtin') diff --git a/builtin/shortlog.c b/builtin/shortlog.c index edcf2e0d54..880ce19304 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -129,7 +129,17 @@ static void read_from_stdin(struct shortlog *log) static const char *committer_match[2] = { "Commit: ", "committer " }; const char **match; - match = log->committer ? committer_match : author_match; + switch (log->group) { + case SHORTLOG_GROUP_AUTHOR: + match = author_match; + break; + case SHORTLOG_GROUP_COMMITTER: + match = committer_match; + break; + default: + BUG("unhandled shortlog group"); + } + while (strbuf_getline_lf(&ident, stdin) != EOF) { const char *v; if (!skip_prefix(ident.buf, match[0], &v) && @@ -158,7 +168,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) struct strbuf ident = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; struct pretty_print_context ctx = {0}; - const char *fmt; + const char *oneline_str; ctx.fmt = CMIT_FMT_USERFORMAT; ctx.abbrev = log->abbrev; @@ -166,19 +176,28 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); - fmt = log->committer ? - (log->email ? "%cN <%cE>" : "%cN") : - (log->email ? "%aN <%aE>" : "%aN"); - - format_commit_message(commit, fmt, &ident, &ctx); if (!log->summary) { if (log->user_format) pretty_print_commit(&ctx, commit, &oneline); else format_commit_message(commit, "%s", &oneline, &ctx); } - - insert_one_record(log, ident.buf, oneline.len ? oneline.buf : ""); + oneline_str = oneline.len ? oneline.buf : ""; + + switch (log->group) { + case SHORTLOG_GROUP_AUTHOR: + format_commit_message(commit, + log->email ? "%aN <%aE>" : "%aN", + &ident, &ctx); + insert_one_record(log, ident.buf, oneline_str); + break; + case SHORTLOG_GROUP_COMMITTER: + format_commit_message(commit, + log->email ? "%cN <%cE>" : "%cN", + &ident, &ctx); + insert_one_record(log, ident.buf, oneline_str); + break; + } strbuf_release(&ident); strbuf_release(&oneline); @@ -241,6 +260,21 @@ static int parse_wrap_args(const struct option *opt, const char *arg, int unset) return 0; } +static int parse_group_option(const struct option *opt, const char *arg, int unset) +{ + struct shortlog *log = opt->value; + + if (unset || !strcasecmp(arg, "author")) + log->group = SHORTLOG_GROUP_AUTHOR; + else if (!strcasecmp(arg, "committer")) + log->group = SHORTLOG_GROUP_COMMITTER; + else + return error(_("unknown group type: %s"), arg); + + return 0; +} + + void shortlog_init(struct shortlog *log) { memset(log, 0, sizeof(*log)); @@ -260,8 +294,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) int nongit = !startup_info->have_repository; const struct option options[] = { - OPT_BOOL('c', "committer", &log.committer, - N_("Group by committer rather than author")), + OPT_SET_INT('c', "committer", &log.group, + 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, @@ -271,6 +306,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) OPT_CALLBACK_F('w', NULL, &log, N_("[,[,]]"), N_("Linewrap output"), PARSE_OPT_OPTARG, &parse_wrap_args), + OPT_CALLBACK(0, "group", &log, N_("field"), + N_("Group by field"), parse_group_option), OPT_END(), }; -- cgit v1.2.3 From 47beb37bc60b690e83b19aa7ff646255fb643ad9 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 27 Sep 2020 04:40:04 -0400 Subject: 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 Signed-off-by: Junio C Hamano --- builtin/shortlog.c | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) (limited to 'builtin') diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 880ce19304..e1d9ee909f 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -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 [] [] [[--] ...]"), @@ -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; -- cgit v1.2.3 From f17b0b99bf2dc2fcd74544ce35d058e558e6b056 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 27 Sep 2020 04:40:07 -0400 Subject: shortlog: de-duplicate trailer values The current documentation is vague about what happens with --group=trailer:signed-off-by when we see a commit with: Signed-off-by: One Signed-off-by: Two Signed-off-by: One We clearly should credit both "One" and "Two", but should "One" get credited twice? The current code does so, but mostly because that was the easiest thing to do. It's probably more useful to count each commit at most once. This will become especially important when we allow values from multiple sources in a future patch. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/shortlog.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'builtin') diff --git a/builtin/shortlog.c b/builtin/shortlog.c index e1d9ee909f..d2d8103dd3 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -166,6 +166,60 @@ static void read_from_stdin(struct shortlog *log) strbuf_release(&oneline); } +struct strset_item { + struct hashmap_entry ent; + char value[FLEX_ARRAY]; +}; + +struct strset { + struct hashmap map; +}; + +#define STRSET_INIT { { NULL } } + +static int strset_item_hashcmp(const void *hash_data, + const struct hashmap_entry *entry, + const struct hashmap_entry *entry_or_key, + const void *keydata) +{ + const struct strset_item *a, *b; + + a = container_of(entry, const struct strset_item, ent); + if (keydata) + return strcmp(a->value, keydata); + + b = container_of(entry_or_key, const struct strset_item, ent); + return strcmp(a->value, b->value); +} + +/* + * Adds "str" to the set if it was not already present; returns true if it was + * already there. + */ +static int strset_check_and_add(struct strset *ss, const char *str) +{ + unsigned int hash = strhash(str); + struct strset_item *item; + + if (!ss->map.table) + hashmap_init(&ss->map, strset_item_hashcmp, NULL, 0); + + if (hashmap_get_from_hash(&ss->map, hash, str)) + return 1; + + FLEX_ALLOC_STR(item, value, str); + hashmap_entry_init(&item->ent, hash); + hashmap_add(&ss->map, &item->ent); + return 0; +} + +static void strset_clear(struct strset *ss) +{ + if (!ss->map.table) + return; + hashmap_free_entries(&ss->map, struct strset_item, ent); +} + static void insert_records_from_trailers(struct shortlog *log, struct commit *commit, struct pretty_print_context *ctx, @@ -173,6 +227,7 @@ static void insert_records_from_trailers(struct shortlog *log, { struct trailer_iterator iter; const char *commit_buffer, *body; + struct strset dups = STRSET_INIT; /* * Using format_commit_message("%B") would be simpler here, but @@ -190,10 +245,13 @@ static void insert_records_from_trailers(struct shortlog *log, if (strcasecmp(iter.key.buf, log->trailer)) continue; + if (strset_check_and_add(&dups, value)) + continue; insert_one_record(log, value, oneline); } trailer_iterator_release(&iter); + strset_clear(&dups); unuse_commit_buffer(commit, commit_buffer); } -- cgit v1.2.3 From 87abb96222b076a1a9aa9c92f799107141feeca4 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 27 Sep 2020 04:40:09 -0400 Subject: shortlog: rename parse_stdin_ident() This function is actually useful for parsing any identity, whether from stdin or not. We'll need it for handling trailers. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/shortlog.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'builtin') diff --git a/builtin/shortlog.c b/builtin/shortlog.c index d2d8103dd3..e6f4faec7c 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -98,8 +98,8 @@ static void insert_one_record(struct shortlog *log, } } -static int parse_stdin_ident(struct shortlog *log, - struct strbuf *out, const char *in) +static int parse_ident(struct shortlog *log, + struct strbuf *out, const char *in) { const char *mailbuf, *namebuf; size_t namelen, maillen; @@ -156,7 +156,7 @@ static void read_from_stdin(struct shortlog *log) ; /* discard blanks */ strbuf_reset(&mapped_ident); - if (parse_stdin_ident(log, &mapped_ident, v) < 0) + if (parse_ident(log, &mapped_ident, v) < 0) continue; insert_one_record(log, mapped_ident.buf, oneline.buf); -- cgit v1.2.3 From 56d5dde7526e725a9e590f32fe38ce6b3391bc36 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 27 Sep 2020 04:40:11 -0400 Subject: shortlog: parse trailer idents Trailers don't necessarily contain name/email identity values, so shortlog has so far treated them as opaque strings. However, since many trailers do contain identities, it's useful to treat them as such when they can be parsed. That lets "-e" work as usual, as well as mailmap. When they can't be parsed, we'll continue with the old behavior of treating them as a single string (there's no new test for that here, since the existing tests cover a trailer like this). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/shortlog.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'builtin') diff --git a/builtin/shortlog.c b/builtin/shortlog.c index e6f4faec7c..28133aec68 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -228,6 +228,7 @@ static void insert_records_from_trailers(struct shortlog *log, struct trailer_iterator iter; const char *commit_buffer, *body; struct strset dups = STRSET_INIT; + struct strbuf ident = STRBUF_INIT; /* * Using format_commit_message("%B") would be simpler here, but @@ -245,12 +246,17 @@ static void insert_records_from_trailers(struct shortlog *log, if (strcasecmp(iter.key.buf, log->trailer)) continue; + strbuf_reset(&ident); + if (!parse_ident(log, &ident, value)) + value = ident.buf; + 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); } -- cgit v1.2.3 From 63d24fa0b055d3f0386ee3686c07428450add708 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 27 Sep 2020 04:40:15 -0400 Subject: 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 Signed-off-by: Junio C Hamano --- builtin/log.c | 1 + builtin/shortlog.c | 64 ++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 24 deletions(-) (limited to 'builtin') 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 : ""; - 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); -- cgit v1.2.3