git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/1] blame: Skip missing ignore-revs file
@ 2021-08-07 20:27 Noah Pendleton
  2021-08-07 20:58 ` Junio C Hamano
  2021-08-08 17:48 ` [PATCH v2] blame: add config `blame.ignoreRevsFileIsOptional` Noah Pendleton
  0 siblings, 2 replies; 39+ messages in thread
From: Noah Pendleton @ 2021-08-07 20:27 UTC (permalink / raw)
  To: git; +Cc: Noah Pendleton

Setting a global `blame.ignoreRevsFile` can be convenient, since I
usually use `.git-blame-ignore-revs` in repos. If the file is missing,
though, `git blame` exits with failure. This patch changes it to skip
over non-existent ignore-rev files instead of erroring.


Noah Pendleton (1):
  blame: skip missing ignore-revs-file's

 Documentation/blame-options.txt |  2 +-
 Documentation/config/blame.txt  |  3 ++-
 builtin/blame.c                 |  2 +-
 t/t8013-blame-ignore-revs.sh    | 10 ++++++----
 4 files changed, 10 insertions(+), 7 deletions(-)

-- 
2.32.0


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH 0/1] blame: Skip missing ignore-revs file
@ 2021-08-07 20:29 Noah Pendleton
  0 siblings, 0 replies; 39+ messages in thread
From: Noah Pendleton @ 2021-08-07 20:29 UTC (permalink / raw)
  To: git; +Cc: Noah Pendleton

Setting a global `blame.ignoreRevsFile` can be convenient, since I
usually use `.git-blame-ignore-revs` in repos. If the file is missing,
though, `git blame` exits with failure. This patch changes it to skip
over non-existent ignore-rev files instead of erroring.


Noah Pendleton (1):
  blame: skip missing ignore-revs-file's

 Documentation/blame-options.txt |  2 +-
 Documentation/config/blame.txt  |  3 ++-
 builtin/blame.c                 |  2 +-
 t/t8013-blame-ignore-revs.sh    | 10 ++++++----
 4 files changed, 10 insertions(+), 7 deletions(-)

-- 
2.32.0


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/1] blame: Skip missing ignore-revs file
  2021-08-07 20:27 [PATCH 0/1] blame: Skip missing ignore-revs file Noah Pendleton
@ 2021-08-07 20:58 ` Junio C Hamano
  2021-08-07 21:34   ` Noah Pendleton
  2022-03-04  9:51   ` [PATCH 0/1] blame: Skip missing ignore-revs file Thranur Andul
  2021-08-08 17:48 ` [PATCH v2] blame: add config `blame.ignoreRevsFileIsOptional` Noah Pendleton
  1 sibling, 2 replies; 39+ messages in thread
From: Junio C Hamano @ 2021-08-07 20:58 UTC (permalink / raw)
  To: Noah Pendleton; +Cc: git

Noah Pendleton <noah.pendleton@gmail.com> writes:

> Setting a global `blame.ignoreRevsFile` can be convenient, since I
> usually use `.git-blame-ignore-revs` in repos. If the file is missing,
> though, `git blame` exits with failure. This patch changes it to skip
> over non-existent ignore-rev files instead of erroring.

That cuts both ways, though.  Failing upon missing configuration
file is a way to catch misconfiguration that is hard to diagnose.

I wonder if we can easily learn where the configuration variable
came from in the codepath that diagnoses it as a misconfiguration.

If it came from a per-repo configuration and names a non-existent
file, it clearly is a misconfiguration that we want to flag as an
error.  Even if it came from a per-user configuration, if it was
specified in a conditionally included file, it is likely to be a
misconfiguration.  If it came from a per-user configuration that
applies without any condition, it can be a good convenience feature
to silently (or with a warning) ignore missing file.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/1] blame: Skip missing ignore-revs file
  2021-08-07 20:58 ` Junio C Hamano
@ 2021-08-07 21:34   ` Noah Pendleton
  2021-08-08  5:43     ` Junio C Hamano
  2022-03-04  9:51   ` [PATCH 0/1] blame: Skip missing ignore-revs file Thranur Andul
  1 sibling, 1 reply; 39+ messages in thread
From: Noah Pendleton @ 2021-08-07 21:34 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Thanks for the quick response!

Very good point about no longer catching misconfiguration. For
detecting provenance of a setting, I think we'd need to tag the config
options with it when they're loaded, possibly in 'struct
config_set_element' or similar. What do you think about instead
emitting a warning message on stderr in the case of misconfiguration,
but still continuing? Eg:

diff --git a/builtin/blame.c b/builtin/blame.c
index e5b45eddf4..6ee8f29313 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -835,7 +835,9 @@ static void build_ignorelist(struct blame_scoreboard *sb,
  for_each_string_list_item(i, ignore_revs_file_list) {
  if (!strcmp(i->string, ""))
  oidset_clear(&sb->ignore_list);
- else if (file_exists(i->string))
+ else if (!file_exists(i->string))
+ warning(_("skipping ignore-revs-file %s"), i->string);
+ else
  oidset_parse_file_carefully(&sb->ignore_list, i->string,
     peel_to_commit_oid, sb);
  }

On Sat, Aug 7, 2021, 16:58 Junio C Hamano <gitster@pobox.com> wrote:
>
> Noah Pendleton <noah.pendleton@gmail.com> writes:
>
> > Setting a global `blame.ignoreRevsFile` can be convenient, since I
> > usually use `.git-blame-ignore-revs` in repos. If the file is missing,
> > though, `git blame` exits with failure. This patch changes it to skip
> > over non-existent ignore-rev files instead of erroring.
>
> That cuts both ways, though.  Failing upon missing configuration
> file is a way to catch misconfiguration that is hard to diagnose.
>
> I wonder if we can easily learn where the configuration variable
> came from in the codepath that diagnoses it as a misconfiguration.
>
> If it came from a per-repo configuration and names a non-existent
> file, it clearly is a misconfiguration that we want to flag as an
> error.  Even if it came from a per-user configuration, if it was
> specified in a conditionally included file, it is likely to be a
> misconfiguration.  If it came from a per-user configuration that
> applies without any condition, it can be a good convenience feature
> to silently (or with a warning) ignore missing file.

^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/1] blame: Skip missing ignore-revs file
  2021-08-07 21:34   ` Noah Pendleton
@ 2021-08-08  5:43     ` Junio C Hamano
  2021-08-08 17:50       ` Junio C Hamano
  0 siblings, 1 reply; 39+ messages in thread
From: Junio C Hamano @ 2021-08-08  5:43 UTC (permalink / raw)
  To: Noah Pendleton; +Cc: git

Noah Pendleton <noah.pendleton@gmail.com> writes:

> Very good point about no longer catching misconfiguration. For
> detecting provenance of a setting, I think we'd need to tag the config
> options with it when they're loaded, possibly in 'struct
> config_set_element' or similar. What do you think about instead
> emitting a warning message on stderr in the case of misconfiguration,
> but still continuing? Eg:

Unconditionally continuing with just a warning would not be a good
approach for at least two reasons.  (1) the user may truly have
intended that ignoreRevsFile to be optional, in which case the
warning is a nuisance that does not add any value, and (2) it truly
may be a misconfiguration that the named file did not exist, but the
output from "git blame" will wipe the display and the warning would
very well go unnoticed (or more likely the user may notice that
there was a warning, but it will go away before the user has a
chance to really read it, which is a lot worse and frustrating
experience).

I think an easier way out is to introduce a new configuration
variable blame.ignoreRevsFileIsOptional which takes a boolean value,
and when it is set to true, silently ignore when the named file does
not exist without any warning.  When the variable is set to false
(or the variable does not exist), we can keep the current behaviour
of noticing a misconfigured blame.ignoreRevsFile and error out.

That way, the current users who rely on the typo detection feature
can keep relying on it, and those who want to make it optional can
do so without getting annoyed by a warning.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH v2] blame: add config `blame.ignoreRevsFileIsOptional`
  2021-08-07 20:27 [PATCH 0/1] blame: Skip missing ignore-revs file Noah Pendleton
  2021-08-07 20:58 ` Junio C Hamano
@ 2021-08-08 17:48 ` Noah Pendleton
  1 sibling, 0 replies; 39+ messages in thread
From: Noah Pendleton @ 2021-08-08 17:48 UTC (permalink / raw)
  To: git; +Cc: Noah Pendleton, Junio C Hamano

Setting the config option `blame.ignoreRevsFile` globally to eg
`.git-blame-ignore-revs` causes `git blame` to error when the file
doesn't exist in the current repository:

```
fatal: could not open object name list: .git-blame-ignore-revs
```

Add a new config option, `blame.ignoreRevsFileIsOptional`, that when set
to true, `git blame` will silently ignore any missing ignoreRevsFile's.

Signed-off-by: Noah Pendleton <noah.pendleton@gmail.com>
---
Reworked this patch to add a new config
`blame.ignoreRevsFileIsOptional`, which controls whether missing
files specified by ignoreRevsFile cause an error or are silently
ignored.

Updated tests and docs to match.

 Documentation/blame-options.txt |  3 ++-
 Documentation/config/blame.txt  |  5 +++++
 builtin/blame.c                 |  7 ++++++-
 t/t8013-blame-ignore-revs.sh    | 14 ++++++++++----
 4 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt
index 117f4cf806..199a28ab79 100644
--- a/Documentation/blame-options.txt
+++ b/Documentation/blame-options.txt
@@ -134,7 +134,8 @@ take effect.
 	`fsck.skipList`.  This option may be repeated, and these files will be
 	processed after any files specified with the `blame.ignoreRevsFile` config
 	option.  An empty file name, `""`, will clear the list of revs from
-	previously processed files.
+	previously processed files. If `blame.ignoreRevsFileIsOptional` is true,
+	missing files will be silently ignored.
 
 -h::
 	Show help message.
diff --git a/Documentation/config/blame.txt b/Documentation/config/blame.txt
index 4d047c1790..2aae851e4b 100644
--- a/Documentation/config/blame.txt
+++ b/Documentation/config/blame.txt
@@ -27,6 +27,11 @@ blame.ignoreRevsFile::
 	file names will reset the list of ignored revisions.  This option will
 	be handled before the command line option `--ignore-revs-file`.
 
+blame.ignoreRevsFileIsOptional::
+	Silently skip missing files specified by ignoreRevsFile or the command line
+	option `--ignore-revs-file`. If unset, or set to false, missing files will
+	cause a nonrecoverable error.
+
 blame.markUnblamableLines::
 	Mark lines that were changed by an ignored revision that we could not
 	attribute to another commit with a '*' in the output of
diff --git a/builtin/blame.c b/builtin/blame.c
index 641523ff9a..df132b34ce 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -56,6 +56,7 @@ static int coloring_mode;
 static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;
 static int mark_unblamable_lines;
 static int mark_ignored_lines;
+static int ignorerevsfileisoptional;
 
 static struct date_mode blame_date_mode = { DATE_ISO8601 };
 static size_t blame_date_width;
@@ -715,6 +716,9 @@ static int git_blame_config(const char *var, const char *value, void *cb)
 		string_list_insert(&ignore_revs_file_list, str);
 		return 0;
 	}
+	if (!strcmp(var, "blame.ignorerevsfileisoptional")) {
+		ignorerevsfileisoptional = git_config_bool(var, value);
+	}
 	if (!strcmp(var, "blame.markunblamablelines")) {
 		mark_unblamable_lines = git_config_bool(var, value);
 		return 0;
@@ -835,7 +839,8 @@ static void build_ignorelist(struct blame_scoreboard *sb,
 	for_each_string_list_item(i, ignore_revs_file_list) {
 		if (!strcmp(i->string, ""))
 			oidset_clear(&sb->ignore_list);
-		else
+		/* skip non-existent files if ignorerevsfileisoptional is set */
+		else if (!ignorerevsfileisoptional || file_exists(i->string))
 			oidset_parse_file_carefully(&sb->ignore_list, i->string,
 						    peel_to_commit_oid, sb);
 	}
diff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh
index b18633dee1..f789426cbf 100755
--- a/t/t8013-blame-ignore-revs.sh
+++ b/t/t8013-blame-ignore-revs.sh
@@ -127,18 +127,24 @@ test_expect_success override_ignore_revs_file '
 	grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual &&
 	test_cmp expect actual
 	'
-test_expect_success bad_files_and_revs '
+test_expect_success bad_revs '
 	test_must_fail git blame file --ignore-rev NOREV 2>err &&
 	test_i18ngrep "cannot find revision NOREV to ignore" err &&
 
-	test_must_fail git blame file --ignore-revs-file NOFILE 2>err &&
-	test_i18ngrep "could not open.*: NOFILE" err &&
-
 	echo NOREV >ignore_norev &&
 	test_must_fail git blame file --ignore-revs-file ignore_norev 2>err &&
 	test_i18ngrep "invalid object name: NOREV" err
 '
 
+# Non-existent ignore-revs-file should fail unless
+# blame.ignoreRevsFileIsOptional is set
+test_expect_success bad_file '
+	test_must_fail git blame file --ignore-revs-file NOFILE &&
+
+	git config --add blame.ignorerevsfileisoptional true &&
+	git blame file --ignore-revs-file NOFILE
+'
+
 # For ignored revs that have added 'unblamable' lines, mark those lines with a
 # '*'
 # 	A--B--X--Y
-- 
2.32.0


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/1] blame: Skip missing ignore-revs file
  2021-08-08  5:43     ` Junio C Hamano
@ 2021-08-08 17:50       ` Junio C Hamano
  2021-08-08 18:21         ` Noah Pendleton
  0 siblings, 1 reply; 39+ messages in thread
From: Junio C Hamano @ 2021-08-08 17:50 UTC (permalink / raw)
  To: Noah Pendleton; +Cc: git

Junio C Hamano <gitster@pobox.com> writes:

> I think an easier way out is to introduce a new configuration
> variable blame.ignoreRevsFileIsOptional which takes a boolean value,
> and when it is set to true, silently ignore when the named file does
> not exist without any warning.  When the variable is set to false
> (or the variable does not exist), we can keep the current behaviour
> of noticing a misconfigured blame.ignoreRevsFile and error out.
>
> That way, the current users who rely on the typo detection feature
> can keep relying on it, and those who want to make it optional can
> do so without getting annoyed by a warning.

A bit more ambitious might want to consider another more generally
applicable avenue, which would help the userbase a lot more, before
continuing.

We start from the realization that this is not the only
configuration variable that specifies a filename that could be
missing.  There may be other variables that name files to be used
("git config --help" would hopefully be the most comprehensive, but
"git grep -e git_config_pathname \*.c" would give us quicker
starting point to gauge how big an impact to the system we would be
talking about).

What do the codepaths that use these variables do when they find
that the named files are missing?  Do some of them die, some
others just warn, and yet some others silently ignore?  Would such
an inconsistency hurt our users?

Among the ones that die, are there ones that could reasonably
continue as if the configuration variable weren't there and no file
was specified (i.e. similar to what you want blame.ignoreRevsFile to
do)?  Among the ones that are silently ignored, are there ones that
may benefit by having a typo-detection?  Do all of them benefit if
the behaviour upon missing files can be configurable by the end-user?

Depending on the answers to the above questions, it might be that it
is not a desirable approach to add "blame.ignoreRevsFileIsOptional"
configuration variable, as all the existing configuration variables
that name files would want to add their own.  We might be better off
inventing a syntax for the value of blame.ignoreRevsFile (and other
variables that name files) to mark if the file is optional (i.e.
silently ignore if the named file does not exist) or required (i.e.
diagnose as a configuration error).  For example, we may borrow from
the "magic" syntax for pathspecs that begin with ":(", with comma
separated "magic" keywords and ends with ")" and specify optional
pathname configuration like so:

    [blame] ignoreRevsFile = :(optional).gitignorerevs

and teach the config parser to pretend as if it saw nothing when it
notices that the named file is missing.  That approach would cover
not just this single variable, but other variables that are parsed
using git_config_pathname() may benefit the same way (of course, the
callsites for git_config_pathmame() must be inspected and adjusted
for this to happen).

Thanks.


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/1] blame: Skip missing ignore-revs file
  2021-08-08 17:50       ` Junio C Hamano
@ 2021-08-08 18:21         ` Noah Pendleton
  2021-08-09 15:47           ` Junio C Hamano
  0 siblings, 1 reply; 39+ messages in thread
From: Noah Pendleton @ 2021-08-08 18:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Very good point- I see about 21 call sites for `git_config_pathname`,
plus a few others (`git_config_get_pathname`) that bottom out in the
same function. I could see the utility of optional paths for some of
them: for example, `commit.template`, `core.excludesfile`. Some of the
others seem a little more ambiguous, eg `http.sslcert` probably wants
to always fail in case of missing file.

There seems to be a mix of fail-hard on invalid paths, printing a
warning message and skipping, and silently ignoring.

Hard for me to predict what the least confusing behavior is around
path configuration values, though, so maybe adding support for the
`:(optional)` (and maybe additionally a `:(required)`) tag across the
board to pathname configs is the right move.

That patch might be beyond what I'm capable of, though I'm happy to
put up a draft that applies it to the original `ignoreRevsFile` case
as a starting point.

On Sun, Aug 8, 2021 at 1:50 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Junio C Hamano <gitster@pobox.com> writes:
>
> > I think an easier way out is to introduce a new configuration
> > variable blame.ignoreRevsFileIsOptional which takes a boolean value,
> > and when it is set to true, silently ignore when the named file does
> > not exist without any warning.  When the variable is set to false
> > (or the variable does not exist), we can keep the current behaviour
> > of noticing a misconfigured blame.ignoreRevsFile and error out.
> >
> > That way, the current users who rely on the typo detection feature
> > can keep relying on it, and those who want to make it optional can
> > do so without getting annoyed by a warning.
>
> A bit more ambitious might want to consider another more generally
> applicable avenue, which would help the userbase a lot more, before
> continuing.
>
> We start from the realization that this is not the only
> configuration variable that specifies a filename that could be
> missing.  There may be other variables that name files to be used
> ("git config --help" would hopefully be the most comprehensive, but
> "git grep -e git_config_pathname \*.c" would give us quicker
> starting point to gauge how big an impact to the system we would be
> talking about).
>
> What do the codepaths that use these variables do when they find
> that the named files are missing?  Do some of them die, some
> others just warn, and yet some others silently ignore?  Would such
> an inconsistency hurt our users?
>
> Among the ones that die, are there ones that could reasonably
> continue as if the configuration variable weren't there and no file
> was specified (i.e. similar to what you want blame.ignoreRevsFile to
> do)?  Among the ones that are silently ignored, are there ones that
> may benefit by having a typo-detection?  Do all of them benefit if
> the behaviour upon missing files can be configurable by the end-user?
>
> Depending on the answers to the above questions, it might be that it
> is not a desirable approach to add "blame.ignoreRevsFileIsOptional"
> configuration variable, as all the existing configuration variables
> that name files would want to add their own.  We might be better off
> inventing a syntax for the value of blame.ignoreRevsFile (and other
> variables that name files) to mark if the file is optional (i.e.
> silently ignore if the named file does not exist) or required (i.e.
> diagnose as a configuration error).  For example, we may borrow from
> the "magic" syntax for pathspecs that begin with ":(", with comma
> separated "magic" keywords and ends with ")" and specify optional
> pathname configuration like so:
>
>     [blame] ignoreRevsFile = :(optional).gitignorerevs
>
> and teach the config parser to pretend as if it saw nothing when it
> notices that the named file is missing.  That approach would cover
> not just this single variable, but other variables that are parsed
> using git_config_pathname() may benefit the same way (of course, the
> callsites for git_config_pathmame() must be inspected and adjusted
> for this to happen).
>
> Thanks.
>

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/1] blame: Skip missing ignore-revs file
  2021-08-08 18:21         ` Noah Pendleton
@ 2021-08-09 15:47           ` Junio C Hamano
  2024-10-14 20:44             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
  2025-05-01 21:40             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
  0 siblings, 2 replies; 39+ messages in thread
From: Junio C Hamano @ 2021-08-09 15:47 UTC (permalink / raw)
  To: Noah Pendleton; +Cc: git

Noah Pendleton <noah.pendleton@gmail.com> writes:

> Very good point- I see about 21 call sites for `git_config_pathname`,
> plus a few others (`git_config_get_pathname`) that bottom out in the
> same function. I could see the utility of optional paths for some of
> them: for example, `commit.template`, `core.excludesfile`. Some of the
> others seem a little more ambiguous, eg `http.sslcert` probably wants
> to always fail in case of missing file.

Thanks for already doing initial surveillance.  Very useful.

> There seems to be a mix of fail-hard on invalid paths, printing a
> warning message and skipping, and silently ignoring.
>
> Hard for me to predict what the least confusing behavior is around
> path configuration values, though, so maybe adding support for the
> `:(optional)` (and maybe additionally a `:(required)`) tag across the
> board to pathname configs is the right move.

I originally hoped only ":(optional)" would be necessary, but to
keep the continuity in behaviour for those currently that do not die
upon seeing a missing file, we probably should treat an unadorned
value as asking for the "traditional" behaviour, at least in the
shorter term, and allow those users who want to detect typos to
tighten the rule using ":(required)".  I dunno.

> That patch might be beyond what I'm capable of, though I'm happy to
> put up a draft that applies it to the original `ignoreRevsFile` case
> as a starting point.

Thanks for an offer.  We are not in a hurry (especially during the
pre-release feature freeze), and hopefully this discussion would
pique other developers' interest to nudge them to help ;-)

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 0/1] blame: Skip missing ignore-revs file
  2021-08-07 20:58 ` Junio C Hamano
  2021-08-07 21:34   ` Noah Pendleton
@ 2022-03-04  9:51   ` Thranur Andul
  1 sibling, 0 replies; 39+ messages in thread
From: Thranur Andul @ 2022-03-04  9:51 UTC (permalink / raw)
  To: Junio C Hamano, Noah Pendleton; +Cc: git



On 07/08/2021 22:58, Junio C Hamano wrote:
> Noah Pendleton <noah.pendleton@gmail.com> writes:
> 
> 
> That cuts both ways, though.  Failing upon missing configuration
> file is a way to catch misconfiguration that is hard to diagnose.
> 
> I wonder if we can easily learn where the configuration variable
> came from in the codepath that diagnoses it as a misconfiguration.
> 
> If it came from a per-repo configuration and names a non-existent
> file, it clearly is a misconfiguration that we want to flag as an
> error.  Even if it came from a per-user configuration, if it was
> specified in a conditionally included file, it is likely to be a
> misconfiguration.  If it came from a per-user configuration that
> applies without any condition, it can be a good convenience feature
> to silently (or with a warning) ignore missing file.
>
I am very interested in this feature, but I'd like to add another point 
to the discussion: in the case of ignoreRevsFile in particular, no one 
creates a repository with such a file; it is always added later. 
However, when bisecting (a typical usage scenario for git-blame), we may 
end up returning back to a point _before_ the file had been added, and 
then, git-blame fails. This often happens to me, and I am then forced to 
`touch` the file to create it again, only to ensure git-blame keeps 
working. And then, when I want to return to the HEAD commit, the file 
must be erased again otherwise there is a conflict. So, for me, the 
"ignore if absent" behavior seems to me like it should be the default.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH 0/3] specifying a file that can optionally exist
  2021-08-09 15:47           ` Junio C Hamano
@ 2024-10-14 20:44             ` Junio C Hamano
  2024-10-14 20:44               ` [PATCH 1/3] t7500: make each piece more independent Junio C Hamano
                                 ` (2 more replies)
  2025-05-01 21:40             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
  1 sibling, 3 replies; 39+ messages in thread
From: Junio C Hamano @ 2024-10-14 20:44 UTC (permalink / raw)
  To: git

In a discussion a few years ago (cf. <xmqq5ywehb69.fsf@gitster.g>),
we wondered if it is a good idea to allow a configuration variable
(or a command line option for that matter) to name an "optional"
file, and pretend as if such a configuration setting or a command
line option was not even given when the named file did not exist or
empty.

Here are a few patches I did while passing time without anything
better to do.

Even though I updated the documentation for the configuration
variables, I didn't find a good central place to do the same for
parse-options.  I'll leave it as an exercise for the readers ;-).

The first patch is a preliminary clean-up for test script that is
used to house tests added by the later patches.

The second patch is for configuration variables, and the last one is
for command line options.

Junio C Hamano (3):
  t7500: make each piece more independent
  config: values of pathname type can be prefixed with :(optional)
  parseopt: values of pathname type can be prefixed with :(optional)

 Documentation/config.txt                  |  5 +++-
 config.c                                  | 16 +++++++++--
 parse-options.c                           | 31 +++++++++++++-------
 t/t7500-commit-template-squash-signoff.sh | 35 +++++++++++++++++------
 4 files changed, 65 insertions(+), 22 deletions(-)

-- 
2.47.0-148-g19c85929c5


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH 1/3] t7500: make each piece more independent
  2024-10-14 20:44             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
@ 2024-10-14 20:44               ` Junio C Hamano
  2024-10-14 20:44               ` [PATCH 2/3] config: values of pathname type can be prefixed with :(optional) Junio C Hamano
  2024-10-14 20:44               ` [PATCH 3/3] parseopt: " Junio C Hamano
  2 siblings, 0 replies; 39+ messages in thread
From: Junio C Hamano @ 2024-10-14 20:44 UTC (permalink / raw)
  To: git

These tests prepare the working tree & index state to have something
to be committed, and try a sequence of "test_must_fail git commit".
If an earlier one did not fail by a bug, a later one will fail for
a wrong reason (namely, "nothing to commit").

Give them "--allow-empty" to make sure that they would work even
when there is nothing to commit by accident.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t7500-commit-template-squash-signoff.sh | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 4dca8d97a7..4927b7260d 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -50,33 +50,33 @@ test_expect_success 'nonexistent template file in config should return error' '
 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"
 '
-- 
2.47.0-148-g19c85929c5


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH 2/3] config: values of pathname type can be prefixed with :(optional)
  2024-10-14 20:44             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
  2024-10-14 20:44               ` [PATCH 1/3] t7500: make each piece more independent Junio C Hamano
@ 2024-10-14 20:44               ` Junio C Hamano
  2024-10-14 20:44               ` [PATCH 3/3] parseopt: " Junio C Hamano
  2 siblings, 0 replies; 39+ messages in thread
From: Junio C Hamano @ 2024-10-14 20:44 UTC (permalink / raw)
  To: git

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 or
an empty file is not even seen.

cf. <xmqq5ywehb69.fsf@gitster.g>

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/config.txt                  |  5 ++++-
 config.c                                  | 16 ++++++++++++++--
 t/t7500-commit-template-squash-signoff.sh |  9 +++++++++
 3 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 8c0b3ed807..199e29ccea 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -358,7 +358,10 @@ 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 or names an
+empty file.
 
 Variables
 ~~~~~~~~~
diff --git a/config.c b/config.c
index a11bb85da3..4a060f1d82 100644
--- a/config.c
+++ b/config.c
@@ -1364,11 +1364,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_empty_or_missing_file(path)) {
+		free(path);
+		return 0;
+	}
+
+	*dest = path;
 	return 0;
 }
 
diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 4927b7260d..e28a79987d 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -46,6 +46,15 @@ test_expect_success 'nonexistent template file in config should return error' '
 	)
 '
 
+test_expect_success 'nonexistent optional template file in config' '
+	test_config commit.template ":(optional)$PWD"/notexist &&
+	(
+		GIT_EDITOR="echo hello >\"\$1\"" &&
+		export GIT_EDITOR &&
+		git commit --allow-empty
+	)
+'
+
 # From now on we'll use a template file that exists.
 TEMPLATE="$PWD"/template
 
-- 
2.47.0-148-g19c85929c5


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH 3/3] parseopt: values of pathname type can be prefixed with :(optional)
  2024-10-14 20:44             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
  2024-10-14 20:44               ` [PATCH 1/3] t7500: make each piece more independent Junio C Hamano
  2024-10-14 20:44               ` [PATCH 2/3] config: values of pathname type can be prefixed with :(optional) Junio C Hamano
@ 2024-10-14 20:44               ` Junio C Hamano
  2 siblings, 0 replies; 39+ messages in thread
From: Junio C Hamano @ 2024-10-14 20:44 UTC (permalink / raw)
  To: git

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>
---
 parse-options.c                           | 31 +++++++++++++++--------
 t/t7500-commit-template-squash-signoff.sh | 12 ++++++++-
 2 files changed, 31 insertions(+), 12 deletions(-)

diff --git a/parse-options.c b/parse-options.c
index 33bfba0ed4..7a2a3b1f08 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -75,7 +75,6 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
 {
 	const char *s, *arg;
 	const int unset = flags & OPT_UNSET;
-	int err;
 
 	if (unset && p->opt)
 		return error(_("%s takes no value"), optname(opt, flags));
@@ -131,21 +130,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 e28a79987d..c065f12baf 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -37,12 +37,22 @@ test_expect_success 'nonexistent template file should return error' '
 	)
 '
 
+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\"" &&
 		export GIT_EDITOR &&
-		test_must_fail git commit
+		test_must_fail git commit --allow-empty
 	)
 '
 
-- 
2.47.0-148-g19c85929c5


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Feature request: automatically read .git-blame-ignore-revs or allow global optional config
@ 2025-04-25 18:41 Michael Grosser
  2025-04-25 19:54 ` Eric Sunshine
  0 siblings, 1 reply; 39+ messages in thread
From: Michael Grosser @ 2025-04-25 18:41 UTC (permalink / raw)
  To: git

I have many repos where I use .git-blame-ignore-revs,
but I cannot set it globally because then I get
```
fatal: could not open object name list: .git-blame-ignore-revs
```
so please make it either the default for `git blame` to check that file,
or add a "blame: ignoreMissingFile: true" option so I an set
```
[blame]
  ignoreRevsFile = .git-blame-ignore-revs
  ignoreMissingFile: true
```
and can use this feature without constantly having to think about it

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: Feature request: automatically read .git-blame-ignore-revs or allow global optional config
  2025-04-25 18:41 Feature request: automatically read .git-blame-ignore-revs or allow global optional config Michael Grosser
@ 2025-04-25 19:54 ` Eric Sunshine
  2025-05-01 18:00   ` D. Ben Knoble
  0 siblings, 1 reply; 39+ messages in thread
From: Eric Sunshine @ 2025-04-25 19:54 UTC (permalink / raw)
  To: Michael Grosser; +Cc: git

On Fri, Apr 25, 2025 at 2:42 PM Michael Grosser
<grosser.michael@gmail.com> wrote:>
> I have many repos where I use .git-blame-ignore-revs,
> but I cannot set it globally because then I get
> ```
> fatal: could not open object name list: .git-blame-ignore-revs
> ```
> so please make it either the default for `git blame` to check that file,
> or add a "blame: ignoreMissingFile: true" option so I an set
> ```
> [blame]
>   ignoreRevsFile = .git-blame-ignore-revs
>   ignoreMissingFile: true
> ```
> and can use this feature without constantly having to think about it

Relevant threads:
https://lore.kernel.org/git/pull.1947.git.git.1745088194384.gitgitgadget@gmail.com/T/#u
https://lore.kernel.org/git/20241014204427.1712182-1-gitster@pobox.com/T/#u

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: Feature request: automatically read .git-blame-ignore-revs or allow global optional config
  2025-04-25 19:54 ` Eric Sunshine
@ 2025-05-01 18:00   ` D. Ben Knoble
  2025-05-01 18:28     ` Eric Sunshine
  0 siblings, 1 reply; 39+ messages in thread
From: D. Ben Knoble @ 2025-05-01 18:00 UTC (permalink / raw)
  To: Eric Sunshine; +Cc: Michael Grosser, git

On Fri, Apr 25, 2025 at 3:55 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
>
> On Fri, Apr 25, 2025 at 2:42 PM Michael Grosser
> <grosser.michael@gmail.com> wrote:>
> > I have many repos where I use .git-blame-ignore-revs,
> > but I cannot set it globally because then I get
> > ```
> > fatal: could not open object name list: .git-blame-ignore-revs
> > ```
> > so please make it either the default for `git blame` to check that file,
> > or add a "blame: ignoreMissingFile: true" option so I an set
> > ```
> > [blame]
> >   ignoreRevsFile = .git-blame-ignore-revs
> >   ignoreMissingFile: true
> > ```
> > and can use this feature without constantly having to think about it
>
> Relevant threads:
> https://lore.kernel.org/git/pull.1947.git.git.1745088194384.gitgitgadget@gmail.com/T/#u
> https://lore.kernel.org/git/20241014204427.1712182-1-gitster@pobox.com/T/#u

Did that second set of patches ever go anywhere? It seems similar to
what Junio proposed in the first linked thread and possibly worth
resurrecting. (A log --grep :(optional) didn't find anything.)

-- 
D. Ben Knoble

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: Feature request: automatically read .git-blame-ignore-revs or allow global optional config
  2025-05-01 18:00   ` D. Ben Knoble
@ 2025-05-01 18:28     ` Eric Sunshine
  0 siblings, 0 replies; 39+ messages in thread
From: Eric Sunshine @ 2025-05-01 18:28 UTC (permalink / raw)
  To: D. Ben Knoble; +Cc: Michael Grosser, git

On Thu, May 1, 2025 at 2:00 PM D. Ben Knoble <ben.knoble@gmail.com> wrote:
> On Fri, Apr 25, 2025 at 3:55 PM Eric Sunshine <sunshine@sunshineco.com> wrote:
> > On Fri, Apr 25, 2025 at 2:42 PM Michael Grosser
> > <grosser.michael@gmail.com> wrote:>
> > > so please make it either the default for `git blame` to check that file,
> > > or add a "blame: ignoreMissingFile: true" option so I an set
> >
> > Relevant threads:
> > https://lore.kernel.org/git/pull.1947.git.git.1745088194384.gitgitgadget@gmail.com/T/#u
> > https://lore.kernel.org/git/20241014204427.1712182-1-gitster@pobox.com/T/#u
>
> Did that second set of patches ever go anywhere? It seems similar to
> what Junio proposed in the first linked thread and possibly worth
> resurrecting. (A log --grep :(optional) didn't find anything.)

The patches Junio submitted[1] did not go anywhere. The topic sat in
his tree for several "What's Cooking" reports and then he dropped it
because it received no response from reviewers[2]. I agree that it
could be a useful enhancement if resurrected.

[1]: https://lore.kernel.org/git/20241014204427.1712182-1-gitster@pobox.com/T/#u
[2]: https://lore.kernel.org/git/CAPig+cT1BNXRotrz=rnVgvhQjZZwYgsAOQMonHFFTPfK-C0LOQ@mail.gmail.com/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH 0/3] specifying a file that can optionally exist
  2021-08-09 15:47           ` Junio C Hamano
  2024-10-14 20:44             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
@ 2025-05-01 21:40             ` Junio C Hamano
  2025-05-01 21:40               ` [PATCH 1/3] t7500: make each piece more independent Junio C Hamano
                                 ` (3 more replies)
  1 sibling, 4 replies; 39+ messages in thread
From: Junio C Hamano @ 2025-05-01 21:40 UTC (permalink / raw)
  To: git

In a discussion some years ago (cf. <xmqq5ywehb69.fsf@gitster.g>),
we wondered if it is a good idea to allow a configuration variable
(or a command line option for that matter) to name an "optional"
file, and pretend as if such a configuration setting or a command
line option was not even given when the named file did not exist or
empty.  Then I floated a set of patches to implement the feature,
but the topic did not get any traction and was dropped.

I am resurrecting the patches after seeing some interest in it in
recent discussion threads; it would be easier for people to
comment on, if they are in more recent parts of their mailbox.
I didn't change anything in the patch; they are verbatim copies
that I happened to have found lying somewhere in my filesystem.

Even though I updated the documentation for the configuration
variables, I didn't find a good central place to do the same for
parse-options.  I'll leave it as an exercise for the readers ;-).

The first patch is a preliminary clean-up for test script that is
used to house tests added by the later patches.

The second patch is for configuration variables, and the last one is
for command line options.

Junio C Hamano (3):
  t7500: make each piece more independent
  config: values of pathname type can be prefixed with :(optional)
  parseopt: values of pathname type can be prefixed with :(optional)

 Documentation/config.txt                  |  5 +++-
 config.c                                  | 16 +++++++++--
 parse-options.c                           | 31 +++++++++++++-------
 t/t7500-commit-template-squash-signoff.sh | 35 +++++++++++++++++------
 4 files changed, 65 insertions(+), 22 deletions(-)

-- 
2.47.0-148-g19c85929c5


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH 1/3] t7500: make each piece more independent
  2025-05-01 21:40             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
@ 2025-05-01 21:40               ` Junio C Hamano
  2025-05-01 21:40               ` [PATCH 2/3] config: values of pathname type can be prefixed with :(optional) Junio C Hamano
                                 ` (2 subsequent siblings)
  3 siblings, 0 replies; 39+ messages in thread
From: Junio C Hamano @ 2025-05-01 21:40 UTC (permalink / raw)
  To: git

These tests prepare the working tree & index state to have something
to be committed, and try a sequence of "test_must_fail git commit".
If an earlier one did not fail by a bug, a later one will fail for
a wrong reason (namely, "nothing to commit").

Give them "--allow-empty" to make sure that they would work even
when there is nothing to commit by accident.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t7500-commit-template-squash-signoff.sh | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 4dca8d97a7..4927b7260d 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -50,33 +50,33 @@ test_expect_success 'nonexistent template file in config should return error' '
 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"
 '
-- 
2.47.0-148-g19c85929c5


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-05-01 21:40             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
  2025-05-01 21:40               ` [PATCH 1/3] t7500: make each piece more independent Junio C Hamano
@ 2025-05-01 21:40               ` Junio C Hamano
  2025-05-02  8:52                 ` Patrick Steinhardt
  2025-05-01 21:40               ` [PATCH 3/3] parseopt: " Junio C Hamano
  2025-09-28 21:29               ` [PATCH v2 0/3] Support :(optional) filepaths D. Ben Knoble
  3 siblings, 1 reply; 39+ messages in thread
From: Junio C Hamano @ 2025-05-01 21:40 UTC (permalink / raw)
  To: git

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 or
an empty file is not even seen.

cf. <xmqq5ywehb69.fsf@gitster.g>

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/config.txt                  |  5 ++++-
 config.c                                  | 16 ++++++++++++++--
 t/t7500-commit-template-squash-signoff.sh |  9 +++++++++
 3 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 8c0b3ed807..199e29ccea 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -358,7 +358,10 @@ 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 or names an
+empty file.
 
 Variables
 ~~~~~~~~~
diff --git a/config.c b/config.c
index a11bb85da3..4a060f1d82 100644
--- a/config.c
+++ b/config.c
@@ -1364,11 +1364,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_empty_or_missing_file(path)) {
+		free(path);
+		return 0;
+	}
+
+	*dest = path;
 	return 0;
 }
 
diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 4927b7260d..e28a79987d 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -46,6 +46,15 @@ test_expect_success 'nonexistent template file in config should return error' '
 	)
 '
 
+test_expect_success 'nonexistent optional template file in config' '
+	test_config commit.template ":(optional)$PWD"/notexist &&
+	(
+		GIT_EDITOR="echo hello >\"\$1\"" &&
+		export GIT_EDITOR &&
+		git commit --allow-empty
+	)
+'
+
 # From now on we'll use a template file that exists.
 TEMPLATE="$PWD"/template
 
-- 
2.47.0-148-g19c85929c5


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH 3/3] parseopt: values of pathname type can be prefixed with :(optional)
  2025-05-01 21:40             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
  2025-05-01 21:40               ` [PATCH 1/3] t7500: make each piece more independent Junio C Hamano
  2025-05-01 21:40               ` [PATCH 2/3] config: values of pathname type can be prefixed with :(optional) Junio C Hamano
@ 2025-05-01 21:40               ` Junio C Hamano
  2025-09-28 21:29               ` [PATCH v2 0/3] Support :(optional) filepaths D. Ben Knoble
  3 siblings, 0 replies; 39+ messages in thread
From: Junio C Hamano @ 2025-05-01 21:40 UTC (permalink / raw)
  To: git

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>
---
 parse-options.c                           | 31 +++++++++++++++--------
 t/t7500-commit-template-squash-signoff.sh | 12 ++++++++-
 2 files changed, 31 insertions(+), 12 deletions(-)

diff --git a/parse-options.c b/parse-options.c
index 33bfba0ed4..7a2a3b1f08 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -75,7 +75,6 @@ static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
 {
 	const char *s, *arg;
 	const int unset = flags & OPT_UNSET;
-	int err;
 
 	if (unset && p->opt)
 		return error(_("%s takes no value"), optname(opt, flags));
@@ -131,21 +130,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 e28a79987d..c065f12baf 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -37,12 +37,22 @@ test_expect_success 'nonexistent template file should return error' '
 	)
 '
 
+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\"" &&
 		export GIT_EDITOR &&
-		test_must_fail git commit
+		test_must_fail git commit --allow-empty
 	)
 '
 
-- 
2.47.0-148-g19c85929c5


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-05-01 21:40               ` [PATCH 2/3] config: values of pathname type can be prefixed with :(optional) Junio C Hamano
@ 2025-05-02  8:52                 ` Patrick Steinhardt
  2025-05-02 14:28                   ` Phillip Wood
  2025-05-02 20:05                   ` Junio C Hamano
  0 siblings, 2 replies; 39+ messages in thread
From: Patrick Steinhardt @ 2025-05-02  8:52 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Thu, May 01, 2025 at 02:40:56PM -0700, Junio C Hamano wrote:
> 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 or
> an empty file is not even seen.
> 
> cf. <xmqq5ywehb69.fsf@gitster.g>
> 
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  Documentation/config.txt                  |  5 ++++-
>  config.c                                  | 16 ++++++++++++++--
>  t/t7500-commit-template-squash-signoff.sh |  9 +++++++++
>  3 files changed, 27 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/config.txt b/Documentation/config.txt
> index 8c0b3ed807..199e29ccea 100644
> --- a/Documentation/config.txt
> +++ b/Documentation/config.txt
> @@ -358,7 +358,10 @@ 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 or names an
> +empty file.

I can see why it may be useful to allow for non-existent paths. But I
wonder whether we really should be skipping over empty files, as well,
as it may be assuming too much about the semantics of a given config
key. In other words, are we reasonably sure that there won't ever be a
usecase where you may want to specify an optional and empty file? And
are there any use cases where an empty file should be ignored?

Patrick

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-05-02  8:52                 ` Patrick Steinhardt
@ 2025-05-02 14:28                   ` Phillip Wood
  2025-05-02 20:05                   ` Junio C Hamano
  1 sibling, 0 replies; 39+ messages in thread
From: Phillip Wood @ 2025-05-02 14:28 UTC (permalink / raw)
  To: Patrick Steinhardt, Junio C Hamano; +Cc: git

On 02/05/2025 09:52, Patrick Steinhardt wrote:
> On Thu, May 01, 2025 at 02:40:56PM -0700, Junio C Hamano wrote:
> 
>> ++
>> +If prefixed with `:(optional)`, the configuration variable is treated
>> +as if it does not exist, if the named path does not exist or names an
>> +empty file.
> 
> I can see why it may be useful to allow for non-existent paths. But I
> wonder whether we really should be skipping over empty files, as well,
> as it may be assuming too much about the semantics of a given config
> key. In other words, are we reasonably sure that there won't ever be a
> usecase where you may want to specify an optional and empty file? And
> are there any use cases where an empty file should be ignored?

That's my thought too - ignoring a missing file sounds like a good idea 
but why an empty file too?

Best Wishes

Phillip


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-05-02  8:52                 ` Patrick Steinhardt
  2025-05-02 14:28                   ` Phillip Wood
@ 2025-05-02 20:05                   ` Junio C Hamano
  1 sibling, 0 replies; 39+ messages in thread
From: Junio C Hamano @ 2025-05-02 20:05 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git

Patrick Steinhardt <ps@pks.im> writes:

> I can see why it may be useful to allow for non-existent paths. But I
> wonder whether we really should be skipping over empty files, as well,
> as it may be assuming too much about the semantics of a given config
> key. In other words, are we reasonably sure that there won't ever be a
> usecase where you may want to specify an optional and empty file? And
> are there any use cases where an empty file should be ignored?

If somebody goes back to the original discussion that happened a few
years before the patches were originally written, they might find a
use case where it is more convenient to ignore an empty file, but it
is an old patch series, so I do not remember the details.

I would not be surprised if the design decision for an empty blob
was done without any deep thought or motivationg use case.  After
all, this was "I had nothing better to do, so wrote these out of
boredom" patchset, as its cover letter said.

If somebody wants to carry these patches forward (which I am hoping
because there were a few people who expressed interest recently, and
because I am not all that interested, certainly not more than those
who wanted to have this feature), I think that the right approach is
to extend the system by taking advantage of the syntax that was
designed to be extensible.  In addition to ":(optional)", we could
add different variants like ":(optional,ignore-empty)" with desired
semantics, for example.

Thanks.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH v2 0/3] Support :(optional) filepaths
  2025-05-01 21:40             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
                                 ` (2 preceding siblings ...)
  2025-05-01 21:40               ` [PATCH 3/3] parseopt: " Junio C Hamano
@ 2025-09-28 21:29               ` D. Ben Knoble
  2025-09-28 21:29                 ` [PATCH v2 1/3] t7500: make each piece more independent D. Ben Knoble
                                   ` (3 more replies)
  3 siblings, 4 replies; 39+ messages in thread
From: D. Ben Knoble @ 2025-09-28 21:29 UTC (permalink / raw)
  To: git
  Cc: D. Ben Knoble, Junio C Hamano, Noah Pendleton, Patrick Steinhardt,
	Phillip Wood, Thranur Andul, Michael Grosser, Eric Sunshine

Notes:
- Based on commit 2da08f2c3d (parseopt: values of pathname type can be
  prefixed with :(optional), 2024-10-14) (broken-out/wip/optional-path)
- Rebased on v2.51.0
- I'm least sure of the 3rd patch and am happy to drop it in support of
  the first 2. I think it might be better to (a) integrate :(optional)
  support as pathspec magic and (b) use pathspec magic in parse-options
  when getting filenames. But I'm not sure, and this has other
  ramifications I'm not prepared to deal with. (For example: `git grep
  path <file>… :(optional)non-existent` could pretend like
  `non-existent` was never given?)
- The parsing is not exactly a "clean API," but I wasn't sure how to
  make it cleaner :)

Changes in v2:
- Only check for missing files, not empty files
- Move a test change to the appropriate commit
- Document optional magic in options in gitcli(1)

This series adds support for optional filepaths in config and
parse-options, which supports use-cases such as missing commit templates
or blame.ignoreRevsFile values without erroring.

v1: https://lore.kernel.org/git/20250501214057.371711-1-gitster@pobox.com/

Junio C Hamano (3):
  t7500: make each piece more independent
  config: values of pathname type can be prefixed with :(optional)
  parseopt: values of pathname type can be prefixed with :(optional)

 Documentation/config.adoc                 |  4 ++-
 Documentation/gitcli.adoc                 | 14 +++++++++
 config.c                                  | 16 +++++++++--
 parse-options.c                           | 31 +++++++++++++-------
 t/t7500-commit-template-squash-signoff.sh | 35 +++++++++++++++++------
 wrapper.c                                 | 13 +++++++++
 wrapper.h                                 |  4 ++-
 7 files changed, 94 insertions(+), 23 deletions(-)

Diff-intervalle contre v1 :
1:  82d283c626 ! 1:  63b2b24d42 t7500: make each piece more independent
    @@ Commit message
         Signed-off-by: Taylor Blau <me@ttaylorr.com>
     
      ## t/t7500-commit-template-squash-signoff.sh ##
    +@@ t/t7500-commit-template-squash-signoff.sh: commit_msg_is ()
    + 	(
    + 		GIT_EDITOR="echo hello >\"\$1\"" &&
    + 		export GIT_EDITOR &&
    +-		test_must_fail git commit
    ++		test_must_fail git commit --allow-empty
    + 	)
    + '
    + 
     @@ t/t7500-commit-template-squash-signoff.sh: commit_msg_is ()
      TEMPLATE="$PWD"/template
      
2:  dbafaff13b ! 2:  5c97f580a9 config: values of pathname type can be prefixed with :(optional)
    @@ Commit message
         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 or
    -    an empty file is not even seen.
    +    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>
     
    - ## Documentation/config.txt ##
    -@@ Documentation/config.txt: compiled without runtime prefix support, the compiled-in prefix will be
    +
    + ## Notes ##
    +    The 2nd paragraph in this commit is wrapped strangely
    +
    +    I've kept the strange wrapping length for now, but can reflow it if
    +    desired.
    +
    + ## Documentation/config.adoc ##
    +@@ Documentation/config.adoc: 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 or names an
    -+empty file.
    ++as if it does not exist, if the named path does not exist.
      
      Variables
      ~~~~~~~~~
    @@ config.c: int git_config_string(char **dest, const char *var, const char *value)
     +	if (!path)
      		die(_("failed to expand user dir in: '%s'"), value);
     +
    -+	if (is_optional && is_empty_or_missing_file(path)) {
    ++	if (is_optional && is_missing_file(path)) {
     +		free(path);
     +		return 0;
     +	}
    @@ t/t7500-commit-template-squash-signoff.sh: commit_msg_is ()
      # From now on we'll use a template file that exists.
      TEMPLATE="$PWD"/template
      
    +
    + ## wrapper.c ##
    +@@ wrapper.c: 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;
    +
    + ## wrapper.h ##
    +@@ wrapper.h: 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 {
3:  2da08f2c3d ! 3:  5f7057c236 parseopt: values of pathname type can be prefixed with :(optional)
    @@ Commit message
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
         Signed-off-by: Taylor Blau <me@ttaylorr.com>
     
    + ## Documentation/gitcli.adoc ##
    +@@ Documentation/gitcli.adoc: $ 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
    + ------------------------------------
    + 
    +
      ## parse-options.c ##
     @@ parse-options.c: static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p,
      {
    - 	const char *s, *arg;
    + 	const char *arg;
      	const int unset = flags & OPT_UNSET;
     -	int err;
      
    @@ t/t7500-commit-template-squash-signoff.sh: commit_msg_is ()
      test_expect_success 'nonexistent template file in config should return error' '
      	test_config commit.template "$PWD"/notexist &&
      	(
    - 		GIT_EDITOR="echo hello >\"\$1\"" &&
    - 		export GIT_EDITOR &&
    --		test_must_fail git commit
    -+		test_must_fail git commit --allow-empty
    - 	)
    - '
    - 

base-commit: c44beea485f0f2feaf460e2ac87fdd5608d63cf0
-- 
2.48.1


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH v2 1/3] t7500: make each piece more independent
  2025-09-28 21:29               ` [PATCH v2 0/3] Support :(optional) filepaths D. Ben Knoble
@ 2025-09-28 21:29                 ` D. Ben Knoble
  2025-09-28 21:29                 ` [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional) D. Ben Knoble
                                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 39+ messages in thread
From: D. Ben Knoble @ 2025-09-28 21:29 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Noah Pendleton, Patrick Steinhardt, Phillip Wood,
	Thranur Andul, Michael Grosser, Eric Sunshine, Taylor Blau,
	D. Ben Knoble

From: Junio C Hamano <gitster@pobox.com>

These tests prepare the working tree & index state to have something
to be committed, and try a sequence of "test_must_fail git commit".
If an earlier one did not fail by a bug, a later one will fail for
a wrong reason (namely, "nothing to commit").

Give them "--allow-empty" to make sure that they would work even
when there is nothing to commit by accident.

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>
---
 t/t7500-commit-template-squash-signoff.sh | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 4dca8d97a7..05cda50186 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -42,7 +42,7 @@ commit_msg_is ()
 	(
 		GIT_EDITOR="echo hello >\"\$1\"" &&
 		export GIT_EDITOR &&
-		test_must_fail git commit
+		test_must_fail git commit --allow-empty
 	)
 '
 
@@ -50,33 +50,33 @@ commit_msg_is ()
 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"
 '
-- 
2.48.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-09-28 21:29               ` [PATCH v2 0/3] Support :(optional) filepaths D. Ben Knoble
  2025-09-28 21:29                 ` [PATCH v2 1/3] t7500: make each piece more independent D. Ben Knoble
@ 2025-09-28 21:29                 ` D. Ben Knoble
  2025-09-30 15:26                   ` Phillip Wood
  2025-09-28 21:29                 ` [PATCH v2 3/3] parseopt: " D. Ben Knoble
  2025-09-28 22:40                 ` [PATCH v2 0/3] Support :(optional) filepaths Junio C Hamano
  3 siblings, 1 reply; 39+ messages in thread
From: D. Ben Knoble @ 2025-09-28 21:29 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Noah Pendleton, Patrick Steinhardt, Phillip Wood,
	Thranur Andul, Michael Grosser, Eric Sunshine, Taylor Blau,
	D. Ben Knoble, Matheus Tavares, Johannes Schindelin, Calvin Wan,
	brian m. carlson, Martin Ågren

From: Junio C Hamano <gitster@pobox.com>

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>
---

Notes:
    The 2nd paragraph in this commit is wrapped strangely
    
    I've kept the strange wrapping length for now, but can reflow it if
    desired.

 Documentation/config.adoc                 |  4 +++-
 config.c                                  | 16 ++++++++++++++--
 t/t7500-commit-template-squash-signoff.sh |  9 +++++++++
 wrapper.c                                 | 13 +++++++++++++
 wrapper.h                                 |  4 +++-
 5 files changed, 42 insertions(+), 4 deletions(-)

diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index cc769251be..7301ced836 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -358,7 +358,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/config.c b/config.c
index 97ffef4270..73fc74c8fa 100644
--- a/config.c
+++ b/config.c
@@ -1279,11 +1279,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/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 05cda50186..366f7f23b3 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -46,6 +46,15 @@ commit_msg_is ()
 	)
 '
 
+test_expect_success 'nonexistent optional template file in config' '
+	test_config commit.template ":(optional)$PWD"/notexist &&
+	(
+		GIT_EDITOR="echo hello >\"\$1\"" &&
+		export GIT_EDITOR &&
+		git commit --allow-empty
+	)
+'
+
 # From now on we'll use a template file that exists.
 TEMPLATE="$PWD"/template
 
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 {
-- 
2.48.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v2 3/3] parseopt: values of pathname type can be prefixed with :(optional)
  2025-09-28 21:29               ` [PATCH v2 0/3] Support :(optional) filepaths D. Ben Knoble
  2025-09-28 21:29                 ` [PATCH v2 1/3] t7500: make each piece more independent D. Ben Knoble
  2025-09-28 21:29                 ` [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional) D. Ben Knoble
@ 2025-09-28 21:29                 ` D. Ben Knoble
  2025-09-30 15:26                   ` Phillip Wood
  2025-09-28 22:40                 ` [PATCH v2 0/3] Support :(optional) filepaths Junio C Hamano
  3 siblings, 1 reply; 39+ messages in thread
From: D. Ben Knoble @ 2025-09-28 21:29 UTC (permalink / raw)
  To: git
  Cc: Junio C Hamano, Noah Pendleton, Patrick Steinhardt, Phillip Wood,
	Thranur Andul, Michael Grosser, Eric Sunshine, Taylor Blau,
	D. Ben Knoble

From: Junio C Hamano <gitster@pobox.com>

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>
---
 Documentation/gitcli.adoc                 | 14 ++++++++++
 parse-options.c                           | 31 +++++++++++++++--------
 t/t7500-commit-template-squash-signoff.sh | 10 ++++++++
 3 files changed, 44 insertions(+), 11 deletions(-)

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/parse-options.c b/parse-options.c
index 5224203ffe..4faf66023a 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 366f7f23b3..c065f12baf 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -37,6 +37,16 @@ commit_msg_is ()
 	)
 '
 
+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 &&
 	(
-- 
2.48.1


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 0/3] Support :(optional) filepaths
  2025-09-28 21:29               ` [PATCH v2 0/3] Support :(optional) filepaths D. Ben Knoble
                                   ` (2 preceding siblings ...)
  2025-09-28 21:29                 ` [PATCH v2 3/3] parseopt: " D. Ben Knoble
@ 2025-09-28 22:40                 ` Junio C Hamano
  2025-09-29 16:42                   ` Ben Knoble
  3 siblings, 1 reply; 39+ messages in thread
From: Junio C Hamano @ 2025-09-28 22:40 UTC (permalink / raw)
  To: D. Ben Knoble
  Cc: git, Noah Pendleton, Patrick Steinhardt, Phillip Wood,
	Thranur Andul, Michael Grosser, Eric Sunshine

"D. Ben Knoble" <ben.knoble+github@gmail.com> writes:

Before "notes" you would want an overall description of what the
topic is for those who no longer remember the previous iteration,
or for those this iteration is the first one they see.

> Notes:
> - Based on commit 2da08f2c3d (parseopt: values of pathname type can be
>   prefixed with :(optional), 2024-10-14) (broken-out/wip/optional-path)
> - Rebased on v2.51.0

Thanks.

> - I'm least sure of the 3rd patch and am happy to drop it in support of
>   the first 2. I think it might be better to (a) integrate :(optional)
>   support as pathspec magic and (b) use pathspec magic in parse-options
>   when getting filenames. But I'm not sure, and this has other
>   ramifications I'm not prepared to deal with. (For example: `git grep
>   path <file>… :(optional)non-existent` could pretend like
>   `non-existent` was never given?)

While it might not hurt, I do not see a need for such a support.

Pathspec _is_ a pattern.  If an existing path does not match the
pattern, there is no ill effect.  In other words, in this command
invocation:

    $ git grep -e needle -- Makefile no-such-file.txt

neither Makefile or no-such-file.txt is required nor optional.  If
there are paths that match these two "patterns" among the paths in
the working tree that are known to the index, the contents of these
paths are inspected by the command.  If no paths match the patterns,
that is fine as well.

The command line parser helpfully offers to notice a pathspec
pattern that did not match any path when you do not give "--", but
that is up to the caller of match_pathspec() API to do so.  The
pathspec machinery only reports if each pathspec element matched a
path in its seen[] array, and the caller can use that information to
report which pathspec elements did not contribute to finding the set
of paths to work on.

> - The parsing is not exactly a "clean API," but I wasn't sure how to
>   make it cleaner :)

What you have in [2/3], the update to git_config_pathname(), seems
quite reasonable and something that cannot be made cleaner, to me.

> Changes in v2:
> - Only check for missing files, not empty files
> - Move a test change to the appropriate commit
> - Document optional magic in options in gitcli(1)

I agree that it is a better design not to special case an empty file
like the previous round did.  Looking better.

> This series adds support for optional filepaths in config and
> parse-options, which supports use-cases such as missing commit templates
> or blame.ignoreRevsFile values without erroring.

Yes, this is what you wanted to have at the very beginning, before
listing points you want to call attention to under "Notes" label.

Will queue.  Thanks for resurrecting the topic.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 0/3] Support :(optional) filepaths
  2025-09-28 22:40                 ` [PATCH v2 0/3] Support :(optional) filepaths Junio C Hamano
@ 2025-09-29 16:42                   ` Ben Knoble
  0 siblings, 0 replies; 39+ messages in thread
From: Ben Knoble @ 2025-09-29 16:42 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: D. Ben Knoble, git, Noah Pendleton, Patrick Steinhardt,
	Phillip Wood, Thranur Andul, Michael Grosser, Eric Sunshine


> Le 28 sept. 2025 à 18:40, Junio C Hamano <gitster@pobox.com> a écrit :
> 
> "D. Ben Knoble" <ben.knoble+github@gmail.com> writes:
> 
> Before "notes" you would want an overall description of what the
> topic is for those who no longer remember the previous iteration,
> or for those this iteration is the first one they see.

Agreed, thanks.

>> - I'm least sure of the 3rd patch and am happy to drop it in support of
>>  the first 2. I think it might be better to (a) integrate :(optional)
>>  support as pathspec magic and (b) use pathspec magic in parse-options
>>  when getting filenames. But I'm not sure, and this has other
>>  ramifications I'm not prepared to deal with. (For example: `git grep
>>  path <file>… :(optional)non-existent` could pretend like
>>  `non-existent` was never given?)
> 
> While it might not hurt, I do not see a need for such a support.
> 
> Pathspec _is_ a pattern.  If an existing path does not match the
> pattern, there is no ill effect.  In other words, in this command
> invocation:
> 
>    $ git grep -e needle -- Makefile no-such-file.txt
> 
> neither Makefile or no-such-file.txt is required nor optional.  If
> there are paths that match these two "patterns" among the paths in
> the working tree that are known to the index, the contents of these
> paths are inspected by the command.  If no paths match the patterns,
> that is fine as well.
> 
> The command line parser helpfully offers to notice a pathspec
> pattern that did not match any path when you do not give "--", but
> that is up to the caller of match_pathspec() API to do so.  The
> pathspec machinery only reports if each pathspec element matched a
> path in its seen[] array, and the caller can use that information to
> report which pathspec elements did not contribute to finding the set
> of paths to work on.

I must have been thinking of the case without --, which triggers the usual ambiguity error. Either way, for now, I think a smaller feature is better :)

> Will queue.  Thanks for resurrecting the topic.

Thanks!

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-09-28 21:29                 ` [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional) D. Ben Knoble
@ 2025-09-30 15:26                   ` Phillip Wood
  2025-10-06 19:00                     ` Junio C Hamano
  0 siblings, 1 reply; 39+ messages in thread
From: Phillip Wood @ 2025-09-30 15:26 UTC (permalink / raw)
  To: D. Ben Knoble, git
  Cc: Junio C Hamano, Noah Pendleton, Patrick Steinhardt, Thranur Andul,
	Michael Grosser, Eric Sunshine, Taylor Blau, Matheus Tavares,
	Johannes Schindelin, Calvin Wan, brian m. carlson,
	Martin Ågren

Hi Ben

On 28/09/2025 22:29, D. Ben Knoble wrote:
> From: Junio C Hamano <gitster@pobox.com>
> 
> 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.

I think this would be a useful addition, we've had several people 
wanting to make blame.ignoreRevsFile optional and this provides a 
general way to do that.

> --- a/config.c
> +++ b/config.c
> @@ -1279,11 +1279,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;

This could be bool rather than int, the rest of the implementation looks 
good.

> --- a/t/t7500-commit-template-squash-signoff.sh
> +++ b/t/t7500-commit-template-squash-signoff.sh
> @@ -46,6 +46,15 @@ commit_msg_is ()
>   	)
>   '
>   
> +test_expect_success 'nonexistent optional template file in config' '
> +	test_config commit.template ":(optional)$PWD"/notexist &&
> +	(
> +		GIT_EDITOR="echo hello >\"\$1\"" &&

when git runs the editor this will be expanded to

     sh -c 'echo hello >"$1" "$@"' 'echo hello >"$1"' path/to/file

I think it should be

     GIT_EDITOR="echo hello >"

instead
> +		export GIT_EDITOR &&
> +		git commit --allow-empty

Maybe I'm missing something but don't we want to ensure that we have a 
non-empty message here? Also as it is a single command we can avoid the 
subshell with

     GIT_EDITOR="echo hello >" git commit

Thanks

Phillip


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 3/3] parseopt: values of pathname type can be prefixed with :(optional)
  2025-09-28 21:29                 ` [PATCH v2 3/3] parseopt: " D. Ben Knoble
@ 2025-09-30 15:26                   ` Phillip Wood
  0 siblings, 0 replies; 39+ messages in thread
From: Phillip Wood @ 2025-09-30 15:26 UTC (permalink / raw)
  To: D. Ben Knoble, git
  Cc: Junio C Hamano, Noah Pendleton, Patrick Steinhardt, Thranur Andul,
	Michael Grosser, Eric Sunshine, Taylor Blau

Hi Ben

On 28/09/2025 22:29, D. Ben Knoble wrote:
> From: Junio C Hamano <gitster@pobox.com>
> 
> 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.

Sounds sensible

> +Magic filename options

I assume we're calling these "magic" to match to pathspec "magic" 
options? I wonder if that is a good idea but I don't have a better 
suggestion.

> +~~~~~~~~~~~~~~~~~~~~~~
> +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

This doesn't quite scan for me, maybe s/, /, it is/ ?

> +git commit
> +----------------------------
> +
> +Like with configuration values, if the named file is missing Git behaves as if

I'd drop "with" here

> +the option was not given at all. See "Values" in linkgit:git-config[1].
> +

> @@ -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;

This can be a bool as in the last patch.

>   		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;

I'm not sure why we're changing the cast here (or why we need one in the 
first place assuming opt->defval is "void*")

> +		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;

I'm struggling to see how value can be NULL here as we return early if 
it NULL before calling skip_prefix()

> +		value = fix_filename(p->prefix, value);
> +		if (is_optional && is_empty_or_missing_file(value)) {
> +			free((char *)value);

I think we want to call is_missing_file() here. If the file is missing 
then we do nothing which matches the documentation above - Good.

> +		} else {
> +			FREE_AND_NULL(*(char **)opt->value);
> +			*(const char **)opt->value = value;

If the file isn't optional or it is optional and exists then we behave 
as before - Good.

Thanks

Phillip

> +		}
> +		return 0;
>   	}
>   	case OPTION_CALLBACK:
>   	{
> diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
> index 366f7f23b3..c065f12baf 100755
> --- a/t/t7500-commit-template-squash-signoff.sh
> +++ b/t/t7500-commit-template-squash-signoff.sh
> @@ -37,6 +37,16 @@ commit_msg_is ()
>   	)
>   '
>   
> +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 &&
>   	(


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-09-30 15:26                   ` Phillip Wood
@ 2025-10-06 19:00                     ` Junio C Hamano
  2025-10-06 19:59                       ` Junio C Hamano
  0 siblings, 1 reply; 39+ messages in thread
From: Junio C Hamano @ 2025-10-06 19:00 UTC (permalink / raw)
  To: Phillip Wood
  Cc: D. Ben Knoble, git, Noah Pendleton, Patrick Steinhardt,
	Thranur Andul, Michael Grosser, Eric Sunshine, Taylor Blau,
	Matheus Tavares, Johannes Schindelin, Calvin Wan,
	brian m. carlson, Martin Ågren

Phillip Wood <phillip.wood123@gmail.com> writes:

>> +	test_config commit.template ":(optional)$PWD"/notexist &&
>> +	(
>> +		GIT_EDITOR="echo hello >\"\$1\"" &&
>
> when git runs the editor this will be expanded to
>
>     sh -c 'echo hello >"$1" "$@"' 'echo hello >"$1"' path/to/file
>
> I think it should be
>
>     GIT_EDITOR="echo hello >"
>
> instead

That's interesting in that I find it unusual.  Fine as long as it
works ;-)

> Maybe I'm missing something but don't we want to ensure that we have a
> non-empty message here? Also as it is a single command we can avoid
> the subshell with
>
>     GIT_EDITOR="echo hello >" git commit

Yeah, that does sound better.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-10-06 19:00                     ` Junio C Hamano
@ 2025-10-06 19:59                       ` Junio C Hamano
  2025-10-06 20:21                         ` Junio C Hamano
  0 siblings, 1 reply; 39+ messages in thread
From: Junio C Hamano @ 2025-10-06 19:59 UTC (permalink / raw)
  To: Phillip Wood
  Cc: D. Ben Knoble, git, Noah Pendleton, Patrick Steinhardt,
	Thranur Andul, Michael Grosser, Eric Sunshine, Taylor Blau,
	Matheus Tavares, Johannes Schindelin, Calvin Wan,
	brian m. carlson, Martin Ågren

Junio C Hamano <gitster@pobox.com> writes:

> Phillip Wood <phillip.wood123@gmail.com> writes:
>
>>> +	test_config commit.template ":(optional)$PWD"/notexist &&
>>> +	(
>>> +		GIT_EDITOR="echo hello >\"\$1\"" &&
>>
>> when git runs the editor this will be expanded to
>>
>>     sh -c 'echo hello >"$1" "$@"' 'echo hello >"$1"' path/to/file
>>
>> I think it should be
>>
>>     GIT_EDITOR="echo hello >"
>>
>> instead

It seems that this was a copy-paste from a few of tests before this
new piece.  They all _expect_ to fail, so probably nobody bothered
to inspect the outcome ;-)

I just looked at what actually goes to COMMIT_EDITMSG with this test
that expects to succeed.

$ cat .git/COMMIT_EDITMSG
hello /home/gitster/w/git.git/t/trash directory.t7500-commit-template-squash-signoff/.git/COMMIT_EDITMSG

So, you're right to say "$@" will be given in addition to "hello" as
arguments to "echo".  That extra argument is to tell the editor the
path to the edited file.

We'd probably need a preliminary clean-up patch to fix all of these
in the vicinity.

Thanks.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-10-06 19:59                       ` Junio C Hamano
@ 2025-10-06 20:21                         ` Junio C Hamano
  2025-10-06 20:22                           ` Junio C Hamano
  2025-10-07 12:24                           ` Kristoffer Haugsbakk
  0 siblings, 2 replies; 39+ messages in thread
From: Junio C Hamano @ 2025-10-06 20:21 UTC (permalink / raw)
  To: Phillip Wood
  Cc: D. Ben Knoble, git, Noah Pendleton, Patrick Steinhardt,
	Thranur Andul, Michael Grosser, Eric Sunshine, Taylor Blau,
	Matheus Tavares, Johannes Schindelin, Calvin Wan,
	brian m. carlson, Martin Ågren

Junio C Hamano <gitster@pobox.com> writes:

> We'd probably need a preliminary clean-up patch to fix all of these
> in the vicinity.

So, here is the preliminary clea-up step that should come before
[2/3]

--- >8 ---
Subject: [PATCH] t7500: fix GIT_EDITOR shell snippet

2140b140 (commit: error out for missing commit message template,
2011-02-25) defined

    GIT_EDITOR="echo hello >\"\$1\""

for thest two tests, with the intention that 'hello' would be
written in the given file, but as Phillip Wood points out,
GIT_EDITOR is invoked by shell after getting expanded to

    sh -c 'echo hello >"$1" "$@"' 'echo hello >"$1"' path/to/file

which is not what we want.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t7500-commit-template-squash-signoff.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 05cda50186..4922543256 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -31,7 +31,7 @@ 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
 	)
@@ -40,7 +40,7 @@ test_expect_success 'nonexistent template file should return error' '
 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 --allow-empty
 	)
-- 
2.51.0-580-g8258b70b6e


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-10-06 20:21                         ` Junio C Hamano
@ 2025-10-06 20:22                           ` Junio C Hamano
  2025-10-07 12:24                           ` Kristoffer Haugsbakk
  1 sibling, 0 replies; 39+ messages in thread
From: Junio C Hamano @ 2025-10-06 20:22 UTC (permalink / raw)
  To: Phillip Wood
  Cc: D. Ben Knoble, git, Noah Pendleton, Patrick Steinhardt,
	Thranur Andul, Michael Grosser, Eric Sunshine, Taylor Blau,
	Matheus Tavares, Johannes Schindelin, Calvin Wan,
	brian m. carlson, Martin Ågren

Junio C Hamano <gitster@pobox.com> writes:

> So, here is the preliminary clea-up step that should come before
> [2/3]
>
> --- >8 ---
> Subject: [PATCH] t7500: fix GIT_EDITOR shell snippet

And this is [2/3] rebased on top.

--- >8 ---
Subject: [PATCH] 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>
---
 Documentation/config.adoc                 |  4 +++-
 config.c                                  | 16 ++++++++++++++--
 t/t7500-commit-template-squash-signoff.sh |  8 ++++++++
 wrapper.c                                 | 13 +++++++++++++
 wrapper.h                                 |  4 +++-
 5 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index cc769251be..7301ced836 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -358,7 +358,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/config.c b/config.c
index 97ffef4270..73fc74c8fa 100644
--- a/config.c
+++ b/config.c
@@ -1279,11 +1279,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/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh
index 4922543256..a85229e556 100755
--- a/t/t7500-commit-template-squash-signoff.sh
+++ b/t/t7500-commit-template-squash-signoff.sh
@@ -46,6 +46,14 @@ test_expect_success 'nonexistent template file in config should return error' '
 	)
 '
 
+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
 
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 {
-- 
2.51.0-580-g8258b70b6e


^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-10-06 20:21                         ` Junio C Hamano
  2025-10-06 20:22                           ` Junio C Hamano
@ 2025-10-07 12:24                           ` Kristoffer Haugsbakk
  2025-10-07 17:04                             ` Junio C Hamano
  1 sibling, 1 reply; 39+ messages in thread
From: Kristoffer Haugsbakk @ 2025-10-07 12:24 UTC (permalink / raw)
  To: Junio C Hamano, Phillip Wood
  Cc: D. Ben Knoble, git, Noah Pendleton, Patrick Steinhardt,
	Thranur Andul, Michael Grosser, Eric Sunshine, Taylor Blau,
	Matheus Tavares, Johannes Schindelin, Calvin Wan,
	brian m. carlson, Martin Ågren

On Mon, Oct 6, 2025, at 22:21, Junio C Hamano wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
>> We'd probably need a preliminary clean-up patch to fix all of these
>> in the vicinity.
>
> So, here is the preliminary clea-up step that should come before
> [2/3]
>
> --- >8 ---
> Subject: [PATCH] t7500: fix GIT_EDITOR shell snippet
>
> 2140b140 (commit: error out for missing commit message template,
> 2011-02-25) defined
>
>     GIT_EDITOR="echo hello >\"\$1\""
>
> for thest two tests, with the intention that 'hello' would be

s/thest/these/

>[snip]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional)
  2025-10-07 12:24                           ` Kristoffer Haugsbakk
@ 2025-10-07 17:04                             ` Junio C Hamano
  0 siblings, 0 replies; 39+ messages in thread
From: Junio C Hamano @ 2025-10-07 17:04 UTC (permalink / raw)
  To: Kristoffer Haugsbakk
  Cc: Phillip Wood, D. Ben Knoble, git, Noah Pendleton,
	Patrick Steinhardt, Thranur Andul, Michael Grosser, Eric Sunshine,
	Taylor Blau, Matheus Tavares, Johannes Schindelin, Calvin Wan,
	brian m. carlson, Martin Ågren

"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:

> On Mon, Oct 6, 2025, at 22:21, Junio C Hamano wrote:
>> Junio C Hamano <gitster@pobox.com> writes:
>>
>>> We'd probably need a preliminary clean-up patch to fix all of these
>>> in the vicinity.
>>
>> So, here is the preliminary clea-up step that should come before
>> [2/3]
>>
>> --- >8 ---
>> Subject: [PATCH] t7500: fix GIT_EDITOR shell snippet
>>
>> 2140b140 (commit: error out for missing commit message template,
>> 2011-02-25) defined
>>
>>     GIT_EDITOR="echo hello >\"\$1\""
>>
>> for thest two tests, with the intention that 'hello' would be
>
> s/thest/these/

Thanks.  Will modify locally.

^ permalink raw reply	[flat|nested] 39+ messages in thread

end of thread, other threads:[~2025-10-07 17:04 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2021-08-07 20:27 [PATCH 0/1] blame: Skip missing ignore-revs file Noah Pendleton
2021-08-07 20:58 ` Junio C Hamano
2021-08-07 21:34   ` Noah Pendleton
2021-08-08  5:43     ` Junio C Hamano
2021-08-08 17:50       ` Junio C Hamano
2021-08-08 18:21         ` Noah Pendleton
2021-08-09 15:47           ` Junio C Hamano
2024-10-14 20:44             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
2024-10-14 20:44               ` [PATCH 1/3] t7500: make each piece more independent Junio C Hamano
2024-10-14 20:44               ` [PATCH 2/3] config: values of pathname type can be prefixed with :(optional) Junio C Hamano
2024-10-14 20:44               ` [PATCH 3/3] parseopt: " Junio C Hamano
2025-05-01 21:40             ` [PATCH 0/3] specifying a file that can optionally exist Junio C Hamano
2025-05-01 21:40               ` [PATCH 1/3] t7500: make each piece more independent Junio C Hamano
2025-05-01 21:40               ` [PATCH 2/3] config: values of pathname type can be prefixed with :(optional) Junio C Hamano
2025-05-02  8:52                 ` Patrick Steinhardt
2025-05-02 14:28                   ` Phillip Wood
2025-05-02 20:05                   ` Junio C Hamano
2025-05-01 21:40               ` [PATCH 3/3] parseopt: " Junio C Hamano
2025-09-28 21:29               ` [PATCH v2 0/3] Support :(optional) filepaths D. Ben Knoble
2025-09-28 21:29                 ` [PATCH v2 1/3] t7500: make each piece more independent D. Ben Knoble
2025-09-28 21:29                 ` [PATCH v2 2/3] config: values of pathname type can be prefixed with :(optional) D. Ben Knoble
2025-09-30 15:26                   ` Phillip Wood
2025-10-06 19:00                     ` Junio C Hamano
2025-10-06 19:59                       ` Junio C Hamano
2025-10-06 20:21                         ` Junio C Hamano
2025-10-06 20:22                           ` Junio C Hamano
2025-10-07 12:24                           ` Kristoffer Haugsbakk
2025-10-07 17:04                             ` Junio C Hamano
2025-09-28 21:29                 ` [PATCH v2 3/3] parseopt: " D. Ben Knoble
2025-09-30 15:26                   ` Phillip Wood
2025-09-28 22:40                 ` [PATCH v2 0/3] Support :(optional) filepaths Junio C Hamano
2025-09-29 16:42                   ` Ben Knoble
2022-03-04  9:51   ` [PATCH 0/1] blame: Skip missing ignore-revs file Thranur Andul
2021-08-08 17:48 ` [PATCH v2] blame: add config `blame.ignoreRevsFileIsOptional` Noah Pendleton
  -- strict thread matches above, loose matches on Subject: below --
2021-08-07 20:29 [PATCH 0/1] blame: Skip missing ignore-revs file Noah Pendleton
2025-04-25 18:41 Feature request: automatically read .git-blame-ignore-revs or allow global optional config Michael Grosser
2025-04-25 19:54 ` Eric Sunshine
2025-05-01 18:00   ` D. Ben Knoble
2025-05-01 18:28     ` Eric Sunshine

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).