aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2025-10-14 12:56:09 -0700
committerJunio C Hamano <gitster@pobox.com>2025-10-14 12:56:09 -0700
commit44dee53a303ad91305158bb1e2d9a8704b300c15 (patch)
treede50ca1587e3919b39a676dea702c88ac89608a9
parent1003719fb700df37557d6d161e757fabb952e78a (diff)
parentccfcaf399ffcc91553395a8de8e833e7685e7cc2 (diff)
downloadgit-44dee53a303ad91305158bb1e2d9a8704b300c15.tar.xz
Merge branch 'jc/optional-path'
Configuration variables that take a pathname as a value (e.g. blame.ignorerevsfile) can be marked as optional by prefixing ":(optoinal)" before its value. * jc/optional-path: parseopt: values of pathname type can be prefixed with :(optional) config: values of pathname type can be prefixed with :(optional) t7500: fix GIT_EDITOR shell snippet t7500: make each piece more independent
-rw-r--r--Documentation/config.adoc4
-rw-r--r--Documentation/gitcli.adoc14
-rw-r--r--config.c16
-rw-r--r--parse-options.c31
-rwxr-xr-xt/t7500-commit-template-squash-signoff.sh38
-rw-r--r--wrapper.c13
-rw-r--r--wrapper.h4
7 files changed, 95 insertions, 25 deletions
diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index 05f1ca7293..62eebe7c54 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -357,7 +357,9 @@ compiled without runtime prefix support, the compiled-in prefix will be
substituted instead. In the unlikely event that a literal path needs to
be specified that should _not_ be expanded, it needs to be prefixed by
`./`, like so: `./%(prefix)/bin`.
-
++
+If prefixed with `:(optional)`, the configuration variable is treated
+as if it does not exist, if the named path does not exist.
Variables
~~~~~~~~~
diff --git a/Documentation/gitcli.adoc b/Documentation/gitcli.adoc
index 1ea681b59d..ef2a0a399d 100644
--- a/Documentation/gitcli.adoc
+++ b/Documentation/gitcli.adoc
@@ -216,6 +216,20 @@ $ git describe --abbrev=10 HEAD # correct
$ git describe --abbrev 10 HEAD # NOT WHAT YOU MEANT
----------------------------
+
+Magic filename options
+~~~~~~~~~~~~~~~~~~~~~~
+Options that take a filename allow a prefix `:(optional)`. For example:
+
+----------------------------
+git commit -F :(optional)COMMIT_EDITMSG
+# if COMMIT_EDITMSG does not exist, equivalent to
+git commit
+----------------------------
+
+Like with configuration values, if the named file is missing Git behaves as if
+the option was not given at all. See "Values" in linkgit:git-config[1].
+
NOTES ON FREQUENTLY CONFUSED OPTIONS
------------------------------------
diff --git a/config.c b/config.c
index 74bf76a97e..71b136bf7f 100644
--- a/config.c
+++ b/config.c
@@ -1278,11 +1278,23 @@ int git_config_string(char **dest, const char *var, const char *value)
int git_config_pathname(char **dest, const char *var, const char *value)
{
+ int is_optional;
+ char *path;
+
if (!value)
return config_error_nonbool(var);
- *dest = interpolate_path(value, 0);
- if (!*dest)
+
+ is_optional = skip_prefix(value, ":(optional)", &value);
+ path = interpolate_path(value, 0);
+ if (!path)
die(_("failed to expand user dir in: '%s'"), value);
+
+ if (is_optional && is_missing_file(path)) {
+ free(path);
+ return 0;
+ }
+
+ *dest = path;
return 0;
}
diff --git a/parse-options.c b/parse-options.c
index 992ec9631f..5933468c19 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -133,7 +133,6 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
{
const char *arg;
const int unset = flags & OPT_UNSET;
- int err;
if (unset && p->opt)
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,
case OPTION_FILENAME:
{
const char *value;
-
- FREE_AND_NULL(*(char **)opt->value);
-
- err = 0;
+ int is_optional;
if (unset)
value = NULL;
else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
- value = (const char *) opt->defval;
- else
- err = get_arg(p, opt, flags, &value);
+ value = (char *)opt->defval;
+ else {
+ int err = get_arg(p, opt, flags, &value);
+ if (err)
+ return err;
+ }
+ if (!value)
+ return 0;
- if (!err)
- *(char **)opt->value = fix_filename(p->prefix, value);
- return err;
+ is_optional = skip_prefix(value, ":(optional)", &value);
+ if (!value)
+ is_optional = 0;
+ value = fix_filename(p->prefix, value);
+ if (is_optional && is_empty_or_missing_file(value)) {
+ free((char *)value);
+ } else {
+ FREE_AND_NULL(*(char **)opt->value);
+ *(const char **)opt->value = value;
+ }
+ return 0;
}
case OPTION_CALLBACK:
{
diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 4dca8d97a7..1935171d68 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -31,52 +31,70 @@ test_expect_success 'nonexistent template file should return error' '
echo changes >> foo &&
git add foo &&
(
- GIT_EDITOR="echo hello >\"\$1\"" &&
+ GIT_EDITOR="echo hello >" &&
export GIT_EDITOR &&
test_must_fail git commit --template "$PWD"/notexist
)
'
+test_expect_success 'nonexistent optional template file on command line' '
+ echo changes >> foo &&
+ git add foo &&
+ (
+ GIT_EDITOR="echo hello >\"\$1\"" &&
+ export GIT_EDITOR &&
+ git commit --template ":(optional)$PWD/notexist"
+ )
+'
+
test_expect_success 'nonexistent template file in config should return error' '
test_config commit.template "$PWD"/notexist &&
(
- GIT_EDITOR="echo hello >\"\$1\"" &&
+ GIT_EDITOR="echo hello >" &&
export GIT_EDITOR &&
- test_must_fail git commit
+ test_must_fail git commit --allow-empty
)
'
+test_expect_success 'nonexistent optional template file in config' '
+ test_config commit.template ":(optional)$PWD"/notexist &&
+ GIT_EDITOR="echo hello >" git commit --allow-empty &&
+ git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
+ echo hello >expect &&
+ test_cmp expect actual
+'
+
# From now on we'll use a template file that exists.
TEMPLATE="$PWD"/template
test_expect_success 'unedited template should not commit' '
- echo "template line" > "$TEMPLATE" &&
- test_must_fail git commit --template "$TEMPLATE"
+ echo "template line" >"$TEMPLATE" &&
+ test_must_fail git commit --allow-empty --template "$TEMPLATE"
'
test_expect_success 'unedited template with comments should not commit' '
- echo "# comment in template" >> "$TEMPLATE" &&
- test_must_fail git commit --template "$TEMPLATE"
+ echo "# comment in template" >>"$TEMPLATE" &&
+ test_must_fail git commit --allow-empty --template "$TEMPLATE"
'
test_expect_success 'a Signed-off-by line by itself should not commit' '
(
test_set_editor "$TEST_DIRECTORY"/t7500/add-signed-off &&
- test_must_fail git commit --template "$TEMPLATE"
+ test_must_fail git commit --allow-empty --template "$TEMPLATE"
)
'
test_expect_success 'adding comments to a template should not commit' '
(
test_set_editor "$TEST_DIRECTORY"/t7500/add-comments &&
- test_must_fail git commit --template "$TEMPLATE"
+ test_must_fail git commit --allow-empty --template "$TEMPLATE"
)
'
test_expect_success 'adding real content to a template should commit' '
(
test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
- git commit --template "$TEMPLATE"
+ git commit --allow-empty --template "$TEMPLATE"
) &&
commit_msg_is "template linecommit message"
'
diff --git a/wrapper.c b/wrapper.c
index 2f00d2ac87..3d507d4204 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -721,6 +721,19 @@ int xgethostname(char *buf, size_t len)
return ret;
}
+int is_missing_file(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) < 0) {
+ if (errno == ENOENT)
+ return 1;
+ die_errno(_("could not stat %s"), filename);
+ }
+
+ return 0;
+}
+
int is_empty_or_missing_file(const char *filename)
{
struct stat st;
diff --git a/wrapper.h b/wrapper.h
index 7df824e34a..44a8597ac3 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -66,7 +66,9 @@ void write_file_buf(const char *path, const char *buf, size_t len);
__attribute__((format (printf, 2, 3)))
void write_file(const char *path, const char *fmt, ...);
-/* Return 1 if the file is empty or does not exists, 0 otherwise. */
+/* Return 1 if the file does not exist, 0 otherwise. */
+int is_missing_file(const char *filename);
+/* Return 1 if the file is empty or does not exist, 0 otherwise. */
int is_empty_or_missing_file(const char *filename);
enum fsync_action {