Skip to content

Commit cfaee75

Browse files
committed
config: really pretend missing :(optional) value is not there
Earlier we added support for a value spelled as ":(optional)path" for configuration variables whose values are of type "path", with the documented semantics "if the path is missing, behave as if such a variable definition is not even there." This has worked OK for code paths that reads configuration files and stores the configured value as a string, where NULL in such a string is treated as if the setting is not there, left as the default. However, there are other code paths that do not _ignore_ such NULL values and misbehave. "git config get --path" is one of them. When git_config_pathname() helper function finds that the value of the variable is an optional path *and* the path is missing, it leaves the destination pointer intact (which usually is left to NULL) and returns 0 to signal a success. format_config() helper however assumed that the destination pointer always gets a string, which no longer is the case, and segfaulted. Make sure that git_config_pathname() clears the destination pointer in such a case, and teach format_config() to react to the condition by returning 1 (which is different from 0 that is a normal success and negative that is an error) to its callers. Adjust the callers to react to this new return value that tells them to pretend as if they did not even see this partcular <key, value> pair. Reported-by: Han Jiang <jhcarl0814@gmail.com> Helped-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 770afe4 commit cfaee75

4 files changed

Lines changed: 74 additions & 9 deletions

File tree

builtin/config.c

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ struct strbuf_list {
261261
int alloc;
262262
};
263263

264+
/*
265+
* Format the configuration key-value pair (`key_`, `value_`) and
266+
* append it into strbuf `buf`. Returns a negative value on failure,
267+
* 0 on success, 1 on a missing optional value (i.e., telling the
268+
* caller to pretend that <key_,value_> did not exist).
269+
*/
264270
static int format_config(const struct config_display_options *opts,
265271
struct strbuf *buf, const char *key_,
266272
const char *value_, const struct key_value_info *kvi)
@@ -299,7 +305,10 @@ static int format_config(const struct config_display_options *opts,
299305
char *v;
300306
if (git_config_pathname(&v, key_, value_) < 0)
301307
return -1;
302-
strbuf_addstr(buf, v);
308+
if (v)
309+
strbuf_addstr(buf, v);
310+
else
311+
return 1; /* :(optional)no-such-file */
303312
free((char *)v);
304313
} else if (opts->type == TYPE_EXPIRY_DATE) {
305314
timestamp_t t;
@@ -344,6 +353,7 @@ static int collect_config(const char *key_, const char *value_,
344353
struct collect_config_data *data = cb;
345354
struct strbuf_list *values = data->values;
346355
const struct key_value_info *kvi = ctx->kvi;
356+
int status;
347357

348358
if (!(data->get_value_flags & GET_VALUE_KEY_REGEXP) &&
349359
strcmp(key_, data->key))
@@ -361,8 +371,15 @@ static int collect_config(const char *key_, const char *value_,
361371
ALLOC_GROW(values->items, values->nr + 1, values->alloc);
362372
strbuf_init(&values->items[values->nr], 0);
363373

364-
return format_config(data->display_opts, &values->items[values->nr++],
365-
key_, value_, kvi);
374+
status = format_config(data->display_opts, &values->items[values->nr++],
375+
key_, value_, kvi);
376+
if (status < 0)
377+
return status;
378+
if (status) {
379+
strbuf_release(&values->items[--values->nr]);
380+
status = 0;
381+
}
382+
return status;
366383
}
367384

368385
static int get_value(const struct config_location_options *opts,
@@ -438,15 +455,23 @@ static int get_value(const struct config_location_options *opts,
438455
if (!values.nr && display_opts->default_value) {
439456
struct key_value_info kvi = KVI_INIT;
440457
struct strbuf *item;
458+
int status;
441459

442460
kvi_from_param(&kvi);
443461
ALLOC_GROW(values.items, values.nr + 1, values.alloc);
444462
item = &values.items[values.nr++];
445463
strbuf_init(item, 0);
446-
if (format_config(display_opts, item, key_,
447-
display_opts->default_value, &kvi) < 0)
464+
465+
status = format_config(display_opts, item, key_,
466+
display_opts->default_value, &kvi);
467+
if (status < 0)
448468
die(_("failed to format default config value: %s"),
449469
display_opts->default_value);
470+
if (status) {
471+
/* default was a missing optional value */
472+
values.nr--;
473+
strbuf_release(item);
474+
}
450475
}
451476

452477
ret = !values.nr;
@@ -706,11 +731,13 @@ static int get_urlmatch(const struct config_location_options *opts,
706731
for_each_string_list_item(item, &values) {
707732
struct urlmatch_current_candidate_value *matched = item->util;
708733
struct strbuf buf = STRBUF_INIT;
734+
int status;
709735

710-
format_config(&display_opts, &buf, item->string,
711-
matched->value_is_null ? NULL : matched->value.buf,
712-
&matched->kvi);
713-
fwrite(buf.buf, 1, buf.len, stdout);
736+
status = format_config(&display_opts, &buf, item->string,
737+
matched->value_is_null ? NULL : matched->value.buf,
738+
&matched->kvi);
739+
if (!status)
740+
fwrite(buf.buf, 1, buf.len, stdout);
714741
strbuf_release(&buf);
715742

716743
strbuf_release(&matched->value);

config.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,7 @@ int git_config_pathname(char **dest, const char *var, const char *value)
12921292

12931293
if (is_optional && is_missing_file(path)) {
12941294
free(path);
1295+
*dest = NULL;
12951296
return 0;
12961297
}
12971298

t/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ integration_tests = [
182182
't1308-config-set.sh',
183183
't1309-early-config.sh',
184184
't1310-config-default.sh',
185+
't1311-config-optional.sh',
185186
't1350-config-hooks-path.sh',
186187
't1400-update-ref.sh',
187188
't1401-symbolic-ref.sh',

t/t1311-config-optional.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/bin/sh
2+
#
3+
# Copyright (c) 2025 Google LLC
4+
#
5+
6+
test_description=':(optional) paths'
7+
8+
. ./test-lib.sh
9+
10+
test_expect_success 'var=:(optional)path-exists' '
11+
test_config a.path ":(optional)path-exists" &&
12+
>path-exists &&
13+
echo path-exists >expect &&
14+
15+
git config get --path a.path >actual &&
16+
test_cmp expect actual
17+
'
18+
19+
test_expect_success 'missing optional value is ignored' '
20+
test_config a.path ":(optional)no-such-path" &&
21+
test_must_fail git config get --path a.path >actual &&
22+
test_line_count = 0 actual
23+
'
24+
25+
test_expect_success 'missing optional value is ignored in multi-value config' '
26+
test_when_finished "git config unset --all a.path" &&
27+
git config set --append a.path ":(optional)path-exists" &&
28+
git config set --append a.path ":(optional)no-such-path" &&
29+
>path-exists &&
30+
echo path-exists >expect &&
31+
32+
git config --get --path a.path >actual &&
33+
test_cmp expect actual
34+
'
35+
36+
test_done

0 commit comments

Comments
 (0)