parseopt: values of pathname type can be prefixed with :(optional)
In the previous step, we introduced an optional filename that can be given to a configuration variable, and nullify the fact that such a configuration setting even existed if the named path is missing or empty. Let's do the same for command line options that name a pathname. 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
ccfcaf399ffcc91553395a8de8e833e7685e7cc2
3 files changed
+44
-11
Documentation/gitcli.adoc
+14
@@ -216,6 +216,20 @@ $ git describe --abbrev=10 HEAD # correct
216
$ git describe --abbrev 10 HEAD # NOT WHAT YOU MEANT
217
----------------------------
218
219
+
220
+Magic filename options
221
+~~~~~~~~~~~~~~~~~~~~~~
222
+Options that take a filename allow a prefix `:(optional)`. For example:
223
+
224
+----------------------------
225
+git commit -F :(optional)COMMIT_EDITMSG
226
+# if COMMIT_EDITMSG does not exist, equivalent to
227
+git commit
228
+----------------------------
229
+
230
+Like with configuration values, if the named file is missing Git behaves as if
231
+the option was not given at all. See "Values" in linkgit:git-config[1].
232
+
233
NOTES ON FREQUENTLY CONFUSED OPTIONS
234
------------------------------------
235
parse-options.c
+20
-11
@@ -133,7 +133,6 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
133
{
134
const char *arg;
135
const int unset = flags & OPT_UNSET;
136
- int err;
136
137
if (unset && p->opt)
138
return error(_("%s takes no value"), optname(opt, flags));
@@ -209,21 +208,31 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
208
case OPTION_FILENAME:
209
{
210
const char *value;
212
-
213
- FREE_AND_NULL(*(char **)opt->value);
214
-
215
- err = 0;
211
+ int is_optional;
212
213
if (unset)
214
value = NULL;
215
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
220
- value = (const char *) opt->defval;
221
- else
222
- err = get_arg(p, opt, flags, &value);
216
+ value = (char *)opt->defval;
217
+ else {
218
+ int err = get_arg(p, opt, flags, &value);
219
+ if (err)
220
+ return err;
221
+ }
222
+ if (!value)
223
+ return 0;
224
224
- if (!err)
225
- *(char **)opt->value = fix_filename(p->prefix, value);
226
- return err;
225
+ is_optional = skip_prefix(value, ":(optional)", &value);
226
+ if (!value)
227
+ is_optional = 0;
228
+ value = fix_filename(p->prefix, value);
229
+ if (is_optional && is_empty_or_missing_file(value)) {
230
+ free((char *)value);
231
+ } else {
232
+ FREE_AND_NULL(*(char **)opt->value);
233
+ *(const char **)opt->value = value;
234
+ }
235
+ return 0;
236
}
237
case OPTION_CALLBACK:
238
{
t/t7500-commit-template-squash-signoff.sh
+10
@@ -37,6 +37,16 @@ test_expect_success 'nonexistent template file should return error' '
37
)
38
'
39
40
+test_expect_success 'nonexistent optional template file on command line' '
41
+ echo changes >> foo &&
42
+ git add foo &&
43
+ (
44
+ GIT_EDITOR="echo hello >\"\$1\"" &&
45
+ export GIT_EDITOR &&
46
+ git commit --template ":(optional)$PWD/notexist"
47
+ )
48
+'
49
+
50
test_expect_success 'nonexistent template file in config should return error' '
51
test_config commit.template "$PWD"/notexist &&
52
(