format-patch: make output filename configurable

For the past 15 years, we've used the hardcoded 64 as the length limit of the filename of the output from the "git format-patch" command. Since the value is shorter than the 80-column terminal, it could grow without line wrapping a bit. At the same time, since the value is longer than half of the 80-column terminal, we could fit two or more of them in "ls" output on such a terminal if we allowed to lower it. Introduce a new command line option --filename-max-length=<n> and a new configuration variable format.filenameMaxLength to override the hardcoded default. While we are at it, remove a check that the name of output directory does not exceed PATH_MAX---this check is pointless in that by the time control reaches the function, the caller would already have done an equivalent of "mkdir -p", so if the system does not like an overly long directory name, the control wouldn't have reached here, and otherwise, we know that the system allowed the output directory to exist. In the worst case, we will get an error when we try to open the output file and handle the error correctly anyway. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Nov 6, 2020 at 13:56 UTC 3baf58bfb4bcfe201970abeffa8e1396d2324250
7 files changed +83 -8
Documentation/config/format.txt
+5
@@ -94,6 +94,11 @@ format.outputDirectory::
94 Set a custom directory to store the resulting files instead of the
95 current working directory. All directory components will be created.
96
97 +format.filenameMaxLength::
98 + The maximum length of the output filenames generated by the
99 + `format-patch` command; defaults to 64. Can be overridden
100 + by the `--filename-max-length=<n>` command line option.
101 +
102 format.useAutoBase::
103 A boolean value which lets you enable the `--base=auto` option of
104 format-patch by default. Can also be set to "whenAble" to allow
Documentation/git-format-patch.txt
+8
@@ -28,6 +28,7 @@ SYNOPSIS
28 [--no-notes | --notes[=<ref>]]
29 [--interdiff=<previous>]
30 [--range-diff=<previous> [--creation-factor=<percent>]]
31 + [--filename-max-length=<n>]
32 [--progress]
33 [<common diff options>]
34 [ <since> | <revision range> ]
@@ -200,6 +201,13 @@ populated with placeholder text.
201 allows for useful naming of a patch series, and can be
202 combined with the `--numbered` option.
203
204 +--filename-max-length=<n>::
205 + Instead of the standard 64 bytes, chomp the generated output
206 + filenames at around '<n>' bytes (too short a value will be
207 + silently raised to a reasonable length). Defaults to the
208 + value of the `format.filenameMaxLength` configuration
209 + variable, or 64 if unconfigured.
210 +
211 --rfc::
212 Alias for `--subject-prefix="RFC PATCH"`. RFC means "Request For
213 Comments"; use this when sending an experimental patch for
builtin/log.c
+14 -6
@@ -37,6 +37,7 @@
37
38 #define MAIL_DEFAULT_WRAP 72
39 #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
40 +#define FORMAT_PATCH_NAME_MAX_DEFAULT 64
41
42 /* Set a default date-time format for git log ("log.date" config variable) */
43 static const char *default_date_mode = NULL;
@@ -50,6 +51,7 @@ static int decoration_style;
51 static int decoration_given;
52 static int use_mailmap_config = 1;
53 static const char *fmt_patch_subject_prefix = "PATCH";
54 +static int fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT;
55 static const char *fmt_pretty;
56
57 static const char * const builtin_log_usage[] = {
@@ -150,6 +152,7 @@ static void cmd_log_init_defaults(struct rev_info *rev)
152 rev->abbrev_commit = default_abbrev_commit;
153 rev->show_root_diff = default_show_root;
154 rev->subject_prefix = fmt_patch_subject_prefix;
155 + rev->patch_name_max = fmt_patch_name_max;
156 rev->show_signature = default_show_signature;
157 rev->encode_email_headers = default_encode_email_headers;
158 rev->diffopt.flags.allow_textconv = 1;
@@ -454,6 +457,10 @@ static int git_log_config(const char *var, const char *value, void *cb)
457 return git_config_string(&fmt_pretty, var, value);
458 if (!strcmp(var, "format.subjectprefix"))
459 return git_config_string(&fmt_patch_subject_prefix, var, value);
460 + if (!strcmp(var, "format.filenamemaxlength")) {
461 + fmt_patch_name_max = git_config_int(var, value);
462 + return 0;
463 + }
464 if (!strcmp(var, "format.encodeemailheaders")) {
465 default_encode_email_headers = git_config_bool(var, value);
466 return 0;
@@ -955,15 +962,9 @@ static int open_next_file(struct commit *commit, const char *subject,
962 struct rev_info *rev, int quiet)
963 {
964 struct strbuf filename = STRBUF_INIT;
958 - int suffix_len = strlen(rev->patch_suffix) + 1;
965
966 if (output_directory) {
967 strbuf_addstr(&filename, output_directory);
962 - if (filename.len >=
963 - PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) {
964 - strbuf_release(&filename);
965 - return error(_("name of output directory is too long"));
966 - }
968 strbuf_complete(&filename, '/');
969 }
970
@@ -1751,6 +1752,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
1752 N_("start numbering patches at <n> instead of 1")),
1753 OPT_INTEGER('v', "reroll-count", &reroll_count,
1754 N_("mark the series as Nth re-roll")),
1755 + OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max,
1756 + N_("max length of output filename")),
1757 OPT_CALLBACK_F(0, "rfc", &rev, NULL,
1758 N_("Use [RFC PATCH] instead of [PATCH]"),
1759 PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback),
@@ -1851,6 +1854,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
1854 PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
1855 PARSE_OPT_KEEP_DASHDASH);
1856
1857 + /* Make sure "0000-$sub.patch" gives non-negative length for $sub */
1858 + if (fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix))
1859 + fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix);
1860 +
1861 if (cover_from_description_arg)
1862 cover_from_description_mode = parse_cover_from_description(cover_from_description_arg);
1863
@@ -1935,6 +1942,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
1942 rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
1943
1944 rev.zero_commit = zero_commit;
1945 + rev.patch_name_max = fmt_patch_name_max;
1946
1947 if (!rev.diffopt.flags.text && !no_binary_diff)
1948 rev.diffopt.flags.binary = 1;
log-tree.c
+1 -1
@@ -367,7 +367,7 @@ void fmt_output_subject(struct strbuf *filename,
367 const char *suffix = info->patch_suffix;
368 int nr = info->nr;
369 int start_len = filename->len;
370 - int max_len = start_len + FORMAT_PATCH_NAME_MAX - (strlen(suffix) + 1);
370 + int max_len = start_len + info->patch_name_max - (strlen(suffix) + 1);
371
372 if (0 < info->reroll_count)
373 strbuf_addf(filename, "v%d-", info->reroll_count);
log-tree.h
-1
@@ -33,7 +33,6 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
33 int maybe_multipart);
34 void load_ref_decorations(struct decoration_filter *filter, int flags);
35
36 -#define FORMAT_PATCH_NAME_MAX 64
36 void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *);
37 void fmt_output_subject(struct strbuf *, const char *subject, struct rev_info *);
38 void fmt_output_email_subject(struct strbuf *, struct rev_info *);
revision.h
+1
@@ -238,6 +238,7 @@ struct rev_info {
238 const char *extra_headers;
239 const char *log_reencode;
240 const char *subject_prefix;
241 + int patch_name_max;
242 int no_inline;
243 int show_log_size;
244 struct string_list *mailmap;
t/t4014-format-patch.sh
+54
@@ -313,6 +313,60 @@ test_expect_success 'multiple files' '
313 ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch
314 '
315
316 +test_expect_success 'filename length limit' '
317 + test_when_finished "rm -f 000*" &&
318 + rm -rf 000[1-9]-*.patch &&
319 + for len in 15 25 35
320 + do
321 + git format-patch --filename-max-length=$len -3 side &&
322 + max=$(
323 + for patch in 000[1-9]-*.patch
324 + do
325 + echo "$patch" | wc -c
326 + done |
327 + sort -nr |
328 + head -n 1
329 + ) &&
330 + test $max -le $len || return 1
331 + done
332 +'
333 +
334 +test_expect_success 'filename length limit from config' '
335 + test_when_finished "rm -f 000*" &&
336 + rm -rf 000[1-9]-*.patch &&
337 + for len in 15 25 35
338 + do
339 + git -c format.filenameMaxLength=$len format-patch -3 side &&
340 + max=$(
341 + for patch in 000[1-9]-*.patch
342 + do
343 + echo "$patch" | wc -c
344 + done |
345 + sort -nr |
346 + head -n 1
347 + ) &&
348 + test $max -le $len || return 1
349 + done
350 +'
351 +
352 +test_expect_success 'filename limit applies only to basename' '
353 + test_when_finished "rm -rf patches/" &&
354 + rm -rf patches/ &&
355 + for len in 15 25 35
356 + do
357 + git format-patch -o patches --filename-max-length=$len -3 side &&
358 + max=$(
359 + for patch in patches/000[1-9]-*.patch
360 + do
361 + echo "${patch#patches/}" | wc -c
362 + done |
363 + sort -nr |
364 + head -n 1
365 + ) &&
366 + test $max -le $len || return 1
367 + done
368 +'
369 +
370 test_expect_success 'reroll count' '
371 rm -fr patches &&
372 git format-patch -o patches --cover-letter --reroll-count 4 master..side >list &&