int alloc;
};
+/*
+ * Format the configuration key-value pair (`key_`, `value_`) and
+ * append it into strbuf `buf`. Returns a negative value on failure,
+ * 0 on success, 1 on a missing optional value (i.e., telling the
+ * caller to pretend that <key_,value_> did not exist).
+ */
static int format_config(const struct config_display_options *opts,
struct strbuf *buf, const char *key_,
const char *value_, const struct key_value_info *kvi)
char *v;
if (git_config_pathname(&v, key_, value_) < 0)
return -1;
- strbuf_addstr(buf, v);
+ if (v)
+ strbuf_addstr(buf, v);
+ else
+ return 1; /* :(optional)no-such-file */
free((char *)v);
} else if (opts->type == TYPE_EXPIRY_DATE) {
timestamp_t t;
struct collect_config_data *data = cb;
struct strbuf_list *values = data->values;
const struct key_value_info *kvi = ctx->kvi;
+ int status;
if (!(data->get_value_flags & GET_VALUE_KEY_REGEXP) &&
strcmp(key_, data->key))
ALLOC_GROW(values->items, values->nr + 1, values->alloc);
strbuf_init(&values->items[values->nr], 0);
- return format_config(data->display_opts, &values->items[values->nr++],
- key_, value_, kvi);
+ status = format_config(data->display_opts, &values->items[values->nr++],
+ key_, value_, kvi);
+ if (status < 0)
+ return status;
+ if (status) {
+ strbuf_release(&values->items[--values->nr]);
+ status = 0;
+ }
+ return status;
}
static int get_value(const struct config_location_options *opts,
if (!values.nr && display_opts->default_value) {
struct key_value_info kvi = KVI_INIT;
struct strbuf *item;
+ int status;
kvi_from_param(&kvi);
ALLOC_GROW(values.items, values.nr + 1, values.alloc);
item = &values.items[values.nr++];
strbuf_init(item, 0);
- if (format_config(display_opts, item, key_,
- display_opts->default_value, &kvi) < 0)
+
+ status = format_config(display_opts, item, key_,
+ display_opts->default_value, &kvi);
+ if (status < 0)
die(_("failed to format default config value: %s"),
display_opts->default_value);
+ if (status) {
+ /* default was a missing optional value */
+ values.nr--;
+ strbuf_release(item);
+ }
}
ret = !values.nr;
for_each_string_list_item(item, &values) {
struct urlmatch_current_candidate_value *matched = item->util;
struct strbuf buf = STRBUF_INIT;
+ int status;
- format_config(&display_opts, &buf, item->string,
- matched->value_is_null ? NULL : matched->value.buf,
- &matched->kvi);
- fwrite(buf.buf, 1, buf.len, stdout);
+ status = format_config(&display_opts, &buf, item->string,
+ matched->value_is_null ? NULL : matched->value.buf,
+ &matched->kvi);
+ if (!status)
+ fwrite(buf.buf, 1, buf.len, stdout);
strbuf_release(&buf);
strbuf_release(&matched->value);
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2025 Google LLC
+#
+
+test_description=':(optional) paths'
+
+. ./test-lib.sh
+
+test_expect_success 'var=:(optional)path-exists' '
+ test_config a.path ":(optional)path-exists" &&
+ >path-exists &&
+ echo path-exists >expect &&
+
+ git config get --path a.path >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'missing optional value is ignored' '
+ test_config a.path ":(optional)no-such-path" &&
+ # Using --show-scope ensures we skip writing not only the value
+ # but also any meta-information about the ignored key.
+ test_must_fail git config get --show-scope --path a.path >actual &&
+ test_line_count = 0 actual
+'
+
+test_expect_success 'missing optional value is ignored in multi-value config' '
+ test_when_finished "git config unset --all a.path" &&
+ git config set --append a.path ":(optional)path-exists" &&
+ git config set --append a.path ":(optional)no-such-path" &&
+ >path-exists &&
+ echo path-exists >expect &&
+
+ git config --get --path a.path >actual &&
+ test_cmp expect actual
+'
+
+test_done