{"thread":{"id":"65032","subject":"[RFC PATCH] format-patch: better commit list for cover letter","startedAt":"2026-02-20T23:07:00Z","lastAt":"2026-03-13T20:22:53Z","messageCount":113,"participants":["Mirko Faina","Junio C Hamano","Jeff King","Bert Wesarg","Phillip Wood","Mroik"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"536561","messageId":"20260220230633.132213-1-mroik@delayed.space","threadId":"65032","inReplyTo":null,"subject":"[RFC PATCH] format-patch: better commit list for cover letter","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-20T23:06:31Z","receivedAt":"2026-02-20T23:07:00Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nTeach the make_cover_letter() a better cover letter format to replace\nthe current. The format can be seen from the following example:\n\n    [1/3] abcc234s: this is a summary\n    [2/3] 73s84ns2: this is another summary\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\nThis patch comes after a small off-topic[1] that discussed the\nusefulness (or the lack thereof) of the current cover letter template.\nThis patch hopes to make a better format so that developers won't have\nto make their own custom script to generate better info.\n\n[1] https://lore.kernel.org/git/xmqqbjhjxp2d.fsf@gitster.g/\n\n builtin/log.c | 40 ++++++++++++++++++++++++++--------------\n 1 file changed, 26 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex c1cd3999a7..4fea895527 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -30,7 +30,6 @@\n #include \"reflog-walk.h\"\n #include \"patch-ids.h\"\n #include \"path.h\"\n-#include \"shortlog.h\"\n #include \"remote.h\"\n #include \"string-list.h\"\n #include \"parse-options.h\"\n@@ -40,6 +39,8 @@\n #include \"mailmap.h\"\n #include \"progress.h\"\n #include \"commit-slab.h\"\n+#include \"pretty.h\"\n+#include \"strbuf.h\"\n \n #include \"commit-reach.h\"\n #include \"range-diff.h\"\n@@ -1324,6 +1325,29 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_cover_commit_list(FILE *cover_file, struct commit** list, int nr)\n+{\n+\tchar* commit_hash_buf = malloc(GIT_MAX_HEXSZ + 1);\n+\tstruct strbuf *sb_loglines = malloc(sizeof(struct strbuf));\n+\tint temp;\n+\n+\tfor (int i = 0; i < nr; i++) {\n+\t\tstrbuf_init(sb_loglines, 0);\n+\t\tstrbuf_addf(sb_loglines, \"[%0*d/%d] \", decimal_width(nr), i + 1, nr);\n+\t\ttemp = sb_loglines->len;\n+\t\tstrbuf_addstr(sb_loglines, oid_to_hex_r(commit_hash_buf, &list[i]->object.oid));\n+\t\tstrbuf_addch(sb_loglines, ':');\n+\t\tstrbuf_remove(sb_loglines, temp, sb_loglines->len - temp - 8);\n+\t\tstrbuf_addch(sb_loglines, ' ');\n+\t\tpp_commit_easy(CMIT_FMT_ONELINE, list[i], sb_loglines);\n+\t\tfprintf(cover_file, \"%s\\n\", sb_loglines->buf);\n+\t}\n+\n+\tfprintf(cover_file, \"\\n\");\n+\tstrbuf_release(sb_loglines);\n+\tfree(commit_hash_buf);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n@@ -1333,7 +1357,6 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      const struct format_config *cfg)\n {\n \tconst char *from;\n-\tstruct shortlog log;\n \tstruct strbuf sb = STRBUF_INIT;\n \tint i;\n \tconst char *encoding = \"UTF-8\";\n@@ -1377,18 +1400,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n-\n-\tshortlog_output(&log);\n+\tgenerate_cover_commit_list(rev->diffopt.file, list, nr);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n\nbase-commit: a8e89346a7731cb3104010f322c65e2a0c922618\n-- \n2.53.0\n\n"},{"id":"536566","messageId":"20260220235502.759002-1-mroik@delayed.space","threadId":"65032","inReplyTo":"20260220230633.132213-1-mroik@delayed.space","subject":"[RFC PATCH v2] format-patch: better commit list for cover letter","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-20T23:55:01Z","receivedAt":"2026-02-20T23:55:21Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nTeach the make_cover_letter() a better cover letter format to replace\nthe current. The format can be seen from the following example:\n\n    [1/3] abcc234s: this is a summary\n    [2/3] 73s84ns2: this is another summary\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\nThis patch comes after a small off-topic[1] that discussed the\nusefulness (or the lack thereof) of the current cover letter template.\nThis patch hopes to make a better format so that developers won't have\nto make their own custom script to generate better info.\n\nP.s. I'm sorry, the first version was a bit wrong. This one is correct.\n\n[1] https://lore.kernel.org/git/xmqqbjhjxp2d.fsf@gitster.g/\n\n builtin/log.c | 40 ++++++++++++++++++++++++++--------------\n 1 file changed, 26 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex c1cd3999a7..7d446b09b4 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -30,7 +30,6 @@\n #include \"reflog-walk.h\"\n #include \"patch-ids.h\"\n #include \"path.h\"\n-#include \"shortlog.h\"\n #include \"remote.h\"\n #include \"string-list.h\"\n #include \"parse-options.h\"\n@@ -40,6 +39,8 @@\n #include \"mailmap.h\"\n #include \"progress.h\"\n #include \"commit-slab.h\"\n+#include \"pretty.h\"\n+#include \"strbuf.h\"\n \n #include \"commit-reach.h\"\n #include \"range-diff.h\"\n@@ -1324,6 +1325,29 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_cover_commit_list(FILE *cover_file, struct commit** list, int nr)\n+{\n+\tchar* commit_hash_buf = malloc(GIT_MAX_HEXSZ + 1);\n+\tstruct strbuf *sb_loglines = malloc(sizeof(struct strbuf));\n+\tint temp;\n+\n+\tfor (int i = 0; i < nr; i++) {\n+\t\tstrbuf_init(sb_loglines, 0);\n+\t\tstrbuf_addf(sb_loglines, \"[%0*d/%d] \", decimal_width(nr), i + 1, nr);\n+\t\ttemp = sb_loglines->len;\n+\t\tstrbuf_addstr(sb_loglines, oid_to_hex_r(commit_hash_buf, &list[i]->object.oid));\n+\t\tstrbuf_remove(sb_loglines, temp + 8, sb_loglines->len - (temp + 8));\n+\t\tstrbuf_addch(sb_loglines, ':');\n+\t\tstrbuf_addch(sb_loglines, ' ');\n+\t\tpp_commit_easy(CMIT_FMT_ONELINE, list[i], sb_loglines);\n+\t\tfprintf(cover_file, \"%s\\n\", sb_loglines->buf);\n+\t}\n+\n+\tfprintf(cover_file, \"\\n\");\n+\tstrbuf_release(sb_loglines);\n+\tfree(commit_hash_buf);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n@@ -1333,7 +1357,6 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      const struct format_config *cfg)\n {\n \tconst char *from;\n-\tstruct shortlog log;\n \tstruct strbuf sb = STRBUF_INIT;\n \tint i;\n \tconst char *encoding = \"UTF-8\";\n@@ -1377,18 +1400,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n-\n-\tshortlog_output(&log);\n+\tgenerate_cover_commit_list(rev->diffopt.file, list, nr);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n\nbase-commit: a8e89346a7731cb3104010f322c65e2a0c922618\n-- \n2.53.0\n\n"},{"id":"536568","messageId":"aZkAo4_RmfKNFwow@exploit","threadId":"65032","inReplyTo":"20260220235502.759002-1-mroik@delayed.space","subject":"Re: [RFC PATCH v2] format-patch: better commit list for cover letter","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-21T00:51:51Z","receivedAt":"2026-02-21T00:51:55Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Sat, Feb 21, 2026 at 12:55:01AM +0100, Mirko Faina wrote:\n> +\tfor (int i = 0; i < nr; i++) {\n> +\t\tstrbuf_init(sb_loglines, 0);\n> +\t\tstrbuf_addf(sb_loglines, \"[%0*d/%d] \", decimal_width(nr), i + 1, nr);\n> +\t\ttemp = sb_loglines->len;\n> +\t\tstrbuf_addstr(sb_loglines, oid_to_hex_r(commit_hash_buf, &list[i]->object.oid));\n> +\t\tstrbuf_remove(sb_loglines, temp + 8, sb_loglines->len - (temp + 8));\n> +\t\tstrbuf_addch(sb_loglines, ':');\n> +\t\tstrbuf_addch(sb_loglines, ' ');\n> +\t\tpp_commit_easy(CMIT_FMT_ONELINE, list[i], sb_loglines);\n> +\t\tfprintf(cover_file, \"%s\\n\", sb_loglines->buf);\n> +\t}\n\nI also noticed it generates the list in the reversed order (and probably\nleaks), but this was just a PoC and I'm more interested in knowing if\nthe format is liked by the users.\n"},{"id":"536572","messageId":"xmqqldgmu25h.fsf@gitster.g","threadId":"65032","inReplyTo":"20260220230633.132213-1-mroik@delayed.space","subject":"Re: [RFC PATCH] format-patch: better commit list for cover letter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-21T04:54:50Z","receivedAt":"2026-02-21T04:54:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Often when sending patch series there's a need to clarify to the\n> reviewer what's the purpose of said series, since it might be difficult\n> to understand it from reading the commits messages one by one.\n>\n> \"git format-patch\" provides the useful \"--cover-letter\" flag to declare\n> if we want it to generate a template for us to use. By default it will\n> generate a \"git shortlog\" of the changes, which developers find less\n> useful than they'd like, mainly because the shortlog groups commits by\n> author, and gives no obvious chronological order.\n\nAll true.\n\n> Teach the make_cover_letter() a better cover letter format to replace\n> the current. The format can be seen from the following example:\n>\n>     [1/3] abcc234s: this is a summary\n>     [2/3] 73s84ns2: this is another summary\n\nTwo things to consider.\n\n (1) Drop the abbreviated object name, as they are useless garbage.\n     The result of applying these patches will not have these commit\n     object names anyway, so even when people find these messages on\n     a mail archive in 6 months, they will not find the result of\n     applying the patches from the official project history with\n     these object names.\n\n (2) Do we need to make this optional, in order to allow those users\n     who do prefer the current \"shortlog\" style that groups patches\n     from the same person together to keep the original style?  I am\n     undecided myself.\n"},{"id":"536573","messageId":"aZk9QlH2PDugXKrh@exploit","threadId":"65032","inReplyTo":"xmqqldgmu25h.fsf@gitster.g","subject":"Re: [RFC PATCH] format-patch: better commit list for cover letter","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-21T05:18:46Z","receivedAt":"2026-02-21T05:18:50Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Fri, Feb 20, 2026 at 08:54:50PM -0800, Junio C Hamano wrote:\n>  (1) Drop the abbreviated object name, as they are useless garbage.\n>      The result of applying these patches will not have these commit\n>      object names anyway, so even when people find these messages on\n>      a mail archive in 6 months, they will not find the result of\n>      applying the patches from the official project history with\n>      these object names.\n\nShould there be a reference to the author ident instead of the object\nname then? A quick glance on who worked on what before diving into the\npatches themselves might be useful.\n\n>  (2) Do we need to make this optional, in order to allow those users\n>      who do prefer the current \"shortlog\" style that groups patches\n>      from the same person together to keep the original style?  I am\n>      undecided myself.\n\nMaybe the \"--cover-letter\" option can take an argument like\n\"--cover-letter=<shortlog | commitlist>\". Although I doubt there's\nanyone that actually likes the shortlog version, it gives very little\ninformation. I'm inclined to think that most leave it there because they\nthink it must be somewhat since it is the default, tho this is just my\nassumption.\n"},{"id":"536574","messageId":"xmqqh5ratzc6.fsf@gitster.g","threadId":"65032","inReplyTo":"aZk9QlH2PDugXKrh@exploit","subject":"Re: [RFC PATCH] format-patch: better commit list for cover letter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-21T05:55:37Z","receivedAt":"2026-02-21T05:55:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> On Fri, Feb 20, 2026 at 08:54:50PM -0800, Junio C Hamano wrote:\n>>  (1) Drop the abbreviated object name, as they are useless garbage.\n>>      The result of applying these patches will not have these commit\n>>      object names anyway, so even when people find these messages on\n>>      a mail archive in 6 months, they will not find the result of\n>>      applying the patches from the official project history with\n>>      these object names.\n>\n> Should there be a reference to the author ident instead of the object\n> name then? A quick glance on who worked on what before diving into the\n> patches themselves might be useful.\n>\n>>  (2) Do we need to make this optional, in order to allow those users\n>>      who do prefer the current \"shortlog\" style that groups patches\n>>      from the same person together to keep the original style?  I am\n>>      undecided myself.\n>\n> Maybe the \"--cover-letter\" option can take an argument like\n> \"--cover-letter=<shortlog | commitlist>\". Although I doubt there's\n> anyone that actually likes the shortlog version, it gives very little\n> information. I'm inclined to think that most leave it there because they\n> think it must be somewhat since it is the default, tho this is just my\n> assumption.\n\nJust off the top of my head...\n\nPerhaps with\n\n    [format]\n\tcommitListFormat ;# true\n\nwe use\n\n\t[1/1] format-patch: better commit list for cover letter\n\nand with\n\n    [format]\n\tcommitListFormat=\"%s (%an)\"\n\nwe use\n\n\t[1/1] format-patch: better commit list for cover letter\t(Mirko Faina)\n\ninstead.  IOW, the value of the configuration variable is used as\nthe format argument \"log --format=...\", and appended to the fixed\n[n/m] that gives the numbers.\n\nWithout format.commitListFormat defined, or when it is defined to\nfalse, we'd use the traditional \"shortlog\" format.\n\nHmm?\n"},{"id":"536575","messageId":"xmqqcy1ytz0y.fsf@gitster.g","threadId":"65032","inReplyTo":"xmqqh5ratzc6.fsf@gitster.g","subject":"Re: [RFC PATCH] format-patch: better commit list for cover letter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-21T06:02:21Z","receivedAt":"2026-02-21T06:02:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> Maybe the \"--cover-letter\" option can take an argument like\n>> \"--cover-letter=<shortlog | commitlist>\". ...\n>\n> Just off the top of my head...\n>\n> Perhaps with\n>\n>     [format]\n> \tcommitListFormat ;# true\n>\n> we use\n>\n> \t[1/1] format-patch: better commit list for cover letter\n>\n> and with\n>\n>     [format]\n> \tcommitListFormat=\"%s (%an)\"\n>\n> we use\n>\n> \t[1/1] format-patch: better commit list for cover letter\t(Mirko Faina)\n>\n> instead.  IOW, the value of the configuration variable is used as\n> the format argument \"log --format=...\", and appended to the fixed\n> [n/m] that gives the numbers.\n>\n> Without format.commitListFormat defined, or when it is defined to\n> false, we'd use the traditional \"shortlog\" format.\n>\n> Hmm?\n\nAnd the corresponding command line option can look like\n\n\t--cover-letter=(shortlog | log:<log format spec>)\n\ne.g.,\n\n\t--cover-letter=log:\"%s (%an)\"\n\nto override configured value per invocation basis.\n"},{"id":"536589","messageId":"aZnOyYGzOByayPFK@exploit","threadId":"65032","inReplyTo":"xmqqcy1ytz0y.fsf@gitster.g","subject":"Re: [RFC PATCH] format-patch: better commit list for cover letter","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-21T15:59:06Z","receivedAt":"2026-02-21T15:59:10Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Fri, Feb 20, 2026 at 10:02:21PM -0800, Junio C Hamano wrote:\n>\n> Just off the top of my head...\n>\n> Perhaps with\n>\n>     [format]\n> \tcommitListFormat ;# true\n>\n> we use\n>\n> \t[1/1] format-patch: better commit list for cover letter\n>\n> and with\n>\n>     [format]\n> \tcommitListFormat=\"%s (%an)\"\n>\n> we use\n>\n> \t[1/1] format-patch: better commit list for cover letter\t(Mirko Faina)\n>\n> instead.  IOW, the value of the configuration variable is used as\n> the format argument \"log --format=...\", and appended to the fixed\n> [n/m] that gives the numbers.\n>\n> Without format.commitListFormat defined, or when it is defined to\n> false, we'd use the traditional \"shortlog\" format.\n>\n> Hmm?\n\nYes, sounds good.\n\n> And the corresponding command line option can look like\n> \n> \t--cover-letter=(shortlog | log:<log format spec>)\n> \n> e.g.,\n> \n> \t--cover-letter=log:\"%s (%an)\"\n> \n> to override configured value per invocation basis.\n\nNot too sure about this one. The point was to have a useful default for\nthe cover letter template. If users have to pass a format spec through\nthe command line it kinda defeates the purpose.\n\nSince we're close to having Git 3.0, maybe the change in default\nbehaviour can be scheduled for 3.0?\n"},{"id":"536603","messageId":"xmqqh5rarohf.fsf@gitster.g","threadId":"65032","inReplyTo":"aZnOyYGzOByayPFK@exploit","subject":"Re: [RFC PATCH] format-patch: better commit list for cover letter","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-21T17:33:00Z","receivedAt":"2026-02-21T17:33:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> On Fri, Feb 20, 2026 at 10:02:21PM -0800, Junio C Hamano wrote:\n>>\n>> Just off the top of my head...\n>>\n>> Perhaps with\n>>\n>>     [format]\n>> \tcommitListFormat ;# true\n>>\n>> we use\n>>\n>> \t[1/1] format-patch: better commit list for cover letter\n>>\n>> and with\n>>\n>>     [format]\n>> \tcommitListFormat=\"%s (%an)\"\n>>\n>> we use\n>>\n>> \t[1/1] format-patch: better commit list for cover letter\t(Mirko Faina)\n>>\n>> instead.  IOW, the value of the configuration variable is used as\n>> the format argument \"log --format=...\", and appended to the fixed\n>> [n/m] that gives the numbers.\n>>\n>> Without format.commitListFormat defined, or when it is defined to\n>> false, we'd use the traditional \"shortlog\" format.\n>>\n>> Hmm?\n>\n> Yes, sounds good.\n>\n>> And the corresponding command line option can look like\n>> \n>> \t--cover-letter=(shortlog | log:<log format spec>)\n>> \n>> e.g.,\n>> \n>> \t--cover-letter=log:\"%s (%an)\"\n>> \n>> to override configured value per invocation basis.\n>\n> Not too sure about this one. The point was to have a useful default for\n> the cover letter template. If users have to pass a format spec through\n> the command line it kinda defeates the purpose.\n\nWhen adding a configuration, never assume that the setting the user\nchooses is good for that user 100% of the time.  You'd need a way\nfrom the command line to override a configured value.\n\nNot having a command line option does defeat the point of adding a\nconfiguration, which can even be used for a setting that is good for\nthe user only 80% of the time.\n"},{"id":"536604","messageId":"aZoB1ddjgfzJll6B@exploit","threadId":"65032","inReplyTo":"xmqqh5rarohf.fsf@gitster.g","subject":"Re: [RFC PATCH] format-patch: better commit list for cover letter","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-21T19:16:07Z","receivedAt":"2026-02-21T19:16:11Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Sat, Feb 21, 2026 at 09:33:00AM -0800, Junio C Hamano wrote:\n> When adding a configuration, never assume that the setting the user\n> chooses is good for that user 100% of the time.  You'd need a way\n> from the command line to override a configured value.\n> \n> Not having a command line option does defeat the point of adding a\n> configuration, which can even be used for a setting that is good for\n> the user only 80% of the time.\n\nGood point. I will start working on this in the following week.\n"},{"id":"536905","messageId":"20260224040400.751247-1-mroik@delayed.space","threadId":"65032","inReplyTo":"20260220230633.132213-1-mroik@delayed.space","subject":"[PATCH 0/3] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T04:03:55Z","receivedAt":"2026-02-24T04:04:18Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"I've implemented the \"--cover-letter-format\" as we discussed. In the end\nI've decided to add a new option instead of changing \"--cover-letter\".\nThis better reflects how the config file is structured too since we can\nhave \"commitListFormat\" set while also having \"coverLetter\" to false.\n\nWhile working on this patch series I also encountered a NULL dereference\nbug in commit_format_is_empty(). Since I needed it for this patch series\nI went ahead and fixed it.\n\n[1/3] pretty.c: fix null pointer dereference (Mirko Faina)\n[2/3] format-patch: add ability to use alt cover format (Mirko Faina)\n[3/3] format-patch: add commitListFormat config (Mirko Faina)\n\n builtin/log.c | 90 ++++++++++++++++++++++++++++++++++++++++++---------\n pretty.c      |  2 +-\n 2 files changed, 75 insertions(+), 17 deletions(-)\n\n-- \n2.53.0.4.geaa3cc5f7e\n\n"},{"id":"536906","messageId":"20260224040400.751247-2-mroik@delayed.space","threadId":"65032","inReplyTo":"20260220230633.132213-1-mroik@delayed.space","subject":"[PATCH 1/3] pretty.c: fix null pointer dereference","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T04:03:56Z","receivedAt":"2026-02-24T04:04:18Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"commit_format_is_empty() is used to check whether \"user_format\" is set\nto a value. Unfortunately this function crashes the program if no\nuser_format is set. This is because instead of checking for the pointer\nvalue it checks for its dereferenced value, this being NULL if\nuser_format is not set.\n\nTeach the proper condition to check if user_format is set.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n pretty.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex e0646bbc5d..cdb8bf559d 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -47,7 +47,7 @@ static struct cmt_fmt_map *find_commit_format(const char *sought);\n \n int commit_format_is_empty(enum cmit_fmt fmt)\n {\n-\treturn fmt == CMIT_FMT_USERFORMAT && !*user_format;\n+\treturn fmt == CMIT_FMT_USERFORMAT && !user_format;\n }\n \n static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)\n-- \n2.53.0.4.geaa3cc5f7e\n\n"},{"id":"536907","messageId":"20260224040400.751247-3-mroik@delayed.space","threadId":"65032","inReplyTo":"20260220230633.132213-1-mroik@delayed.space","subject":"[PATCH 2/3] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T04:03:57Z","receivedAt":"2026-02-24T04:04:27Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nGive the ability to format-patch to specify an alternative format spec\nthrough the \"--cover-letter-format\" option. This option either takes\n\"shortlog\", which is the current format, or a format spec prefixed with\n\"log:\".\n\nExample:\n    git format-patch --cover-letter \\\n        --cover-letter-format=\"log:%s (%an)\" HEAD~3\n\n    [1/3] this is a commit summary (Mirko Faina)\n    [2/3] this is another commit summary (Mirko Faina)\n    ...\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 68 +++++++++++++++++++++++++++++++++++++++------------\n 1 file changed, 52 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex c1cd3999a7..80ec79efe8 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1324,13 +1324,35 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_commit_list_cover(FILE *cover_file, const char *format, struct commit **list, int n)\n+{\n+\tstruct strbuf commit_line = STRBUF_INIT;\n+\tstruct rev_info rev = REV_INFO_INIT;\n+\n+\tstrbuf_init(&commit_line, 0);\n+\tget_commit_format(format, &rev);\n+\tif (commit_format_is_empty(CMIT_FMT_USERFORMAT))\n+\t\tdie(_(\"invalid format spec\"));\n+\n+\tfor (int i = n - 1; i >= 0; i--) {\n+\t\tstrbuf_addf(&commit_line, \"[%0*d/%d] \", decimal_width(n), n - i, n);\n+\t\tpp_commit_easy(CMIT_FMT_USERFORMAT, list[i], &commit_line);\n+\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n+\t\tstrbuf_reset(&commit_line);\n+\t}\n+\tfprintf(cover_file, \"\\n\");\n+\n+\tstrbuf_release(&commit_line);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n \t\t\t      const char *description_file,\n \t\t\t      const char *branch_name,\n \t\t\t      int quiet,\n-\t\t\t      const struct format_config *cfg)\n+\t\t\t      const struct format_config *cfg,\n+\t\t\t      const char *format)\n {\n \tconst char *from;\n \tstruct shortlog log;\n@@ -1342,6 +1364,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tstruct commit *head = list[0];\n \tchar *to_free = NULL;\n \n+\tassert(format);\n+\n \tif (!cmit_fmt_is_mail(rev->commit_format))\n \t\tdie(_(\"cover letter needs email format\"));\n \n@@ -1377,18 +1401,22 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n+\tif (skip_prefix(format, \"log:\", &format)) {\n+\t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n+\t} else {\n+\t\tshortlog_init(&log);\n+\t\tlog.wrap_lines = 1;\n+\t\tlog.wrap = MAIL_DEFAULT_WRAP;\n+\t\tlog.in1 = 2;\n+\t\tlog.in2 = 4;\n+\t\tlog.file = rev->diffopt.file;\n+\t\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n+\t\tshortlog_finish_setup(&log);\n+\t\tfor (i = 0; i < nr; i++)\n+\t\t\tshortlog_add_commit(&log, list[i]);\n \n-\tshortlog_output(&log);\n+\t\tshortlog_output(&log);\n+\t}\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n@@ -1906,6 +1934,7 @@ int cmd_format_patch(int argc,\n \tint just_numbers = 0;\n \tint ignore_if_in_upstream = 0;\n \tint cover_letter = -1;\n+\tchar *cover_letter_fmt = NULL;\n \tint boundary_count = 0;\n \tint no_binary_diff = 0;\n \tint zero_commit = 0;\n@@ -1952,6 +1981,8 @@ int cmd_format_patch(int argc,\n \t\t\t    N_(\"print patches to standard out\")),\n \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n \t\t\t    N_(\"generate a cover letter\")),\n+\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n+\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n \t\t\t    N_(\"use simple number sequence for output file names\")),\n \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n@@ -2289,13 +2320,14 @@ int cmd_format_patch(int argc,\n \t\t/* nothing to do */\n \t\tgoto done;\n \ttotal = list.nr;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n-\t\t\tcover_letter = (total > 1);\n+\t\t\tcover_letter = total > 1;\n \t\telse if ((idiff_prev.nr || rdiff_prev) && (total > 1))\n-\t\t\tcover_letter = (cfg.config_cover_letter != COVER_OFF);\n+\t\t\tcover_letter = cfg.config_cover_letter != COVER_OFF;\n \t\telse\n-\t\t\tcover_letter = (cfg.config_cover_letter == COVER_ON);\n+\t\t\tcover_letter = cfg.config_cover_letter == COVER_ON;\n \t}\n \tif (!cfg.keep_subject && cfg.auto_number && (total > 1 || cover_letter))\n \t\tcfg.numbered = 1;\n@@ -2375,12 +2407,16 @@ int cmd_format_patch(int argc,\n \t}\n \trev.numbered_files = just_numbers;\n \trev.patch_suffix = fmt_patch_suffix;\n+\n+\tif (cover_letter && !cover_letter_fmt)\n+\t\tcover_letter_fmt = \"shortlog\";\n+\n \tif (cover_letter) {\n \t\tif (cfg.thread)\n \t\t\tgen_message_id(&rev, \"cover\");\n \t\tmake_cover_letter(&rev, !!output_directory,\n \t\t\t\t  origin, list.nr, list.items,\n-\t\t\t\t  description_file, branch_name, quiet, &cfg);\n+\t\t\t\t  description_file, branch_name, quiet, &cfg, cover_letter_fmt);\n \t\tprint_bases(&bases, rev.diffopt.file);\n \t\tprint_signature(signature, rev.diffopt.file);\n \t\ttotal++;\n-- \n2.53.0.4.geaa3cc5f7e\n\n"},{"id":"536908","messageId":"20260224040400.751247-4-mroik@delayed.space","threadId":"65032","inReplyTo":"20260220230633.132213-1-mroik@delayed.space","subject":"[PATCH 3/3] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T04:03:58Z","receivedAt":"2026-02-24T04:04:29Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Using \"--cover-letter\" we can tell format-patch to generate a cover\nletter, in this cover letter there's a list of commits included in the\npatch series and the format is specified by the \"--cover-letter-format\"\noption. Would be useful if this format could be configured from the\nconfig file instead of always needing to pass it from the command line.\n\nTeach format-patch how to read the format spec for the cover letter from\nthe config files. The variable it should look for is called\n\"commitListFormat\".\n\nIf commitListFormat is set but not string is passed, it will use the\n\"%s\" format spec, if a string is passed will use it as a format spec, if\nit is not set at all it will default to the shortlog format.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 80ec79efe8..7f41c83e18 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -886,6 +886,7 @@ struct format_config {\n \tchar *signature;\n \tchar *signature_file;\n \tenum cover_setting config_cover_letter;\n+\tchar* fmt_cover_letter_commit_list;\n \tchar *config_output_directory;\n \tenum cover_from_description cover_from_description_mode;\n \tint show_notes;\n@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n \tstring_list_clear(&cfg->extra_cc, 0);\n \tstrbuf_release(&cfg->sprefix);\n \tfree(cfg->fmt_patch_suffix);\n+\tfree(cfg->fmt_cover_letter_commit_list);\n }\n \n static enum cover_from_description parse_cover_from_description(const char *arg)\n@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"format.commitlistformat\")) {\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\t\tstrbuf_init(&tmp, 0);\n+\t\tstrbuf_addstr(&tmp, \"log:\");\n+\t\tif (value)\n+\t\t\tstrbuf_addstr(&tmp, value);\n+\t\telse\n+\t\t\tstrbuf_addstr(&tmp, \"%s\");\n+\n+\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"format.outputdirectory\")) {\n \t\tFREE_AND_NULL(cfg->config_output_directory);\n \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n@@ -2321,6 +2336,13 @@ int cmd_format_patch(int argc,\n \t\tgoto done;\n \ttotal = list.nr;\n \n+\tif (cover_letter_fmt && (strcmp(cover_letter_fmt, \"shortlog\") && strncmp(cover_letter_fmt, \"log:\", 4))) {\n+\t\tdie(_(\"--cover-letter: invalid format spec\"));\n+\t}\n+\n+\tif (!cover_letter_fmt)\n+\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = total > 1;\n-- \n2.53.0.4.geaa3cc5f7e\n\n"},{"id":"536909","messageId":"aZ0jdMHoSMwXpntp@exploit","threadId":"65032","inReplyTo":"20260224040400.751247-1-mroik@delayed.space","subject":"Re: [PATCH 0/3] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T04:06:23Z","receivedAt":"2026-02-24T04:06:26Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Sorry, I forgot to thread it\n"},{"id":"536919","messageId":"xmqqcy1uk69o.fsf@gitster.g","threadId":"65032","inReplyTo":"20260224040400.751247-2-mroik@delayed.space","subject":"Re: [PATCH 1/3] pretty.c: fix null pointer dereference","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-24T06:25:07Z","receivedAt":"2026-02-24T06:25:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> commit_format_is_empty() is used to check whether \"user_format\" is set\n> to a value. Unfortunately this function crashes the program if no\n> user_format is set. This is because instead of checking for the pointer\n> value it checks for its dereferenced value, this being NULL if\n> user_format is not set.\n>\n> Teach the proper condition to check if user_format is set.\n>\n> Signed-off-by: Mirko Faina <mroik@delayed.space>\n> ---\n>  pretty.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n\nInteresting.\n\nAre any of the existing calls to this function that passes\nCMIT_FMT_USERFORMAT trigger a segfault with certain condition?\n\nFor example, there are only two places where CMIT_FMT_USERFORMAT is\nassigned to something.  One is save_user_format() where user_format\ngets a non NULL string before rev->commit_format gets assigned\nCMIT_FMT_USERFORMAT.  Another is git_pretty_formats_config() that\nparses configuration variables \"pretty.*\" and populate\ncommit_formats map.  This is later used in get_commit_format() and\nthat function always calls save_user_format() we just saw when the\nformat used is CMIT_FMT_USERFORMAT.  So the existing code paths seem\nto be safe by design.\n\nWhat I am wondering is if a NULL user_format should be flagged as a\nprogramming error, instead of getting swept under the rug like this\npatch does.  IOW,\n\n\tint commit_format_is_empty(enum cmit_fmt fmt)\n\t{\n\t\tif (fmt != CMIT_FMT_USERFORMAT)\n\t\t\treturn 0;\n\t\tif (!user_format)\n\t\t\tBUG(\"never called save_user_format() and using USERFORMAT?\");\n\t\treturn !*user_format;\n\t}\n\n\n\n\n\n>\n> diff --git a/pretty.c b/pretty.c\n> index e0646bbc5d..cdb8bf559d 100644\n> --- a/pretty.c\n> +++ b/pretty.c\n> @@ -47,7 +47,7 @@ static struct cmt_fmt_map *find_commit_format(const char *sought);\n>  \n>  int commit_format_is_empty(enum cmit_fmt fmt)\n>  {\n> -\treturn fmt == CMIT_FMT_USERFORMAT && !*user_format;\n> +\treturn fmt == CMIT_FMT_USERFORMAT && !user_format;\n>  }\n>  \n>  static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)\n"},{"id":"536928","messageId":"aZ1JND7sGspCJEoc@exploit","threadId":"65032","inReplyTo":"xmqqcy1uk69o.fsf@gitster.g","subject":"Re: [PATCH 1/3] pretty.c: fix null pointer dereference","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T07:08:35Z","receivedAt":"2026-02-24T07:08:40Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Mon, Feb 23, 2026 at 10:25:07PM -0800, Junio C Hamano wrote:\n> Interesting.\n> \n> Are any of the existing calls to this function that passes\n> CMIT_FMT_USERFORMAT trigger a segfault with certain condition?\n> \n> For example, there are only two places where CMIT_FMT_USERFORMAT is\n> assigned to something.  One is save_user_format() where user_format\n> gets a non NULL string before rev->commit_format gets assigned\n> CMIT_FMT_USERFORMAT.  Another is git_pretty_formats_config() that\n> parses configuration variables \"pretty.*\" and populate\n> commit_formats map.  This is later used in get_commit_format() and\n> that function always calls save_user_format() we just saw when the\n> format used is CMIT_FMT_USERFORMAT.  So the existing code paths seem\n> to be safe by design.\n> \n> What I am wondering is if a NULL user_format should be flagged as a\n> programming error, instead of getting swept under the rug like this\n> patch does.  IOW,\n> \n> \tint commit_format_is_empty(enum cmit_fmt fmt)\n> \t{\n> \t\tif (fmt != CMIT_FMT_USERFORMAT)\n> \t\t\treturn 0;\n> \t\tif (!user_format)\n> \t\t\tBUG(\"never called save_user_format() and using USERFORMAT?\");\n> \t\treturn !*user_format;\n> \t}\n\nThis doesn't convince me, I think it is a bug. If dereferencing NULL was\ndone on purpose why not use die() instead? Also, the only way for us to\ncheck user_format is through commit_format_is_empty() as it is static.\nIn a complex config setup it might be useful to double check just to be\nsure, and I wouldn't want the program to crash on a failed check.\n\nsave_user_format() is not available neither, so evaluation of the format\nstring has to be done through get_commit_format(). If I pass any of the\npredefined formats (CMIT_FMT_*) get_commit_format() won't set\nuser_format. So the only way for us to check if it was set is through\ncommit_format_is_empty() (well technically there's\nrev_info->commit_format but it still doesn't feel like it was\nintentional).\n\nBut if the intended behaviour was for the program to crash then I take\nissue with the name.\n"},{"id":"536929","messageId":"aZ1VUDSc7LUDC-Ty@exploit","threadId":"65032","inReplyTo":"aZ1JND7sGspCJEoc@exploit","subject":"Re: [PATCH 1/3] pretty.c: fix null pointer dereference","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T07:43:22Z","receivedAt":"2026-02-24T07:43:25Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"> void get_commit_format(const char *arg, struct rev_info *rev)\n> {\n> \tstruct cmt_fmt_map *commit_format;\n> \n> \trev->use_terminator = 0;\n> \tif (!arg) {\n> \t\trev->commit_format = CMIT_FMT_DEFAULT;\n> \t\treturn;\n> \t}\n> \tif (skip_prefix(arg, \"format:\", &arg)) {\n> \t\tsave_user_format(rev, arg, 0);\n> \t\treturn;\n> \t}\n> \n> \tif (!*arg || skip_prefix(arg, \"tformat:\", &arg) || strchr(arg, '%')) {\n\nSeeing how \"arg\" is being handled here I am now unsure if it is actually\na bug.\n\nCC'ing Jeff for clarifications\n\n> \t\tsave_user_format(rev, arg, 1);\n> \t\treturn;\n> \t}\n\n"},{"id":"536935","messageId":"20260224084149.GA986367@coredump.intra.peff.net","threadId":"65032","inReplyTo":"aZ1JND7sGspCJEoc@exploit","subject":"Re: [PATCH 1/3] pretty.c: fix null pointer dereference","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-02-24T08:41:49Z","receivedAt":"2026-02-24T08:41:53Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 24, 2026 at 08:08:35AM +0100, Mirko Faina wrote:\n\n> On Mon, Feb 23, 2026 at 10:25:07PM -0800, Junio C Hamano wrote:\n> > Interesting.\n> > \n> > Are any of the existing calls to this function that passes\n> > CMIT_FMT_USERFORMAT trigger a segfault with certain condition?\n> > \n> > For example, there are only two places where CMIT_FMT_USERFORMAT is\n> > assigned to something.  One is save_user_format() where user_format\n> > gets a non NULL string before rev->commit_format gets assigned\n> > CMIT_FMT_USERFORMAT.  Another is git_pretty_formats_config() that\n> > parses configuration variables \"pretty.*\" and populate\n> > commit_formats map.  This is later used in get_commit_format() and\n> > that function always calls save_user_format() we just saw when the\n> > format used is CMIT_FMT_USERFORMAT.  So the existing code paths seem\n> > to be safe by design.\n> > \n> > What I am wondering is if a NULL user_format should be flagged as a\n> > programming error, instead of getting swept under the rug like this\n> > patch does.  IOW,\n> > \n> > \tint commit_format_is_empty(enum cmit_fmt fmt)\n> > \t{\n> > \t\tif (fmt != CMIT_FMT_USERFORMAT)\n> > \t\t\treturn 0;\n> > \t\tif (!user_format)\n> > \t\t\tBUG(\"never called save_user_format() and using USERFORMAT?\");\n> > \t\treturn !*user_format;\n> > \t}\n> \n> This doesn't convince me, I think it is a bug. If dereferencing NULL was\n> done on purpose why not use die() instead? Also, the only way for us to\n> check user_format is through commit_format_is_empty() as it is static.\n> In a complex config setup it might be useful to double check just to be\n> sure, and I wouldn't want the program to crash on a failed check.\n> \n> save_user_format() is not available neither, so evaluation of the format\n> string has to be done through get_commit_format(). If I pass any of the\n> predefined formats (CMIT_FMT_*) get_commit_format() won't set\n> user_format. So the only way for us to check if it was set is through\n> commit_format_is_empty() (well technically there's\n> rev_info->commit_format but it still doesn't feel like it was\n> intentional).\n> \n> But if the intended behaviour was for the program to crash then I take\n> issue with the name.\n\nI am not quite sure what you are asking. No, the intent of that function\nis not to crash. It is to check whether the user passed us an empty\nstring. Like the \"git diff-tree --format= $commit\" example given\nin the commit message of b9c7d6e433 (pretty: make empty userformats\ntruly empty, 2014-07-29). If they did, then the first character of the\nstring will be the NUL terminator.\n\nSo dropping the \"*\" as your patch 1/3 does is just wrong. It is losing\nthe check for an empty string and replacing it with a check for a NULL\npointer.\n\nThe user_format string should never be NULL if we are using\nCMIT_FMT_USERFORMAT. That's not checked for explicitly here, but is an\nassumption of the pretty.c code. If there's some way to violate that\nassumption, that's a bug (but it sounds from digging that there isn't).\n\nIf you have _new_ code which is using CMIT_FMT_USERFORMAT without\nsetting user_format to a non-NULL value, we might need to work around\nthat assumption. But I think what your 2/3 is doing is not quite at the\nright level, which is the source of the trouble. I'll respond separately\nto that patch.\n\n-Peff\n"},{"id":"536945","messageId":"20260224090207.GB986367@coredump.intra.peff.net","threadId":"65032","inReplyTo":"20260224040400.751247-3-mroik@delayed.space","subject":"Re: [PATCH 2/3] format-patch: add ability to use alt cover format","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-02-24T09:02:07Z","receivedAt":"2026-02-24T09:02:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 24, 2026 at 05:03:57AM +0100, Mirko Faina wrote:\n\n> +static void generate_commit_list_cover(FILE *cover_file, const char *format, struct commit **list, int n)\n> +{\n\nOK, so we're expecting \"format\" here to be the full format string here\n(so \"%s\" or whatever).\n\nBut here...\n\n> +\tget_commit_format(format, &rev);\n\n...this isn't quite the function you want to parse it. This function is\nmore about parsing the --pretty option for git-log, etc. It allows named\nformats like \"oneline\", \"medium\", and so on, as well as \"format:%s\" (and\njust \"%s\", as we treat unknown names with a percent as if they had\ntformat: prepended).\n\nAs a side effect, it sets up the global user_format variable. Which is\nhorrible and subtle, but a result of historical function interfaces.\nI'll get to that at the bottom of this email.\n\nBack to your patch, I guess here:\n\n> +\tif (commit_format_is_empty(CMIT_FMT_USERFORMAT))\n> +\t\tdie(_(\"invalid format spec\"));\n\nyou are trying to check if we got a USERFORMAT, versus something else. A\nmore direct way to check that would be:\n\n  if (rev->commit_format != CMIT_FMT_USERFORMAT)\n\nBut if we do not use get_commit_format() in the first place, we don't\nneed to worry about that. And I guess you used it because of:\n\n> +\t\tpp_commit_easy(CMIT_FMT_USERFORMAT, list[i], &commit_line);\n\nthat line, which is using the full-on pretty-print system that git-log,\netc, use. It doesn't really work with CMIT_FMT_USERFORMAT because you\nhave to set up the global user_format first (for those same historical\nreasons).\n\nSo I think the entry point you want is repo_format_commit_message(),\nwhich does a one-off custom format into a strbuf without needing a\nrev_info, or touching the global user_format, etc.\n\nSomething like this:\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 5e9e337be4..370367a15a 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1326,16 +1326,12 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n static void generate_commit_list_cover(FILE *cover_file, const char *format, struct commit **list, int n)\n {\n \tstruct strbuf commit_line = STRBUF_INIT;\n-\tstruct rev_info rev = REV_INFO_INIT;\n-\n-\tstrbuf_init(&commit_line, 0);\n-\tget_commit_format(format, &rev);\n-\tif (commit_format_is_empty(CMIT_FMT_USERFORMAT))\n-\t\tdie(_(\"invalid format spec\"));\n+\tstruct pretty_print_context ctx = {0};\n \n \tfor (int i = n - 1; i >= 0; i--) {\n \t\tstrbuf_addf(&commit_line, \"[%0*d/%d] \", decimal_width(n), n - i, n);\n-\t\tpp_commit_easy(CMIT_FMT_USERFORMAT, list[i], &commit_line);\n+\t\trepo_format_commit_message(the_repository, list[i],\n+\t\t\t\t\t   format, &commit_line, &ctx);\n \t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n \t\tstrbuf_reset(&commit_line);\n \t}\n\n\nI think that should make your series do what you want. Now...is the\npretty-print code a horrible minefield of booby traps waiting to spring\non the unwary developer? Yes, it is. ;)\n\nThe global user_format thing is there because it was bolted onto the\nexisting pretty-print code, which used a single enum to store the\nformat. But that enum isn't enough for a user-format, because we also\nhave an associated string. The \"right\" type is probably something like:\n\n  struct commit_format {\n\tenum cmit_fmt fmt;\n\tconst char *user_format;\n  };\n\nBut C being what it is, switching all of the functions to that breaks\nall of the callers which just want to pass CMIT_FMT_ONELINE or whatever.\nSo we have sort of a split world, where you can use pp_commit_easy()\nwith the baked-in formats, and using the custom format code uses a\ntotally different function.\n\nI think in the long run we probably do need to clean up the global\nuser_format. Two pieces of code interleaving calls to the pretty-printer\nwould stomp on each other's formats, for example. We've mostly gotten by\nbecause Git, being organized as a set of small programs, generally has\none \"main\" output (for git-log, etc) and then any smaller outputs within\na program are done by one-off calls to repo_format_commit_message(),\netc.\n\n-Peff\n"},{"id":"536946","messageId":"aZ1qLtteLECvnY4R@exploit","threadId":"65032","inReplyTo":"20260224090207.GB986367@coredump.intra.peff.net","subject":"Re: [PATCH 2/3] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T09:09:39Z","receivedAt":"2026-02-24T09:09:43Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Thank you for taking time to explain more in depth. I'm sorry for having\nCC'd you for such a trivial matter, I just didn't realise it was\npossible for an empty string to make it there, I assumed it would've\nbeen checked earlier.\n\nNow I understand what Junio was trying to say. I'll apply the changes\nand drop the first patch.\n"},{"id":"536950","messageId":"20260224091849.GA994347@coredump.intra.peff.net","threadId":"65032","inReplyTo":"aZ1qLtteLECvnY4R@exploit","subject":"Re: [PATCH 2/3] format-patch: add ability to use alt cover format","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-02-24T09:18:49Z","receivedAt":"2026-02-24T09:18:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 24, 2026 at 10:09:39AM +0100, Mirko Faina wrote:\n\n> Thank you for taking time to explain more in depth. I'm sorry for having\n> CC'd you for such a trivial matter, I just didn't realise it was\n> possible for an empty string to make it there, I assumed it would've\n> been checked earlier.\n\nIt is my penance for having been involved in the mess that is pretty.c\nin the first place. ;)\n\n-Peff\n"},{"id":"536951","messageId":"cover.1771925291.git.mroik@delayed.space","threadId":"65032","inReplyTo":"20260224040400.751247-1-mroik@delayed.space","subject":"[PATCH v2 0/2] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T09:29:00Z","receivedAt":"2026-02-24T09:29:17Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"From: Mroik <mroik@delayed.space>\n\nI've dropped the first patch of the series and applied the changes that\nJeff suggested.\n\nThank you both for the review.\n\n[1/2] format-patch: add ability to use alt cover format (Mirko Faina)\n[2/2] format-patch: add commitListFormat config (Mirko Faina)\n\n builtin/log.c | 87 +++++++++++++++++++++++++++++++++++++++++----------\n 1 file changed, 71 insertions(+), 16 deletions(-)\n\n\nbase-commit: a8e89346a7731cb3104010f322c65e2a0c922618\n-- \n2.53.0.3.g6a0c7aecfd\n\n"},{"id":"536952","messageId":"66cac565f8a40f8de3dc3d857feb681bb80cb136.1771925291.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1771925291.git.mroik@delayed.space","subject":"[PATCH v2 1/2] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T09:29:01Z","receivedAt":"2026-02-24T09:29:17Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nGive the ability to format-patch to specify an alternative format spec\nthrough the \"--cover-letter-format\" option. This option either takes\n\"shortlog\", which is the current format, or a format spec prefixed with\n\"log:\".\n\nExample:\n    git format-patch --cover-letter \\\n        --cover-letter-format=\"log:%s (%an)\" HEAD~3\n\n    [1/3] this is a commit summary (Mirko Faina)\n    [2/3] this is another commit summary (Mirko Faina)\n    ...\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 65 ++++++++++++++++++++++++++++++++++++++-------------\n 1 file changed, 49 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex c1cd3999a7..5e99660d7c 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1324,13 +1324,32 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_commit_list_cover(FILE *cover_file,const char *format,\n+\t\t\t\t       struct commit **list, int n)\n+{\n+\tstruct strbuf commit_line = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\n+\tstrbuf_init(&commit_line, 0);\n+\tfor (int i = n - 1; i >= 0; i--) {\n+\t\tstrbuf_addf(&commit_line, \"[%0*d/%d] \", decimal_width(n), n - i, n);\n+\t\trepo_format_commit_message(the_repository, list[i], format, &commit_line, &ctx);\n+\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n+\t\tstrbuf_reset(&commit_line);\n+\t}\n+\tfprintf(cover_file, \"\\n\");\n+\n+\tstrbuf_release(&commit_line);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n \t\t\t      const char *description_file,\n \t\t\t      const char *branch_name,\n \t\t\t      int quiet,\n-\t\t\t      const struct format_config *cfg)\n+\t\t\t      const struct format_config *cfg,\n+\t\t\t      const char *format)\n {\n \tconst char *from;\n \tstruct shortlog log;\n@@ -1342,6 +1361,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tstruct commit *head = list[0];\n \tchar *to_free = NULL;\n \n+\tassert(format);\n+\n \tif (!cmit_fmt_is_mail(rev->commit_format))\n \t\tdie(_(\"cover letter needs email format\"));\n \n@@ -1377,18 +1398,22 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n+\tif (skip_prefix(format, \"log:\", &format)) {\n+\t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n+\t} else {\n+\t\tshortlog_init(&log);\n+\t\tlog.wrap_lines = 1;\n+\t\tlog.wrap = MAIL_DEFAULT_WRAP;\n+\t\tlog.in1 = 2;\n+\t\tlog.in2 = 4;\n+\t\tlog.file = rev->diffopt.file;\n+\t\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n+\t\tshortlog_finish_setup(&log);\n+\t\tfor (i = 0; i < nr; i++)\n+\t\t\tshortlog_add_commit(&log, list[i]);\n \n-\tshortlog_output(&log);\n+\t\tshortlog_output(&log);\n+\t}\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n@@ -1906,6 +1931,7 @@ int cmd_format_patch(int argc,\n \tint just_numbers = 0;\n \tint ignore_if_in_upstream = 0;\n \tint cover_letter = -1;\n+\tchar *cover_letter_fmt = NULL;\n \tint boundary_count = 0;\n \tint no_binary_diff = 0;\n \tint zero_commit = 0;\n@@ -1952,6 +1978,8 @@ int cmd_format_patch(int argc,\n \t\t\t    N_(\"print patches to standard out\")),\n \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n \t\t\t    N_(\"generate a cover letter\")),\n+\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n+\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n \t\t\t    N_(\"use simple number sequence for output file names\")),\n \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n@@ -2289,13 +2317,14 @@ int cmd_format_patch(int argc,\n \t\t/* nothing to do */\n \t\tgoto done;\n \ttotal = list.nr;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n-\t\t\tcover_letter = (total > 1);\n+\t\t\tcover_letter = total > 1;\n \t\telse if ((idiff_prev.nr || rdiff_prev) && (total > 1))\n-\t\t\tcover_letter = (cfg.config_cover_letter != COVER_OFF);\n+\t\t\tcover_letter = cfg.config_cover_letter != COVER_OFF;\n \t\telse\n-\t\t\tcover_letter = (cfg.config_cover_letter == COVER_ON);\n+\t\t\tcover_letter = cfg.config_cover_letter == COVER_ON;\n \t}\n \tif (!cfg.keep_subject && cfg.auto_number && (total > 1 || cover_letter))\n \t\tcfg.numbered = 1;\n@@ -2375,12 +2404,16 @@ int cmd_format_patch(int argc,\n \t}\n \trev.numbered_files = just_numbers;\n \trev.patch_suffix = fmt_patch_suffix;\n+\n+\tif (cover_letter && !cover_letter_fmt)\n+\t\tcover_letter_fmt = \"shortlog\";\n+\n \tif (cover_letter) {\n \t\tif (cfg.thread)\n \t\t\tgen_message_id(&rev, \"cover\");\n \t\tmake_cover_letter(&rev, !!output_directory,\n \t\t\t\t  origin, list.nr, list.items,\n-\t\t\t\t  description_file, branch_name, quiet, &cfg);\n+\t\t\t\t  description_file, branch_name, quiet, &cfg, cover_letter_fmt);\n \t\tprint_bases(&bases, rev.diffopt.file);\n \t\tprint_signature(signature, rev.diffopt.file);\n \t\ttotal++;\n-- \n2.53.0.3.g6a0c7aecfd\n\n"},{"id":"536953","messageId":"6a0c7aecfd6dc1ee873d5e81110b723fa2d225fb.1771925291.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1771925291.git.mroik@delayed.space","subject":"[PATCH v2 2/2] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T09:29:02Z","receivedAt":"2026-02-24T09:29:17Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Using \"--cover-letter\" we can tell format-patch to generate a cover\nletter, in this cover letter there's a list of commits included in the\npatch series and the format is specified by the \"--cover-letter-format\"\noption. Would be useful if this format could be configured from the\nconfig file instead of always needing to pass it from the command line.\n\nTeach format-patch how to read the format spec for the cover letter from\nthe config files. The variable it should look for is called\n\"commitListFormat\".\n\nIf commitListFormat is set but not string is passed, it will use the\n\"%s\" format spec, if a string is passed will use it as a format spec, if\nit is not set at all it will default to the shortlog format.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 5e99660d7c..725ebf13f6 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -886,6 +886,7 @@ struct format_config {\n \tchar *signature;\n \tchar *signature_file;\n \tenum cover_setting config_cover_letter;\n+\tchar* fmt_cover_letter_commit_list;\n \tchar *config_output_directory;\n \tenum cover_from_description cover_from_description_mode;\n \tint show_notes;\n@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n \tstring_list_clear(&cfg->extra_cc, 0);\n \tstrbuf_release(&cfg->sprefix);\n \tfree(cfg->fmt_patch_suffix);\n+\tfree(cfg->fmt_cover_letter_commit_list);\n }\n \n static enum cover_from_description parse_cover_from_description(const char *arg)\n@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"format.commitlistformat\")) {\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\t\tstrbuf_init(&tmp, 0);\n+\t\tstrbuf_addstr(&tmp, \"log:\");\n+\t\tif (value)\n+\t\t\tstrbuf_addstr(&tmp, value);\n+\t\telse\n+\t\t\tstrbuf_addstr(&tmp, \"%s\");\n+\n+\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"format.outputdirectory\")) {\n \t\tFREE_AND_NULL(cfg->config_output_directory);\n \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n@@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,\n \t\tgoto done;\n \ttotal = list.nr;\n \n+\tif (cover_letter_fmt && (strcmp(cover_letter_fmt, \"shortlog\") && strncmp(cover_letter_fmt, \"log:\", 4))) {\n+\t\tdie(_(\"--cover-letter: invalid format spec\"));\n+\t}\n+\n+\tif (!cover_letter_fmt)\n+\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = total > 1;\n-- \n2.53.0.3.g6a0c7aecfd\n\n"},{"id":"536985","messageId":"xmqqpl5uhwex.fsf@gitster.g","threadId":"65032","inReplyTo":"66cac565f8a40f8de3dc3d857feb681bb80cb136.1771925291.git.mroik@delayed.space","subject":"Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-24T17:40:54Z","receivedAt":"2026-02-24T17:40:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Often when sending patch series there's a need to clarify to the\n> reviewer what's the purpose of said series, since it might be difficult\n> to understand it from reading the commits messages one by one.\n>\n> \"git format-patch\" provides the useful \"--cover-letter\" flag to declare\n> if we want it to generate a template for us to use. By default it will\n> generate a \"git shortlog\" of the changes, which developers find less\n> useful than they'd like, mainly because the shortlog groups commits by\n> author, and gives no obvious chronological order.\n>\n> Give the ability to format-patch to specify an alternative format spec\n> through the \"--cover-letter-format\" option. This option either takes\n> \"shortlog\", which is the current format, or a format spec prefixed with\n> \"log:\".\n>\n> Example:\n>     git format-patch --cover-letter \\\n>         --cover-letter-format=\"log:%s (%an)\" HEAD~3\n>\n>     [1/3] this is a commit summary (Mirko Faina)\n>     [2/3] this is another commit summary (Mirko Faina)\n>     ...\n>\n> Signed-off-by: Mirko Faina <mroik@delayed.space>\n> ---\n>  builtin/log.c | 65 ++++++++++++++++++++++++++++++++++++++-------------\n>  1 file changed, 49 insertions(+), 16 deletions(-)\n>\n> diff --git a/builtin/log.c b/builtin/log.c\n> index c1cd3999a7..5e99660d7c 100644\n> --- a/builtin/log.c\n> +++ b/builtin/log.c\n> @@ -1324,13 +1324,32 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n>  \t}\n>  }\n>  \n> +static void generate_commit_list_cover(FILE *cover_file,const char *format,\n\n\"cover_file,const\" -> \"cover_file, const\" (missing SP).\n\n> +\t\t\t\t       struct commit **list, int n)\n> +{\n> +\tstruct strbuf commit_line = STRBUF_INIT;\n> +\tstruct pretty_print_context ctx = {0};\n> +\n> +\tstrbuf_init(&commit_line, 0);\n\nSo a single commit_line is given to repo_format_commit_message()\nrepeatedly to accumulate those lines in it, which makes sense.\n\nWe prepare pretty_print_context once above and feed it repeatedly to\nrepo_format_commit_message()---is that intended?  Not a rhetorical\nquestion; I do not know the answer.  I guess some existing callers\n(like builtin/archive.c) do reuse the structure when making multiple\ncalls to the function, so this would be kosher, perhaps?\n\n> +\tfor (int i = n - 1; i >= 0; i--) {\n> +\t\tstrbuf_addf(&commit_line, \"[%0*d/%d] \", decimal_width(n), n - i, n);\n> +\t\trepo_format_commit_message(the_repository, list[i], format, &commit_line, &ctx);\n\nLet's line-wrap this overly long line (my lithmus test to complain\nabout \"overly long lines\" is after losing three columns to the left\nfor \"> +\" in e-mail quote like the above the right edge of the line\ndoes not fit on my 92-column terminal, which will never happen if\nyou stick to the official \"fit 80-column after quoted for a few\ntimes in e-mail\" guideline).\n\n\t\trepo_format_commit_message(the_repository, list[i], format,\n\t\t\t\t\t   &commit_line, &ctx);\n\nperhaps.\n\n> +\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n\nI somehow would have expected that as we internally prepare \"format\"\nstring given to this function , we ensure it ends with \"\\n\" so we do\nnot have to do a fprintf() here.\n\n> +\t\tstrbuf_reset(&commit_line);\n> +\t}\n> +\tfprintf(cover_file, \"\\n\");\n\nOK, so we have a blank line after a block of one line per commit.\nSensible.\n\n> +\tstrbuf_release(&commit_line);\n> +}\n> +\n>  static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n>  \t\t\t      struct commit *origin,\n>  \t\t\t      int nr, struct commit **list,\n>  \t\t\t      const char *description_file,\n>  \t\t\t      const char *branch_name,\n>  \t\t\t      int quiet,\n> -\t\t\t      const struct format_config *cfg)\n> +\t\t\t      const struct format_config *cfg,\n> +\t\t\t      const char *format)\n>  {\n>  \tconst char *from;\n>  \tstruct shortlog log;\n> @@ -1342,6 +1361,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n>  \tstruct commit *head = list[0];\n>  \tchar *to_free = NULL;\n>  \n> +\tassert(format);\n> +\n\nCurious.  We do not assert() for any other pointer arguments.  What\nmakes this so special?\n\n>  \tif (!cmit_fmt_is_mail(rev->commit_format))\n>  \t\tdie(_(\"cover letter needs email format\"));\n>  \n> @@ -1377,18 +1398,22 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n>  \tfree(pp.after_subject);\n>  \tstrbuf_release(&sb);\n>  \n> -\tshortlog_init(&log);\n> -\tlog.wrap_lines = 1;\n> -\tlog.wrap = MAIL_DEFAULT_WRAP;\n> -\tlog.in1 = 2;\n> -\tlog.in2 = 4;\n> -\tlog.file = rev->diffopt.file;\n> -\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n> -\tshortlog_finish_setup(&log);\n> -\tfor (i = 0; i < nr; i++)\n> -\t\tshortlog_add_commit(&log, list[i]);\n\nIt would have been much nicer to first create a short helper\nfunction generate_shortlog_cover(), move the above plus a call to\nshortlog_output(&log) to that helper function, without doing\nanything else and make it a preliminary patch [1/n].  Then introduce\nthe corresponding generate_commit_list_cover() function in patch\n[2/n] (which is what this step is doing), which would have resulted\nin the body of the make_cover_letter() around here a short-and-sweet\n\n\tif (... we are doing shortlog style ...)\n\t\tgenerate_shortlog_cover(...);\n\telse\n\t\tgenerate_commit_list_cover(...);\n\nJust like readers of _this_ function are helped by not having to\nknow the details of what happens inside commit-list style cover\ngeneration, they can concentratre on the flow without having to care\nabout details on shortlog side that way.\n\n> @@ -1906,6 +1931,7 @@ int cmd_format_patch(int argc,\n>  \tint just_numbers = 0;\n>  \tint ignore_if_in_upstream = 0;\n>  \tint cover_letter = -1;\n> +\tchar *cover_letter_fmt = NULL;\n>  \tint boundary_count = 0;\n>  \tint no_binary_diff = 0;\n>  \tint zero_commit = 0;\n> @@ -1952,6 +1978,8 @@ int cmd_format_patch(int argc,\n>  \t\t\t    N_(\"print patches to standard out\")),\n>  \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n>  \t\t\t    N_(\"generate a cover letter\")),\n> +\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n> +\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n>  \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n>  \t\t\t    N_(\"use simple number sequence for output file names\")),\n>  \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n> @@ -2289,13 +2317,14 @@ int cmd_format_patch(int argc,\n>  \t\t/* nothing to do */\n>  \t\tgoto done;\n>  \ttotal = list.nr;\n> +\n\nWhat is this churn about?\n\n>  \tif (cover_letter == -1) {\n>  \t\tif (cfg.config_cover_letter == COVER_AUTO)\n> -\t\t\tcover_letter = (total > 1);\n> +\t\t\tcover_letter = total > 1;\n\nWhat is this churn about?\n\n>  \t\telse if ((idiff_prev.nr || rdiff_prev) && (total > 1))\n> -\t\t\tcover_letter = (cfg.config_cover_letter != COVER_OFF);\n> +\t\t\tcover_letter = cfg.config_cover_letter != COVER_OFF;\n\nWhat is this churn about?\n\n>  \t\telse\n> -\t\t\tcover_letter = (cfg.config_cover_letter == COVER_ON);\n> +\t\t\tcover_letter = cfg.config_cover_letter == COVER_ON;\n\nWhat is this churn about?\n\nPlease do not distract reviewers by mixing immaterial \"style fixes\"\non existing code to a patch whose primary purpose is to introduce\nnew code.  A separate \"preliminary fix and/or clean-up\" patch before\nthe main series begins is often a welcome addition, though.\n\n\n> @@ -2375,12 +2404,16 @@ int cmd_format_patch(int argc,\n>  \t}\n>  \trev.numbered_files = just_numbers;\n>  \trev.patch_suffix = fmt_patch_suffix;\n> +\n> +\tif (cover_letter && !cover_letter_fmt)\n> +\t\tcover_letter_fmt = \"shortlog\";\n> +\n\nI do not quite see the point of doing this.  There is no law that\n\"cover_letter_fmt != NULL\" is a crime when cover_letter is false.\n\ncover_letter_fmt can be initialized to a fixed string \"shortlog\",\nand nobody cares what random value cover_letter_fmt has when\ncover_letter is false.\n\n>  \tif (cover_letter) {\n>  \t\tif (cfg.thread)\n>  \t\t\tgen_message_id(&rev, \"cover\");\n>  \t\tmake_cover_letter(&rev, !!output_directory,\n>  \t\t\t\t  origin, list.nr, list.items,\n> -\t\t\t\t  description_file, branch_name, quiet, &cfg);\n> +\t\t\t\t  description_file, branch_name, quiet, &cfg, cover_letter_fmt);\n\nThis line has become overly long.  Please wrap it.\n\n>  \t\tprint_bases(&bases, rev.diffopt.file);\n>  \t\tprint_signature(signature, rev.diffopt.file);\n>  \t\ttotal++;\n\nIn any case, the implementation in this iteration looks much nicer\nthan the previous one.\n\nNew set of tests should come together with implementation of a new\nfeature in the same patch.\n\nThanks.\n"},{"id":"536986","messageId":"xmqqqzqaggln.fsf@gitster.g","threadId":"65032","inReplyTo":"6a0c7aecfd6dc1ee873d5e81110b723fa2d225fb.1771925291.git.mroik@delayed.space","subject":"Re: [PATCH v2 2/2] format-patch: add commitListFormat config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-24T18:07:48Z","receivedAt":"2026-02-24T18:07:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> +\tchar* fmt_cover_letter_commit_list;\n\nIn this project, asterisk sticks to the variable, not the type,\ni.e.,\n\n\tchar *fmt_cover_letter_commit_list;\n\nI think you got this point right in the previous patch.\n\n> @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n>  \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n>  \t\treturn 0;\n>  \t}\n> +\tif (!strcmp(var, \"format.commitlistformat\")) {\n> +\t\tstruct strbuf tmp = STRBUF_INIT;\n> +\t\tstrbuf_init(&tmp, 0);\n> +\t\tstrbuf_addstr(&tmp, \"log:\");\n> +\t\tif (value)\n> +\t\t\tstrbuf_addstr(&tmp, value);\n> +\t\telse\n> +\t\t\tstrbuf_addstr(&tmp, \"%s\");\n> +\n> +\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n\nWhat if /etc/gitconfig has \"[format] commitListFormat = shortlog\",\n~/.gitconfig has a different setting, and then .git/config has yet\nanother setting?  Woudln't cfg->fmt_cover_letter_commit_list at this\npoint have a copy of the value read from the previous configuration\nfile?  Without first freeing it, wouldn't we leak the previous value?\n\n    $ git grep -C2 git_config_string\\(\n\ngives plenty of precedence, like this one.\n\nbuiltin/commit.c-\tif (!strcmp(k, \"commit.cleanup\")) {\nbuiltin/commit.c-\t\tFREE_AND_NULL(cleanup_config);\nbuiltin/commit.c:\t\treturn git_config_string(&cleanup_config, k, v);\nbuiltin/commit.c-\t}\nbuiltin/commit.c-\tif (!strcmp(k, \"commit.gpgsign\")) {\n\n> +\t\tstrbuf_release(&tmp);\n> +\t\treturn 0;\n> +\t}\n>  \tif (!strcmp(var, \"format.outputdirectory\")) {\n>  \t\tFREE_AND_NULL(cfg->config_output_directory);\n>  \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n> @@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,\n>  \t\tgoto done;\n>  \ttotal = list.nr;\n>  \n> +\tif (cover_letter_fmt && (strcmp(cover_letter_fmt, \"shortlog\") && strncmp(cover_letter_fmt, \"log:\", 4))) {\n\nOverly long line.\n\nWhat if it turns out that the --cover-letter option is not given\n(and we are dealing with a single-patch topic, so auto setting has\ndecided that there is no need for cover letter)?  Shouldn't we\ncontinue ignoring the typo on a setting that we are not going to use\nanyway?\n\nStepping back a bit, even if we do not validate the format *here*,\nshouldn't the code that does use cover_letter_fmt later in the\ncontrol flow *already* be checking the validity of the format and\ncomplaining?  If that happens early enough, perhaps we do not want\nto have an extra \"early check and die\" here.\n\n> +\t\tdie(_(\"--cover-letter: invalid format spec\"));\n> +\t}\n> +\n> +\tif (!cover_letter_fmt)\n> +\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n\nAs I pointed out in my review of [1/2], it is not a crime to set a\nvalue to cover_letter_fmt even when !cover_letter, and the above\nline does exactly that ;-).\n\nBy the way, the usual technique used in this codebase when handing\nconfiguration and command line option is to these in this order:\n\n * Initialize a variable to the built-in hardcoded default (e.g.,\n   \"shortlog\") upon variable declaration.\n\n * Let repo_config() call overwrite that same variable.  This is the\n   typical implementation of \"if there is no configuration, we use\n   the hardcoded default, but the configured value can override it\".\n\n * Then parse_options() overwrites that same variable.\n\nBut because we read configuration into a separarte variable (i.e.,\nmembers of cfg structure), this function cannot literally follow the\nusual pattern.  But the pattern we instead can follow is this:\n\n\t/* initiailize to NULL */\n\tchar *cover_letter_fmt = NULL;\n\n        /* read configuration */\n        repo_config(... &cfg);\n\n\t/* cover_letter_fmt will point at command line arg */\n\tparse_options(...);\n\n        /* NULL if no command line argument */\n\tif (!cover_letter_fmt) {\n\t\t/* perhaps configuration has one */\n        \tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n\n                /* otherwise, use hardcoded default */\n                if (!cover_letter_fmt)\n                \tcover_letter_fmt = \"shortlog\";\n\t}\n\n"},{"id":"537014","messageId":"xmqqo6ldga89.fsf@gitster.g","threadId":"65032","inReplyTo":"66cac565f8a40f8de3dc3d857feb681bb80cb136.1771925291.git.mroik@delayed.space","subject":"Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-24T20:25:26Z","receivedAt":"2026-02-24T20:25:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> +\tchar *cover_letter_fmt = NULL;\n> ...\n> +\n> +\tif (cover_letter && !cover_letter_fmt)\n> +\t\tcover_letter_fmt = \"shortlog\";\n\nThe compiler flags the above assignment, which is understandable.\n\nbuiltin/log.c:2442:34: error: assignment discards 'const' qualifier from pointer target type [-Werror=discarded-qualifiers]\n 2442 |                 cover_letter_fmt = \"shortlog\";\n"},{"id":"537015","messageId":"xmqqjyw1g9m5.fsf@gitster.g","threadId":"65032","inReplyTo":"cover.1771925291.git.mroik@delayed.space","subject":"Re: [PATCH v2 0/2] format-patch: add cover-letter-format option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-24T20:38:42Z","receivedAt":"2026-02-24T20:38:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> From: Mroik <mroik@delayed.space>\n>\n> I've dropped the first patch of the series and applied the changes that\n> Jeff suggested.\n\n\nThese are queued somewhere in 'seen', with a small fix-up to have\nthe build pass plus a bit of obvious tweak on error handling, but\nmost of the remarks I made in my reviews (like \"doesn't this leak?\"\nand \"shouldn't we have tests\") are not addressed with the fix-up.\n\nYou'll find the topic in 'seen' after I push it out for today\nperhaps in a few hours.\n\n    a981554b03 format-patch: add ability to use alt cover format\n    8bf8e6ccda format-patch: add commitListFormat config\n    a85e8e535d SQUASH???\n\nThanks.\n"},{"id":"537024","messageId":"xmqq1pi9g6sb.fsf@gitster.g","threadId":"65032","inReplyTo":"xmqqjyw1g9m5.fsf@gitster.g","subject":"Re: [PATCH v2 0/2] format-patch: add cover-letter-format option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-24T21:39:48Z","receivedAt":"2026-02-24T21:39:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Mirko Faina <mroik@delayed.space> writes:\n>\n>> From: Mroik <mroik@delayed.space>\n>>\n>> I've dropped the first patch of the series and applied the changes that\n>> Jeff suggested.\n>\n>\n> These are queued somewhere in 'seen', with a small fix-up to have\n> the build pass plus a bit of obvious tweak on error handling, but\n> most of the remarks I made in my reviews (like \"doesn't this leak?\"\n> and \"shouldn't we have tests\") are not addressed with the fix-up.\n>\n> You'll find the topic in 'seen' after I push it out for today\n> perhaps in a few hours.\n>\n>     a981554b03 format-patch: add ability to use alt cover format\n>     8bf8e6ccda format-patch: add commitListFormat config\n>     a85e8e535d SQUASH???\n>\n> Thanks.\n\nI found another change that is needed to make the tests pass, so the\ncommit object name for the squash fixup is no longer a85e8e535d;\nfetching my 'seen' from any of the mirrors should let you find these\ncommits near the tip of 'seen'.\n\n\n\n\n builtin/log.c         | 18 ++++++++----------\n t/t9902-completion.sh |  1 +\n 2 files changed, 9 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex e6ff3627b8..c531784581 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1415,7 +1415,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \n \tif (skip_prefix(format, \"log:\", &format)) {\n \t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n-\t} else {\n+\t} else if (!strcmp(format, \"shortlog\")) {\n \t\tshortlog_init(&log);\n \t\tlog.wrap_lines = 1;\n \t\tlog.wrap = MAIL_DEFAULT_WRAP;\n@@ -1428,6 +1428,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\tshortlog_add_commit(&log, list[i]);\n \n \t\tshortlog_output(&log);\n+\t} else {\n+\t\tdie(_(\"--cover-letter: invalid format spec '%s'\"), format);\n \t}\n \n \t/* We can only do diffstat with a unique reference point */\n@@ -1946,7 +1948,7 @@ int cmd_format_patch(int argc,\n \tint just_numbers = 0;\n \tint ignore_if_in_upstream = 0;\n \tint cover_letter = -1;\n-\tchar *cover_letter_fmt = NULL;\n+\tconst char *cover_letter_fmt = NULL;\n \tint boundary_count = 0;\n \tint no_binary_diff = 0;\n \tint zero_commit = 0;\n@@ -2333,12 +2335,11 @@ int cmd_format_patch(int argc,\n \t\tgoto done;\n \ttotal = list.nr;\n \n-\tif (cover_letter_fmt && (strcmp(cover_letter_fmt, \"shortlog\") && strncmp(cover_letter_fmt, \"log:\", 4))) {\n-\t\tdie(_(\"--cover-letter: invalid format spec\"));\n-\t}\n-\n-\tif (!cover_letter_fmt)\n+\tif (!cover_letter_fmt) {\n \t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n+\t\tif (!cover_letter_fmt)\n+\t\t\tcover_letter_fmt = \"shortlog\";\n+\t}\n \n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n@@ -2427,9 +2428,6 @@ int cmd_format_patch(int argc,\n \trev.numbered_files = just_numbers;\n \trev.patch_suffix = fmt_patch_suffix;\n \n-\tif (cover_letter && !cover_letter_fmt)\n-\t\tcover_letter_fmt = \"shortlog\";\n-\n \tif (cover_letter) {\n \t\tif (cfg.thread)\n \t\t\tgen_message_id(&rev, \"cover\");\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 964e1f1569..4f760a7468 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '\n \ttest_completion \"git send-email --cov\" <<-\\EOF &&\n \t--cover-from-description=Z\n \t--cover-letter Z\n+\t--cover-letter-format=Z\n \tEOF\n \ttest_completion \"git send-email --val\" <<-\\EOF &&\n \t--validate Z\n-- \n2.53.0-485-gd48bbca10f\n\n"},{"id":"537040","messageId":"aZ4v6p_oKCayr9A7@exploit","threadId":"65032","inReplyTo":"xmqqpl5uhwex.fsf@gitster.g","subject":"Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-24T23:54:54Z","receivedAt":"2026-02-24T23:54:59Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Tue, Feb 24, 2026 at 09:40:54AM -0800, Junio C Hamano wrote:\n> > +\t\t\t\t       struct commit **list, int n)\n> > +{\n> > +\tstruct strbuf commit_line = STRBUF_INIT;\n> > +\tstruct pretty_print_context ctx = {0};\n> > +\n> > +\tstrbuf_init(&commit_line, 0);\n> \n> So a single commit_line is given to repo_format_commit_message()\n> repeatedly to accumulate those lines in it, which makes sense.\n> \n> We prepare pretty_print_context once above and feed it repeatedly to\n> repo_format_commit_message()---is that intended?  Not a rhetorical\n> question; I do not know the answer.  I guess some existing callers\n> (like builtin/archive.c) do reuse the structure when making multiple\n> calls to the function, so this would be kosher, perhaps?\n\nHonestly I applied Jeff change trusting his knowledge of the pretty.c\ncode. After taking a better look at repo_format_commit_message(), after\nthe pretty_print_context is passed to a format_commit_context as\npretty_ctx, it doesn't seem like this value is modified at all thoughout\nthe call. So it should be ok to reuse the same structure in multiple\ncalls of repo_format_commit_message().\n\n> > +\tfor (int i = n - 1; i >= 0; i--) {\n> > +\t\tstrbuf_addf(&commit_line, \"[%0*d/%d] \", decimal_width(n), n - i, n);\n> > +\t\trepo_format_commit_message(the_repository, list[i], format, &commit_line, &ctx);\n> \n> Let's line-wrap this overly long line (my lithmus test to complain\n> about \"overly long lines\" is after losing three columns to the left\n> for \"> +\" in e-mail quote like the above the right edge of the line\n> does not fit on my 92-column terminal, which will never happen if\n> you stick to the official \"fit 80-column after quoted for a few\n> times in e-mail\" guideline).\n> \n> \t\trepo_format_commit_message(the_repository, list[i], format,\n> \t\t\t\t\t   &commit_line, &ctx);\n> \n> perhaps.\n\nMy bad, didn't realise it went over. I'll change settings on my editor\nto ensure that I can visually see it going over and do an overall coding\nstyle check.\n\n> > +\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n> \n> I somehow would have expected that as we internally prepare \"format\"\n> string given to this function , we ensure it ends with \"\\n\" so we do\n> not have to do a fprintf() here.\n\nThe value of format is user defined, I'm not doing any pre proccessing\nto it apart from stripping the prefix. Would it be much different had I\nappended a newline here? I personally think it's fine doing a fprintf\nhere.\n\n> > +\tstrbuf_release(&commit_line);\n> > +}\n> > +\n> >  static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n> >  \t\t\t      struct commit *origin,\n> >  \t\t\t      int nr, struct commit **list,\n> >  \t\t\t      const char *description_file,\n> >  \t\t\t      const char *branch_name,\n> >  \t\t\t      int quiet,\n> > -\t\t\t      const struct format_config *cfg)\n> > +\t\t\t      const struct format_config *cfg,\n> > +\t\t\t      const char *format)\n> >  {\n> >  \tconst char *from;\n> >  \tstruct shortlog log;\n> > @@ -1342,6 +1361,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n> >  \tstruct commit *head = list[0];\n> >  \tchar *to_free = NULL;\n> >  \n> > +\tassert(format);\n> > +\n> \n> Curious.  We do not assert() for any other pointer arguments.  What\n> makes this so special?\n\nIt's a leftover of code that should've been stripped. Sorry for this.\n\n> >  \tif (!cmit_fmt_is_mail(rev->commit_format))\n> >  \t\tdie(_(\"cover letter needs email format\"));\n> >  \n> > @@ -1377,18 +1398,22 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n> >  \tfree(pp.after_subject);\n> >  \tstrbuf_release(&sb);\n> >  \n> > -\tshortlog_init(&log);\n> > -\tlog.wrap_lines = 1;\n> > -\tlog.wrap = MAIL_DEFAULT_WRAP;\n> > -\tlog.in1 = 2;\n> > -\tlog.in2 = 4;\n> > -\tlog.file = rev->diffopt.file;\n> > -\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n> > -\tshortlog_finish_setup(&log);\n> > -\tfor (i = 0; i < nr; i++)\n> > -\t\tshortlog_add_commit(&log, list[i]);\n> \n> It would have been much nicer to first create a short helper\n> function generate_shortlog_cover(), move the above plus a call to\n> shortlog_output(&log) to that helper function, without doing\n> anything else and make it a preliminary patch [1/n].  Then introduce\n> the corresponding generate_commit_list_cover() function in patch\n> [2/n] (which is what this step is doing), which would have resulted\n> in the body of the make_cover_letter() around here a short-and-sweet\n> \n> \tif (... we are doing shortlog style ...)\n> \t\tgenerate_shortlog_cover(...);\n> \telse\n> \t\tgenerate_commit_list_cover(...);\n> \n> Just like readers of _this_ function are helped by not having to\n> know the details of what happens inside commit-list style cover\n> generation, they can concentratre on the flow without having to care\n> about details on shortlog side that way.\n\nYes, it does read nicer. Will do.\n\n> > @@ -1906,6 +1931,7 @@ int cmd_format_patch(int argc,\n> >  \tint just_numbers = 0;\n> >  \tint ignore_if_in_upstream = 0;\n> >  \tint cover_letter = -1;\n> > +\tchar *cover_letter_fmt = NULL;\n> >  \tint boundary_count = 0;\n> >  \tint no_binary_diff = 0;\n> >  \tint zero_commit = 0;\n> > @@ -1952,6 +1978,8 @@ int cmd_format_patch(int argc,\n> >  \t\t\t    N_(\"print patches to standard out\")),\n> >  \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n> >  \t\t\t    N_(\"generate a cover letter\")),\n> > +\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n> > +\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n> >  \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n> >  \t\t\t    N_(\"use simple number sequence for output file names\")),\n> >  \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n> > @@ -2289,13 +2317,14 @@ int cmd_format_patch(int argc,\n> >  \t\t/* nothing to do */\n> >  \t\tgoto done;\n> >  \ttotal = list.nr;\n> > +\n> \n> What is this churn about?\n\nThis is introducing \"--cover-letter-format\"\n\n> >  \tif (cover_letter == -1) {\n> >  \t\tif (cfg.config_cover_letter == COVER_AUTO)\n> > -\t\t\tcover_letter = (total > 1);\n> > +\t\t\tcover_letter = total > 1;\n> \n> What is this churn about?\n> \n> >  \t\telse if ((idiff_prev.nr || rdiff_prev) && (total > 1))\n> > -\t\t\tcover_letter = (cfg.config_cover_letter != COVER_OFF);\n> > +\t\t\tcover_letter = cfg.config_cover_letter != COVER_OFF;\n> \n> What is this churn about?\n> \n> >  \t\telse\n> > -\t\t\tcover_letter = (cfg.config_cover_letter == COVER_ON);\n> > +\t\t\tcover_letter = cfg.config_cover_letter == COVER_ON;\n> \n> What is this churn about?\n> \n> Please do not distract reviewers by mixing immaterial \"style fixes\"\n> on existing code to a patch whose primary purpose is to introduce\n> new code.  A separate \"preliminary fix and/or clean-up\" patch before\n> the main series begins is often a welcome addition, though.\n\nThis was not inteded. Is the result of me copy-pasting and then\nrestoring when I changed my mind on having the format spec be passed\nthrough \"--cover-letter\".\n\nSorry for the unnecessary noise, will restore.\n\n> > @@ -2375,12 +2404,16 @@ int cmd_format_patch(int argc,\n> >  \t}\n> >  \trev.numbered_files = just_numbers;\n> >  \trev.patch_suffix = fmt_patch_suffix;\n> > +\n> > +\tif (cover_letter && !cover_letter_fmt)\n> > +\t\tcover_letter_fmt = \"shortlog\";\n> > +\n> \n> I do not quite see the point of doing this.  There is no law that\n> \"cover_letter_fmt != NULL\" is a crime when cover_letter is false.\n> \n> cover_letter_fmt can be initialized to a fixed string \"shortlog\",\n> and nobody cares what random value cover_letter_fmt has when\n> cover_letter is false.\n\nIndeed, was the first thing that came to mind at the time. Had I had\nreread a few more times before sending I probably wouldn't have left it\nlike this.\n\nI'll try to take my time with the next version as to not waste your time\non unnecessary initialization trivialities.\n\n> >  \tif (cover_letter) {\n> >  \t\tif (cfg.thread)\n> >  \t\t\tgen_message_id(&rev, \"cover\");\n> >  \t\tmake_cover_letter(&rev, !!output_directory,\n> >  \t\t\t\t  origin, list.nr, list.items,\n> > -\t\t\t\t  description_file, branch_name, quiet, &cfg);\n> > +\t\t\t\t  description_file, branch_name, quiet, &cfg, cover_letter_fmt);\n> \n> This line has become overly long.  Please wrap it.\n\nWill do.\n\n> New set of tests should come together with implementation of a new\n> feature in the same patch.\n\nYes, this is something you told me about on the other patch series as\nwell, sorry for forgetting. Will try to include them right away next\ntime (along with the necessary docs update).\n"},{"id":"537041","messageId":"aZ46xqCusF1av-va@exploit","threadId":"65032","inReplyTo":"xmqqqzqaggln.fsf@gitster.g","subject":"Re: [PATCH v2 2/2] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-25T00:14:13Z","receivedAt":"2026-02-25T00:14:16Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Tue, Feb 24, 2026 at 10:07:48AM -0800, Junio C Hamano wrote:\n> In this project, asterisk sticks to the variable, not the type,\n> i.e.,\n> \n> \tchar *fmt_cover_letter_commit_list;\n> \n> I think you got this point right in the previous patch.\n\nYes, that was not intentional, must've been a typo.\n\n> > @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n> >  \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n> >  \t\treturn 0;\n> >  \t}\n> > +\tif (!strcmp(var, \"format.commitlistformat\")) {\n> > +\t\tstruct strbuf tmp = STRBUF_INIT;\n> > +\t\tstrbuf_init(&tmp, 0);\n> > +\t\tstrbuf_addstr(&tmp, \"log:\");\n> > +\t\tif (value)\n> > +\t\t\tstrbuf_addstr(&tmp, value);\n> > +\t\telse\n> > +\t\t\tstrbuf_addstr(&tmp, \"%s\");\n> > +\n> > +\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n> \n> What if /etc/gitconfig has \"[format] commitListFormat = shortlog\",\n> ~/.gitconfig has a different setting, and then .git/config has yet\n> another setting?  Woudln't cfg->fmt_cover_letter_commit_list at this\n> point have a copy of the value read from the previous configuration\n> file?  Without first freeing it, wouldn't we leak the previous value?\n> \n>     $ git grep -C2 git_config_string\\(\n> \n> gives plenty of precedence, like this one.\n> \n> builtin/commit.c-\tif (!strcmp(k, \"commit.cleanup\")) {\n> builtin/commit.c-\t\tFREE_AND_NULL(cleanup_config);\n> builtin/commit.c:\t\treturn git_config_string(&cleanup_config, k, v);\n> builtin/commit.c-\t}\n> builtin/commit.c-\tif (!strcmp(k, \"commit.gpgsign\")) {\n\nWill do.\n\n> > +\t\tstrbuf_release(&tmp);\n> > +\t\treturn 0;\n> > +\t}\n> >  \tif (!strcmp(var, \"format.outputdirectory\")) {\n> >  \t\tFREE_AND_NULL(cfg->config_output_directory);\n> >  \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n> > @@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,\n> >  \t\tgoto done;\n> >  \ttotal = list.nr;\n> >  \n> > +\tif (cover_letter_fmt && (strcmp(cover_letter_fmt, \"shortlog\") && strncmp(cover_letter_fmt, \"log:\", 4))) {\n> \n> Overly long line.\n\nWill fix.\n\n> What if it turns out that the --cover-letter option is not given\n> (and we are dealing with a single-patch topic, so auto setting has\n> decided that there is no need for cover letter)?  Shouldn't we\n> continue ignoring the typo on a setting that we are not going to use\n> anyway?\n\nYes, a check on cover_letter should fix this.\n\n> Stepping back a bit, even if we do not validate the format *here*,\n> shouldn't the code that does use cover_letter_fmt later in the\n> control flow *already* be checking the validity of the format and\n> complaining?  If that happens early enough, perhaps we do not want\n> to have an extra \"early check and die\" here.\n\nThat is true, and initially I did not introduce a check here, but\nmake_cover_letter() is called after the cover letter file has already\nbeen created. Failing before format-patch could create a file or print\nanything on screeen seemed more clean to me, that's the only reason\nthere's a check there.\n\n> By the way, the usual technique used in this codebase when handing\n> configuration and command line option is to these in this order:\n> \n>  * Initialize a variable to the built-in hardcoded default (e.g.,\n>    \"shortlog\") upon variable declaration.\n> \n>  * Let repo_config() call overwrite that same variable.  This is the\n>    typical implementation of \"if there is no configuration, we use\n>    the hardcoded default, but the configured value can override it\".\n> \n>  * Then parse_options() overwrites that same variable.\n> \n> But because we read configuration into a separarte variable (i.e.,\n> members of cfg structure), this function cannot literally follow the\n> usual pattern.  But the pattern we instead can follow is this:\n> \n> \t/* initiailize to NULL */\n> \tchar *cover_letter_fmt = NULL;\n> \n>         /* read configuration */\n>         repo_config(... &cfg);\n> \n> \t/* cover_letter_fmt will point at command line arg */\n> \tparse_options(...);\n> \n>         /* NULL if no command line argument */\n> \tif (!cover_letter_fmt) {\n> \t\t/* perhaps configuration has one */\n>         \tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n> \n>                 /* otherwise, use hardcoded default */\n>                 if (!cover_letter_fmt)\n>                 \tcover_letter_fmt = \"shortlog\";\n> \t}\n\nWill rewrite to follow this config flow.\n"},{"id":"537043","messageId":"aZ4-_jmFS8yS4g49@exploit","threadId":"65032","inReplyTo":"xmqq1pi9g6sb.fsf@gitster.g","subject":"Re: [PATCH v2 0/2] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-25T00:19:31Z","receivedAt":"2026-02-25T00:19:35Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Tue, Feb 24, 2026 at 01:39:48PM -0800, Junio C Hamano wrote:\n> I found another change that is needed to make the tests pass, so the\n> commit object name for the squash fixup is no longer a85e8e535d;\n> fetching my 'seen' from any of the mirrors should let you find these\n> commits near the tip of 'seen'.\n\nDidn't notice the test failing, I only ran *format-patch* tests.\nThinking about it more carefully there are other related tests that\ncould fail due to the nature of format-patch being used in preparation\nfor other commands.\n\nWill run all tests next time.\n"},{"id":"537060","messageId":"xmqqo6ldd5sa.fsf@gitster.g","threadId":"65032","inReplyTo":"aZ4v6p_oKCayr9A7@exploit","subject":"Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-25T00:29:41Z","receivedAt":"2026-02-25T00:29:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n>> > +\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n>> \n>> I somehow would have expected that as we internally prepare \"format\"\n>> string given to this function , we ensure it ends with \"\\n\" so we do\n>> not have to do a fprintf() here.\n>\n> The value of format is user defined, I'm not doing any pre proccessing\n> to it apart from stripping the prefix. Would it be much different had I\n> appended a newline here? I personally think it's fine doing a fprintf\n> here.\n\nI thought you are stripping the prefix and making a copy in the\nconfiguration parser.  I didn't look closely but I thought it would\nbe trivially cheap to also append LF there.  If not, I agree with\nyou that it is not a huge deal to do the \"%s\\n\" here instead.\n\nThanks for working on this topic.\n"},{"id":"537065","messageId":"xmqqcy1tczga.fsf@gitster.g","threadId":"65032","inReplyTo":"aZ4-_jmFS8yS4g49@exploit","subject":"Re: [PATCH v2 0/2] format-patch: add cover-letter-format option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-25T02:46:29Z","receivedAt":"2026-02-25T02:46:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Didn't notice the test failing, I only ran *format-patch* tests.\n> Thinking about it more carefully there are other related tests that\n> could fail due to the nature of format-patch being used in preparation\n> for other commands.\n>\n> Will run all tests next time.\n\nYeah, it often is simpler and with less effort to just run all of\nthem, as one can never know what fallouts a particular change will\nhave in unexpected areas.\n\nThanks.\n"},{"id":"537085","messageId":"20260225134708.GC2139176@coredump.intra.peff.net","threadId":"65032","inReplyTo":"aZ4v6p_oKCayr9A7@exploit","subject":"Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-02-25T13:47:08Z","receivedAt":"2026-02-25T13:47:09Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Feb 25, 2026 at 12:54:54AM +0100, Mirko Faina wrote:\n\n> > So a single commit_line is given to repo_format_commit_message()\n> > repeatedly to accumulate those lines in it, which makes sense.\n> > \n> > We prepare pretty_print_context once above and feed it repeatedly to\n> > repo_format_commit_message()---is that intended?  Not a rhetorical\n> > question; I do not know the answer.  I guess some existing callers\n> > (like builtin/archive.c) do reuse the structure when making multiple\n> > calls to the function, so this would be kosher, perhaps?\n> \n> Honestly I applied Jeff change trusting his knowledge of the pretty.c\n> code. After taking a better look at repo_format_commit_message(), after\n> the pretty_print_context is passed to a format_commit_context as\n> pretty_ctx, it doesn't seem like this value is modified at all thoughout\n> the call. So it should be ok to reuse the same structure in multiple\n> calls of repo_format_commit_message().\n\nA dangerous place to put your trust. ;)\n\nI do think it is OK here, though. And in general I'd assume a \"context\"\nvariable like this is re-usable unless documented differently. The\nshortlog does use a pretty_print_context in a loop like this, too.\n\n> > > +\tif (cover_letter && !cover_letter_fmt)\n> > > +\t\tcover_letter_fmt = \"shortlog\";\n> > > +\n> > \n> > I do not quite see the point of doing this.  There is no law that\n> > \"cover_letter_fmt != NULL\" is a crime when cover_letter is false.\n> > \n> > cover_letter_fmt can be initialized to a fixed string \"shortlog\",\n> > and nobody cares what random value cover_letter_fmt has when\n> > cover_letter is false.\n> \n> Indeed, was the first thing that came to mind at the time. Had I had\n> reread a few more times before sending I probably wouldn't have left it\n> like this.\n> \n> I'll try to take my time with the next version as to not waste your time\n> on unnecessary initialization trivialities.\n\nI think that is OK, but FWIW my instinct on reading the patch was to go\nthe opposite direction: leave it as NULL and fill that in as the default\nat the last minute. Like this (on top of your patch 1):\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex d13fcf830b..6907a680f2 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1360,8 +1360,6 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tstruct commit *head = list[0];\n \tchar *to_free = NULL;\n \n-\tassert(format);\n-\n \tif (!cmit_fmt_is_mail(rev->commit_format))\n \t\tdie(_(\"cover letter needs email format\"));\n \n@@ -1397,7 +1395,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tif (skip_prefix(format, \"log:\", &format)) {\n+\tif (format && skip_prefix(format, \"log:\", &format)) {\n \t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n \t} else {\n \t\tshortlog_init(&log);\n@@ -2404,9 +2402,6 @@ int cmd_format_patch(int argc,\n \trev.numbered_files = just_numbers;\n \trev.patch_suffix = fmt_patch_suffix;\n \n-\tif (cover_letter && !cover_letter_fmt)\n-\t\tcover_letter_fmt = \"shortlog\";\n-\n \tif (cover_letter) {\n \t\tif (cfg.thread)\n \t\t\tgen_message_id(&rev, \"cover\");\n\nI tend to like keeping the sentinel value as long as possible, because\nsometimes it later becomes useful to distinguish \"the user asked for\nshortlog\" vs \"the user did not ask for anything\". We don't need that\nnow, but I could imagine some hypothetical future where we are going to\nswitch the default, and start issuing an advice/warning message.\n\nBut it is a pretty minor point, and mostly subjective, so I am happy\neither way.\n\nHowever, the patch above does raise another interesting question if you\nlook closely: why do we never need to check for the string \"shortlog\" in\nthis logic?\n\nWe just assume anything that does not start with \"log:\" should show the\nshortlog, and that is true both before and after my suggested change\nabove. What should:\n\n  git format-patch --cover-letter --cover-letter-format=foo\n\ndo? With \"log:%s\" as the syntax, it should probably be an error. And\nthis seems like a good place to detect it, like:\n\n  if (!format || !strcmp(format, \"shortlog\"))\n\t...do shortlog...\n  else if (skip_prefix(format, \"log:\", &format))\n\t...do format...\n  else\n\tdie(\"what the heck does %s mean?\", format);\n\nBut that gives me more thoughts (another dangerous habit, I know). Do we\nneed \"log:\" at all? Could the value be interpreted as a format, unless\nit is the string \"shortlog\"? We would want to leave room for future\nexpansion of new hard-coded names (like \"shortlog\"), but I do not think\nthere is any problem there. Any reasonable format provided by the user\nwill have at least one %-placeholder in it, and no hard-coded name we\nprovide would have a percent in it.\n\nAnd then you could just do:\n\n  git format-patch --cover-letter --cover-letter-format=%s\n\nMaybe not a big deal, as I'd expect everyone to set it once in config\n(or within a script) anyway. But food for thought.\n\n-Peff\n"},{"id":"537086","messageId":"20260225135628.GD2139176@coredump.intra.peff.net","threadId":"65032","inReplyTo":"66cac565f8a40f8de3dc3d857feb681bb80cb136.1771925291.git.mroik@delayed.space","subject":"Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-02-25T13:56:28Z","receivedAt":"2026-02-25T13:56:30Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Feb 24, 2026 at 10:29:01AM +0100, Mirko Faina wrote:\n\n> +static void generate_commit_list_cover(FILE *cover_file,const char *format,\n> +\t\t\t\t       struct commit **list, int n)\n> +{\n> +\tstruct strbuf commit_line = STRBUF_INIT;\n> +\tstruct pretty_print_context ctx = {0};\n> +\n> +\tstrbuf_init(&commit_line, 0);\n> +\tfor (int i = n - 1; i >= 0; i--) {\n> +\t\tstrbuf_addf(&commit_line, \"[%0*d/%d] \", decimal_width(n), n - i, n);\n> +\t\trepo_format_commit_message(the_repository, list[i], format, &commit_line, &ctx);\n> +\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n> +\t\tstrbuf_reset(&commit_line);\n> +\t}\n\nWhen this topic came up initially I imagined that the user would be able\nto tweak the \"[1/5]\" part of the string, too. It would take a little\nextra work, though:\n\n  - introduce new placeholders like %(count) and %(total) or similar\n\n  - introduce count/total fields to pretty_print_context\n\n  - probably when total is unset, refuse to interpret %(count) and\n    %(total) at all (so they do not have meaning for regular \"git log\n    --format\" calls.\n\n  - not sure how to handle the width field for the count. Would it have\n    an option to default to the width of the total field?\n\nBut I'm not sure if it is worth all of that effort. It is more flexible,\nbut would people really want to customize how it is shown (after all, I\ndon't think we allow configuring it in the patch subjects themselves,\nthough we do allow suppressing the \"[PATCH]\" part completely).\n\n\nI had a few other comments while reading the patch, but Junio already\nmade them for me. :) I do like the direction this is heading.\n\n-Peff\n"},{"id":"537106","messageId":"xmqqfr6oburb.fsf@gitster.g","threadId":"65032","inReplyTo":"aZ46xqCusF1av-va@exploit","subject":"Re: [PATCH v2 2/2] format-patch: add commitListFormat config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-25T17:25:28Z","receivedAt":"2026-02-25T17:25:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n>> > +\tif (cover_letter_fmt && (strcmp(cover_letter_fmt, \"shortlog\") && strncmp(cover_letter_fmt, \"log:\", 4))) {\n>> \n>> Overly long line.\n>\n> Will fix.\n\nA minor thing, but using starts_with(cover_letter_fmt, \"log:\") would\nlet us without having to count the number of bytes in the \"log:\"\nprefix (and we do not have to keep recounting every time we decide\nto change the prefix used---which is not likely to happen in this\nparticular case).\n\nThanks.\n"},{"id":"537159","messageId":"aZ98aIf2i4bRdlJn@exploit","threadId":"65032","inReplyTo":"20260225135628.GD2139176@coredump.intra.peff.net","subject":"Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-25T22:55:21Z","receivedAt":"2026-02-25T22:55:31Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Wed, Feb 25, 2026 at 08:56:28AM -0500, Jeff King wrote:\n> When this topic came up initially I imagined that the user would be able\n> to tweak the \"[1/5]\" part of the string, too. It would take a little\n> extra work, though:\n> \n>   - introduce new placeholders like %(count) and %(total) or similar\n> \n>   - introduce count/total fields to pretty_print_context\n> \n>   - probably when total is unset, refuse to interpret %(count) and\n>     %(total) at all (so they do not have meaning for regular \"git log\n>     --format\" calls.\n> \n>   - not sure how to handle the width field for the count. Would it have\n>     an option to default to the width of the total field?\n> \n> But I'm not sure if it is worth all of that effort. It is more flexible,\n> but would people really want to customize how it is shown (after all, I\n> don't think we allow configuring it in the patch subjects themselves,\n> though we do allow suppressing the \"[PATCH]\" part completely).\n\nI can give it a try.\n"},{"id":"537248","messageId":"aaC81Hk3tO5N2Rl0@exploit","threadId":"65032","inReplyTo":"aZ46xqCusF1av-va@exploit","subject":"Re: [PATCH v2 2/2] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-26T21:40:09Z","receivedAt":"2026-02-26T21:40:13Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Wed, Feb 25, 2026 at 01:14:13AM +0100, Mirko Faina wrote:\n> > > +\t\tstrbuf_release(&tmp);\n> > > +\t\treturn 0;\n> > > +\t}\n> > >  \tif (!strcmp(var, \"format.outputdirectory\")) {\n> > >  \t\tFREE_AND_NULL(cfg->config_output_directory);\n> > >  \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n> > > @@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,\n> > >  \t\tgoto done;\n> > >  \ttotal = list.nr;\n> > >  \n> > > +\tif (cover_letter_fmt && (strcmp(cover_letter_fmt, \"shortlog\") && strncmp(cover_letter_fmt, \"log:\", 4))) {\n> > \n> > Overly long line.\n> \n> Will fix.\n> \n> > Stepping back a bit, even if we do not validate the format *here*,\n> > shouldn't the code that does use cover_letter_fmt later in the\n> > control flow *already* be checking the validity of the format and\n> > complaining?  If that happens early enough, perhaps we do not want\n> > to have an extra \"early check and die\" here.\n> \n> That is true, and initially I did not introduce a check here, but\n> make_cover_letter() is called after the cover letter file has already\n> been created. Failing before format-patch could create a file or print\n> anything on screeen seemed more clean to me, that's the only reason\n> there's a check there.\n\nMay I have a confirmation on this. Is it ok to leave the extra check\nhere or would you like me to remove it and just let make_cover_letter()\nhandle it?\n"},{"id":"537251","messageId":"xmqqv7fjuozt.fsf@gitster.g","threadId":"65032","inReplyTo":"aaC81Hk3tO5N2Rl0@exploit","subject":"Re: [PATCH v2 2/2] format-patch: add commitListFormat config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-26T22:19:34Z","receivedAt":"2026-02-26T22:19:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> On Wed, Feb 25, 2026 at 01:14:13AM +0100, Mirko Faina wrote:\n>> > > +\t\tstrbuf_release(&tmp);\n>> > > +\t\treturn 0;\n>> > > +\t}\n>> > >  \tif (!strcmp(var, \"format.outputdirectory\")) {\n>> > >  \t\tFREE_AND_NULL(cfg->config_output_directory);\n>> > >  \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n>> > > @@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,\n>> > >  \t\tgoto done;\n>> > >  \ttotal = list.nr;\n>> > >  \n>> > > +\tif (cover_letter_fmt && (strcmp(cover_letter_fmt, \"shortlog\") && strncmp(cover_letter_fmt, \"log:\", 4))) {\n>> > \n>> > Overly long line.\n>> \n>> Will fix.\n>> \n>> > Stepping back a bit, even if we do not validate the format *here*,\n>> > shouldn't the code that does use cover_letter_fmt later in the\n>> > control flow *already* be checking the validity of the format and\n>> > complaining?  If that happens early enough, perhaps we do not want\n>> > to have an extra \"early check and die\" here.\n>> \n>> That is true, and initially I did not introduce a check here, but\n>> make_cover_letter() is called after the cover letter file has already\n>> been created. Failing before format-patch could create a file or print\n>> anything on screeen seemed more clean to me, that's the only reason\n>> there's a check there.\n>\n> May I have a confirmation on this. Is it ok to leave the extra check\n> here or would you like me to remove it and just let make_cover_letter()\n> handle it?\n\nAn extra check here would be warranted if that let us avoid\nend-users spend extra time and effort before we make a call to\nmake_cover_letter() and bad arguments cause it to die.  Otherwise,\nnot.  As the underlying helper function, make_cover_letter() should\nbe doing its own sanity check on its input anyway, so it is\npreferrable not to duplicate the check elsewhere if we do not have\nto.\n"},{"id":"537260","messageId":"cfed3bddf66ed2fab1f4da896759de1ba086578f.1772156996.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772156996.git.mroik@delayed.space","subject":"[PATCH v3 1/4] pretty.c: add %(count) and %(total) placeholders","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T01:52:36Z","receivedAt":"2026-02-27T01:53:02Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"In many commands we can customize the output through the \"--format\" or\nthe \"--pretty\" options. This patch adds two new placeholders used mainly\nwhen there's a range of commits that we want to show.\n\nCurrently these two placeholders are not usable as they're coupled with\nthe rev_info->nr and rev_info->total fields, fields that are used only\nby the format-patch numbered email subjects.\n\nTeach repo_format_commit_message() the %(count) and %(total)\nplaceholders.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n pretty.c | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git a/pretty.c b/pretty.c\nindex e0646bbc5d..e29bb8b877 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tif (!commit->object.parsed)\n \t\tparse_object(the_repository, &commit->object.oid);\n \n+\tif (starts_with(placeholder, \"(count)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%0*d\", decimal_width(c->pretty_ctx->rev->total),\n+\t\t\t    c->pretty_ctx->rev->nr);\n+\t\treturn 7;\n+\t}\n+\n+\tif (starts_with(placeholder, \"(total)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%d\", c->pretty_ctx->rev->total);\n+\t\treturn 7;\n+\t}\n+\n \tswitch (placeholder[0]) {\n \tcase 'H':\t\t/* commit hash */\n \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));\n-- \n2.53.0.4.g55f3102ead\n\n"},{"id":"537263","messageId":"cover.1772156996.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1771925291.git.mroik@delayed.space","subject":"[PATCH v3 0/4] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T01:52:35Z","receivedAt":"2026-02-27T01:53:02Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"As Jeff requested I added two new placeholders to make the numbering\npart customizable as well. The width of %(count) will always be the same\nwidth of %(total).\n\nI've also moved the code for generating the shortlog format into its own\nfunction.\n\nThere are now tests for both patch 3 and 4. Unfortunately I couldn't add\ntests for patch 1 as the variables that are coupled with %(count) and\n%(total) are not used for any command that accepts \"--format\" or\n\"--pretty\".\n\nI've also went over the code and fixed the parts that you marked as\nproblematic (hopefully I haven't missed anything).\n\nThank you both for the reviews.\n\n[1/4] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)\n[2/4] format-patch: move cover letter summary generation (Mirko Faina)\n[3/4] format-patch: add ability to use alt cover format (Mirko Faina)\n[4/4] format-patch: add commitListFormat config (Mirko Faina)\n\n builtin/log.c           |  94 +++++++++++++++++++++++++++++++------\n pretty.c                |  15 ++++++\n t/t4014-format-patch.sh | 101 ++++++++++++++++++++++++++++++++++++++++\n t/t9902-completion.sh   |   1 +\n 4 files changed, 197 insertions(+), 14 deletions(-)\n\n-- \n2.53.0.4.g55f3102ead\n\n"},{"id":"537264","messageId":"dc131c756520a0982d1d6f733dff48a464900ea5.1772156996.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772156996.git.mroik@delayed.space","subject":"[PATCH v3 2/4] format-patch: move cover letter summary generation","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T01:52:37Z","receivedAt":"2026-02-27T01:53:02Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"As of now format-patch allows generation of a template cover letter for\npatch series through \"--cover-letter\".\n\nMove shortlog summary code generation to its own function. This is done\nin preparation to other patches where we enable the user to format the\ncommit list using thier own format string.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 32 ++++++++++++++++++++------------\n 1 file changed, 20 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 5c9a8ef363..0d12272031 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_shortlog_cover_letter(struct shortlog *log,\n+\t\t\t\t\t   struct rev_info *rev,\n+\t\t\t\t\t   struct commit **list,\n+\t\t\t\t\t   int nr)\n+{\n+\tshortlog_init(log);\n+\tlog->wrap_lines = 1;\n+\tlog->wrap = MAIL_DEFAULT_WRAP;\n+\tlog->in1 = 2;\n+\tlog->in2 = 4;\n+\tlog->file = rev->diffopt.file;\n+\tlog->groups = SHORTLOG_GROUP_AUTHOR;\n+\tshortlog_finish_setup(log);\n+\tfor (int i = 0; i < nr; i++)\n+\t\tshortlog_add_commit(log, list[i]);\n+\n+\tshortlog_output(log);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n-\n-\tshortlog_output(&log);\n+\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n-- \n2.53.0.4.g55f3102ead\n\n"},{"id":"537261","messageId":"55f3102ead7e9a5887138bc62706ab76ed7236bd.1772156996.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772156996.git.mroik@delayed.space","subject":"[PATCH v3 4/4] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T01:52:39Z","receivedAt":"2026-02-27T01:53:03Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Using \"--cover-letter\" we can tell format-patch to generate a cover\nletter, in this cover letter there's a list of commits included in the\npatch series and the format is specified by the \"--cover-letter-format\"\noption. Would be useful if this format could be configured from the\nconfig file instead of always needing to pass it from the command line.\n\nTeach format-patch how to read the format spec for the cover letter from\nthe config files. The variable it should look for is called\n\"commitListFormat\".\n\nPossible values:\n- commitListFormat is set but no string is passed: it will default to\n  \"[%(count)/%(total)] %s\"\n- if a string is passed: will use it as a format spec. Note that this is\n  either \"shortlog\" or a format spec without the \"log:\" prefix\n  e.g. \"%s (%an)\"\n- if commitListFormat is not set: it will default to \"shortlog\"\nshortlog format.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 24 +++++++++++++++++++\n t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 77 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 46c8e33773..dcf7cd1792 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -886,6 +886,7 @@ struct format_config {\n \tchar *signature;\n \tchar *signature_file;\n \tenum cover_setting config_cover_letter;\n+\tchar *fmt_cover_letter_commit_list;\n \tchar *config_output_directory;\n \tenum cover_from_description cover_from_description_mode;\n \tint show_notes;\n@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n \tstring_list_clear(&cfg->extra_cc, 0);\n \tstrbuf_release(&cfg->sprefix);\n \tfree(cfg->fmt_patch_suffix);\n+\tfree(cfg->fmt_cover_letter_commit_list);\n }\n \n static enum cover_from_description parse_cover_from_description(const char *arg)\n@@ -1052,6 +1054,22 @@ static int git_format_config(const char *var, const char *value,\n \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"format.commitlistformat\")) {\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\t\tstrbuf_init(&tmp, 0);\n+\t\tif (value) {\n+\t\t\tif (strcmp(value, \"shortlog\"))\n+\t\t\t\tstrbuf_addstr(&tmp, \"log:\");\n+\t\t\tstrbuf_addstr(&tmp, value);\n+\t\t} else {\n+\t\t\tstrbuf_addstr(&tmp, \"log:[%(count)/%(total)] %s\");\n+\t\t}\n+\n+\t\tFREE_AND_NULL(cfg->fmt_cover_letter_commit_list);\n+\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"format.outputdirectory\")) {\n \t\tFREE_AND_NULL(cfg->config_output_directory);\n \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n@@ -2329,6 +2347,12 @@ int cmd_format_patch(int argc,\n \t\tgoto done;\n \ttotal = list.nr;\n \n+\tif (!cover_letter_fmt) {\n+\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n+\t\tif (!cover_letter_fmt)\n+\t\t\tcover_letter_fmt = \"shortlog\";\n+\t}\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 458da80721..c43c6972af 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '\n \ttest_line_count = 1 result\n '\n \n+test_expect_success 'cover letter config with count, subject and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"[%(count)/%(total)] %s (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .* \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config with count and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"[%(count)/%(total)] (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set but no format' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tprintf \"\\tcommitlistformat\" >> .git/config &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .*\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set to shortlog' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat shortlog &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat not set' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\tgit config set format.coverletter true &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n-- \n2.53.0.4.g55f3102ead\n\n"},{"id":"537262","messageId":"190817fe6ac5199c6df95558a7dee4be3d2fffdd.1772156996.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772156996.git.mroik@delayed.space","subject":"[PATCH v3 3/4] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T01:52:38Z","receivedAt":"2026-02-27T01:53:03Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nGive the ability to format-patch to specify an alternative format spec\nthrough the \"--cover-letter-format\" option. This option either takes\n\"shortlog\", which is the current format, or a format spec prefixed with\n\"log:\".\n\nExample:\n    git format-patch --cover-letter \\\n        --cover-letter-format=\"log:%s (%an)\" HEAD~3\n\n    [1/3] this is a commit summary (Mirko Faina)\n    [2/3] this is another commit summary (Mirko Faina)\n    ...\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 40 +++++++++++++++++++++++++++++++---\n t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++\n t/t9902-completion.sh   |  1 +\n 3 files changed, 86 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 0d12272031..46c8e33773 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,\n \tshortlog_output(log);\n }\n \n+static void generate_commit_list_cover(FILE *cover_file,const char *format,\n+\t\t\t\t       struct commit **list, int n)\n+{\n+\tstruct strbuf commit_line = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\tstruct rev_info rev = REV_INFO_INIT;\n+\n+\tstrbuf_init(&commit_line, 0);\n+\trev.total = n;\n+\tctx.rev = &rev;\n+\tfor (int i = n - 1; i >= 0; i--) {\n+\t\trev.nr = n - i;\n+\t\trepo_format_commit_message(the_repository, list[i], format,\n+\t\t\t\t&commit_line, &ctx);\n+\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n+\t\tstrbuf_reset(&commit_line);\n+\t}\n+\tfprintf(cover_file, \"\\n\");\n+\n+\tstrbuf_release(&commit_line);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n \t\t\t      const char *description_file,\n \t\t\t      const char *branch_name,\n \t\t\t      int quiet,\n-\t\t\t      const struct format_config *cfg)\n+\t\t\t      const struct format_config *cfg,\n+\t\t\t      const char *format)\n {\n \tconst char *committer;\n \tstruct shortlog log;\n@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\tif (skip_prefix(format, \"log:\", &format))\n+\t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n+\telse if (!strcmp(format, \"shortlog\"))\n+\t\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\telse\n+\t\tdie(_(\"'%s' is not a valid format string\"), format);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,\n \tint just_numbers = 0;\n \tint ignore_if_in_upstream = 0;\n \tint cover_letter = -1;\n+\tconst char *cover_letter_fmt = NULL;\n \tint boundary_count = 0;\n \tint no_binary_diff = 0;\n \tint zero_commit = 0;\n@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,\n \t\t\t    N_(\"print patches to standard out\")),\n \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n \t\t\t    N_(\"generate a cover letter\")),\n+\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n+\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n \t\t\t    N_(\"use simple number sequence for output file names\")),\n \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,\n \t\t/* nothing to do */\n \t\tgoto done;\n \ttotal = list.nr;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\n@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,\n \t}\n \trev.numbered_files = just_numbers;\n \trev.patch_suffix = fmt_patch_suffix;\n+\n \tif (cover_letter) {\n \t\tif (cfg.thread)\n \t\t\tgen_message_id(&rev, \"cover\");\n \t\tmake_cover_letter(&rev, !!output_directory,\n \t\t\t\t  origin, list.nr, list.items,\n-\t\t\t\t  description_file, branch_name, quiet, &cfg);\n+\t\t\t\t  description_file, branch_name, quiet, &cfg,\n+\t\t\t\t  cover_letter_fmt);\n \t\tprint_bases(&bases, rev.diffopt.file);\n \t\tprint_signature(signature, rev.diffopt.file);\n \t\ttotal++;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 21d6d0cd9e..458da80721 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '\n \tdone\n '\n \n+test_expect_success 'cover letter with subject, author and count' '\n+\trm -rf patches &&\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expected_success 'cover letter with author and count' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %an\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] A U Thor$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter shortlog' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter --cover-letter-format=shortlog \\\n+\t-o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter no format' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter -o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 964e1f1569..4f760a7468 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '\n \ttest_completion \"git send-email --cov\" <<-\\EOF &&\n \t--cover-from-description=Z\n \t--cover-letter Z\n+\t--cover-letter-format=Z\n \tEOF\n \ttest_completion \"git send-email --val\" <<-\\EOF &&\n \t--validate Z\n-- \n2.53.0.4.g55f3102ead\n\n"},{"id":"537268","messageId":"xmqq1pi6vmpo.fsf@gitster.g","threadId":"65032","inReplyTo":"190817fe6ac5199c6df95558a7dee4be3d2fffdd.1772156996.git.mroik@delayed.space","subject":"Re: [PATCH v3 3/4] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-27T04:23:31Z","receivedAt":"2026-02-27T04:23:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Example:\n>     git format-patch --cover-letter \\\n>         --cover-letter-format=\"log:%s (%an)\" HEAD~3\n>\n>     [1/3] this is a commit summary (Mirko Faina)\n>     [2/3] this is another commit summary (Mirko Faina)\n>     ...\n\nDon't you need to update this example?  I personally find the\n%(count)/%(total) thing a bit overengineered, but now we have it,\nyou'd need to write it in your forrmat string if you want to get the\n[N/M] prefix, right?\n"},{"id":"537285","messageId":"aaGQoLt_HlfZf5Zr@exploit","threadId":"65032","inReplyTo":"xmqq1pi6vmpo.fsf@gitster.g","subject":"Re: [PATCH v3 3/4] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T12:41:22Z","receivedAt":"2026-02-27T12:41:27Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Thu, Feb 26, 2026 at 08:23:31PM -0800, Junio C Hamano wrote:\n> Don't you need to update this example?  I personally find the\n> %(count)/%(total) thing a bit overengineered, but now we have it,\n> you'd need to write it in your forrmat string if you want to get the\n> [N/M] prefix, right?\n\nRight, forgot to change this commit message. I will send the updated\nseries.\n"},{"id":"537288","messageId":"cover.1772196510.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772156996.git.mroik@delayed.space","subject":"[PATCH v4 0/4] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T13:18:08Z","receivedAt":"2026-02-27T13:18:30Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"From: Mroik <mroik@delayed.space>\n\nI've fixed the example in patch number 3\n\n[1/4] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)\n[2/4] format-patch: move cover letter summary generation (Mirko Faina)\n[3/4] format-patch: add ability to use alt cover format (Mirko Faina)\n[4/4] format-patch: add commitListFormat config (Mirko Faina)\n\n builtin/log.c           |  94 +++++++++++++++++++++++++++++++------\n pretty.c                |  15 ++++++\n t/t4014-format-patch.sh | 101 ++++++++++++++++++++++++++++++++++++++++\n t/t9902-completion.sh   |   1 +\n 4 files changed, 197 insertions(+), 14 deletions(-)\n\n-- \n2.53.0.4.gf9ee8e2400\n\n"},{"id":"537289","messageId":"cfed3bddf66ed2fab1f4da896759de1ba086578f.1772196510.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772196510.git.mroik@delayed.space","subject":"[PATCH v4 1/4] pretty.c: add %(count) and %(total) placeholders","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T13:18:09Z","receivedAt":"2026-02-27T13:18:31Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"In many commands we can customize the output through the \"--format\" or\nthe \"--pretty\" options. This patch adds two new placeholders used mainly\nwhen there's a range of commits that we want to show.\n\nCurrently these two placeholders are not usable as they're coupled with\nthe rev_info->nr and rev_info->total fields, fields that are used only\nby the format-patch numbered email subjects.\n\nTeach repo_format_commit_message() the %(count) and %(total)\nplaceholders.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n pretty.c | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git a/pretty.c b/pretty.c\nindex e0646bbc5d..e29bb8b877 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tif (!commit->object.parsed)\n \t\tparse_object(the_repository, &commit->object.oid);\n \n+\tif (starts_with(placeholder, \"(count)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%0*d\", decimal_width(c->pretty_ctx->rev->total),\n+\t\t\t    c->pretty_ctx->rev->nr);\n+\t\treturn 7;\n+\t}\n+\n+\tif (starts_with(placeholder, \"(total)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%d\", c->pretty_ctx->rev->total);\n+\t\treturn 7;\n+\t}\n+\n \tswitch (placeholder[0]) {\n \tcase 'H':\t\t/* commit hash */\n \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));\n-- \n2.53.0.4.gf9ee8e2400\n\n"},{"id":"537290","messageId":"d21aab31cce4ed22a4ae9a989ca84f4c66603225.1772196510.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772196510.git.mroik@delayed.space","subject":"[PATCH v4 3/4] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T13:18:11Z","receivedAt":"2026-02-27T13:18:31Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nGive the ability to format-patch to specify an alternative format spec\nthrough the \"--cover-letter-format\" option. This option either takes\n\"shortlog\", which is the current format, or a format spec prefixed with\n\"log:\".\n\nExample:\n    git format-patch --cover-letter \\\n        --cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" HEAD~3\n\n    [1/3] this is a commit summary (Mirko Faina)\n    [2/3] this is another commit summary (Mirko Faina)\n    ...\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 40 +++++++++++++++++++++++++++++++---\n t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++\n t/t9902-completion.sh   |  1 +\n 3 files changed, 86 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 0d12272031..46c8e33773 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,\n \tshortlog_output(log);\n }\n \n+static void generate_commit_list_cover(FILE *cover_file,const char *format,\n+\t\t\t\t       struct commit **list, int n)\n+{\n+\tstruct strbuf commit_line = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\tstruct rev_info rev = REV_INFO_INIT;\n+\n+\tstrbuf_init(&commit_line, 0);\n+\trev.total = n;\n+\tctx.rev = &rev;\n+\tfor (int i = n - 1; i >= 0; i--) {\n+\t\trev.nr = n - i;\n+\t\trepo_format_commit_message(the_repository, list[i], format,\n+\t\t\t\t&commit_line, &ctx);\n+\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n+\t\tstrbuf_reset(&commit_line);\n+\t}\n+\tfprintf(cover_file, \"\\n\");\n+\n+\tstrbuf_release(&commit_line);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n \t\t\t      const char *description_file,\n \t\t\t      const char *branch_name,\n \t\t\t      int quiet,\n-\t\t\t      const struct format_config *cfg)\n+\t\t\t      const struct format_config *cfg,\n+\t\t\t      const char *format)\n {\n \tconst char *committer;\n \tstruct shortlog log;\n@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\tif (skip_prefix(format, \"log:\", &format))\n+\t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n+\telse if (!strcmp(format, \"shortlog\"))\n+\t\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\telse\n+\t\tdie(_(\"'%s' is not a valid format string\"), format);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,\n \tint just_numbers = 0;\n \tint ignore_if_in_upstream = 0;\n \tint cover_letter = -1;\n+\tconst char *cover_letter_fmt = NULL;\n \tint boundary_count = 0;\n \tint no_binary_diff = 0;\n \tint zero_commit = 0;\n@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,\n \t\t\t    N_(\"print patches to standard out\")),\n \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n \t\t\t    N_(\"generate a cover letter\")),\n+\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n+\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n \t\t\t    N_(\"use simple number sequence for output file names\")),\n \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,\n \t\t/* nothing to do */\n \t\tgoto done;\n \ttotal = list.nr;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\n@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,\n \t}\n \trev.numbered_files = just_numbers;\n \trev.patch_suffix = fmt_patch_suffix;\n+\n \tif (cover_letter) {\n \t\tif (cfg.thread)\n \t\t\tgen_message_id(&rev, \"cover\");\n \t\tmake_cover_letter(&rev, !!output_directory,\n \t\t\t\t  origin, list.nr, list.items,\n-\t\t\t\t  description_file, branch_name, quiet, &cfg);\n+\t\t\t\t  description_file, branch_name, quiet, &cfg,\n+\t\t\t\t  cover_letter_fmt);\n \t\tprint_bases(&bases, rev.diffopt.file);\n \t\tprint_signature(signature, rev.diffopt.file);\n \t\ttotal++;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 21d6d0cd9e..458da80721 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '\n \tdone\n '\n \n+test_expect_success 'cover letter with subject, author and count' '\n+\trm -rf patches &&\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expected_success 'cover letter with author and count' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %an\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] A U Thor$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter shortlog' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter --cover-letter-format=shortlog \\\n+\t-o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter no format' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter -o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 964e1f1569..4f760a7468 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '\n \ttest_completion \"git send-email --cov\" <<-\\EOF &&\n \t--cover-from-description=Z\n \t--cover-letter Z\n+\t--cover-letter-format=Z\n \tEOF\n \ttest_completion \"git send-email --val\" <<-\\EOF &&\n \t--validate Z\n-- \n2.53.0.4.gf9ee8e2400\n\n"},{"id":"537291","messageId":"dc131c756520a0982d1d6f733dff48a464900ea5.1772196510.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772196510.git.mroik@delayed.space","subject":"[PATCH v4 2/4] format-patch: move cover letter summary generation","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T13:18:10Z","receivedAt":"2026-02-27T13:18:31Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"As of now format-patch allows generation of a template cover letter for\npatch series through \"--cover-letter\".\n\nMove shortlog summary code generation to its own function. This is done\nin preparation to other patches where we enable the user to format the\ncommit list using thier own format string.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 32 ++++++++++++++++++++------------\n 1 file changed, 20 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 5c9a8ef363..0d12272031 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_shortlog_cover_letter(struct shortlog *log,\n+\t\t\t\t\t   struct rev_info *rev,\n+\t\t\t\t\t   struct commit **list,\n+\t\t\t\t\t   int nr)\n+{\n+\tshortlog_init(log);\n+\tlog->wrap_lines = 1;\n+\tlog->wrap = MAIL_DEFAULT_WRAP;\n+\tlog->in1 = 2;\n+\tlog->in2 = 4;\n+\tlog->file = rev->diffopt.file;\n+\tlog->groups = SHORTLOG_GROUP_AUTHOR;\n+\tshortlog_finish_setup(log);\n+\tfor (int i = 0; i < nr; i++)\n+\t\tshortlog_add_commit(log, list[i]);\n+\n+\tshortlog_output(log);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n-\n-\tshortlog_output(&log);\n+\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n-- \n2.53.0.4.gf9ee8e2400\n\n"},{"id":"537292","messageId":"f9ee8e24001aa16e5df3dc0bbfccd1b381e37d85.1772196510.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772196510.git.mroik@delayed.space","subject":"[PATCH v4 4/4] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T13:18:12Z","receivedAt":"2026-02-27T13:18:32Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Using \"--cover-letter\" we can tell format-patch to generate a cover\nletter, in this cover letter there's a list of commits included in the\npatch series and the format is specified by the \"--cover-letter-format\"\noption. Would be useful if this format could be configured from the\nconfig file instead of always needing to pass it from the command line.\n\nTeach format-patch how to read the format spec for the cover letter from\nthe config files. The variable it should look for is called\nformat.commitListFormat.\n\nPossible values:\n  - commitListFormat is set but no string is passed: it will default to\n    \"[%(count)/%(total)] %s\"\n\n  - if a string is passed: will use it as a format spec. Note that this\n    is either \"shortlog\" or a format spec without the \"log:\" prefix e.g.\n    \"%s (%an)\"\n\n  - if commitListFormat is not set: it will default to the shortlog\n    format.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 24 +++++++++++++++++++\n t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 77 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 46c8e33773..dcf7cd1792 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -886,6 +886,7 @@ struct format_config {\n \tchar *signature;\n \tchar *signature_file;\n \tenum cover_setting config_cover_letter;\n+\tchar *fmt_cover_letter_commit_list;\n \tchar *config_output_directory;\n \tenum cover_from_description cover_from_description_mode;\n \tint show_notes;\n@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n \tstring_list_clear(&cfg->extra_cc, 0);\n \tstrbuf_release(&cfg->sprefix);\n \tfree(cfg->fmt_patch_suffix);\n+\tfree(cfg->fmt_cover_letter_commit_list);\n }\n \n static enum cover_from_description parse_cover_from_description(const char *arg)\n@@ -1052,6 +1054,22 @@ static int git_format_config(const char *var, const char *value,\n \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"format.commitlistformat\")) {\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\t\tstrbuf_init(&tmp, 0);\n+\t\tif (value) {\n+\t\t\tif (strcmp(value, \"shortlog\"))\n+\t\t\t\tstrbuf_addstr(&tmp, \"log:\");\n+\t\t\tstrbuf_addstr(&tmp, value);\n+\t\t} else {\n+\t\t\tstrbuf_addstr(&tmp, \"log:[%(count)/%(total)] %s\");\n+\t\t}\n+\n+\t\tFREE_AND_NULL(cfg->fmt_cover_letter_commit_list);\n+\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"format.outputdirectory\")) {\n \t\tFREE_AND_NULL(cfg->config_output_directory);\n \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n@@ -2329,6 +2347,12 @@ int cmd_format_patch(int argc,\n \t\tgoto done;\n \ttotal = list.nr;\n \n+\tif (!cover_letter_fmt) {\n+\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n+\t\tif (!cover_letter_fmt)\n+\t\t\tcover_letter_fmt = \"shortlog\";\n+\t}\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 458da80721..c43c6972af 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '\n \ttest_line_count = 1 result\n '\n \n+test_expect_success 'cover letter config with count, subject and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"[%(count)/%(total)] %s (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .* \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config with count and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"[%(count)/%(total)] (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set but no format' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tprintf \"\\tcommitlistformat\" >> .git/config &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .*\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set to shortlog' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat shortlog &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat not set' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\tgit config set format.coverletter true &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n-- \n2.53.0.4.gf9ee8e2400\n\n"},{"id":"537300","messageId":"20260227164234.11632-1-mroik@delayed.space","threadId":"65032","inReplyTo":"f9ee8e24001aa16e5df3dc0bbfccd1b381e37d85.1772196510.git.mroik@delayed.space","subject":"[PATCH v4 5/4] docs: add usage for the cover-letter fmt feature","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T16:42:33Z","receivedAt":"2026-02-27T16:42:58Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Document the new \"--cover-letter-format\" feature in format-patch and its\nrelated config variable \"format.commitListFormat\".\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\nSorry, forgot to update the docs. This patch is to be applied on top of\npatch #4 of the series.\n\n Documentation/config/format.adoc    |  6 ++++++\n Documentation/git-format-patch.adoc | 11 +++++++++++\n 2 files changed, 17 insertions(+)\n\ndiff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc\nindex ab0710e86a..b91e59321c 100644\n--- a/Documentation/config/format.adoc\n+++ b/Documentation/config/format.adoc\n@@ -101,6 +101,12 @@ format.coverLetter::\n \tgenerate a cover-letter only when there's more than one patch.\n \tDefault is false.\n \n+format.commitListFormat::\n+\tA format string that specifies how to generate the commit list of a\n+\tcover-letter when format-patch is invoked. This is the config coupled\n+\twith `--cover-letter-format` in the format-patch command.\n+\tDefault is \"shortlog\".\n+\n format.outputDirectory::\n \tSet a custom directory to store the resulting files instead of the\n \tcurrent working directory. All directory components will be created.\ndiff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\nindex 9a7807ca71..bdcb5f989c 100644\n--- a/Documentation/git-format-patch.adoc\n+++ b/Documentation/git-format-patch.adoc\n@@ -24,6 +24,7 @@ SYNOPSIS\n \t\t   [(--reroll-count|-v) <n>]\n \t\t   [--to=<email>] [--cc=<email>]\n \t\t   [--[no-]cover-letter] [--quiet]\n+\t\t   [--cover-letter-format=<format-spec>]\n \t\t   [--[no-]encode-email-headers]\n \t\t   [--no-notes | --notes[=<ref>]]\n \t\t   [--interdiff=<previous>]\n@@ -321,6 +322,15 @@ feeding the result to `git send-email`.\n \tcontaining the branch description, shortlog and the overall diffstat.  You can\n \tfill in a description in the file before sending it out.\n \n+--cover-letter-format=<format-spec>::\n+\tSpecify the format in which to generate the commit list of the\n+\tpatch series. This option is available if the user wants to use\n+\tan alternative to the default shortlog format. The accepted\n+\tvalues for format-spec are \"shortlog\" or a format string\n+\tprefixed with `log:`.  \n+\te.g. `log: %s (%an)`  \n+\tThis option is relevant only if a cover letter is generated.\n+\n --encode-email-headers::\n --no-encode-email-headers::\n \tEncode email headers that have non-ASCII characters with\n@@ -452,6 +462,7 @@ with configuration variables.\n \tsignOff = true\n \toutputDirectory = <directory>\n \tcoverLetter = auto\n+\tcommitListFormat = shortlog\n \tcoverFromDescription = auto\n ------------\n \n-- \n2.53.0\n\n"},{"id":"537310","messageId":"xmqqecm6rs6s.fsf@gitster.g","threadId":"65032","inReplyTo":"f9ee8e24001aa16e5df3dc0bbfccd1b381e37d85.1772196510.git.mroik@delayed.space","subject":"Re: [PATCH v4 4/4] format-patch: add commitListFormat config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-27T17:51:07Z","receivedAt":"2026-02-27T17:51:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Possible values:\n>   - commitListFormat is set but no string is passed: it will default to\n>     \"[%(count)/%(total)] %s\"\n>\n>   - if a string is passed: will use it as a format spec. Note that this\n>     is either \"shortlog\" or a format spec without the \"log:\" prefix e.g.\n>     \"%s (%an)\"\n\nHmph.  \n\nSome sort of DWIM (e.g., it is not \"shortlog\", so it must be \"log:\"\nas there is nothing else supported) can be beneficial to help make\nit less cumbersome for users to type for command line options, just\nlike we did for \"git log --pretty=X\" where we take a string with '%'\nin it as a cue to default to \"--pretty=tformat:\".  But as \"set and\nforget\" facility, there is no strong need to prefer \"%s (%an)\" over\n\"log:%s (%an)\" on the configuration variable side (other than being\nconsistent to the command line option parsing).  We would probably\nwant to give an escape hatch in the design to avoid painting\nourselves into a corner we cannot get out of (imagine what happens\nwhen later others want to use something completely different from\nthe log-pretty machinery, making \"not a shortlog, must be log:\"\nassumption invalid).\n\nSomething like\n\n  \"log:\" is prefixed to the string value, UNLESS the value matches\n  \"^[-a-z0-9]*$\" or the value matches \"^[-a-z0-9]*:\".\n\nwould probably be extensible enough.  To avoid exposing regexp to\nthe users, we can say \"a string that is not an alphanumeric token,\nnor an alphanumeric token followed by a colon, is prefixed with\n'log:'\", or something.\n\nWIth such rules in place, a new fixed format other than \"shortlog\"\ncan be introduced (e.g., \"middlelog\") without getting munged into\nnonsense \"log:middlelog\", and a new parameterized format other than\n\"log:\" can be introduced (e.g., \"pretty:%(subject) %(author)\")\nwithout turning into a nonsense \"log:pretty:%(subject) %(author)\".\n\nOr we do not have to worry about these if we just use what the user\ngives us without any DWIM.  I just do not think we want to see the\n\"not a shortlog?  must be log\" burned into the syntax that will have\nto stay with us forever.\n\nThanks.\n"},{"id":"537343","messageId":"aaIQcXgOVLHXP_WM@exploit","threadId":"65032","inReplyTo":"xmqqecm6rs6s.fsf@gitster.g","subject":"Re: [PATCH v4 4/4] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T21:51:57Z","receivedAt":"2026-02-27T21:52:01Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Fri, Feb 27, 2026 at 09:51:07AM -0800, Junio C Hamano wrote:\n> Some sort of DWIM (e.g., it is not \"shortlog\", so it must be \"log:\"\n> as there is nothing else supported) can be beneficial to help make\n> it less cumbersome for users to type for command line options, just\n> like we did for \"git log --pretty=X\" where we take a string with '%'\n> in it as a cue to default to \"--pretty=tformat:\".  But as \"set and\n> forget\" facility, there is no strong need to prefer \"%s (%an)\" over\n> \"log:%s (%an)\" on the configuration variable side (other than being\n> consistent to the command line option parsing).  We would probably\n> want to give an escape hatch in the design to avoid painting\n> ourselves into a corner we cannot get out of (imagine what happens\n> when later others want to use something completely different from\n> the log-pretty machinery, making \"not a shortlog, must be log:\"\n> assumption invalid).\n> \n> Something like\n> \n>   \"log:\" is prefixed to the string value, UNLESS the value matches\n>   \"^[-a-z0-9]*$\" or the value matches \"^[-a-z0-9]*:\".\n> \n> would probably be extensible enough.  To avoid exposing regexp to\n> the users, we can say \"a string that is not an alphanumeric token,\n> nor an alphanumeric token followed by a colon, is prefixed with\n> 'log:'\", or something.\n> \n> WIth such rules in place, a new fixed format other than \"shortlog\"\n> can be introduced (e.g., \"middlelog\") without getting munged into\n> nonsense \"log:middlelog\", and a new parameterized format other than\n> \"log:\" can be introduced (e.g., \"pretty:%(subject) %(author)\")\n> without turning into a nonsense \"log:pretty:%(subject) %(author)\".\n> \n> Or we do not have to worry about these if we just use what the user\n> gives us without any DWIM.  I just do not think we want to see the\n> \"not a shortlog?  must be log\" burned into the syntax that will have\n> to stay with us forever.\n\nSince we already require the prefix in the command line might as well\njust make it the same for the configuration file. That way, if someone\nwill ever introduce a new preset format other than shortlog we just\ncheck for \"log:\" first and then cascade into checking the rest of the\npresets.\n\nPlease confirm that you like this and I'll start making changes to the\npatch.\n\nThank you\n"},{"id":"537346","messageId":"xmqqy0kdomjx.fsf@gitster.g","threadId":"65032","inReplyTo":"aaIQcXgOVLHXP_WM@exploit","subject":"Re: [PATCH v4 4/4] format-patch: add commitListFormat config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-27T22:21:06Z","receivedAt":"2026-02-27T22:21:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n>> Or we do not have to worry about these if we just use what the user\n>> gives us without any DWIM.  I just do not think we want to see the\n>> \"not a shortlog?  must be log\" burned into the syntax that will have\n>> to stay with us forever.\n>\n> Since we already require the prefix in the command line might as well\n> just make it the same for the configuration file.\n\nSounds sensible.  Either with or without DWIM (with escape hatch),\nbeing consistent between the command line and configuration is a\ngood thing.\n"},{"id":"537351","messageId":"cover.1772232373.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772196510.git.mroik@delayed.space","subject":"[PATCH v5 0/5] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T22:48:10Z","receivedAt":"2026-02-27T22:48:47Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"I've reconciled the formats between the command line option and the\nconfiguration variable. Changes are reflected in the documentation and\nthe tests.\n\nThank you for the review\n\n[1/5] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)\n[2/5] format-patch: move cover letter summary generation (Mirko Faina)\n[3/5] format-patch: add ability to use alt cover format (Mirko Faina)\n[4/5] format-patch: add commitListFormat config (Mirko Faina)\n[5/5] docs: add usage for the cover-letter fmt feature (Mirko Faina)\n\n Documentation/config/format.adoc    |   7 ++\n Documentation/git-format-patch.adoc |  11 +++\n builtin/log.c                       |  91 +++++++++++++++++++++----\n pretty.c                            |  15 +++++\n t/t4014-format-patch.sh             | 101 ++++++++++++++++++++++++++++\n t/t9902-completion.sh               |   1 +\n 6 files changed, 212 insertions(+), 14 deletions(-)\n\n-- \n2.53.0.5.ga216069370\n\n"},{"id":"537352","messageId":"cfed3bddf66ed2fab1f4da896759de1ba086578f.1772232373.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772232373.git.mroik@delayed.space","subject":"[PATCH v5 1/5] pretty.c: add %(count) and %(total) placeholders","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T22:48:11Z","receivedAt":"2026-02-27T22:48:48Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"In many commands we can customize the output through the \"--format\" or\nthe \"--pretty\" options. This patch adds two new placeholders used mainly\nwhen there's a range of commits that we want to show.\n\nCurrently these two placeholders are not usable as they're coupled with\nthe rev_info->nr and rev_info->total fields, fields that are used only\nby the format-patch numbered email subjects.\n\nTeach repo_format_commit_message() the %(count) and %(total)\nplaceholders.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n pretty.c | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git a/pretty.c b/pretty.c\nindex e0646bbc5d..e29bb8b877 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tif (!commit->object.parsed)\n \t\tparse_object(the_repository, &commit->object.oid);\n \n+\tif (starts_with(placeholder, \"(count)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%0*d\", decimal_width(c->pretty_ctx->rev->total),\n+\t\t\t    c->pretty_ctx->rev->nr);\n+\t\treturn 7;\n+\t}\n+\n+\tif (starts_with(placeholder, \"(total)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%d\", c->pretty_ctx->rev->total);\n+\t\treturn 7;\n+\t}\n+\n \tswitch (placeholder[0]) {\n \tcase 'H':\t\t/* commit hash */\n \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));\n-- \n2.53.0.5.ga216069370\n\n"},{"id":"537353","messageId":"dc131c756520a0982d1d6f733dff48a464900ea5.1772232373.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772232373.git.mroik@delayed.space","subject":"[PATCH v5 2/5] format-patch: move cover letter summary generation","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T22:48:12Z","receivedAt":"2026-02-27T22:48:48Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"As of now format-patch allows generation of a template cover letter for\npatch series through \"--cover-letter\".\n\nMove shortlog summary code generation to its own function. This is done\nin preparation to other patches where we enable the user to format the\ncommit list using thier own format string.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 32 ++++++++++++++++++++------------\n 1 file changed, 20 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 5c9a8ef363..0d12272031 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_shortlog_cover_letter(struct shortlog *log,\n+\t\t\t\t\t   struct rev_info *rev,\n+\t\t\t\t\t   struct commit **list,\n+\t\t\t\t\t   int nr)\n+{\n+\tshortlog_init(log);\n+\tlog->wrap_lines = 1;\n+\tlog->wrap = MAIL_DEFAULT_WRAP;\n+\tlog->in1 = 2;\n+\tlog->in2 = 4;\n+\tlog->file = rev->diffopt.file;\n+\tlog->groups = SHORTLOG_GROUP_AUTHOR;\n+\tshortlog_finish_setup(log);\n+\tfor (int i = 0; i < nr; i++)\n+\t\tshortlog_add_commit(log, list[i]);\n+\n+\tshortlog_output(log);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n-\n-\tshortlog_output(&log);\n+\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n-- \n2.53.0.5.ga216069370\n\n"},{"id":"537354","messageId":"d21aab31cce4ed22a4ae9a989ca84f4c66603225.1772232373.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772232373.git.mroik@delayed.space","subject":"[PATCH v5 3/5] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T22:48:13Z","receivedAt":"2026-02-27T22:48:49Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nGive the ability to format-patch to specify an alternative format spec\nthrough the \"--cover-letter-format\" option. This option either takes\n\"shortlog\", which is the current format, or a format spec prefixed with\n\"log:\".\n\nExample:\n    git format-patch --cover-letter \\\n        --cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" HEAD~3\n\n    [1/3] this is a commit summary (Mirko Faina)\n    [2/3] this is another commit summary (Mirko Faina)\n    ...\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 40 +++++++++++++++++++++++++++++++---\n t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++\n t/t9902-completion.sh   |  1 +\n 3 files changed, 86 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 0d12272031..46c8e33773 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,\n \tshortlog_output(log);\n }\n \n+static void generate_commit_list_cover(FILE *cover_file,const char *format,\n+\t\t\t\t       struct commit **list, int n)\n+{\n+\tstruct strbuf commit_line = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\tstruct rev_info rev = REV_INFO_INIT;\n+\n+\tstrbuf_init(&commit_line, 0);\n+\trev.total = n;\n+\tctx.rev = &rev;\n+\tfor (int i = n - 1; i >= 0; i--) {\n+\t\trev.nr = n - i;\n+\t\trepo_format_commit_message(the_repository, list[i], format,\n+\t\t\t\t&commit_line, &ctx);\n+\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n+\t\tstrbuf_reset(&commit_line);\n+\t}\n+\tfprintf(cover_file, \"\\n\");\n+\n+\tstrbuf_release(&commit_line);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n \t\t\t      const char *description_file,\n \t\t\t      const char *branch_name,\n \t\t\t      int quiet,\n-\t\t\t      const struct format_config *cfg)\n+\t\t\t      const struct format_config *cfg,\n+\t\t\t      const char *format)\n {\n \tconst char *committer;\n \tstruct shortlog log;\n@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\tif (skip_prefix(format, \"log:\", &format))\n+\t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n+\telse if (!strcmp(format, \"shortlog\"))\n+\t\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\telse\n+\t\tdie(_(\"'%s' is not a valid format string\"), format);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,\n \tint just_numbers = 0;\n \tint ignore_if_in_upstream = 0;\n \tint cover_letter = -1;\n+\tconst char *cover_letter_fmt = NULL;\n \tint boundary_count = 0;\n \tint no_binary_diff = 0;\n \tint zero_commit = 0;\n@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,\n \t\t\t    N_(\"print patches to standard out\")),\n \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n \t\t\t    N_(\"generate a cover letter\")),\n+\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n+\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n \t\t\t    N_(\"use simple number sequence for output file names\")),\n \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,\n \t\t/* nothing to do */\n \t\tgoto done;\n \ttotal = list.nr;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\n@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,\n \t}\n \trev.numbered_files = just_numbers;\n \trev.patch_suffix = fmt_patch_suffix;\n+\n \tif (cover_letter) {\n \t\tif (cfg.thread)\n \t\t\tgen_message_id(&rev, \"cover\");\n \t\tmake_cover_letter(&rev, !!output_directory,\n \t\t\t\t  origin, list.nr, list.items,\n-\t\t\t\t  description_file, branch_name, quiet, &cfg);\n+\t\t\t\t  description_file, branch_name, quiet, &cfg,\n+\t\t\t\t  cover_letter_fmt);\n \t\tprint_bases(&bases, rev.diffopt.file);\n \t\tprint_signature(signature, rev.diffopt.file);\n \t\ttotal++;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 21d6d0cd9e..458da80721 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '\n \tdone\n '\n \n+test_expect_success 'cover letter with subject, author and count' '\n+\trm -rf patches &&\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expected_success 'cover letter with author and count' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %an\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] A U Thor$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter shortlog' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter --cover-letter-format=shortlog \\\n+\t-o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter no format' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter -o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 964e1f1569..4f760a7468 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '\n \ttest_completion \"git send-email --cov\" <<-\\EOF &&\n \t--cover-from-description=Z\n \t--cover-letter Z\n+\t--cover-letter-format=Z\n \tEOF\n \ttest_completion \"git send-email --val\" <<-\\EOF &&\n \t--validate Z\n-- \n2.53.0.5.ga216069370\n\n"},{"id":"537355","messageId":"6b1ad71470a4d0a83c26b8709ead7e2e3d423dae.1772232373.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772232373.git.mroik@delayed.space","subject":"[PATCH v5 4/5] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T22:48:14Z","receivedAt":"2026-02-27T22:48:49Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Using \"--cover-letter\" we can tell format-patch to generate a cover\nletter, in this cover letter there's a list of commits included in the\npatch series and the format is specified by the \"--cover-letter-format\"\noption. Would be useful if this format could be configured from the\nconfig file instead of always needing to pass it from the command line.\n\nTeach format-patch how to read the format spec for the cover letter from\nthe config files. The variable it should look for is called\nformat.commitListFormat.\n\nPossible values:\n  - commitListFormat is set but no string is passed: it will default to\n    \"[%(count)/%(total)] %s\"\n\n  - if a string is passed: will use it as a format spec. Note that this\n    is either \"shortlog\" or a format spec prefixed by \"log:\"\n    e.g.\"log:%s (%an)\"\n\n  - if commitListFormat is not set: it will default to the shortlog\n    format.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 21 ++++++++++++++++\n t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 74 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 46c8e33773..e8d2e373ec 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -886,6 +886,7 @@ struct format_config {\n \tchar *signature;\n \tchar *signature_file;\n \tenum cover_setting config_cover_letter;\n+\tchar *fmt_cover_letter_commit_list;\n \tchar *config_output_directory;\n \tenum cover_from_description cover_from_description_mode;\n \tint show_notes;\n@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n \tstring_list_clear(&cfg->extra_cc, 0);\n \tstrbuf_release(&cfg->sprefix);\n \tfree(cfg->fmt_patch_suffix);\n+\tfree(cfg->fmt_cover_letter_commit_list);\n }\n \n static enum cover_from_description parse_cover_from_description(const char *arg)\n@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"format.commitlistformat\")) {\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\t\tstrbuf_init(&tmp, 0);\n+\t\tif (value)\n+\t\t\tstrbuf_addstr(&tmp, value);\n+\t\telse\n+\t\t\tstrbuf_addstr(&tmp, \"log:[%(count)/%(total)] %s\");\n+\n+\t\tFREE_AND_NULL(cfg->fmt_cover_letter_commit_list);\n+\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"format.outputdirectory\")) {\n \t\tFREE_AND_NULL(cfg->config_output_directory);\n \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n@@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,\n \t\tgoto done;\n \ttotal = list.nr;\n \n+\tif (!cover_letter_fmt) {\n+\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n+\t\tif (!cover_letter_fmt)\n+\t\t\tcover_letter_fmt = \"shortlog\";\n+\t}\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 458da80721..4891389a53 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '\n \ttest_line_count = 1 result\n '\n \n+test_expect_success 'cover letter config with count, subject and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"log:[%(count)/%(total)] %s (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .* \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config with count and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"log:[%(count)/%(total)] (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set but no format' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tprintf \"\\tcommitlistformat\" >> .git/config &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .*\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set to shortlog' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat shortlog &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat not set' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\tgit config set format.coverletter true &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n-- \n2.53.0.5.ga216069370\n\n"},{"id":"537356","messageId":"a2160693709ff6867ea5db9a944d13091f03bc5d.1772232373.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772232373.git.mroik@delayed.space","subject":"[PATCH v5 5/5] docs: add usage for the cover-letter fmt feature","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-02-27T22:48:15Z","receivedAt":"2026-02-27T22:48:50Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Document the new \"--cover-letter-format\" feature in format-patch and its\nrelated config variable \"format.commitListFormat\".\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n Documentation/config/format.adoc    |  7 +++++++\n Documentation/git-format-patch.adoc | 11 +++++++++++\n 2 files changed, 18 insertions(+)\n\ndiff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc\nindex ab0710e86a..771e84af98 100644\n--- a/Documentation/config/format.adoc\n+++ b/Documentation/config/format.adoc\n@@ -101,6 +101,13 @@ format.coverLetter::\n \tgenerate a cover-letter only when there's more than one patch.\n \tDefault is false.\n \n+format.commitListFormat::\n+\tA format string that specifies how to generate the commit list\n+\tof a cover-letter when format-patch is invoked. This is the\n+\tconfig coupled with `--cover-letter-format` in the format-patch\n+\tcommand and they both accept the same values.\n+\tDefault is shortlog.\n+\n format.outputDirectory::\n \tSet a custom directory to store the resulting files instead of the\n \tcurrent working directory. All directory components will be created.\ndiff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\nindex 9a7807ca71..bdcb5f989c 100644\n--- a/Documentation/git-format-patch.adoc\n+++ b/Documentation/git-format-patch.adoc\n@@ -24,6 +24,7 @@ SYNOPSIS\n \t\t   [(--reroll-count|-v) <n>]\n \t\t   [--to=<email>] [--cc=<email>]\n \t\t   [--[no-]cover-letter] [--quiet]\n+\t\t   [--cover-letter-format=<format-spec>]\n \t\t   [--[no-]encode-email-headers]\n \t\t   [--no-notes | --notes[=<ref>]]\n \t\t   [--interdiff=<previous>]\n@@ -321,6 +322,15 @@ feeding the result to `git send-email`.\n \tcontaining the branch description, shortlog and the overall diffstat.  You can\n \tfill in a description in the file before sending it out.\n \n+--cover-letter-format=<format-spec>::\n+\tSpecify the format in which to generate the commit list of the\n+\tpatch series. This option is available if the user wants to use\n+\tan alternative to the default shortlog format. The accepted\n+\tvalues for format-spec are \"shortlog\" or a format string\n+\tprefixed with `log:`.  \n+\te.g. `log: %s (%an)`  \n+\tThis option is relevant only if a cover letter is generated.\n+\n --encode-email-headers::\n --no-encode-email-headers::\n \tEncode email headers that have non-ASCII characters with\n@@ -452,6 +462,7 @@ with configuration variables.\n \tsignOff = true\n \toutputDirectory = <directory>\n \tcoverLetter = auto\n+\tcommitListFormat = shortlog\n \tcoverFromDescription = auto\n ------------\n \n-- \n2.53.0.5.ga216069370\n\n"},{"id":"538125","messageId":"xmqq7broy4et.fsf@gitster.g","threadId":"65032","inReplyTo":"cover.1772232373.git.mroik@delayed.space","subject":"Re: [PATCH v5 0/5] format-patch: add cover-letter-format option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-06T22:33:14Z","receivedAt":"2026-03-06T22:33:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> I've reconciled the formats between the command line option and the\n> configuration variable. Changes are reflected in the documentation and\n> the tests.\n>\n> Thank you for the review\n>\n> [1/5] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)\n> [2/5] format-patch: move cover letter summary generation (Mirko Faina)\n> [3/5] format-patch: add ability to use alt cover format (Mirko Faina)\n> [4/5] format-patch: add commitListFormat config (Mirko Faina)\n> [5/5] docs: add usage for the cover-letter fmt feature (Mirko Faina)\n\nHow do people find this latest round, which unfortunately haven't\nseen any reactions to?  The earlier rounds have good discussions and\nI do not think this round misses anything discovered and discussed\nso far.  Shall we declare victory and mark the topic for 'next'?\n\nThanks.\n"},{"id":"538127","messageId":"aatZcl0KisQW0PiT@exploit","threadId":"65032","inReplyTo":"xmqq7broy4et.fsf@gitster.g","subject":"Re: [PATCH v5 0/5] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T22:49:36Z","receivedAt":"2026-03-06T22:49:40Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Fri, Mar 06, 2026 at 02:33:14PM -0800, Junio C Hamano wrote:\n> How do people find this latest round, which unfortunately haven't\n> seen any reactions to?  The earlier rounds have good discussions and\n> I do not think this round misses anything discovered and discussed\n> so far.  Shall we declare victory and mark the topic for 'next'?\n\nOn my tree I have a few retouches I made on some commit messages and\nmore importantly on the docs. Other than that I think it is good to go.\n"},{"id":"538128","messageId":"cfed3bddf66ed2fab1f4da896759de1ba086578f.1772837832.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772837832.git.mroik@delayed.space","subject":"[PATCH v6 1/5] pretty.c: add %(count) and %(total) placeholders","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T22:58:44Z","receivedAt":"2026-03-06T22:59:03Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"In many commands we can customize the output through the \"--format\" or\nthe \"--pretty\" options. This patch adds two new placeholders used mainly\nwhen there's a range of commits that we want to show.\n\nCurrently these two placeholders are not usable as they're coupled with\nthe rev_info->nr and rev_info->total fields, fields that are used only\nby the format-patch numbered email subjects.\n\nTeach repo_format_commit_message() the %(count) and %(total)\nplaceholders.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n pretty.c | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git a/pretty.c b/pretty.c\nindex e0646bbc5d..e29bb8b877 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tif (!commit->object.parsed)\n \t\tparse_object(the_repository, &commit->object.oid);\n \n+\tif (starts_with(placeholder, \"(count)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%0*d\", decimal_width(c->pretty_ctx->rev->total),\n+\t\t\t    c->pretty_ctx->rev->nr);\n+\t\treturn 7;\n+\t}\n+\n+\tif (starts_with(placeholder, \"(total)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%d\", c->pretty_ctx->rev->total);\n+\t\treturn 7;\n+\t}\n+\n \tswitch (placeholder[0]) {\n \tcase 'H':\t\t/* commit hash */\n \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));\n-- \n2.53.0.5.g1a4ba6dc33\n\n"},{"id":"538129","messageId":"cover.1772837832.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772232373.git.mroik@delayed.space","subject":"[PATCH v6 0/5] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T22:58:43Z","receivedAt":"2026-03-06T22:59:03Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"From: Mroik <mroik@delayed.space>\n\nLast version\n\n[1/5] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)\n[2/5] format-patch: move cover letter summary generation (Mirko Faina)\n[3/5] format-patch: add ability to use alt cover format (Mirko Faina)\n[4/5] format-patch: add commitListFormat config (Mirko Faina)\n[5/5] docs: add usage for the cover-letter fmt feature (Mirko Faina)\n\n Documentation/config/format.adoc    |   6 ++\n Documentation/git-format-patch.adoc |  11 +++\n builtin/log.c                       |  91 +++++++++++++++++++++----\n pretty.c                            |  15 +++++\n t/t4014-format-patch.sh             | 101 ++++++++++++++++++++++++++++\n t/t9902-completion.sh               |   1 +\n 6 files changed, 211 insertions(+), 14 deletions(-)\n\nRange-diff against v5:\n1:  169db9df4d ! 1:  cfed3bddf6 pretty.c: add %(count) and %(total) placeholders\n    @@ Commit message\n         placeholders.\n     \n         Signed-off-by: Mirko Faina <mroik@delayed.space>\n    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n      ## pretty.c ##\n     @@ pretty.c: static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n2:  c69d6409d5 ! 2:  dc131c7565 format-patch: move cover letter summary generation\n    @@ Commit message\n         commit list using thier own format string.\n     \n         Signed-off-by: Mirko Faina <mroik@delayed.space>\n    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n      ## builtin/log.c ##\n     @@ builtin/log.c: static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n3:  c3a92d8896 ! 3:  316c9e76ee format-patch: add ability to use alt cover format\n    @@ Commit message\n         useful than they'd like, mainly because the shortlog groups commits by\n         author, and gives no obvious chronological order.\n     \n    -    Give the ability to format-patch to specify an alternative format spec\n    +    Give format-patch the ability to specify an alternative format spec\n         through the \"--cover-letter-format\" option. This option either takes\n         \"shortlog\", which is the current format, or a format spec prefixed with\n         \"log:\".\n    @@ Commit message\n             ...\n     \n         Signed-off-by: Mirko Faina <mroik@delayed.space>\n    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n      ## builtin/log.c ##\n     @@ builtin/log.c: static void generate_shortlog_cover_letter(struct shortlog *log,\n      \tshortlog_output(log);\n      }\n      \n    -+static void generate_commit_list_cover(FILE *cover_file,const char *format,\n    ++static void generate_commit_list_cover(FILE *cover_file, const char *format,\n     +\t\t\t\t       struct commit **list, int n)\n     +{\n     +\tstruct strbuf commit_line = STRBUF_INIT;\n4:  a6e4e1ac84 ! 4:  c522f47e5b format-patch: add commitListFormat config\n    @@ Commit message\n             format.\n     \n         Signed-off-by: Mirko Faina <mroik@delayed.space>\n    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n      ## builtin/log.c ##\n     @@ builtin/log.c: struct format_config {\n5:  40d1aa8785 ! 5:  1a4ba6dc33 docs: add usage for the cover-letter fmt feature\n    @@ Metadata\n      ## Commit message ##\n         docs: add usage for the cover-letter fmt feature\n     \n    -    Document the new \"--cover-letter-format\" feature in format-patch and its\n    +    Document the new \"--cover-letter-format\" option in format-patch and its\n         related config variable \"format.commitListFormat\".\n     \n         Signed-off-by: Mirko Faina <mroik@delayed.space>\n    -    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n      ## Documentation/config/format.adoc ##\n     @@ Documentation/config/format.adoc: format.coverLetter::\n    @@ Documentation/config/format.adoc: format.coverLetter::\n      \tDefault is false.\n      \n     +format.commitListFormat::\n    -+\tA format string that specifies how to generate the commit list\n    -+\tof a cover-letter when format-patch is invoked. This is the\n    -+\tconfig coupled with `--cover-letter-format` in the format-patch\n    -+\tcommand and they both accept the same values.\n    -+\tDefault is shortlog.\n    ++\tA format string that specifies how to generate the commit list of a\n    ++\tcover-letter when format-patch is invoked. This config is coupled with\n    ++\tthe `--cover-letter-format` format-patch option command and they both\n    ++\taccept the same values. Default is `shortlog`.\n     +\n      format.outputDirectory::\n      \tSet a custom directory to store the resulting files instead of the\n    @@ Documentation/git-format-patch.adoc: feeding the result to `git send-email`.\n     +--cover-letter-format=<format-spec>::\n     +\tSpecify the format in which to generate the commit list of the\n     +\tpatch series. This option is available if the user wants to use\n    -+\tan alternative to the default shortlog format. The accepted\n    ++\tan alternative to the default `shortlog` format. The accepted\n     +\tvalues for format-spec are \"shortlog\" or a format string\n     +\tprefixed with `log:`.\n     +\te.g. `log: %s (%an)`\n-- \n2.53.0.5.g1a4ba6dc33\n\n"},{"id":"538130","messageId":"dc131c756520a0982d1d6f733dff48a464900ea5.1772837832.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772837832.git.mroik@delayed.space","subject":"[PATCH v6 2/5] format-patch: move cover letter summary generation","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T22:58:45Z","receivedAt":"2026-03-06T22:59:04Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"As of now format-patch allows generation of a template cover letter for\npatch series through \"--cover-letter\".\n\nMove shortlog summary code generation to its own function. This is done\nin preparation to other patches where we enable the user to format the\ncommit list using thier own format string.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 32 ++++++++++++++++++++------------\n 1 file changed, 20 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 5c9a8ef363..0d12272031 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_shortlog_cover_letter(struct shortlog *log,\n+\t\t\t\t\t   struct rev_info *rev,\n+\t\t\t\t\t   struct commit **list,\n+\t\t\t\t\t   int nr)\n+{\n+\tshortlog_init(log);\n+\tlog->wrap_lines = 1;\n+\tlog->wrap = MAIL_DEFAULT_WRAP;\n+\tlog->in1 = 2;\n+\tlog->in2 = 4;\n+\tlog->file = rev->diffopt.file;\n+\tlog->groups = SHORTLOG_GROUP_AUTHOR;\n+\tshortlog_finish_setup(log);\n+\tfor (int i = 0; i < nr; i++)\n+\t\tshortlog_add_commit(log, list[i]);\n+\n+\tshortlog_output(log);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n-\n-\tshortlog_output(&log);\n+\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n-- \n2.53.0.5.g1a4ba6dc33\n\n"},{"id":"538131","messageId":"316c9e76ee49d73aff75b63299c970e9f55f79b6.1772837832.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772837832.git.mroik@delayed.space","subject":"[PATCH v6 3/5] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T22:58:46Z","receivedAt":"2026-03-06T22:59:04Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nGive format-patch the ability to specify an alternative format spec\nthrough the \"--cover-letter-format\" option. This option either takes\n\"shortlog\", which is the current format, or a format spec prefixed with\n\"log:\".\n\nExample:\n    git format-patch --cover-letter \\\n        --cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" HEAD~3\n\n    [1/3] this is a commit summary (Mirko Faina)\n    [2/3] this is another commit summary (Mirko Faina)\n    ...\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 40 +++++++++++++++++++++++++++++++---\n t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++\n t/t9902-completion.sh   |  1 +\n 3 files changed, 86 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 0d12272031..95e5d9755f 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,\n \tshortlog_output(log);\n }\n \n+static void generate_commit_list_cover(FILE *cover_file, const char *format,\n+\t\t\t\t       struct commit **list, int n)\n+{\n+\tstruct strbuf commit_line = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\tstruct rev_info rev = REV_INFO_INIT;\n+\n+\tstrbuf_init(&commit_line, 0);\n+\trev.total = n;\n+\tctx.rev = &rev;\n+\tfor (int i = n - 1; i >= 0; i--) {\n+\t\trev.nr = n - i;\n+\t\trepo_format_commit_message(the_repository, list[i], format,\n+\t\t\t\t&commit_line, &ctx);\n+\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n+\t\tstrbuf_reset(&commit_line);\n+\t}\n+\tfprintf(cover_file, \"\\n\");\n+\n+\tstrbuf_release(&commit_line);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n \t\t\t      const char *description_file,\n \t\t\t      const char *branch_name,\n \t\t\t      int quiet,\n-\t\t\t      const struct format_config *cfg)\n+\t\t\t      const struct format_config *cfg,\n+\t\t\t      const char *format)\n {\n \tconst char *committer;\n \tstruct shortlog log;\n@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\tif (skip_prefix(format, \"log:\", &format))\n+\t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n+\telse if (!strcmp(format, \"shortlog\"))\n+\t\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\telse\n+\t\tdie(_(\"'%s' is not a valid format string\"), format);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,\n \tint just_numbers = 0;\n \tint ignore_if_in_upstream = 0;\n \tint cover_letter = -1;\n+\tconst char *cover_letter_fmt = NULL;\n \tint boundary_count = 0;\n \tint no_binary_diff = 0;\n \tint zero_commit = 0;\n@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,\n \t\t\t    N_(\"print patches to standard out\")),\n \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n \t\t\t    N_(\"generate a cover letter\")),\n+\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n+\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n \t\t\t    N_(\"use simple number sequence for output file names\")),\n \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,\n \t\t/* nothing to do */\n \t\tgoto done;\n \ttotal = list.nr;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\n@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,\n \t}\n \trev.numbered_files = just_numbers;\n \trev.patch_suffix = fmt_patch_suffix;\n+\n \tif (cover_letter) {\n \t\tif (cfg.thread)\n \t\t\tgen_message_id(&rev, \"cover\");\n \t\tmake_cover_letter(&rev, !!output_directory,\n \t\t\t\t  origin, list.nr, list.items,\n-\t\t\t\t  description_file, branch_name, quiet, &cfg);\n+\t\t\t\t  description_file, branch_name, quiet, &cfg,\n+\t\t\t\t  cover_letter_fmt);\n \t\tprint_bases(&bases, rev.diffopt.file);\n \t\tprint_signature(signature, rev.diffopt.file);\n \t\ttotal++;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 21d6d0cd9e..458da80721 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '\n \tdone\n '\n \n+test_expect_success 'cover letter with subject, author and count' '\n+\trm -rf patches &&\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expected_success 'cover letter with author and count' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %an\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] A U Thor$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter shortlog' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter --cover-letter-format=shortlog \\\n+\t-o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter no format' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter -o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 964e1f1569..4f760a7468 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '\n \ttest_completion \"git send-email --cov\" <<-\\EOF &&\n \t--cover-from-description=Z\n \t--cover-letter Z\n+\t--cover-letter-format=Z\n \tEOF\n \ttest_completion \"git send-email --val\" <<-\\EOF &&\n \t--validate Z\n-- \n2.53.0.5.g1a4ba6dc33\n\n"},{"id":"538132","messageId":"c522f47e5b574c0c889c40284c71c36158b6bb6e.1772837832.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772837832.git.mroik@delayed.space","subject":"[PATCH v6 4/5] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T22:58:47Z","receivedAt":"2026-03-06T22:59:05Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Using \"--cover-letter\" we can tell format-patch to generate a cover\nletter, in this cover letter there's a list of commits included in the\npatch series and the format is specified by the \"--cover-letter-format\"\noption. Would be useful if this format could be configured from the\nconfig file instead of always needing to pass it from the command line.\n\nTeach format-patch how to read the format spec for the cover letter from\nthe config files. The variable it should look for is called\nformat.commitListFormat.\n\nPossible values:\n  - commitListFormat is set but no string is passed: it will default to\n    \"[%(count)/%(total)] %s\"\n\n  - if a string is passed: will use it as a format spec. Note that this\n    is either \"shortlog\" or a format spec prefixed by \"log:\"\n    e.g.\"log:%s (%an)\"\n\n  - if commitListFormat is not set: it will default to the shortlog\n    format.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 21 ++++++++++++++++\n t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 74 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 95e5d9755f..5fec0ddaf9 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -886,6 +886,7 @@ struct format_config {\n \tchar *signature;\n \tchar *signature_file;\n \tenum cover_setting config_cover_letter;\n+\tchar *fmt_cover_letter_commit_list;\n \tchar *config_output_directory;\n \tenum cover_from_description cover_from_description_mode;\n \tint show_notes;\n@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n \tstring_list_clear(&cfg->extra_cc, 0);\n \tstrbuf_release(&cfg->sprefix);\n \tfree(cfg->fmt_patch_suffix);\n+\tfree(cfg->fmt_cover_letter_commit_list);\n }\n \n static enum cover_from_description parse_cover_from_description(const char *arg)\n@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"format.commitlistformat\")) {\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\t\tstrbuf_init(&tmp, 0);\n+\t\tif (value)\n+\t\t\tstrbuf_addstr(&tmp, value);\n+\t\telse\n+\t\t\tstrbuf_addstr(&tmp, \"log:[%(count)/%(total)] %s\");\n+\n+\t\tFREE_AND_NULL(cfg->fmt_cover_letter_commit_list);\n+\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"format.outputdirectory\")) {\n \t\tFREE_AND_NULL(cfg->config_output_directory);\n \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n@@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,\n \t\tgoto done;\n \ttotal = list.nr;\n \n+\tif (!cover_letter_fmt) {\n+\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n+\t\tif (!cover_letter_fmt)\n+\t\t\tcover_letter_fmt = \"shortlog\";\n+\t}\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 458da80721..4891389a53 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '\n \ttest_line_count = 1 result\n '\n \n+test_expect_success 'cover letter config with count, subject and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"log:[%(count)/%(total)] %s (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .* \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config with count and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"log:[%(count)/%(total)] (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set but no format' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tprintf \"\\tcommitlistformat\" >> .git/config &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .*\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set to shortlog' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat shortlog &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat not set' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\tgit config set format.coverletter true &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n-- \n2.53.0.5.g1a4ba6dc33\n\n"},{"id":"538133","messageId":"1a4ba6dc338e82df1d58c277031d3ec2ec2ccf83.1772837832.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772837832.git.mroik@delayed.space","subject":"[PATCH v6 5/5] docs: add usage for the cover-letter fmt feature","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T22:58:48Z","receivedAt":"2026-03-06T22:59:06Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Document the new \"--cover-letter-format\" option in format-patch and its\nrelated config variable \"format.commitListFormat\".\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n Documentation/config/format.adoc    |  6 ++++++\n Documentation/git-format-patch.adoc | 11 +++++++++++\n 2 files changed, 17 insertions(+)\n\ndiff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc\nindex ab0710e86a..151e574052 100644\n--- a/Documentation/config/format.adoc\n+++ b/Documentation/config/format.adoc\n@@ -101,6 +101,12 @@ format.coverLetter::\n \tgenerate a cover-letter only when there's more than one patch.\n \tDefault is false.\n \n+format.commitListFormat::\n+\tA format string that specifies how to generate the commit list of a\n+\tcover-letter when format-patch is invoked. This config is coupled with\n+\tthe `--cover-letter-format` format-patch option command and they both\n+\taccept the same values. Default is `shortlog`.\n+\n format.outputDirectory::\n \tSet a custom directory to store the resulting files instead of the\n \tcurrent working directory. All directory components will be created.\ndiff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\nindex 9a7807ca71..42d2633860 100644\n--- a/Documentation/git-format-patch.adoc\n+++ b/Documentation/git-format-patch.adoc\n@@ -24,6 +24,7 @@ SYNOPSIS\n \t\t   [(--reroll-count|-v) <n>]\n \t\t   [--to=<email>] [--cc=<email>]\n \t\t   [--[no-]cover-letter] [--quiet]\n+\t\t   [--cover-letter-format=<format-spec>]\n \t\t   [--[no-]encode-email-headers]\n \t\t   [--no-notes | --notes[=<ref>]]\n \t\t   [--interdiff=<previous>]\n@@ -321,6 +322,15 @@ feeding the result to `git send-email`.\n \tcontaining the branch description, shortlog and the overall diffstat.  You can\n \tfill in a description in the file before sending it out.\n \n+--cover-letter-format=<format-spec>::\n+\tSpecify the format in which to generate the commit list of the\n+\tpatch series. This option is available if the user wants to use\n+\tan alternative to the default `shortlog` format. The accepted\n+\tvalues for format-spec are \"shortlog\" or a format string\n+\tprefixed with `log:`.\n+\te.g. `log: %s (%an)`\n+\tThis option is relevant only if a cover letter is generated.\n+\n --encode-email-headers::\n --no-encode-email-headers::\n \tEncode email headers that have non-ASCII characters with\n@@ -452,6 +462,7 @@ with configuration variables.\n \tsignOff = true\n \toutputDirectory = <directory>\n \tcoverLetter = auto\n+\tcommitListFormat = shortlog\n \tcoverFromDescription = auto\n ------------\n \n-- \n2.53.0.5.g1a4ba6dc33\n\n"},{"id":"538136","messageId":"xmqq8qc4wnqa.fsf@gitster.g","threadId":"65032","inReplyTo":"1a4ba6dc338e82df1d58c277031d3ec2ec2ccf83.1772837832.git.mroik@delayed.space","subject":"Re: [PATCH v6 5/5] docs: add usage for the cover-letter fmt feature","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-06T23:18:53Z","receivedAt":"2026-03-06T23:18:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Document the new \"--cover-letter-format\" option in format-patch and its\n> related config variable \"format.commitListFormat\".\n>\n> Signed-off-by: Mirko Faina <mroik@delayed.space>\n> ---\n>  Documentation/config/format.adoc    |  6 ++++++\n>  Documentation/git-format-patch.adoc | 11 +++++++++++\n>  2 files changed, 17 insertions(+)\n>\n> diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc\n> index ab0710e86a..151e574052 100644\n> --- a/Documentation/config/format.adoc\n> +++ b/Documentation/config/format.adoc\n> @@ -101,6 +101,12 @@ format.coverLetter::\n>  \tgenerate a cover-letter only when there's more than one patch.\n>  \tDefault is false.\n>  \n> +format.commitListFormat::\n> +\tA format string that specifies how to generate the commit list of a\n> +\tcover-letter when format-patch is invoked. This config is coupled with\n> +\tthe `--cover-letter-format` format-patch option command and they both\n> +\taccept the same values. Default is `shortlog`.\n\nLet's not call \"configuration variable\" a \"config\".  When a variable\ngives the default value for a command line option, we usually do not\nsay \"coupled with\".\n\n    When the `--cover-letter-format` option is not given,\n    `format-patch` uses the value of this variable to decide how to\n    format the title of each commit.  Default to `shortlog`.\n\nperhaps?\n\n> diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\n> index 9a7807ca71..42d2633860 100644\n> --- a/Documentation/git-format-patch.adoc\n> +++ b/Documentation/git-format-patch.adoc\n> @@ -24,6 +24,7 @@ SYNOPSIS\n>  \t\t   [(--reroll-count|-v) <n>]\n>  \t\t   [--to=<email>] [--cc=<email>]\n>  \t\t   [--[no-]cover-letter] [--quiet]\n> +\t\t   [--cover-letter-format=<format-spec>]\n>  \t\t   [--[no-]encode-email-headers]\n>  \t\t   [--no-notes | --notes[=<ref>]]\n>  \t\t   [--interdiff=<previous>]\n> @@ -321,6 +322,15 @@ feeding the result to `git send-email`.\n>  \tcontaining the branch description, shortlog and the overall diffstat.  You can\n>  \tfill in a description in the file before sending it out.\n>  \n> +--cover-letter-format=<format-spec>::\n> +\tSpecify the format in which to generate the commit list of the\n> +\tpatch series. This option is available if the user wants to use\n> +\tan alternative to the default `shortlog` format. The accepted\n> +\tvalues for format-spec are \"shortlog\" or a format string\n> +\tprefixed with `log:`.\n> +\te.g. `log: %s (%an)`\n> +\tThis option is relevant only if a cover letter is generated.\n\nSomewhere in this paragraph, it must mention that format.commitListFormat\ngives the default value when this command line option is not given.\n\nEverything else in the changes since the previous iteration,\nincluding the parameter list to generate_commit_list_cover() that\nshows the level of attention to details, looked great to me.\n\nThanks.\n"},{"id":"538139","messageId":"cover.1772839973.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772837832.git.mroik@delayed.space","subject":"[PATCH v7 0/5] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T23:34:39Z","receivedAt":"2026-03-06T23:35:04Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"I've applied the changes you suggested.\nThank you for the review\n\n[1/5] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)\n[2/5] format-patch: move cover letter summary generation (Mirko Faina)\n[3/5] format-patch: add ability to use alt cover format (Mirko Faina)\n[4/5] format-patch: add commitListFormat config (Mirko Faina)\n[5/5] docs: add usage for the cover-letter fmt feature (Mirko Faina)\n\n Documentation/config/format.adoc    |   5 ++\n Documentation/git-format-patch.adoc |  13 ++++\n builtin/log.c                       |  91 +++++++++++++++++++++----\n pretty.c                            |  15 +++++\n t/t4014-format-patch.sh             | 101 ++++++++++++++++++++++++++++\n t/t9902-completion.sh               |   1 +\n 6 files changed, 212 insertions(+), 14 deletions(-)\n\n-- \n2.53.0.5.gbe7197aef5\n\n"},{"id":"538140","messageId":"cfed3bddf66ed2fab1f4da896759de1ba086578f.1772839973.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772839973.git.mroik@delayed.space","subject":"[PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T23:34:40Z","receivedAt":"2026-03-06T23:35:04Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"In many commands we can customize the output through the \"--format\" or\nthe \"--pretty\" options. This patch adds two new placeholders used mainly\nwhen there's a range of commits that we want to show.\n\nCurrently these two placeholders are not usable as they're coupled with\nthe rev_info->nr and rev_info->total fields, fields that are used only\nby the format-patch numbered email subjects.\n\nTeach repo_format_commit_message() the %(count) and %(total)\nplaceholders.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n pretty.c | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git a/pretty.c b/pretty.c\nindex e0646bbc5d..e29bb8b877 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tif (!commit->object.parsed)\n \t\tparse_object(the_repository, &commit->object.oid);\n \n+\tif (starts_with(placeholder, \"(count)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%0*d\", decimal_width(c->pretty_ctx->rev->total),\n+\t\t\t    c->pretty_ctx->rev->nr);\n+\t\treturn 7;\n+\t}\n+\n+\tif (starts_with(placeholder, \"(total)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n+\t\tstrbuf_addf(sb, \"%d\", c->pretty_ctx->rev->total);\n+\t\treturn 7;\n+\t}\n+\n \tswitch (placeholder[0]) {\n \tcase 'H':\t\t/* commit hash */\n \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));\n-- \n2.53.0.5.gbe7197aef5\n\n"},{"id":"538141","messageId":"dc131c756520a0982d1d6f733dff48a464900ea5.1772839973.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772839973.git.mroik@delayed.space","subject":"[PATCH v7 2/5] format-patch: move cover letter summary generation","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T23:34:41Z","receivedAt":"2026-03-06T23:35:05Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"As of now format-patch allows generation of a template cover letter for\npatch series through \"--cover-letter\".\n\nMove shortlog summary code generation to its own function. This is done\nin preparation to other patches where we enable the user to format the\ncommit list using thier own format string.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 32 ++++++++++++++++++++------------\n 1 file changed, 20 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 5c9a8ef363..0d12272031 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_shortlog_cover_letter(struct shortlog *log,\n+\t\t\t\t\t   struct rev_info *rev,\n+\t\t\t\t\t   struct commit **list,\n+\t\t\t\t\t   int nr)\n+{\n+\tshortlog_init(log);\n+\tlog->wrap_lines = 1;\n+\tlog->wrap = MAIL_DEFAULT_WRAP;\n+\tlog->in1 = 2;\n+\tlog->in2 = 4;\n+\tlog->file = rev->diffopt.file;\n+\tlog->groups = SHORTLOG_GROUP_AUTHOR;\n+\tshortlog_finish_setup(log);\n+\tfor (int i = 0; i < nr; i++)\n+\t\tshortlog_add_commit(log, list[i]);\n+\n+\tshortlog_output(log);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n-\n-\tshortlog_output(&log);\n+\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n-- \n2.53.0.5.gbe7197aef5\n\n"},{"id":"538142","messageId":"316c9e76ee49d73aff75b63299c970e9f55f79b6.1772839973.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772839973.git.mroik@delayed.space","subject":"[PATCH v7 3/5] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T23:34:42Z","receivedAt":"2026-03-06T23:35:06Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nGive format-patch the ability to specify an alternative format spec\nthrough the \"--cover-letter-format\" option. This option either takes\n\"shortlog\", which is the current format, or a format spec prefixed with\n\"log:\".\n\nExample:\n    git format-patch --cover-letter \\\n        --cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" HEAD~3\n\n    [1/3] this is a commit summary (Mirko Faina)\n    [2/3] this is another commit summary (Mirko Faina)\n    ...\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 40 +++++++++++++++++++++++++++++++---\n t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++\n t/t9902-completion.sh   |  1 +\n 3 files changed, 86 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 0d12272031..95e5d9755f 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,\n \tshortlog_output(log);\n }\n \n+static void generate_commit_list_cover(FILE *cover_file, const char *format,\n+\t\t\t\t       struct commit **list, int n)\n+{\n+\tstruct strbuf commit_line = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\tstruct rev_info rev = REV_INFO_INIT;\n+\n+\tstrbuf_init(&commit_line, 0);\n+\trev.total = n;\n+\tctx.rev = &rev;\n+\tfor (int i = n - 1; i >= 0; i--) {\n+\t\trev.nr = n - i;\n+\t\trepo_format_commit_message(the_repository, list[i], format,\n+\t\t\t\t&commit_line, &ctx);\n+\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n+\t\tstrbuf_reset(&commit_line);\n+\t}\n+\tfprintf(cover_file, \"\\n\");\n+\n+\tstrbuf_release(&commit_line);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n \t\t\t      const char *description_file,\n \t\t\t      const char *branch_name,\n \t\t\t      int quiet,\n-\t\t\t      const struct format_config *cfg)\n+\t\t\t      const struct format_config *cfg,\n+\t\t\t      const char *format)\n {\n \tconst char *committer;\n \tstruct shortlog log;\n@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\tif (skip_prefix(format, \"log:\", &format))\n+\t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n+\telse if (!strcmp(format, \"shortlog\"))\n+\t\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\telse\n+\t\tdie(_(\"'%s' is not a valid format string\"), format);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,\n \tint just_numbers = 0;\n \tint ignore_if_in_upstream = 0;\n \tint cover_letter = -1;\n+\tconst char *cover_letter_fmt = NULL;\n \tint boundary_count = 0;\n \tint no_binary_diff = 0;\n \tint zero_commit = 0;\n@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,\n \t\t\t    N_(\"print patches to standard out\")),\n \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n \t\t\t    N_(\"generate a cover letter\")),\n+\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n+\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n \t\t\t    N_(\"use simple number sequence for output file names\")),\n \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,\n \t\t/* nothing to do */\n \t\tgoto done;\n \ttotal = list.nr;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\n@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,\n \t}\n \trev.numbered_files = just_numbers;\n \trev.patch_suffix = fmt_patch_suffix;\n+\n \tif (cover_letter) {\n \t\tif (cfg.thread)\n \t\t\tgen_message_id(&rev, \"cover\");\n \t\tmake_cover_letter(&rev, !!output_directory,\n \t\t\t\t  origin, list.nr, list.items,\n-\t\t\t\t  description_file, branch_name, quiet, &cfg);\n+\t\t\t\t  description_file, branch_name, quiet, &cfg,\n+\t\t\t\t  cover_letter_fmt);\n \t\tprint_bases(&bases, rev.diffopt.file);\n \t\tprint_signature(signature, rev.diffopt.file);\n \t\ttotal++;\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 21d6d0cd9e..458da80721 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '\n \tdone\n '\n \n+test_expect_success 'cover letter with subject, author and count' '\n+\trm -rf patches &&\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expected_success 'cover letter with author and count' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"log:[%(count)/%(total)] %an\" -o patches HEAD~1 &&\n+\tgrep \"^\\[1/1\\] A U Thor$\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter shortlog' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter --cover-letter-format=shortlog \\\n+\t-o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter no format' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter -o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 964e1f1569..4f760a7468 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '\n \ttest_completion \"git send-email --cov\" <<-\\EOF &&\n \t--cover-from-description=Z\n \t--cover-letter Z\n+\t--cover-letter-format=Z\n \tEOF\n \ttest_completion \"git send-email --val\" <<-\\EOF &&\n \t--validate Z\n-- \n2.53.0.5.gbe7197aef5\n\n"},{"id":"538143","messageId":"c522f47e5b574c0c889c40284c71c36158b6bb6e.1772839973.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772839973.git.mroik@delayed.space","subject":"[PATCH v7 4/5] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T23:34:43Z","receivedAt":"2026-03-06T23:35:06Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Using \"--cover-letter\" we can tell format-patch to generate a cover\nletter, in this cover letter there's a list of commits included in the\npatch series and the format is specified by the \"--cover-letter-format\"\noption. Would be useful if this format could be configured from the\nconfig file instead of always needing to pass it from the command line.\n\nTeach format-patch how to read the format spec for the cover letter from\nthe config files. The variable it should look for is called\nformat.commitListFormat.\n\nPossible values:\n  - commitListFormat is set but no string is passed: it will default to\n    \"[%(count)/%(total)] %s\"\n\n  - if a string is passed: will use it as a format spec. Note that this\n    is either \"shortlog\" or a format spec prefixed by \"log:\"\n    e.g.\"log:%s (%an)\"\n\n  - if commitListFormat is not set: it will default to the shortlog\n    format.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c           | 21 ++++++++++++++++\n t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 74 insertions(+)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 95e5d9755f..5fec0ddaf9 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -886,6 +886,7 @@ struct format_config {\n \tchar *signature;\n \tchar *signature_file;\n \tenum cover_setting config_cover_letter;\n+\tchar *fmt_cover_letter_commit_list;\n \tchar *config_output_directory;\n \tenum cover_from_description cover_from_description_mode;\n \tint show_notes;\n@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n \tstring_list_clear(&cfg->extra_cc, 0);\n \tstrbuf_release(&cfg->sprefix);\n \tfree(cfg->fmt_patch_suffix);\n+\tfree(cfg->fmt_cover_letter_commit_list);\n }\n \n static enum cover_from_description parse_cover_from_description(const char *arg)\n@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"format.commitlistformat\")) {\n+\t\tstruct strbuf tmp = STRBUF_INIT;\n+\t\tstrbuf_init(&tmp, 0);\n+\t\tif (value)\n+\t\t\tstrbuf_addstr(&tmp, value);\n+\t\telse\n+\t\t\tstrbuf_addstr(&tmp, \"log:[%(count)/%(total)] %s\");\n+\n+\t\tFREE_AND_NULL(cfg->fmt_cover_letter_commit_list);\n+\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n+\t\tstrbuf_release(&tmp);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"format.outputdirectory\")) {\n \t\tFREE_AND_NULL(cfg->config_output_directory);\n \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n@@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,\n \t\tgoto done;\n \ttotal = list.nr;\n \n+\tif (!cover_letter_fmt) {\n+\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n+\t\tif (!cover_letter_fmt)\n+\t\t\tcover_letter_fmt = \"shortlog\";\n+\t}\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 458da80721..4891389a53 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '\n \ttest_line_count = 1 result\n '\n \n+test_expect_success 'cover letter config with count, subject and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"log:[%(count)/%(total)] %s (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .* \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config with count and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"log:[%(count)/%(total)] (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set but no format' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tprintf \"\\tcommitlistformat\" >> .git/config &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .*\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set to shortlog' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat shortlog &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat not set' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\tgit config set format.coverletter true &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n-- \n2.53.0.5.gbe7197aef5\n\n"},{"id":"538144","messageId":"be7197aef5a7bc3de06216054fee6ee67e65cc33.1772839973.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772839973.git.mroik@delayed.space","subject":"[PATCH v7 5/5] docs: add usage for the cover-letter fmt feature","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-06T23:34:44Z","receivedAt":"2026-03-06T23:35:09Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Document the new \"--cover-letter-format\" option in format-patch and its\nrelated configuration variable \"format.commitListFormat\".\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n Documentation/config/format.adoc    |  5 +++++\n Documentation/git-format-patch.adoc | 13 +++++++++++++\n 2 files changed, 18 insertions(+)\n\ndiff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc\nindex ab0710e86a..ea5ec5df7a 100644\n--- a/Documentation/config/format.adoc\n+++ b/Documentation/config/format.adoc\n@@ -101,6 +101,11 @@ format.coverLetter::\n \tgenerate a cover-letter only when there's more than one patch.\n \tDefault is false.\n \n+format.commitListFormat::\n+\tWhen the `--cover-letter-format` option is not given, `format-patch`\n+\tuses the value of this variable to decide how to format the title of\n+\teach commit. Default to `shortlog`.\n+\n format.outputDirectory::\n \tSet a custom directory to store the resulting files instead of the\n \tcurrent working directory. All directory components will be created.\ndiff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\nindex 9a7807ca71..668330a015 100644\n--- a/Documentation/git-format-patch.adoc\n+++ b/Documentation/git-format-patch.adoc\n@@ -24,6 +24,7 @@ SYNOPSIS\n \t\t   [(--reroll-count|-v) <n>]\n \t\t   [--to=<email>] [--cc=<email>]\n \t\t   [--[no-]cover-letter] [--quiet]\n+\t\t   [--cover-letter-format=<format-spec>]\n \t\t   [--[no-]encode-email-headers]\n \t\t   [--no-notes | --notes[=<ref>]]\n \t\t   [--interdiff=<previous>]\n@@ -321,6 +322,17 @@ feeding the result to `git send-email`.\n \tcontaining the branch description, shortlog and the overall diffstat.  You can\n \tfill in a description in the file before sending it out.\n \n+--cover-letter-format=<format-spec>::\n+\tSpecify the format in which to generate the commit list of the\n+\tpatch series. This option is available if the user wants to use\n+\tan alternative to the default `shortlog` format. The accepted\n+\tvalues for format-spec are \"shortlog\" or a format string\n+\tprefixed with `log:`.\n+\te.g. `log: %s (%an)`\n+\tIf defined, defaults to the `format.commitListFormat` configuration\n+\tvariable.\n+\tThis option is relevant only if a cover letter is generated.\n+\n --encode-email-headers::\n --no-encode-email-headers::\n \tEncode email headers that have non-ASCII characters with\n@@ -452,6 +464,7 @@ with configuration variables.\n \tsignOff = true\n \toutputDirectory = <directory>\n \tcoverLetter = auto\n+\tcommitListFormat = shortlog\n \tcoverFromDescription = auto\n ------------\n \n-- \n2.53.0.5.gbe7197aef5\n\n"},{"id":"538376","messageId":"CAKPyHN1tCzX0Yqd-5KeWqBEC=nCkAySn9q7q+TY08XHqztjMkw@mail.gmail.com","threadId":"65032","inReplyTo":"be7197aef5a7bc3de06216054fee6ee67e65cc33.1772839973.git.mroik@delayed.space","subject":"Re: [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature","fromName":"Bert Wesarg","fromEmail":"bert.wesarg@googlemail.com","sentAt":"2026-03-10T09:51:17Z","receivedAt":"2026-03-10T09:51:32Z","isPatch":true,"sender":{"key":"bert.wesarg@googlemail.com","avatar":"https://avatars.githubusercontent.com/u/111934?v=4"},"body":"On Sat, Mar 7, 2026 at 12:38 AM Mirko Faina <mroik@delayed.space> wrote:\n>\n> Document the new \"--cover-letter-format\" option in format-patch and its\n> related configuration variable \"format.commitListFormat\".\n>\n> Signed-off-by: Mirko Faina <mroik@delayed.space>\n> ---\n>  Documentation/config/format.adoc    |  5 +++++\n>  Documentation/git-format-patch.adoc | 13 +++++++++++++\n>  2 files changed, 18 insertions(+)\n>\n> diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc\n> index ab0710e86a..ea5ec5df7a 100644\n> --- a/Documentation/config/format.adoc\n> +++ b/Documentation/config/format.adoc\n> @@ -101,6 +101,11 @@ format.coverLetter::\n>         generate a cover-letter only when there's more than one patch.\n>         Default is false.\n>\n> +format.commitListFormat::\n> +       When the `--cover-letter-format` option is not given, `format-patch`\n> +       uses the value of this variable to decide how to format the title of\n> +       each commit. Default to `shortlog`.\n> +\n\n\"Defaults to `shortlog`.\"\n\nor\n\n\"Default is `shortlog`.\"\n\n4 to 1 for the former style in that file.\n\nAnd it looks like that other one is right above. So maybe change this\ntoo, so its always \"Defaults to …\"?\n\nBert\n\n>  format.outputDirectory::\n>         Set a custom directory to store the resulting files instead of the\n>         current working directory. All directory components will be created.\n> diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\n> index 9a7807ca71..668330a015 100644\n> --- a/Documentation/git-format-patch.adoc\n> +++ b/Documentation/git-format-patch.adoc\n> @@ -24,6 +24,7 @@ SYNOPSIS\n>                    [(--reroll-count|-v) <n>]\n>                    [--to=<email>] [--cc=<email>]\n>                    [--[no-]cover-letter] [--quiet]\n> +                  [--cover-letter-format=<format-spec>]\n>                    [--[no-]encode-email-headers]\n>                    [--no-notes | --notes[=<ref>]]\n>                    [--interdiff=<previous>]\n> @@ -321,6 +322,17 @@ feeding the result to `git send-email`.\n>         containing the branch description, shortlog and the overall diffstat.  You can\n>         fill in a description in the file before sending it out.\n>\n> +--cover-letter-format=<format-spec>::\n> +       Specify the format in which to generate the commit list of the\n> +       patch series. This option is available if the user wants to use\n> +       an alternative to the default `shortlog` format. The accepted\n> +       values for format-spec are \"shortlog\" or a format string\n> +       prefixed with `log:`.\n> +       e.g. `log: %s (%an)`\n> +       If defined, defaults to the `format.commitListFormat` configuration\n> +       variable.\n> +       This option is relevant only if a cover letter is generated.\n> +\n>  --encode-email-headers::\n>  --no-encode-email-headers::\n>         Encode email headers that have non-ASCII characters with\n> @@ -452,6 +464,7 @@ with configuration variables.\n>         signOff = true\n>         outputDirectory = <directory>\n>         coverLetter = auto\n> +       commitListFormat = shortlog\n>         coverFromDescription = auto\n>  ------------\n>\n> --\n> 2.53.0.5.gbe7197aef5\n>\n>\n"},{"id":"538448","messageId":"8855efd5-a53a-47fc-8708-4cd6d416e441@gmail.com","threadId":"65032","inReplyTo":"cfed3bddf66ed2fab1f4da896759de1ba086578f.1772839973.git.mroik@delayed.space","subject":"Re: [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-03-10T14:32:54Z","receivedAt":"2026-03-10T14:32:57Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 06/03/2026 23:34, Mirko Faina wrote:\n> diff --git a/pretty.c b/pretty.c\n> index e0646bbc5d..e29bb8b877 100644\n> --- a/pretty.c\n> +++ b/pretty.c\n> @@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n>   \tif (!commit->object.parsed)\n>   \t\tparse_object(the_repository, &commit->object.oid);\n>   \n> +\tif (starts_with(placeholder, \"(count)\")) {\n> +\t\tif (!c->pretty_ctx->rev)\n> +\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n\n From the user's point of view it would be more helpful if this message \ntold them which format specifier isn't supported\n\n\tdie(_(\"%s is not supported by this command\"), \"%(count)\");\n\nNormally we do not add code that cannot be exercised so you may want to \nsquash this change into a later patch that adds support and \ndocumentation for the new specifiers.\n\nThanks\n\nPhillip\n\n> +\t\tstrbuf_addf(sb, \"%0*d\", decimal_width(c->pretty_ctx->rev->total),\n> +\t\t\t    c->pretty_ctx->rev->nr);\n> +\t\treturn 7;\n> +\t}\n> +\n> +\tif (starts_with(placeholder, \"(total)\")) {\n> +\t\tif (!c->pretty_ctx->rev)\n> +\t\t\tdie(_(\"this format specifier can't be used with this command\"));\n> +\t\tstrbuf_addf(sb, \"%d\", c->pretty_ctx->rev->total);\n> +\t\treturn 7;\n> +\t}\n> +\n>   \tswitch (placeholder[0]) {\n>   \tcase 'H':\t\t/* commit hash */\n>   \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));\n\n"},{"id":"538449","messageId":"8f49d551-9741-4461-b076-3caba75e6122@gmail.com","threadId":"65032","inReplyTo":"316c9e76ee49d73aff75b63299c970e9f55f79b6.1772839973.git.mroik@delayed.space","subject":"Re: [PATCH v7 3/5] format-patch: add ability to use alt cover format","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-03-10T14:33:46Z","receivedAt":"2026-03-10T14:33:50Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 06/03/2026 23:34, Mirko Faina wrote:\n> Often when sending patch series there's a need to clarify to the\n> reviewer what's the purpose of said series, since it might be difficult\n> to understand it from reading the commits messages one by one.\n> \n> \"git format-patch\" provides the useful \"--cover-letter\" flag to declare\n> if we want it to generate a template for us to use. By default it will\n> generate a \"git shortlog\" of the changes, which developers find less\n> useful than they'd like, mainly because the shortlog groups commits by\n> author, and gives no obvious chronological order.\n\nI think we probably care more about topological order for format-patch. \nIn practice that's the same as chronological order by commit date, but \nit does not necessarily match chronological order by author date.\n\n> Give format-patch the ability to specify an alternative format spec\n> through the \"--cover-letter-format\" option. This option either takes\n> \"shortlog\", which is the current format, or a format spec prefixed with\n> \"log:\".\n\nThat sounds like a nice improvement over using the shortlog output. \nHowever it is rather cumbersome to have to type \"log:\" each time. As \n--cover-letter-format=shortlog is a nonsensical format I don't think we \nneed to require the \"log:\" prefix. --no-cover-letter-format should \nbehave like --cover-letter-format=shortlog.\n\n> Example:\n>      git format-patch --cover-letter \\\n>          --cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" HEAD~3\n> \n>      [1/3] this is a commit summary (Mirko Faina)\n>      [2/3] this is another commit summary (Mirko Faina)\n>      ...\n> \n> Signed-off-by: Mirko Faina <mroik@delayed.space>\n> ---\n>   builtin/log.c           | 40 +++++++++++++++++++++++++++++++---\n>   t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++\n>   t/t9902-completion.sh   |  1 +\n>   3 files changed, 86 insertions(+), 3 deletions(-)\n> \n> diff --git a/builtin/log.c b/builtin/log.c\n> index 0d12272031..95e5d9755f 100644\n> --- a/builtin/log.c\n> +++ b/builtin/log.c\n> @@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,\n>   \tshortlog_output(log);\n>   }\n>   \n> +static void generate_commit_list_cover(FILE *cover_file, const char *format,\n> +\t\t\t\t       struct commit **list, int n)\n> +{\n> +\tstruct strbuf commit_line = STRBUF_INIT;\n> +\tstruct pretty_print_context ctx = {0};\n> +\tstruct rev_info rev = REV_INFO_INIT;\n> +\n> +\tstrbuf_init(&commit_line, 0);\n\nThis is unnecessary as commit_line is initialized in the declaration above.\n\n> +\trev.total = n;\n> +\tctx.rev = &rev;\n> +\tfor (int i = n - 1; i >= 0; i--) {\n> +\t\trev.nr = n - i;\n > +\t\trepo_format_commit_message(the_repository, list[i], format,\n > +\t\t\t\t&commit_line, &ctx);\n\nThis loop is a bit confusing, I wonder if it would be simpler to count up\n\tfor (int i = 1; i <= n; i++) {\n\t\trev.nr = i\n\t\trepo_format_commit_message(the_repository, list[n - i], ...);\n\nThe shortlog sets some wrapping and indent options, do we want to do \nsomething similar here?\n> @@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,\n>   \t\t/* nothing to do */\n>   \t\tgoto done;\n>   \ttotal = list.nr;\n> +\n\nPlease don't mix unrelated whitespace changes in with your changes.\n\n>   \tif (cover_letter == -1) {\n>   \t\tif (cfg.config_cover_letter == COVER_AUTO)\n>   \t\t\tcover_letter = (total > 1);\n> @@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,\n>   \t}\n>   \trev.numbered_files = just_numbers;\n>   \trev.patch_suffix = fmt_patch_suffix;\n> +\n\nThe same here\n\n> +test_expect_success 'cover letter with subject, author and count' '\n> +\trm -rf patches &&\n> +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> +\ttouch test_file &&\n> +\tgit add test_file &&\n> +\tgit commit -m \"This is a subject\" &&\n> +\tgit format-patch --cover-letter \\\n> +\t--cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n> +\tgrep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch >result &&\n\nusing test_grep here would make it easier to debug test failures.\n\n> +\ttest_line_count = 1 result\n> +'\n> +\n> +test_expected_success 'cover letter with author and count' '\n> +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> +\ttouch test_file &&\n> +\tgit add test_file &&\n> +\tgit commit -m \"This is a subject\" &&\n> +\tgit format-patch --cover-letter \\\n> +\t--cover-letter-format=\"log:[%(count)/%(total)] %an\" -o patches HEAD~1 &&\n> +\tgrep \"^\\[1/1\\] A U Thor$\" patches/0000-cover-letter.patch >result &&\n\nI'm not clear what new coverage this test adds\n\n> +\ttest_line_count = 1 result\n> +'\n> +\n> +test_expect_success 'cover letter shortlog' '\n> +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> +\ttouch test_file &&\n> +\tgit add test_file &&\n> +\tgit commit -m \"This is a subject\" &&\n> +\tgit format-patch --cover-letter --cover-letter-format=shortlog \\\n> +\t-o patches HEAD~1 &&\n> +\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n\nThis just checks that the author name appears in the coverletter, not \nthat the patches are formatted with shortlog.\n\n> +\ttest_line_count = 1 result\n> +'\n> +\n> +test_expect_success 'cover letter no format' '\n> +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> +\ttouch test_file &&\n> +\tgit add test_file &&\n> +\tgit commit -m \"This is a subject\" &&\n> +\tgit format-patch --cover-letter -o patches HEAD~1 &&\n> +\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n\nDon't we already have test coverage for the case where \n--cover-letter-format isn't given? Testing that --no-cover-letter-format \nworks as expected would be useful.\n\nI think this is a useful improvement to the cover letter generated by \n\"git format-patch\"\n\nThanks\n\nPhillip\n\n> +\ttest_line_count = 1 result\n> +'\n> +\n>   test_expect_success 'reroll count' '\n>   \trm -fr patches &&\n>   \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\n> index 964e1f1569..4f760a7468 100755\n> --- a/t/t9902-completion.sh\n> +++ b/t/t9902-completion.sh\n> @@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '\n>   \ttest_completion \"git send-email --cov\" <<-\\EOF &&\n>   \t--cover-from-description=Z\n>   \t--cover-letter Z\n> +\t--cover-letter-format=Z\n>   \tEOF\n>   \ttest_completion \"git send-email --val\" <<-\\EOF &&\n>   \t--validate Z\n\n"},{"id":"538450","messageId":"6b160915-1cdf-48b5-abe4-3efd0771598e@gmail.com","threadId":"65032","inReplyTo":"c522f47e5b574c0c889c40284c71c36158b6bb6e.1772839973.git.mroik@delayed.space","subject":"Re: [PATCH v7 4/5] format-patch: add commitListFormat config","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-03-10T14:34:01Z","receivedAt":"2026-03-10T14:34:04Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 06/03/2026 23:34, Mirko Faina wrote:\n> Using \"--cover-letter\" we can tell format-patch to generate a cover\n> letter, in this cover letter there's a list of commits included in the\n> patch series and the format is specified by the \"--cover-letter-format\"\n> option. Would be useful if this format could be configured from the\n> config file instead of always needing to pass it from the command line.\n> \n> Teach format-patch how to read the format spec for the cover letter from\n> the config files. The variable it should look for is called\n> format.commitListFormat.\n> \n> Possible values:\n>    - commitListFormat is set but no string is passed: it will default to\n>      \"[%(count)/%(total)] %s\"\n\nIt is unusual for an empty config value to mean something different from \nit not being set. The reason for this is that it allows\n\n     git -c config.key some-command\n\nto act as though config.key was not set.\n\nPerhaps we should use the value \"default\" to generate a default format. \nIt would be nice to support a default format on the commandline as well.\n\n>    - if a string is passed: will use it as a format spec. Note that this\n>      is either \"shortlog\" or a format spec prefixed by \"log:\"\n>      e.g.\"log:%s (%an)\"\n\nHaving the config value behave like --cover-letter-format=<value> is \nsensible\n\n>    - if commitListFormat is not set: it will default to the shortlog\n>      format.\n\nmakes sense\n\nThanks\n\nPhillip\n\n> Signed-off-by: Mirko Faina <mroik@delayed.space>\n> ---\n>   builtin/log.c           | 21 ++++++++++++++++\n>   t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++\n>   2 files changed, 74 insertions(+)\n> \n> diff --git a/builtin/log.c b/builtin/log.c\n> index 95e5d9755f..5fec0ddaf9 100644\n> --- a/builtin/log.c\n> +++ b/builtin/log.c\n> @@ -886,6 +886,7 @@ struct format_config {\n>   \tchar *signature;\n>   \tchar *signature_file;\n>   \tenum cover_setting config_cover_letter;\n> +\tchar *fmt_cover_letter_commit_list;\n>   \tchar *config_output_directory;\n>   \tenum cover_from_description cover_from_description_mode;\n>   \tint show_notes;\n> @@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n>   \tstring_list_clear(&cfg->extra_cc, 0);\n>   \tstrbuf_release(&cfg->sprefix);\n>   \tfree(cfg->fmt_patch_suffix);\n> +\tfree(cfg->fmt_cover_letter_commit_list);\n>   }\n>   \n>   static enum cover_from_description parse_cover_from_description(const char *arg)\n> @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n>   \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n>   \t\treturn 0;\n>   \t}\n> +\tif (!strcmp(var, \"format.commitlistformat\")) {\n> +\t\tstruct strbuf tmp = STRBUF_INIT;\n> +\t\tstrbuf_init(&tmp, 0);\n> +\t\tif (value)\n> +\t\t\tstrbuf_addstr(&tmp, value);\n> +\t\telse\n> +\t\t\tstrbuf_addstr(&tmp, \"log:[%(count)/%(total)] %s\");\n> +\n> +\t\tFREE_AND_NULL(cfg->fmt_cover_letter_commit_list);\n> +\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n\n\n\n> +\t\tstrbuf_release(&tmp);\n> +\t\treturn 0;\n> +\t}\n>   \tif (!strcmp(var, \"format.outputdirectory\")) {\n>   \t\tFREE_AND_NULL(cfg->config_output_directory);\n>   \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n> @@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,\n>   \t\tgoto done;\n>   \ttotal = list.nr;\n>   \n> +\tif (!cover_letter_fmt) {\n> +\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n> +\t\tif (!cover_letter_fmt)\n> +\t\t\tcover_letter_fmt = \"shortlog\";\n> +\t}\n> +\n>   \tif (cover_letter == -1) {\n>   \t\tif (cfg.config_cover_letter == COVER_AUTO)\n>   \t\t\tcover_letter = (total > 1);\n> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\n> index 458da80721..4891389a53 100755\n> --- a/t/t4014-format-patch.sh\n> +++ b/t/t4014-format-patch.sh\n> @@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '\n>   \ttest_line_count = 1 result\n>   '\n>   \n> +test_expect_success 'cover letter config with count, subject and author' '\n> +\ttest_when_finished \"rm -rf patches result\" &&\n> +\ttest_when_finished \"git config unset format.coverletter\" &&\n> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n> +\tgit config set format.coverletter true &&\n> +\tgit config set format.commitlistformat \"log:[%(count)/%(total)] %s (%an)\" &&\n> +\tgit format-patch -o patches HEAD~2 &&\n> +\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .* \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n> +\ttest_line_count = 2 result\n> +'\n> +\n> +test_expect_success 'cover letter config with count and author' '\n> +\ttest_when_finished \"rm -rf patches result\" &&\n> +\ttest_when_finished \"git config unset format.coverletter\" &&\n> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n> +\tgit config set format.coverletter true &&\n> +\tgit config set format.commitlistformat \"log:[%(count)/%(total)] (%an)\" &&\n> +\tgit format-patch -o patches HEAD~2 &&\n> +\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n> +\ttest_line_count = 2 result\n> +'\n> +\n> +test_expect_success 'cover letter config commitlistformat set but no format' '\n> +\ttest_when_finished \"rm -rf patches result\" &&\n> +\ttest_when_finished \"git config unset format.coverletter\" &&\n> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n> +\tgit config set format.coverletter true &&\n> +\tprintf \"\\tcommitlistformat\" >> .git/config &&\n> +\tgit format-patch -o patches HEAD~2 &&\n> +\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .*\" patches/0000-cover-letter.patch >result &&\n> +\ttest_line_count = 2 result\n> +'\n> +\n> +test_expect_success 'cover letter config commitlistformat set to shortlog' '\n> +\ttest_when_finished \"rm -rf patches result\" &&\n> +\ttest_when_finished \"git config unset format.coverletter\" &&\n> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n> +\tgit config set format.coverletter true &&\n> +\tgit config set format.commitlistformat shortlog &&\n> +\tgit format-patch -o patches HEAD~2 &&\n> +\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n> +\ttest_line_count = 1 result\n> +'\n> +\n> +test_expect_success 'cover letter config commitlistformat not set' '\n> +\ttest_when_finished \"rm -rf patches result\" &&\n> +\ttest_when_finished \"git config unset format.coverletter\" &&\n> +\tgit config set format.coverletter true &&\n> +\tgit format-patch -o patches HEAD~2 &&\n> +\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n> +\ttest_line_count = 1 result\n> +'\n> +\n>   test_expect_success 'reroll count' '\n>   \trm -fr patches &&\n>   \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n\n"},{"id":"538451","messageId":"976684fc-c44c-4cd3-b015-d35043013e39@gmail.com","threadId":"65032","inReplyTo":"be7197aef5a7bc3de06216054fee6ee67e65cc33.1772839973.git.mroik@delayed.space","subject":"Re: [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-03-10T14:34:07Z","receivedAt":"2026-03-10T14:34:09Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 06/03/2026 23:34, Mirko Faina wrote:\n> Document the new \"--cover-letter-format\" option in format-patch and its\n> related configuration variable \"format.commitListFormat\".\n> \n> Signed-off-by: Mirko Faina <mroik@delayed.space>\n> ---\n>   Documentation/config/format.adoc    |  5 +++++\n>   Documentation/git-format-patch.adoc | 13 +++++++++++++\n>   2 files changed, 18 insertions(+)\n> \n> diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc\n> index ab0710e86a..ea5ec5df7a 100644\n> --- a/Documentation/config/format.adoc\n> +++ b/Documentation/config/format.adoc\n> @@ -101,6 +101,11 @@ format.coverLetter::\n>   \tgenerate a cover-letter only when there's more than one patch.\n>   \tDefault is false.\n>   \n> +format.commitListFormat::\n> +\tWhen the `--cover-letter-format` option is not given, `format-patch`\n> +\tuses the value of this variable to decide how to format the title of\n> +\teach commit. Default to `shortlog`.\n\nBeing able to set a default format like this is useful. It would be \nhelpful to document what the expected format is. This should be added to \npatch 4 which adds support for the config variable.\n\n> +\n>   format.outputDirectory::\n>   \tSet a custom directory to store the resulting files instead of the\n>   \tcurrent working directory. All directory components will be created.\n> diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\n> index 9a7807ca71..668330a015 100644\n> --- a/Documentation/git-format-patch.adoc\n> +++ b/Documentation/git-format-patch.adoc\n> @@ -24,6 +24,7 @@ SYNOPSIS\n>   \t\t   [(--reroll-count|-v) <n>]\n>   \t\t   [--to=<email>] [--cc=<email>]\n>   \t\t   [--[no-]cover-letter] [--quiet]\n> +\t\t   [--cover-letter-format=<format-spec>]\n>   \t\t   [--[no-]encode-email-headers]\n>   \t\t   [--no-notes | --notes[=<ref>]]\n>   \t\t   [--interdiff=<previous>]\n> @@ -321,6 +322,17 @@ feeding the result to `git send-email`.\n>   \tcontaining the branch description, shortlog and the overall diffstat.  You can\n>   \tfill in a description in the file before sending it out.\n>   \n> +--cover-letter-format=<format-spec>::\n> +\tSpecify the format in which to generate the commit list of the\n> +\tpatch series. This option is available if the user wants to use\n> +\tan alternative to the default `shortlog` format. The accepted\n> +\tvalues for format-spec are \"shortlog\" or a format string\n> +\tprefixed with `log:`.\n> +\te.g. `log: %s (%an)`\n> +\tIf defined, defaults to the `format.commitListFormat` configuration\n> +\tvariable.\n> +\tThis option is relevant only if a cover letter is generated.\n\nThis should be added in patch 3 which adds the --cover-letter-format option.\n\nThanks\n\nPhillip\n\n>   --encode-email-headers::\n>   --no-encode-email-headers::\n>   \tEncode email headers that have non-ASCII characters with\n> @@ -452,6 +464,7 @@ with configuration variables.\n>   \tsignOff = true\n>   \toutputDirectory = <directory>\n>   \tcoverLetter = auto\n> +\tcommitListFormat = shortlog\n>   \tcoverFromDescription = auto\n>   ------------\n>   \n\n"},{"id":"538473","messageId":"xmqqikb3ws3e.fsf@gitster.g","threadId":"65032","inReplyTo":"6b160915-1cdf-48b5-abe4-3efd0771598e@gmail.com","subject":"Re: [PATCH v7 4/5] format-patch: add commitListFormat config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-10T16:45:57Z","receivedAt":"2026-03-10T16:46:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> Possible values:\n>>    - commitListFormat is set but no string is passed: it will default to\n>>      \"[%(count)/%(total)] %s\"\n>\n> It is unusual for an empty config value to mean something different from \n> it not being set. The reason for this is that it allows\n>\n>      git -c config.key some-command\n>\n> to act as though config.key was not set.\n\nThat syntax is the same as setting config.key=true; disabling the\nfeature triggered by config.key is quite counter-intuitive, isn't\nit?\n\nWe are by default using \"shortlog\", but use of this configuration\nvariable is a sign that the user wants to use a more modern custom\nformat that is not the traditional \"shortlog\".  It would be quite\nnatural to invoke the modern default by setting it to \"true\" (i.e.,\n\"I want to enable the new format.commitlistformat feature, but I am\nnot saying which format, and the \"log:[%(count)/%(total)] %s\" format\nis used).\n\nPerhaps \"format.commitlistformat = false\" should disable the modern\nformat and fall back to \"shortlog\", setting it to true (including\nthe use of \"valueless true\" syntax) should enable it and use the\nmodern default \"log:[%c/%t] %s\" format, and non-bool text should be\nused as a custom specification (\"shortlog\", or \"log:<format>\")?\n\nI.e.\n\n\tswitch (git_parse_maybe_bool_text(value)) {\n        case 0: /* false */\n\t\tfmt_cover_letter_commit_list = \"shortlog\";\n\t\tbreak;\n\tcase 1: /* true - use the modern default format */\n\t\tfmt_cover_letter_commit_list = \"log:[%c/%t] %s\";\n\t\tbreak;\t\t\n\tdefault:\n\t\tfmt_cover_letter_commit_list = value;\n\t\tbreak;\n\t}\n\nHmm?\n\n\n> It would be nice to support a default format on the commandline as well.\n\n\n>\n>>    - if a string is passed: will use it as a format spec. Note that this\n>>      is either \"shortlog\" or a format spec prefixed by \"log:\"\n>>      e.g.\"log:%s (%an)\"\n>\n> Having the config value behave like --cover-letter-format=<value> is \n> sensible\n>\n>>    - if commitListFormat is not set: it will default to the shortlog\n>>      format.\n>\n> makes sense\n>\n> Thanks\n>\n> Phillip\n>\n>> Signed-off-by: Mirko Faina <mroik@delayed.space>\n>> ---\n>>   builtin/log.c           | 21 ++++++++++++++++\n>>   t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++\n>>   2 files changed, 74 insertions(+)\n>> \n>> diff --git a/builtin/log.c b/builtin/log.c\n>> index 95e5d9755f..5fec0ddaf9 100644\n>> --- a/builtin/log.c\n>> +++ b/builtin/log.c\n>> @@ -886,6 +886,7 @@ struct format_config {\n>>   \tchar *signature;\n>>   \tchar *signature_file;\n>>   \tenum cover_setting config_cover_letter;\n>> +\tchar *fmt_cover_letter_commit_list;\n>>   \tchar *config_output_directory;\n>>   \tenum cover_from_description cover_from_description_mode;\n>>   \tint show_notes;\n>> @@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n>>   \tstring_list_clear(&cfg->extra_cc, 0);\n>>   \tstrbuf_release(&cfg->sprefix);\n>>   \tfree(cfg->fmt_patch_suffix);\n>> +\tfree(cfg->fmt_cover_letter_commit_list);\n>>   }\n>>   \n>>   static enum cover_from_description parse_cover_from_description(const char *arg)\n>> @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n>>   \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n>>   \t\treturn 0;\n>>   \t}\n>> +\tif (!strcmp(var, \"format.commitlistformat\")) {\n>> +\t\tstruct strbuf tmp = STRBUF_INIT;\n>> +\t\tstrbuf_init(&tmp, 0);\n>> +\t\tif (value)\n>> +\t\t\tstrbuf_addstr(&tmp, value);\n>> +\t\telse\n>> +\t\t\tstrbuf_addstr(&tmp, \"log:[%(count)/%(total)] %s\");\n>> +\n>> +\t\tFREE_AND_NULL(cfg->fmt_cover_letter_commit_list);\n>> +\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n>\n>\n>\n>> +\t\tstrbuf_release(&tmp);\n>> +\t\treturn 0;\n>> +\t}\n>>   \tif (!strcmp(var, \"format.outputdirectory\")) {\n>>   \t\tFREE_AND_NULL(cfg->config_output_directory);\n>>   \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n>> @@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,\n>>   \t\tgoto done;\n>>   \ttotal = list.nr;\n>>   \n>> +\tif (!cover_letter_fmt) {\n>> +\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n>> +\t\tif (!cover_letter_fmt)\n>> +\t\t\tcover_letter_fmt = \"shortlog\";\n>> +\t}\n>> +\n>>   \tif (cover_letter == -1) {\n>>   \t\tif (cfg.config_cover_letter == COVER_AUTO)\n>>   \t\t\tcover_letter = (total > 1);\n>> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\n>> index 458da80721..4891389a53 100755\n>> --- a/t/t4014-format-patch.sh\n>> +++ b/t/t4014-format-patch.sh\n>> @@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '\n>>   \ttest_line_count = 1 result\n>>   '\n>>   \n>> +test_expect_success 'cover letter config with count, subject and author' '\n>> +\ttest_when_finished \"rm -rf patches result\" &&\n>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n>> +\tgit config set format.coverletter true &&\n>> +\tgit config set format.commitlistformat \"log:[%(count)/%(total)] %s (%an)\" &&\n>> +\tgit format-patch -o patches HEAD~2 &&\n>> +\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .* \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n>> +\ttest_line_count = 2 result\n>> +'\n>> +\n>> +test_expect_success 'cover letter config with count and author' '\n>> +\ttest_when_finished \"rm -rf patches result\" &&\n>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n>> +\tgit config set format.coverletter true &&\n>> +\tgit config set format.commitlistformat \"log:[%(count)/%(total)] (%an)\" &&\n>> +\tgit format-patch -o patches HEAD~2 &&\n>> +\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n>> +\ttest_line_count = 2 result\n>> +'\n>> +\n>> +test_expect_success 'cover letter config commitlistformat set but no format' '\n>> +\ttest_when_finished \"rm -rf patches result\" &&\n>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n>> +\tgit config set format.coverletter true &&\n>> +\tprintf \"\\tcommitlistformat\" >> .git/config &&\n>> +\tgit format-patch -o patches HEAD~2 &&\n>> +\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .*\" patches/0000-cover-letter.patch >result &&\n>> +\ttest_line_count = 2 result\n>> +'\n>> +\n>> +test_expect_success 'cover letter config commitlistformat set to shortlog' '\n>> +\ttest_when_finished \"rm -rf patches result\" &&\n>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n>> +\tgit config set format.coverletter true &&\n>> +\tgit config set format.commitlistformat shortlog &&\n>> +\tgit format-patch -o patches HEAD~2 &&\n>> +\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n>> +\ttest_line_count = 1 result\n>> +'\n>> +\n>> +test_expect_success 'cover letter config commitlistformat not set' '\n>> +\ttest_when_finished \"rm -rf patches result\" &&\n>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>> +\tgit config set format.coverletter true &&\n>> +\tgit format-patch -o patches HEAD~2 &&\n>> +\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n>> +\ttest_line_count = 1 result\n>> +'\n>> +\n>>   test_expect_success 'reroll count' '\n>>   \trm -fr patches &&\n>>   \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n"},{"id":"538522","messageId":"abCE0p6F9r4Gwl8O@exploit","threadId":"65032","inReplyTo":"8855efd5-a53a-47fc-8708-4cd6d416e441@gmail.com","subject":"Re: [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-10T20:55:06Z","receivedAt":"2026-03-10T20:55:10Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Tue, Mar 10, 2026 at 02:32:54PM +0000, Phillip Wood wrote:\n> From the user's point of view it would be more helpful if this message told\n> them which format specifier isn't supported\n> \n> \tdie(_(\"%s is not supported by this command\"), \"%(count)\");\n\nIndeed, will fix the message.\n"},{"id":"538525","messageId":"abCFlUkXbth08nPS@exploit","threadId":"65032","inReplyTo":"8f49d551-9741-4461-b076-3caba75e6122@gmail.com","subject":"Re: [PATCH v7 3/5] format-patch: add ability to use alt cover format","fromName":"Mroik","fromEmail":"mroik@delayed.space","sentAt":"2026-03-10T21:05:04Z","receivedAt":"2026-03-10T21:05:08Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Tue, Mar 10, 2026 at 02:33:46PM +0000, Phillip Wood wrote:\n> > Give format-patch the ability to specify an alternative format spec\n> > through the \"--cover-letter-format\" option. This option either takes\n> > \"shortlog\", which is the current format, or a format spec prefixed with\n> > \"log:\".\n> \n> That sounds like a nice improvement over using the shortlog output. However\n> it is rather cumbersome to have to type \"log:\" each time. As\n> --cover-letter-format=shortlog is a nonsensical format I don't think we need\n> to require the \"log:\" prefix. --no-cover-letter-format should behave like\n> --cover-letter-format=shortlog.\n\nThis was done to avoid having \"anything that is not shortlog should be\nin pretty-print format\". Having it this way in the future we can check\nfor the prefix and see if the user indeed would like the pretty-print\nformat, otherwise check againts other formats available (so far only\nshortlog). If \"log:\" is too cumbersome maybe we can drop it and check\ninstead for existing presets first, \"shortlog, etc...\" and then when\nnothing is found interpret it as pp format. I like the explicit prefix\nbut I guess it is unlikely that a user will want to print \"shortlog\" on\nevery line.\n\nDo we all agree on dropping the prefix?\n\n> This is unnecessary as commit_line is initialized in the declaration above.\n\nWill remove\n\n> This loop is a bit confusing, I wonder if it would be simpler to count up\n> \tfor (int i = 1; i <= n; i++) {\n> \t\trev.nr = i\n> \t\trepo_format_commit_message(the_repository, list[n - i], ...);\n\nWill rewrite for readability\n\n> > +test_expect_success 'cover letter with subject, author and count' '\n> > +\trm -rf patches &&\n> > +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> > +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> > +\ttouch test_file &&\n> > +\tgit add test_file &&\n> > +\tgit commit -m \"This is a subject\" &&\n> > +\tgit format-patch --cover-letter \\\n> > +\t--cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n> > +\tgrep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch >result &&\n> \n> using test_grep here would make it easier to debug test failures.\n\nWill do\n\n> > +\ttest_line_count = 1 result\n> > +'\n> > +\n> > +test_expect_success 'cover letter shortlog' '\n> > +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> > +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> > +\ttouch test_file &&\n> > +\tgit add test_file &&\n> > +\tgit commit -m \"This is a subject\" &&\n> > +\tgit format-patch --cover-letter --cover-letter-format=shortlog \\\n> > +\t-o patches HEAD~1 &&\n> > +\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n> \n> This just checks that the author name appears in the coverletter, not that\n> the patches are formatted with shortlog.\n\nThe name at the start of the line can appear only if the foramt is\nshortlog, but if in the future it we do decide to support more prefix\nthat might not be true anymore. Will rewrite this test to be more\nrobust.\n"},{"id":"538529","messageId":"abCKBXo8vKgv-8RG@exploit","threadId":"65032","inReplyTo":"6b160915-1cdf-48b5-abe4-3efd0771598e@gmail.com","subject":"Re: [PATCH v7 4/5] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-10T21:19:25Z","receivedAt":"2026-03-10T21:19:29Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Tue, Mar 10, 2026 at 02:34:01PM +0000, Phillip Wood wrote:\n> It is unusual for an empty config value to mean something different from it\n> not being set. The reason for this is that it allows\n> \n>     git -c config.key some-command\n> \n> to act as though config.key was not set.\n> \n> Perhaps we should use the value \"default\" to generate a default format. It\n> would be nice to support a default format on the commandline as well.\n\nIf nothing is passed it defaults to \"shortlog\", it wouldn't make sense\nto call it \"default\". But I agree, accepting a value as set but without\npassing anything to it can create some issues when interacting from cli.\n"},{"id":"538531","messageId":"abCLFS3QP7rJHueq@exploit","threadId":"65032","inReplyTo":"xmqqikb3ws3e.fsf@gitster.g","subject":"Re: [PATCH v7 4/5] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-10T21:23:13Z","receivedAt":"2026-03-10T21:23:16Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Tue, Mar 10, 2026 at 09:45:57AM -0700, Junio C Hamano wrote:\n> That syntax is the same as setting config.key=true; disabling the\n> feature triggered by config.key is quite counter-intuitive, isn't\n> it?\n> \n> We are by default using \"shortlog\", but use of this configuration\n> variable is a sign that the user wants to use a more modern custom\n> format that is not the traditional \"shortlog\".  It would be quite\n> natural to invoke the modern default by setting it to \"true\" (i.e.,\n> \"I want to enable the new format.commitlistformat feature, but I am\n> not saying which format, and the \"log:[%(count)/%(total)] %s\" format\n> is used).\n> \n> Perhaps \"format.commitlistformat = false\" should disable the modern\n> format and fall back to \"shortlog\", setting it to true (including\n> the use of \"valueless true\" syntax) should enable it and use the\n> modern default \"log:[%c/%t] %s\" format, and non-bool text should be\n> used as a custom specification (\"shortlog\", or \"log:<format>\")?\n> \n> I.e.\n> \n> \tswitch (git_parse_maybe_bool_text(value)) {\n>         case 0: /* false */\n> \t\tfmt_cover_letter_commit_list = \"shortlog\";\n> \t\tbreak;\n> \tcase 1: /* true - use the modern default format */\n> \t\tfmt_cover_letter_commit_list = \"log:[%c/%t] %s\";\n> \t\tbreak;\t\t\n> \tdefault:\n> \t\tfmt_cover_letter_commit_list = value;\n> \t\tbreak;\n> \t}\n> \n> Hmm?\n\nMmh, what if instead we defined a prefix format just like shortlog?\nMaybe call it something like \"numbered\" or something similar (not too\ngood with coming up with names).\n\nI dislike the idea of having an option be multiple types. Should bool or\nstring, not both.\n"},{"id":"538533","messageId":"xmqqldfzs567.fsf@gitster.g","threadId":"65032","inReplyTo":"316c9e76ee49d73aff75b63299c970e9f55f79b6.1772837832.git.mroik@delayed.space","subject":"Re: [PATCH v6 3/5] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-10T22:14:40Z","receivedAt":"2026-03-10T22:14:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> +static void generate_commit_list_cover(FILE *cover_file, const char *format,\n> +\t\t\t\t       struct commit **list, int n)\n> +{\n> +\tstruct strbuf commit_line = STRBUF_INIT;\n> +\tstruct pretty_print_context ctx = {0};\n> +\tstruct rev_info rev = REV_INFO_INIT;\n> +\n> +\tstrbuf_init(&commit_line, 0);\n\nWe don't need this, when the struct is already initialized at the\ndefinition a few lines above, do we?\n\n> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\n> index 21d6d0cd9e..458da80721 100755\n> --- a/t/t4014-format-patch.sh\n> +++ b/t/t4014-format-patch.sh\n> @@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '\n>  \tdone\n>  '\n>  \n> +test_expect_success 'cover letter with subject, author and count' '\n> +\trm -rf patches &&\n> +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> +\ttouch test_file &&\n> +\tgit add test_file &&\n> +\tgit commit -m \"This is a subject\" &&\n> +\tgit format-patch --cover-letter \\\n> +\t--cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n> +\tgrep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch >result &&\n> +\ttest_line_count = 1 result\n> +'\n> +\n> +test_expected_success 'cover letter with author and count' '\n\nHas this test been run successfully?  It is unsual that only one\namong several you are adding has this typo.\n\n\n"},{"id":"538535","messageId":"abCbhOt26wJNnHGJ@exploit","threadId":"65032","inReplyTo":"xmqqldfzs567.fsf@gitster.g","subject":"Re: [PATCH v6 3/5] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-10T22:32:03Z","receivedAt":"2026-03-10T22:32:08Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Tue, Mar 10, 2026 at 03:14:40PM -0700, Junio C Hamano wrote:\n> > diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\n> > index 21d6d0cd9e..458da80721 100755\n> > --- a/t/t4014-format-patch.sh\n> > +++ b/t/t4014-format-patch.sh\n> > @@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '\n> >  \tdone\n> >  '\n> >  \n> > +test_expect_success 'cover letter with subject, author and count' '\n> > +\trm -rf patches &&\n> > +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> > +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> > +\ttouch test_file &&\n> > +\tgit add test_file &&\n> > +\tgit commit -m \"This is a subject\" &&\n> > +\tgit format-patch --cover-letter \\\n> > +\t--cover-letter-format=\"log:[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n> > +\tgrep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch >result &&\n> > +\ttest_line_count = 1 result\n> > +'\n> > +\n> > +test_expected_success 'cover letter with author and count' '\n> \n> Has this test been run successfully?  It is unsual that only one\n> among several you are adding has this typo.\n\nSorry, didn't notice. I saw no red tests and assumed it was workig when\nit didn't even run.\n\nWill fix\n"},{"id":"538576","messageId":"3fb4baf7-a820-401d-815b-a0b7c11fe6c3@gmail.com","threadId":"65032","inReplyTo":"xmqqikb3ws3e.fsf@gitster.g","subject":"Re: [PATCH v7 4/5] format-patch: add commitListFormat config","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-03-11T10:32:50Z","receivedAt":"2026-03-11T10:32:53Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 10/03/2026 16:45, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>>> Possible values:\n>>>     - commitListFormat is set but no string is passed: it will default to\n>>>       \"[%(count)/%(total)] %s\"\n>>\n>> It is unusual for an empty config value to mean something different from\n>> it not being set. The reason for this is that it allows\n>>\n>>       git -c config.key some-command\n>>\n>> to act as though config.key was not set.\n> \n> That syntax is the same as setting config.key=true; disabling the\n> feature triggered by config.key is quite counter-intuitive, isn't\n> it?\n\nI'd forgotten about the boolean case, I was thinking about an empty or \nmissing value clearing multi-valued keys which is quite common I think. \nAnyway we seem to agree that we don't want to use a missing value to \nsignal a default here. The suggestion below using true and false seems \nquite reasonable to me.\n\nThanks\n\nPhillip\n\n> We are by default using \"shortlog\", but use of this configuration\n> variable is a sign that the user wants to use a more modern custom\n> format that is not the traditional \"shortlog\".  It would be quite\n> natural to invoke the modern default by setting it to \"true\" (i.e.,\n> \"I want to enable the new format.commitlistformat feature, but I am\n> not saying which format, and the \"log:[%(count)/%(total)] %s\" format\n> is used).\n> \n> Perhaps \"format.commitlistformat = false\" should disable the modern\n> format and fall back to \"shortlog\", setting it to true (including\n> the use of \"valueless true\" syntax) should enable it and use the\n> modern default \"log:[%c/%t] %s\" format, and non-bool text should be\n> used as a custom specification (\"shortlog\", or \"log:<format>\")?\n> \n> I.e.\n> \n> \tswitch (git_parse_maybe_bool_text(value)) {\n>          case 0: /* false */\n> \t\tfmt_cover_letter_commit_list = \"shortlog\";\n> \t\tbreak;\n> \tcase 1: /* true - use the modern default format */\n> \t\tfmt_cover_letter_commit_list = \"log:[%c/%t] %s\";\n> \t\tbreak;\t\t\n> \tdefault:\n> \t\tfmt_cover_letter_commit_list = value;\n> \t\tbreak;\n> \t}\n> \n> Hmm?\n> \n> \n>> It would be nice to support a default format on the commandline as well.\n> \n> \n>>\n>>>     - if a string is passed: will use it as a format spec. Note that this\n>>>       is either \"shortlog\" or a format spec prefixed by \"log:\"\n>>>       e.g.\"log:%s (%an)\"\n>>\n>> Having the config value behave like --cover-letter-format=<value> is\n>> sensible\n>>\n>>>     - if commitListFormat is not set: it will default to the shortlog\n>>>       format.\n>>\n>> makes sense\n>>\n>> Thanks\n>>\n>> Phillip\n>>\n>>> Signed-off-by: Mirko Faina <mroik@delayed.space>\n>>> ---\n>>>    builtin/log.c           | 21 ++++++++++++++++\n>>>    t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++\n>>>    2 files changed, 74 insertions(+)\n>>>\n>>> diff --git a/builtin/log.c b/builtin/log.c\n>>> index 95e5d9755f..5fec0ddaf9 100644\n>>> --- a/builtin/log.c\n>>> +++ b/builtin/log.c\n>>> @@ -886,6 +886,7 @@ struct format_config {\n>>>    \tchar *signature;\n>>>    \tchar *signature_file;\n>>>    \tenum cover_setting config_cover_letter;\n>>> +\tchar *fmt_cover_letter_commit_list;\n>>>    \tchar *config_output_directory;\n>>>    \tenum cover_from_description cover_from_description_mode;\n>>>    \tint show_notes;\n>>> @@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)\n>>>    \tstring_list_clear(&cfg->extra_cc, 0);\n>>>    \tstrbuf_release(&cfg->sprefix);\n>>>    \tfree(cfg->fmt_patch_suffix);\n>>> +\tfree(cfg->fmt_cover_letter_commit_list);\n>>>    }\n>>>    \n>>>    static enum cover_from_description parse_cover_from_description(const char *arg)\n>>> @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,\n>>>    \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n>>>    \t\treturn 0;\n>>>    \t}\n>>> +\tif (!strcmp(var, \"format.commitlistformat\")) {\n>>> +\t\tstruct strbuf tmp = STRBUF_INIT;\n>>> +\t\tstrbuf_init(&tmp, 0);\n>>> +\t\tif (value)\n>>> +\t\t\tstrbuf_addstr(&tmp, value);\n>>> +\t\telse\n>>> +\t\t\tstrbuf_addstr(&tmp, \"log:[%(count)/%(total)] %s\");\n>>> +\n>>> +\t\tFREE_AND_NULL(cfg->fmt_cover_letter_commit_list);\n>>> +\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);\n>>\n>>\n>>\n>>> +\t\tstrbuf_release(&tmp);\n>>> +\t\treturn 0;\n>>> +\t}\n>>>    \tif (!strcmp(var, \"format.outputdirectory\")) {\n>>>    \t\tFREE_AND_NULL(cfg->config_output_directory);\n>>>    \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n>>> @@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,\n>>>    \t\tgoto done;\n>>>    \ttotal = list.nr;\n>>>    \n>>> +\tif (!cover_letter_fmt) {\n>>> +\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n>>> +\t\tif (!cover_letter_fmt)\n>>> +\t\t\tcover_letter_fmt = \"shortlog\";\n>>> +\t}\n>>> +\n>>>    \tif (cover_letter == -1) {\n>>>    \t\tif (cfg.config_cover_letter == COVER_AUTO)\n>>>    \t\t\tcover_letter = (total > 1);\n>>> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\n>>> index 458da80721..4891389a53 100755\n>>> --- a/t/t4014-format-patch.sh\n>>> +++ b/t/t4014-format-patch.sh\n>>> @@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '\n>>>    \ttest_line_count = 1 result\n>>>    '\n>>>    \n>>> +test_expect_success 'cover letter config with count, subject and author' '\n>>> +\ttest_when_finished \"rm -rf patches result\" &&\n>>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>>> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n>>> +\tgit config set format.coverletter true &&\n>>> +\tgit config set format.commitlistformat \"log:[%(count)/%(total)] %s (%an)\" &&\n>>> +\tgit format-patch -o patches HEAD~2 &&\n>>> +\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .* \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n>>> +\ttest_line_count = 2 result\n>>> +'\n>>> +\n>>> +test_expect_success 'cover letter config with count and author' '\n>>> +\ttest_when_finished \"rm -rf patches result\" &&\n>>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>>> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n>>> +\tgit config set format.coverletter true &&\n>>> +\tgit config set format.commitlistformat \"log:[%(count)/%(total)] (%an)\" &&\n>>> +\tgit format-patch -o patches HEAD~2 &&\n>>> +\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n>>> +\ttest_line_count = 2 result\n>>> +'\n>>> +\n>>> +test_expect_success 'cover letter config commitlistformat set but no format' '\n>>> +\ttest_when_finished \"rm -rf patches result\" &&\n>>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>>> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n>>> +\tgit config set format.coverletter true &&\n>>> +\tprintf \"\\tcommitlistformat\" >> .git/config &&\n>>> +\tgit format-patch -o patches HEAD~2 &&\n>>> +\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .*\" patches/0000-cover-letter.patch >result &&\n>>> +\ttest_line_count = 2 result\n>>> +'\n>>> +\n>>> +test_expect_success 'cover letter config commitlistformat set to shortlog' '\n>>> +\ttest_when_finished \"rm -rf patches result\" &&\n>>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>>> +\ttest_when_finished \"git config unset format.commitlistformat\" &&\n>>> +\tgit config set format.coverletter true &&\n>>> +\tgit config set format.commitlistformat shortlog &&\n>>> +\tgit format-patch -o patches HEAD~2 &&\n>>> +\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n>>> +\ttest_line_count = 1 result\n>>> +'\n>>> +\n>>> +test_expect_success 'cover letter config commitlistformat not set' '\n>>> +\ttest_when_finished \"rm -rf patches result\" &&\n>>> +\ttest_when_finished \"git config unset format.coverletter\" &&\n>>> +\tgit config set format.coverletter true &&\n>>> +\tgit format-patch -o patches HEAD~2 &&\n>>> +\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n>>> +\ttest_line_count = 1 result\n>>> +'\n>>> +\n>>>    test_expect_success 'reroll count' '\n>>>    \trm -fr patches &&\n>>>    \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n\n"},{"id":"538577","messageId":"3191559b-d79a-4d50-8364-50581df24ebc@gmail.com","threadId":"65032","inReplyTo":"abCLFS3QP7rJHueq@exploit","subject":"Re: [PATCH v7 4/5] format-patch: add commitListFormat config","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-03-11T10:38:09Z","receivedAt":"2026-03-11T10:38:12Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 10/03/2026 21:23, Mirko Faina wrote:\n> On Tue, Mar 10, 2026 at 09:45:57AM -0700, Junio C Hamano wrote:\n>> That syntax is the same as setting config.key=true; disabling the\n>> feature triggered by config.key is quite counter-intuitive, isn't\n>> it?\n>>\n>> We are by default using \"shortlog\", but use of this configuration\n>> variable is a sign that the user wants to use a more modern custom\n>> format that is not the traditional \"shortlog\".  It would be quite\n>> natural to invoke the modern default by setting it to \"true\" (i.e.,\n>> \"I want to enable the new format.commitlistformat feature, but I am\n>> not saying which format, and the \"log:[%(count)/%(total)] %s\" format\n>> is used).\n>>\n>> Perhaps \"format.commitlistformat = false\" should disable the modern\n>> format and fall back to \"shortlog\", setting it to true (including\n>> the use of \"valueless true\" syntax) should enable it and use the\n>> modern default \"log:[%c/%t] %s\" format, and non-bool text should be\n>> used as a custom specification (\"shortlog\", or \"log:<format>\")?\n>>\n>> I.e.\n>>\n>> \tswitch (git_parse_maybe_bool_text(value)) {\n>>          case 0: /* false */\n>> \t\tfmt_cover_letter_commit_list = \"shortlog\";\n>> \t\tbreak;\n>> \tcase 1: /* true - use the modern default format */\n>> \t\tfmt_cover_letter_commit_list = \"log:[%c/%t] %s\";\n>> \t\tbreak;\t\t\n>> \tdefault:\n>> \t\tfmt_cover_letter_commit_list = value;\n>> \t\tbreak;\n>> \t}\n>>\n>> Hmm?\n> \n> Mmh, what if instead we defined a prefix format just like shortlog?\n> Maybe call it something like \"numbered\" or something similar (not too\n> good with coming up with names).\n> \n> I dislike the idea of having an option be multiple types. Should bool or\n> string, not both.\n\nI don't mind either way if we can come up with some sensible names \ninstead of \"true\" and \"false\". For the \"false\" case above we could just \nuse \"shortlog\", \"numbered\" sounds petty good for the \"true\" case above \nas well.\n\nThanks\n\nPhillip\n\n"},{"id":"538625","messageId":"xmqqeclqqofs.fsf@gitster.g","threadId":"65032","inReplyTo":"3191559b-d79a-4d50-8364-50581df24ebc@gmail.com","subject":"Re: [PATCH v7 4/5] format-patch: add commitListFormat config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-11T17:13:43Z","receivedAt":"2026-03-11T17:13:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>>> Perhaps \"format.commitlistformat = false\" should disable the modern\n>>> format and fall back to \"shortlog\", setting it to true (including\n>>> the use of \"valueless true\" syntax) should enable it and use the\n>>> modern default \"log:[%c/%t] %s\" format, and non-bool text should be\n>>> used as a custom specification (\"shortlog\", or \"log:<format>\")?\n>>>\n>>> I.e.\n>>>\n>>> \tswitch (git_parse_maybe_bool_text(value)) {\n>>>          case 0: /* false */\n>>> \t\tfmt_cover_letter_commit_list = \"shortlog\";\n>>> \t\tbreak;\n>>> \tcase 1: /* true - use the modern default format */\n>>> \t\tfmt_cover_letter_commit_list = \"log:[%c/%t] %s\";\n>>> \t\tbreak;\t\t\n>>> \tdefault:\n>>> \t\tfmt_cover_letter_commit_list = value;\n>>> \t\tbreak;\n>>> \t}\n>>>\n>>> Hmm?\n>> \n>> Mmh, what if instead we defined a prefix format just like shortlog?\n>> Maybe call it something like \"numbered\" or something similar (not too\n>> good with coming up with names).\n>> \n>> I dislike the idea of having an option be multiple types. Should bool or\n>> string, not both.\n>\n> I don't mind either way if we can come up with some sensible names \n> instead of \"true\" and \"false\".\n\nI do not mind a pair of synonym if you insist, but I disagree with\nyour assessment on true and false being not sensible.  When you view\nthe feature as \"use the modern commit list format\" (specified either\nwith a command line option, or a configuration variable), \"yes, I do\nwant to use that modern one, not the historical shortlog format\" and\n\"no, don't bother. I like the old one just fine\" are both natural\nanswers to the question \"do you want to use the modern one?\".  Of\ncourse, \"let's use the modern one, and as it lets me give a custom\nformat string, please use this one\" would be how the user feeds a\nvalue that is not boolean to the feature.\n\nSo in short, I view this very similar to other \"extended bool\" (or\n\"bool or even more customization\") option/configuration, and \"true\"\nand \"false\", while they may not be so descriptive, are also sensible\nchoices in that context.\n\n"},{"id":"538626","messageId":"xmqqa4weqo7q.fsf@gitster.g","threadId":"65032","inReplyTo":"3fb4baf7-a820-401d-815b-a0b7c11fe6c3@gmail.com","subject":"Re: [PATCH v7 4/5] format-patch: add commitListFormat config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-11T17:18:33Z","receivedAt":"2026-03-11T17:18:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 10/03/2026 16:45, Junio C Hamano wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>> \n>>>> Possible values:\n>>>>     - commitListFormat is set but no string is passed: it will default to\n>>>>       \"[%(count)/%(total)] %s\"\n>>>\n>>> It is unusual for an empty config value to mean something different from\n>>> it not being set. The reason for this is that it allows\n>>>\n>>>       git -c config.key some-command\n>>>\n>>> to act as though config.key was not set.\n>> \n>> That syntax is the same as setting config.key=true; disabling the\n>> feature triggered by config.key is quite counter-intuitive, isn't\n>> it?\n>\n> I'd forgotten about the boolean case, I was thinking about an empty or \n> missing value clearing multi-valued keys which is quite common I think. \n\nYes, \"git -c config.list= -c config.list=one -c config.list=two cmd\"\nwould defeat list-valued config.list defined in /etc/gitconfig and\n~/.gitconfig and then use a list with only \"one\" and \"two\" on it.\nConfiguration variables like push.pushOption, merge.suppressDest,\nremote.<name>.url, etc. all use this convention.\n\nThe form without '=' is a valueless true that I do not think is used\nfor such \"clear the list\", though.\n"},{"id":"538759","messageId":"cover.1773331753.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1772839973.git.mroik@delayed.space","subject":"[PATCH v8 0/4] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-12T16:20:07Z","receivedAt":"2026-03-12T16:20:23Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Squashed the documentation changes to their relevant patch and added a\nnew format preset \"chronological\".\n\nThank you for the reviews\n\n[1/4] format-patch: move cover letter summary generation (Mirko Faina)\n[2/4] format-patch: add ability to use alt cover format (Mirko Faina)\n[3/4] format-patch: add \"chronological\" format for cover (Mirko Faina)\n[4/4] format-patch: add commitListFormat config (Mirko Faina)\n\n Documentation/config/format.adoc    |   5 ++\n Documentation/git-format-patch.adoc |  12 +++\n builtin/log.c                       |  82 ++++++++++++++----\n pretty.c                            |  15 ++++\n t/t4014-format-patch.sh             | 126 ++++++++++++++++++++++++++++\n t/t9902-completion.sh               |   1 +\n 6 files changed, 227 insertions(+), 14 deletions(-)\n\n-- \n2.53.0.904.g2727be2e99\n\n"},{"id":"538760","messageId":"3b008fb94dff3b54f19eb0754938a33968acd1cc.1773331753.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1773331753.git.mroik@delayed.space","subject":"[PATCH v8 1/4] format-patch: move cover letter summary generation","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-12T16:20:08Z","receivedAt":"2026-03-12T16:20:24Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"As of now format-patch allows generation of a template cover letter for\npatch series through \"--cover-letter\".\n\nMove shortlog summary code generation to its own function. This is done\nin preparation to other patches where we enable the user to format the\ncommit list using thier own format string.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n builtin/log.c | 32 ++++++++++++++++++++------------\n 1 file changed, 20 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 5c9a8ef363..0d12272031 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n \t}\n }\n \n+static void generate_shortlog_cover_letter(struct shortlog *log,\n+\t\t\t\t\t   struct rev_info *rev,\n+\t\t\t\t\t   struct commit **list,\n+\t\t\t\t\t   int nr)\n+{\n+\tshortlog_init(log);\n+\tlog->wrap_lines = 1;\n+\tlog->wrap = MAIL_DEFAULT_WRAP;\n+\tlog->in1 = 2;\n+\tlog->in2 = 4;\n+\tlog->file = rev->diffopt.file;\n+\tlog->groups = SHORTLOG_GROUP_AUTHOR;\n+\tshortlog_finish_setup(log);\n+\tfor (int i = 0; i < nr; i++)\n+\t\tshortlog_add_commit(log, list[i]);\n+\n+\tshortlog_output(log);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tshortlog_init(&log);\n-\tlog.wrap_lines = 1;\n-\tlog.wrap = MAIL_DEFAULT_WRAP;\n-\tlog.in1 = 2;\n-\tlog.in2 = 4;\n-\tlog.file = rev->diffopt.file;\n-\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n-\tshortlog_finish_setup(&log);\n-\tfor (i = 0; i < nr; i++)\n-\t\tshortlog_add_commit(&log, list[i]);\n-\n-\tshortlog_output(&log);\n+\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n-- \n2.53.0.904.g2727be2e99\n\n"},{"id":"538762","messageId":"225065cc0dd54d1a592939d41783a904a98fb2ad.1773331753.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1773331753.git.mroik@delayed.space","subject":"[PATCH v8 2/4] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-12T16:20:09Z","receivedAt":"2026-03-12T16:20:25Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Often when sending patch series there's a need to clarify to the\nreviewer what's the purpose of said series, since it might be difficult\nto understand it from reading the commits messages one by one.\n\n\"git format-patch\" provides the useful \"--cover-letter\" flag to declare\nif we want it to generate a template for us to use. By default it will\ngenerate a \"git shortlog\" of the changes, which developers find less\nuseful than they'd like, mainly because the shortlog groups commits by\nauthor, and gives no obvious chronological order.\n\nTo better reference relevant patches in the coverletter this patch\nintroduces two new placeholders that can be used in the format spec:\n%(count) and %(total). These are the chronological number of the patch\nin the series and the total amount of patches in the series. Note that\nthe width of %(count) will always be the same witdh of %(total).\n\nGive format-patch the ability to specify an alternative format spec\nthrough the \"--cover-letter-format\" option. This option either takes\n\"shortlog\", which is the current format, or a format spec prefixed with\n\"log:\".\n\nExample:\n    git format-patch --cover-letter \\\n        --cover-letter-format=\"[%(count)/%(total)] %s (%an)\" HEAD~3\n\n    [1/3] this is a commit summary (Mirko Faina)\n    [2/3] this is another commit summary (Mirko Faina)\n    ...\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n Documentation/git-format-patch.adoc | 11 ++++++\n builtin/log.c                       | 36 ++++++++++++++++--\n pretty.c                            | 15 ++++++++\n t/t4014-format-patch.sh             | 58 +++++++++++++++++++++++++++++\n t/t9902-completion.sh               |  1 +\n 5 files changed, 118 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\nindex 9a7807ca71..de36b87a85 100644\n--- a/Documentation/git-format-patch.adoc\n+++ b/Documentation/git-format-patch.adoc\n@@ -24,6 +24,7 @@ SYNOPSIS\n \t\t   [(--reroll-count|-v) <n>]\n \t\t   [--to=<email>] [--cc=<email>]\n \t\t   [--[no-]cover-letter] [--quiet]\n+\t\t   [--cover-letter-format=<format-spec>]\n \t\t   [--[no-]encode-email-headers]\n \t\t   [--no-notes | --notes[=<ref>]]\n \t\t   [--interdiff=<previous>]\n@@ -321,6 +322,16 @@ feeding the result to `git send-email`.\n \tcontaining the branch description, shortlog and the overall diffstat.  You can\n \tfill in a description in the file before sending it out.\n \n+--cover-letter-format=<format-spec>::\n+\tSpecify the format in which to generate the commit list of the patch\n+\tseries. This option is available if the user wants to use an\n+\talternative to the default `shortlog` format. The accepted values for\n+\tformat-spec are \"shortlog\" or a format string.\n+\te.g. `%s (%an)`\n+\tIf defined, defaults to the `format.commitListFormat` configuration\n+\tvariable.\n+\tThis option is relevant only if a cover letter is generated.\n+\n --encode-email-headers::\n --no-encode-email-headers::\n \tEncode email headers that have non-ASCII characters with\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 0d12272031..4f22012395 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -1343,13 +1343,35 @@ static void generate_shortlog_cover_letter(struct shortlog *log,\n \tshortlog_output(log);\n }\n \n+static void generate_commit_list_cover(FILE *cover_file, const char *format,\n+\t\t\t\t       struct commit **list, int n)\n+{\n+\tstruct strbuf commit_line = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\tstruct rev_info rev = REV_INFO_INIT;\n+\n+\trev.total = n;\n+\tctx.rev = &rev;\n+\tfor (int i = 1; i <= n; i++) {\n+\t\trev.nr = i;\n+\t\trepo_format_commit_message(the_repository, list[n - i], format,\n+\t\t\t\t&commit_line, &ctx);\n+\t\tfprintf(cover_file, \"%s\\n\", commit_line.buf);\n+\t\tstrbuf_reset(&commit_line);\n+\t}\n+\tfprintf(cover_file, \"\\n\");\n+\n+\tstrbuf_release(&commit_line);\n+}\n+\n static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \t\t\t      struct commit *origin,\n \t\t\t      int nr, struct commit **list,\n \t\t\t      const char *description_file,\n \t\t\t      const char *branch_name,\n \t\t\t      int quiet,\n-\t\t\t      const struct format_config *cfg)\n+\t\t\t      const struct format_config *cfg,\n+\t\t\t      const char *format)\n {\n \tconst char *committer;\n \tstruct shortlog log;\n@@ -1396,7 +1418,10 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \tfree(pp.after_subject);\n \tstrbuf_release(&sb);\n \n-\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\tif (format == NULL || !strcmp(format, \"shortlog\"))\n+\t\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\telse\n+\t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n \n \t/* We can only do diffstat with a unique reference point */\n \tif (origin)\n@@ -1914,6 +1939,7 @@ int cmd_format_patch(int argc,\n \tint just_numbers = 0;\n \tint ignore_if_in_upstream = 0;\n \tint cover_letter = -1;\n+\tconst char *cover_letter_fmt = NULL;\n \tint boundary_count = 0;\n \tint no_binary_diff = 0;\n \tint zero_commit = 0;\n@@ -1960,6 +1986,8 @@ int cmd_format_patch(int argc,\n \t\t\t    N_(\"print patches to standard out\")),\n \t\tOPT_BOOL(0, \"cover-letter\", &cover_letter,\n \t\t\t    N_(\"generate a cover letter\")),\n+\t\tOPT_STRING(0, \"cover-letter-format\", &cover_letter_fmt, N_(\"format-spec\"),\n+\t\t\t    N_(\"format spec used for the commit list in the cover letter\")),\n \t\tOPT_BOOL(0, \"numbered-files\", &just_numbers,\n \t\t\t    N_(\"use simple number sequence for output file names\")),\n \t\tOPT_STRING(0, \"suffix\", &fmt_patch_suffix, N_(\"sfx\"),\n@@ -2297,6 +2325,7 @@ int cmd_format_patch(int argc,\n \t\t/* nothing to do */\n \t\tgoto done;\n \ttotal = list.nr;\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\n@@ -2388,7 +2417,8 @@ int cmd_format_patch(int argc,\n \t\t\tgen_message_id(&rev, \"cover\");\n \t\tmake_cover_letter(&rev, !!output_directory,\n \t\t\t\t  origin, list.nr, list.items,\n-\t\t\t\t  description_file, branch_name, quiet, &cfg);\n+\t\t\t\t  description_file, branch_name, quiet, &cfg,\n+\t\t\t\t  cover_letter_fmt);\n \t\tprint_bases(&bases, rev.diffopt.file);\n \t\tprint_signature(signature, rev.diffopt.file);\n \t\ttotal++;\ndiff --git a/pretty.c b/pretty.c\nindex e0646bbc5d..8f3c434817 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tif (!commit->object.parsed)\n \t\tparse_object(the_repository, &commit->object.oid);\n \n+\tif (starts_with(placeholder, \"(count)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"%s is not supported by this command\"), \"%(count)\");\n+\t\tstrbuf_addf(sb, \"%0*d\", decimal_width(c->pretty_ctx->rev->total),\n+\t\t\t    c->pretty_ctx->rev->nr);\n+\t\treturn 7;\n+\t}\n+\n+\tif (starts_with(placeholder, \"(total)\")) {\n+\t\tif (!c->pretty_ctx->rev)\n+\t\t\tdie(_(\"%s is not supported by this command\"), \"%(total)\");\n+\t\tstrbuf_addf(sb, \"%d\", c->pretty_ctx->rev->total);\n+\t\treturn 7;\n+\t}\n+\n \tswitch (placeholder[0]) {\n \tcase 'H':\t\t/* commit hash */\n \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 21d6d0cd9e..631669c159 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -380,6 +380,64 @@ test_expect_success 'filename limit applies only to basename' '\n \tdone\n '\n \n+test_expect_success 'cover letter with subject, author and count' '\n+\trm -rf patches &&\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n+\ttest_grep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch\n+'\n+\n+cat > expected <<EOF\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+\n+A U Thor (1):\n+  This is a subject\n+\n+\n+\n+\n+\n+\n+\n+\n+EOF\n+\n+test_expect_success 'cover letter shortlog' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches expected test_file result\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter --cover-letter-format=shortlog \\\n+\t-o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor (1):$\\|^  This is a subject$/!s/.*//; /.*/p\" patches/0000-cover-letter.patch >result &&\n+\ttest_cmp expected result\n+'\n+\n+test_expect_success 'cover letter no format' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --no-cover-letter-format --cover-letter -o patches HEAD~1 &&\n+\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex 964e1f1569..4f760a7468 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '\n \ttest_completion \"git send-email --cov\" <<-\\EOF &&\n \t--cover-from-description=Z\n \t--cover-letter Z\n+\t--cover-letter-format=Z\n \tEOF\n \ttest_completion \"git send-email --val\" <<-\\EOF &&\n \t--validate Z\n-- \n2.53.0.904.g2727be2e99\n\n"},{"id":"538761","messageId":"b67f1c9603bac9ea464357c77743e71c27003bd2.1773331753.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1773331753.git.mroik@delayed.space","subject":"[PATCH v8 3/4] format-patch: add \"chronological\" format for cover","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-12T16:20:10Z","receivedAt":"2026-03-12T16:20:26Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Having a user craft a custom format spec everytime might be a hassle,\nand having a common format that seems reasonable might make reviews\neasier. This patch introduces a new simple preset called \"chronological\".\n\nTeach make_cover_letter() the \"chronological\" format.\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n Documentation/git-format-patch.adoc |  2 +-\n builtin/log.c                       |  3 +++\n t/t4014-format-patch.sh             | 11 +++++++++++\n 3 files changed, 15 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\nindex de36b87a85..6364fd1f5a 100644\n--- a/Documentation/git-format-patch.adoc\n+++ b/Documentation/git-format-patch.adoc\n@@ -326,7 +326,7 @@ feeding the result to `git send-email`.\n \tSpecify the format in which to generate the commit list of the patch\n \tseries. This option is available if the user wants to use an\n \talternative to the default `shortlog` format. The accepted values for\n-\tformat-spec are \"shortlog\" or a format string.\n+\tformat-spec are \"shortlog\", \"chronological\" or a format string.\n \te.g. `%s (%an)`\n \tIf defined, defaults to the `format.commitListFormat` configuration\n \tvariable.\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 4f22012395..12877a0ac2 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -50,6 +50,7 @@\n #define MAIL_DEFAULT_WRAP 72\n #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100\n #define FORMAT_PATCH_NAME_MAX_DEFAULT 64\n+#define CHRONOLOGICAL \"[%(count)/%(total)] %s\"\n \n static unsigned int force_in_body_from;\n static int stdout_mboxrd;\n@@ -1420,6 +1421,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n \n \tif (format == NULL || !strcmp(format, \"shortlog\"))\n \t\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n+\telse if (!strcmp(format, \"chronological\"))\n+\t\tgenerate_commit_list_cover(rev->diffopt.file, CHRONOLOGICAL, list, nr);\n \telse\n \t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n \ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 631669c159..5ec527bce9 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -392,6 +392,17 @@ test_expect_success 'cover letter with subject, author and count' '\n \ttest_grep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch\n '\n \n+test_expect_success 'cover letter chronological' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch --cover-letter \\\n+\t--cover-letter-format=\"chronological\" -o patches HEAD~1 &&\n+\ttest_grep \"^\\[1/1\\] This is a subject$\" patches/0000-cover-letter.patch\n+'\n+\n cat > expected <<EOF\n \n \n-- \n2.53.0.904.g2727be2e99\n\n"},{"id":"538763","messageId":"50b1f37e8cd6091deca793f9a972b1d15d9aae7d.1773331754.git.mroik@delayed.space","threadId":"65032","inReplyTo":"cover.1773331753.git.mroik@delayed.space","subject":"[PATCH v8 4/4] format-patch: add commitListFormat config","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-12T16:20:11Z","receivedAt":"2026-03-12T16:20:26Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"Using \"--cover-letter\" we can tell format-patch to generate a cover\nletter, in this cover letter there's a list of commits included in the\npatch series and the format is specified by the \"--cover-letter-format\"\noption. Would be useful if this format could be configured from the\nconfig file instead of always needing to pass it from the command line.\n\nTeach format-patch how to read the format spec for the cover letter from\nthe config files. The variable it should look for is called\nformat.commitListFormat, it accepts the same values as the option\n\"--cover-letter-format\".\n\nSigned-off-by: Mirko Faina <mroik@delayed.space>\n---\n Documentation/config/format.adoc    |  5 +++\n Documentation/git-format-patch.adoc |  1 +\n builtin/log.c                       | 13 +++++++\n t/t4014-format-patch.sh             | 57 +++++++++++++++++++++++++++++\n 4 files changed, 76 insertions(+)\n\ndiff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc\nindex ab0710e86a..ef1ed1d250 100644\n--- a/Documentation/config/format.adoc\n+++ b/Documentation/config/format.adoc\n@@ -101,6 +101,11 @@ format.coverLetter::\n \tgenerate a cover-letter only when there's more than one patch.\n \tDefault is false.\n \n+format.commitListFormat::\n+\tWhen the `--cover-letter-format` option is not given, `format-patch`\n+\tuses the value of this variable to decide how to format the title of\n+\teach commit. Defaults to `shortlog`.\n+\n format.outputDirectory::\n \tSet a custom directory to store the resulting files instead of the\n \tcurrent working directory. All directory components will be created.\ndiff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\nindex 6364fd1f5a..88e580d1b3 100644\n--- a/Documentation/git-format-patch.adoc\n+++ b/Documentation/git-format-patch.adoc\n@@ -463,6 +463,7 @@ with configuration variables.\n \tsignOff = true\n \toutputDirectory = <directory>\n \tcoverLetter = auto\n+\tcommitListFormat = shortlog\n \tcoverFromDescription = auto\n ------------\n \ndiff --git a/builtin/log.c b/builtin/log.c\nindex 12877a0ac2..e19e122bc9 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -887,6 +887,7 @@ struct format_config {\n \tchar *signature;\n \tchar *signature_file;\n \tenum cover_setting config_cover_letter;\n+\tchar *fmt_cover_letter_commit_list;\n \tchar *config_output_directory;\n \tenum cover_from_description cover_from_description_mode;\n \tint show_notes;\n@@ -931,6 +932,7 @@ static void format_config_release(struct format_config *cfg)\n \tstring_list_clear(&cfg->extra_cc, 0);\n \tstrbuf_release(&cfg->sprefix);\n \tfree(cfg->fmt_patch_suffix);\n+\tfree(cfg->fmt_cover_letter_commit_list);\n }\n \n static enum cover_from_description parse_cover_from_description(const char *arg)\n@@ -1053,6 +1055,11 @@ static int git_format_config(const char *var, const char *value,\n \t\tcfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"format.commitlistformat\")) {\n+\t\tFREE_AND_NULL(cfg->fmt_cover_letter_commit_list);\n+\t\tgit_config_string(&cfg->fmt_cover_letter_commit_list, var, value);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"format.outputdirectory\")) {\n \t\tFREE_AND_NULL(cfg->config_output_directory);\n \t\treturn git_config_string(&cfg->config_output_directory, var, value);\n@@ -2329,6 +2336,12 @@ int cmd_format_patch(int argc,\n \t\tgoto done;\n \ttotal = list.nr;\n \n+\tif (!cover_letter_fmt) {\n+\t\tcover_letter_fmt = cfg.fmt_cover_letter_commit_list;\n+\t\tif (!cover_letter_fmt)\n+\t\t\tcover_letter_fmt = \"shortlog\";\n+\t}\n+\n \tif (cover_letter == -1) {\n \t\tif (cfg.config_cover_letter == COVER_AUTO)\n \t\t\tcover_letter = (total > 1);\ndiff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\nindex 5ec527bce9..2091b5e9bb 100755\n--- a/t/t4014-format-patch.sh\n+++ b/t/t4014-format-patch.sh\n@@ -449,6 +449,63 @@ test_expect_success 'cover letter no format' '\n \ttest_line_count = 1 result\n '\n \n+test_expect_success 'cover letter config with count, subject and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"[%(count)/%(total)] %s (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] .* \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config with count and author' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat \"[%(count)/%(total)] (%an)\" &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+] \\(A U Thor\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 2 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set to shortlog' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat shortlog &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat set to chronological' '\n+\ttest_when_finished \"git reset --hard HEAD~1\" &&\n+\ttest_when_finished \"rm -rf patches result test_file\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\ttest_when_finished \"git config unset format.commitlistformat\" &&\n+\tgit config set format.coverletter true &&\n+\tgit config set format.commitlistformat chronological &&\n+\ttouch test_file &&\n+\tgit add test_file &&\n+\tgit commit -m \"This is a subject\" &&\n+\tgit format-patch -o patches HEAD~1 &&\n+\tgrep -E \"^[[[:digit:]]+/[[:digit:]]+]\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n+test_expect_success 'cover letter config commitlistformat not set' '\n+\ttest_when_finished \"rm -rf patches result\" &&\n+\ttest_when_finished \"git config unset format.coverletter\" &&\n+\tgit config set format.coverletter true &&\n+\tgit format-patch -o patches HEAD~2 &&\n+\tgrep -E \"^A U Thor \\([[:digit:]]+\\)\" patches/0000-cover-letter.patch >result &&\n+\ttest_line_count = 1 result\n+'\n+\n test_expect_success 'reroll count' '\n \trm -fr patches &&\n \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n-- \n2.53.0.904.g2727be2e99\n\n"},{"id":"538767","messageId":"xmqqeclpggge.fsf@gitster.g","threadId":"65032","inReplyTo":"3b008fb94dff3b54f19eb0754938a33968acd1cc.1773331753.git.mroik@delayed.space","subject":"Re: [PATCH v8 1/4] format-patch: move cover letter summary generation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T16:28:33Z","receivedAt":"2026-03-12T16:28:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> As of now format-patch allows generation of a template cover letter for\n> patch series through \"--cover-letter\".\n\n\"As of now\" is redundant.  Our convention is to describe the state\nof the codebase _before_ your changes in the present tense to set\nthe stage for describing the problem.\n\n> Move shortlog summary code generation to its own function. This is done\n> in preparation to other patches where we enable the user to format the\n> commit list using thier own format string.\n\nMakes sense.\n\n>\n> Signed-off-by: Mirko Faina <mroik@delayed.space>\n> ---\n>  builtin/log.c | 32 ++++++++++++++++++++------------\n>  1 file changed, 20 insertions(+), 12 deletions(-)\n>\n> diff --git a/builtin/log.c b/builtin/log.c\n> index 5c9a8ef363..0d12272031 100644\n> --- a/builtin/log.c\n> +++ b/builtin/log.c\n> @@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)\n>  \t}\n>  }\n>  \n> +static void generate_shortlog_cover_letter(struct shortlog *log,\n> +\t\t\t\t\t   struct rev_info *rev,\n> +\t\t\t\t\t   struct commit **list,\n> +\t\t\t\t\t   int nr)\n> +{\n> +\tshortlog_init(log);\n> +\tlog->wrap_lines = 1;\n> +\tlog->wrap = MAIL_DEFAULT_WRAP;\n> +\tlog->in1 = 2;\n> +\tlog->in2 = 4;\n> +\tlog->file = rev->diffopt.file;\n> +\tlog->groups = SHORTLOG_GROUP_AUTHOR;\n> +\tshortlog_finish_setup(log);\n> +\tfor (int i = 0; i < nr; i++)\n> +\t\tshortlog_add_commit(log, list[i]);\n> +\n> +\tshortlog_output(log);\n> +}\n> +\n>  static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n>  \t\t\t      struct commit *origin,\n>  \t\t\t      int nr, struct commit **list,\n> @@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n>  \tfree(pp.after_subject);\n>  \tstrbuf_release(&sb);\n>  \n> -\tshortlog_init(&log);\n> -\tlog.wrap_lines = 1;\n> -\tlog.wrap = MAIL_DEFAULT_WRAP;\n> -\tlog.in1 = 2;\n> -\tlog.in2 = 4;\n> -\tlog.file = rev->diffopt.file;\n> -\tlog.groups = SHORTLOG_GROUP_AUTHOR;\n> -\tshortlog_finish_setup(&log);\n> -\tfor (i = 0; i < nr; i++)\n> -\t\tshortlog_add_commit(&log, list[i]);\n> -\n> -\tshortlog_output(&log);\n> +\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n>  \n>  \t/* We can only do diffstat with a unique reference point */\n>  \tif (origin)\n"},{"id":"538770","messageId":"xmqq5x71gfci.fsf@gitster.g","threadId":"65032","inReplyTo":"225065cc0dd54d1a592939d41783a904a98fb2ad.1773331753.git.mroik@delayed.space","subject":"Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T16:52:29Z","receivedAt":"2026-03-12T16:52:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Often when sending patch series there's a need to clarify to the\n> reviewer what's the purpose of said series, since it might be difficult\n> to understand it from reading the commits messages one by one.\n\nYes, that is the whole point of having a cover letter.\n\n> \"git format-patch\" provides the useful \"--cover-letter\" flag to declare\n> if we want it to generate a template for us to use. By default it will\n> generate a \"git shortlog\" of the changes, which developers find less\n> useful than they'd like, mainly because the shortlog groups commits by\n> author, and gives no obvious chronological order.\n\nVery true.\n\n> To better reference relevant patches in the coverletter this patch\n> introduces two new placeholders that can be used in the format spec:\n\n\"this patch introduces\" -> \"introduce\" (imperative).\n\n> %(count) and %(total). These are the chronological number of the patch\n> in the series and the total amount of patches in the series. Note that\n> the width of %(count) will always be the same witdh of %(total).\n\n\"total amount\" -> \"total number\"?\n\n\"the width of %(count)...\" -> \"%(count) will zero-padded to the left\nto match the number of digits in %(total)\".\n\nBut the paragraph \"To better reference ...\" up to this point should\nprobably be moved a bit low?  The punchline \"Give format-patch the\nability\" to specify a custom format is the most important thing to\ntell readers, and %(count)/%(total) is an implementation detail of\nonly one possible customized format.\n\n> Give format-patch the ability to specify an alternative format spec\n> through the \"--cover-letter-format\" option. This option either takes\n> \"shortlog\", which is the current format, or a format spec prefixed with\n> \"log:\".\n>\n> Example:\n>     git format-patch --cover-letter \\\n>         --cover-letter-format=\"[%(count)/%(total)] %s (%an)\" HEAD~3\n>\n>     [1/3] this is a commit summary (Mirko Faina)\n>     [2/3] this is another commit summary (Mirko Faina)\n>     ...\n\nThe example does not use a format spec 'prefixed with \"log:\"',\nthough?\n\n> +--cover-letter-format=<format-spec>::\n> +\tSpecify the format in which to generate the commit list of the patch\n> +\tseries. This option is available if the user wants to use an\n> +\talternative to the default `shortlog` format. The accepted values for\n\nThe second sentence reads funny.  The option is available whether\nthe user wants to use it or not.  I'd suggest dropping the sentence,\nwithout which the paragraph reads just fine.\n\n> +\tformat-spec are \"shortlog\" or a format string.\n> +\te.g. `%s (%an)`\n\nOK, so we are not requiring \"log:\"?  This robs extensibility from\nfuture developers to introduce something other than \"shortlog\", no?\nIf the version of Git in 'next' supports \"longlog\" and user gives\n\"--cover-letter-format=longlog\" to their version that does not yet\nsupport it, it would be mistaken by the version of the code here as\na \"log:longlog\" without any placeholder that shows a fixed string\n\"longlog\" for each commit in the series?  We'd rather want such an\ninput to cause failure, no?\n\n> +\tIf defined, defaults to the `format.commitListFormat` configuration\n> +\tvariable.\n\ns/If defined, d\\(efaults.*variable\\)\\./D\\1, if defined./ would avoid\n\"if I define --cover-letter-format, why does it default to a\nconfiguration?  do you mean 'if not given'?\"\n\nSo, \"If defined,\" -> \"If not given\" would be another possible\nimprovement.  I think I like it better, actually.\n\n> +\tThis option is relevant only if a cover letter is generated.\n> +\n\nHmph, an alternative that may make it easier to use is to make the\ncommand line option _imply_ \"--cover-letter\", so that the user does\nnot have to give two similar looking command line options.\n\n    git format-patch --cover-letter --cover-letter-format=...\n\nOf course, the presence of the configuration variable should not\nimply generation of a cover letter. I.e.\n\n    git -c format.commitListInCoverLetterFormat=shortlog \\\n\t    format-patch -1 HEAD\n\nshould not imply --cover-letter.\n\n> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\n> index 21d6d0cd9e..631669c159 100755\n> --- a/t/t4014-format-patch.sh\n> +++ b/t/t4014-format-patch.sh\n> @@ -380,6 +380,64 @@ test_expect_success 'filename limit applies only to basename' '\n>  \tdone\n>  '\n>  \n> +test_expect_success 'cover letter with subject, author and count' '\n> +\trm -rf patches &&\n> +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> +\ttouch test_file &&\n> +\tgit add test_file &&\n> +\tgit commit -m \"This is a subject\" &&\n> +\tgit format-patch --cover-letter \\\n> +\t--cover-letter-format=\"[%(count)/%(total)] %s (%an)\" -o patches HEAD~1 &&\n> +\ttest_grep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch\n> +'\n> +\n> +cat > expected <<EOF\n> +\n> +\n> +\n> +\n> +\n> +\n> +\n> +\n> +\n> +\n> +A U Thor (1):\n> +  This is a subject\n> +\n> +\n> +\n> +\n> +\n> +\n> +\n> +\n> +EOF\n\nMany issues.\n\nIn modern tests (written within the past 10 years), we try not to\nexecute things outside text_expect_foo blocks.  The golden output\nto compare with is customary called 'expect' (not 'expected').  A\nredirection operator \">\", \"<<\", etc. has a single SP before but no\nSP after it, when there is no parameter interpolation happens in a\nHERE document, quote the \"EOF\" marker to show the intention of the\nauthor of the test that no parameter interpolation is expected.\n\ni.e.,\n\n    cat >expect <<\\-EOF\n\t...\n    EOF\n\nand do so inside a set-up test_expect_success block.\n\nWhy do we have so many blank lines?  Are the number of blank lines\nsignificant?  Such a hidden and hard to count dependency would hurt\nmaintainability of this test script.\n\n> +test_expect_success 'cover letter shortlog' '\n> +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> +\ttest_when_finished \"rm -rf patches expected test_file result\" &&\n> +\ttouch test_file &&\n> +\tgit add test_file &&\n> +\tgit commit -m \"This is a subject\" &&\n> +\tgit format-patch --cover-letter --cover-letter-format=shortlog \\\n> +\t-o patches HEAD~1 &&\n> +\tsed -n -e \"/^A U Thor (1):$\\|^  This is a subject$/!s/.*//; /.*/p\" patches/0000-cover-letter.patch >result &&\n> +\ttest_cmp expected result\n> +'\n> +\n> +test_expect_success 'cover letter no format' '\n> +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> +\ttouch test_file &&\n> +\tgit add test_file &&\n> +\tgit commit -m \"This is a subject\" &&\n> +\tgit format-patch --no-cover-letter-format --cover-letter -o patches HEAD~1 &&\n> +\tsed -n -e \"/^A U Thor/p;\" patches/0000-cover-letter.patch >result &&\n> +\ttest_line_count = 1 result\n> +'\n> +\n>  test_expect_success 'reroll count' '\n>  \trm -fr patches &&\n>  \tgit format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&\n> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\n> index 964e1f1569..4f760a7468 100755\n> --- a/t/t9902-completion.sh\n> +++ b/t/t9902-completion.sh\n> @@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '\n>  \ttest_completion \"git send-email --cov\" <<-\\EOF &&\n>  \t--cover-from-description=Z\n>  \t--cover-letter Z\n> +\t--cover-letter-format=Z\n>  \tEOF\n>  \ttest_completion \"git send-email --val\" <<-\\EOF &&\n>  \t--validate Z\n"},{"id":"538771","messageId":"xmqq1phpgf7f.fsf@gitster.g","threadId":"65032","inReplyTo":"b67f1c9603bac9ea464357c77743e71c27003bd2.1773331753.git.mroik@delayed.space","subject":"Re: [PATCH v8 3/4] format-patch: add \"chronological\" format for cover","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T16:55:32Z","receivedAt":"2026-03-12T16:55:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Having a user craft a custom format spec everytime might be a hassle,\n> and having a common format that seems reasonable might make reviews\n> easier. This patch introduces a new simple preset called \"chronological\".\n>\n> Teach make_cover_letter() the \"chronological\" format.\n\nOK.  The \"a fixed 'log:' prefix is still needed for future proofing\"\ncomment from my review on an earlier step of this series still\napplies after this step.\n\n> Signed-off-by: Mirko Faina <mroik@delayed.space>\n> ---\n>  Documentation/git-format-patch.adoc |  2 +-\n>  builtin/log.c                       |  3 +++\n>  t/t4014-format-patch.sh             | 11 +++++++++++\n>  3 files changed, 15 insertions(+), 1 deletion(-)\n>\n> diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc\n> index de36b87a85..6364fd1f5a 100644\n> --- a/Documentation/git-format-patch.adoc\n> +++ b/Documentation/git-format-patch.adoc\n> @@ -326,7 +326,7 @@ feeding the result to `git send-email`.\n>  \tSpecify the format in which to generate the commit list of the patch\n>  \tseries. This option is available if the user wants to use an\n>  \talternative to the default `shortlog` format. The accepted values for\n> -\tformat-spec are \"shortlog\" or a format string.\n> +\tformat-spec are \"shortlog\", \"chronological\" or a format string.\n>  \te.g. `%s (%an)`\n>  \tIf defined, defaults to the `format.commitListFormat` configuration\n>  \tvariable.\n> diff --git a/builtin/log.c b/builtin/log.c\n> index 4f22012395..12877a0ac2 100644\n> --- a/builtin/log.c\n> +++ b/builtin/log.c\n> @@ -50,6 +50,7 @@\n>  #define MAIL_DEFAULT_WRAP 72\n>  #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100\n>  #define FORMAT_PATCH_NAME_MAX_DEFAULT 64\n> +#define CHRONOLOGICAL \"[%(count)/%(total)] %s\"\n>  \n>  static unsigned int force_in_body_from;\n>  static int stdout_mboxrd;\n> @@ -1420,6 +1421,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,\n>  \n>  \tif (format == NULL || !strcmp(format, \"shortlog\"))\n>  \t\tgenerate_shortlog_cover_letter(&log, rev, list, nr);\n> +\telse if (!strcmp(format, \"chronological\"))\n> +\t\tgenerate_commit_list_cover(rev->diffopt.file, CHRONOLOGICAL, list, nr);\n>  \telse\n>  \t\tgenerate_commit_list_cover(rev->diffopt.file, format, list, nr);\n>  \n> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh\n> index 631669c159..5ec527bce9 100755\n> --- a/t/t4014-format-patch.sh\n> +++ b/t/t4014-format-patch.sh\n> @@ -392,6 +392,17 @@ test_expect_success 'cover letter with subject, author and count' '\n>  \ttest_grep \"^\\[1/1\\] This is a subject (A U Thor)$\" patches/0000-cover-letter.patch\n>  '\n>  \n> +test_expect_success 'cover letter chronological' '\n> +\ttest_when_finished \"git reset --hard HEAD~1\" &&\n> +\ttest_when_finished \"rm -rf patches result test_file\" &&\n> +\ttouch test_file &&\n> +\tgit add test_file &&\n> +\tgit commit -m \"This is a subject\" &&\n> +\tgit format-patch --cover-letter \\\n> +\t--cover-letter-format=\"chronological\" -o patches HEAD~1 &&\n> +\ttest_grep \"^\\[1/1\\] This is a subject$\" patches/0000-cover-letter.patch\n> +'\n> +\n>  cat > expected <<EOF\n"},{"id":"538772","messageId":"xmqqwlzhf0fc.fsf@gitster.g","threadId":"65032","inReplyTo":"50b1f37e8cd6091deca793f9a972b1d15d9aae7d.1773331754.git.mroik@delayed.space","subject":"Re: [PATCH v8 4/4] format-patch: add commitListFormat config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T17:00:07Z","receivedAt":"2026-03-12T17:00:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Using \"--cover-letter\" we can tell format-patch to generate a cover\n> letter, in this cover letter there's a list of commits included in the\n> patch series and the format is specified by the \"--cover-letter-format\"\n> option. Would be useful if this format could be configured from the\n> config file instead of always needing to pass it from the command line.\n>\n> Teach format-patch how to read the format spec for the cover letter from\n> the config files. The variable it should look for is called\n> format.commitListFormat, it accepts the same values as the option\n> \"--cover-letter-format\".\n\nIf we are calling this commitListFormat, we would want to name the\ncommand line option to match it more closely.  Doing so would have\nan added benefit that tab completion on the command line becomes\neasier to use, because --cover-<TAB> would expand to --cover-letter\nuniquely and we do not have to force the user choose between\n\"--cover-letter\" and \"--cover-letter-format=\".\n\nPerhaps \"--commit-list-format=<format>\" or something?\n"},{"id":"538774","messageId":"abLw6vUUh36zFK4n@exploit2","threadId":"65032","inReplyTo":"xmqq5x71gfci.fsf@gitster.g","subject":"Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-12T17:18:23Z","receivedAt":"2026-03-12T17:18:28Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Thu, Mar 12, 2026 at 09:52:29AM -0700, Junio C Hamano wrote:\n> The example does not use a format spec 'prefixed with \"log:\"',\n> though?\n\nYes, I fixed the example but forgot fix the paragraph when rewording.\nWill fix\n\n> The second sentence reads funny.  The option is available whether\n> the user wants to use it or not.  I'd suggest dropping the sentence,\n> without which the paragraph reads just fine.\n\nWill do\n\n> OK, so we are not requiring \"log:\"?  This robs extensibility from\n\nLike I said above, we won't require \"log:\" as per the discussion with\nPhillip.\n\n>                                      This robs extensibility from\n> future developers to introduce something other than \"shortlog\", no?\n> If the version of Git in 'next' supports \"longlog\" and user gives\n\nNot really, anyone can introduce new formats, it's just an additional if\nstatement.\n\n> \"--cover-letter-format=longlog\" to their version that does not yet\n> support it, it would be mistaken by the version of the code here as\n> a \"log:longlog\" without any placeholder that shows a fixed string\n> \"longlog\" for each commit in the series?  We'd rather want such an\n> input to cause failure, no?\n\nIsn't that the same for any feature that is in next but not merged in\nmaster yet? I wouldn't expect subcommands of history not yet merged in\nmaster to work either if I'm using a version built from master. This is\nan issue with the user and I don't think it's grounds for any issue.\n\n> s/If defined, d\\(efaults.*variable\\)\\./D\\1, if defined./ would avoid\n> \"if I define --cover-letter-format, why does it default to a\n> configuration?  do you mean 'if not given'?\"\n> \n> So, \"If defined,\" -> \"If not given\" would be another possible\n> improvement.  I think I like it better, actually.\n\nWill do\n\n> Hmph, an alternative that may make it easier to use is to make the\n> command line option _imply_ \"--cover-letter\", so that the user does\n> not have to give two similar looking command line options.\n> \n>     git format-patch --cover-letter --cover-letter-format=...\n> \n> Of course, the presence of the configuration variable should not\n> imply generation of a cover letter. I.e.\n> \n>     git -c format.commitListInCoverLetterFormat=shortlog \\\n> \t    format-patch -1 HEAD\n> \n> should not imply --cover-letter.\n\nYes, that'd be a nice behaviour to have, it is indeed obvious that I\nwant a cover letter if I'm specifying a format from command line.\n\n> Many issues.\n> \n> In modern tests (written within the past 10 years), we try not to\n> execute things outside text_expect_foo blocks.  The golden output\n> to compare with is customary called 'expect' (not 'expected').  A\n> redirection operator \">\", \"<<\", etc. has a single SP before but no\n> SP after it, when there is no parameter interpolation happens in a\n> HERE document, quote the \"EOF\" marker to show the intention of the\n> author of the test that no parameter interpolation is expected.\n> \n> i.e.,\n> \n>     cat >expect <<\\-EOF\n> \t...\n>     EOF\n> \n> and do so inside a set-up test_expect_success block.\n\nWill fix\n\n> Why do we have so many blank lines?  Are the number of blank lines\n> significant?  Such a hidden and hard to count dependency would hurt\n> maintainability of this test script.\n\nIn the beginning I thought about stripping the empty lines, but doing so\nwould not ensure that those two lines were next to each other. grep\nmatches line by line so I couldn't ensure that they'd be next to each\nother like that.\n\nIf I were to strip the empty lines the test would be not better (imo)\nthan the previous series (where it was flagged down).\n"},{"id":"538775","messageId":"xmqqo6ktezh2.fsf@gitster.g","threadId":"65032","inReplyTo":"cover.1773331753.git.mroik@delayed.space","subject":"Re: [PATCH v8 0/4] format-patch: add cover-letter-format option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T17:20:41Z","receivedAt":"2026-03-12T17:20:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> Squashed the documentation changes to their relevant patch and added a\n> new format preset \"chronological\".\n>\n> Thank you for the reviews\n>\n> [1/4] format-patch: move cover letter summary generation (Mirko Faina)\n> [2/4] format-patch: add ability to use alt cover format (Mirko Faina)\n> [3/4] format-patch: add \"chronological\" format for cover (Mirko Faina)\n> [4/4] format-patch: add commitListFormat config (Mirko Faina)\n\nBy the way, we have merged the topic to 'next' on March 9th already,\nso it is a bit awkward to see a wholesale replacement series.\n\nWe could revert the merge of the previous attempt out of 'next' and\nqueue the new iteration in 'seen', but I think the major changes in\nthis iteration are\n\n (1) the \"log:\" prefix is omitted (which I think is a bad change\n     that we do not want),\n\n (2) we no longer consider the option an extended boolean \"use the\n     modern customized format [Yes/no/use this format]?\" (which I\n     think is OK), and\n\n (3) the default modern format has a name (which is OK, even though\n     \"chronological\" may be a mouthful to say).\n\nand associated documentation and test updates, so at this point,\nmaking incremental changes on top of what we already have in 'next'\nmay be more appropriate.  The incremental changes are easier to\njustify as well.\n\n\n"},{"id":"538777","messageId":"xmqqjyvhez96.fsf@gitster.g","threadId":"65032","inReplyTo":"abLw6vUUh36zFK4n@exploit2","subject":"Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T17:25:25Z","receivedAt":"2026-03-12T17:25:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n>> future developers to introduce something other than \"shortlog\", no?\n>> If the version of Git in 'next' supports \"longlog\" and user gives\n>\n> Not really, anyone can introduce new formats, it's just an additional if\n> statement.\n>\n>> \"--cover-letter-format=longlog\" to their version that does not yet\n>> support it, it would be mistaken by the version of the code here as\n>> a \"log:longlog\" without any placeholder that shows a fixed string\n>> \"longlog\" for each commit in the series?  We'd rather want such an\n>> input to cause failure, no?\n>\n> Isn't that the same for any feature that is in next but not merged in\n> master yet? I wouldn't expect subcommands of history not yet merged in\n> master to work either if I'm using a version built from master. This is\n> an issue with the user and I don't think it's grounds for any issue.\n\nOr misspelt \"chrnological\".  What are we gaining by removing it,\nwhich would rob safe extensibility (aka \"future-proofing\") and typo\ndetection?\n\nIt is a bad idea to drop \"log:\", period.\n"},{"id":"538778","messageId":"xmqqfr65ez5x.fsf@gitster.g","threadId":"65032","inReplyTo":"xmqqjyvhez96.fsf@gitster.g","subject":"Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T17:27:22Z","receivedAt":"2026-03-12T17:27:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Mirko Faina <mroik@delayed.space> writes:\n>\n>>> future developers to introduce something other than \"shortlog\", no?\n>>> If the version of Git in 'next' supports \"longlog\" and user gives\n>>\n>> Not really, anyone can introduce new formats, it's just an additional if\n>> statement.\n>>\n>>> \"--cover-letter-format=longlog\" to their version that does not yet\n>>> support it, it would be mistaken by the version of the code here as\n>>> a \"log:longlog\" without any placeholder that shows a fixed string\n>>> \"longlog\" for each commit in the series?  We'd rather want such an\n>>> input to cause failure, no?\n>>\n>> Isn't that the same for any feature that is in next but not merged in\n>> master yet? I wouldn't expect subcommands of history not yet merged in\n>> master to work either if I'm using a version built from master. This is\n>> an issue with the user and I don't think it's grounds for any issue.\n\nYou need to remember that some people use multiple machines with\ndifferent versions of Git installed.  Yet to be enabled option\nshould be diagnosed as an error to be safe.\n\n> Or misspelt \"chrnological\".  What are we gaining by removing it,\n> which would rob safe extensibility (aka \"future-proofing\") and typo\n> detection?\n>\n> It is a bad idea to drop \"log:\", period.\n"},{"id":"538782","messageId":"abL6gNTwmVfQsmU2@exploit2","threadId":"65032","inReplyTo":"xmqqo6ktezh2.fsf@gitster.g","subject":"Re: [PATCH v8 0/4] format-patch: add cover-letter-format option","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-12T17:45:48Z","receivedAt":"2026-03-12T17:45:52Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Thu, Mar 12, 2026 at 10:20:41AM -0700, Junio C Hamano wrote:\n> By the way, we have merged the topic to 'next' on March 9th already,\n> so it is a bit awkward to see a wholesale replacement series.\n> \n> We could revert the merge of the previous attempt out of 'next' and\n> queue the new iteration in 'seen', but I think the major changes in\n> this iteration are\n> \n>  (1) the \"log:\" prefix is omitted (which I think is a bad change\n>      that we do not want),\n> \n>  (2) we no longer consider the option an extended boolean \"use the\n>      modern customized format [Yes/no/use this format]?\" (which I\n>      think is OK), and\n> \n>  (3) the default modern format has a name (which is OK, even though\n>      \"chronological\" may be a mouthful to say).\n> \n> and associated documentation and test updates, so at this point,\n> making incremental changes on top of what we already have in 'next'\n> may be more appropriate.  The incremental changes are easier to\n> justify as well.\n\n(1) Very well, given that there are more cons than pros to dropping the\nprefix, this change will be dropped and will no longer be discussed\ngoing forward.\n\n(3) Would an abbreviation like \"chrono\" be better? The originally\nsuggested \"numbered\" didn't seem to be very descriptive, that's why I\nwent with chronological.\n\nPlease disregard series v8, I will send a new series that builds on top\nof v7. Regarding this, how should incremental changes be numbered in\ntheir version? Do I keep going incrementally (v9 despite building on top\nof another version), or should I use something like \"v7.1, v7.2, v7.3\"?\n"},{"id":"538784","messageId":"xmqq5x70gbn0.fsf@gitster.g","threadId":"65032","inReplyTo":"abL6gNTwmVfQsmU2@exploit2","subject":"Re: [PATCH v8 0/4] format-patch: add cover-letter-format option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-12T18:12:35Z","receivedAt":"2026-03-12T18:12:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> ..., how should incremental changes be numbered in their version?\n> Do I keep going incrementally (v9 despite building on top of\n> another version), or should I use something like \"v7.1, v7.2,\n> v7.3\"?\n\nWe often see a series that depends on topics that are in 'next' but\nnot yet in 'master', with its cover letter describing how exactly\nthe base of the series should be created.  Patrick often does this,\nand we can learn from cover letters of his topics.  A good example\nis here.\n\nhttps://lore.kernel.org/git/20260305-b4-pks-odb-source-pluggable-v2-0-3290bfd1f444@pks.im/\n\nAnd treating such a series starting from v1 (i.e., the first attempt\nto make that other topic better) would be the easiest to understand.\nThe cover letter would start with something like\n\n    An earlier mf/format-patch-cover-letter-format series that is in\n    'next' but not in 'master' added THESE THINGS to the system.\n    Here is a follow-on series to further improve it by changing\n    SUCH and SUCH things.\n\nbefore describing how its base was constructed.\n\nAs to the naming, I find \"chrono\" worse than \"chronological\",\nbecause there are other possible ways to shorten the overly long\nword, and picking one will force users to remember that choice.\n\nThanks.\n\n"},{"id":"538873","messageId":"1759c2fe-6e7a-41b6-9869-97544870ebef@gmail.com","threadId":"65032","inReplyTo":"xmqqjyvhez96.fsf@gitster.g","subject":"Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-03-13T10:38:05Z","receivedAt":"2026-03-13T10:38:08Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"\n\nOn 12/03/2026 17:25, Junio C Hamano wrote:\n> Mirko Faina <mroik@delayed.space> writes:\n> \n>>> future developers to introduce something other than \"shortlog\", no?\n>>> If the version of Git in 'next' supports \"longlog\" and user gives\n>>\n>> Not really, anyone can introduce new formats, it's just an additional if\n>> statement.\n>>\n>>> \"--cover-letter-format=longlog\" to their version that does not yet\n>>> support it, it would be mistaken by the version of the code here as\n>>> a \"log:longlog\" without any placeholder that shows a fixed string\n>>> \"longlog\" for each commit in the series?  We'd rather want such an\n>>> input to cause failure, no?\n>>\n>> Isn't that the same for any feature that is in next but not merged in\n>> master yet? I wouldn't expect subcommands of history not yet merged in\n>> master to work either if I'm using a version built from master. This is\n>> an issue with the user and I don't think it's grounds for any issue.\n> \n> Or misspelt \"chrnological\".  What are we gaining by removing it,\n> which would rob safe extensibility (aka \"future-proofing\") and typo\n> detection?\n> \n> It is a bad idea to drop \"log:\", period.\n\nIt's a pain to have to prefix the format string with \"log:\" when we \ndon't require it anywhere else. If we want to error out on unknown fixed \nstrings then we could reject format strings that do not contain a '%' \nand do not match any of the other fixed format names such as \"shortlog\". \nIt means you cannot specify a fixed string for the format but that would \nseem to be a rather strange thing to do in the first place.\n\nThanks\n\nPhillip\n\n"},{"id":"538916","messageId":"xmqqqzpn63yn.fsf@gitster.g","threadId":"65032","inReplyTo":"1759c2fe-6e7a-41b6-9869-97544870ebef@gmail.com","subject":"Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-13T17:20:48Z","receivedAt":"2026-03-13T17:20:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> It's a pain to have to prefix the format string with \"log:\" when we \n> don't require it anywhere else.\n\nI do not mind a sort of DWIM similar to \"log --pretty=format:%s\";\ntechnically, \"git log --prefix\" requires the \"format:\" prefix when\nusing a custom format (i.e., not the canned \"short\", \"fuller\", etc.)\nbut we DWIM when the string appears to use %-interpolation.\n"},{"id":"538924","messageId":"abRTEXspvX_z0usP@exploit","threadId":"65032","inReplyTo":"xmqqqzpn63yn.fsf@gitster.g","subject":"Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format","fromName":"Mirko Faina","fromEmail":"mroik@delayed.space","sentAt":"2026-03-13T19:17:01Z","receivedAt":"2026-03-13T19:17:11Z","isPatch":true,"sender":{"key":"mroik@delayed.space","avatar":"https://avatars.githubusercontent.com/u/25752903?v=4"},"body":"On Fri, Mar 13, 2026 at 10:20:48AM -0700, Junio C Hamano wrote:\n> I do not mind a sort of DWIM similar to \"log --pretty=format:%s\";\n> technically, \"git log --prefix\" requires the \"format:\" prefix when\n> using a custom format (i.e., not the canned \"short\", \"fuller\", etc.)\n> but we DWIM when the string appears to use %-interpolation.\n\nThen we can move towards keeping the \"log:\" prefix but\nallowing for it to be dropped when %-interpolation occurs, just like\n--pretty does.\n\nSomething to point out, --pretty does a very simple check (it seems to\ncheck only for the presence of '%'). This does indeed catch typos\nrelated to preset formats like \"short\", \"full\", etc... but it doesn't\ncatch typos in format-strings.\n\"an%\" is an accepted format-string even without the prefix despite not\ndoing any substitution.\n\nMaybe there should be a function, to be used as a check, that parses the\nstring and checks whether the format-string should be accepted.\nThis could be an improvement for a future patch.\n"},{"id":"538931","messageId":"xmqqcy174gyt.fsf@gitster.g","threadId":"65032","inReplyTo":"abRTEXspvX_z0usP@exploit","subject":"Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-13T20:22:50Z","receivedAt":"2026-03-13T20:22:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Mirko Faina <mroik@delayed.space> writes:\n\n> On Fri, Mar 13, 2026 at 10:20:48AM -0700, Junio C Hamano wrote:\n>> I do not mind a sort of DWIM similar to \"log --pretty=format:%s\";\n>> technically, \"git log --prefix\" requires the \"format:\" prefix when\n>> using a custom format (i.e., not the canned \"short\", \"fuller\", etc.)\n>> but we DWIM when the string appears to use %-interpolation.\n>\n> Then we can move towards keeping the \"log:\" prefix but\n> allowing for it to be dropped when %-interpolation occurs, just like\n> --pretty does.\n\nYes.  I vewi the DWIM as icing on the case, though.  IOW, the\nfeature is perfectly fine to require \"log:\" and still move forward,\nand it would be nice if people can say \"--pretty=%s\" without it.\n\nBut that DWIM is \"it would be nice if this is added\".\n"}]}