* [RFC PATCH] format-patch: better commit list for cover letter
@ 2026-02-20 23:06 Mirko Faina
2026-02-20 23:55 ` [RFC PATCH v2] " Mirko Faina
` (5 more replies)
0 siblings, 6 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-20 23:06 UTC (permalink / raw)
To: git; +Cc: Mirko Faina
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
Teach the make_cover_letter() a better cover letter format to replace
the current. The format can be seen from the following example:
[1/3] abcc234s: this is a summary
[2/3] 73s84ns2: this is another summary
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
This patch comes after a small off-topic[1] that discussed the
usefulness (or the lack thereof) of the current cover letter template.
This patch hopes to make a better format so that developers won't have
to make their own custom script to generate better info.
[1] https://lore.kernel.org/git/xmqqbjhjxp2d.fsf@gitster.g/
builtin/log.c | 40 ++++++++++++++++++++++++++--------------
1 file changed, 26 insertions(+), 14 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index c1cd3999a7..4fea895527 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -30,7 +30,6 @@
#include "reflog-walk.h"
#include "patch-ids.h"
#include "path.h"
-#include "shortlog.h"
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
@@ -40,6 +39,8 @@
#include "mailmap.h"
#include "progress.h"
#include "commit-slab.h"
+#include "pretty.h"
+#include "strbuf.h"
#include "commit-reach.h"
#include "range-diff.h"
@@ -1324,6 +1325,29 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_cover_commit_list(FILE *cover_file, struct commit** list, int nr)
+{
+ char* commit_hash_buf = malloc(GIT_MAX_HEXSZ + 1);
+ struct strbuf *sb_loglines = malloc(sizeof(struct strbuf));
+ int temp;
+
+ for (int i = 0; i < nr; i++) {
+ strbuf_init(sb_loglines, 0);
+ strbuf_addf(sb_loglines, "[%0*d/%d] ", decimal_width(nr), i + 1, nr);
+ temp = sb_loglines->len;
+ strbuf_addstr(sb_loglines, oid_to_hex_r(commit_hash_buf, &list[i]->object.oid));
+ strbuf_addch(sb_loglines, ':');
+ strbuf_remove(sb_loglines, temp, sb_loglines->len - temp - 8);
+ strbuf_addch(sb_loglines, ' ');
+ pp_commit_easy(CMIT_FMT_ONELINE, list[i], sb_loglines);
+ fprintf(cover_file, "%s\n", sb_loglines->buf);
+ }
+
+ fprintf(cover_file, "\n");
+ strbuf_release(sb_loglines);
+ free(commit_hash_buf);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
@@ -1333,7 +1357,6 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
const struct format_config *cfg)
{
const char *from;
- struct shortlog log;
struct strbuf sb = STRBUF_INIT;
int i;
const char *encoding = "UTF-8";
@@ -1377,18 +1400,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
-
- shortlog_output(&log);
+ generate_cover_commit_list(rev->diffopt.file, list, nr);
/* We can only do diffstat with a unique reference point */
if (origin)
base-commit: a8e89346a7731cb3104010f322c65e2a0c922618
--
2.53.0
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [RFC PATCH v2] format-patch: better commit list for cover letter
2026-02-20 23:06 [RFC PATCH] format-patch: better commit list for cover letter Mirko Faina
@ 2026-02-20 23:55 ` Mirko Faina
2026-02-21 0:51 ` Mirko Faina
2026-02-21 4:54 ` [RFC PATCH] " Junio C Hamano
` (4 subsequent siblings)
5 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-20 23:55 UTC (permalink / raw)
To: git; +Cc: Mirko Faina
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
Teach the make_cover_letter() a better cover letter format to replace
the current. The format can be seen from the following example:
[1/3] abcc234s: this is a summary
[2/3] 73s84ns2: this is another summary
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
This patch comes after a small off-topic[1] that discussed the
usefulness (or the lack thereof) of the current cover letter template.
This patch hopes to make a better format so that developers won't have
to make their own custom script to generate better info.
P.s. I'm sorry, the first version was a bit wrong. This one is correct.
[1] https://lore.kernel.org/git/xmqqbjhjxp2d.fsf@gitster.g/
builtin/log.c | 40 ++++++++++++++++++++++++++--------------
1 file changed, 26 insertions(+), 14 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index c1cd3999a7..7d446b09b4 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -30,7 +30,6 @@
#include "reflog-walk.h"
#include "patch-ids.h"
#include "path.h"
-#include "shortlog.h"
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
@@ -40,6 +39,8 @@
#include "mailmap.h"
#include "progress.h"
#include "commit-slab.h"
+#include "pretty.h"
+#include "strbuf.h"
#include "commit-reach.h"
#include "range-diff.h"
@@ -1324,6 +1325,29 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_cover_commit_list(FILE *cover_file, struct commit** list, int nr)
+{
+ char* commit_hash_buf = malloc(GIT_MAX_HEXSZ + 1);
+ struct strbuf *sb_loglines = malloc(sizeof(struct strbuf));
+ int temp;
+
+ for (int i = 0; i < nr; i++) {
+ strbuf_init(sb_loglines, 0);
+ strbuf_addf(sb_loglines, "[%0*d/%d] ", decimal_width(nr), i + 1, nr);
+ temp = sb_loglines->len;
+ strbuf_addstr(sb_loglines, oid_to_hex_r(commit_hash_buf, &list[i]->object.oid));
+ strbuf_remove(sb_loglines, temp + 8, sb_loglines->len - (temp + 8));
+ strbuf_addch(sb_loglines, ':');
+ strbuf_addch(sb_loglines, ' ');
+ pp_commit_easy(CMIT_FMT_ONELINE, list[i], sb_loglines);
+ fprintf(cover_file, "%s\n", sb_loglines->buf);
+ }
+
+ fprintf(cover_file, "\n");
+ strbuf_release(sb_loglines);
+ free(commit_hash_buf);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
@@ -1333,7 +1357,6 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
const struct format_config *cfg)
{
const char *from;
- struct shortlog log;
struct strbuf sb = STRBUF_INIT;
int i;
const char *encoding = "UTF-8";
@@ -1377,18 +1400,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
-
- shortlog_output(&log);
+ generate_cover_commit_list(rev->diffopt.file, list, nr);
/* We can only do diffstat with a unique reference point */
if (origin)
base-commit: a8e89346a7731cb3104010f322c65e2a0c922618
--
2.53.0
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [RFC PATCH v2] format-patch: better commit list for cover letter
2026-02-20 23:55 ` [RFC PATCH v2] " Mirko Faina
@ 2026-02-21 0:51 ` Mirko Faina
0 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-21 0:51 UTC (permalink / raw)
To: git; +Cc: Mirko Faina
On Sat, Feb 21, 2026 at 12:55:01AM +0100, Mirko Faina wrote:
> + for (int i = 0; i < nr; i++) {
> + strbuf_init(sb_loglines, 0);
> + strbuf_addf(sb_loglines, "[%0*d/%d] ", decimal_width(nr), i + 1, nr);
> + temp = sb_loglines->len;
> + strbuf_addstr(sb_loglines, oid_to_hex_r(commit_hash_buf, &list[i]->object.oid));
> + strbuf_remove(sb_loglines, temp + 8, sb_loglines->len - (temp + 8));
> + strbuf_addch(sb_loglines, ':');
> + strbuf_addch(sb_loglines, ' ');
> + pp_commit_easy(CMIT_FMT_ONELINE, list[i], sb_loglines);
> + fprintf(cover_file, "%s\n", sb_loglines->buf);
> + }
I also noticed it generates the list in the reversed order (and probably
leaks), but this was just a PoC and I'm more interested in knowing if
the format is liked by the users.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [RFC PATCH] format-patch: better commit list for cover letter
2026-02-20 23:06 [RFC PATCH] format-patch: better commit list for cover letter Mirko Faina
2026-02-20 23:55 ` [RFC PATCH v2] " Mirko Faina
@ 2026-02-21 4:54 ` Junio C Hamano
2026-02-21 5:18 ` Mirko Faina
2026-02-24 4:03 ` [PATCH 0/3] format-patch: add cover-letter-format option Mirko Faina
` (3 subsequent siblings)
5 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-21 4:54 UTC (permalink / raw)
To: Mirko Faina; +Cc: git
Mirko Faina <mroik@delayed.space> writes:
> Often when sending patch series there's a need to clarify to the
> reviewer what's the purpose of said series, since it might be difficult
> to understand it from reading the commits messages one by one.
>
> "git format-patch" provides the useful "--cover-letter" flag to declare
> if we want it to generate a template for us to use. By default it will
> generate a "git shortlog" of the changes, which developers find less
> useful than they'd like, mainly because the shortlog groups commits by
> author, and gives no obvious chronological order.
All true.
> Teach the make_cover_letter() a better cover letter format to replace
> the current. The format can be seen from the following example:
>
> [1/3] abcc234s: this is a summary
> [2/3] 73s84ns2: this is another summary
Two things to consider.
(1) Drop the abbreviated object name, as they are useless garbage.
The result of applying these patches will not have these commit
object names anyway, so even when people find these messages on
a mail archive in 6 months, they will not find the result of
applying the patches from the official project history with
these object names.
(2) Do we need to make this optional, in order to allow those users
who do prefer the current "shortlog" style that groups patches
from the same person together to keep the original style? I am
undecided myself.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [RFC PATCH] format-patch: better commit list for cover letter
2026-02-21 4:54 ` [RFC PATCH] " Junio C Hamano
@ 2026-02-21 5:18 ` Mirko Faina
2026-02-21 5:55 ` Junio C Hamano
0 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-21 5:18 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Mirko Faina
On Fri, Feb 20, 2026 at 08:54:50PM -0800, Junio C Hamano wrote:
> (1) Drop the abbreviated object name, as they are useless garbage.
> The result of applying these patches will not have these commit
> object names anyway, so even when people find these messages on
> a mail archive in 6 months, they will not find the result of
> applying the patches from the official project history with
> these object names.
Should there be a reference to the author ident instead of the object
name then? A quick glance on who worked on what before diving into the
patches themselves might be useful.
> (2) Do we need to make this optional, in order to allow those users
> who do prefer the current "shortlog" style that groups patches
> from the same person together to keep the original style? I am
> undecided myself.
Maybe the "--cover-letter" option can take an argument like
"--cover-letter=<shortlog | commitlist>". Although I doubt there's
anyone that actually likes the shortlog version, it gives very little
information. I'm inclined to think that most leave it there because they
think it must be somewhat since it is the default, tho this is just my
assumption.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [RFC PATCH] format-patch: better commit list for cover letter
2026-02-21 5:18 ` Mirko Faina
@ 2026-02-21 5:55 ` Junio C Hamano
2026-02-21 6:02 ` Junio C Hamano
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-21 5:55 UTC (permalink / raw)
To: Mirko Faina; +Cc: git
Mirko Faina <mroik@delayed.space> writes:
> On Fri, Feb 20, 2026 at 08:54:50PM -0800, Junio C Hamano wrote:
>> (1) Drop the abbreviated object name, as they are useless garbage.
>> The result of applying these patches will not have these commit
>> object names anyway, so even when people find these messages on
>> a mail archive in 6 months, they will not find the result of
>> applying the patches from the official project history with
>> these object names.
>
> Should there be a reference to the author ident instead of the object
> name then? A quick glance on who worked on what before diving into the
> patches themselves might be useful.
>
>> (2) Do we need to make this optional, in order to allow those users
>> who do prefer the current "shortlog" style that groups patches
>> from the same person together to keep the original style? I am
>> undecided myself.
>
> Maybe the "--cover-letter" option can take an argument like
> "--cover-letter=<shortlog | commitlist>". Although I doubt there's
> anyone that actually likes the shortlog version, it gives very little
> information. I'm inclined to think that most leave it there because they
> think it must be somewhat since it is the default, tho this is just my
> assumption.
Just off the top of my head...
Perhaps with
[format]
commitListFormat ;# true
we use
[1/1] format-patch: better commit list for cover letter
and with
[format]
commitListFormat="%s (%an)"
we use
[1/1] format-patch: better commit list for cover letter (Mirko Faina)
instead. IOW, the value of the configuration variable is used as
the format argument "log --format=...", and appended to the fixed
[n/m] that gives the numbers.
Without format.commitListFormat defined, or when it is defined to
false, we'd use the traditional "shortlog" format.
Hmm?
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [RFC PATCH] format-patch: better commit list for cover letter
2026-02-21 5:55 ` Junio C Hamano
@ 2026-02-21 6:02 ` Junio C Hamano
2026-02-21 15:59 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-21 6:02 UTC (permalink / raw)
To: Mirko Faina; +Cc: git
Junio C Hamano <gitster@pobox.com> writes:
>> Maybe the "--cover-letter" option can take an argument like
>> "--cover-letter=<shortlog | commitlist>". ...
>
> Just off the top of my head...
>
> Perhaps with
>
> [format]
> commitListFormat ;# true
>
> we use
>
> [1/1] format-patch: better commit list for cover letter
>
> and with
>
> [format]
> commitListFormat="%s (%an)"
>
> we use
>
> [1/1] format-patch: better commit list for cover letter (Mirko Faina)
>
> instead. IOW, the value of the configuration variable is used as
> the format argument "log --format=...", and appended to the fixed
> [n/m] that gives the numbers.
>
> Without format.commitListFormat defined, or when it is defined to
> false, we'd use the traditional "shortlog" format.
>
> Hmm?
And the corresponding command line option can look like
--cover-letter=(shortlog | log:<log format spec>)
e.g.,
--cover-letter=log:"%s (%an)"
to override configured value per invocation basis.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [RFC PATCH] format-patch: better commit list for cover letter
2026-02-21 6:02 ` Junio C Hamano
@ 2026-02-21 15:59 ` Mirko Faina
2026-02-21 17:33 ` Junio C Hamano
0 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-21 15:59 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Mirko Faina
On Fri, Feb 20, 2026 at 10:02:21PM -0800, Junio C Hamano wrote:
>
> Just off the top of my head...
>
> Perhaps with
>
> [format]
> commitListFormat ;# true
>
> we use
>
> [1/1] format-patch: better commit list for cover letter
>
> and with
>
> [format]
> commitListFormat="%s (%an)"
>
> we use
>
> [1/1] format-patch: better commit list for cover letter (Mirko Faina)
>
> instead. IOW, the value of the configuration variable is used as
> the format argument "log --format=...", and appended to the fixed
> [n/m] that gives the numbers.
>
> Without format.commitListFormat defined, or when it is defined to
> false, we'd use the traditional "shortlog" format.
>
> Hmm?
Yes, sounds good.
> And the corresponding command line option can look like
>
> --cover-letter=(shortlog | log:<log format spec>)
>
> e.g.,
>
> --cover-letter=log:"%s (%an)"
>
> to override configured value per invocation basis.
Not too sure about this one. The point was to have a useful default for
the cover letter template. If users have to pass a format spec through
the command line it kinda defeates the purpose.
Since we're close to having Git 3.0, maybe the change in default
behaviour can be scheduled for 3.0?
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [RFC PATCH] format-patch: better commit list for cover letter
2026-02-21 15:59 ` Mirko Faina
@ 2026-02-21 17:33 ` Junio C Hamano
2026-02-21 19:16 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-21 17:33 UTC (permalink / raw)
To: Mirko Faina; +Cc: git
Mirko Faina <mroik@delayed.space> writes:
> On Fri, Feb 20, 2026 at 10:02:21PM -0800, Junio C Hamano wrote:
>>
>> Just off the top of my head...
>>
>> Perhaps with
>>
>> [format]
>> commitListFormat ;# true
>>
>> we use
>>
>> [1/1] format-patch: better commit list for cover letter
>>
>> and with
>>
>> [format]
>> commitListFormat="%s (%an)"
>>
>> we use
>>
>> [1/1] format-patch: better commit list for cover letter (Mirko Faina)
>>
>> instead. IOW, the value of the configuration variable is used as
>> the format argument "log --format=...", and appended to the fixed
>> [n/m] that gives the numbers.
>>
>> Without format.commitListFormat defined, or when it is defined to
>> false, we'd use the traditional "shortlog" format.
>>
>> Hmm?
>
> Yes, sounds good.
>
>> And the corresponding command line option can look like
>>
>> --cover-letter=(shortlog | log:<log format spec>)
>>
>> e.g.,
>>
>> --cover-letter=log:"%s (%an)"
>>
>> to override configured value per invocation basis.
>
> Not too sure about this one. The point was to have a useful default for
> the cover letter template. If users have to pass a format spec through
> the command line it kinda defeates the purpose.
When adding a configuration, never assume that the setting the user
chooses is good for that user 100% of the time. You'd need a way
from the command line to override a configured value.
Not having a command line option does defeat the point of adding a
configuration, which can even be used for a setting that is good for
the user only 80% of the time.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [RFC PATCH] format-patch: better commit list for cover letter
2026-02-21 17:33 ` Junio C Hamano
@ 2026-02-21 19:16 ` Mirko Faina
0 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-21 19:16 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Mirko Faina
On Sat, Feb 21, 2026 at 09:33:00AM -0800, Junio C Hamano wrote:
> When adding a configuration, never assume that the setting the user
> chooses is good for that user 100% of the time. You'd need a way
> from the command line to override a configured value.
>
> Not having a command line option does defeat the point of adding a
> configuration, which can even be used for a setting that is good for
> the user only 80% of the time.
Good point. I will start working on this in the following week.
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH 0/3] format-patch: add cover-letter-format option
2026-02-20 23:06 [RFC PATCH] format-patch: better commit list for cover letter Mirko Faina
2026-02-20 23:55 ` [RFC PATCH v2] " Mirko Faina
2026-02-21 4:54 ` [RFC PATCH] " Junio C Hamano
@ 2026-02-24 4:03 ` Mirko Faina
2026-02-24 4:06 ` Mirko Faina
2026-02-24 9:29 ` [PATCH v2 0/2] " Mirko Faina
2026-02-24 4:03 ` [PATCH 1/3] pretty.c: fix null pointer dereference Mirko Faina
` (2 subsequent siblings)
5 siblings, 2 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 4:03 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano
I've implemented the "--cover-letter-format" as we discussed. In the end
I've decided to add a new option instead of changing "--cover-letter".
This better reflects how the config file is structured too since we can
have "commitListFormat" set while also having "coverLetter" to false.
While working on this patch series I also encountered a NULL dereference
bug in commit_format_is_empty(). Since I needed it for this patch series
I went ahead and fixed it.
[1/3] pretty.c: fix null pointer dereference (Mirko Faina)
[2/3] format-patch: add ability to use alt cover format (Mirko Faina)
[3/3] format-patch: add commitListFormat config (Mirko Faina)
builtin/log.c | 90 ++++++++++++++++++++++++++++++++++++++++++---------
pretty.c | 2 +-
2 files changed, 75 insertions(+), 17 deletions(-)
--
2.53.0.4.geaa3cc5f7e
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH 1/3] pretty.c: fix null pointer dereference
2026-02-20 23:06 [RFC PATCH] format-patch: better commit list for cover letter Mirko Faina
` (2 preceding siblings ...)
2026-02-24 4:03 ` [PATCH 0/3] format-patch: add cover-letter-format option Mirko Faina
@ 2026-02-24 4:03 ` Mirko Faina
2026-02-24 6:25 ` Junio C Hamano
2026-02-24 4:03 ` [PATCH 2/3] format-patch: add ability to use alt cover format Mirko Faina
2026-02-24 4:03 ` [PATCH 3/3] format-patch: add commitListFormat config Mirko Faina
5 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 4:03 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano
commit_format_is_empty() is used to check whether "user_format" is set
to a value. Unfortunately this function crashes the program if no
user_format is set. This is because instead of checking for the pointer
value it checks for its dereferenced value, this being NULL if
user_format is not set.
Teach the proper condition to check if user_format is set.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
pretty.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pretty.c b/pretty.c
index e0646bbc5d..cdb8bf559d 100644
--- a/pretty.c
+++ b/pretty.c
@@ -47,7 +47,7 @@ static struct cmt_fmt_map *find_commit_format(const char *sought);
int commit_format_is_empty(enum cmit_fmt fmt)
{
- return fmt == CMIT_FMT_USERFORMAT && !*user_format;
+ return fmt == CMIT_FMT_USERFORMAT && !user_format;
}
static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)
--
2.53.0.4.geaa3cc5f7e
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH 2/3] format-patch: add ability to use alt cover format
2026-02-20 23:06 [RFC PATCH] format-patch: better commit list for cover letter Mirko Faina
` (3 preceding siblings ...)
2026-02-24 4:03 ` [PATCH 1/3] pretty.c: fix null pointer dereference Mirko Faina
@ 2026-02-24 4:03 ` Mirko Faina
2026-02-24 9:02 ` Jeff King
2026-02-24 4:03 ` [PATCH 3/3] format-patch: add commitListFormat config Mirko Faina
5 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 4:03 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
Give the ability to format-patch to specify an alternative format spec
through the "--cover-letter-format" option. This option either takes
"shortlog", which is the current format, or a format spec prefixed with
"log:".
Example:
git format-patch --cover-letter \
--cover-letter-format="log:%s (%an)" HEAD~3
[1/3] this is a commit summary (Mirko Faina)
[2/3] this is another commit summary (Mirko Faina)
...
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 68 +++++++++++++++++++++++++++++++++++++++------------
1 file changed, 52 insertions(+), 16 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index c1cd3999a7..80ec79efe8 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1324,13 +1324,35 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_commit_list_cover(FILE *cover_file, const char *format, struct commit **list, int n)
+{
+ struct strbuf commit_line = STRBUF_INIT;
+ struct rev_info rev = REV_INFO_INIT;
+
+ strbuf_init(&commit_line, 0);
+ get_commit_format(format, &rev);
+ if (commit_format_is_empty(CMIT_FMT_USERFORMAT))
+ die(_("invalid format spec"));
+
+ for (int i = n - 1; i >= 0; i--) {
+ strbuf_addf(&commit_line, "[%0*d/%d] ", decimal_width(n), n - i, n);
+ pp_commit_easy(CMIT_FMT_USERFORMAT, list[i], &commit_line);
+ fprintf(cover_file, "%s\n", commit_line.buf);
+ strbuf_reset(&commit_line);
+ }
+ fprintf(cover_file, "\n");
+
+ strbuf_release(&commit_line);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
const char *description_file,
const char *branch_name,
int quiet,
- const struct format_config *cfg)
+ const struct format_config *cfg,
+ const char *format)
{
const char *from;
struct shortlog log;
@@ -1342,6 +1364,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *head = list[0];
char *to_free = NULL;
+ assert(format);
+
if (!cmit_fmt_is_mail(rev->commit_format))
die(_("cover letter needs email format"));
@@ -1377,18 +1401,22 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
+ if (skip_prefix(format, "log:", &format)) {
+ generate_commit_list_cover(rev->diffopt.file, format, list, nr);
+ } else {
+ shortlog_init(&log);
+ log.wrap_lines = 1;
+ log.wrap = MAIL_DEFAULT_WRAP;
+ log.in1 = 2;
+ log.in2 = 4;
+ log.file = rev->diffopt.file;
+ log.groups = SHORTLOG_GROUP_AUTHOR;
+ shortlog_finish_setup(&log);
+ for (i = 0; i < nr; i++)
+ shortlog_add_commit(&log, list[i]);
- shortlog_output(&log);
+ shortlog_output(&log);
+ }
/* We can only do diffstat with a unique reference point */
if (origin)
@@ -1906,6 +1934,7 @@ int cmd_format_patch(int argc,
int just_numbers = 0;
int ignore_if_in_upstream = 0;
int cover_letter = -1;
+ char *cover_letter_fmt = NULL;
int boundary_count = 0;
int no_binary_diff = 0;
int zero_commit = 0;
@@ -1952,6 +1981,8 @@ int cmd_format_patch(int argc,
N_("print patches to standard out")),
OPT_BOOL(0, "cover-letter", &cover_letter,
N_("generate a cover letter")),
+ OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
+ N_("format spec used for the commit list in the cover letter")),
OPT_BOOL(0, "numbered-files", &just_numbers,
N_("use simple number sequence for output file names")),
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
@@ -2289,13 +2320,14 @@ int cmd_format_patch(int argc,
/* nothing to do */
goto done;
total = list.nr;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
- cover_letter = (total > 1);
+ cover_letter = total > 1;
else if ((idiff_prev.nr || rdiff_prev) && (total > 1))
- cover_letter = (cfg.config_cover_letter != COVER_OFF);
+ cover_letter = cfg.config_cover_letter != COVER_OFF;
else
- cover_letter = (cfg.config_cover_letter == COVER_ON);
+ cover_letter = cfg.config_cover_letter == COVER_ON;
}
if (!cfg.keep_subject && cfg.auto_number && (total > 1 || cover_letter))
cfg.numbered = 1;
@@ -2375,12 +2407,16 @@ int cmd_format_patch(int argc,
}
rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
+
+ if (cover_letter && !cover_letter_fmt)
+ cover_letter_fmt = "shortlog";
+
if (cover_letter) {
if (cfg.thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, !!output_directory,
origin, list.nr, list.items,
- description_file, branch_name, quiet, &cfg);
+ description_file, branch_name, quiet, &cfg, cover_letter_fmt);
print_bases(&bases, rev.diffopt.file);
print_signature(signature, rev.diffopt.file);
total++;
--
2.53.0.4.geaa3cc5f7e
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH 3/3] format-patch: add commitListFormat config
2026-02-20 23:06 [RFC PATCH] format-patch: better commit list for cover letter Mirko Faina
` (4 preceding siblings ...)
2026-02-24 4:03 ` [PATCH 2/3] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-02-24 4:03 ` Mirko Faina
5 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 4:03 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano
Using "--cover-letter" we can tell format-patch to generate a cover
letter, in this cover letter there's a list of commits included in the
patch series and the format is specified by the "--cover-letter-format"
option. Would be useful if this format could be configured from the
config file instead of always needing to pass it from the command line.
Teach format-patch how to read the format spec for the cover letter from
the config files. The variable it should look for is called
"commitListFormat".
If commitListFormat is set but not string is passed, it will use the
"%s" format spec, if a string is passed will use it as a format spec, if
it is not set at all it will default to the shortlog format.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/builtin/log.c b/builtin/log.c
index 80ec79efe8..7f41c83e18 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -886,6 +886,7 @@ struct format_config {
char *signature;
char *signature_file;
enum cover_setting config_cover_letter;
+ char* fmt_cover_letter_commit_list;
char *config_output_directory;
enum cover_from_description cover_from_description_mode;
int show_notes;
@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
string_list_clear(&cfg->extra_cc, 0);
strbuf_release(&cfg->sprefix);
free(cfg->fmt_patch_suffix);
+ free(cfg->fmt_cover_letter_commit_list);
}
static enum cover_from_description parse_cover_from_description(const char *arg)
@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
return 0;
}
+ if (!strcmp(var, "format.commitlistformat")) {
+ struct strbuf tmp = STRBUF_INIT;
+ strbuf_init(&tmp, 0);
+ strbuf_addstr(&tmp, "log:");
+ if (value)
+ strbuf_addstr(&tmp, value);
+ else
+ strbuf_addstr(&tmp, "%s");
+
+ git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
+ strbuf_release(&tmp);
+ return 0;
+ }
if (!strcmp(var, "format.outputdirectory")) {
FREE_AND_NULL(cfg->config_output_directory);
return git_config_string(&cfg->config_output_directory, var, value);
@@ -2321,6 +2336,13 @@ int cmd_format_patch(int argc,
goto done;
total = list.nr;
+ if (cover_letter_fmt && (strcmp(cover_letter_fmt, "shortlog") && strncmp(cover_letter_fmt, "log:", 4))) {
+ die(_("--cover-letter: invalid format spec"));
+ }
+
+ if (!cover_letter_fmt)
+ cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = total > 1;
--
2.53.0.4.geaa3cc5f7e
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH 0/3] format-patch: add cover-letter-format option
2026-02-24 4:03 ` [PATCH 0/3] format-patch: add cover-letter-format option Mirko Faina
@ 2026-02-24 4:06 ` Mirko Faina
2026-02-24 9:29 ` [PATCH v2 0/2] " Mirko Faina
1 sibling, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 4:06 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano
Sorry, I forgot to thread it
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH 1/3] pretty.c: fix null pointer dereference
2026-02-24 4:03 ` [PATCH 1/3] pretty.c: fix null pointer dereference Mirko Faina
@ 2026-02-24 6:25 ` Junio C Hamano
2026-02-24 7:08 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-24 6:25 UTC (permalink / raw)
To: Mirko Faina; +Cc: git
Mirko Faina <mroik@delayed.space> writes:
> commit_format_is_empty() is used to check whether "user_format" is set
> to a value. Unfortunately this function crashes the program if no
> user_format is set. This is because instead of checking for the pointer
> value it checks for its dereferenced value, this being NULL if
> user_format is not set.
>
> Teach the proper condition to check if user_format is set.
>
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> pretty.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
Interesting.
Are any of the existing calls to this function that passes
CMIT_FMT_USERFORMAT trigger a segfault with certain condition?
For example, there are only two places where CMIT_FMT_USERFORMAT is
assigned to something. One is save_user_format() where user_format
gets a non NULL string before rev->commit_format gets assigned
CMIT_FMT_USERFORMAT. Another is git_pretty_formats_config() that
parses configuration variables "pretty.*" and populate
commit_formats map. This is later used in get_commit_format() and
that function always calls save_user_format() we just saw when the
format used is CMIT_FMT_USERFORMAT. So the existing code paths seem
to be safe by design.
What I am wondering is if a NULL user_format should be flagged as a
programming error, instead of getting swept under the rug like this
patch does. IOW,
int commit_format_is_empty(enum cmit_fmt fmt)
{
if (fmt != CMIT_FMT_USERFORMAT)
return 0;
if (!user_format)
BUG("never called save_user_format() and using USERFORMAT?");
return !*user_format;
}
>
> diff --git a/pretty.c b/pretty.c
> index e0646bbc5d..cdb8bf559d 100644
> --- a/pretty.c
> +++ b/pretty.c
> @@ -47,7 +47,7 @@ static struct cmt_fmt_map *find_commit_format(const char *sought);
>
> int commit_format_is_empty(enum cmit_fmt fmt)
> {
> - return fmt == CMIT_FMT_USERFORMAT && !*user_format;
> + return fmt == CMIT_FMT_USERFORMAT && !user_format;
> }
>
> static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH 1/3] pretty.c: fix null pointer dereference
2026-02-24 6:25 ` Junio C Hamano
@ 2026-02-24 7:08 ` Mirko Faina
2026-02-24 7:43 ` Mirko Faina
2026-02-24 8:41 ` Jeff King
0 siblings, 2 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 7:08 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Mirko Faina
On Mon, Feb 23, 2026 at 10:25:07PM -0800, Junio C Hamano wrote:
> Interesting.
>
> Are any of the existing calls to this function that passes
> CMIT_FMT_USERFORMAT trigger a segfault with certain condition?
>
> For example, there are only two places where CMIT_FMT_USERFORMAT is
> assigned to something. One is save_user_format() where user_format
> gets a non NULL string before rev->commit_format gets assigned
> CMIT_FMT_USERFORMAT. Another is git_pretty_formats_config() that
> parses configuration variables "pretty.*" and populate
> commit_formats map. This is later used in get_commit_format() and
> that function always calls save_user_format() we just saw when the
> format used is CMIT_FMT_USERFORMAT. So the existing code paths seem
> to be safe by design.
>
> What I am wondering is if a NULL user_format should be flagged as a
> programming error, instead of getting swept under the rug like this
> patch does. IOW,
>
> int commit_format_is_empty(enum cmit_fmt fmt)
> {
> if (fmt != CMIT_FMT_USERFORMAT)
> return 0;
> if (!user_format)
> BUG("never called save_user_format() and using USERFORMAT?");
> return !*user_format;
> }
This doesn't convince me, I think it is a bug. If dereferencing NULL was
done on purpose why not use die() instead? Also, the only way for us to
check user_format is through commit_format_is_empty() as it is static.
In a complex config setup it might be useful to double check just to be
sure, and I wouldn't want the program to crash on a failed check.
save_user_format() is not available neither, so evaluation of the format
string has to be done through get_commit_format(). If I pass any of the
predefined formats (CMIT_FMT_*) get_commit_format() won't set
user_format. So the only way for us to check if it was set is through
commit_format_is_empty() (well technically there's
rev_info->commit_format but it still doesn't feel like it was
intentional).
But if the intended behaviour was for the program to crash then I take
issue with the name.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH 1/3] pretty.c: fix null pointer dereference
2026-02-24 7:08 ` Mirko Faina
@ 2026-02-24 7:43 ` Mirko Faina
2026-02-24 8:41 ` Jeff King
1 sibling, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 7:43 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Mirko Faina, Jeff King
> void get_commit_format(const char *arg, struct rev_info *rev)
> {
> struct cmt_fmt_map *commit_format;
>
> rev->use_terminator = 0;
> if (!arg) {
> rev->commit_format = CMIT_FMT_DEFAULT;
> return;
> }
> if (skip_prefix(arg, "format:", &arg)) {
> save_user_format(rev, arg, 0);
> return;
> }
>
> if (!*arg || skip_prefix(arg, "tformat:", &arg) || strchr(arg, '%')) {
Seeing how "arg" is being handled here I am now unsure if it is actually
a bug.
CC'ing Jeff for clarifications
> save_user_format(rev, arg, 1);
> return;
> }
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH 1/3] pretty.c: fix null pointer dereference
2026-02-24 7:08 ` Mirko Faina
2026-02-24 7:43 ` Mirko Faina
@ 2026-02-24 8:41 ` Jeff King
1 sibling, 0 replies; 113+ messages in thread
From: Jeff King @ 2026-02-24 8:41 UTC (permalink / raw)
To: Mirko Faina; +Cc: Junio C Hamano, git
On Tue, Feb 24, 2026 at 08:08:35AM +0100, Mirko Faina wrote:
> On Mon, Feb 23, 2026 at 10:25:07PM -0800, Junio C Hamano wrote:
> > Interesting.
> >
> > Are any of the existing calls to this function that passes
> > CMIT_FMT_USERFORMAT trigger a segfault with certain condition?
> >
> > For example, there are only two places where CMIT_FMT_USERFORMAT is
> > assigned to something. One is save_user_format() where user_format
> > gets a non NULL string before rev->commit_format gets assigned
> > CMIT_FMT_USERFORMAT. Another is git_pretty_formats_config() that
> > parses configuration variables "pretty.*" and populate
> > commit_formats map. This is later used in get_commit_format() and
> > that function always calls save_user_format() we just saw when the
> > format used is CMIT_FMT_USERFORMAT. So the existing code paths seem
> > to be safe by design.
> >
> > What I am wondering is if a NULL user_format should be flagged as a
> > programming error, instead of getting swept under the rug like this
> > patch does. IOW,
> >
> > int commit_format_is_empty(enum cmit_fmt fmt)
> > {
> > if (fmt != CMIT_FMT_USERFORMAT)
> > return 0;
> > if (!user_format)
> > BUG("never called save_user_format() and using USERFORMAT?");
> > return !*user_format;
> > }
>
> This doesn't convince me, I think it is a bug. If dereferencing NULL was
> done on purpose why not use die() instead? Also, the only way for us to
> check user_format is through commit_format_is_empty() as it is static.
> In a complex config setup it might be useful to double check just to be
> sure, and I wouldn't want the program to crash on a failed check.
>
> save_user_format() is not available neither, so evaluation of the format
> string has to be done through get_commit_format(). If I pass any of the
> predefined formats (CMIT_FMT_*) get_commit_format() won't set
> user_format. So the only way for us to check if it was set is through
> commit_format_is_empty() (well technically there's
> rev_info->commit_format but it still doesn't feel like it was
> intentional).
>
> But if the intended behaviour was for the program to crash then I take
> issue with the name.
I am not quite sure what you are asking. No, the intent of that function
is not to crash. It is to check whether the user passed us an empty
string. Like the "git diff-tree --format= $commit" example given
in the commit message of b9c7d6e433 (pretty: make empty userformats
truly empty, 2014-07-29). If they did, then the first character of the
string will be the NUL terminator.
So dropping the "*" as your patch 1/3 does is just wrong. It is losing
the check for an empty string and replacing it with a check for a NULL
pointer.
The user_format string should never be NULL if we are using
CMIT_FMT_USERFORMAT. That's not checked for explicitly here, but is an
assumption of the pretty.c code. If there's some way to violate that
assumption, that's a bug (but it sounds from digging that there isn't).
If you have _new_ code which is using CMIT_FMT_USERFORMAT without
setting user_format to a non-NULL value, we might need to work around
that assumption. But I think what your 2/3 is doing is not quite at the
right level, which is the source of the trouble. I'll respond separately
to that patch.
-Peff
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH 2/3] format-patch: add ability to use alt cover format
2026-02-24 4:03 ` [PATCH 2/3] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-02-24 9:02 ` Jeff King
2026-02-24 9:09 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Jeff King @ 2026-02-24 9:02 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Junio C Hamano
On Tue, Feb 24, 2026 at 05:03:57AM +0100, Mirko Faina wrote:
> +static void generate_commit_list_cover(FILE *cover_file, const char *format, struct commit **list, int n)
> +{
OK, so we're expecting "format" here to be the full format string here
(so "%s" or whatever).
But here...
> + get_commit_format(format, &rev);
...this isn't quite the function you want to parse it. This function is
more about parsing the --pretty option for git-log, etc. It allows named
formats like "oneline", "medium", and so on, as well as "format:%s" (and
just "%s", as we treat unknown names with a percent as if they had
tformat: prepended).
As a side effect, it sets up the global user_format variable. Which is
horrible and subtle, but a result of historical function interfaces.
I'll get to that at the bottom of this email.
Back to your patch, I guess here:
> + if (commit_format_is_empty(CMIT_FMT_USERFORMAT))
> + die(_("invalid format spec"));
you are trying to check if we got a USERFORMAT, versus something else. A
more direct way to check that would be:
if (rev->commit_format != CMIT_FMT_USERFORMAT)
But if we do not use get_commit_format() in the first place, we don't
need to worry about that. And I guess you used it because of:
> + pp_commit_easy(CMIT_FMT_USERFORMAT, list[i], &commit_line);
that line, which is using the full-on pretty-print system that git-log,
etc, use. It doesn't really work with CMIT_FMT_USERFORMAT because you
have to set up the global user_format first (for those same historical
reasons).
So I think the entry point you want is repo_format_commit_message(),
which does a one-off custom format into a strbuf without needing a
rev_info, or touching the global user_format, etc.
Something like this:
diff --git a/builtin/log.c b/builtin/log.c
index 5e9e337be4..370367a15a 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1326,16 +1326,12 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
static void generate_commit_list_cover(FILE *cover_file, const char *format, struct commit **list, int n)
{
struct strbuf commit_line = STRBUF_INIT;
- struct rev_info rev = REV_INFO_INIT;
-
- strbuf_init(&commit_line, 0);
- get_commit_format(format, &rev);
- if (commit_format_is_empty(CMIT_FMT_USERFORMAT))
- die(_("invalid format spec"));
+ struct pretty_print_context ctx = {0};
for (int i = n - 1; i >= 0; i--) {
strbuf_addf(&commit_line, "[%0*d/%d] ", decimal_width(n), n - i, n);
- pp_commit_easy(CMIT_FMT_USERFORMAT, list[i], &commit_line);
+ repo_format_commit_message(the_repository, list[i],
+ format, &commit_line, &ctx);
fprintf(cover_file, "%s\n", commit_line.buf);
strbuf_reset(&commit_line);
}
I think that should make your series do what you want. Now...is the
pretty-print code a horrible minefield of booby traps waiting to spring
on the unwary developer? Yes, it is. ;)
The global user_format thing is there because it was bolted onto the
existing pretty-print code, which used a single enum to store the
format. But that enum isn't enough for a user-format, because we also
have an associated string. The "right" type is probably something like:
struct commit_format {
enum cmit_fmt fmt;
const char *user_format;
};
But C being what it is, switching all of the functions to that breaks
all of the callers which just want to pass CMIT_FMT_ONELINE or whatever.
So we have sort of a split world, where you can use pp_commit_easy()
with the baked-in formats, and using the custom format code uses a
totally different function.
I think in the long run we probably do need to clean up the global
user_format. Two pieces of code interleaving calls to the pretty-printer
would stomp on each other's formats, for example. We've mostly gotten by
because Git, being organized as a set of small programs, generally has
one "main" output (for git-log, etc) and then any smaller outputs within
a program are done by one-off calls to repo_format_commit_message(),
etc.
-Peff
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH 2/3] format-patch: add ability to use alt cover format
2026-02-24 9:02 ` Jeff King
@ 2026-02-24 9:09 ` Mirko Faina
2026-02-24 9:18 ` Jeff King
0 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 9:09 UTC (permalink / raw)
To: Jeff King; +Cc: git, Junio C Hamano, Mirko Faina
Thank you for taking time to explain more in depth. I'm sorry for having
CC'd you for such a trivial matter, I just didn't realise it was
possible for an empty string to make it there, I assumed it would've
been checked earlier.
Now I understand what Junio was trying to say. I'll apply the changes
and drop the first patch.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH 2/3] format-patch: add ability to use alt cover format
2026-02-24 9:09 ` Mirko Faina
@ 2026-02-24 9:18 ` Jeff King
0 siblings, 0 replies; 113+ messages in thread
From: Jeff King @ 2026-02-24 9:18 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Junio C Hamano
On Tue, Feb 24, 2026 at 10:09:39AM +0100, Mirko Faina wrote:
> Thank you for taking time to explain more in depth. I'm sorry for having
> CC'd you for such a trivial matter, I just didn't realise it was
> possible for an empty string to make it there, I assumed it would've
> been checked earlier.
It is my penance for having been involved in the mess that is pretty.c
in the first place. ;)
-Peff
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v2 0/2] format-patch: add cover-letter-format option
2026-02-24 4:03 ` [PATCH 0/3] format-patch: add cover-letter-format option Mirko Faina
2026-02-24 4:06 ` Mirko Faina
@ 2026-02-24 9:29 ` Mirko Faina
2026-02-24 9:29 ` [PATCH v2 1/2] format-patch: add ability to use alt cover format Mirko Faina
` (3 more replies)
1 sibling, 4 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 9:29 UTC (permalink / raw)
To: git; +Cc: Mroik, Junio C Hamano, Jeff King
From: Mroik <mroik@delayed.space>
I've dropped the first patch of the series and applied the changes that
Jeff suggested.
Thank you both for the review.
[1/2] format-patch: add ability to use alt cover format (Mirko Faina)
[2/2] format-patch: add commitListFormat config (Mirko Faina)
builtin/log.c | 87 +++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 71 insertions(+), 16 deletions(-)
base-commit: a8e89346a7731cb3104010f322c65e2a0c922618
--
2.53.0.3.g6a0c7aecfd
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v2 1/2] format-patch: add ability to use alt cover format
2026-02-24 9:29 ` [PATCH v2 0/2] " Mirko Faina
@ 2026-02-24 9:29 ` Mirko Faina
2026-02-24 17:40 ` Junio C Hamano
` (2 more replies)
2026-02-24 9:29 ` [PATCH v2 2/2] format-patch: add commitListFormat config Mirko Faina
` (2 subsequent siblings)
3 siblings, 3 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 9:29 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
Give the ability to format-patch to specify an alternative format spec
through the "--cover-letter-format" option. This option either takes
"shortlog", which is the current format, or a format spec prefixed with
"log:".
Example:
git format-patch --cover-letter \
--cover-letter-format="log:%s (%an)" HEAD~3
[1/3] this is a commit summary (Mirko Faina)
[2/3] this is another commit summary (Mirko Faina)
...
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 65 ++++++++++++++++++++++++++++++++++++++-------------
1 file changed, 49 insertions(+), 16 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index c1cd3999a7..5e99660d7c 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1324,13 +1324,32 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_commit_list_cover(FILE *cover_file,const char *format,
+ struct commit **list, int n)
+{
+ struct strbuf commit_line = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+
+ strbuf_init(&commit_line, 0);
+ for (int i = n - 1; i >= 0; i--) {
+ strbuf_addf(&commit_line, "[%0*d/%d] ", decimal_width(n), n - i, n);
+ repo_format_commit_message(the_repository, list[i], format, &commit_line, &ctx);
+ fprintf(cover_file, "%s\n", commit_line.buf);
+ strbuf_reset(&commit_line);
+ }
+ fprintf(cover_file, "\n");
+
+ strbuf_release(&commit_line);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
const char *description_file,
const char *branch_name,
int quiet,
- const struct format_config *cfg)
+ const struct format_config *cfg,
+ const char *format)
{
const char *from;
struct shortlog log;
@@ -1342,6 +1361,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *head = list[0];
char *to_free = NULL;
+ assert(format);
+
if (!cmit_fmt_is_mail(rev->commit_format))
die(_("cover letter needs email format"));
@@ -1377,18 +1398,22 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
+ if (skip_prefix(format, "log:", &format)) {
+ generate_commit_list_cover(rev->diffopt.file, format, list, nr);
+ } else {
+ shortlog_init(&log);
+ log.wrap_lines = 1;
+ log.wrap = MAIL_DEFAULT_WRAP;
+ log.in1 = 2;
+ log.in2 = 4;
+ log.file = rev->diffopt.file;
+ log.groups = SHORTLOG_GROUP_AUTHOR;
+ shortlog_finish_setup(&log);
+ for (i = 0; i < nr; i++)
+ shortlog_add_commit(&log, list[i]);
- shortlog_output(&log);
+ shortlog_output(&log);
+ }
/* We can only do diffstat with a unique reference point */
if (origin)
@@ -1906,6 +1931,7 @@ int cmd_format_patch(int argc,
int just_numbers = 0;
int ignore_if_in_upstream = 0;
int cover_letter = -1;
+ char *cover_letter_fmt = NULL;
int boundary_count = 0;
int no_binary_diff = 0;
int zero_commit = 0;
@@ -1952,6 +1978,8 @@ int cmd_format_patch(int argc,
N_("print patches to standard out")),
OPT_BOOL(0, "cover-letter", &cover_letter,
N_("generate a cover letter")),
+ OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
+ N_("format spec used for the commit list in the cover letter")),
OPT_BOOL(0, "numbered-files", &just_numbers,
N_("use simple number sequence for output file names")),
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
@@ -2289,13 +2317,14 @@ int cmd_format_patch(int argc,
/* nothing to do */
goto done;
total = list.nr;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
- cover_letter = (total > 1);
+ cover_letter = total > 1;
else if ((idiff_prev.nr || rdiff_prev) && (total > 1))
- cover_letter = (cfg.config_cover_letter != COVER_OFF);
+ cover_letter = cfg.config_cover_letter != COVER_OFF;
else
- cover_letter = (cfg.config_cover_letter == COVER_ON);
+ cover_letter = cfg.config_cover_letter == COVER_ON;
}
if (!cfg.keep_subject && cfg.auto_number && (total > 1 || cover_letter))
cfg.numbered = 1;
@@ -2375,12 +2404,16 @@ int cmd_format_patch(int argc,
}
rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
+
+ if (cover_letter && !cover_letter_fmt)
+ cover_letter_fmt = "shortlog";
+
if (cover_letter) {
if (cfg.thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, !!output_directory,
origin, list.nr, list.items,
- description_file, branch_name, quiet, &cfg);
+ description_file, branch_name, quiet, &cfg, cover_letter_fmt);
print_bases(&bases, rev.diffopt.file);
print_signature(signature, rev.diffopt.file);
total++;
--
2.53.0.3.g6a0c7aecfd
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v2 2/2] format-patch: add commitListFormat config
2026-02-24 9:29 ` [PATCH v2 0/2] " Mirko Faina
2026-02-24 9:29 ` [PATCH v2 1/2] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-02-24 9:29 ` Mirko Faina
2026-02-24 18:07 ` Junio C Hamano
2026-02-24 20:38 ` [PATCH v2 0/2] format-patch: add cover-letter-format option Junio C Hamano
2026-02-27 1:52 ` [PATCH v3 0/4] " Mirko Faina
3 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 9:29 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Using "--cover-letter" we can tell format-patch to generate a cover
letter, in this cover letter there's a list of commits included in the
patch series and the format is specified by the "--cover-letter-format"
option. Would be useful if this format could be configured from the
config file instead of always needing to pass it from the command line.
Teach format-patch how to read the format spec for the cover letter from
the config files. The variable it should look for is called
"commitListFormat".
If commitListFormat is set but not string is passed, it will use the
"%s" format spec, if a string is passed will use it as a format spec, if
it is not set at all it will default to the shortlog format.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/builtin/log.c b/builtin/log.c
index 5e99660d7c..725ebf13f6 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -886,6 +886,7 @@ struct format_config {
char *signature;
char *signature_file;
enum cover_setting config_cover_letter;
+ char* fmt_cover_letter_commit_list;
char *config_output_directory;
enum cover_from_description cover_from_description_mode;
int show_notes;
@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
string_list_clear(&cfg->extra_cc, 0);
strbuf_release(&cfg->sprefix);
free(cfg->fmt_patch_suffix);
+ free(cfg->fmt_cover_letter_commit_list);
}
static enum cover_from_description parse_cover_from_description(const char *arg)
@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
return 0;
}
+ if (!strcmp(var, "format.commitlistformat")) {
+ struct strbuf tmp = STRBUF_INIT;
+ strbuf_init(&tmp, 0);
+ strbuf_addstr(&tmp, "log:");
+ if (value)
+ strbuf_addstr(&tmp, value);
+ else
+ strbuf_addstr(&tmp, "%s");
+
+ git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
+ strbuf_release(&tmp);
+ return 0;
+ }
if (!strcmp(var, "format.outputdirectory")) {
FREE_AND_NULL(cfg->config_output_directory);
return git_config_string(&cfg->config_output_directory, var, value);
@@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,
goto done;
total = list.nr;
+ if (cover_letter_fmt && (strcmp(cover_letter_fmt, "shortlog") && strncmp(cover_letter_fmt, "log:", 4))) {
+ die(_("--cover-letter: invalid format spec"));
+ }
+
+ if (!cover_letter_fmt)
+ cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = total > 1;
--
2.53.0.3.g6a0c7aecfd
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format
2026-02-24 9:29 ` [PATCH v2 1/2] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-02-24 17:40 ` Junio C Hamano
2026-02-24 23:54 ` Mirko Faina
2026-02-24 20:25 ` Junio C Hamano
2026-02-25 13:56 ` Jeff King
2 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-24 17:40 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> Often when sending patch series there's a need to clarify to the
> reviewer what's the purpose of said series, since it might be difficult
> to understand it from reading the commits messages one by one.
>
> "git format-patch" provides the useful "--cover-letter" flag to declare
> if we want it to generate a template for us to use. By default it will
> generate a "git shortlog" of the changes, which developers find less
> useful than they'd like, mainly because the shortlog groups commits by
> author, and gives no obvious chronological order.
>
> Give the ability to format-patch to specify an alternative format spec
> through the "--cover-letter-format" option. This option either takes
> "shortlog", which is the current format, or a format spec prefixed with
> "log:".
>
> Example:
> git format-patch --cover-letter \
> --cover-letter-format="log:%s (%an)" HEAD~3
>
> [1/3] this is a commit summary (Mirko Faina)
> [2/3] this is another commit summary (Mirko Faina)
> ...
>
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> builtin/log.c | 65 ++++++++++++++++++++++++++++++++++++++-------------
> 1 file changed, 49 insertions(+), 16 deletions(-)
>
> diff --git a/builtin/log.c b/builtin/log.c
> index c1cd3999a7..5e99660d7c 100644
> --- a/builtin/log.c
> +++ b/builtin/log.c
> @@ -1324,13 +1324,32 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
> }
> }
>
> +static void generate_commit_list_cover(FILE *cover_file,const char *format,
"cover_file,const" -> "cover_file, const" (missing SP).
> + struct commit **list, int n)
> +{
> + struct strbuf commit_line = STRBUF_INIT;
> + struct pretty_print_context ctx = {0};
> +
> + strbuf_init(&commit_line, 0);
So a single commit_line is given to repo_format_commit_message()
repeatedly to accumulate those lines in it, which makes sense.
We prepare pretty_print_context once above and feed it repeatedly to
repo_format_commit_message()---is that intended? Not a rhetorical
question; I do not know the answer. I guess some existing callers
(like builtin/archive.c) do reuse the structure when making multiple
calls to the function, so this would be kosher, perhaps?
> + for (int i = n - 1; i >= 0; i--) {
> + strbuf_addf(&commit_line, "[%0*d/%d] ", decimal_width(n), n - i, n);
> + repo_format_commit_message(the_repository, list[i], format, &commit_line, &ctx);
Let's line-wrap this overly long line (my lithmus test to complain
about "overly long lines" is after losing three columns to the left
for "> +" in e-mail quote like the above the right edge of the line
does not fit on my 92-column terminal, which will never happen if
you stick to the official "fit 80-column after quoted for a few
times in e-mail" guideline).
repo_format_commit_message(the_repository, list[i], format,
&commit_line, &ctx);
perhaps.
> + fprintf(cover_file, "%s\n", commit_line.buf);
I somehow would have expected that as we internally prepare "format"
string given to this function , we ensure it ends with "\n" so we do
not have to do a fprintf() here.
> + strbuf_reset(&commit_line);
> + }
> + fprintf(cover_file, "\n");
OK, so we have a blank line after a block of one line per commit.
Sensible.
> + strbuf_release(&commit_line);
> +}
> +
> static void make_cover_letter(struct rev_info *rev, int use_separate_file,
> struct commit *origin,
> int nr, struct commit **list,
> const char *description_file,
> const char *branch_name,
> int quiet,
> - const struct format_config *cfg)
> + const struct format_config *cfg,
> + const char *format)
> {
> const char *from;
> struct shortlog log;
> @@ -1342,6 +1361,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
> struct commit *head = list[0];
> char *to_free = NULL;
>
> + assert(format);
> +
Curious. We do not assert() for any other pointer arguments. What
makes this so special?
> if (!cmit_fmt_is_mail(rev->commit_format))
> die(_("cover letter needs email format"));
>
> @@ -1377,18 +1398,22 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
> free(pp.after_subject);
> strbuf_release(&sb);
>
> - shortlog_init(&log);
> - log.wrap_lines = 1;
> - log.wrap = MAIL_DEFAULT_WRAP;
> - log.in1 = 2;
> - log.in2 = 4;
> - log.file = rev->diffopt.file;
> - log.groups = SHORTLOG_GROUP_AUTHOR;
> - shortlog_finish_setup(&log);
> - for (i = 0; i < nr; i++)
> - shortlog_add_commit(&log, list[i]);
It would have been much nicer to first create a short helper
function generate_shortlog_cover(), move the above plus a call to
shortlog_output(&log) to that helper function, without doing
anything else and make it a preliminary patch [1/n]. Then introduce
the corresponding generate_commit_list_cover() function in patch
[2/n] (which is what this step is doing), which would have resulted
in the body of the make_cover_letter() around here a short-and-sweet
if (... we are doing shortlog style ...)
generate_shortlog_cover(...);
else
generate_commit_list_cover(...);
Just like readers of _this_ function are helped by not having to
know the details of what happens inside commit-list style cover
generation, they can concentratre on the flow without having to care
about details on shortlog side that way.
> @@ -1906,6 +1931,7 @@ int cmd_format_patch(int argc,
> int just_numbers = 0;
> int ignore_if_in_upstream = 0;
> int cover_letter = -1;
> + char *cover_letter_fmt = NULL;
> int boundary_count = 0;
> int no_binary_diff = 0;
> int zero_commit = 0;
> @@ -1952,6 +1978,8 @@ int cmd_format_patch(int argc,
> N_("print patches to standard out")),
> OPT_BOOL(0, "cover-letter", &cover_letter,
> N_("generate a cover letter")),
> + OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
> + N_("format spec used for the commit list in the cover letter")),
> OPT_BOOL(0, "numbered-files", &just_numbers,
> N_("use simple number sequence for output file names")),
> OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
> @@ -2289,13 +2317,14 @@ int cmd_format_patch(int argc,
> /* nothing to do */
> goto done;
> total = list.nr;
> +
What is this churn about?
> if (cover_letter == -1) {
> if (cfg.config_cover_letter == COVER_AUTO)
> - cover_letter = (total > 1);
> + cover_letter = total > 1;
What is this churn about?
> else if ((idiff_prev.nr || rdiff_prev) && (total > 1))
> - cover_letter = (cfg.config_cover_letter != COVER_OFF);
> + cover_letter = cfg.config_cover_letter != COVER_OFF;
What is this churn about?
> else
> - cover_letter = (cfg.config_cover_letter == COVER_ON);
> + cover_letter = cfg.config_cover_letter == COVER_ON;
What is this churn about?
Please do not distract reviewers by mixing immaterial "style fixes"
on existing code to a patch whose primary purpose is to introduce
new code. A separate "preliminary fix and/or clean-up" patch before
the main series begins is often a welcome addition, though.
> @@ -2375,12 +2404,16 @@ int cmd_format_patch(int argc,
> }
> rev.numbered_files = just_numbers;
> rev.patch_suffix = fmt_patch_suffix;
> +
> + if (cover_letter && !cover_letter_fmt)
> + cover_letter_fmt = "shortlog";
> +
I do not quite see the point of doing this. There is no law that
"cover_letter_fmt != NULL" is a crime when cover_letter is false.
cover_letter_fmt can be initialized to a fixed string "shortlog",
and nobody cares what random value cover_letter_fmt has when
cover_letter is false.
> if (cover_letter) {
> if (cfg.thread)
> gen_message_id(&rev, "cover");
> make_cover_letter(&rev, !!output_directory,
> origin, list.nr, list.items,
> - description_file, branch_name, quiet, &cfg);
> + description_file, branch_name, quiet, &cfg, cover_letter_fmt);
This line has become overly long. Please wrap it.
> print_bases(&bases, rev.diffopt.file);
> print_signature(signature, rev.diffopt.file);
> total++;
In any case, the implementation in this iteration looks much nicer
than the previous one.
New set of tests should come together with implementation of a new
feature in the same patch.
Thanks.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 2/2] format-patch: add commitListFormat config
2026-02-24 9:29 ` [PATCH v2 2/2] format-patch: add commitListFormat config Mirko Faina
@ 2026-02-24 18:07 ` Junio C Hamano
2026-02-25 0:14 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-24 18:07 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> + char* fmt_cover_letter_commit_list;
In this project, asterisk sticks to the variable, not the type,
i.e.,
char *fmt_cover_letter_commit_list;
I think you got this point right in the previous patch.
> @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
> cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
> return 0;
> }
> + if (!strcmp(var, "format.commitlistformat")) {
> + struct strbuf tmp = STRBUF_INIT;
> + strbuf_init(&tmp, 0);
> + strbuf_addstr(&tmp, "log:");
> + if (value)
> + strbuf_addstr(&tmp, value);
> + else
> + strbuf_addstr(&tmp, "%s");
> +
> + git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
What if /etc/gitconfig has "[format] commitListFormat = shortlog",
~/.gitconfig has a different setting, and then .git/config has yet
another setting? Woudln't cfg->fmt_cover_letter_commit_list at this
point have a copy of the value read from the previous configuration
file? Without first freeing it, wouldn't we leak the previous value?
$ git grep -C2 git_config_string\(
gives plenty of precedence, like this one.
builtin/commit.c- if (!strcmp(k, "commit.cleanup")) {
builtin/commit.c- FREE_AND_NULL(cleanup_config);
builtin/commit.c: return git_config_string(&cleanup_config, k, v);
builtin/commit.c- }
builtin/commit.c- if (!strcmp(k, "commit.gpgsign")) {
> + strbuf_release(&tmp);
> + return 0;
> + }
> if (!strcmp(var, "format.outputdirectory")) {
> FREE_AND_NULL(cfg->config_output_directory);
> return git_config_string(&cfg->config_output_directory, var, value);
> @@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,
> goto done;
> total = list.nr;
>
> + if (cover_letter_fmt && (strcmp(cover_letter_fmt, "shortlog") && strncmp(cover_letter_fmt, "log:", 4))) {
Overly long line.
What if it turns out that the --cover-letter option is not given
(and we are dealing with a single-patch topic, so auto setting has
decided that there is no need for cover letter)? Shouldn't we
continue ignoring the typo on a setting that we are not going to use
anyway?
Stepping back a bit, even if we do not validate the format *here*,
shouldn't the code that does use cover_letter_fmt later in the
control flow *already* be checking the validity of the format and
complaining? If that happens early enough, perhaps we do not want
to have an extra "early check and die" here.
> + die(_("--cover-letter: invalid format spec"));
> + }
> +
> + if (!cover_letter_fmt)
> + cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
As I pointed out in my review of [1/2], it is not a crime to set a
value to cover_letter_fmt even when !cover_letter, and the above
line does exactly that ;-).
By the way, the usual technique used in this codebase when handing
configuration and command line option is to these in this order:
* Initialize a variable to the built-in hardcoded default (e.g.,
"shortlog") upon variable declaration.
* Let repo_config() call overwrite that same variable. This is the
typical implementation of "if there is no configuration, we use
the hardcoded default, but the configured value can override it".
* Then parse_options() overwrites that same variable.
But because we read configuration into a separarte variable (i.e.,
members of cfg structure), this function cannot literally follow the
usual pattern. But the pattern we instead can follow is this:
/* initiailize to NULL */
char *cover_letter_fmt = NULL;
/* read configuration */
repo_config(... &cfg);
/* cover_letter_fmt will point at command line arg */
parse_options(...);
/* NULL if no command line argument */
if (!cover_letter_fmt) {
/* perhaps configuration has one */
cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
/* otherwise, use hardcoded default */
if (!cover_letter_fmt)
cover_letter_fmt = "shortlog";
}
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format
2026-02-24 9:29 ` [PATCH v2 1/2] format-patch: add ability to use alt cover format Mirko Faina
2026-02-24 17:40 ` Junio C Hamano
@ 2026-02-24 20:25 ` Junio C Hamano
2026-02-25 13:56 ` Jeff King
2 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-02-24 20:25 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> + char *cover_letter_fmt = NULL;
> ...
> +
> + if (cover_letter && !cover_letter_fmt)
> + cover_letter_fmt = "shortlog";
The compiler flags the above assignment, which is understandable.
builtin/log.c:2442:34: error: assignment discards 'const' qualifier from pointer target type [-Werror=discarded-qualifiers]
2442 | cover_letter_fmt = "shortlog";
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 0/2] format-patch: add cover-letter-format option
2026-02-24 9:29 ` [PATCH v2 0/2] " Mirko Faina
2026-02-24 9:29 ` [PATCH v2 1/2] format-patch: add ability to use alt cover format Mirko Faina
2026-02-24 9:29 ` [PATCH v2 2/2] format-patch: add commitListFormat config Mirko Faina
@ 2026-02-24 20:38 ` Junio C Hamano
2026-02-24 21:39 ` Junio C Hamano
2026-02-27 1:52 ` [PATCH v3 0/4] " Mirko Faina
3 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-24 20:38 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> From: Mroik <mroik@delayed.space>
>
> I've dropped the first patch of the series and applied the changes that
> Jeff suggested.
These are queued somewhere in 'seen', with a small fix-up to have
the build pass plus a bit of obvious tweak on error handling, but
most of the remarks I made in my reviews (like "doesn't this leak?"
and "shouldn't we have tests") are not addressed with the fix-up.
You'll find the topic in 'seen' after I push it out for today
perhaps in a few hours.
a981554b03 format-patch: add ability to use alt cover format
8bf8e6ccda format-patch: add commitListFormat config
a85e8e535d SQUASH???
Thanks.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 0/2] format-patch: add cover-letter-format option
2026-02-24 20:38 ` [PATCH v2 0/2] format-patch: add cover-letter-format option Junio C Hamano
@ 2026-02-24 21:39 ` Junio C Hamano
2026-02-25 0:19 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-24 21:39 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Junio C Hamano <gitster@pobox.com> writes:
> Mirko Faina <mroik@delayed.space> writes:
>
>> From: Mroik <mroik@delayed.space>
>>
>> I've dropped the first patch of the series and applied the changes that
>> Jeff suggested.
>
>
> These are queued somewhere in 'seen', with a small fix-up to have
> the build pass plus a bit of obvious tweak on error handling, but
> most of the remarks I made in my reviews (like "doesn't this leak?"
> and "shouldn't we have tests") are not addressed with the fix-up.
>
> You'll find the topic in 'seen' after I push it out for today
> perhaps in a few hours.
>
> a981554b03 format-patch: add ability to use alt cover format
> 8bf8e6ccda format-patch: add commitListFormat config
> a85e8e535d SQUASH???
>
> Thanks.
I found another change that is needed to make the tests pass, so the
commit object name for the squash fixup is no longer a85e8e535d;
fetching my 'seen' from any of the mirrors should let you find these
commits near the tip of 'seen'.
builtin/log.c | 18 ++++++++----------
t/t9902-completion.sh | 1 +
2 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index e6ff3627b8..c531784581 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1415,7 +1415,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
if (skip_prefix(format, "log:", &format)) {
generate_commit_list_cover(rev->diffopt.file, format, list, nr);
- } else {
+ } else if (!strcmp(format, "shortlog")) {
shortlog_init(&log);
log.wrap_lines = 1;
log.wrap = MAIL_DEFAULT_WRAP;
@@ -1428,6 +1428,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
shortlog_add_commit(&log, list[i]);
shortlog_output(&log);
+ } else {
+ die(_("--cover-letter: invalid format spec '%s'"), format);
}
/* We can only do diffstat with a unique reference point */
@@ -1946,7 +1948,7 @@ int cmd_format_patch(int argc,
int just_numbers = 0;
int ignore_if_in_upstream = 0;
int cover_letter = -1;
- char *cover_letter_fmt = NULL;
+ const char *cover_letter_fmt = NULL;
int boundary_count = 0;
int no_binary_diff = 0;
int zero_commit = 0;
@@ -2333,12 +2335,11 @@ int cmd_format_patch(int argc,
goto done;
total = list.nr;
- if (cover_letter_fmt && (strcmp(cover_letter_fmt, "shortlog") && strncmp(cover_letter_fmt, "log:", 4))) {
- die(_("--cover-letter: invalid format spec"));
- }
-
- if (!cover_letter_fmt)
+ if (!cover_letter_fmt) {
cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
+ if (!cover_letter_fmt)
+ cover_letter_fmt = "shortlog";
+ }
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
@@ -2427,9 +2428,6 @@ int cmd_format_patch(int argc,
rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
- if (cover_letter && !cover_letter_fmt)
- cover_letter_fmt = "shortlog";
-
if (cover_letter) {
if (cfg.thread)
gen_message_id(&rev, "cover");
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 964e1f1569..4f760a7468 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '
test_completion "git send-email --cov" <<-\EOF &&
--cover-from-description=Z
--cover-letter Z
+ --cover-letter-format=Z
EOF
test_completion "git send-email --val" <<-\EOF &&
--validate Z
--
2.53.0-485-gd48bbca10f
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format
2026-02-24 17:40 ` Junio C Hamano
@ 2026-02-24 23:54 ` Mirko Faina
2026-02-25 0:29 ` Junio C Hamano
2026-02-25 13:47 ` Jeff King
0 siblings, 2 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-24 23:54 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Mirko Faina
On Tue, Feb 24, 2026 at 09:40:54AM -0800, Junio C Hamano wrote:
> > + struct commit **list, int n)
> > +{
> > + struct strbuf commit_line = STRBUF_INIT;
> > + struct pretty_print_context ctx = {0};
> > +
> > + strbuf_init(&commit_line, 0);
>
> So a single commit_line is given to repo_format_commit_message()
> repeatedly to accumulate those lines in it, which makes sense.
>
> We prepare pretty_print_context once above and feed it repeatedly to
> repo_format_commit_message()---is that intended? Not a rhetorical
> question; I do not know the answer. I guess some existing callers
> (like builtin/archive.c) do reuse the structure when making multiple
> calls to the function, so this would be kosher, perhaps?
Honestly I applied Jeff change trusting his knowledge of the pretty.c
code. After taking a better look at repo_format_commit_message(), after
the pretty_print_context is passed to a format_commit_context as
pretty_ctx, it doesn't seem like this value is modified at all thoughout
the call. So it should be ok to reuse the same structure in multiple
calls of repo_format_commit_message().
> > + for (int i = n - 1; i >= 0; i--) {
> > + strbuf_addf(&commit_line, "[%0*d/%d] ", decimal_width(n), n - i, n);
> > + repo_format_commit_message(the_repository, list[i], format, &commit_line, &ctx);
>
> Let's line-wrap this overly long line (my lithmus test to complain
> about "overly long lines" is after losing three columns to the left
> for "> +" in e-mail quote like the above the right edge of the line
> does not fit on my 92-column terminal, which will never happen if
> you stick to the official "fit 80-column after quoted for a few
> times in e-mail" guideline).
>
> repo_format_commit_message(the_repository, list[i], format,
> &commit_line, &ctx);
>
> perhaps.
My bad, didn't realise it went over. I'll change settings on my editor
to ensure that I can visually see it going over and do an overall coding
style check.
> > + fprintf(cover_file, "%s\n", commit_line.buf);
>
> I somehow would have expected that as we internally prepare "format"
> string given to this function , we ensure it ends with "\n" so we do
> not have to do a fprintf() here.
The value of format is user defined, I'm not doing any pre proccessing
to it apart from stripping the prefix. Would it be much different had I
appended a newline here? I personally think it's fine doing a fprintf
here.
> > + strbuf_release(&commit_line);
> > +}
> > +
> > static void make_cover_letter(struct rev_info *rev, int use_separate_file,
> > struct commit *origin,
> > int nr, struct commit **list,
> > const char *description_file,
> > const char *branch_name,
> > int quiet,
> > - const struct format_config *cfg)
> > + const struct format_config *cfg,
> > + const char *format)
> > {
> > const char *from;
> > struct shortlog log;
> > @@ -1342,6 +1361,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
> > struct commit *head = list[0];
> > char *to_free = NULL;
> >
> > + assert(format);
> > +
>
> Curious. We do not assert() for any other pointer arguments. What
> makes this so special?
It's a leftover of code that should've been stripped. Sorry for this.
> > if (!cmit_fmt_is_mail(rev->commit_format))
> > die(_("cover letter needs email format"));
> >
> > @@ -1377,18 +1398,22 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
> > free(pp.after_subject);
> > strbuf_release(&sb);
> >
> > - shortlog_init(&log);
> > - log.wrap_lines = 1;
> > - log.wrap = MAIL_DEFAULT_WRAP;
> > - log.in1 = 2;
> > - log.in2 = 4;
> > - log.file = rev->diffopt.file;
> > - log.groups = SHORTLOG_GROUP_AUTHOR;
> > - shortlog_finish_setup(&log);
> > - for (i = 0; i < nr; i++)
> > - shortlog_add_commit(&log, list[i]);
>
> It would have been much nicer to first create a short helper
> function generate_shortlog_cover(), move the above plus a call to
> shortlog_output(&log) to that helper function, without doing
> anything else and make it a preliminary patch [1/n]. Then introduce
> the corresponding generate_commit_list_cover() function in patch
> [2/n] (which is what this step is doing), which would have resulted
> in the body of the make_cover_letter() around here a short-and-sweet
>
> if (... we are doing shortlog style ...)
> generate_shortlog_cover(...);
> else
> generate_commit_list_cover(...);
>
> Just like readers of _this_ function are helped by not having to
> know the details of what happens inside commit-list style cover
> generation, they can concentratre on the flow without having to care
> about details on shortlog side that way.
Yes, it does read nicer. Will do.
> > @@ -1906,6 +1931,7 @@ int cmd_format_patch(int argc,
> > int just_numbers = 0;
> > int ignore_if_in_upstream = 0;
> > int cover_letter = -1;
> > + char *cover_letter_fmt = NULL;
> > int boundary_count = 0;
> > int no_binary_diff = 0;
> > int zero_commit = 0;
> > @@ -1952,6 +1978,8 @@ int cmd_format_patch(int argc,
> > N_("print patches to standard out")),
> > OPT_BOOL(0, "cover-letter", &cover_letter,
> > N_("generate a cover letter")),
> > + OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
> > + N_("format spec used for the commit list in the cover letter")),
> > OPT_BOOL(0, "numbered-files", &just_numbers,
> > N_("use simple number sequence for output file names")),
> > OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
> > @@ -2289,13 +2317,14 @@ int cmd_format_patch(int argc,
> > /* nothing to do */
> > goto done;
> > total = list.nr;
> > +
>
> What is this churn about?
This is introducing "--cover-letter-format"
> > if (cover_letter == -1) {
> > if (cfg.config_cover_letter == COVER_AUTO)
> > - cover_letter = (total > 1);
> > + cover_letter = total > 1;
>
> What is this churn about?
>
> > else if ((idiff_prev.nr || rdiff_prev) && (total > 1))
> > - cover_letter = (cfg.config_cover_letter != COVER_OFF);
> > + cover_letter = cfg.config_cover_letter != COVER_OFF;
>
> What is this churn about?
>
> > else
> > - cover_letter = (cfg.config_cover_letter == COVER_ON);
> > + cover_letter = cfg.config_cover_letter == COVER_ON;
>
> What is this churn about?
>
> Please do not distract reviewers by mixing immaterial "style fixes"
> on existing code to a patch whose primary purpose is to introduce
> new code. A separate "preliminary fix and/or clean-up" patch before
> the main series begins is often a welcome addition, though.
This was not inteded. Is the result of me copy-pasting and then
restoring when I changed my mind on having the format spec be passed
through "--cover-letter".
Sorry for the unnecessary noise, will restore.
> > @@ -2375,12 +2404,16 @@ int cmd_format_patch(int argc,
> > }
> > rev.numbered_files = just_numbers;
> > rev.patch_suffix = fmt_patch_suffix;
> > +
> > + if (cover_letter && !cover_letter_fmt)
> > + cover_letter_fmt = "shortlog";
> > +
>
> I do not quite see the point of doing this. There is no law that
> "cover_letter_fmt != NULL" is a crime when cover_letter is false.
>
> cover_letter_fmt can be initialized to a fixed string "shortlog",
> and nobody cares what random value cover_letter_fmt has when
> cover_letter is false.
Indeed, was the first thing that came to mind at the time. Had I had
reread a few more times before sending I probably wouldn't have left it
like this.
I'll try to take my time with the next version as to not waste your time
on unnecessary initialization trivialities.
> > if (cover_letter) {
> > if (cfg.thread)
> > gen_message_id(&rev, "cover");
> > make_cover_letter(&rev, !!output_directory,
> > origin, list.nr, list.items,
> > - description_file, branch_name, quiet, &cfg);
> > + description_file, branch_name, quiet, &cfg, cover_letter_fmt);
>
> This line has become overly long. Please wrap it.
Will do.
> New set of tests should come together with implementation of a new
> feature in the same patch.
Yes, this is something you told me about on the other patch series as
well, sorry for forgetting. Will try to include them right away next
time (along with the necessary docs update).
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 2/2] format-patch: add commitListFormat config
2026-02-24 18:07 ` Junio C Hamano
@ 2026-02-25 0:14 ` Mirko Faina
2026-02-25 17:25 ` Junio C Hamano
2026-02-26 21:40 ` Mirko Faina
0 siblings, 2 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-25 0:14 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Mirko Faina
On Tue, Feb 24, 2026 at 10:07:48AM -0800, Junio C Hamano wrote:
> In this project, asterisk sticks to the variable, not the type,
> i.e.,
>
> char *fmt_cover_letter_commit_list;
>
> I think you got this point right in the previous patch.
Yes, that was not intentional, must've been a typo.
> > @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
> > cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
> > return 0;
> > }
> > + if (!strcmp(var, "format.commitlistformat")) {
> > + struct strbuf tmp = STRBUF_INIT;
> > + strbuf_init(&tmp, 0);
> > + strbuf_addstr(&tmp, "log:");
> > + if (value)
> > + strbuf_addstr(&tmp, value);
> > + else
> > + strbuf_addstr(&tmp, "%s");
> > +
> > + git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
>
> What if /etc/gitconfig has "[format] commitListFormat = shortlog",
> ~/.gitconfig has a different setting, and then .git/config has yet
> another setting? Woudln't cfg->fmt_cover_letter_commit_list at this
> point have a copy of the value read from the previous configuration
> file? Without first freeing it, wouldn't we leak the previous value?
>
> $ git grep -C2 git_config_string\(
>
> gives plenty of precedence, like this one.
>
> builtin/commit.c- if (!strcmp(k, "commit.cleanup")) {
> builtin/commit.c- FREE_AND_NULL(cleanup_config);
> builtin/commit.c: return git_config_string(&cleanup_config, k, v);
> builtin/commit.c- }
> builtin/commit.c- if (!strcmp(k, "commit.gpgsign")) {
Will do.
> > + strbuf_release(&tmp);
> > + return 0;
> > + }
> > if (!strcmp(var, "format.outputdirectory")) {
> > FREE_AND_NULL(cfg->config_output_directory);
> > return git_config_string(&cfg->config_output_directory, var, value);
> > @@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,
> > goto done;
> > total = list.nr;
> >
> > + if (cover_letter_fmt && (strcmp(cover_letter_fmt, "shortlog") && strncmp(cover_letter_fmt, "log:", 4))) {
>
> Overly long line.
Will fix.
> What if it turns out that the --cover-letter option is not given
> (and we are dealing with a single-patch topic, so auto setting has
> decided that there is no need for cover letter)? Shouldn't we
> continue ignoring the typo on a setting that we are not going to use
> anyway?
Yes, a check on cover_letter should fix this.
> Stepping back a bit, even if we do not validate the format *here*,
> shouldn't the code that does use cover_letter_fmt later in the
> control flow *already* be checking the validity of the format and
> complaining? If that happens early enough, perhaps we do not want
> to have an extra "early check and die" here.
That is true, and initially I did not introduce a check here, but
make_cover_letter() is called after the cover letter file has already
been created. Failing before format-patch could create a file or print
anything on screeen seemed more clean to me, that's the only reason
there's a check there.
> By the way, the usual technique used in this codebase when handing
> configuration and command line option is to these in this order:
>
> * Initialize a variable to the built-in hardcoded default (e.g.,
> "shortlog") upon variable declaration.
>
> * Let repo_config() call overwrite that same variable. This is the
> typical implementation of "if there is no configuration, we use
> the hardcoded default, but the configured value can override it".
>
> * Then parse_options() overwrites that same variable.
>
> But because we read configuration into a separarte variable (i.e.,
> members of cfg structure), this function cannot literally follow the
> usual pattern. But the pattern we instead can follow is this:
>
> /* initiailize to NULL */
> char *cover_letter_fmt = NULL;
>
> /* read configuration */
> repo_config(... &cfg);
>
> /* cover_letter_fmt will point at command line arg */
> parse_options(...);
>
> /* NULL if no command line argument */
> if (!cover_letter_fmt) {
> /* perhaps configuration has one */
> cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
>
> /* otherwise, use hardcoded default */
> if (!cover_letter_fmt)
> cover_letter_fmt = "shortlog";
> }
Will rewrite to follow this config flow.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 0/2] format-patch: add cover-letter-format option
2026-02-24 21:39 ` Junio C Hamano
@ 2026-02-25 0:19 ` Mirko Faina
2026-02-25 2:46 ` Junio C Hamano
0 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-25 0:19 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Mirko Faina
On Tue, Feb 24, 2026 at 01:39:48PM -0800, Junio C Hamano wrote:
> I found another change that is needed to make the tests pass, so the
> commit object name for the squash fixup is no longer a85e8e535d;
> fetching my 'seen' from any of the mirrors should let you find these
> commits near the tip of 'seen'.
Didn't notice the test failing, I only ran *format-patch* tests.
Thinking about it more carefully there are other related tests that
could fail due to the nature of format-patch being used in preparation
for other commands.
Will run all tests next time.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format
2026-02-24 23:54 ` Mirko Faina
@ 2026-02-25 0:29 ` Junio C Hamano
2026-02-25 13:47 ` Jeff King
1 sibling, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-02-25 0:29 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
>> > + fprintf(cover_file, "%s\n", commit_line.buf);
>>
>> I somehow would have expected that as we internally prepare "format"
>> string given to this function , we ensure it ends with "\n" so we do
>> not have to do a fprintf() here.
>
> The value of format is user defined, I'm not doing any pre proccessing
> to it apart from stripping the prefix. Would it be much different had I
> appended a newline here? I personally think it's fine doing a fprintf
> here.
I thought you are stripping the prefix and making a copy in the
configuration parser. I didn't look closely but I thought it would
be trivially cheap to also append LF there. If not, I agree with
you that it is not a huge deal to do the "%s\n" here instead.
Thanks for working on this topic.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 0/2] format-patch: add cover-letter-format option
2026-02-25 0:19 ` Mirko Faina
@ 2026-02-25 2:46 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-02-25 2:46 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> Didn't notice the test failing, I only ran *format-patch* tests.
> Thinking about it more carefully there are other related tests that
> could fail due to the nature of format-patch being used in preparation
> for other commands.
>
> Will run all tests next time.
Yeah, it often is simpler and with less effort to just run all of
them, as one can never know what fallouts a particular change will
have in unexpected areas.
Thanks.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format
2026-02-24 23:54 ` Mirko Faina
2026-02-25 0:29 ` Junio C Hamano
@ 2026-02-25 13:47 ` Jeff King
1 sibling, 0 replies; 113+ messages in thread
From: Jeff King @ 2026-02-25 13:47 UTC (permalink / raw)
To: Mirko Faina; +Cc: Junio C Hamano, git
On Wed, Feb 25, 2026 at 12:54:54AM +0100, Mirko Faina wrote:
> > So a single commit_line is given to repo_format_commit_message()
> > repeatedly to accumulate those lines in it, which makes sense.
> >
> > We prepare pretty_print_context once above and feed it repeatedly to
> > repo_format_commit_message()---is that intended? Not a rhetorical
> > question; I do not know the answer. I guess some existing callers
> > (like builtin/archive.c) do reuse the structure when making multiple
> > calls to the function, so this would be kosher, perhaps?
>
> Honestly I applied Jeff change trusting his knowledge of the pretty.c
> code. After taking a better look at repo_format_commit_message(), after
> the pretty_print_context is passed to a format_commit_context as
> pretty_ctx, it doesn't seem like this value is modified at all thoughout
> the call. So it should be ok to reuse the same structure in multiple
> calls of repo_format_commit_message().
A dangerous place to put your trust. ;)
I do think it is OK here, though. And in general I'd assume a "context"
variable like this is re-usable unless documented differently. The
shortlog does use a pretty_print_context in a loop like this, too.
> > > + if (cover_letter && !cover_letter_fmt)
> > > + cover_letter_fmt = "shortlog";
> > > +
> >
> > I do not quite see the point of doing this. There is no law that
> > "cover_letter_fmt != NULL" is a crime when cover_letter is false.
> >
> > cover_letter_fmt can be initialized to a fixed string "shortlog",
> > and nobody cares what random value cover_letter_fmt has when
> > cover_letter is false.
>
> Indeed, was the first thing that came to mind at the time. Had I had
> reread a few more times before sending I probably wouldn't have left it
> like this.
>
> I'll try to take my time with the next version as to not waste your time
> on unnecessary initialization trivialities.
I think that is OK, but FWIW my instinct on reading the patch was to go
the opposite direction: leave it as NULL and fill that in as the default
at the last minute. Like this (on top of your patch 1):
diff --git a/builtin/log.c b/builtin/log.c
index d13fcf830b..6907a680f2 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1360,8 +1360,6 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *head = list[0];
char *to_free = NULL;
- assert(format);
-
if (!cmit_fmt_is_mail(rev->commit_format))
die(_("cover letter needs email format"));
@@ -1397,7 +1395,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- if (skip_prefix(format, "log:", &format)) {
+ if (format && skip_prefix(format, "log:", &format)) {
generate_commit_list_cover(rev->diffopt.file, format, list, nr);
} else {
shortlog_init(&log);
@@ -2404,9 +2402,6 @@ int cmd_format_patch(int argc,
rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
- if (cover_letter && !cover_letter_fmt)
- cover_letter_fmt = "shortlog";
-
if (cover_letter) {
if (cfg.thread)
gen_message_id(&rev, "cover");
I tend to like keeping the sentinel value as long as possible, because
sometimes it later becomes useful to distinguish "the user asked for
shortlog" vs "the user did not ask for anything". We don't need that
now, but I could imagine some hypothetical future where we are going to
switch the default, and start issuing an advice/warning message.
But it is a pretty minor point, and mostly subjective, so I am happy
either way.
However, the patch above does raise another interesting question if you
look closely: why do we never need to check for the string "shortlog" in
this logic?
We just assume anything that does not start with "log:" should show the
shortlog, and that is true both before and after my suggested change
above. What should:
git format-patch --cover-letter --cover-letter-format=foo
do? With "log:%s" as the syntax, it should probably be an error. And
this seems like a good place to detect it, like:
if (!format || !strcmp(format, "shortlog"))
...do shortlog...
else if (skip_prefix(format, "log:", &format))
...do format...
else
die("what the heck does %s mean?", format);
But that gives me more thoughts (another dangerous habit, I know). Do we
need "log:" at all? Could the value be interpreted as a format, unless
it is the string "shortlog"? We would want to leave room for future
expansion of new hard-coded names (like "shortlog"), but I do not think
there is any problem there. Any reasonable format provided by the user
will have at least one %-placeholder in it, and no hard-coded name we
provide would have a percent in it.
And then you could just do:
git format-patch --cover-letter --cover-letter-format=%s
Maybe not a big deal, as I'd expect everyone to set it once in config
(or within a script) anyway. But food for thought.
-Peff
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format
2026-02-24 9:29 ` [PATCH v2 1/2] format-patch: add ability to use alt cover format Mirko Faina
2026-02-24 17:40 ` Junio C Hamano
2026-02-24 20:25 ` Junio C Hamano
@ 2026-02-25 13:56 ` Jeff King
2026-02-25 22:55 ` Mirko Faina
2 siblings, 1 reply; 113+ messages in thread
From: Jeff King @ 2026-02-25 13:56 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Junio C Hamano
On Tue, Feb 24, 2026 at 10:29:01AM +0100, Mirko Faina wrote:
> +static void generate_commit_list_cover(FILE *cover_file,const char *format,
> + struct commit **list, int n)
> +{
> + struct strbuf commit_line = STRBUF_INIT;
> + struct pretty_print_context ctx = {0};
> +
> + strbuf_init(&commit_line, 0);
> + for (int i = n - 1; i >= 0; i--) {
> + strbuf_addf(&commit_line, "[%0*d/%d] ", decimal_width(n), n - i, n);
> + repo_format_commit_message(the_repository, list[i], format, &commit_line, &ctx);
> + fprintf(cover_file, "%s\n", commit_line.buf);
> + strbuf_reset(&commit_line);
> + }
When this topic came up initially I imagined that the user would be able
to tweak the "[1/5]" part of the string, too. It would take a little
extra work, though:
- introduce new placeholders like %(count) and %(total) or similar
- introduce count/total fields to pretty_print_context
- probably when total is unset, refuse to interpret %(count) and
%(total) at all (so they do not have meaning for regular "git log
--format" calls.
- not sure how to handle the width field for the count. Would it have
an option to default to the width of the total field?
But I'm not sure if it is worth all of that effort. It is more flexible,
but would people really want to customize how it is shown (after all, I
don't think we allow configuring it in the patch subjects themselves,
though we do allow suppressing the "[PATCH]" part completely).
I had a few other comments while reading the patch, but Junio already
made them for me. :) I do like the direction this is heading.
-Peff
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 2/2] format-patch: add commitListFormat config
2026-02-25 0:14 ` Mirko Faina
@ 2026-02-25 17:25 ` Junio C Hamano
2026-02-26 21:40 ` Mirko Faina
1 sibling, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-02-25 17:25 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
>> > + if (cover_letter_fmt && (strcmp(cover_letter_fmt, "shortlog") && strncmp(cover_letter_fmt, "log:", 4))) {
>>
>> Overly long line.
>
> Will fix.
A minor thing, but using starts_with(cover_letter_fmt, "log:") would
let us without having to count the number of bytes in the "log:"
prefix (and we do not have to keep recounting every time we decide
to change the prefix used---which is not likely to happen in this
particular case).
Thanks.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 1/2] format-patch: add ability to use alt cover format
2026-02-25 13:56 ` Jeff King
@ 2026-02-25 22:55 ` Mirko Faina
0 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-25 22:55 UTC (permalink / raw)
To: Jeff King; +Cc: git, Junio C Hamano, Mirko Faina
On Wed, Feb 25, 2026 at 08:56:28AM -0500, Jeff King wrote:
> When this topic came up initially I imagined that the user would be able
> to tweak the "[1/5]" part of the string, too. It would take a little
> extra work, though:
>
> - introduce new placeholders like %(count) and %(total) or similar
>
> - introduce count/total fields to pretty_print_context
>
> - probably when total is unset, refuse to interpret %(count) and
> %(total) at all (so they do not have meaning for regular "git log
> --format" calls.
>
> - not sure how to handle the width field for the count. Would it have
> an option to default to the width of the total field?
>
> But I'm not sure if it is worth all of that effort. It is more flexible,
> but would people really want to customize how it is shown (after all, I
> don't think we allow configuring it in the patch subjects themselves,
> though we do allow suppressing the "[PATCH]" part completely).
I can give it a try.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 2/2] format-patch: add commitListFormat config
2026-02-25 0:14 ` Mirko Faina
2026-02-25 17:25 ` Junio C Hamano
@ 2026-02-26 21:40 ` Mirko Faina
2026-02-26 22:19 ` Junio C Hamano
1 sibling, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-26 21:40 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Mirko Faina
On Wed, Feb 25, 2026 at 01:14:13AM +0100, Mirko Faina wrote:
> > > + strbuf_release(&tmp);
> > > + return 0;
> > > + }
> > > if (!strcmp(var, "format.outputdirectory")) {
> > > FREE_AND_NULL(cfg->config_output_directory);
> > > return git_config_string(&cfg->config_output_directory, var, value);
> > > @@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,
> > > goto done;
> > > total = list.nr;
> > >
> > > + if (cover_letter_fmt && (strcmp(cover_letter_fmt, "shortlog") && strncmp(cover_letter_fmt, "log:", 4))) {
> >
> > Overly long line.
>
> Will fix.
>
> > Stepping back a bit, even if we do not validate the format *here*,
> > shouldn't the code that does use cover_letter_fmt later in the
> > control flow *already* be checking the validity of the format and
> > complaining? If that happens early enough, perhaps we do not want
> > to have an extra "early check and die" here.
>
> That is true, and initially I did not introduce a check here, but
> make_cover_letter() is called after the cover letter file has already
> been created. Failing before format-patch could create a file or print
> anything on screeen seemed more clean to me, that's the only reason
> there's a check there.
May I have a confirmation on this. Is it ok to leave the extra check
here or would you like me to remove it and just let make_cover_letter()
handle it?
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v2 2/2] format-patch: add commitListFormat config
2026-02-26 21:40 ` Mirko Faina
@ 2026-02-26 22:19 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-02-26 22:19 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> On Wed, Feb 25, 2026 at 01:14:13AM +0100, Mirko Faina wrote:
>> > > + strbuf_release(&tmp);
>> > > + return 0;
>> > > + }
>> > > if (!strcmp(var, "format.outputdirectory")) {
>> > > FREE_AND_NULL(cfg->config_output_directory);
>> > > return git_config_string(&cfg->config_output_directory, var, value);
>> > > @@ -2318,6 +2333,13 @@ int cmd_format_patch(int argc,
>> > > goto done;
>> > > total = list.nr;
>> > >
>> > > + if (cover_letter_fmt && (strcmp(cover_letter_fmt, "shortlog") && strncmp(cover_letter_fmt, "log:", 4))) {
>> >
>> > Overly long line.
>>
>> Will fix.
>>
>> > Stepping back a bit, even if we do not validate the format *here*,
>> > shouldn't the code that does use cover_letter_fmt later in the
>> > control flow *already* be checking the validity of the format and
>> > complaining? If that happens early enough, perhaps we do not want
>> > to have an extra "early check and die" here.
>>
>> That is true, and initially I did not introduce a check here, but
>> make_cover_letter() is called after the cover letter file has already
>> been created. Failing before format-patch could create a file or print
>> anything on screeen seemed more clean to me, that's the only reason
>> there's a check there.
>
> May I have a confirmation on this. Is it ok to leave the extra check
> here or would you like me to remove it and just let make_cover_letter()
> handle it?
An extra check here would be warranted if that let us avoid
end-users spend extra time and effort before we make a call to
make_cover_letter() and bad arguments cause it to die. Otherwise,
not. As the underlying helper function, make_cover_letter() should
be doing its own sanity check on its input anyway, so it is
preferrable not to duplicate the check elsewhere if we do not have
to.
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v3 0/4] format-patch: add cover-letter-format option
2026-02-24 9:29 ` [PATCH v2 0/2] " Mirko Faina
` (2 preceding siblings ...)
2026-02-24 20:38 ` [PATCH v2 0/2] format-patch: add cover-letter-format option Junio C Hamano
@ 2026-02-27 1:52 ` Mirko Faina
2026-02-27 1:52 ` [PATCH v3 1/4] pretty.c: add %(count) and %(total) placeholders Mirko Faina
` (4 more replies)
3 siblings, 5 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 1:52 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
As Jeff requested I added two new placeholders to make the numbering
part customizable as well. The width of %(count) will always be the same
width of %(total).
I've also moved the code for generating the shortlog format into its own
function.
There are now tests for both patch 3 and 4. Unfortunately I couldn't add
tests for patch 1 as the variables that are coupled with %(count) and
%(total) are not used for any command that accepts "--format" or
"--pretty".
I've also went over the code and fixed the parts that you marked as
problematic (hopefully I haven't missed anything).
Thank you both for the reviews.
[1/4] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)
[2/4] format-patch: move cover letter summary generation (Mirko Faina)
[3/4] format-patch: add ability to use alt cover format (Mirko Faina)
[4/4] format-patch: add commitListFormat config (Mirko Faina)
builtin/log.c | 94 +++++++++++++++++++++++++++++++------
pretty.c | 15 ++++++
t/t4014-format-patch.sh | 101 ++++++++++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
4 files changed, 197 insertions(+), 14 deletions(-)
--
2.53.0.4.g55f3102ead
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v3 1/4] pretty.c: add %(count) and %(total) placeholders
2026-02-27 1:52 ` [PATCH v3 0/4] " Mirko Faina
@ 2026-02-27 1:52 ` Mirko Faina
2026-02-27 1:52 ` [PATCH v3 2/4] format-patch: move cover letter summary generation Mirko Faina
` (3 subsequent siblings)
4 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 1:52 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
In many commands we can customize the output through the "--format" or
the "--pretty" options. This patch adds two new placeholders used mainly
when there's a range of commits that we want to show.
Currently these two placeholders are not usable as they're coupled with
the rev_info->nr and rev_info->total fields, fields that are used only
by the format-patch numbered email subjects.
Teach repo_format_commit_message() the %(count) and %(total)
placeholders.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
pretty.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/pretty.c b/pretty.c
index e0646bbc5d..e29bb8b877 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
if (!commit->object.parsed)
parse_object(the_repository, &commit->object.oid);
+ if (starts_with(placeholder, "(count)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total),
+ c->pretty_ctx->rev->nr);
+ return 7;
+ }
+
+ if (starts_with(placeholder, "(total)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%d", c->pretty_ctx->rev->total);
+ return 7;
+ }
+
switch (placeholder[0]) {
case 'H': /* commit hash */
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
--
2.53.0.4.g55f3102ead
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v3 2/4] format-patch: move cover letter summary generation
2026-02-27 1:52 ` [PATCH v3 0/4] " Mirko Faina
2026-02-27 1:52 ` [PATCH v3 1/4] pretty.c: add %(count) and %(total) placeholders Mirko Faina
@ 2026-02-27 1:52 ` Mirko Faina
2026-02-27 1:52 ` [PATCH v3 3/4] format-patch: add ability to use alt cover format Mirko Faina
` (2 subsequent siblings)
4 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 1:52 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
As of now format-patch allows generation of a template cover letter for
patch series through "--cover-letter".
Move shortlog summary code generation to its own function. This is done
in preparation to other patches where we enable the user to format the
commit list using thier own format string.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 32 ++++++++++++++++++++------------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 5c9a8ef363..0d12272031 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_shortlog_cover_letter(struct shortlog *log,
+ struct rev_info *rev,
+ struct commit **list,
+ int nr)
+{
+ shortlog_init(log);
+ log->wrap_lines = 1;
+ log->wrap = MAIL_DEFAULT_WRAP;
+ log->in1 = 2;
+ log->in2 = 4;
+ log->file = rev->diffopt.file;
+ log->groups = SHORTLOG_GROUP_AUTHOR;
+ shortlog_finish_setup(log);
+ for (int i = 0; i < nr; i++)
+ shortlog_add_commit(log, list[i]);
+
+ shortlog_output(log);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
-
- shortlog_output(&log);
+ generate_shortlog_cover_letter(&log, rev, list, nr);
/* We can only do diffstat with a unique reference point */
if (origin)
--
2.53.0.4.g55f3102ead
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v3 3/4] format-patch: add ability to use alt cover format
2026-02-27 1:52 ` [PATCH v3 0/4] " Mirko Faina
2026-02-27 1:52 ` [PATCH v3 1/4] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-02-27 1:52 ` [PATCH v3 2/4] format-patch: move cover letter summary generation Mirko Faina
@ 2026-02-27 1:52 ` Mirko Faina
2026-02-27 4:23 ` Junio C Hamano
2026-02-27 1:52 ` [PATCH v3 4/4] format-patch: add commitListFormat config Mirko Faina
2026-02-27 13:18 ` [PATCH v4 0/4] format-patch: add cover-letter-format option Mirko Faina
4 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 1:52 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
Give the ability to format-patch to specify an alternative format spec
through the "--cover-letter-format" option. This option either takes
"shortlog", which is the current format, or a format spec prefixed with
"log:".
Example:
git format-patch --cover-letter \
--cover-letter-format="log:%s (%an)" HEAD~3
[1/3] this is a commit summary (Mirko Faina)
[2/3] this is another commit summary (Mirko Faina)
...
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 40 +++++++++++++++++++++++++++++++---
t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
3 files changed, 86 insertions(+), 3 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 0d12272031..46c8e33773 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,
shortlog_output(log);
}
+static void generate_commit_list_cover(FILE *cover_file,const char *format,
+ struct commit **list, int n)
+{
+ struct strbuf commit_line = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ struct rev_info rev = REV_INFO_INIT;
+
+ strbuf_init(&commit_line, 0);
+ rev.total = n;
+ ctx.rev = &rev;
+ for (int i = n - 1; i >= 0; i--) {
+ rev.nr = n - i;
+ repo_format_commit_message(the_repository, list[i], format,
+ &commit_line, &ctx);
+ fprintf(cover_file, "%s\n", commit_line.buf);
+ strbuf_reset(&commit_line);
+ }
+ fprintf(cover_file, "\n");
+
+ strbuf_release(&commit_line);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
const char *description_file,
const char *branch_name,
int quiet,
- const struct format_config *cfg)
+ const struct format_config *cfg,
+ const char *format)
{
const char *committer;
struct shortlog log;
@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- generate_shortlog_cover_letter(&log, rev, list, nr);
+ if (skip_prefix(format, "log:", &format))
+ generate_commit_list_cover(rev->diffopt.file, format, list, nr);
+ else if (!strcmp(format, "shortlog"))
+ generate_shortlog_cover_letter(&log, rev, list, nr);
+ else
+ die(_("'%s' is not a valid format string"), format);
/* We can only do diffstat with a unique reference point */
if (origin)
@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,
int just_numbers = 0;
int ignore_if_in_upstream = 0;
int cover_letter = -1;
+ const char *cover_letter_fmt = NULL;
int boundary_count = 0;
int no_binary_diff = 0;
int zero_commit = 0;
@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,
N_("print patches to standard out")),
OPT_BOOL(0, "cover-letter", &cover_letter,
N_("generate a cover letter")),
+ OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
+ N_("format spec used for the commit list in the cover letter")),
OPT_BOOL(0, "numbered-files", &just_numbers,
N_("use simple number sequence for output file names")),
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,
/* nothing to do */
goto done;
total = list.nr;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,
}
rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
+
if (cover_letter) {
if (cfg.thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, !!output_directory,
origin, list.nr, list.items,
- description_file, branch_name, quiet, &cfg);
+ description_file, branch_name, quiet, &cfg,
+ cover_letter_fmt);
print_bases(&bases, rev.diffopt.file);
print_signature(signature, rev.diffopt.file);
total++;
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 21d6d0cd9e..458da80721 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '
done
'
+test_expect_success 'cover letter with subject, author and count' '
+ rm -rf patches &&
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
+ grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expected_success 'cover letter with author and count' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %an" -o patches HEAD~1 &&
+ grep "^\[1/1\] A U Thor$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter shortlog' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter --cover-letter-format=shortlog \
+ -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter no format' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 964e1f1569..4f760a7468 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '
test_completion "git send-email --cov" <<-\EOF &&
--cover-from-description=Z
--cover-letter Z
+ --cover-letter-format=Z
EOF
test_completion "git send-email --val" <<-\EOF &&
--validate Z
--
2.53.0.4.g55f3102ead
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v3 4/4] format-patch: add commitListFormat config
2026-02-27 1:52 ` [PATCH v3 0/4] " Mirko Faina
` (2 preceding siblings ...)
2026-02-27 1:52 ` [PATCH v3 3/4] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-02-27 1:52 ` Mirko Faina
2026-02-27 13:18 ` [PATCH v4 0/4] format-patch: add cover-letter-format option Mirko Faina
4 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 1:52 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Using "--cover-letter" we can tell format-patch to generate a cover
letter, in this cover letter there's a list of commits included in the
patch series and the format is specified by the "--cover-letter-format"
option. Would be useful if this format could be configured from the
config file instead of always needing to pass it from the command line.
Teach format-patch how to read the format spec for the cover letter from
the config files. The variable it should look for is called
"commitListFormat".
Possible values:
- commitListFormat is set but no string is passed: it will default to
"[%(count)/%(total)] %s"
- if a string is passed: will use it as a format spec. Note that this is
either "shortlog" or a format spec without the "log:" prefix
e.g. "%s (%an)"
- if commitListFormat is not set: it will default to "shortlog"
shortlog format.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 24 +++++++++++++++++++
t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 77 insertions(+)
diff --git a/builtin/log.c b/builtin/log.c
index 46c8e33773..dcf7cd1792 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -886,6 +886,7 @@ struct format_config {
char *signature;
char *signature_file;
enum cover_setting config_cover_letter;
+ char *fmt_cover_letter_commit_list;
char *config_output_directory;
enum cover_from_description cover_from_description_mode;
int show_notes;
@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
string_list_clear(&cfg->extra_cc, 0);
strbuf_release(&cfg->sprefix);
free(cfg->fmt_patch_suffix);
+ free(cfg->fmt_cover_letter_commit_list);
}
static enum cover_from_description parse_cover_from_description(const char *arg)
@@ -1052,6 +1054,22 @@ static int git_format_config(const char *var, const char *value,
cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
return 0;
}
+ if (!strcmp(var, "format.commitlistformat")) {
+ struct strbuf tmp = STRBUF_INIT;
+ strbuf_init(&tmp, 0);
+ if (value) {
+ if (strcmp(value, "shortlog"))
+ strbuf_addstr(&tmp, "log:");
+ strbuf_addstr(&tmp, value);
+ } else {
+ strbuf_addstr(&tmp, "log:[%(count)/%(total)] %s");
+ }
+
+ FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
+ git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
+ strbuf_release(&tmp);
+ return 0;
+ }
if (!strcmp(var, "format.outputdirectory")) {
FREE_AND_NULL(cfg->config_output_directory);
return git_config_string(&cfg->config_output_directory, var, value);
@@ -2329,6 +2347,12 @@ int cmd_format_patch(int argc,
goto done;
total = list.nr;
+ if (!cover_letter_fmt) {
+ cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
+ if (!cover_letter_fmt)
+ cover_letter_fmt = "shortlog";
+ }
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 458da80721..c43c6972af 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '
test_line_count = 1 result
'
+test_expect_success 'cover letter config with count, subject and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "[%(count)/%(total)] %s (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config with count and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "[%(count)/%(total)] (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set but no format' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ printf "\tcommitlistformat" >> .git/config &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .*" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set to shortlog' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat shortlog &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter config commitlistformat not set' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ git config set format.coverletter true &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
--
2.53.0.4.g55f3102ead
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH v3 3/4] format-patch: add ability to use alt cover format
2026-02-27 1:52 ` [PATCH v3 3/4] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-02-27 4:23 ` Junio C Hamano
2026-02-27 12:41 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-27 4:23 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> Example:
> git format-patch --cover-letter \
> --cover-letter-format="log:%s (%an)" HEAD~3
>
> [1/3] this is a commit summary (Mirko Faina)
> [2/3] this is another commit summary (Mirko Faina)
> ...
Don't you need to update this example? I personally find the
%(count)/%(total) thing a bit overengineered, but now we have it,
you'd need to write it in your forrmat string if you want to get the
[N/M] prefix, right?
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v3 3/4] format-patch: add ability to use alt cover format
2026-02-27 4:23 ` Junio C Hamano
@ 2026-02-27 12:41 ` Mirko Faina
0 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 12:41 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Mirko Faina
On Thu, Feb 26, 2026 at 08:23:31PM -0800, Junio C Hamano wrote:
> Don't you need to update this example? I personally find the
> %(count)/%(total) thing a bit overengineered, but now we have it,
> you'd need to write it in your forrmat string if you want to get the
> [N/M] prefix, right?
Right, forgot to change this commit message. I will send the updated
series.
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v4 0/4] format-patch: add cover-letter-format option
2026-02-27 1:52 ` [PATCH v3 0/4] " Mirko Faina
` (3 preceding siblings ...)
2026-02-27 1:52 ` [PATCH v3 4/4] format-patch: add commitListFormat config Mirko Faina
@ 2026-02-27 13:18 ` Mirko Faina
2026-02-27 13:18 ` [PATCH v4 1/4] pretty.c: add %(count) and %(total) placeholders Mirko Faina
` (4 more replies)
4 siblings, 5 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 13:18 UTC (permalink / raw)
To: git; +Cc: Mroik, Junio C Hamano, Jeff King
From: Mroik <mroik@delayed.space>
I've fixed the example in patch number 3
[1/4] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)
[2/4] format-patch: move cover letter summary generation (Mirko Faina)
[3/4] format-patch: add ability to use alt cover format (Mirko Faina)
[4/4] format-patch: add commitListFormat config (Mirko Faina)
builtin/log.c | 94 +++++++++++++++++++++++++++++++------
pretty.c | 15 ++++++
t/t4014-format-patch.sh | 101 ++++++++++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
4 files changed, 197 insertions(+), 14 deletions(-)
--
2.53.0.4.gf9ee8e2400
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v4 1/4] pretty.c: add %(count) and %(total) placeholders
2026-02-27 13:18 ` [PATCH v4 0/4] format-patch: add cover-letter-format option Mirko Faina
@ 2026-02-27 13:18 ` Mirko Faina
2026-02-27 13:18 ` [PATCH v4 2/4] format-patch: move cover letter summary generation Mirko Faina
` (3 subsequent siblings)
4 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 13:18 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
In many commands we can customize the output through the "--format" or
the "--pretty" options. This patch adds two new placeholders used mainly
when there's a range of commits that we want to show.
Currently these two placeholders are not usable as they're coupled with
the rev_info->nr and rev_info->total fields, fields that are used only
by the format-patch numbered email subjects.
Teach repo_format_commit_message() the %(count) and %(total)
placeholders.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
pretty.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/pretty.c b/pretty.c
index e0646bbc5d..e29bb8b877 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
if (!commit->object.parsed)
parse_object(the_repository, &commit->object.oid);
+ if (starts_with(placeholder, "(count)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total),
+ c->pretty_ctx->rev->nr);
+ return 7;
+ }
+
+ if (starts_with(placeholder, "(total)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%d", c->pretty_ctx->rev->total);
+ return 7;
+ }
+
switch (placeholder[0]) {
case 'H': /* commit hash */
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
--
2.53.0.4.gf9ee8e2400
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v4 2/4] format-patch: move cover letter summary generation
2026-02-27 13:18 ` [PATCH v4 0/4] format-patch: add cover-letter-format option Mirko Faina
2026-02-27 13:18 ` [PATCH v4 1/4] pretty.c: add %(count) and %(total) placeholders Mirko Faina
@ 2026-02-27 13:18 ` Mirko Faina
2026-02-27 13:18 ` [PATCH v4 3/4] format-patch: add ability to use alt cover format Mirko Faina
` (2 subsequent siblings)
4 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 13:18 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
As of now format-patch allows generation of a template cover letter for
patch series through "--cover-letter".
Move shortlog summary code generation to its own function. This is done
in preparation to other patches where we enable the user to format the
commit list using thier own format string.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 32 ++++++++++++++++++++------------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 5c9a8ef363..0d12272031 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_shortlog_cover_letter(struct shortlog *log,
+ struct rev_info *rev,
+ struct commit **list,
+ int nr)
+{
+ shortlog_init(log);
+ log->wrap_lines = 1;
+ log->wrap = MAIL_DEFAULT_WRAP;
+ log->in1 = 2;
+ log->in2 = 4;
+ log->file = rev->diffopt.file;
+ log->groups = SHORTLOG_GROUP_AUTHOR;
+ shortlog_finish_setup(log);
+ for (int i = 0; i < nr; i++)
+ shortlog_add_commit(log, list[i]);
+
+ shortlog_output(log);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
-
- shortlog_output(&log);
+ generate_shortlog_cover_letter(&log, rev, list, nr);
/* We can only do diffstat with a unique reference point */
if (origin)
--
2.53.0.4.gf9ee8e2400
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v4 3/4] format-patch: add ability to use alt cover format
2026-02-27 13:18 ` [PATCH v4 0/4] format-patch: add cover-letter-format option Mirko Faina
2026-02-27 13:18 ` [PATCH v4 1/4] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-02-27 13:18 ` [PATCH v4 2/4] format-patch: move cover letter summary generation Mirko Faina
@ 2026-02-27 13:18 ` Mirko Faina
2026-02-27 13:18 ` [PATCH v4 4/4] format-patch: add commitListFormat config Mirko Faina
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
4 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 13:18 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
Give the ability to format-patch to specify an alternative format spec
through the "--cover-letter-format" option. This option either takes
"shortlog", which is the current format, or a format spec prefixed with
"log:".
Example:
git format-patch --cover-letter \
--cover-letter-format="log:[%(count)/%(total)] %s (%an)" HEAD~3
[1/3] this is a commit summary (Mirko Faina)
[2/3] this is another commit summary (Mirko Faina)
...
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 40 +++++++++++++++++++++++++++++++---
t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
3 files changed, 86 insertions(+), 3 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 0d12272031..46c8e33773 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,
shortlog_output(log);
}
+static void generate_commit_list_cover(FILE *cover_file,const char *format,
+ struct commit **list, int n)
+{
+ struct strbuf commit_line = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ struct rev_info rev = REV_INFO_INIT;
+
+ strbuf_init(&commit_line, 0);
+ rev.total = n;
+ ctx.rev = &rev;
+ for (int i = n - 1; i >= 0; i--) {
+ rev.nr = n - i;
+ repo_format_commit_message(the_repository, list[i], format,
+ &commit_line, &ctx);
+ fprintf(cover_file, "%s\n", commit_line.buf);
+ strbuf_reset(&commit_line);
+ }
+ fprintf(cover_file, "\n");
+
+ strbuf_release(&commit_line);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
const char *description_file,
const char *branch_name,
int quiet,
- const struct format_config *cfg)
+ const struct format_config *cfg,
+ const char *format)
{
const char *committer;
struct shortlog log;
@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- generate_shortlog_cover_letter(&log, rev, list, nr);
+ if (skip_prefix(format, "log:", &format))
+ generate_commit_list_cover(rev->diffopt.file, format, list, nr);
+ else if (!strcmp(format, "shortlog"))
+ generate_shortlog_cover_letter(&log, rev, list, nr);
+ else
+ die(_("'%s' is not a valid format string"), format);
/* We can only do diffstat with a unique reference point */
if (origin)
@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,
int just_numbers = 0;
int ignore_if_in_upstream = 0;
int cover_letter = -1;
+ const char *cover_letter_fmt = NULL;
int boundary_count = 0;
int no_binary_diff = 0;
int zero_commit = 0;
@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,
N_("print patches to standard out")),
OPT_BOOL(0, "cover-letter", &cover_letter,
N_("generate a cover letter")),
+ OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
+ N_("format spec used for the commit list in the cover letter")),
OPT_BOOL(0, "numbered-files", &just_numbers,
N_("use simple number sequence for output file names")),
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,
/* nothing to do */
goto done;
total = list.nr;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,
}
rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
+
if (cover_letter) {
if (cfg.thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, !!output_directory,
origin, list.nr, list.items,
- description_file, branch_name, quiet, &cfg);
+ description_file, branch_name, quiet, &cfg,
+ cover_letter_fmt);
print_bases(&bases, rev.diffopt.file);
print_signature(signature, rev.diffopt.file);
total++;
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 21d6d0cd9e..458da80721 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '
done
'
+test_expect_success 'cover letter with subject, author and count' '
+ rm -rf patches &&
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
+ grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expected_success 'cover letter with author and count' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %an" -o patches HEAD~1 &&
+ grep "^\[1/1\] A U Thor$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter shortlog' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter --cover-letter-format=shortlog \
+ -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter no format' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 964e1f1569..4f760a7468 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '
test_completion "git send-email --cov" <<-\EOF &&
--cover-from-description=Z
--cover-letter Z
+ --cover-letter-format=Z
EOF
test_completion "git send-email --val" <<-\EOF &&
--validate Z
--
2.53.0.4.gf9ee8e2400
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v4 4/4] format-patch: add commitListFormat config
2026-02-27 13:18 ` [PATCH v4 0/4] format-patch: add cover-letter-format option Mirko Faina
` (2 preceding siblings ...)
2026-02-27 13:18 ` [PATCH v4 3/4] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-02-27 13:18 ` Mirko Faina
2026-02-27 16:42 ` [PATCH v4 5/4] docs: add usage for the cover-letter fmt feature Mirko Faina
2026-02-27 17:51 ` [PATCH v4 4/4] format-patch: add commitListFormat config Junio C Hamano
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
4 siblings, 2 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 13:18 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Using "--cover-letter" we can tell format-patch to generate a cover
letter, in this cover letter there's a list of commits included in the
patch series and the format is specified by the "--cover-letter-format"
option. Would be useful if this format could be configured from the
config file instead of always needing to pass it from the command line.
Teach format-patch how to read the format spec for the cover letter from
the config files. The variable it should look for is called
format.commitListFormat.
Possible values:
- commitListFormat is set but no string is passed: it will default to
"[%(count)/%(total)] %s"
- if a string is passed: will use it as a format spec. Note that this
is either "shortlog" or a format spec without the "log:" prefix e.g.
"%s (%an)"
- if commitListFormat is not set: it will default to the shortlog
format.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 24 +++++++++++++++++++
t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 77 insertions(+)
diff --git a/builtin/log.c b/builtin/log.c
index 46c8e33773..dcf7cd1792 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -886,6 +886,7 @@ struct format_config {
char *signature;
char *signature_file;
enum cover_setting config_cover_letter;
+ char *fmt_cover_letter_commit_list;
char *config_output_directory;
enum cover_from_description cover_from_description_mode;
int show_notes;
@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
string_list_clear(&cfg->extra_cc, 0);
strbuf_release(&cfg->sprefix);
free(cfg->fmt_patch_suffix);
+ free(cfg->fmt_cover_letter_commit_list);
}
static enum cover_from_description parse_cover_from_description(const char *arg)
@@ -1052,6 +1054,22 @@ static int git_format_config(const char *var, const char *value,
cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
return 0;
}
+ if (!strcmp(var, "format.commitlistformat")) {
+ struct strbuf tmp = STRBUF_INIT;
+ strbuf_init(&tmp, 0);
+ if (value) {
+ if (strcmp(value, "shortlog"))
+ strbuf_addstr(&tmp, "log:");
+ strbuf_addstr(&tmp, value);
+ } else {
+ strbuf_addstr(&tmp, "log:[%(count)/%(total)] %s");
+ }
+
+ FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
+ git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
+ strbuf_release(&tmp);
+ return 0;
+ }
if (!strcmp(var, "format.outputdirectory")) {
FREE_AND_NULL(cfg->config_output_directory);
return git_config_string(&cfg->config_output_directory, var, value);
@@ -2329,6 +2347,12 @@ int cmd_format_patch(int argc,
goto done;
total = list.nr;
+ if (!cover_letter_fmt) {
+ cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
+ if (!cover_letter_fmt)
+ cover_letter_fmt = "shortlog";
+ }
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 458da80721..c43c6972af 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '
test_line_count = 1 result
'
+test_expect_success 'cover letter config with count, subject and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "[%(count)/%(total)] %s (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config with count and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "[%(count)/%(total)] (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set but no format' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ printf "\tcommitlistformat" >> .git/config &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .*" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set to shortlog' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat shortlog &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter config commitlistformat not set' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ git config set format.coverletter true &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
--
2.53.0.4.gf9ee8e2400
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v4 5/4] docs: add usage for the cover-letter fmt feature
2026-02-27 13:18 ` [PATCH v4 4/4] format-patch: add commitListFormat config Mirko Faina
@ 2026-02-27 16:42 ` Mirko Faina
2026-02-27 17:51 ` [PATCH v4 4/4] format-patch: add commitListFormat config Junio C Hamano
1 sibling, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 16:42 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Document the new "--cover-letter-format" feature in format-patch and its
related config variable "format.commitListFormat".
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
Sorry, forgot to update the docs. This patch is to be applied on top of
patch #4 of the series.
Documentation/config/format.adoc | 6 ++++++
Documentation/git-format-patch.adoc | 11 +++++++++++
2 files changed, 17 insertions(+)
diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc
index ab0710e86a..b91e59321c 100644
--- a/Documentation/config/format.adoc
+++ b/Documentation/config/format.adoc
@@ -101,6 +101,12 @@ format.coverLetter::
generate a cover-letter only when there's more than one patch.
Default is false.
+format.commitListFormat::
+ A format string that specifies how to generate the commit list of a
+ cover-letter when format-patch is invoked. This is the config coupled
+ with `--cover-letter-format` in the format-patch command.
+ Default is "shortlog".
+
format.outputDirectory::
Set a custom directory to store the resulting files instead of the
current working directory. All directory components will be created.
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index 9a7807ca71..bdcb5f989c 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -24,6 +24,7 @@ SYNOPSIS
[(--reroll-count|-v) <n>]
[--to=<email>] [--cc=<email>]
[--[no-]cover-letter] [--quiet]
+ [--cover-letter-format=<format-spec>]
[--[no-]encode-email-headers]
[--no-notes | --notes[=<ref>]]
[--interdiff=<previous>]
@@ -321,6 +322,15 @@ feeding the result to `git send-email`.
containing the branch description, shortlog and the overall diffstat. You can
fill in a description in the file before sending it out.
+--cover-letter-format=<format-spec>::
+ Specify the format in which to generate the commit list of the
+ patch series. This option is available if the user wants to use
+ an alternative to the default shortlog format. The accepted
+ values for format-spec are "shortlog" or a format string
+ prefixed with `log:`.
+ e.g. `log: %s (%an)`
+ This option is relevant only if a cover letter is generated.
+
--encode-email-headers::
--no-encode-email-headers::
Encode email headers that have non-ASCII characters with
@@ -452,6 +462,7 @@ with configuration variables.
signOff = true
outputDirectory = <directory>
coverLetter = auto
+ commitListFormat = shortlog
coverFromDescription = auto
------------
--
2.53.0
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH v4 4/4] format-patch: add commitListFormat config
2026-02-27 13:18 ` [PATCH v4 4/4] format-patch: add commitListFormat config Mirko Faina
2026-02-27 16:42 ` [PATCH v4 5/4] docs: add usage for the cover-letter fmt feature Mirko Faina
@ 2026-02-27 17:51 ` Junio C Hamano
2026-02-27 21:51 ` Mirko Faina
1 sibling, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-02-27 17:51 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> Possible values:
> - commitListFormat is set but no string is passed: it will default to
> "[%(count)/%(total)] %s"
>
> - if a string is passed: will use it as a format spec. Note that this
> is either "shortlog" or a format spec without the "log:" prefix e.g.
> "%s (%an)"
Hmph.
Some sort of DWIM (e.g., it is not "shortlog", so it must be "log:"
as there is nothing else supported) can be beneficial to help make
it less cumbersome for users to type for command line options, just
like we did for "git log --pretty=X" where we take a string with '%'
in it as a cue to default to "--pretty=tformat:". But as "set and
forget" facility, there is no strong need to prefer "%s (%an)" over
"log:%s (%an)" on the configuration variable side (other than being
consistent to the command line option parsing). We would probably
want to give an escape hatch in the design to avoid painting
ourselves into a corner we cannot get out of (imagine what happens
when later others want to use something completely different from
the log-pretty machinery, making "not a shortlog, must be log:"
assumption invalid).
Something like
"log:" is prefixed to the string value, UNLESS the value matches
"^[-a-z0-9]*$" or the value matches "^[-a-z0-9]*:".
would probably be extensible enough. To avoid exposing regexp to
the users, we can say "a string that is not an alphanumeric token,
nor an alphanumeric token followed by a colon, is prefixed with
'log:'", or something.
WIth such rules in place, a new fixed format other than "shortlog"
can be introduced (e.g., "middlelog") without getting munged into
nonsense "log:middlelog", and a new parameterized format other than
"log:" can be introduced (e.g., "pretty:%(subject) %(author)")
without turning into a nonsense "log:pretty:%(subject) %(author)".
Or we do not have to worry about these if we just use what the user
gives us without any DWIM. I just do not think we want to see the
"not a shortlog? must be log" burned into the syntax that will have
to stay with us forever.
Thanks.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v4 4/4] format-patch: add commitListFormat config
2026-02-27 17:51 ` [PATCH v4 4/4] format-patch: add commitListFormat config Junio C Hamano
@ 2026-02-27 21:51 ` Mirko Faina
2026-02-27 22:21 ` Junio C Hamano
0 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 21:51 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Mirko Faina
On Fri, Feb 27, 2026 at 09:51:07AM -0800, Junio C Hamano wrote:
> Some sort of DWIM (e.g., it is not "shortlog", so it must be "log:"
> as there is nothing else supported) can be beneficial to help make
> it less cumbersome for users to type for command line options, just
> like we did for "git log --pretty=X" where we take a string with '%'
> in it as a cue to default to "--pretty=tformat:". But as "set and
> forget" facility, there is no strong need to prefer "%s (%an)" over
> "log:%s (%an)" on the configuration variable side (other than being
> consistent to the command line option parsing). We would probably
> want to give an escape hatch in the design to avoid painting
> ourselves into a corner we cannot get out of (imagine what happens
> when later others want to use something completely different from
> the log-pretty machinery, making "not a shortlog, must be log:"
> assumption invalid).
>
> Something like
>
> "log:" is prefixed to the string value, UNLESS the value matches
> "^[-a-z0-9]*$" or the value matches "^[-a-z0-9]*:".
>
> would probably be extensible enough. To avoid exposing regexp to
> the users, we can say "a string that is not an alphanumeric token,
> nor an alphanumeric token followed by a colon, is prefixed with
> 'log:'", or something.
>
> WIth such rules in place, a new fixed format other than "shortlog"
> can be introduced (e.g., "middlelog") without getting munged into
> nonsense "log:middlelog", and a new parameterized format other than
> "log:" can be introduced (e.g., "pretty:%(subject) %(author)")
> without turning into a nonsense "log:pretty:%(subject) %(author)".
>
> Or we do not have to worry about these if we just use what the user
> gives us without any DWIM. I just do not think we want to see the
> "not a shortlog? must be log" burned into the syntax that will have
> to stay with us forever.
Since we already require the prefix in the command line might as well
just make it the same for the configuration file. That way, if someone
will ever introduce a new preset format other than shortlog we just
check for "log:" first and then cascade into checking the rest of the
presets.
Please confirm that you like this and I'll start making changes to the
patch.
Thank you
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v4 4/4] format-patch: add commitListFormat config
2026-02-27 21:51 ` Mirko Faina
@ 2026-02-27 22:21 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-02-27 22:21 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
>> Or we do not have to worry about these if we just use what the user
>> gives us without any DWIM. I just do not think we want to see the
>> "not a shortlog? must be log" burned into the syntax that will have
>> to stay with us forever.
>
> Since we already require the prefix in the command line might as well
> just make it the same for the configuration file.
Sounds sensible. Either with or without DWIM (with escape hatch),
being consistent between the command line and configuration is a
good thing.
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v5 0/5] format-patch: add cover-letter-format option
2026-02-27 13:18 ` [PATCH v4 0/4] format-patch: add cover-letter-format option Mirko Faina
` (3 preceding siblings ...)
2026-02-27 13:18 ` [PATCH v4 4/4] format-patch: add commitListFormat config Mirko Faina
@ 2026-02-27 22:48 ` Mirko Faina
2026-02-27 22:48 ` [PATCH v5 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
` (6 more replies)
4 siblings, 7 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 22:48 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
I've reconciled the formats between the command line option and the
configuration variable. Changes are reflected in the documentation and
the tests.
Thank you for the review
[1/5] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)
[2/5] format-patch: move cover letter summary generation (Mirko Faina)
[3/5] format-patch: add ability to use alt cover format (Mirko Faina)
[4/5] format-patch: add commitListFormat config (Mirko Faina)
[5/5] docs: add usage for the cover-letter fmt feature (Mirko Faina)
Documentation/config/format.adoc | 7 ++
Documentation/git-format-patch.adoc | 11 +++
builtin/log.c | 91 +++++++++++++++++++++----
pretty.c | 15 +++++
t/t4014-format-patch.sh | 101 ++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
6 files changed, 212 insertions(+), 14 deletions(-)
--
2.53.0.5.ga216069370
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v5 1/5] pretty.c: add %(count) and %(total) placeholders
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
@ 2026-02-27 22:48 ` Mirko Faina
2026-02-27 22:48 ` [PATCH v5 2/5] format-patch: move cover letter summary generation Mirko Faina
` (5 subsequent siblings)
6 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 22:48 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
In many commands we can customize the output through the "--format" or
the "--pretty" options. This patch adds two new placeholders used mainly
when there's a range of commits that we want to show.
Currently these two placeholders are not usable as they're coupled with
the rev_info->nr and rev_info->total fields, fields that are used only
by the format-patch numbered email subjects.
Teach repo_format_commit_message() the %(count) and %(total)
placeholders.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
pretty.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/pretty.c b/pretty.c
index e0646bbc5d..e29bb8b877 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
if (!commit->object.parsed)
parse_object(the_repository, &commit->object.oid);
+ if (starts_with(placeholder, "(count)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total),
+ c->pretty_ctx->rev->nr);
+ return 7;
+ }
+
+ if (starts_with(placeholder, "(total)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%d", c->pretty_ctx->rev->total);
+ return 7;
+ }
+
switch (placeholder[0]) {
case 'H': /* commit hash */
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
--
2.53.0.5.ga216069370
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v5 2/5] format-patch: move cover letter summary generation
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
2026-02-27 22:48 ` [PATCH v5 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
@ 2026-02-27 22:48 ` Mirko Faina
2026-02-27 22:48 ` [PATCH v5 3/5] format-patch: add ability to use alt cover format Mirko Faina
` (4 subsequent siblings)
6 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 22:48 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
As of now format-patch allows generation of a template cover letter for
patch series through "--cover-letter".
Move shortlog summary code generation to its own function. This is done
in preparation to other patches where we enable the user to format the
commit list using thier own format string.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 32 ++++++++++++++++++++------------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 5c9a8ef363..0d12272031 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_shortlog_cover_letter(struct shortlog *log,
+ struct rev_info *rev,
+ struct commit **list,
+ int nr)
+{
+ shortlog_init(log);
+ log->wrap_lines = 1;
+ log->wrap = MAIL_DEFAULT_WRAP;
+ log->in1 = 2;
+ log->in2 = 4;
+ log->file = rev->diffopt.file;
+ log->groups = SHORTLOG_GROUP_AUTHOR;
+ shortlog_finish_setup(log);
+ for (int i = 0; i < nr; i++)
+ shortlog_add_commit(log, list[i]);
+
+ shortlog_output(log);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
-
- shortlog_output(&log);
+ generate_shortlog_cover_letter(&log, rev, list, nr);
/* We can only do diffstat with a unique reference point */
if (origin)
--
2.53.0.5.ga216069370
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v5 3/5] format-patch: add ability to use alt cover format
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
2026-02-27 22:48 ` [PATCH v5 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-02-27 22:48 ` [PATCH v5 2/5] format-patch: move cover letter summary generation Mirko Faina
@ 2026-02-27 22:48 ` Mirko Faina
2026-02-27 22:48 ` [PATCH v5 4/5] format-patch: add commitListFormat config Mirko Faina
` (3 subsequent siblings)
6 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 22:48 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
Give the ability to format-patch to specify an alternative format spec
through the "--cover-letter-format" option. This option either takes
"shortlog", which is the current format, or a format spec prefixed with
"log:".
Example:
git format-patch --cover-letter \
--cover-letter-format="log:[%(count)/%(total)] %s (%an)" HEAD~3
[1/3] this is a commit summary (Mirko Faina)
[2/3] this is another commit summary (Mirko Faina)
...
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 40 +++++++++++++++++++++++++++++++---
t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
3 files changed, 86 insertions(+), 3 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 0d12272031..46c8e33773 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,
shortlog_output(log);
}
+static void generate_commit_list_cover(FILE *cover_file,const char *format,
+ struct commit **list, int n)
+{
+ struct strbuf commit_line = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ struct rev_info rev = REV_INFO_INIT;
+
+ strbuf_init(&commit_line, 0);
+ rev.total = n;
+ ctx.rev = &rev;
+ for (int i = n - 1; i >= 0; i--) {
+ rev.nr = n - i;
+ repo_format_commit_message(the_repository, list[i], format,
+ &commit_line, &ctx);
+ fprintf(cover_file, "%s\n", commit_line.buf);
+ strbuf_reset(&commit_line);
+ }
+ fprintf(cover_file, "\n");
+
+ strbuf_release(&commit_line);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
const char *description_file,
const char *branch_name,
int quiet,
- const struct format_config *cfg)
+ const struct format_config *cfg,
+ const char *format)
{
const char *committer;
struct shortlog log;
@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- generate_shortlog_cover_letter(&log, rev, list, nr);
+ if (skip_prefix(format, "log:", &format))
+ generate_commit_list_cover(rev->diffopt.file, format, list, nr);
+ else if (!strcmp(format, "shortlog"))
+ generate_shortlog_cover_letter(&log, rev, list, nr);
+ else
+ die(_("'%s' is not a valid format string"), format);
/* We can only do diffstat with a unique reference point */
if (origin)
@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,
int just_numbers = 0;
int ignore_if_in_upstream = 0;
int cover_letter = -1;
+ const char *cover_letter_fmt = NULL;
int boundary_count = 0;
int no_binary_diff = 0;
int zero_commit = 0;
@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,
N_("print patches to standard out")),
OPT_BOOL(0, "cover-letter", &cover_letter,
N_("generate a cover letter")),
+ OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
+ N_("format spec used for the commit list in the cover letter")),
OPT_BOOL(0, "numbered-files", &just_numbers,
N_("use simple number sequence for output file names")),
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,
/* nothing to do */
goto done;
total = list.nr;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,
}
rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
+
if (cover_letter) {
if (cfg.thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, !!output_directory,
origin, list.nr, list.items,
- description_file, branch_name, quiet, &cfg);
+ description_file, branch_name, quiet, &cfg,
+ cover_letter_fmt);
print_bases(&bases, rev.diffopt.file);
print_signature(signature, rev.diffopt.file);
total++;
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 21d6d0cd9e..458da80721 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '
done
'
+test_expect_success 'cover letter with subject, author and count' '
+ rm -rf patches &&
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
+ grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expected_success 'cover letter with author and count' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %an" -o patches HEAD~1 &&
+ grep "^\[1/1\] A U Thor$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter shortlog' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter --cover-letter-format=shortlog \
+ -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter no format' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 964e1f1569..4f760a7468 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '
test_completion "git send-email --cov" <<-\EOF &&
--cover-from-description=Z
--cover-letter Z
+ --cover-letter-format=Z
EOF
test_completion "git send-email --val" <<-\EOF &&
--validate Z
--
2.53.0.5.ga216069370
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v5 4/5] format-patch: add commitListFormat config
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
` (2 preceding siblings ...)
2026-02-27 22:48 ` [PATCH v5 3/5] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-02-27 22:48 ` Mirko Faina
2026-02-27 22:48 ` [PATCH v5 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
` (2 subsequent siblings)
6 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 22:48 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Using "--cover-letter" we can tell format-patch to generate a cover
letter, in this cover letter there's a list of commits included in the
patch series and the format is specified by the "--cover-letter-format"
option. Would be useful if this format could be configured from the
config file instead of always needing to pass it from the command line.
Teach format-patch how to read the format spec for the cover letter from
the config files. The variable it should look for is called
format.commitListFormat.
Possible values:
- commitListFormat is set but no string is passed: it will default to
"[%(count)/%(total)] %s"
- if a string is passed: will use it as a format spec. Note that this
is either "shortlog" or a format spec prefixed by "log:"
e.g."log:%s (%an)"
- if commitListFormat is not set: it will default to the shortlog
format.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 21 ++++++++++++++++
t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 74 insertions(+)
diff --git a/builtin/log.c b/builtin/log.c
index 46c8e33773..e8d2e373ec 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -886,6 +886,7 @@ struct format_config {
char *signature;
char *signature_file;
enum cover_setting config_cover_letter;
+ char *fmt_cover_letter_commit_list;
char *config_output_directory;
enum cover_from_description cover_from_description_mode;
int show_notes;
@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
string_list_clear(&cfg->extra_cc, 0);
strbuf_release(&cfg->sprefix);
free(cfg->fmt_patch_suffix);
+ free(cfg->fmt_cover_letter_commit_list);
}
static enum cover_from_description parse_cover_from_description(const char *arg)
@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
return 0;
}
+ if (!strcmp(var, "format.commitlistformat")) {
+ struct strbuf tmp = STRBUF_INIT;
+ strbuf_init(&tmp, 0);
+ if (value)
+ strbuf_addstr(&tmp, value);
+ else
+ strbuf_addstr(&tmp, "log:[%(count)/%(total)] %s");
+
+ FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
+ git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
+ strbuf_release(&tmp);
+ return 0;
+ }
if (!strcmp(var, "format.outputdirectory")) {
FREE_AND_NULL(cfg->config_output_directory);
return git_config_string(&cfg->config_output_directory, var, value);
@@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,
goto done;
total = list.nr;
+ if (!cover_letter_fmt) {
+ cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
+ if (!cover_letter_fmt)
+ cover_letter_fmt = "shortlog";
+ }
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 458da80721..4891389a53 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '
test_line_count = 1 result
'
+test_expect_success 'cover letter config with count, subject and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "log:[%(count)/%(total)] %s (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config with count and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "log:[%(count)/%(total)] (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set but no format' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ printf "\tcommitlistformat" >> .git/config &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .*" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set to shortlog' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat shortlog &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter config commitlistformat not set' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ git config set format.coverletter true &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
--
2.53.0.5.ga216069370
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v5 5/5] docs: add usage for the cover-letter fmt feature
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
` (3 preceding siblings ...)
2026-02-27 22:48 ` [PATCH v5 4/5] format-patch: add commitListFormat config Mirko Faina
@ 2026-02-27 22:48 ` Mirko Faina
2026-03-06 22:33 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Junio C Hamano
2026-03-06 22:58 ` [PATCH v6 " Mirko Faina
6 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-02-27 22:48 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Document the new "--cover-letter-format" feature in format-patch and its
related config variable "format.commitListFormat".
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
Documentation/config/format.adoc | 7 +++++++
Documentation/git-format-patch.adoc | 11 +++++++++++
2 files changed, 18 insertions(+)
diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc
index ab0710e86a..771e84af98 100644
--- a/Documentation/config/format.adoc
+++ b/Documentation/config/format.adoc
@@ -101,6 +101,13 @@ format.coverLetter::
generate a cover-letter only when there's more than one patch.
Default is false.
+format.commitListFormat::
+ A format string that specifies how to generate the commit list
+ of a cover-letter when format-patch is invoked. This is the
+ config coupled with `--cover-letter-format` in the format-patch
+ command and they both accept the same values.
+ Default is shortlog.
+
format.outputDirectory::
Set a custom directory to store the resulting files instead of the
current working directory. All directory components will be created.
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index 9a7807ca71..bdcb5f989c 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -24,6 +24,7 @@ SYNOPSIS
[(--reroll-count|-v) <n>]
[--to=<email>] [--cc=<email>]
[--[no-]cover-letter] [--quiet]
+ [--cover-letter-format=<format-spec>]
[--[no-]encode-email-headers]
[--no-notes | --notes[=<ref>]]
[--interdiff=<previous>]
@@ -321,6 +322,15 @@ feeding the result to `git send-email`.
containing the branch description, shortlog and the overall diffstat. You can
fill in a description in the file before sending it out.
+--cover-letter-format=<format-spec>::
+ Specify the format in which to generate the commit list of the
+ patch series. This option is available if the user wants to use
+ an alternative to the default shortlog format. The accepted
+ values for format-spec are "shortlog" or a format string
+ prefixed with `log:`.
+ e.g. `log: %s (%an)`
+ This option is relevant only if a cover letter is generated.
+
--encode-email-headers::
--no-encode-email-headers::
Encode email headers that have non-ASCII characters with
@@ -452,6 +462,7 @@ with configuration variables.
signOff = true
outputDirectory = <directory>
coverLetter = auto
+ commitListFormat = shortlog
coverFromDescription = auto
------------
--
2.53.0.5.ga216069370
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH v5 0/5] format-patch: add cover-letter-format option
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
` (4 preceding siblings ...)
2026-02-27 22:48 ` [PATCH v5 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
@ 2026-03-06 22:33 ` Junio C Hamano
2026-03-06 22:49 ` Mirko Faina
2026-03-06 22:58 ` [PATCH v6 " Mirko Faina
6 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-03-06 22:33 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> I've reconciled the formats between the command line option and the
> configuration variable. Changes are reflected in the documentation and
> the tests.
>
> Thank you for the review
>
> [1/5] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)
> [2/5] format-patch: move cover letter summary generation (Mirko Faina)
> [3/5] format-patch: add ability to use alt cover format (Mirko Faina)
> [4/5] format-patch: add commitListFormat config (Mirko Faina)
> [5/5] docs: add usage for the cover-letter fmt feature (Mirko Faina)
How do people find this latest round, which unfortunately haven't
seen any reactions to? The earlier rounds have good discussions and
I do not think this round misses anything discovered and discussed
so far. Shall we declare victory and mark the topic for 'next'?
Thanks.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v5 0/5] format-patch: add cover-letter-format option
2026-03-06 22:33 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Junio C Hamano
@ 2026-03-06 22:49 ` Mirko Faina
0 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 22:49 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Mirko Faina
On Fri, Mar 06, 2026 at 02:33:14PM -0800, Junio C Hamano wrote:
> How do people find this latest round, which unfortunately haven't
> seen any reactions to? The earlier rounds have good discussions and
> I do not think this round misses anything discovered and discussed
> so far. Shall we declare victory and mark the topic for 'next'?
On my tree I have a few retouches I made on some commit messages and
more importantly on the docs. Other than that I think it is good to go.
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v6 0/5] format-patch: add cover-letter-format option
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
` (5 preceding siblings ...)
2026-03-06 22:33 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Junio C Hamano
@ 2026-03-06 22:58 ` Mirko Faina
2026-03-06 22:58 ` [PATCH v6 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
` (5 more replies)
6 siblings, 6 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 22:58 UTC (permalink / raw)
To: git; +Cc: Mroik, Junio C Hamano, Jeff King
From: Mroik <mroik@delayed.space>
Last version
[1/5] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)
[2/5] format-patch: move cover letter summary generation (Mirko Faina)
[3/5] format-patch: add ability to use alt cover format (Mirko Faina)
[4/5] format-patch: add commitListFormat config (Mirko Faina)
[5/5] docs: add usage for the cover-letter fmt feature (Mirko Faina)
Documentation/config/format.adoc | 6 ++
Documentation/git-format-patch.adoc | 11 +++
builtin/log.c | 91 +++++++++++++++++++++----
pretty.c | 15 +++++
t/t4014-format-patch.sh | 101 ++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
6 files changed, 211 insertions(+), 14 deletions(-)
Range-diff against v5:
1: 169db9df4d ! 1: cfed3bddf6 pretty.c: add %(count) and %(total) placeholders
@@ Commit message
placeholders.
Signed-off-by: Mirko Faina <mroik@delayed.space>
- Signed-off-by: Junio C Hamano <gitster@pobox.com>
## pretty.c ##
@@ pretty.c: static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
2: c69d6409d5 ! 2: dc131c7565 format-patch: move cover letter summary generation
@@ Commit message
commit list using thier own format string.
Signed-off-by: Mirko Faina <mroik@delayed.space>
- Signed-off-by: Junio C Hamano <gitster@pobox.com>
## builtin/log.c ##
@@ builtin/log.c: static void get_notes_args(struct strvec *arg, struct rev_info *rev)
3: c3a92d8896 ! 3: 316c9e76ee format-patch: add ability to use alt cover format
@@ Commit message
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
- Give the ability to format-patch to specify an alternative format spec
+ Give format-patch the ability to specify an alternative format spec
through the "--cover-letter-format" option. This option either takes
"shortlog", which is the current format, or a format spec prefixed with
"log:".
@@ Commit message
...
Signed-off-by: Mirko Faina <mroik@delayed.space>
- Signed-off-by: Junio C Hamano <gitster@pobox.com>
## builtin/log.c ##
@@ builtin/log.c: static void generate_shortlog_cover_letter(struct shortlog *log,
shortlog_output(log);
}
-+static void generate_commit_list_cover(FILE *cover_file,const char *format,
++static void generate_commit_list_cover(FILE *cover_file, const char *format,
+ struct commit **list, int n)
+{
+ struct strbuf commit_line = STRBUF_INIT;
4: a6e4e1ac84 ! 4: c522f47e5b format-patch: add commitListFormat config
@@ Commit message
format.
Signed-off-by: Mirko Faina <mroik@delayed.space>
- Signed-off-by: Junio C Hamano <gitster@pobox.com>
## builtin/log.c ##
@@ builtin/log.c: struct format_config {
5: 40d1aa8785 ! 5: 1a4ba6dc33 docs: add usage for the cover-letter fmt feature
@@ Metadata
## Commit message ##
docs: add usage for the cover-letter fmt feature
- Document the new "--cover-letter-format" feature in format-patch and its
+ Document the new "--cover-letter-format" option in format-patch and its
related config variable "format.commitListFormat".
Signed-off-by: Mirko Faina <mroik@delayed.space>
- Signed-off-by: Junio C Hamano <gitster@pobox.com>
## Documentation/config/format.adoc ##
@@ Documentation/config/format.adoc: format.coverLetter::
@@ Documentation/config/format.adoc: format.coverLetter::
Default is false.
+format.commitListFormat::
-+ A format string that specifies how to generate the commit list
-+ of a cover-letter when format-patch is invoked. This is the
-+ config coupled with `--cover-letter-format` in the format-patch
-+ command and they both accept the same values.
-+ Default is shortlog.
++ A format string that specifies how to generate the commit list of a
++ cover-letter when format-patch is invoked. This config is coupled with
++ the `--cover-letter-format` format-patch option command and they both
++ accept the same values. Default is `shortlog`.
+
format.outputDirectory::
Set a custom directory to store the resulting files instead of the
@@ Documentation/git-format-patch.adoc: feeding the result to `git send-email`.
+--cover-letter-format=<format-spec>::
+ Specify the format in which to generate the commit list of the
+ patch series. This option is available if the user wants to use
-+ an alternative to the default shortlog format. The accepted
++ an alternative to the default `shortlog` format. The accepted
+ values for format-spec are "shortlog" or a format string
+ prefixed with `log:`.
+ e.g. `log: %s (%an)`
--
2.53.0.5.g1a4ba6dc33
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v6 1/5] pretty.c: add %(count) and %(total) placeholders
2026-03-06 22:58 ` [PATCH v6 " Mirko Faina
@ 2026-03-06 22:58 ` Mirko Faina
2026-03-06 22:58 ` [PATCH v6 2/5] format-patch: move cover letter summary generation Mirko Faina
` (4 subsequent siblings)
5 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 22:58 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
In many commands we can customize the output through the "--format" or
the "--pretty" options. This patch adds two new placeholders used mainly
when there's a range of commits that we want to show.
Currently these two placeholders are not usable as they're coupled with
the rev_info->nr and rev_info->total fields, fields that are used only
by the format-patch numbered email subjects.
Teach repo_format_commit_message() the %(count) and %(total)
placeholders.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
pretty.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/pretty.c b/pretty.c
index e0646bbc5d..e29bb8b877 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
if (!commit->object.parsed)
parse_object(the_repository, &commit->object.oid);
+ if (starts_with(placeholder, "(count)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total),
+ c->pretty_ctx->rev->nr);
+ return 7;
+ }
+
+ if (starts_with(placeholder, "(total)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%d", c->pretty_ctx->rev->total);
+ return 7;
+ }
+
switch (placeholder[0]) {
case 'H': /* commit hash */
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
--
2.53.0.5.g1a4ba6dc33
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v6 2/5] format-patch: move cover letter summary generation
2026-03-06 22:58 ` [PATCH v6 " Mirko Faina
2026-03-06 22:58 ` [PATCH v6 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
@ 2026-03-06 22:58 ` Mirko Faina
2026-03-06 22:58 ` [PATCH v6 3/5] format-patch: add ability to use alt cover format Mirko Faina
` (3 subsequent siblings)
5 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 22:58 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
As of now format-patch allows generation of a template cover letter for
patch series through "--cover-letter".
Move shortlog summary code generation to its own function. This is done
in preparation to other patches where we enable the user to format the
commit list using thier own format string.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 32 ++++++++++++++++++++------------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 5c9a8ef363..0d12272031 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_shortlog_cover_letter(struct shortlog *log,
+ struct rev_info *rev,
+ struct commit **list,
+ int nr)
+{
+ shortlog_init(log);
+ log->wrap_lines = 1;
+ log->wrap = MAIL_DEFAULT_WRAP;
+ log->in1 = 2;
+ log->in2 = 4;
+ log->file = rev->diffopt.file;
+ log->groups = SHORTLOG_GROUP_AUTHOR;
+ shortlog_finish_setup(log);
+ for (int i = 0; i < nr; i++)
+ shortlog_add_commit(log, list[i]);
+
+ shortlog_output(log);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
-
- shortlog_output(&log);
+ generate_shortlog_cover_letter(&log, rev, list, nr);
/* We can only do diffstat with a unique reference point */
if (origin)
--
2.53.0.5.g1a4ba6dc33
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v6 3/5] format-patch: add ability to use alt cover format
2026-03-06 22:58 ` [PATCH v6 " Mirko Faina
2026-03-06 22:58 ` [PATCH v6 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-03-06 22:58 ` [PATCH v6 2/5] format-patch: move cover letter summary generation Mirko Faina
@ 2026-03-06 22:58 ` Mirko Faina
2026-03-10 22:14 ` Junio C Hamano
2026-03-06 22:58 ` [PATCH v6 4/5] format-patch: add commitListFormat config Mirko Faina
` (2 subsequent siblings)
5 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 22:58 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
Give format-patch the ability to specify an alternative format spec
through the "--cover-letter-format" option. This option either takes
"shortlog", which is the current format, or a format spec prefixed with
"log:".
Example:
git format-patch --cover-letter \
--cover-letter-format="log:[%(count)/%(total)] %s (%an)" HEAD~3
[1/3] this is a commit summary (Mirko Faina)
[2/3] this is another commit summary (Mirko Faina)
...
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 40 +++++++++++++++++++++++++++++++---
t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
3 files changed, 86 insertions(+), 3 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 0d12272031..95e5d9755f 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,
shortlog_output(log);
}
+static void generate_commit_list_cover(FILE *cover_file, const char *format,
+ struct commit **list, int n)
+{
+ struct strbuf commit_line = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ struct rev_info rev = REV_INFO_INIT;
+
+ strbuf_init(&commit_line, 0);
+ rev.total = n;
+ ctx.rev = &rev;
+ for (int i = n - 1; i >= 0; i--) {
+ rev.nr = n - i;
+ repo_format_commit_message(the_repository, list[i], format,
+ &commit_line, &ctx);
+ fprintf(cover_file, "%s\n", commit_line.buf);
+ strbuf_reset(&commit_line);
+ }
+ fprintf(cover_file, "\n");
+
+ strbuf_release(&commit_line);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
const char *description_file,
const char *branch_name,
int quiet,
- const struct format_config *cfg)
+ const struct format_config *cfg,
+ const char *format)
{
const char *committer;
struct shortlog log;
@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- generate_shortlog_cover_letter(&log, rev, list, nr);
+ if (skip_prefix(format, "log:", &format))
+ generate_commit_list_cover(rev->diffopt.file, format, list, nr);
+ else if (!strcmp(format, "shortlog"))
+ generate_shortlog_cover_letter(&log, rev, list, nr);
+ else
+ die(_("'%s' is not a valid format string"), format);
/* We can only do diffstat with a unique reference point */
if (origin)
@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,
int just_numbers = 0;
int ignore_if_in_upstream = 0;
int cover_letter = -1;
+ const char *cover_letter_fmt = NULL;
int boundary_count = 0;
int no_binary_diff = 0;
int zero_commit = 0;
@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,
N_("print patches to standard out")),
OPT_BOOL(0, "cover-letter", &cover_letter,
N_("generate a cover letter")),
+ OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
+ N_("format spec used for the commit list in the cover letter")),
OPT_BOOL(0, "numbered-files", &just_numbers,
N_("use simple number sequence for output file names")),
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,
/* nothing to do */
goto done;
total = list.nr;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,
}
rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
+
if (cover_letter) {
if (cfg.thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, !!output_directory,
origin, list.nr, list.items,
- description_file, branch_name, quiet, &cfg);
+ description_file, branch_name, quiet, &cfg,
+ cover_letter_fmt);
print_bases(&bases, rev.diffopt.file);
print_signature(signature, rev.diffopt.file);
total++;
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 21d6d0cd9e..458da80721 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '
done
'
+test_expect_success 'cover letter with subject, author and count' '
+ rm -rf patches &&
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
+ grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expected_success 'cover letter with author and count' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %an" -o patches HEAD~1 &&
+ grep "^\[1/1\] A U Thor$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter shortlog' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter --cover-letter-format=shortlog \
+ -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter no format' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 964e1f1569..4f760a7468 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '
test_completion "git send-email --cov" <<-\EOF &&
--cover-from-description=Z
--cover-letter Z
+ --cover-letter-format=Z
EOF
test_completion "git send-email --val" <<-\EOF &&
--validate Z
--
2.53.0.5.g1a4ba6dc33
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v6 4/5] format-patch: add commitListFormat config
2026-03-06 22:58 ` [PATCH v6 " Mirko Faina
` (2 preceding siblings ...)
2026-03-06 22:58 ` [PATCH v6 3/5] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-03-06 22:58 ` Mirko Faina
2026-03-06 22:58 ` [PATCH v6 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
2026-03-06 23:34 ` [PATCH v7 0/5] format-patch: add cover-letter-format option Mirko Faina
5 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 22:58 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Using "--cover-letter" we can tell format-patch to generate a cover
letter, in this cover letter there's a list of commits included in the
patch series and the format is specified by the "--cover-letter-format"
option. Would be useful if this format could be configured from the
config file instead of always needing to pass it from the command line.
Teach format-patch how to read the format spec for the cover letter from
the config files. The variable it should look for is called
format.commitListFormat.
Possible values:
- commitListFormat is set but no string is passed: it will default to
"[%(count)/%(total)] %s"
- if a string is passed: will use it as a format spec. Note that this
is either "shortlog" or a format spec prefixed by "log:"
e.g."log:%s (%an)"
- if commitListFormat is not set: it will default to the shortlog
format.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 21 ++++++++++++++++
t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 74 insertions(+)
diff --git a/builtin/log.c b/builtin/log.c
index 95e5d9755f..5fec0ddaf9 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -886,6 +886,7 @@ struct format_config {
char *signature;
char *signature_file;
enum cover_setting config_cover_letter;
+ char *fmt_cover_letter_commit_list;
char *config_output_directory;
enum cover_from_description cover_from_description_mode;
int show_notes;
@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
string_list_clear(&cfg->extra_cc, 0);
strbuf_release(&cfg->sprefix);
free(cfg->fmt_patch_suffix);
+ free(cfg->fmt_cover_letter_commit_list);
}
static enum cover_from_description parse_cover_from_description(const char *arg)
@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
return 0;
}
+ if (!strcmp(var, "format.commitlistformat")) {
+ struct strbuf tmp = STRBUF_INIT;
+ strbuf_init(&tmp, 0);
+ if (value)
+ strbuf_addstr(&tmp, value);
+ else
+ strbuf_addstr(&tmp, "log:[%(count)/%(total)] %s");
+
+ FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
+ git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
+ strbuf_release(&tmp);
+ return 0;
+ }
if (!strcmp(var, "format.outputdirectory")) {
FREE_AND_NULL(cfg->config_output_directory);
return git_config_string(&cfg->config_output_directory, var, value);
@@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,
goto done;
total = list.nr;
+ if (!cover_letter_fmt) {
+ cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
+ if (!cover_letter_fmt)
+ cover_letter_fmt = "shortlog";
+ }
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 458da80721..4891389a53 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '
test_line_count = 1 result
'
+test_expect_success 'cover letter config with count, subject and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "log:[%(count)/%(total)] %s (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config with count and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "log:[%(count)/%(total)] (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set but no format' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ printf "\tcommitlistformat" >> .git/config &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .*" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set to shortlog' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat shortlog &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter config commitlistformat not set' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ git config set format.coverletter true &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
--
2.53.0.5.g1a4ba6dc33
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v6 5/5] docs: add usage for the cover-letter fmt feature
2026-03-06 22:58 ` [PATCH v6 " Mirko Faina
` (3 preceding siblings ...)
2026-03-06 22:58 ` [PATCH v6 4/5] format-patch: add commitListFormat config Mirko Faina
@ 2026-03-06 22:58 ` Mirko Faina
2026-03-06 23:18 ` Junio C Hamano
2026-03-06 23:34 ` [PATCH v7 0/5] format-patch: add cover-letter-format option Mirko Faina
5 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 22:58 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Document the new "--cover-letter-format" option in format-patch and its
related config variable "format.commitListFormat".
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
Documentation/config/format.adoc | 6 ++++++
Documentation/git-format-patch.adoc | 11 +++++++++++
2 files changed, 17 insertions(+)
diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc
index ab0710e86a..151e574052 100644
--- a/Documentation/config/format.adoc
+++ b/Documentation/config/format.adoc
@@ -101,6 +101,12 @@ format.coverLetter::
generate a cover-letter only when there's more than one patch.
Default is false.
+format.commitListFormat::
+ A format string that specifies how to generate the commit list of a
+ cover-letter when format-patch is invoked. This config is coupled with
+ the `--cover-letter-format` format-patch option command and they both
+ accept the same values. Default is `shortlog`.
+
format.outputDirectory::
Set a custom directory to store the resulting files instead of the
current working directory. All directory components will be created.
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index 9a7807ca71..42d2633860 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -24,6 +24,7 @@ SYNOPSIS
[(--reroll-count|-v) <n>]
[--to=<email>] [--cc=<email>]
[--[no-]cover-letter] [--quiet]
+ [--cover-letter-format=<format-spec>]
[--[no-]encode-email-headers]
[--no-notes | --notes[=<ref>]]
[--interdiff=<previous>]
@@ -321,6 +322,15 @@ feeding the result to `git send-email`.
containing the branch description, shortlog and the overall diffstat. You can
fill in a description in the file before sending it out.
+--cover-letter-format=<format-spec>::
+ Specify the format in which to generate the commit list of the
+ patch series. This option is available if the user wants to use
+ an alternative to the default `shortlog` format. The accepted
+ values for format-spec are "shortlog" or a format string
+ prefixed with `log:`.
+ e.g. `log: %s (%an)`
+ This option is relevant only if a cover letter is generated.
+
--encode-email-headers::
--no-encode-email-headers::
Encode email headers that have non-ASCII characters with
@@ -452,6 +462,7 @@ with configuration variables.
signOff = true
outputDirectory = <directory>
coverLetter = auto
+ commitListFormat = shortlog
coverFromDescription = auto
------------
--
2.53.0.5.g1a4ba6dc33
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH v6 5/5] docs: add usage for the cover-letter fmt feature
2026-03-06 22:58 ` [PATCH v6 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
@ 2026-03-06 23:18 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-06 23:18 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> Document the new "--cover-letter-format" option in format-patch and its
> related config variable "format.commitListFormat".
>
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> Documentation/config/format.adoc | 6 ++++++
> Documentation/git-format-patch.adoc | 11 +++++++++++
> 2 files changed, 17 insertions(+)
>
> diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc
> index ab0710e86a..151e574052 100644
> --- a/Documentation/config/format.adoc
> +++ b/Documentation/config/format.adoc
> @@ -101,6 +101,12 @@ format.coverLetter::
> generate a cover-letter only when there's more than one patch.
> Default is false.
>
> +format.commitListFormat::
> + A format string that specifies how to generate the commit list of a
> + cover-letter when format-patch is invoked. This config is coupled with
> + the `--cover-letter-format` format-patch option command and they both
> + accept the same values. Default is `shortlog`.
Let's not call "configuration variable" a "config". When a variable
gives the default value for a command line option, we usually do not
say "coupled with".
When the `--cover-letter-format` option is not given,
`format-patch` uses the value of this variable to decide how to
format the title of each commit. Default to `shortlog`.
perhaps?
> diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
> index 9a7807ca71..42d2633860 100644
> --- a/Documentation/git-format-patch.adoc
> +++ b/Documentation/git-format-patch.adoc
> @@ -24,6 +24,7 @@ SYNOPSIS
> [(--reroll-count|-v) <n>]
> [--to=<email>] [--cc=<email>]
> [--[no-]cover-letter] [--quiet]
> + [--cover-letter-format=<format-spec>]
> [--[no-]encode-email-headers]
> [--no-notes | --notes[=<ref>]]
> [--interdiff=<previous>]
> @@ -321,6 +322,15 @@ feeding the result to `git send-email`.
> containing the branch description, shortlog and the overall diffstat. You can
> fill in a description in the file before sending it out.
>
> +--cover-letter-format=<format-spec>::
> + Specify the format in which to generate the commit list of the
> + patch series. This option is available if the user wants to use
> + an alternative to the default `shortlog` format. The accepted
> + values for format-spec are "shortlog" or a format string
> + prefixed with `log:`.
> + e.g. `log: %s (%an)`
> + This option is relevant only if a cover letter is generated.
Somewhere in this paragraph, it must mention that format.commitListFormat
gives the default value when this command line option is not given.
Everything else in the changes since the previous iteration,
including the parameter list to generate_commit_list_cover() that
shows the level of attention to details, looked great to me.
Thanks.
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v7 0/5] format-patch: add cover-letter-format option
2026-03-06 22:58 ` [PATCH v6 " Mirko Faina
` (4 preceding siblings ...)
2026-03-06 22:58 ` [PATCH v6 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
@ 2026-03-06 23:34 ` Mirko Faina
2026-03-06 23:34 ` [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
` (5 more replies)
5 siblings, 6 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 23:34 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
I've applied the changes you suggested.
Thank you for the review
[1/5] pretty.c: add %(count) and %(total) placeholders (Mirko Faina)
[2/5] format-patch: move cover letter summary generation (Mirko Faina)
[3/5] format-patch: add ability to use alt cover format (Mirko Faina)
[4/5] format-patch: add commitListFormat config (Mirko Faina)
[5/5] docs: add usage for the cover-letter fmt feature (Mirko Faina)
Documentation/config/format.adoc | 5 ++
Documentation/git-format-patch.adoc | 13 ++++
builtin/log.c | 91 +++++++++++++++++++++----
pretty.c | 15 +++++
t/t4014-format-patch.sh | 101 ++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
6 files changed, 212 insertions(+), 14 deletions(-)
--
2.53.0.5.gbe7197aef5
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders
2026-03-06 23:34 ` [PATCH v7 0/5] format-patch: add cover-letter-format option Mirko Faina
@ 2026-03-06 23:34 ` Mirko Faina
2026-03-10 14:32 ` Phillip Wood
2026-03-06 23:34 ` [PATCH v7 2/5] format-patch: move cover letter summary generation Mirko Faina
` (4 subsequent siblings)
5 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 23:34 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
In many commands we can customize the output through the "--format" or
the "--pretty" options. This patch adds two new placeholders used mainly
when there's a range of commits that we want to show.
Currently these two placeholders are not usable as they're coupled with
the rev_info->nr and rev_info->total fields, fields that are used only
by the format-patch numbered email subjects.
Teach repo_format_commit_message() the %(count) and %(total)
placeholders.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
pretty.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/pretty.c b/pretty.c
index e0646bbc5d..e29bb8b877 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
if (!commit->object.parsed)
parse_object(the_repository, &commit->object.oid);
+ if (starts_with(placeholder, "(count)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total),
+ c->pretty_ctx->rev->nr);
+ return 7;
+ }
+
+ if (starts_with(placeholder, "(total)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("this format specifier can't be used with this command"));
+ strbuf_addf(sb, "%d", c->pretty_ctx->rev->total);
+ return 7;
+ }
+
switch (placeholder[0]) {
case 'H': /* commit hash */
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
--
2.53.0.5.gbe7197aef5
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v7 2/5] format-patch: move cover letter summary generation
2026-03-06 23:34 ` [PATCH v7 0/5] format-patch: add cover-letter-format option Mirko Faina
2026-03-06 23:34 ` [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
@ 2026-03-06 23:34 ` Mirko Faina
2026-03-06 23:34 ` [PATCH v7 3/5] format-patch: add ability to use alt cover format Mirko Faina
` (3 subsequent siblings)
5 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 23:34 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
As of now format-patch allows generation of a template cover letter for
patch series through "--cover-letter".
Move shortlog summary code generation to its own function. This is done
in preparation to other patches where we enable the user to format the
commit list using thier own format string.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 32 ++++++++++++++++++++------------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 5c9a8ef363..0d12272031 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_shortlog_cover_letter(struct shortlog *log,
+ struct rev_info *rev,
+ struct commit **list,
+ int nr)
+{
+ shortlog_init(log);
+ log->wrap_lines = 1;
+ log->wrap = MAIL_DEFAULT_WRAP;
+ log->in1 = 2;
+ log->in2 = 4;
+ log->file = rev->diffopt.file;
+ log->groups = SHORTLOG_GROUP_AUTHOR;
+ shortlog_finish_setup(log);
+ for (int i = 0; i < nr; i++)
+ shortlog_add_commit(log, list[i]);
+
+ shortlog_output(log);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
-
- shortlog_output(&log);
+ generate_shortlog_cover_letter(&log, rev, list, nr);
/* We can only do diffstat with a unique reference point */
if (origin)
--
2.53.0.5.gbe7197aef5
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v7 3/5] format-patch: add ability to use alt cover format
2026-03-06 23:34 ` [PATCH v7 0/5] format-patch: add cover-letter-format option Mirko Faina
2026-03-06 23:34 ` [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-03-06 23:34 ` [PATCH v7 2/5] format-patch: move cover letter summary generation Mirko Faina
@ 2026-03-06 23:34 ` Mirko Faina
2026-03-10 14:33 ` Phillip Wood
2026-03-06 23:34 ` [PATCH v7 4/5] format-patch: add commitListFormat config Mirko Faina
` (2 subsequent siblings)
5 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 23:34 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
Give format-patch the ability to specify an alternative format spec
through the "--cover-letter-format" option. This option either takes
"shortlog", which is the current format, or a format spec prefixed with
"log:".
Example:
git format-patch --cover-letter \
--cover-letter-format="log:[%(count)/%(total)] %s (%an)" HEAD~3
[1/3] this is a commit summary (Mirko Faina)
[2/3] this is another commit summary (Mirko Faina)
...
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 40 +++++++++++++++++++++++++++++++---
t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
3 files changed, 86 insertions(+), 3 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 0d12272031..95e5d9755f 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,
shortlog_output(log);
}
+static void generate_commit_list_cover(FILE *cover_file, const char *format,
+ struct commit **list, int n)
+{
+ struct strbuf commit_line = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ struct rev_info rev = REV_INFO_INIT;
+
+ strbuf_init(&commit_line, 0);
+ rev.total = n;
+ ctx.rev = &rev;
+ for (int i = n - 1; i >= 0; i--) {
+ rev.nr = n - i;
+ repo_format_commit_message(the_repository, list[i], format,
+ &commit_line, &ctx);
+ fprintf(cover_file, "%s\n", commit_line.buf);
+ strbuf_reset(&commit_line);
+ }
+ fprintf(cover_file, "\n");
+
+ strbuf_release(&commit_line);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
const char *description_file,
const char *branch_name,
int quiet,
- const struct format_config *cfg)
+ const struct format_config *cfg,
+ const char *format)
{
const char *committer;
struct shortlog log;
@@ -1396,7 +1419,12 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- generate_shortlog_cover_letter(&log, rev, list, nr);
+ if (skip_prefix(format, "log:", &format))
+ generate_commit_list_cover(rev->diffopt.file, format, list, nr);
+ else if (!strcmp(format, "shortlog"))
+ generate_shortlog_cover_letter(&log, rev, list, nr);
+ else
+ die(_("'%s' is not a valid format string"), format);
/* We can only do diffstat with a unique reference point */
if (origin)
@@ -1914,6 +1942,7 @@ int cmd_format_patch(int argc,
int just_numbers = 0;
int ignore_if_in_upstream = 0;
int cover_letter = -1;
+ const char *cover_letter_fmt = NULL;
int boundary_count = 0;
int no_binary_diff = 0;
int zero_commit = 0;
@@ -1960,6 +1989,8 @@ int cmd_format_patch(int argc,
N_("print patches to standard out")),
OPT_BOOL(0, "cover-letter", &cover_letter,
N_("generate a cover letter")),
+ OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
+ N_("format spec used for the commit list in the cover letter")),
OPT_BOOL(0, "numbered-files", &just_numbers,
N_("use simple number sequence for output file names")),
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
@@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,
/* nothing to do */
goto done;
total = list.nr;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
@@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,
}
rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
+
if (cover_letter) {
if (cfg.thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, !!output_directory,
origin, list.nr, list.items,
- description_file, branch_name, quiet, &cfg);
+ description_file, branch_name, quiet, &cfg,
+ cover_letter_fmt);
print_bases(&bases, rev.diffopt.file);
print_signature(signature, rev.diffopt.file);
total++;
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 21d6d0cd9e..458da80721 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '
done
'
+test_expect_success 'cover letter with subject, author and count' '
+ rm -rf patches &&
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
+ grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expected_success 'cover letter with author and count' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="log:[%(count)/%(total)] %an" -o patches HEAD~1 &&
+ grep "^\[1/1\] A U Thor$" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter shortlog' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter --cover-letter-format=shortlog \
+ -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter no format' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 964e1f1569..4f760a7468 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '
test_completion "git send-email --cov" <<-\EOF &&
--cover-from-description=Z
--cover-letter Z
+ --cover-letter-format=Z
EOF
test_completion "git send-email --val" <<-\EOF &&
--validate Z
--
2.53.0.5.gbe7197aef5
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v7 4/5] format-patch: add commitListFormat config
2026-03-06 23:34 ` [PATCH v7 0/5] format-patch: add cover-letter-format option Mirko Faina
` (2 preceding siblings ...)
2026-03-06 23:34 ` [PATCH v7 3/5] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-03-06 23:34 ` Mirko Faina
2026-03-10 14:34 ` Phillip Wood
2026-03-06 23:34 ` [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
2026-03-12 16:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Mirko Faina
5 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 23:34 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Using "--cover-letter" we can tell format-patch to generate a cover
letter, in this cover letter there's a list of commits included in the
patch series and the format is specified by the "--cover-letter-format"
option. Would be useful if this format could be configured from the
config file instead of always needing to pass it from the command line.
Teach format-patch how to read the format spec for the cover letter from
the config files. The variable it should look for is called
format.commitListFormat.
Possible values:
- commitListFormat is set but no string is passed: it will default to
"[%(count)/%(total)] %s"
- if a string is passed: will use it as a format spec. Note that this
is either "shortlog" or a format spec prefixed by "log:"
e.g."log:%s (%an)"
- if commitListFormat is not set: it will default to the shortlog
format.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 21 ++++++++++++++++
t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 74 insertions(+)
diff --git a/builtin/log.c b/builtin/log.c
index 95e5d9755f..5fec0ddaf9 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -886,6 +886,7 @@ struct format_config {
char *signature;
char *signature_file;
enum cover_setting config_cover_letter;
+ char *fmt_cover_letter_commit_list;
char *config_output_directory;
enum cover_from_description cover_from_description_mode;
int show_notes;
@@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
string_list_clear(&cfg->extra_cc, 0);
strbuf_release(&cfg->sprefix);
free(cfg->fmt_patch_suffix);
+ free(cfg->fmt_cover_letter_commit_list);
}
static enum cover_from_description parse_cover_from_description(const char *arg)
@@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
return 0;
}
+ if (!strcmp(var, "format.commitlistformat")) {
+ struct strbuf tmp = STRBUF_INIT;
+ strbuf_init(&tmp, 0);
+ if (value)
+ strbuf_addstr(&tmp, value);
+ else
+ strbuf_addstr(&tmp, "log:[%(count)/%(total)] %s");
+
+ FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
+ git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
+ strbuf_release(&tmp);
+ return 0;
+ }
if (!strcmp(var, "format.outputdirectory")) {
FREE_AND_NULL(cfg->config_output_directory);
return git_config_string(&cfg->config_output_directory, var, value);
@@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,
goto done;
total = list.nr;
+ if (!cover_letter_fmt) {
+ cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
+ if (!cover_letter_fmt)
+ cover_letter_fmt = "shortlog";
+ }
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 458da80721..4891389a53 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '
test_line_count = 1 result
'
+test_expect_success 'cover letter config with count, subject and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "log:[%(count)/%(total)] %s (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config with count and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "log:[%(count)/%(total)] (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set but no format' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ printf "\tcommitlistformat" >> .git/config &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .*" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set to shortlog' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat shortlog &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter config commitlistformat not set' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ git config set format.coverletter true &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
--
2.53.0.5.gbe7197aef5
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature
2026-03-06 23:34 ` [PATCH v7 0/5] format-patch: add cover-letter-format option Mirko Faina
` (3 preceding siblings ...)
2026-03-06 23:34 ` [PATCH v7 4/5] format-patch: add commitListFormat config Mirko Faina
@ 2026-03-06 23:34 ` Mirko Faina
2026-03-10 9:51 ` Bert Wesarg
2026-03-10 14:34 ` Phillip Wood
2026-03-12 16:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Mirko Faina
5 siblings, 2 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-06 23:34 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King
Document the new "--cover-letter-format" option in format-patch and its
related configuration variable "format.commitListFormat".
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
Documentation/config/format.adoc | 5 +++++
Documentation/git-format-patch.adoc | 13 +++++++++++++
2 files changed, 18 insertions(+)
diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc
index ab0710e86a..ea5ec5df7a 100644
--- a/Documentation/config/format.adoc
+++ b/Documentation/config/format.adoc
@@ -101,6 +101,11 @@ format.coverLetter::
generate a cover-letter only when there's more than one patch.
Default is false.
+format.commitListFormat::
+ When the `--cover-letter-format` option is not given, `format-patch`
+ uses the value of this variable to decide how to format the title of
+ each commit. Default to `shortlog`.
+
format.outputDirectory::
Set a custom directory to store the resulting files instead of the
current working directory. All directory components will be created.
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index 9a7807ca71..668330a015 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -24,6 +24,7 @@ SYNOPSIS
[(--reroll-count|-v) <n>]
[--to=<email>] [--cc=<email>]
[--[no-]cover-letter] [--quiet]
+ [--cover-letter-format=<format-spec>]
[--[no-]encode-email-headers]
[--no-notes | --notes[=<ref>]]
[--interdiff=<previous>]
@@ -321,6 +322,17 @@ feeding the result to `git send-email`.
containing the branch description, shortlog and the overall diffstat. You can
fill in a description in the file before sending it out.
+--cover-letter-format=<format-spec>::
+ Specify the format in which to generate the commit list of the
+ patch series. This option is available if the user wants to use
+ an alternative to the default `shortlog` format. The accepted
+ values for format-spec are "shortlog" or a format string
+ prefixed with `log:`.
+ e.g. `log: %s (%an)`
+ If defined, defaults to the `format.commitListFormat` configuration
+ variable.
+ This option is relevant only if a cover letter is generated.
+
--encode-email-headers::
--no-encode-email-headers::
Encode email headers that have non-ASCII characters with
@@ -452,6 +464,7 @@ with configuration variables.
signOff = true
outputDirectory = <directory>
coverLetter = auto
+ commitListFormat = shortlog
coverFromDescription = auto
------------
--
2.53.0.5.gbe7197aef5
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature
2026-03-06 23:34 ` [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
@ 2026-03-10 9:51 ` Bert Wesarg
2026-03-10 14:34 ` Phillip Wood
1 sibling, 0 replies; 113+ messages in thread
From: Bert Wesarg @ 2026-03-10 9:51 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Junio C Hamano, Jeff King
On Sat, Mar 7, 2026 at 12:38 AM Mirko Faina <mroik@delayed.space> wrote:
>
> Document the new "--cover-letter-format" option in format-patch and its
> related configuration variable "format.commitListFormat".
>
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> Documentation/config/format.adoc | 5 +++++
> Documentation/git-format-patch.adoc | 13 +++++++++++++
> 2 files changed, 18 insertions(+)
>
> diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc
> index ab0710e86a..ea5ec5df7a 100644
> --- a/Documentation/config/format.adoc
> +++ b/Documentation/config/format.adoc
> @@ -101,6 +101,11 @@ format.coverLetter::
> generate a cover-letter only when there's more than one patch.
> Default is false.
>
> +format.commitListFormat::
> + When the `--cover-letter-format` option is not given, `format-patch`
> + uses the value of this variable to decide how to format the title of
> + each commit. Default to `shortlog`.
> +
"Defaults to `shortlog`."
or
"Default is `shortlog`."
4 to 1 for the former style in that file.
And it looks like that other one is right above. So maybe change this
too, so its always "Defaults to …"?
Bert
> format.outputDirectory::
> Set a custom directory to store the resulting files instead of the
> current working directory. All directory components will be created.
> diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
> index 9a7807ca71..668330a015 100644
> --- a/Documentation/git-format-patch.adoc
> +++ b/Documentation/git-format-patch.adoc
> @@ -24,6 +24,7 @@ SYNOPSIS
> [(--reroll-count|-v) <n>]
> [--to=<email>] [--cc=<email>]
> [--[no-]cover-letter] [--quiet]
> + [--cover-letter-format=<format-spec>]
> [--[no-]encode-email-headers]
> [--no-notes | --notes[=<ref>]]
> [--interdiff=<previous>]
> @@ -321,6 +322,17 @@ feeding the result to `git send-email`.
> containing the branch description, shortlog and the overall diffstat. You can
> fill in a description in the file before sending it out.
>
> +--cover-letter-format=<format-spec>::
> + Specify the format in which to generate the commit list of the
> + patch series. This option is available if the user wants to use
> + an alternative to the default `shortlog` format. The accepted
> + values for format-spec are "shortlog" or a format string
> + prefixed with `log:`.
> + e.g. `log: %s (%an)`
> + If defined, defaults to the `format.commitListFormat` configuration
> + variable.
> + This option is relevant only if a cover letter is generated.
> +
> --encode-email-headers::
> --no-encode-email-headers::
> Encode email headers that have non-ASCII characters with
> @@ -452,6 +464,7 @@ with configuration variables.
> signOff = true
> outputDirectory = <directory>
> coverLetter = auto
> + commitListFormat = shortlog
> coverFromDescription = auto
> ------------
>
> --
> 2.53.0.5.gbe7197aef5
>
>
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders
2026-03-06 23:34 ` [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
@ 2026-03-10 14:32 ` Phillip Wood
2026-03-10 20:55 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Phillip Wood @ 2026-03-10 14:32 UTC (permalink / raw)
To: Mirko Faina, git; +Cc: Junio C Hamano, Jeff King
On 06/03/2026 23:34, Mirko Faina wrote:
> diff --git a/pretty.c b/pretty.c
> index e0646bbc5d..e29bb8b877 100644
> --- a/pretty.c
> +++ b/pretty.c
> @@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
> if (!commit->object.parsed)
> parse_object(the_repository, &commit->object.oid);
>
> + if (starts_with(placeholder, "(count)")) {
> + if (!c->pretty_ctx->rev)
> + die(_("this format specifier can't be used with this command"));
From the user's point of view it would be more helpful if this message
told them which format specifier isn't supported
die(_("%s is not supported by this command"), "%(count)");
Normally we do not add code that cannot be exercised so you may want to
squash this change into a later patch that adds support and
documentation for the new specifiers.
Thanks
Phillip
> + strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total),
> + c->pretty_ctx->rev->nr);
> + return 7;
> + }
> +
> + if (starts_with(placeholder, "(total)")) {
> + if (!c->pretty_ctx->rev)
> + die(_("this format specifier can't be used with this command"));
> + strbuf_addf(sb, "%d", c->pretty_ctx->rev->total);
> + return 7;
> + }
> +
> switch (placeholder[0]) {
> case 'H': /* commit hash */
> strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 3/5] format-patch: add ability to use alt cover format
2026-03-06 23:34 ` [PATCH v7 3/5] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-03-10 14:33 ` Phillip Wood
2026-03-10 21:05 ` Mroik
0 siblings, 1 reply; 113+ messages in thread
From: Phillip Wood @ 2026-03-10 14:33 UTC (permalink / raw)
To: Mirko Faina, git; +Cc: Junio C Hamano, Jeff King
On 06/03/2026 23:34, Mirko Faina wrote:
> Often when sending patch series there's a need to clarify to the
> reviewer what's the purpose of said series, since it might be difficult
> to understand it from reading the commits messages one by one.
>
> "git format-patch" provides the useful "--cover-letter" flag to declare
> if we want it to generate a template for us to use. By default it will
> generate a "git shortlog" of the changes, which developers find less
> useful than they'd like, mainly because the shortlog groups commits by
> author, and gives no obvious chronological order.
I think we probably care more about topological order for format-patch.
In practice that's the same as chronological order by commit date, but
it does not necessarily match chronological order by author date.
> Give format-patch the ability to specify an alternative format spec
> through the "--cover-letter-format" option. This option either takes
> "shortlog", which is the current format, or a format spec prefixed with
> "log:".
That sounds like a nice improvement over using the shortlog output.
However it is rather cumbersome to have to type "log:" each time. As
--cover-letter-format=shortlog is a nonsensical format I don't think we
need to require the "log:" prefix. --no-cover-letter-format should
behave like --cover-letter-format=shortlog.
> Example:
> git format-patch --cover-letter \
> --cover-letter-format="log:[%(count)/%(total)] %s (%an)" HEAD~3
>
> [1/3] this is a commit summary (Mirko Faina)
> [2/3] this is another commit summary (Mirko Faina)
> ...
>
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> builtin/log.c | 40 +++++++++++++++++++++++++++++++---
> t/t4014-format-patch.sh | 48 +++++++++++++++++++++++++++++++++++++++++
> t/t9902-completion.sh | 1 +
> 3 files changed, 86 insertions(+), 3 deletions(-)
>
> diff --git a/builtin/log.c b/builtin/log.c
> index 0d12272031..95e5d9755f 100644
> --- a/builtin/log.c
> +++ b/builtin/log.c
> @@ -1343,13 +1343,36 @@ static void generate_shortlog_cover_letter(struct shortlog *log,
> shortlog_output(log);
> }
>
> +static void generate_commit_list_cover(FILE *cover_file, const char *format,
> + struct commit **list, int n)
> +{
> + struct strbuf commit_line = STRBUF_INIT;
> + struct pretty_print_context ctx = {0};
> + struct rev_info rev = REV_INFO_INIT;
> +
> + strbuf_init(&commit_line, 0);
This is unnecessary as commit_line is initialized in the declaration above.
> + rev.total = n;
> + ctx.rev = &rev;
> + for (int i = n - 1; i >= 0; i--) {
> + rev.nr = n - i;
> + repo_format_commit_message(the_repository, list[i], format,
> + &commit_line, &ctx);
This loop is a bit confusing, I wonder if it would be simpler to count up
for (int i = 1; i <= n; i++) {
rev.nr = i
repo_format_commit_message(the_repository, list[n - i], ...);
The shortlog sets some wrapping and indent options, do we want to do
something similar here?
> @@ -2297,6 +2328,7 @@ int cmd_format_patch(int argc,
> /* nothing to do */
> goto done;
> total = list.nr;
> +
Please don't mix unrelated whitespace changes in with your changes.
> if (cover_letter == -1) {
> if (cfg.config_cover_letter == COVER_AUTO)
> cover_letter = (total > 1);
> @@ -2383,12 +2415,14 @@ int cmd_format_patch(int argc,
> }
> rev.numbered_files = just_numbers;
> rev.patch_suffix = fmt_patch_suffix;
> +
The same here
> +test_expect_success 'cover letter with subject, author and count' '
> + rm -rf patches &&
> + test_when_finished "git reset --hard HEAD~1" &&
> + test_when_finished "rm -rf patches result test_file" &&
> + touch test_file &&
> + git add test_file &&
> + git commit -m "This is a subject" &&
> + git format-patch --cover-letter \
> + --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
> + grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result &&
using test_grep here would make it easier to debug test failures.
> + test_line_count = 1 result
> +'
> +
> +test_expected_success 'cover letter with author and count' '
> + test_when_finished "git reset --hard HEAD~1" &&
> + test_when_finished "rm -rf patches result test_file" &&
> + touch test_file &&
> + git add test_file &&
> + git commit -m "This is a subject" &&
> + git format-patch --cover-letter \
> + --cover-letter-format="log:[%(count)/%(total)] %an" -o patches HEAD~1 &&
> + grep "^\[1/1\] A U Thor$" patches/0000-cover-letter.patch >result &&
I'm not clear what new coverage this test adds
> + test_line_count = 1 result
> +'
> +
> +test_expect_success 'cover letter shortlog' '
> + test_when_finished "git reset --hard HEAD~1" &&
> + test_when_finished "rm -rf patches result test_file" &&
> + touch test_file &&
> + git add test_file &&
> + git commit -m "This is a subject" &&
> + git format-patch --cover-letter --cover-letter-format=shortlog \
> + -o patches HEAD~1 &&
> + sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
This just checks that the author name appears in the coverletter, not
that the patches are formatted with shortlog.
> + test_line_count = 1 result
> +'
> +
> +test_expect_success 'cover letter no format' '
> + test_when_finished "git reset --hard HEAD~1" &&
> + test_when_finished "rm -rf patches result test_file" &&
> + touch test_file &&
> + git add test_file &&
> + git commit -m "This is a subject" &&
> + git format-patch --cover-letter -o patches HEAD~1 &&
> + sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
Don't we already have test coverage for the case where
--cover-letter-format isn't given? Testing that --no-cover-letter-format
works as expected would be useful.
I think this is a useful improvement to the cover letter generated by
"git format-patch"
Thanks
Phillip
> + test_line_count = 1 result
> +'
> +
> test_expect_success 'reroll count' '
> rm -fr patches &&
> git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
> index 964e1f1569..4f760a7468 100755
> --- a/t/t9902-completion.sh
> +++ b/t/t9902-completion.sh
> @@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '
> test_completion "git send-email --cov" <<-\EOF &&
> --cover-from-description=Z
> --cover-letter Z
> + --cover-letter-format=Z
> EOF
> test_completion "git send-email --val" <<-\EOF &&
> --validate Z
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 4/5] format-patch: add commitListFormat config
2026-03-06 23:34 ` [PATCH v7 4/5] format-patch: add commitListFormat config Mirko Faina
@ 2026-03-10 14:34 ` Phillip Wood
2026-03-10 16:45 ` Junio C Hamano
2026-03-10 21:19 ` Mirko Faina
0 siblings, 2 replies; 113+ messages in thread
From: Phillip Wood @ 2026-03-10 14:34 UTC (permalink / raw)
To: Mirko Faina, git; +Cc: Junio C Hamano, Jeff King
On 06/03/2026 23:34, Mirko Faina wrote:
> Using "--cover-letter" we can tell format-patch to generate a cover
> letter, in this cover letter there's a list of commits included in the
> patch series and the format is specified by the "--cover-letter-format"
> option. Would be useful if this format could be configured from the
> config file instead of always needing to pass it from the command line.
>
> Teach format-patch how to read the format spec for the cover letter from
> the config files. The variable it should look for is called
> format.commitListFormat.
>
> Possible values:
> - commitListFormat is set but no string is passed: it will default to
> "[%(count)/%(total)] %s"
It is unusual for an empty config value to mean something different from
it not being set. The reason for this is that it allows
git -c config.key some-command
to act as though config.key was not set.
Perhaps we should use the value "default" to generate a default format.
It would be nice to support a default format on the commandline as well.
> - if a string is passed: will use it as a format spec. Note that this
> is either "shortlog" or a format spec prefixed by "log:"
> e.g."log:%s (%an)"
Having the config value behave like --cover-letter-format=<value> is
sensible
> - if commitListFormat is not set: it will default to the shortlog
> format.
makes sense
Thanks
Phillip
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> builtin/log.c | 21 ++++++++++++++++
> t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 74 insertions(+)
>
> diff --git a/builtin/log.c b/builtin/log.c
> index 95e5d9755f..5fec0ddaf9 100644
> --- a/builtin/log.c
> +++ b/builtin/log.c
> @@ -886,6 +886,7 @@ struct format_config {
> char *signature;
> char *signature_file;
> enum cover_setting config_cover_letter;
> + char *fmt_cover_letter_commit_list;
> char *config_output_directory;
> enum cover_from_description cover_from_description_mode;
> int show_notes;
> @@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
> string_list_clear(&cfg->extra_cc, 0);
> strbuf_release(&cfg->sprefix);
> free(cfg->fmt_patch_suffix);
> + free(cfg->fmt_cover_letter_commit_list);
> }
>
> static enum cover_from_description parse_cover_from_description(const char *arg)
> @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
> cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
> return 0;
> }
> + if (!strcmp(var, "format.commitlistformat")) {
> + struct strbuf tmp = STRBUF_INIT;
> + strbuf_init(&tmp, 0);
> + if (value)
> + strbuf_addstr(&tmp, value);
> + else
> + strbuf_addstr(&tmp, "log:[%(count)/%(total)] %s");
> +
> + FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
> + git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
> + strbuf_release(&tmp);
> + return 0;
> + }
> if (!strcmp(var, "format.outputdirectory")) {
> FREE_AND_NULL(cfg->config_output_directory);
> return git_config_string(&cfg->config_output_directory, var, value);
> @@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,
> goto done;
> total = list.nr;
>
> + if (!cover_letter_fmt) {
> + cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
> + if (!cover_letter_fmt)
> + cover_letter_fmt = "shortlog";
> + }
> +
> if (cover_letter == -1) {
> if (cfg.config_cover_letter == COVER_AUTO)
> cover_letter = (total > 1);
> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
> index 458da80721..4891389a53 100755
> --- a/t/t4014-format-patch.sh
> +++ b/t/t4014-format-patch.sh
> @@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '
> test_line_count = 1 result
> '
>
> +test_expect_success 'cover letter config with count, subject and author' '
> + test_when_finished "rm -rf patches result" &&
> + test_when_finished "git config unset format.coverletter" &&
> + test_when_finished "git config unset format.commitlistformat" &&
> + git config set format.coverletter true &&
> + git config set format.commitlistformat "log:[%(count)/%(total)] %s (%an)" &&
> + git format-patch -o patches HEAD~2 &&
> + grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
> + test_line_count = 2 result
> +'
> +
> +test_expect_success 'cover letter config with count and author' '
> + test_when_finished "rm -rf patches result" &&
> + test_when_finished "git config unset format.coverletter" &&
> + test_when_finished "git config unset format.commitlistformat" &&
> + git config set format.coverletter true &&
> + git config set format.commitlistformat "log:[%(count)/%(total)] (%an)" &&
> + git format-patch -o patches HEAD~2 &&
> + grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
> + test_line_count = 2 result
> +'
> +
> +test_expect_success 'cover letter config commitlistformat set but no format' '
> + test_when_finished "rm -rf patches result" &&
> + test_when_finished "git config unset format.coverletter" &&
> + test_when_finished "git config unset format.commitlistformat" &&
> + git config set format.coverletter true &&
> + printf "\tcommitlistformat" >> .git/config &&
> + git format-patch -o patches HEAD~2 &&
> + grep -E "^[[[:digit:]]+/[[:digit:]]+] .*" patches/0000-cover-letter.patch >result &&
> + test_line_count = 2 result
> +'
> +
> +test_expect_success 'cover letter config commitlistformat set to shortlog' '
> + test_when_finished "rm -rf patches result" &&
> + test_when_finished "git config unset format.coverletter" &&
> + test_when_finished "git config unset format.commitlistformat" &&
> + git config set format.coverletter true &&
> + git config set format.commitlistformat shortlog &&
> + git format-patch -o patches HEAD~2 &&
> + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
> + test_line_count = 1 result
> +'
> +
> +test_expect_success 'cover letter config commitlistformat not set' '
> + test_when_finished "rm -rf patches result" &&
> + test_when_finished "git config unset format.coverletter" &&
> + git config set format.coverletter true &&
> + git format-patch -o patches HEAD~2 &&
> + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
> + test_line_count = 1 result
> +'
> +
> test_expect_success 'reroll count' '
> rm -fr patches &&
> git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature
2026-03-06 23:34 ` [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
2026-03-10 9:51 ` Bert Wesarg
@ 2026-03-10 14:34 ` Phillip Wood
1 sibling, 0 replies; 113+ messages in thread
From: Phillip Wood @ 2026-03-10 14:34 UTC (permalink / raw)
To: Mirko Faina, git; +Cc: Junio C Hamano, Jeff King
On 06/03/2026 23:34, Mirko Faina wrote:
> Document the new "--cover-letter-format" option in format-patch and its
> related configuration variable "format.commitListFormat".
>
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> Documentation/config/format.adoc | 5 +++++
> Documentation/git-format-patch.adoc | 13 +++++++++++++
> 2 files changed, 18 insertions(+)
>
> diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc
> index ab0710e86a..ea5ec5df7a 100644
> --- a/Documentation/config/format.adoc
> +++ b/Documentation/config/format.adoc
> @@ -101,6 +101,11 @@ format.coverLetter::
> generate a cover-letter only when there's more than one patch.
> Default is false.
>
> +format.commitListFormat::
> + When the `--cover-letter-format` option is not given, `format-patch`
> + uses the value of this variable to decide how to format the title of
> + each commit. Default to `shortlog`.
Being able to set a default format like this is useful. It would be
helpful to document what the expected format is. This should be added to
patch 4 which adds support for the config variable.
> +
> format.outputDirectory::
> Set a custom directory to store the resulting files instead of the
> current working directory. All directory components will be created.
> diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
> index 9a7807ca71..668330a015 100644
> --- a/Documentation/git-format-patch.adoc
> +++ b/Documentation/git-format-patch.adoc
> @@ -24,6 +24,7 @@ SYNOPSIS
> [(--reroll-count|-v) <n>]
> [--to=<email>] [--cc=<email>]
> [--[no-]cover-letter] [--quiet]
> + [--cover-letter-format=<format-spec>]
> [--[no-]encode-email-headers]
> [--no-notes | --notes[=<ref>]]
> [--interdiff=<previous>]
> @@ -321,6 +322,17 @@ feeding the result to `git send-email`.
> containing the branch description, shortlog and the overall diffstat. You can
> fill in a description in the file before sending it out.
>
> +--cover-letter-format=<format-spec>::
> + Specify the format in which to generate the commit list of the
> + patch series. This option is available if the user wants to use
> + an alternative to the default `shortlog` format. The accepted
> + values for format-spec are "shortlog" or a format string
> + prefixed with `log:`.
> + e.g. `log: %s (%an)`
> + If defined, defaults to the `format.commitListFormat` configuration
> + variable.
> + This option is relevant only if a cover letter is generated.
This should be added in patch 3 which adds the --cover-letter-format option.
Thanks
Phillip
> --encode-email-headers::
> --no-encode-email-headers::
> Encode email headers that have non-ASCII characters with
> @@ -452,6 +464,7 @@ with configuration variables.
> signOff = true
> outputDirectory = <directory>
> coverLetter = auto
> + commitListFormat = shortlog
> coverFromDescription = auto
> ------------
>
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 4/5] format-patch: add commitListFormat config
2026-03-10 14:34 ` Phillip Wood
@ 2026-03-10 16:45 ` Junio C Hamano
2026-03-10 21:23 ` Mirko Faina
2026-03-11 10:32 ` Phillip Wood
2026-03-10 21:19 ` Mirko Faina
1 sibling, 2 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-10 16:45 UTC (permalink / raw)
To: Phillip Wood; +Cc: Mirko Faina, git, Jeff King
Phillip Wood <phillip.wood123@gmail.com> writes:
>> Possible values:
>> - commitListFormat is set but no string is passed: it will default to
>> "[%(count)/%(total)] %s"
>
> It is unusual for an empty config value to mean something different from
> it not being set. The reason for this is that it allows
>
> git -c config.key some-command
>
> to act as though config.key was not set.
That syntax is the same as setting config.key=true; disabling the
feature triggered by config.key is quite counter-intuitive, isn't
it?
We are by default using "shortlog", but use of this configuration
variable is a sign that the user wants to use a more modern custom
format that is not the traditional "shortlog". It would be quite
natural to invoke the modern default by setting it to "true" (i.e.,
"I want to enable the new format.commitlistformat feature, but I am
not saying which format, and the "log:[%(count)/%(total)] %s" format
is used).
Perhaps "format.commitlistformat = false" should disable the modern
format and fall back to "shortlog", setting it to true (including
the use of "valueless true" syntax) should enable it and use the
modern default "log:[%c/%t] %s" format, and non-bool text should be
used as a custom specification ("shortlog", or "log:<format>")?
I.e.
switch (git_parse_maybe_bool_text(value)) {
case 0: /* false */
fmt_cover_letter_commit_list = "shortlog";
break;
case 1: /* true - use the modern default format */
fmt_cover_letter_commit_list = "log:[%c/%t] %s";
break;
default:
fmt_cover_letter_commit_list = value;
break;
}
Hmm?
> It would be nice to support a default format on the commandline as well.
>
>> - if a string is passed: will use it as a format spec. Note that this
>> is either "shortlog" or a format spec prefixed by "log:"
>> e.g."log:%s (%an)"
>
> Having the config value behave like --cover-letter-format=<value> is
> sensible
>
>> - if commitListFormat is not set: it will default to the shortlog
>> format.
>
> makes sense
>
> Thanks
>
> Phillip
>
>> Signed-off-by: Mirko Faina <mroik@delayed.space>
>> ---
>> builtin/log.c | 21 ++++++++++++++++
>> t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 74 insertions(+)
>>
>> diff --git a/builtin/log.c b/builtin/log.c
>> index 95e5d9755f..5fec0ddaf9 100644
>> --- a/builtin/log.c
>> +++ b/builtin/log.c
>> @@ -886,6 +886,7 @@ struct format_config {
>> char *signature;
>> char *signature_file;
>> enum cover_setting config_cover_letter;
>> + char *fmt_cover_letter_commit_list;
>> char *config_output_directory;
>> enum cover_from_description cover_from_description_mode;
>> int show_notes;
>> @@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
>> string_list_clear(&cfg->extra_cc, 0);
>> strbuf_release(&cfg->sprefix);
>> free(cfg->fmt_patch_suffix);
>> + free(cfg->fmt_cover_letter_commit_list);
>> }
>>
>> static enum cover_from_description parse_cover_from_description(const char *arg)
>> @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
>> cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
>> return 0;
>> }
>> + if (!strcmp(var, "format.commitlistformat")) {
>> + struct strbuf tmp = STRBUF_INIT;
>> + strbuf_init(&tmp, 0);
>> + if (value)
>> + strbuf_addstr(&tmp, value);
>> + else
>> + strbuf_addstr(&tmp, "log:[%(count)/%(total)] %s");
>> +
>> + FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
>> + git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
>
>
>
>> + strbuf_release(&tmp);
>> + return 0;
>> + }
>> if (!strcmp(var, "format.outputdirectory")) {
>> FREE_AND_NULL(cfg->config_output_directory);
>> return git_config_string(&cfg->config_output_directory, var, value);
>> @@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,
>> goto done;
>> total = list.nr;
>>
>> + if (!cover_letter_fmt) {
>> + cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
>> + if (!cover_letter_fmt)
>> + cover_letter_fmt = "shortlog";
>> + }
>> +
>> if (cover_letter == -1) {
>> if (cfg.config_cover_letter == COVER_AUTO)
>> cover_letter = (total > 1);
>> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
>> index 458da80721..4891389a53 100755
>> --- a/t/t4014-format-patch.sh
>> +++ b/t/t4014-format-patch.sh
>> @@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '
>> test_line_count = 1 result
>> '
>>
>> +test_expect_success 'cover letter config with count, subject and author' '
>> + test_when_finished "rm -rf patches result" &&
>> + test_when_finished "git config unset format.coverletter" &&
>> + test_when_finished "git config unset format.commitlistformat" &&
>> + git config set format.coverletter true &&
>> + git config set format.commitlistformat "log:[%(count)/%(total)] %s (%an)" &&
>> + git format-patch -o patches HEAD~2 &&
>> + grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
>> + test_line_count = 2 result
>> +'
>> +
>> +test_expect_success 'cover letter config with count and author' '
>> + test_when_finished "rm -rf patches result" &&
>> + test_when_finished "git config unset format.coverletter" &&
>> + test_when_finished "git config unset format.commitlistformat" &&
>> + git config set format.coverletter true &&
>> + git config set format.commitlistformat "log:[%(count)/%(total)] (%an)" &&
>> + git format-patch -o patches HEAD~2 &&
>> + grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
>> + test_line_count = 2 result
>> +'
>> +
>> +test_expect_success 'cover letter config commitlistformat set but no format' '
>> + test_when_finished "rm -rf patches result" &&
>> + test_when_finished "git config unset format.coverletter" &&
>> + test_when_finished "git config unset format.commitlistformat" &&
>> + git config set format.coverletter true &&
>> + printf "\tcommitlistformat" >> .git/config &&
>> + git format-patch -o patches HEAD~2 &&
>> + grep -E "^[[[:digit:]]+/[[:digit:]]+] .*" patches/0000-cover-letter.patch >result &&
>> + test_line_count = 2 result
>> +'
>> +
>> +test_expect_success 'cover letter config commitlistformat set to shortlog' '
>> + test_when_finished "rm -rf patches result" &&
>> + test_when_finished "git config unset format.coverletter" &&
>> + test_when_finished "git config unset format.commitlistformat" &&
>> + git config set format.coverletter true &&
>> + git config set format.commitlistformat shortlog &&
>> + git format-patch -o patches HEAD~2 &&
>> + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
>> + test_line_count = 1 result
>> +'
>> +
>> +test_expect_success 'cover letter config commitlistformat not set' '
>> + test_when_finished "rm -rf patches result" &&
>> + test_when_finished "git config unset format.coverletter" &&
>> + git config set format.coverletter true &&
>> + git format-patch -o patches HEAD~2 &&
>> + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
>> + test_line_count = 1 result
>> +'
>> +
>> test_expect_success 'reroll count' '
>> rm -fr patches &&
>> git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders
2026-03-10 14:32 ` Phillip Wood
@ 2026-03-10 20:55 ` Mirko Faina
0 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-10 20:55 UTC (permalink / raw)
To: phillip.wood; +Cc: git, Junio C Hamano, Jeff King, Mirko Faina
On Tue, Mar 10, 2026 at 02:32:54PM +0000, Phillip Wood wrote:
> From the user's point of view it would be more helpful if this message told
> them which format specifier isn't supported
>
> die(_("%s is not supported by this command"), "%(count)");
Indeed, will fix the message.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 3/5] format-patch: add ability to use alt cover format
2026-03-10 14:33 ` Phillip Wood
@ 2026-03-10 21:05 ` Mroik
0 siblings, 0 replies; 113+ messages in thread
From: Mroik @ 2026-03-10 21:05 UTC (permalink / raw)
To: phillip.wood; +Cc: git, Junio C Hamano, Jeff King
On Tue, Mar 10, 2026 at 02:33:46PM +0000, Phillip Wood wrote:
> > Give format-patch the ability to specify an alternative format spec
> > through the "--cover-letter-format" option. This option either takes
> > "shortlog", which is the current format, or a format spec prefixed with
> > "log:".
>
> That sounds like a nice improvement over using the shortlog output. However
> it is rather cumbersome to have to type "log:" each time. As
> --cover-letter-format=shortlog is a nonsensical format I don't think we need
> to require the "log:" prefix. --no-cover-letter-format should behave like
> --cover-letter-format=shortlog.
This was done to avoid having "anything that is not shortlog should be
in pretty-print format". Having it this way in the future we can check
for the prefix and see if the user indeed would like the pretty-print
format, otherwise check againts other formats available (so far only
shortlog). If "log:" is too cumbersome maybe we can drop it and check
instead for existing presets first, "shortlog, etc..." and then when
nothing is found interpret it as pp format. I like the explicit prefix
but I guess it is unlikely that a user will want to print "shortlog" on
every line.
Do we all agree on dropping the prefix?
> This is unnecessary as commit_line is initialized in the declaration above.
Will remove
> This loop is a bit confusing, I wonder if it would be simpler to count up
> for (int i = 1; i <= n; i++) {
> rev.nr = i
> repo_format_commit_message(the_repository, list[n - i], ...);
Will rewrite for readability
> > +test_expect_success 'cover letter with subject, author and count' '
> > + rm -rf patches &&
> > + test_when_finished "git reset --hard HEAD~1" &&
> > + test_when_finished "rm -rf patches result test_file" &&
> > + touch test_file &&
> > + git add test_file &&
> > + git commit -m "This is a subject" &&
> > + git format-patch --cover-letter \
> > + --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
> > + grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result &&
>
> using test_grep here would make it easier to debug test failures.
Will do
> > + test_line_count = 1 result
> > +'
> > +
> > +test_expect_success 'cover letter shortlog' '
> > + test_when_finished "git reset --hard HEAD~1" &&
> > + test_when_finished "rm -rf patches result test_file" &&
> > + touch test_file &&
> > + git add test_file &&
> > + git commit -m "This is a subject" &&
> > + git format-patch --cover-letter --cover-letter-format=shortlog \
> > + -o patches HEAD~1 &&
> > + sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
>
> This just checks that the author name appears in the coverletter, not that
> the patches are formatted with shortlog.
The name at the start of the line can appear only if the foramt is
shortlog, but if in the future it we do decide to support more prefix
that might not be true anymore. Will rewrite this test to be more
robust.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 4/5] format-patch: add commitListFormat config
2026-03-10 14:34 ` Phillip Wood
2026-03-10 16:45 ` Junio C Hamano
@ 2026-03-10 21:19 ` Mirko Faina
1 sibling, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-10 21:19 UTC (permalink / raw)
To: phillip.wood; +Cc: git, Junio C Hamano, Jeff King
On Tue, Mar 10, 2026 at 02:34:01PM +0000, Phillip Wood wrote:
> It is unusual for an empty config value to mean something different from it
> not being set. The reason for this is that it allows
>
> git -c config.key some-command
>
> to act as though config.key was not set.
>
> Perhaps we should use the value "default" to generate a default format. It
> would be nice to support a default format on the commandline as well.
If nothing is passed it defaults to "shortlog", it wouldn't make sense
to call it "default". But I agree, accepting a value as set but without
passing anything to it can create some issues when interacting from cli.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 4/5] format-patch: add commitListFormat config
2026-03-10 16:45 ` Junio C Hamano
@ 2026-03-10 21:23 ` Mirko Faina
2026-03-11 10:38 ` Phillip Wood
2026-03-11 10:32 ` Phillip Wood
1 sibling, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-10 21:23 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Phillip Wood, git, Jeff King, Mirko Faina
On Tue, Mar 10, 2026 at 09:45:57AM -0700, Junio C Hamano wrote:
> That syntax is the same as setting config.key=true; disabling the
> feature triggered by config.key is quite counter-intuitive, isn't
> it?
>
> We are by default using "shortlog", but use of this configuration
> variable is a sign that the user wants to use a more modern custom
> format that is not the traditional "shortlog". It would be quite
> natural to invoke the modern default by setting it to "true" (i.e.,
> "I want to enable the new format.commitlistformat feature, but I am
> not saying which format, and the "log:[%(count)/%(total)] %s" format
> is used).
>
> Perhaps "format.commitlistformat = false" should disable the modern
> format and fall back to "shortlog", setting it to true (including
> the use of "valueless true" syntax) should enable it and use the
> modern default "log:[%c/%t] %s" format, and non-bool text should be
> used as a custom specification ("shortlog", or "log:<format>")?
>
> I.e.
>
> switch (git_parse_maybe_bool_text(value)) {
> case 0: /* false */
> fmt_cover_letter_commit_list = "shortlog";
> break;
> case 1: /* true - use the modern default format */
> fmt_cover_letter_commit_list = "log:[%c/%t] %s";
> break;
> default:
> fmt_cover_letter_commit_list = value;
> break;
> }
>
> Hmm?
Mmh, what if instead we defined a prefix format just like shortlog?
Maybe call it something like "numbered" or something similar (not too
good with coming up with names).
I dislike the idea of having an option be multiple types. Should bool or
string, not both.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v6 3/5] format-patch: add ability to use alt cover format
2026-03-06 22:58 ` [PATCH v6 3/5] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-03-10 22:14 ` Junio C Hamano
2026-03-10 22:32 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-03-10 22:14 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King
Mirko Faina <mroik@delayed.space> writes:
> +static void generate_commit_list_cover(FILE *cover_file, const char *format,
> + struct commit **list, int n)
> +{
> + struct strbuf commit_line = STRBUF_INIT;
> + struct pretty_print_context ctx = {0};
> + struct rev_info rev = REV_INFO_INIT;
> +
> + strbuf_init(&commit_line, 0);
We don't need this, when the struct is already initialized at the
definition a few lines above, do we?
> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
> index 21d6d0cd9e..458da80721 100755
> --- a/t/t4014-format-patch.sh
> +++ b/t/t4014-format-patch.sh
> @@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '
> done
> '
>
> +test_expect_success 'cover letter with subject, author and count' '
> + rm -rf patches &&
> + test_when_finished "git reset --hard HEAD~1" &&
> + test_when_finished "rm -rf patches result test_file" &&
> + touch test_file &&
> + git add test_file &&
> + git commit -m "This is a subject" &&
> + git format-patch --cover-letter \
> + --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
> + grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result &&
> + test_line_count = 1 result
> +'
> +
> +test_expected_success 'cover letter with author and count' '
Has this test been run successfully? It is unsual that only one
among several you are adding has this typo.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v6 3/5] format-patch: add ability to use alt cover format
2026-03-10 22:14 ` Junio C Hamano
@ 2026-03-10 22:32 ` Mirko Faina
0 siblings, 0 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-10 22:32 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Mirko Faina
On Tue, Mar 10, 2026 at 03:14:40PM -0700, Junio C Hamano wrote:
> > diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
> > index 21d6d0cd9e..458da80721 100755
> > --- a/t/t4014-format-patch.sh
> > +++ b/t/t4014-format-patch.sh
> > @@ -380,6 +380,54 @@ test_expect_success 'filename limit applies only to basename' '
> > done
> > '
> >
> > +test_expect_success 'cover letter with subject, author and count' '
> > + rm -rf patches &&
> > + test_when_finished "git reset --hard HEAD~1" &&
> > + test_when_finished "rm -rf patches result test_file" &&
> > + touch test_file &&
> > + git add test_file &&
> > + git commit -m "This is a subject" &&
> > + git format-patch --cover-letter \
> > + --cover-letter-format="log:[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
> > + grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch >result &&
> > + test_line_count = 1 result
> > +'
> > +
> > +test_expected_success 'cover letter with author and count' '
>
> Has this test been run successfully? It is unsual that only one
> among several you are adding has this typo.
Sorry, didn't notice. I saw no red tests and assumed it was workig when
it didn't even run.
Will fix
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 4/5] format-patch: add commitListFormat config
2026-03-10 16:45 ` Junio C Hamano
2026-03-10 21:23 ` Mirko Faina
@ 2026-03-11 10:32 ` Phillip Wood
2026-03-11 17:18 ` Junio C Hamano
1 sibling, 1 reply; 113+ messages in thread
From: Phillip Wood @ 2026-03-11 10:32 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Mirko Faina, git, Jeff King
On 10/03/2026 16:45, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
>>> Possible values:
>>> - commitListFormat is set but no string is passed: it will default to
>>> "[%(count)/%(total)] %s"
>>
>> It is unusual for an empty config value to mean something different from
>> it not being set. The reason for this is that it allows
>>
>> git -c config.key some-command
>>
>> to act as though config.key was not set.
>
> That syntax is the same as setting config.key=true; disabling the
> feature triggered by config.key is quite counter-intuitive, isn't
> it?
I'd forgotten about the boolean case, I was thinking about an empty or
missing value clearing multi-valued keys which is quite common I think.
Anyway we seem to agree that we don't want to use a missing value to
signal a default here. The suggestion below using true and false seems
quite reasonable to me.
Thanks
Phillip
> We are by default using "shortlog", but use of this configuration
> variable is a sign that the user wants to use a more modern custom
> format that is not the traditional "shortlog". It would be quite
> natural to invoke the modern default by setting it to "true" (i.e.,
> "I want to enable the new format.commitlistformat feature, but I am
> not saying which format, and the "log:[%(count)/%(total)] %s" format
> is used).
>
> Perhaps "format.commitlistformat = false" should disable the modern
> format and fall back to "shortlog", setting it to true (including
> the use of "valueless true" syntax) should enable it and use the
> modern default "log:[%c/%t] %s" format, and non-bool text should be
> used as a custom specification ("shortlog", or "log:<format>")?
>
> I.e.
>
> switch (git_parse_maybe_bool_text(value)) {
> case 0: /* false */
> fmt_cover_letter_commit_list = "shortlog";
> break;
> case 1: /* true - use the modern default format */
> fmt_cover_letter_commit_list = "log:[%c/%t] %s";
> break;
> default:
> fmt_cover_letter_commit_list = value;
> break;
> }
>
> Hmm?
>
>
>> It would be nice to support a default format on the commandline as well.
>
>
>>
>>> - if a string is passed: will use it as a format spec. Note that this
>>> is either "shortlog" or a format spec prefixed by "log:"
>>> e.g."log:%s (%an)"
>>
>> Having the config value behave like --cover-letter-format=<value> is
>> sensible
>>
>>> - if commitListFormat is not set: it will default to the shortlog
>>> format.
>>
>> makes sense
>>
>> Thanks
>>
>> Phillip
>>
>>> Signed-off-by: Mirko Faina <mroik@delayed.space>
>>> ---
>>> builtin/log.c | 21 ++++++++++++++++
>>> t/t4014-format-patch.sh | 53 +++++++++++++++++++++++++++++++++++++++++
>>> 2 files changed, 74 insertions(+)
>>>
>>> diff --git a/builtin/log.c b/builtin/log.c
>>> index 95e5d9755f..5fec0ddaf9 100644
>>> --- a/builtin/log.c
>>> +++ b/builtin/log.c
>>> @@ -886,6 +886,7 @@ struct format_config {
>>> char *signature;
>>> char *signature_file;
>>> enum cover_setting config_cover_letter;
>>> + char *fmt_cover_letter_commit_list;
>>> char *config_output_directory;
>>> enum cover_from_description cover_from_description_mode;
>>> int show_notes;
>>> @@ -930,6 +931,7 @@ static void format_config_release(struct format_config *cfg)
>>> string_list_clear(&cfg->extra_cc, 0);
>>> strbuf_release(&cfg->sprefix);
>>> free(cfg->fmt_patch_suffix);
>>> + free(cfg->fmt_cover_letter_commit_list);
>>> }
>>>
>>> static enum cover_from_description parse_cover_from_description(const char *arg)
>>> @@ -1052,6 +1054,19 @@ static int git_format_config(const char *var, const char *value,
>>> cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
>>> return 0;
>>> }
>>> + if (!strcmp(var, "format.commitlistformat")) {
>>> + struct strbuf tmp = STRBUF_INIT;
>>> + strbuf_init(&tmp, 0);
>>> + if (value)
>>> + strbuf_addstr(&tmp, value);
>>> + else
>>> + strbuf_addstr(&tmp, "log:[%(count)/%(total)] %s");
>>> +
>>> + FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
>>> + git_config_string(&cfg->fmt_cover_letter_commit_list, var, tmp.buf);
>>
>>
>>
>>> + strbuf_release(&tmp);
>>> + return 0;
>>> + }
>>> if (!strcmp(var, "format.outputdirectory")) {
>>> FREE_AND_NULL(cfg->config_output_directory);
>>> return git_config_string(&cfg->config_output_directory, var, value);
>>> @@ -2329,6 +2344,12 @@ int cmd_format_patch(int argc,
>>> goto done;
>>> total = list.nr;
>>>
>>> + if (!cover_letter_fmt) {
>>> + cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
>>> + if (!cover_letter_fmt)
>>> + cover_letter_fmt = "shortlog";
>>> + }
>>> +
>>> if (cover_letter == -1) {
>>> if (cfg.config_cover_letter == COVER_AUTO)
>>> cover_letter = (total > 1);
>>> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
>>> index 458da80721..4891389a53 100755
>>> --- a/t/t4014-format-patch.sh
>>> +++ b/t/t4014-format-patch.sh
>>> @@ -428,6 +428,59 @@ test_expect_success 'cover letter no format' '
>>> test_line_count = 1 result
>>> '
>>>
>>> +test_expect_success 'cover letter config with count, subject and author' '
>>> + test_when_finished "rm -rf patches result" &&
>>> + test_when_finished "git config unset format.coverletter" &&
>>> + test_when_finished "git config unset format.commitlistformat" &&
>>> + git config set format.coverletter true &&
>>> + git config set format.commitlistformat "log:[%(count)/%(total)] %s (%an)" &&
>>> + git format-patch -o patches HEAD~2 &&
>>> + grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
>>> + test_line_count = 2 result
>>> +'
>>> +
>>> +test_expect_success 'cover letter config with count and author' '
>>> + test_when_finished "rm -rf patches result" &&
>>> + test_when_finished "git config unset format.coverletter" &&
>>> + test_when_finished "git config unset format.commitlistformat" &&
>>> + git config set format.coverletter true &&
>>> + git config set format.commitlistformat "log:[%(count)/%(total)] (%an)" &&
>>> + git format-patch -o patches HEAD~2 &&
>>> + grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
>>> + test_line_count = 2 result
>>> +'
>>> +
>>> +test_expect_success 'cover letter config commitlistformat set but no format' '
>>> + test_when_finished "rm -rf patches result" &&
>>> + test_when_finished "git config unset format.coverletter" &&
>>> + test_when_finished "git config unset format.commitlistformat" &&
>>> + git config set format.coverletter true &&
>>> + printf "\tcommitlistformat" >> .git/config &&
>>> + git format-patch -o patches HEAD~2 &&
>>> + grep -E "^[[[:digit:]]+/[[:digit:]]+] .*" patches/0000-cover-letter.patch >result &&
>>> + test_line_count = 2 result
>>> +'
>>> +
>>> +test_expect_success 'cover letter config commitlistformat set to shortlog' '
>>> + test_when_finished "rm -rf patches result" &&
>>> + test_when_finished "git config unset format.coverletter" &&
>>> + test_when_finished "git config unset format.commitlistformat" &&
>>> + git config set format.coverletter true &&
>>> + git config set format.commitlistformat shortlog &&
>>> + git format-patch -o patches HEAD~2 &&
>>> + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
>>> + test_line_count = 1 result
>>> +'
>>> +
>>> +test_expect_success 'cover letter config commitlistformat not set' '
>>> + test_when_finished "rm -rf patches result" &&
>>> + test_when_finished "git config unset format.coverletter" &&
>>> + git config set format.coverletter true &&
>>> + git format-patch -o patches HEAD~2 &&
>>> + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
>>> + test_line_count = 1 result
>>> +'
>>> +
>>> test_expect_success 'reroll count' '
>>> rm -fr patches &&
>>> git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 4/5] format-patch: add commitListFormat config
2026-03-10 21:23 ` Mirko Faina
@ 2026-03-11 10:38 ` Phillip Wood
2026-03-11 17:13 ` Junio C Hamano
0 siblings, 1 reply; 113+ messages in thread
From: Phillip Wood @ 2026-03-11 10:38 UTC (permalink / raw)
To: Mirko Faina, Junio C Hamano; +Cc: git, Jeff King
On 10/03/2026 21:23, Mirko Faina wrote:
> On Tue, Mar 10, 2026 at 09:45:57AM -0700, Junio C Hamano wrote:
>> That syntax is the same as setting config.key=true; disabling the
>> feature triggered by config.key is quite counter-intuitive, isn't
>> it?
>>
>> We are by default using "shortlog", but use of this configuration
>> variable is a sign that the user wants to use a more modern custom
>> format that is not the traditional "shortlog". It would be quite
>> natural to invoke the modern default by setting it to "true" (i.e.,
>> "I want to enable the new format.commitlistformat feature, but I am
>> not saying which format, and the "log:[%(count)/%(total)] %s" format
>> is used).
>>
>> Perhaps "format.commitlistformat = false" should disable the modern
>> format and fall back to "shortlog", setting it to true (including
>> the use of "valueless true" syntax) should enable it and use the
>> modern default "log:[%c/%t] %s" format, and non-bool text should be
>> used as a custom specification ("shortlog", or "log:<format>")?
>>
>> I.e.
>>
>> switch (git_parse_maybe_bool_text(value)) {
>> case 0: /* false */
>> fmt_cover_letter_commit_list = "shortlog";
>> break;
>> case 1: /* true - use the modern default format */
>> fmt_cover_letter_commit_list = "log:[%c/%t] %s";
>> break;
>> default:
>> fmt_cover_letter_commit_list = value;
>> break;
>> }
>>
>> Hmm?
>
> Mmh, what if instead we defined a prefix format just like shortlog?
> Maybe call it something like "numbered" or something similar (not too
> good with coming up with names).
>
> I dislike the idea of having an option be multiple types. Should bool or
> string, not both.
I don't mind either way if we can come up with some sensible names
instead of "true" and "false". For the "false" case above we could just
use "shortlog", "numbered" sounds petty good for the "true" case above
as well.
Thanks
Phillip
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 4/5] format-patch: add commitListFormat config
2026-03-11 10:38 ` Phillip Wood
@ 2026-03-11 17:13 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-11 17:13 UTC (permalink / raw)
To: Phillip Wood; +Cc: Mirko Faina, git, Jeff King
Phillip Wood <phillip.wood123@gmail.com> writes:
>>> Perhaps "format.commitlistformat = false" should disable the modern
>>> format and fall back to "shortlog", setting it to true (including
>>> the use of "valueless true" syntax) should enable it and use the
>>> modern default "log:[%c/%t] %s" format, and non-bool text should be
>>> used as a custom specification ("shortlog", or "log:<format>")?
>>>
>>> I.e.
>>>
>>> switch (git_parse_maybe_bool_text(value)) {
>>> case 0: /* false */
>>> fmt_cover_letter_commit_list = "shortlog";
>>> break;
>>> case 1: /* true - use the modern default format */
>>> fmt_cover_letter_commit_list = "log:[%c/%t] %s";
>>> break;
>>> default:
>>> fmt_cover_letter_commit_list = value;
>>> break;
>>> }
>>>
>>> Hmm?
>>
>> Mmh, what if instead we defined a prefix format just like shortlog?
>> Maybe call it something like "numbered" or something similar (not too
>> good with coming up with names).
>>
>> I dislike the idea of having an option be multiple types. Should bool or
>> string, not both.
>
> I don't mind either way if we can come up with some sensible names
> instead of "true" and "false".
I do not mind a pair of synonym if you insist, but I disagree with
your assessment on true and false being not sensible. When you view
the feature as "use the modern commit list format" (specified either
with a command line option, or a configuration variable), "yes, I do
want to use that modern one, not the historical shortlog format" and
"no, don't bother. I like the old one just fine" are both natural
answers to the question "do you want to use the modern one?". Of
course, "let's use the modern one, and as it lets me give a custom
format string, please use this one" would be how the user feeds a
value that is not boolean to the feature.
So in short, I view this very similar to other "extended bool" (or
"bool or even more customization") option/configuration, and "true"
and "false", while they may not be so descriptive, are also sensible
choices in that context.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v7 4/5] format-patch: add commitListFormat config
2026-03-11 10:32 ` Phillip Wood
@ 2026-03-11 17:18 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-11 17:18 UTC (permalink / raw)
To: Phillip Wood; +Cc: Mirko Faina, git, Jeff King
Phillip Wood <phillip.wood123@gmail.com> writes:
> On 10/03/2026 16:45, Junio C Hamano wrote:
>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>
>>>> Possible values:
>>>> - commitListFormat is set but no string is passed: it will default to
>>>> "[%(count)/%(total)] %s"
>>>
>>> It is unusual for an empty config value to mean something different from
>>> it not being set. The reason for this is that it allows
>>>
>>> git -c config.key some-command
>>>
>>> to act as though config.key was not set.
>>
>> That syntax is the same as setting config.key=true; disabling the
>> feature triggered by config.key is quite counter-intuitive, isn't
>> it?
>
> I'd forgotten about the boolean case, I was thinking about an empty or
> missing value clearing multi-valued keys which is quite common I think.
Yes, "git -c config.list= -c config.list=one -c config.list=two cmd"
would defeat list-valued config.list defined in /etc/gitconfig and
~/.gitconfig and then use a list with only "one" and "two" on it.
Configuration variables like push.pushOption, merge.suppressDest,
remote.<name>.url, etc. all use this convention.
The form without '=' is a valueless true that I do not think is used
for such "clear the list", though.
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v8 0/4] format-patch: add cover-letter-format option
2026-03-06 23:34 ` [PATCH v7 0/5] format-patch: add cover-letter-format option Mirko Faina
` (4 preceding siblings ...)
2026-03-06 23:34 ` [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
@ 2026-03-12 16:20 ` Mirko Faina
2026-03-12 16:20 ` [PATCH v8 1/4] format-patch: move cover letter summary generation Mirko Faina
` (4 more replies)
5 siblings, 5 replies; 113+ messages in thread
From: Mirko Faina @ 2026-03-12 16:20 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King, Phillip Wood, Bert Wesarg
Squashed the documentation changes to their relevant patch and added a
new format preset "chronological".
Thank you for the reviews
[1/4] format-patch: move cover letter summary generation (Mirko Faina)
[2/4] format-patch: add ability to use alt cover format (Mirko Faina)
[3/4] format-patch: add "chronological" format for cover (Mirko Faina)
[4/4] format-patch: add commitListFormat config (Mirko Faina)
Documentation/config/format.adoc | 5 ++
Documentation/git-format-patch.adoc | 12 +++
builtin/log.c | 82 ++++++++++++++----
pretty.c | 15 ++++
t/t4014-format-patch.sh | 126 ++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
6 files changed, 227 insertions(+), 14 deletions(-)
--
2.53.0.904.g2727be2e99
^ permalink raw reply [flat|nested] 113+ messages in thread
* [PATCH v8 1/4] format-patch: move cover letter summary generation
2026-03-12 16:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Mirko Faina
@ 2026-03-12 16:20 ` Mirko Faina
2026-03-12 16:28 ` Junio C Hamano
2026-03-12 16:20 ` [PATCH v8 2/4] format-patch: add ability to use alt cover format Mirko Faina
` (3 subsequent siblings)
4 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-12 16:20 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King, Phillip Wood, Bert Wesarg
As of now format-patch allows generation of a template cover letter for
patch series through "--cover-letter".
Move shortlog summary code generation to its own function. This is done
in preparation to other patches where we enable the user to format the
commit list using thier own format string.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
builtin/log.c | 32 ++++++++++++++++++++------------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/builtin/log.c b/builtin/log.c
index 5c9a8ef363..0d12272031 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
}
}
+static void generate_shortlog_cover_letter(struct shortlog *log,
+ struct rev_info *rev,
+ struct commit **list,
+ int nr)
+{
+ shortlog_init(log);
+ log->wrap_lines = 1;
+ log->wrap = MAIL_DEFAULT_WRAP;
+ log->in1 = 2;
+ log->in2 = 4;
+ log->file = rev->diffopt.file;
+ log->groups = SHORTLOG_GROUP_AUTHOR;
+ shortlog_finish_setup(log);
+ for (int i = 0; i < nr; i++)
+ shortlog_add_commit(log, list[i]);
+
+ shortlog_output(log);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
@@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- shortlog_init(&log);
- log.wrap_lines = 1;
- log.wrap = MAIL_DEFAULT_WRAP;
- log.in1 = 2;
- log.in2 = 4;
- log.file = rev->diffopt.file;
- log.groups = SHORTLOG_GROUP_AUTHOR;
- shortlog_finish_setup(&log);
- for (i = 0; i < nr; i++)
- shortlog_add_commit(&log, list[i]);
-
- shortlog_output(&log);
+ generate_shortlog_cover_letter(&log, rev, list, nr);
/* We can only do diffstat with a unique reference point */
if (origin)
--
2.53.0.904.g2727be2e99
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v8 2/4] format-patch: add ability to use alt cover format
2026-03-12 16:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Mirko Faina
2026-03-12 16:20 ` [PATCH v8 1/4] format-patch: move cover letter summary generation Mirko Faina
@ 2026-03-12 16:20 ` Mirko Faina
2026-03-12 16:52 ` Junio C Hamano
2026-03-12 16:20 ` [PATCH v8 3/4] format-patch: add "chronological" format for cover Mirko Faina
` (2 subsequent siblings)
4 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-12 16:20 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King, Phillip Wood, Bert Wesarg
Often when sending patch series there's a need to clarify to the
reviewer what's the purpose of said series, since it might be difficult
to understand it from reading the commits messages one by one.
"git format-patch" provides the useful "--cover-letter" flag to declare
if we want it to generate a template for us to use. By default it will
generate a "git shortlog" of the changes, which developers find less
useful than they'd like, mainly because the shortlog groups commits by
author, and gives no obvious chronological order.
To better reference relevant patches in the coverletter this patch
introduces two new placeholders that can be used in the format spec:
%(count) and %(total). These are the chronological number of the patch
in the series and the total amount of patches in the series. Note that
the width of %(count) will always be the same witdh of %(total).
Give format-patch the ability to specify an alternative format spec
through the "--cover-letter-format" option. This option either takes
"shortlog", which is the current format, or a format spec prefixed with
"log:".
Example:
git format-patch --cover-letter \
--cover-letter-format="[%(count)/%(total)] %s (%an)" HEAD~3
[1/3] this is a commit summary (Mirko Faina)
[2/3] this is another commit summary (Mirko Faina)
...
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
Documentation/git-format-patch.adoc | 11 ++++++
builtin/log.c | 36 ++++++++++++++++--
pretty.c | 15 ++++++++
t/t4014-format-patch.sh | 58 +++++++++++++++++++++++++++++
t/t9902-completion.sh | 1 +
5 files changed, 118 insertions(+), 3 deletions(-)
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index 9a7807ca71..de36b87a85 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -24,6 +24,7 @@ SYNOPSIS
[(--reroll-count|-v) <n>]
[--to=<email>] [--cc=<email>]
[--[no-]cover-letter] [--quiet]
+ [--cover-letter-format=<format-spec>]
[--[no-]encode-email-headers]
[--no-notes | --notes[=<ref>]]
[--interdiff=<previous>]
@@ -321,6 +322,16 @@ feeding the result to `git send-email`.
containing the branch description, shortlog and the overall diffstat. You can
fill in a description in the file before sending it out.
+--cover-letter-format=<format-spec>::
+ Specify the format in which to generate the commit list of the patch
+ series. This option is available if the user wants to use an
+ alternative to the default `shortlog` format. The accepted values for
+ format-spec are "shortlog" or a format string.
+ e.g. `%s (%an)`
+ If defined, defaults to the `format.commitListFormat` configuration
+ variable.
+ This option is relevant only if a cover letter is generated.
+
--encode-email-headers::
--no-encode-email-headers::
Encode email headers that have non-ASCII characters with
diff --git a/builtin/log.c b/builtin/log.c
index 0d12272031..4f22012395 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1343,13 +1343,35 @@ static void generate_shortlog_cover_letter(struct shortlog *log,
shortlog_output(log);
}
+static void generate_commit_list_cover(FILE *cover_file, const char *format,
+ struct commit **list, int n)
+{
+ struct strbuf commit_line = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
+ struct rev_info rev = REV_INFO_INIT;
+
+ rev.total = n;
+ ctx.rev = &rev;
+ for (int i = 1; i <= n; i++) {
+ rev.nr = i;
+ repo_format_commit_message(the_repository, list[n - i], format,
+ &commit_line, &ctx);
+ fprintf(cover_file, "%s\n", commit_line.buf);
+ strbuf_reset(&commit_line);
+ }
+ fprintf(cover_file, "\n");
+
+ strbuf_release(&commit_line);
+}
+
static void make_cover_letter(struct rev_info *rev, int use_separate_file,
struct commit *origin,
int nr, struct commit **list,
const char *description_file,
const char *branch_name,
int quiet,
- const struct format_config *cfg)
+ const struct format_config *cfg,
+ const char *format)
{
const char *committer;
struct shortlog log;
@@ -1396,7 +1418,10 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
free(pp.after_subject);
strbuf_release(&sb);
- generate_shortlog_cover_letter(&log, rev, list, nr);
+ if (format == NULL || !strcmp(format, "shortlog"))
+ generate_shortlog_cover_letter(&log, rev, list, nr);
+ else
+ generate_commit_list_cover(rev->diffopt.file, format, list, nr);
/* We can only do diffstat with a unique reference point */
if (origin)
@@ -1914,6 +1939,7 @@ int cmd_format_patch(int argc,
int just_numbers = 0;
int ignore_if_in_upstream = 0;
int cover_letter = -1;
+ const char *cover_letter_fmt = NULL;
int boundary_count = 0;
int no_binary_diff = 0;
int zero_commit = 0;
@@ -1960,6 +1986,8 @@ int cmd_format_patch(int argc,
N_("print patches to standard out")),
OPT_BOOL(0, "cover-letter", &cover_letter,
N_("generate a cover letter")),
+ OPT_STRING(0, "cover-letter-format", &cover_letter_fmt, N_("format-spec"),
+ N_("format spec used for the commit list in the cover letter")),
OPT_BOOL(0, "numbered-files", &just_numbers,
N_("use simple number sequence for output file names")),
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
@@ -2297,6 +2325,7 @@ int cmd_format_patch(int argc,
/* nothing to do */
goto done;
total = list.nr;
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
@@ -2388,7 +2417,8 @@ int cmd_format_patch(int argc,
gen_message_id(&rev, "cover");
make_cover_letter(&rev, !!output_directory,
origin, list.nr, list.items,
- description_file, branch_name, quiet, &cfg);
+ description_file, branch_name, quiet, &cfg,
+ cover_letter_fmt);
print_bases(&bases, rev.diffopt.file);
print_signature(signature, rev.diffopt.file);
total++;
diff --git a/pretty.c b/pretty.c
index e0646bbc5d..8f3c434817 100644
--- a/pretty.c
+++ b/pretty.c
@@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
if (!commit->object.parsed)
parse_object(the_repository, &commit->object.oid);
+ if (starts_with(placeholder, "(count)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("%s is not supported by this command"), "%(count)");
+ strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total),
+ c->pretty_ctx->rev->nr);
+ return 7;
+ }
+
+ if (starts_with(placeholder, "(total)")) {
+ if (!c->pretty_ctx->rev)
+ die(_("%s is not supported by this command"), "%(total)");
+ strbuf_addf(sb, "%d", c->pretty_ctx->rev->total);
+ return 7;
+ }
+
switch (placeholder[0]) {
case 'H': /* commit hash */
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 21d6d0cd9e..631669c159 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -380,6 +380,64 @@ test_expect_success 'filename limit applies only to basename' '
done
'
+test_expect_success 'cover letter with subject, author and count' '
+ rm -rf patches &&
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
+ test_grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch
+'
+
+cat > expected <<EOF
+
+
+
+
+
+
+
+
+
+
+A U Thor (1):
+ This is a subject
+
+
+
+
+
+
+
+
+EOF
+
+test_expect_success 'cover letter shortlog' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches expected test_file result" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter --cover-letter-format=shortlog \
+ -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor (1):$\|^ This is a subject$/!s/.*//; /.*/p" patches/0000-cover-letter.patch >result &&
+ test_cmp expected result
+'
+
+test_expect_success 'cover letter no format' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --no-cover-letter-format --cover-letter -o patches HEAD~1 &&
+ sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 964e1f1569..4f760a7468 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '
test_completion "git send-email --cov" <<-\EOF &&
--cover-from-description=Z
--cover-letter Z
+ --cover-letter-format=Z
EOF
test_completion "git send-email --val" <<-\EOF &&
--validate Z
--
2.53.0.904.g2727be2e99
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v8 3/4] format-patch: add "chronological" format for cover
2026-03-12 16:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Mirko Faina
2026-03-12 16:20 ` [PATCH v8 1/4] format-patch: move cover letter summary generation Mirko Faina
2026-03-12 16:20 ` [PATCH v8 2/4] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-03-12 16:20 ` Mirko Faina
2026-03-12 16:55 ` Junio C Hamano
2026-03-12 16:20 ` [PATCH v8 4/4] format-patch: add commitListFormat config Mirko Faina
2026-03-12 17:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Junio C Hamano
4 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-12 16:20 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King, Phillip Wood, Bert Wesarg
Having a user craft a custom format spec everytime might be a hassle,
and having a common format that seems reasonable might make reviews
easier. This patch introduces a new simple preset called "chronological".
Teach make_cover_letter() the "chronological" format.
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
Documentation/git-format-patch.adoc | 2 +-
builtin/log.c | 3 +++
t/t4014-format-patch.sh | 11 +++++++++++
3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index de36b87a85..6364fd1f5a 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -326,7 +326,7 @@ feeding the result to `git send-email`.
Specify the format in which to generate the commit list of the patch
series. This option is available if the user wants to use an
alternative to the default `shortlog` format. The accepted values for
- format-spec are "shortlog" or a format string.
+ format-spec are "shortlog", "chronological" or a format string.
e.g. `%s (%an)`
If defined, defaults to the `format.commitListFormat` configuration
variable.
diff --git a/builtin/log.c b/builtin/log.c
index 4f22012395..12877a0ac2 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -50,6 +50,7 @@
#define MAIL_DEFAULT_WRAP 72
#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
#define FORMAT_PATCH_NAME_MAX_DEFAULT 64
+#define CHRONOLOGICAL "[%(count)/%(total)] %s"
static unsigned int force_in_body_from;
static int stdout_mboxrd;
@@ -1420,6 +1421,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
if (format == NULL || !strcmp(format, "shortlog"))
generate_shortlog_cover_letter(&log, rev, list, nr);
+ else if (!strcmp(format, "chronological"))
+ generate_commit_list_cover(rev->diffopt.file, CHRONOLOGICAL, list, nr);
else
generate_commit_list_cover(rev->diffopt.file, format, list, nr);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 631669c159..5ec527bce9 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -392,6 +392,17 @@ test_expect_success 'cover letter with subject, author and count' '
test_grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch
'
+test_expect_success 'cover letter chronological' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch --cover-letter \
+ --cover-letter-format="chronological" -o patches HEAD~1 &&
+ test_grep "^\[1/1\] This is a subject$" patches/0000-cover-letter.patch
+'
+
cat > expected <<EOF
--
2.53.0.904.g2727be2e99
^ permalink raw reply related [flat|nested] 113+ messages in thread
* [PATCH v8 4/4] format-patch: add commitListFormat config
2026-03-12 16:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Mirko Faina
` (2 preceding siblings ...)
2026-03-12 16:20 ` [PATCH v8 3/4] format-patch: add "chronological" format for cover Mirko Faina
@ 2026-03-12 16:20 ` Mirko Faina
2026-03-12 17:00 ` Junio C Hamano
2026-03-12 17:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Junio C Hamano
4 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-12 16:20 UTC (permalink / raw)
To: git; +Cc: Mirko Faina, Junio C Hamano, Jeff King, Phillip Wood, Bert Wesarg
Using "--cover-letter" we can tell format-patch to generate a cover
letter, in this cover letter there's a list of commits included in the
patch series and the format is specified by the "--cover-letter-format"
option. Would be useful if this format could be configured from the
config file instead of always needing to pass it from the command line.
Teach format-patch how to read the format spec for the cover letter from
the config files. The variable it should look for is called
format.commitListFormat, it accepts the same values as the option
"--cover-letter-format".
Signed-off-by: Mirko Faina <mroik@delayed.space>
---
Documentation/config/format.adoc | 5 +++
Documentation/git-format-patch.adoc | 1 +
builtin/log.c | 13 +++++++
t/t4014-format-patch.sh | 57 +++++++++++++++++++++++++++++
4 files changed, 76 insertions(+)
diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc
index ab0710e86a..ef1ed1d250 100644
--- a/Documentation/config/format.adoc
+++ b/Documentation/config/format.adoc
@@ -101,6 +101,11 @@ format.coverLetter::
generate a cover-letter only when there's more than one patch.
Default is false.
+format.commitListFormat::
+ When the `--cover-letter-format` option is not given, `format-patch`
+ uses the value of this variable to decide how to format the title of
+ each commit. Defaults to `shortlog`.
+
format.outputDirectory::
Set a custom directory to store the resulting files instead of the
current working directory. All directory components will be created.
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index 6364fd1f5a..88e580d1b3 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -463,6 +463,7 @@ with configuration variables.
signOff = true
outputDirectory = <directory>
coverLetter = auto
+ commitListFormat = shortlog
coverFromDescription = auto
------------
diff --git a/builtin/log.c b/builtin/log.c
index 12877a0ac2..e19e122bc9 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -887,6 +887,7 @@ struct format_config {
char *signature;
char *signature_file;
enum cover_setting config_cover_letter;
+ char *fmt_cover_letter_commit_list;
char *config_output_directory;
enum cover_from_description cover_from_description_mode;
int show_notes;
@@ -931,6 +932,7 @@ static void format_config_release(struct format_config *cfg)
string_list_clear(&cfg->extra_cc, 0);
strbuf_release(&cfg->sprefix);
free(cfg->fmt_patch_suffix);
+ free(cfg->fmt_cover_letter_commit_list);
}
static enum cover_from_description parse_cover_from_description(const char *arg)
@@ -1053,6 +1055,11 @@ static int git_format_config(const char *var, const char *value,
cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
return 0;
}
+ if (!strcmp(var, "format.commitlistformat")) {
+ FREE_AND_NULL(cfg->fmt_cover_letter_commit_list);
+ git_config_string(&cfg->fmt_cover_letter_commit_list, var, value);
+ return 0;
+ }
if (!strcmp(var, "format.outputdirectory")) {
FREE_AND_NULL(cfg->config_output_directory);
return git_config_string(&cfg->config_output_directory, var, value);
@@ -2329,6 +2336,12 @@ int cmd_format_patch(int argc,
goto done;
total = list.nr;
+ if (!cover_letter_fmt) {
+ cover_letter_fmt = cfg.fmt_cover_letter_commit_list;
+ if (!cover_letter_fmt)
+ cover_letter_fmt = "shortlog";
+ }
+
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 5ec527bce9..2091b5e9bb 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -449,6 +449,63 @@ test_expect_success 'cover letter no format' '
test_line_count = 1 result
'
+test_expect_success 'cover letter config with count, subject and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "[%(count)/%(total)] %s (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config with count and author' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat "[%(count)/%(total)] (%an)" &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 2 result
+'
+
+test_expect_success 'cover letter config commitlistformat set to shortlog' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat shortlog &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter config commitlistformat set to chronological' '
+ test_when_finished "git reset --hard HEAD~1" &&
+ test_when_finished "rm -rf patches result test_file" &&
+ test_when_finished "git config unset format.coverletter" &&
+ test_when_finished "git config unset format.commitlistformat" &&
+ git config set format.coverletter true &&
+ git config set format.commitlistformat chronological &&
+ touch test_file &&
+ git add test_file &&
+ git commit -m "This is a subject" &&
+ git format-patch -o patches HEAD~1 &&
+ grep -E "^[[[:digit:]]+/[[:digit:]]+]" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
+test_expect_success 'cover letter config commitlistformat not set' '
+ test_when_finished "rm -rf patches result" &&
+ test_when_finished "git config unset format.coverletter" &&
+ git config set format.coverletter true &&
+ git format-patch -o patches HEAD~2 &&
+ grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result &&
+ test_line_count = 1 result
+'
+
test_expect_success 'reroll count' '
rm -fr patches &&
git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
--
2.53.0.904.g2727be2e99
^ permalink raw reply related [flat|nested] 113+ messages in thread
* Re: [PATCH v8 1/4] format-patch: move cover letter summary generation
2026-03-12 16:20 ` [PATCH v8 1/4] format-patch: move cover letter summary generation Mirko Faina
@ 2026-03-12 16:28 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-12 16:28 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg
Mirko Faina <mroik@delayed.space> writes:
> As of now format-patch allows generation of a template cover letter for
> patch series through "--cover-letter".
"As of now" is redundant. Our convention is to describe the state
of the codebase _before_ your changes in the present tense to set
the stage for describing the problem.
> Move shortlog summary code generation to its own function. This is done
> in preparation to other patches where we enable the user to format the
> commit list using thier own format string.
Makes sense.
>
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> builtin/log.c | 32 ++++++++++++++++++++------------
> 1 file changed, 20 insertions(+), 12 deletions(-)
>
> diff --git a/builtin/log.c b/builtin/log.c
> index 5c9a8ef363..0d12272031 100644
> --- a/builtin/log.c
> +++ b/builtin/log.c
> @@ -1324,6 +1324,25 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev)
> }
> }
>
> +static void generate_shortlog_cover_letter(struct shortlog *log,
> + struct rev_info *rev,
> + struct commit **list,
> + int nr)
> +{
> + shortlog_init(log);
> + log->wrap_lines = 1;
> + log->wrap = MAIL_DEFAULT_WRAP;
> + log->in1 = 2;
> + log->in2 = 4;
> + log->file = rev->diffopt.file;
> + log->groups = SHORTLOG_GROUP_AUTHOR;
> + shortlog_finish_setup(log);
> + for (int i = 0; i < nr; i++)
> + shortlog_add_commit(log, list[i]);
> +
> + shortlog_output(log);
> +}
> +
> static void make_cover_letter(struct rev_info *rev, int use_separate_file,
> struct commit *origin,
> int nr, struct commit **list,
> @@ -1377,18 +1396,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
> free(pp.after_subject);
> strbuf_release(&sb);
>
> - shortlog_init(&log);
> - log.wrap_lines = 1;
> - log.wrap = MAIL_DEFAULT_WRAP;
> - log.in1 = 2;
> - log.in2 = 4;
> - log.file = rev->diffopt.file;
> - log.groups = SHORTLOG_GROUP_AUTHOR;
> - shortlog_finish_setup(&log);
> - for (i = 0; i < nr; i++)
> - shortlog_add_commit(&log, list[i]);
> -
> - shortlog_output(&log);
> + generate_shortlog_cover_letter(&log, rev, list, nr);
>
> /* We can only do diffstat with a unique reference point */
> if (origin)
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format
2026-03-12 16:20 ` [PATCH v8 2/4] format-patch: add ability to use alt cover format Mirko Faina
@ 2026-03-12 16:52 ` Junio C Hamano
2026-03-12 17:18 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-03-12 16:52 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg
Mirko Faina <mroik@delayed.space> writes:
> Often when sending patch series there's a need to clarify to the
> reviewer what's the purpose of said series, since it might be difficult
> to understand it from reading the commits messages one by one.
Yes, that is the whole point of having a cover letter.
> "git format-patch" provides the useful "--cover-letter" flag to declare
> if we want it to generate a template for us to use. By default it will
> generate a "git shortlog" of the changes, which developers find less
> useful than they'd like, mainly because the shortlog groups commits by
> author, and gives no obvious chronological order.
Very true.
> To better reference relevant patches in the coverletter this patch
> introduces two new placeholders that can be used in the format spec:
"this patch introduces" -> "introduce" (imperative).
> %(count) and %(total). These are the chronological number of the patch
> in the series and the total amount of patches in the series. Note that
> the width of %(count) will always be the same witdh of %(total).
"total amount" -> "total number"?
"the width of %(count)..." -> "%(count) will zero-padded to the left
to match the number of digits in %(total)".
But the paragraph "To better reference ..." up to this point should
probably be moved a bit low? The punchline "Give format-patch the
ability" to specify a custom format is the most important thing to
tell readers, and %(count)/%(total) is an implementation detail of
only one possible customized format.
> Give format-patch the ability to specify an alternative format spec
> through the "--cover-letter-format" option. This option either takes
> "shortlog", which is the current format, or a format spec prefixed with
> "log:".
>
> Example:
> git format-patch --cover-letter \
> --cover-letter-format="[%(count)/%(total)] %s (%an)" HEAD~3
>
> [1/3] this is a commit summary (Mirko Faina)
> [2/3] this is another commit summary (Mirko Faina)
> ...
The example does not use a format spec 'prefixed with "log:"',
though?
> +--cover-letter-format=<format-spec>::
> + Specify the format in which to generate the commit list of the patch
> + series. This option is available if the user wants to use an
> + alternative to the default `shortlog` format. The accepted values for
The second sentence reads funny. The option is available whether
the user wants to use it or not. I'd suggest dropping the sentence,
without which the paragraph reads just fine.
> + format-spec are "shortlog" or a format string.
> + e.g. `%s (%an)`
OK, so we are not requiring "log:"? This robs extensibility from
future developers to introduce something other than "shortlog", no?
If the version of Git in 'next' supports "longlog" and user gives
"--cover-letter-format=longlog" to their version that does not yet
support it, it would be mistaken by the version of the code here as
a "log:longlog" without any placeholder that shows a fixed string
"longlog" for each commit in the series? We'd rather want such an
input to cause failure, no?
> + If defined, defaults to the `format.commitListFormat` configuration
> + variable.
s/If defined, d\(efaults.*variable\)\./D\1, if defined./ would avoid
"if I define --cover-letter-format, why does it default to a
configuration? do you mean 'if not given'?"
So, "If defined," -> "If not given" would be another possible
improvement. I think I like it better, actually.
> + This option is relevant only if a cover letter is generated.
> +
Hmph, an alternative that may make it easier to use is to make the
command line option _imply_ "--cover-letter", so that the user does
not have to give two similar looking command line options.
git format-patch --cover-letter --cover-letter-format=...
Of course, the presence of the configuration variable should not
imply generation of a cover letter. I.e.
git -c format.commitListInCoverLetterFormat=shortlog \
format-patch -1 HEAD
should not imply --cover-letter.
> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
> index 21d6d0cd9e..631669c159 100755
> --- a/t/t4014-format-patch.sh
> +++ b/t/t4014-format-patch.sh
> @@ -380,6 +380,64 @@ test_expect_success 'filename limit applies only to basename' '
> done
> '
>
> +test_expect_success 'cover letter with subject, author and count' '
> + rm -rf patches &&
> + test_when_finished "git reset --hard HEAD~1" &&
> + test_when_finished "rm -rf patches result test_file" &&
> + touch test_file &&
> + git add test_file &&
> + git commit -m "This is a subject" &&
> + git format-patch --cover-letter \
> + --cover-letter-format="[%(count)/%(total)] %s (%an)" -o patches HEAD~1 &&
> + test_grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch
> +'
> +
> +cat > expected <<EOF
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +
> +A U Thor (1):
> + This is a subject
> +
> +
> +
> +
> +
> +
> +
> +
> +EOF
Many issues.
In modern tests (written within the past 10 years), we try not to
execute things outside text_expect_foo blocks. The golden output
to compare with is customary called 'expect' (not 'expected'). A
redirection operator ">", "<<", etc. has a single SP before but no
SP after it, when there is no parameter interpolation happens in a
HERE document, quote the "EOF" marker to show the intention of the
author of the test that no parameter interpolation is expected.
i.e.,
cat >expect <<\-EOF
...
EOF
and do so inside a set-up test_expect_success block.
Why do we have so many blank lines? Are the number of blank lines
significant? Such a hidden and hard to count dependency would hurt
maintainability of this test script.
> +test_expect_success 'cover letter shortlog' '
> + test_when_finished "git reset --hard HEAD~1" &&
> + test_when_finished "rm -rf patches expected test_file result" &&
> + touch test_file &&
> + git add test_file &&
> + git commit -m "This is a subject" &&
> + git format-patch --cover-letter --cover-letter-format=shortlog \
> + -o patches HEAD~1 &&
> + sed -n -e "/^A U Thor (1):$\|^ This is a subject$/!s/.*//; /.*/p" patches/0000-cover-letter.patch >result &&
> + test_cmp expected result
> +'
> +
> +test_expect_success 'cover letter no format' '
> + test_when_finished "git reset --hard HEAD~1" &&
> + test_when_finished "rm -rf patches result test_file" &&
> + touch test_file &&
> + git add test_file &&
> + git commit -m "This is a subject" &&
> + git format-patch --no-cover-letter-format --cover-letter -o patches HEAD~1 &&
> + sed -n -e "/^A U Thor/p;" patches/0000-cover-letter.patch >result &&
> + test_line_count = 1 result
> +'
> +
> test_expect_success 'reroll count' '
> rm -fr patches &&
> git format-patch -o patches --cover-letter --reroll-count 4 main..side >list &&
> diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
> index 964e1f1569..4f760a7468 100755
> --- a/t/t9902-completion.sh
> +++ b/t/t9902-completion.sh
> @@ -2774,6 +2774,7 @@ test_expect_success PERL 'send-email' '
> test_completion "git send-email --cov" <<-\EOF &&
> --cover-from-description=Z
> --cover-letter Z
> + --cover-letter-format=Z
> EOF
> test_completion "git send-email --val" <<-\EOF &&
> --validate Z
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 3/4] format-patch: add "chronological" format for cover
2026-03-12 16:20 ` [PATCH v8 3/4] format-patch: add "chronological" format for cover Mirko Faina
@ 2026-03-12 16:55 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-12 16:55 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg
Mirko Faina <mroik@delayed.space> writes:
> Having a user craft a custom format spec everytime might be a hassle,
> and having a common format that seems reasonable might make reviews
> easier. This patch introduces a new simple preset called "chronological".
>
> Teach make_cover_letter() the "chronological" format.
OK. The "a fixed 'log:' prefix is still needed for future proofing"
comment from my review on an earlier step of this series still
applies after this step.
> Signed-off-by: Mirko Faina <mroik@delayed.space>
> ---
> Documentation/git-format-patch.adoc | 2 +-
> builtin/log.c | 3 +++
> t/t4014-format-patch.sh | 11 +++++++++++
> 3 files changed, 15 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
> index de36b87a85..6364fd1f5a 100644
> --- a/Documentation/git-format-patch.adoc
> +++ b/Documentation/git-format-patch.adoc
> @@ -326,7 +326,7 @@ feeding the result to `git send-email`.
> Specify the format in which to generate the commit list of the patch
> series. This option is available if the user wants to use an
> alternative to the default `shortlog` format. The accepted values for
> - format-spec are "shortlog" or a format string.
> + format-spec are "shortlog", "chronological" or a format string.
> e.g. `%s (%an)`
> If defined, defaults to the `format.commitListFormat` configuration
> variable.
> diff --git a/builtin/log.c b/builtin/log.c
> index 4f22012395..12877a0ac2 100644
> --- a/builtin/log.c
> +++ b/builtin/log.c
> @@ -50,6 +50,7 @@
> #define MAIL_DEFAULT_WRAP 72
> #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
> #define FORMAT_PATCH_NAME_MAX_DEFAULT 64
> +#define CHRONOLOGICAL "[%(count)/%(total)] %s"
>
> static unsigned int force_in_body_from;
> static int stdout_mboxrd;
> @@ -1420,6 +1421,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
>
> if (format == NULL || !strcmp(format, "shortlog"))
> generate_shortlog_cover_letter(&log, rev, list, nr);
> + else if (!strcmp(format, "chronological"))
> + generate_commit_list_cover(rev->diffopt.file, CHRONOLOGICAL, list, nr);
> else
> generate_commit_list_cover(rev->diffopt.file, format, list, nr);
>
> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
> index 631669c159..5ec527bce9 100755
> --- a/t/t4014-format-patch.sh
> +++ b/t/t4014-format-patch.sh
> @@ -392,6 +392,17 @@ test_expect_success 'cover letter with subject, author and count' '
> test_grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch
> '
>
> +test_expect_success 'cover letter chronological' '
> + test_when_finished "git reset --hard HEAD~1" &&
> + test_when_finished "rm -rf patches result test_file" &&
> + touch test_file &&
> + git add test_file &&
> + git commit -m "This is a subject" &&
> + git format-patch --cover-letter \
> + --cover-letter-format="chronological" -o patches HEAD~1 &&
> + test_grep "^\[1/1\] This is a subject$" patches/0000-cover-letter.patch
> +'
> +
> cat > expected <<EOF
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 4/4] format-patch: add commitListFormat config
2026-03-12 16:20 ` [PATCH v8 4/4] format-patch: add commitListFormat config Mirko Faina
@ 2026-03-12 17:00 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-12 17:00 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg
Mirko Faina <mroik@delayed.space> writes:
> Using "--cover-letter" we can tell format-patch to generate a cover
> letter, in this cover letter there's a list of commits included in the
> patch series and the format is specified by the "--cover-letter-format"
> option. Would be useful if this format could be configured from the
> config file instead of always needing to pass it from the command line.
>
> Teach format-patch how to read the format spec for the cover letter from
> the config files. The variable it should look for is called
> format.commitListFormat, it accepts the same values as the option
> "--cover-letter-format".
If we are calling this commitListFormat, we would want to name the
command line option to match it more closely. Doing so would have
an added benefit that tab completion on the command line becomes
easier to use, because --cover-<TAB> would expand to --cover-letter
uniquely and we do not have to force the user choose between
"--cover-letter" and "--cover-letter-format=".
Perhaps "--commit-list-format=<format>" or something?
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format
2026-03-12 16:52 ` Junio C Hamano
@ 2026-03-12 17:18 ` Mirko Faina
2026-03-12 17:25 ` Junio C Hamano
0 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-12 17:18 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg, Mirko Faina
On Thu, Mar 12, 2026 at 09:52:29AM -0700, Junio C Hamano wrote:
> The example does not use a format spec 'prefixed with "log:"',
> though?
Yes, I fixed the example but forgot fix the paragraph when rewording.
Will fix
> The second sentence reads funny. The option is available whether
> the user wants to use it or not. I'd suggest dropping the sentence,
> without which the paragraph reads just fine.
Will do
> OK, so we are not requiring "log:"? This robs extensibility from
Like I said above, we won't require "log:" as per the discussion with
Phillip.
> This robs extensibility from
> future developers to introduce something other than "shortlog", no?
> If the version of Git in 'next' supports "longlog" and user gives
Not really, anyone can introduce new formats, it's just an additional if
statement.
> "--cover-letter-format=longlog" to their version that does not yet
> support it, it would be mistaken by the version of the code here as
> a "log:longlog" without any placeholder that shows a fixed string
> "longlog" for each commit in the series? We'd rather want such an
> input to cause failure, no?
Isn't that the same for any feature that is in next but not merged in
master yet? I wouldn't expect subcommands of history not yet merged in
master to work either if I'm using a version built from master. This is
an issue with the user and I don't think it's grounds for any issue.
> s/If defined, d\(efaults.*variable\)\./D\1, if defined./ would avoid
> "if I define --cover-letter-format, why does it default to a
> configuration? do you mean 'if not given'?"
>
> So, "If defined," -> "If not given" would be another possible
> improvement. I think I like it better, actually.
Will do
> Hmph, an alternative that may make it easier to use is to make the
> command line option _imply_ "--cover-letter", so that the user does
> not have to give two similar looking command line options.
>
> git format-patch --cover-letter --cover-letter-format=...
>
> Of course, the presence of the configuration variable should not
> imply generation of a cover letter. I.e.
>
> git -c format.commitListInCoverLetterFormat=shortlog \
> format-patch -1 HEAD
>
> should not imply --cover-letter.
Yes, that'd be a nice behaviour to have, it is indeed obvious that I
want a cover letter if I'm specifying a format from command line.
> Many issues.
>
> In modern tests (written within the past 10 years), we try not to
> execute things outside text_expect_foo blocks. The golden output
> to compare with is customary called 'expect' (not 'expected'). A
> redirection operator ">", "<<", etc. has a single SP before but no
> SP after it, when there is no parameter interpolation happens in a
> HERE document, quote the "EOF" marker to show the intention of the
> author of the test that no parameter interpolation is expected.
>
> i.e.,
>
> cat >expect <<\-EOF
> ...
> EOF
>
> and do so inside a set-up test_expect_success block.
Will fix
> Why do we have so many blank lines? Are the number of blank lines
> significant? Such a hidden and hard to count dependency would hurt
> maintainability of this test script.
In the beginning I thought about stripping the empty lines, but doing so
would not ensure that those two lines were next to each other. grep
matches line by line so I couldn't ensure that they'd be next to each
other like that.
If I were to strip the empty lines the test would be not better (imo)
than the previous series (where it was flagged down).
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 0/4] format-patch: add cover-letter-format option
2026-03-12 16:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Mirko Faina
` (3 preceding siblings ...)
2026-03-12 16:20 ` [PATCH v8 4/4] format-patch: add commitListFormat config Mirko Faina
@ 2026-03-12 17:20 ` Junio C Hamano
2026-03-12 17:45 ` Mirko Faina
4 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-03-12 17:20 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg
Mirko Faina <mroik@delayed.space> writes:
> Squashed the documentation changes to their relevant patch and added a
> new format preset "chronological".
>
> Thank you for the reviews
>
> [1/4] format-patch: move cover letter summary generation (Mirko Faina)
> [2/4] format-patch: add ability to use alt cover format (Mirko Faina)
> [3/4] format-patch: add "chronological" format for cover (Mirko Faina)
> [4/4] format-patch: add commitListFormat config (Mirko Faina)
By the way, we have merged the topic to 'next' on March 9th already,
so it is a bit awkward to see a wholesale replacement series.
We could revert the merge of the previous attempt out of 'next' and
queue the new iteration in 'seen', but I think the major changes in
this iteration are
(1) the "log:" prefix is omitted (which I think is a bad change
that we do not want),
(2) we no longer consider the option an extended boolean "use the
modern customized format [Yes/no/use this format]?" (which I
think is OK), and
(3) the default modern format has a name (which is OK, even though
"chronological" may be a mouthful to say).
and associated documentation and test updates, so at this point,
making incremental changes on top of what we already have in 'next'
may be more appropriate. The incremental changes are easier to
justify as well.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format
2026-03-12 17:18 ` Mirko Faina
@ 2026-03-12 17:25 ` Junio C Hamano
2026-03-12 17:27 ` Junio C Hamano
2026-03-13 10:38 ` Phillip Wood
0 siblings, 2 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-12 17:25 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg
Mirko Faina <mroik@delayed.space> writes:
>> future developers to introduce something other than "shortlog", no?
>> If the version of Git in 'next' supports "longlog" and user gives
>
> Not really, anyone can introduce new formats, it's just an additional if
> statement.
>
>> "--cover-letter-format=longlog" to their version that does not yet
>> support it, it would be mistaken by the version of the code here as
>> a "log:longlog" without any placeholder that shows a fixed string
>> "longlog" for each commit in the series? We'd rather want such an
>> input to cause failure, no?
>
> Isn't that the same for any feature that is in next but not merged in
> master yet? I wouldn't expect subcommands of history not yet merged in
> master to work either if I'm using a version built from master. This is
> an issue with the user and I don't think it's grounds for any issue.
Or misspelt "chrnological". What are we gaining by removing it,
which would rob safe extensibility (aka "future-proofing") and typo
detection?
It is a bad idea to drop "log:", period.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format
2026-03-12 17:25 ` Junio C Hamano
@ 2026-03-12 17:27 ` Junio C Hamano
2026-03-13 10:38 ` Phillip Wood
1 sibling, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-12 17:27 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg
Junio C Hamano <gitster@pobox.com> writes:
> Mirko Faina <mroik@delayed.space> writes:
>
>>> future developers to introduce something other than "shortlog", no?
>>> If the version of Git in 'next' supports "longlog" and user gives
>>
>> Not really, anyone can introduce new formats, it's just an additional if
>> statement.
>>
>>> "--cover-letter-format=longlog" to their version that does not yet
>>> support it, it would be mistaken by the version of the code here as
>>> a "log:longlog" without any placeholder that shows a fixed string
>>> "longlog" for each commit in the series? We'd rather want such an
>>> input to cause failure, no?
>>
>> Isn't that the same for any feature that is in next but not merged in
>> master yet? I wouldn't expect subcommands of history not yet merged in
>> master to work either if I'm using a version built from master. This is
>> an issue with the user and I don't think it's grounds for any issue.
You need to remember that some people use multiple machines with
different versions of Git installed. Yet to be enabled option
should be diagnosed as an error to be safe.
> Or misspelt "chrnological". What are we gaining by removing it,
> which would rob safe extensibility (aka "future-proofing") and typo
> detection?
>
> It is a bad idea to drop "log:", period.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 0/4] format-patch: add cover-letter-format option
2026-03-12 17:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Junio C Hamano
@ 2026-03-12 17:45 ` Mirko Faina
2026-03-12 18:12 ` Junio C Hamano
0 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-12 17:45 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg, Mirko Faina
On Thu, Mar 12, 2026 at 10:20:41AM -0700, Junio C Hamano wrote:
> By the way, we have merged the topic to 'next' on March 9th already,
> so it is a bit awkward to see a wholesale replacement series.
>
> We could revert the merge of the previous attempt out of 'next' and
> queue the new iteration in 'seen', but I think the major changes in
> this iteration are
>
> (1) the "log:" prefix is omitted (which I think is a bad change
> that we do not want),
>
> (2) we no longer consider the option an extended boolean "use the
> modern customized format [Yes/no/use this format]?" (which I
> think is OK), and
>
> (3) the default modern format has a name (which is OK, even though
> "chronological" may be a mouthful to say).
>
> and associated documentation and test updates, so at this point,
> making incremental changes on top of what we already have in 'next'
> may be more appropriate. The incremental changes are easier to
> justify as well.
(1) Very well, given that there are more cons than pros to dropping the
prefix, this change will be dropped and will no longer be discussed
going forward.
(3) Would an abbreviation like "chrono" be better? The originally
suggested "numbered" didn't seem to be very descriptive, that's why I
went with chronological.
Please disregard series v8, I will send a new series that builds on top
of v7. Regarding this, how should incremental changes be numbered in
their version? Do I keep going incrementally (v9 despite building on top
of another version), or should I use something like "v7.1, v7.2, v7.3"?
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 0/4] format-patch: add cover-letter-format option
2026-03-12 17:45 ` Mirko Faina
@ 2026-03-12 18:12 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-12 18:12 UTC (permalink / raw)
To: Mirko Faina; +Cc: git, Jeff King, Phillip Wood, Bert Wesarg
Mirko Faina <mroik@delayed.space> writes:
> ..., how should incremental changes be numbered in their version?
> Do I keep going incrementally (v9 despite building on top of
> another version), or should I use something like "v7.1, v7.2,
> v7.3"?
We often see a series that depends on topics that are in 'next' but
not yet in 'master', with its cover letter describing how exactly
the base of the series should be created. Patrick often does this,
and we can learn from cover letters of his topics. A good example
is here.
https://lore.kernel.org/git/20260305-b4-pks-odb-source-pluggable-v2-0-3290bfd1f444@pks.im/
And treating such a series starting from v1 (i.e., the first attempt
to make that other topic better) would be the easiest to understand.
The cover letter would start with something like
An earlier mf/format-patch-cover-letter-format series that is in
'next' but not in 'master' added THESE THINGS to the system.
Here is a follow-on series to further improve it by changing
SUCH and SUCH things.
before describing how its base was constructed.
As to the naming, I find "chrono" worse than "chronological",
because there are other possible ways to shorten the overly long
word, and picking one will force users to remember that choice.
Thanks.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format
2026-03-12 17:25 ` Junio C Hamano
2026-03-12 17:27 ` Junio C Hamano
@ 2026-03-13 10:38 ` Phillip Wood
2026-03-13 17:20 ` Junio C Hamano
1 sibling, 1 reply; 113+ messages in thread
From: Phillip Wood @ 2026-03-13 10:38 UTC (permalink / raw)
To: Junio C Hamano, Mirko Faina; +Cc: git, Jeff King, Bert Wesarg
On 12/03/2026 17:25, Junio C Hamano wrote:
> Mirko Faina <mroik@delayed.space> writes:
>
>>> future developers to introduce something other than "shortlog", no?
>>> If the version of Git in 'next' supports "longlog" and user gives
>>
>> Not really, anyone can introduce new formats, it's just an additional if
>> statement.
>>
>>> "--cover-letter-format=longlog" to their version that does not yet
>>> support it, it would be mistaken by the version of the code here as
>>> a "log:longlog" without any placeholder that shows a fixed string
>>> "longlog" for each commit in the series? We'd rather want such an
>>> input to cause failure, no?
>>
>> Isn't that the same for any feature that is in next but not merged in
>> master yet? I wouldn't expect subcommands of history not yet merged in
>> master to work either if I'm using a version built from master. This is
>> an issue with the user and I don't think it's grounds for any issue.
>
> Or misspelt "chrnological". What are we gaining by removing it,
> which would rob safe extensibility (aka "future-proofing") and typo
> detection?
>
> It is a bad idea to drop "log:", period.
It's a pain to have to prefix the format string with "log:" when we
don't require it anywhere else. If we want to error out on unknown fixed
strings then we could reject format strings that do not contain a '%'
and do not match any of the other fixed format names such as "shortlog".
It means you cannot specify a fixed string for the format but that would
seem to be a rather strange thing to do in the first place.
Thanks
Phillip
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format
2026-03-13 10:38 ` Phillip Wood
@ 2026-03-13 17:20 ` Junio C Hamano
2026-03-13 19:17 ` Mirko Faina
0 siblings, 1 reply; 113+ messages in thread
From: Junio C Hamano @ 2026-03-13 17:20 UTC (permalink / raw)
To: Phillip Wood; +Cc: Mirko Faina, git, Jeff King, Bert Wesarg
Phillip Wood <phillip.wood123@gmail.com> writes:
> It's a pain to have to prefix the format string with "log:" when we
> don't require it anywhere else.
I do not mind a sort of DWIM similar to "log --pretty=format:%s";
technically, "git log --prefix" requires the "format:" prefix when
using a custom format (i.e., not the canned "short", "fuller", etc.)
but we DWIM when the string appears to use %-interpolation.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format
2026-03-13 17:20 ` Junio C Hamano
@ 2026-03-13 19:17 ` Mirko Faina
2026-03-13 20:22 ` Junio C Hamano
0 siblings, 1 reply; 113+ messages in thread
From: Mirko Faina @ 2026-03-13 19:17 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Phillip Wood, git, Jeff King, Bert Wesarg, Mirko Faina
On Fri, Mar 13, 2026 at 10:20:48AM -0700, Junio C Hamano wrote:
> I do not mind a sort of DWIM similar to "log --pretty=format:%s";
> technically, "git log --prefix" requires the "format:" prefix when
> using a custom format (i.e., not the canned "short", "fuller", etc.)
> but we DWIM when the string appears to use %-interpolation.
Then we can move towards keeping the "log:" prefix but
allowing for it to be dropped when %-interpolation occurs, just like
--pretty does.
Something to point out, --pretty does a very simple check (it seems to
check only for the presence of '%'). This does indeed catch typos
related to preset formats like "short", "full", etc... but it doesn't
catch typos in format-strings.
"an%" is an accepted format-string even without the prefix despite not
doing any substitution.
Maybe there should be a function, to be used as a check, that parses the
string and checks whether the format-string should be accepted.
This could be an improvement for a future patch.
^ permalink raw reply [flat|nested] 113+ messages in thread
* Re: [PATCH v8 2/4] format-patch: add ability to use alt cover format
2026-03-13 19:17 ` Mirko Faina
@ 2026-03-13 20:22 ` Junio C Hamano
0 siblings, 0 replies; 113+ messages in thread
From: Junio C Hamano @ 2026-03-13 20:22 UTC (permalink / raw)
To: Mirko Faina; +Cc: Phillip Wood, git, Jeff King, Bert Wesarg
Mirko Faina <mroik@delayed.space> writes:
> On Fri, Mar 13, 2026 at 10:20:48AM -0700, Junio C Hamano wrote:
>> I do not mind a sort of DWIM similar to "log --pretty=format:%s";
>> technically, "git log --prefix" requires the "format:" prefix when
>> using a custom format (i.e., not the canned "short", "fuller", etc.)
>> but we DWIM when the string appears to use %-interpolation.
>
> Then we can move towards keeping the "log:" prefix but
> allowing for it to be dropped when %-interpolation occurs, just like
> --pretty does.
Yes. I vewi the DWIM as icing on the case, though. IOW, the
feature is perfectly fine to require "log:" and still move forward,
and it would be nice if people can say "--pretty=%s" without it.
But that DWIM is "it would be nice if this is added".
^ permalink raw reply [flat|nested] 113+ messages in thread
end of thread, other threads:[~2026-03-13 20:22 UTC | newest]
Thread overview: 113+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-20 23:06 [RFC PATCH] format-patch: better commit list for cover letter Mirko Faina
2026-02-20 23:55 ` [RFC PATCH v2] " Mirko Faina
2026-02-21 0:51 ` Mirko Faina
2026-02-21 4:54 ` [RFC PATCH] " Junio C Hamano
2026-02-21 5:18 ` Mirko Faina
2026-02-21 5:55 ` Junio C Hamano
2026-02-21 6:02 ` Junio C Hamano
2026-02-21 15:59 ` Mirko Faina
2026-02-21 17:33 ` Junio C Hamano
2026-02-21 19:16 ` Mirko Faina
2026-02-24 4:03 ` [PATCH 0/3] format-patch: add cover-letter-format option Mirko Faina
2026-02-24 4:06 ` Mirko Faina
2026-02-24 9:29 ` [PATCH v2 0/2] " Mirko Faina
2026-02-24 9:29 ` [PATCH v2 1/2] format-patch: add ability to use alt cover format Mirko Faina
2026-02-24 17:40 ` Junio C Hamano
2026-02-24 23:54 ` Mirko Faina
2026-02-25 0:29 ` Junio C Hamano
2026-02-25 13:47 ` Jeff King
2026-02-24 20:25 ` Junio C Hamano
2026-02-25 13:56 ` Jeff King
2026-02-25 22:55 ` Mirko Faina
2026-02-24 9:29 ` [PATCH v2 2/2] format-patch: add commitListFormat config Mirko Faina
2026-02-24 18:07 ` Junio C Hamano
2026-02-25 0:14 ` Mirko Faina
2026-02-25 17:25 ` Junio C Hamano
2026-02-26 21:40 ` Mirko Faina
2026-02-26 22:19 ` Junio C Hamano
2026-02-24 20:38 ` [PATCH v2 0/2] format-patch: add cover-letter-format option Junio C Hamano
2026-02-24 21:39 ` Junio C Hamano
2026-02-25 0:19 ` Mirko Faina
2026-02-25 2:46 ` Junio C Hamano
2026-02-27 1:52 ` [PATCH v3 0/4] " Mirko Faina
2026-02-27 1:52 ` [PATCH v3 1/4] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-02-27 1:52 ` [PATCH v3 2/4] format-patch: move cover letter summary generation Mirko Faina
2026-02-27 1:52 ` [PATCH v3 3/4] format-patch: add ability to use alt cover format Mirko Faina
2026-02-27 4:23 ` Junio C Hamano
2026-02-27 12:41 ` Mirko Faina
2026-02-27 1:52 ` [PATCH v3 4/4] format-patch: add commitListFormat config Mirko Faina
2026-02-27 13:18 ` [PATCH v4 0/4] format-patch: add cover-letter-format option Mirko Faina
2026-02-27 13:18 ` [PATCH v4 1/4] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-02-27 13:18 ` [PATCH v4 2/4] format-patch: move cover letter summary generation Mirko Faina
2026-02-27 13:18 ` [PATCH v4 3/4] format-patch: add ability to use alt cover format Mirko Faina
2026-02-27 13:18 ` [PATCH v4 4/4] format-patch: add commitListFormat config Mirko Faina
2026-02-27 16:42 ` [PATCH v4 5/4] docs: add usage for the cover-letter fmt feature Mirko Faina
2026-02-27 17:51 ` [PATCH v4 4/4] format-patch: add commitListFormat config Junio C Hamano
2026-02-27 21:51 ` Mirko Faina
2026-02-27 22:21 ` Junio C Hamano
2026-02-27 22:48 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Mirko Faina
2026-02-27 22:48 ` [PATCH v5 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-02-27 22:48 ` [PATCH v5 2/5] format-patch: move cover letter summary generation Mirko Faina
2026-02-27 22:48 ` [PATCH v5 3/5] format-patch: add ability to use alt cover format Mirko Faina
2026-02-27 22:48 ` [PATCH v5 4/5] format-patch: add commitListFormat config Mirko Faina
2026-02-27 22:48 ` [PATCH v5 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
2026-03-06 22:33 ` [PATCH v5 0/5] format-patch: add cover-letter-format option Junio C Hamano
2026-03-06 22:49 ` Mirko Faina
2026-03-06 22:58 ` [PATCH v6 " Mirko Faina
2026-03-06 22:58 ` [PATCH v6 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-03-06 22:58 ` [PATCH v6 2/5] format-patch: move cover letter summary generation Mirko Faina
2026-03-06 22:58 ` [PATCH v6 3/5] format-patch: add ability to use alt cover format Mirko Faina
2026-03-10 22:14 ` Junio C Hamano
2026-03-10 22:32 ` Mirko Faina
2026-03-06 22:58 ` [PATCH v6 4/5] format-patch: add commitListFormat config Mirko Faina
2026-03-06 22:58 ` [PATCH v6 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
2026-03-06 23:18 ` Junio C Hamano
2026-03-06 23:34 ` [PATCH v7 0/5] format-patch: add cover-letter-format option Mirko Faina
2026-03-06 23:34 ` [PATCH v7 1/5] pretty.c: add %(count) and %(total) placeholders Mirko Faina
2026-03-10 14:32 ` Phillip Wood
2026-03-10 20:55 ` Mirko Faina
2026-03-06 23:34 ` [PATCH v7 2/5] format-patch: move cover letter summary generation Mirko Faina
2026-03-06 23:34 ` [PATCH v7 3/5] format-patch: add ability to use alt cover format Mirko Faina
2026-03-10 14:33 ` Phillip Wood
2026-03-10 21:05 ` Mroik
2026-03-06 23:34 ` [PATCH v7 4/5] format-patch: add commitListFormat config Mirko Faina
2026-03-10 14:34 ` Phillip Wood
2026-03-10 16:45 ` Junio C Hamano
2026-03-10 21:23 ` Mirko Faina
2026-03-11 10:38 ` Phillip Wood
2026-03-11 17:13 ` Junio C Hamano
2026-03-11 10:32 ` Phillip Wood
2026-03-11 17:18 ` Junio C Hamano
2026-03-10 21:19 ` Mirko Faina
2026-03-06 23:34 ` [PATCH v7 5/5] docs: add usage for the cover-letter fmt feature Mirko Faina
2026-03-10 9:51 ` Bert Wesarg
2026-03-10 14:34 ` Phillip Wood
2026-03-12 16:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Mirko Faina
2026-03-12 16:20 ` [PATCH v8 1/4] format-patch: move cover letter summary generation Mirko Faina
2026-03-12 16:28 ` Junio C Hamano
2026-03-12 16:20 ` [PATCH v8 2/4] format-patch: add ability to use alt cover format Mirko Faina
2026-03-12 16:52 ` Junio C Hamano
2026-03-12 17:18 ` Mirko Faina
2026-03-12 17:25 ` Junio C Hamano
2026-03-12 17:27 ` Junio C Hamano
2026-03-13 10:38 ` Phillip Wood
2026-03-13 17:20 ` Junio C Hamano
2026-03-13 19:17 ` Mirko Faina
2026-03-13 20:22 ` Junio C Hamano
2026-03-12 16:20 ` [PATCH v8 3/4] format-patch: add "chronological" format for cover Mirko Faina
2026-03-12 16:55 ` Junio C Hamano
2026-03-12 16:20 ` [PATCH v8 4/4] format-patch: add commitListFormat config Mirko Faina
2026-03-12 17:00 ` Junio C Hamano
2026-03-12 17:20 ` [PATCH v8 0/4] format-patch: add cover-letter-format option Junio C Hamano
2026-03-12 17:45 ` Mirko Faina
2026-03-12 18:12 ` Junio C Hamano
2026-02-24 4:03 ` [PATCH 1/3] pretty.c: fix null pointer dereference Mirko Faina
2026-02-24 6:25 ` Junio C Hamano
2026-02-24 7:08 ` Mirko Faina
2026-02-24 7:43 ` Mirko Faina
2026-02-24 8:41 ` Jeff King
2026-02-24 4:03 ` [PATCH 2/3] format-patch: add ability to use alt cover format Mirko Faina
2026-02-24 9:02 ` Jeff King
2026-02-24 9:09 ` Mirko Faina
2026-02-24 9:18 ` Jeff King
2026-02-24 4:03 ` [PATCH 3/3] format-patch: add commitListFormat config Mirko Faina
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox