git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 4/7] shortlog: support arbitrary commit format `--group`s

From
Taylor Blau <me@ttaylorr.com>
Date
Oct 24, 2022, 18:55 UTC
Message-ID
<baee19cb8c0375478072ddfce06dc4708a49ef77.1666637725.git.me@ttaylorr.com>
In-Reply-To
<cover.1666637725.git.me@ttaylorr.com>

In addition to generating a shortlog based on committer, author, or the identity in one or more specified trailers, it can be useful to generate a shortlog based on an arbitrary commit format.

This can be used, for example, to generate a distribution of commit activity over time, like so:

    $ git shortlog --group='%cd' --date='format:%Y-%m' -s v2.37.0..
       117  2022-06
       274  2022-07
       324  2022-08
       263  2022-09
         7  2022-10

Arbitrary commit formats can be used. In fact, `git shortlog`'s default behavior (to count by commit authors) can be emulated as follows:

    $ git shortlog --group='%aN <%aE>' ...

and future patches will make the default behavior (as well as `--committer`, and `--group=trailer:<trailer>`) special cases of the more flexible `--group` option.

Note also that the SHORTLOG_GROUP_FORMAT enum value is used only to designate that `--group:<format>` is in use when in stdin mode to declare that the combination is invalid.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
---
 Documentation/git-shortlog.txt |  5 ++++-
 builtin/shortlog.c             | 41 +++++++++++++++++++++++++++++++++-
 shortlog.h                     |  2 ++
 t/t4201-shortlog.sh            | 32 ++++++++++++++++++++++++++
 4 files changed, 78 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
index 9ed9d6a9e7..7d0277d033 100644
--- a/Documentation/git-shortlog.txt
+++ b/Documentation/git-shortlog.txt
@@ -50,7 +50,7 @@ OPTIONS
 --date=<format>::
 	Show dates formatted according to the given date string. (See
 	the `--date` option in the "Commit Formatting" section of
-	linkgit:git-log[1]).
+	linkgit:git-log[1]). Useful with `--group=format:<format>`.
 
 --group=<type>::
 	Group commits based on `<type>`. If no `--group` option is
@@ -64,6 +64,9 @@ OPTIONS
    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`.
+ - `format:<format>`, any string accepted by the `--format` option of
+   'git log'. (See the "PRETTY FORMATS" section of
+   linkgit:git-log[1].)
 +
 Note that commits that do not include the trailer will not be counted.
 Likewise, commits with multiple trailers (e.g., multiple signoffs) may
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index d0645769d7..f3b237c5ff 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -133,6 +133,8 @@ static void read_from_stdin(struct shortlog *log)
 		break;
 	case SHORTLOG_GROUP_TRAILER:
 		die(_("using %s with stdin is not supported"), "--group=trailer");
+	case SHORTLOG_GROUP_FORMAT:
+		die(_("using %s with stdin is not supported"), "--group=format");
 	default:
 		BUG("unhandled shortlog group");
 	}
@@ -203,6 +205,32 @@ static void insert_records_from_trailers(struct shortlog *log,
 	unuse_commit_buffer(commit, commit_buffer);
 }
 
+static int shortlog_needs_dedup(const struct shortlog *log)
+{
+	return HAS_MULTI_BITS(log->groups) || log->format.nr > 1 || log->trailers.nr;
+}
+
+static void insert_records_from_format(struct shortlog *log,
+				       struct strset *dups,
+				       struct commit *commit,
+				       struct pretty_print_context *ctx,
+				       const char *oneline)
+{
+	struct strbuf buf = STRBUF_INIT;
+	struct string_list_item *item;
+
+	for_each_string_list_item(item, &log->format) {
+		strbuf_reset(&buf);
+
+		format_commit_message(commit, item->string, &buf, ctx);
+
+		if (!shortlog_needs_dedup(log) || strset_add(dups, buf.buf))
+			insert_one_record(log, buf.buf, oneline);
+	}
+
+	strbuf_release(&buf);
+}
+
 void shortlog_add_commit(struct shortlog *log, struct commit *commit)
 {
 	struct strbuf ident = STRBUF_INIT;
@@ -244,6 +272,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
 			insert_one_record(log, ident.buf, oneline_str);
 	}
 	insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str);
+	insert_records_from_format(log, &dups, commit, &ctx, oneline_str);
 
 	strset_clear(&dups);
 	strbuf_release(&ident);
@@ -315,6 +344,7 @@ static int parse_group_option(const struct option *opt, const char *arg, int uns
 	if (unset) {
 		log->groups = 0;
 		string_list_clear(&log->trailers, 0);
+		string_list_clear(&log->format, 0);
 	} else if (!strcasecmp(arg, "author"))
 		log->groups |= SHORTLOG_GROUP_AUTHOR;
 	else if (!strcasecmp(arg, "committer"))
@@ -322,8 +352,15 @@ static int parse_group_option(const struct option *opt, const char *arg, int uns
 	else if (skip_prefix(arg, "trailer:", &field)) {
 		log->groups |= SHORTLOG_GROUP_TRAILER;
 		string_list_append(&log->trailers, field);
-	} else
+	} else if (skip_prefix(arg, "format:", &field)) {
+		log->groups |= SHORTLOG_GROUP_FORMAT;
+		string_list_append(&log->format, field);
+	} else if (strchr(arg, '%')) {
+		log->groups |= SHORTLOG_GROUP_FORMAT;
+		string_list_append(&log->format, arg);
+	} else {
 		return error(_("unknown group type: %s"), arg);
+	}
 
 	return 0;
 }
@@ -341,6 +378,7 @@ void shortlog_init(struct shortlog *log)
 	log->in2 = DEFAULT_INDENT2;
 	log->trailers.strdup_strings = 1;
 	log->trailers.cmp = strcasecmp;
+	log->format.strdup_strings = 1;
 }
 
 int cmd_shortlog(int argc, const char **argv, const char *prefix)
@@ -481,4 +519,5 @@ void shortlog_output(struct shortlog *log)
 	log->list.strdup_strings = 1;
 	string_list_clear(&log->list, 1);
 	clear_mailmap(&log->mailmap);
+	string_list_clear(&log->format, 0);
 }
diff --git a/shortlog.h b/shortlog.h
index dc388dd459..4850a8c30f 100644
--- a/shortlog.h
+++ b/shortlog.h
@@ -22,8 +22,10 @@ struct shortlog {
 		SHORTLOG_GROUP_AUTHOR = (1 << 0),
 		SHORTLOG_GROUP_COMMITTER = (1 << 1),
 		SHORTLOG_GROUP_TRAILER = (1 << 2),
+		SHORTLOG_GROUP_FORMAT = (1 << 3),
 	} groups;
 	struct string_list trailers;
+	struct string_list format;
 
 	int email;
 	struct string_list mailmap;
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index 7547da539d..8e4effebdb 100755
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
@@ -244,6 +244,26 @@ test_expect_success 'shortlog --group=trailer:signed-off-by' '
 	test_cmp expect actual
 '
 
+test_expect_success 'shortlog --group=format' '
+	git shortlog -s --date="format:%Y" --group="format:%cN (%cd)" \
+		HEAD >actual &&
+	cat >expect <<-\EOF &&
+	     4	C O Mitter (2005)
+	     1	Sin Nombre (2005)
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'shortlog --group=<format> DWIM' '
+	git shortlog -s --date="format:%Y" --group="%cN (%cd)" HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'shortlog bogus --group' '
+	test_must_fail git shortlog --group=bogus HEAD 2>err &&
+	grep "unknown group type" err
+'
+
 test_expect_success 'trailer idents are split' '
 	cat >expect <<-\EOF &&
 	     2	C O Mitter
@@ -326,6 +346,18 @@ test_expect_success 'shortlog can match multiple groups' '
 	test_cmp expect actual
 '
 
+test_expect_success 'shortlog can match multiple format groups' '
+	GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" \
+		git commit --allow-empty -m "identical names" &&
+	test_tick &&
+	cat >expect <<-\EOF &&
+	     2	A U Thor
+	     1	C O Mitter
+	EOF
+	git shortlog -ns --group="%cn" --group="%an" -2 HEAD >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'set up option selection tests' '
 	git commit --allow-empty -F - <<-\EOF
 	subject
-- 
2.38.0.16.g393fd4c6db
Previous: Taylor BlauNext: Taylor Blau
Message 66 of 72 in “shortlog: introduce `--group=<format>`”
  1. 0/7 shortlog: introduce `--group=<format>`Taylor Blau, Oct 11, 2022
  2. 1/7 Documentation: extract date-options.txtTaylor Blau, Oct 11, 2022
  3. Jeff KingOct 11, 2022
  4. Taylor BlauOct 11, 2022
  5. 2/7 shortlog: accept `--date`-related optionsTaylor Blau, Oct 11, 2022
  6. Jeff KingOct 11, 2022
  7. Taylor BlauOct 11, 2022
  8. 3/7 shortlog: extract `--group` fragment for translationTaylor Blau, Oct 11, 2022
  9. Ævar Arnfjörð BjarmasonOct 11, 2022
  10. Jeff KingOct 11, 2022
  11. 4/7 shortlog: support arbitrary commit format `--group`sTaylor Blau, Oct 11, 2022
  12. Jeff KingOct 11, 2022
  13. Taylor BlauOct 11, 2022
  14. Taylor BlauOct 11, 2022
  15. Jeff KingOct 11, 2022
  16. Jeff KingOct 11, 2022
  17. Taylor BlauOct 21, 2022
  18. Jeff KingOct 21, 2022
  19. Taylor BlauOct 21, 2022
  20. 5/7 shortlog: implement `--group=author` in terms of `--group=<format>`Taylor Blau, Oct 11, 2022
  21. Jeff KingOct 11, 2022
  22. Taylor BlauOct 11, 2022
  23. Jeff KingOct 11, 2022
  24. Taylor BlauOct 21, 2022
  25. Taylor BlauOct 21, 2022
  26. Jeff KingOct 21, 2022
  27. Taylor BlauOct 21, 2022
  28. Ævar Arnfjörð BjarmasonOct 11, 2022
  29. Ævar Arnfjörð BjarmasonOct 11, 2022
  30. 7/7 shortlog: implement `--group=trailer` in terms of `--group=<format>`Taylor Blau, Oct 11, 2022
  31. Jeff KingOct 11, 2022
  32. Jeff KingOct 11, 2022
  33. Taylor BlauOct 21, 2022
  34. 6/7 shortlog: implement `--group=committer` in terms of `--group=<format>`Taylor Blau, Oct 11, 2022
  35. Jeff KingOct 11, 2022
  36. 0/6 shortlog: introduce `--group=<format>`Taylor Blau, Oct 21, 2022
  37. 1/6 shortlog: accept `--date`-related optionsTaylor Blau, Oct 21, 2022
  38. Jeff KingOct 21, 2022
  39. Taylor BlauOct 21, 2022
  40. 2/6 shortlog: make trailer insertion a noop when appropriateTaylor Blau, Oct 21, 2022
  41. Jeff KingOct 21, 2022
  42. Taylor BlauOct 21, 2022
  43. 3/6 shortlog: extract `--group` fragment for translationTaylor Blau, Oct 21, 2022
  44. Jeff KingOct 21, 2022
  45. 4/6 shortlog: support arbitrary commit format `--group`sTaylor Blau, Oct 21, 2022
  46. Jeff KingOct 21, 2022
  47. Taylor BlauOct 21, 2022
  48. 5/6 shortlog: implement `--group=author` in terms of `--group=<format>`Taylor Blau, Oct 21, 2022
  49. Jeff KingOct 21, 2022
  50. 6/6 shortlog: implement `--group=committer` in terms of `--group=<format>`Taylor Blau, Oct 21, 2022
  51. Jeff KingOct 21, 2022
  52. Jeff KingOct 21, 2022
  53. 0/7 shortlog: introduce `--group=<format>`Taylor Blau, Oct 21, 2022
  54. 1/7 shortlog: accept `--date`-related optionsTaylor Blau, Oct 21, 2022
  55. 2/7 shortlog: make trailer insertion a noop when appropriateTaylor Blau, Oct 21, 2022
  56. 4/7 shortlog: support arbitrary commit format `--group`sTaylor Blau, Oct 21, 2022
  57. Jeff KingOct 22, 2022
  58. 5/7 shortlog: extract `shortlog_finish_setup()`Taylor Blau, Oct 21, 2022
  59. 6/7 shortlog: implement `--group=author` in terms of `--group=<format>`Taylor Blau, Oct 21, 2022
  60. 3/7 shortlog: extract `--group` fragment for translationTaylor Blau, Oct 21, 2022
  61. 7/7 shortlog: implement `--group=committer` in terms of `--group=<format>`Taylor Blau, Oct 21, 2022
  62. Jeff KingOct 22, 2022
  63. 0/7 shortlog: introduce `--group=<format>`Taylor Blau, Oct 24, 2022
  64. 7/7 shortlog: implement `--group=committer` in terms of `--group=<format>`Taylor Blau, Oct 24, 2022
  65. 5/7 shortlog: extract `shortlog_finish_setup()`Taylor Blau, Oct 24, 2022
  66. 4/7 shortlog: support arbitrary commit format `--group`sTaylor Blau, Oct 24, 2022
  67. 3/7 shortlog: extract `--group` fragment for translationTaylor Blau, Oct 24, 2022
  68. 1/7 shortlog: accept `--date`-related optionsTaylor Blau, Oct 24, 2022
  69. 6/7 shortlog: implement `--group=author` in terms of `--group=<format>`Taylor Blau, Oct 24, 2022
  70. 2/7 shortlog: make trailer insertion a noop when appropriateTaylor Blau, Oct 24, 2022
  71. Junio C HamanoOct 24, 2022
  72. Jeff KingOct 24, 2022

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.