config: values of pathname type can be prefixed with :(optional)

Sometimes people want to specify additional configuration data as "best effort" basis. Maybe commit.template configuration file points at somewhere in ~/template/ but on a particular system, the file may not exist and the user may be OK without using the template in such a case. When the value given to a configuration variable whose type is pathname wants to signal such an optional file, it can be marked by prepending ":(optional)" in front of it. Such a setting that is marked optional would avoid getting the command barf for a missing file, as an optional configuration setting that names a missing file is not even seen. cf. <xmqq5ywehb69.fsf@gitster.g> Signed-off-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Sep 28, 2025 at 17:29 UTC 749d6d166d8e3ea0ae32ede25f9aa23aa3b5e42b
5 files changed +41 -4
Documentation/config.adoc
+3 -1
@@ -358,7 +358,9 @@ compiled without runtime prefix support, the compiled-in prefix will be
358 substituted instead. In the unlikely event that a literal path needs to
359 be specified that should _not_ be expanded, it needs to be prefixed by
360 `./`, like so: `./%(prefix)/bin`.
361 -
361 ++
362 +If prefixed with `:(optional)`, the configuration variable is treated
363 +as if it does not exist, if the named path does not exist.
364
365 Variables
366 ~~~~~~~~~
config.c
+14 -2
@@ -1279,11 +1279,23 @@ int git_config_string(char **dest, const char *var, const char *value)
1279
1280 int git_config_pathname(char **dest, const char *var, const char *value)
1281 {
1282 + int is_optional;
1283 + char *path;
1284 +
1285 if (!value)
1286 return config_error_nonbool(var);
1284 - *dest = interpolate_path(value, 0);
1285 - if (!*dest)
1287 +
1288 + is_optional = skip_prefix(value, ":(optional)", &value);
1289 + path = interpolate_path(value, 0);
1290 + if (!path)
1291 die(_("failed to expand user dir in: '%s'"), value);
1292 +
1293 + if (is_optional && is_missing_file(path)) {
1294 + free(path);
1295 + return 0;
1296 + }
1297 +
1298 + *dest = path;
1299 return 0;
1300 }
1301
t/t7500-commit-template-squash-signoff.sh
+8
@@ -46,6 +46,14 @@ test_expect_success 'nonexistent template file in config should return error' '
46 )
47 '
48
49 +test_expect_success 'nonexistent optional template file in config' '
50 + test_config commit.template ":(optional)$PWD"/notexist &&
51 + GIT_EDITOR="echo hello >" git commit --allow-empty &&
52 + git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
53 + echo hello >expect &&
54 + test_cmp expect actual
55 +'
56 +
57 # From now on we'll use a template file that exists.
58 TEMPLATE="$PWD"/template
59
wrapper.c
+13
@@ -721,6 +721,19 @@ int xgethostname(char *buf, size_t len)
721 return ret;
722 }
723
724 +int is_missing_file(const char *filename)
725 +{
726 + struct stat st;
727 +
728 + if (stat(filename, &st) < 0) {
729 + if (errno == ENOENT)
730 + return 1;
731 + die_errno(_("could not stat %s"), filename);
732 + }
733 +
734 + return 0;
735 +}
736 +
737 int is_empty_or_missing_file(const char *filename)
738 {
739 struct stat st;
wrapper.h
+3 -1
@@ -66,7 +66,9 @@ void write_file_buf(const char *path, const char *buf, size_t len);
66 __attribute__((format (printf, 2, 3)))
67 void write_file(const char *path, const char *fmt, ...);
68
69 -/* Return 1 if the file is empty or does not exists, 0 otherwise. */
69 +/* Return 1 if the file does not exist, 0 otherwise. */
70 +int is_missing_file(const char *filename);
71 +/* Return 1 if the file is empty or does not exist, 0 otherwise. */
72 int is_empty_or_missing_file(const char *filename);
73
74 enum fsync_action {