git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH 00/12] show-ref: introduce mode to check for ref existence
@ 2023-10-24 13:10 Patrick Steinhardt
  2023-10-24 13:10 ` [PATCH 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
                   ` (14 more replies)
  0 siblings, 15 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:10 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 2772 bytes --]

Hi,

this patch series introduces a new `--exists` mode to git-show-ref(1) to
explicitly check for the existence of a reference, only. It tries to
address a gap in our plumbing tools: while we have a plethora of tools
to resolve revisions and thus also references, we do not have any tool
that can generically check for the existence of both direct and symoblic
references without resolving its contents.

This series has been split out of my other patch series that refactors
our test suite to reduce direct access to on-disk data structures. It is
structured as follows:

    - Patches 1-8 refactor the code to stop relying on global variables,
      addressing some smaller issues that surface. Furthermore, the
      different modes that git-show-ref(1) has are made more explicit
      such that the command becomes more extensible.

    - Patch 9 ensures that the user does not request mutually exclusive
      modes.

    - Patch 10 updates the documentation to better reflect how the modes
      are to be used.

    - Patch 11 introduces the new `--exists` mode as well as a bunch of
      tests for it.

    - Patch 12 introduces two test helpers `test_ref_exists` and
      `test_ref_missing` and updates many of our tests to use those
      instead.

I admittedly may have went a bit overboard with this series. But I had a
hard time understanding git-show-ref(1) and how the global state affects
the different modes.

Patrick

[1]: <cover.1697607222.git.ps@pks.im>

Patrick Steinhardt (12):
  builtin/show-ref: convert pattern to a local variable
  builtin/show-ref: split up different subcommands
  builtin/show-ref: fix leaking string buffer
  builtin/show-ref: fix dead code when passing patterns
  builtin/show-ref: refactor `--exclude-existing` options
  builtin/show-ref: stop using global variable to count matches
  builtin/show-ref: stop using global vars for `show_one()`
  builtin/show-ref: refactor options for patterns subcommand
  builtin/show-ref: ensure mutual exclusiveness of subcommands
  builtin/show-ref: explicitly spell out different modes in synopsis
  builtin/show-ref: add new mode to check for reference existence
  t: use git-show-ref(1) to check for ref existence

 Documentation/git-show-ref.txt |  16 +-
 builtin/show-ref.c             | 275 ++++++++++++++++++++++-----------
 t/t1403-show-ref.sh            |  70 +++++++++
 t/t1430-bad-ref-name.sh        |  27 ++--
 t/t3200-branch.sh              |  33 ++--
 t/t5521-pull-options.sh        |   4 +-
 t/t5605-clone-local.sh         |   2 +-
 t/test-lib-functions.sh        |  55 +++++++
 8 files changed, 363 insertions(+), 119 deletions(-)


base-commit: a9ecda2788e229afc9b611acaa26d0d9d4da53ed
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 01/12] builtin/show-ref: convert pattern to a local variable
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
@ 2023-10-24 13:10 ` Patrick Steinhardt
  2023-10-24 13:10 ` [PATCH 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
                   ` (13 subsequent siblings)
  14 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:10 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 3886 bytes --]

The `pattern` variable is a global variable that tracks either the
reference names (not patterns!) for the `--verify` mode or the patterns
for the non-verify mode. This is a bit confusing due to the slightly
different meanings.

Convert the variable to be local. While this does not yet fix the double
meaning of the variable, this change allows us to address it in a
subsequent patch more easily by explicitly splitting up the different
subcommands of git-show-ref(1).

Note that we introduce a `struct show_ref_data` to pass the patterns to
`show_ref()`. While this is overengineered now, we will extend this
structure in a subsequent patch.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 46 ++++++++++++++++++++++++++++------------------
 1 file changed, 28 insertions(+), 18 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 5110814f796..7efab14b96c 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -20,7 +20,6 @@ static const char * const show_ref_usage[] = {
 
 static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
 	   quiet, hash_only, abbrev, exclude_arg;
-static const char **pattern;
 static const char *exclude_existing_arg;
 
 static void show_one(const char *refname, const struct object_id *oid)
@@ -50,15 +49,21 @@ static void show_one(const char *refname, const struct object_id *oid)
 	}
 }
 
+struct show_ref_data {
+	const char **patterns;
+};
+
 static int show_ref(const char *refname, const struct object_id *oid,
-		    int flag UNUSED, void *cbdata UNUSED)
+		    int flag UNUSED, void *cbdata)
 {
+	struct show_ref_data *data = cbdata;
+
 	if (show_head && !strcmp(refname, "HEAD"))
 		goto match;
 
-	if (pattern) {
+	if (data->patterns) {
 		int reflen = strlen(refname);
-		const char **p = pattern, *m;
+		const char **p = data->patterns, *m;
 		while ((m = *p++) != NULL) {
 			int len = strlen(m);
 			if (len > reflen)
@@ -180,6 +185,9 @@ static const struct option show_ref_options[] = {
 
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
+	struct show_ref_data show_ref_data = {0};
+	const char **patterns;
+
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
@@ -188,38 +196,40 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	if (exclude_arg)
 		return exclude_existing(exclude_existing_arg);
 
-	pattern = argv;
-	if (!*pattern)
-		pattern = NULL;
+	patterns = argv;
+	if (!*patterns)
+		patterns = NULL;
 
 	if (verify) {
-		if (!pattern)
+		if (!patterns)
 			die("--verify requires a reference");
-		while (*pattern) {
+		while (*patterns) {
 			struct object_id oid;
 
-			if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) &&
-			    !read_ref(*pattern, &oid)) {
-				show_one(*pattern, &oid);
+			if ((starts_with(*patterns, "refs/") || !strcmp(*patterns, "HEAD")) &&
+			    !read_ref(*patterns, &oid)) {
+				show_one(*patterns, &oid);
 			}
 			else if (!quiet)
-				die("'%s' - not a valid ref", *pattern);
+				die("'%s' - not a valid ref", *patterns);
 			else
 				return 1;
-			pattern++;
+			patterns++;
 		}
 		return 0;
 	}
 
+	show_ref_data.patterns = patterns;
+
 	if (show_head)
-		head_ref(show_ref, NULL);
+		head_ref(show_ref, &show_ref_data);
 	if (heads_only || tags_only) {
 		if (heads_only)
-			for_each_fullref_in("refs/heads/", show_ref, NULL);
+			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
 		if (tags_only)
-			for_each_fullref_in("refs/tags/", show_ref, NULL);
+			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
 	} else {
-		for_each_ref(show_ref, NULL);
+		for_each_ref(show_ref, &show_ref_data);
 	}
 	if (!found_match) {
 		if (verify && !quiet)
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 02/12] builtin/show-ref: split up different subcommands
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
  2023-10-24 13:10 ` [PATCH 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
@ 2023-10-24 13:10 ` Patrick Steinhardt
  2023-10-24 17:55   ` Eric Sunshine
  2023-10-24 13:10 ` [PATCH 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
                   ` (12 subsequent siblings)
  14 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:10 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 4249 bytes --]

While not immediately obvious, git-show-ref(1) actually implements three
different subcommands:

    - `git show-ref <patterns>` can be used to list references that
      match a specific pattern.

    - `git show-ref --verify <refs>` can be used to list references.
      These are _not_ patterns.

    - `git show-ref --exclude-existing` can be used as a filter that
      reads references from standard input, performing some conversions
      on each of them.

Let's make this more explicit in the code by splitting up the three
subcommands into separate functions. This also allows us to address the
confusingly named `patterns` variable, which may hold either patterns or
reference names.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 100 ++++++++++++++++++++++++---------------------
 1 file changed, 53 insertions(+), 47 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 7efab14b96c..56ee3250c5f 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -104,7 +104,7 @@ static int add_existing(const char *refname,
  * (4) ignore if refname is a ref that exists in the local repository;
  * (5) otherwise output the line.
  */
-static int exclude_existing(const char *match)
+static int cmd_show_ref__exclude_existing(const char *match)
 {
 	static struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
@@ -142,6 +142,53 @@ static int exclude_existing(const char *match)
 	return 0;
 }
 
+static int cmd_show_ref__verify(const char **refs)
+{
+	if (!refs || !*refs)
+		die("--verify requires a reference");
+
+	while (*refs) {
+		struct object_id oid;
+
+		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
+		    !read_ref(*refs, &oid)) {
+			show_one(*refs, &oid);
+		}
+		else if (!quiet)
+			die("'%s' - not a valid ref", *refs);
+		else
+			return 1;
+		refs++;
+	}
+
+	return 0;
+}
+
+static int cmd_show_ref__patterns(const char **patterns)
+{
+	struct show_ref_data show_ref_data = {
+		.patterns = (patterns && *patterns) ? patterns : NULL,
+	};
+
+	if (show_head)
+		head_ref(show_ref, &show_ref_data);
+	if (heads_only || tags_only) {
+		if (heads_only)
+			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
+		if (tags_only)
+			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
+	} else {
+		for_each_ref(show_ref, &show_ref_data);
+	}
+	if (!found_match) {
+		if (verify && !quiet)
+			die("No match");
+		return 1;
+	}
+
+	return 0;
+}
+
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
 	hash_only = 1;
@@ -185,56 +232,15 @@ static const struct option show_ref_options[] = {
 
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
-	struct show_ref_data show_ref_data = {0};
-	const char **patterns;
-
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
 	if (exclude_arg)
-		return exclude_existing(exclude_existing_arg);
-
-	patterns = argv;
-	if (!*patterns)
-		patterns = NULL;
-
-	if (verify) {
-		if (!patterns)
-			die("--verify requires a reference");
-		while (*patterns) {
-			struct object_id oid;
-
-			if ((starts_with(*patterns, "refs/") || !strcmp(*patterns, "HEAD")) &&
-			    !read_ref(*patterns, &oid)) {
-				show_one(*patterns, &oid);
-			}
-			else if (!quiet)
-				die("'%s' - not a valid ref", *patterns);
-			else
-				return 1;
-			patterns++;
-		}
-		return 0;
-	}
-
-	show_ref_data.patterns = patterns;
-
-	if (show_head)
-		head_ref(show_ref, &show_ref_data);
-	if (heads_only || tags_only) {
-		if (heads_only)
-			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
-		if (tags_only)
-			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
-	} else {
-		for_each_ref(show_ref, &show_ref_data);
-	}
-	if (!found_match) {
-		if (verify && !quiet)
-			die("No match");
-		return 1;
-	}
-	return 0;
+		return cmd_show_ref__exclude_existing(exclude_existing_arg);
+	else if (verify)
+		return cmd_show_ref__verify(argv);
+	else
+		return cmd_show_ref__patterns(argv);
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 03/12] builtin/show-ref: fix leaking string buffer
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
  2023-10-24 13:10 ` [PATCH 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
  2023-10-24 13:10 ` [PATCH 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
@ 2023-10-24 13:10 ` Patrick Steinhardt
  2023-10-24 13:10 ` [PATCH 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
                   ` (11 subsequent siblings)
  14 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:10 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 1014 bytes --]

Fix a leaking string buffer in `git show-ref --exclude-existing`. While
the buffer is technically not leaking because its variable is declared
as static, there is no inherent reason why it should be.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 56ee3250c5f..761669d28de 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -106,7 +106,7 @@ static int add_existing(const char *refname,
  */
 static int cmd_show_ref__exclude_existing(const char *match)
 {
-	static struct string_list existing_refs = STRING_LIST_INIT_DUP;
+	struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
 	int matchlen = match ? strlen(match) : 0;
 
@@ -139,6 +139,8 @@ static int cmd_show_ref__exclude_existing(const char *match)
 			printf("%s\n", buf);
 		}
 	}
+
+	string_list_clear(&existing_refs, 0);
 	return 0;
 }
 
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 04/12] builtin/show-ref: fix dead code when passing patterns
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (2 preceding siblings ...)
  2023-10-24 13:10 ` [PATCH 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
@ 2023-10-24 13:10 ` Patrick Steinhardt
  2023-10-24 18:02   ` Eric Sunshine
  2023-10-24 13:10 ` [PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
                   ` (10 subsequent siblings)
  14 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:10 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 914 bytes --]

When passing patterns to `git show-ref` we have some code that will
cause us to die of `verify && !quiet` is true. But because `verify`
indicates a different subcommand of git-show-ref(1) that causes us to
execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the
condition cannot ever be true.

Let's remove this dead code.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 761669d28de..eb60f940a3c 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -182,11 +182,8 @@ static int cmd_show_ref__patterns(const char **patterns)
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
 	}
-	if (!found_match) {
-		if (verify && !quiet)
-			die("No match");
+	if (!found_match)
 		return 1;
-	}
 
 	return 0;
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (3 preceding siblings ...)
  2023-10-24 13:10 ` [PATCH 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
@ 2023-10-24 13:10 ` Patrick Steinhardt
  2023-10-24 18:48   ` Eric Sunshine
  2023-10-24 13:11 ` [PATCH 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
                   ` (9 subsequent siblings)
  14 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:10 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 5784 bytes --]

It's not immediately obvious options which options are applicable to
what subcommand ni git-show-ref(1) because all options exist as global
state. This can easily cause confusion for the reader.

Refactor options for the `--exclude-existing` subcommand to be contained
in a separate structure. This structure is stored on the stack and
passed down as required. Consequentially, it clearly delimits the scope
of those options and requires the reader to worry less about global
state.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 72 +++++++++++++++++++++++++---------------------
 1 file changed, 39 insertions(+), 33 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index eb60f940a3c..e130b999c0b 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {
 };
 
 static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
-	   quiet, hash_only, abbrev, exclude_arg;
-static const char *exclude_existing_arg;
+	   quiet, hash_only, abbrev;
 
 static void show_one(const char *refname, const struct object_id *oid)
 {
@@ -95,6 +94,11 @@ static int add_existing(const char *refname,
 	return 0;
 }
 
+struct exclude_existing_options {
+	int enabled;
+	const char *pattern;
+};
+
 /*
  * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
  * and
@@ -104,11 +108,11 @@ static int add_existing(const char *refname,
  * (4) ignore if refname is a ref that exists in the local repository;
  * (5) otherwise output the line.
  */
-static int cmd_show_ref__exclude_existing(const char *match)
+static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)
 {
 	struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
-	int matchlen = match ? strlen(match) : 0;
+	int matchlen = opts->pattern ? strlen(opts->pattern) : 0;
 
 	for_each_ref(add_existing, &existing_refs);
 	while (fgets(buf, sizeof(buf), stdin)) {
@@ -124,11 +128,11 @@ static int cmd_show_ref__exclude_existing(const char *match)
 		for (ref = buf + len; buf < ref; ref--)
 			if (isspace(ref[-1]))
 				break;
-		if (match) {
+		if (opts->pattern) {
 			int reflen = buf + len - ref;
 			if (reflen < matchlen)
 				continue;
-			if (strncmp(ref, match, matchlen))
+			if (strncmp(ref, opts->pattern, matchlen))
 				continue;
 		}
 		if (check_refname_format(ref, 0)) {
@@ -200,44 +204,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)
 static int exclude_existing_callback(const struct option *opt, const char *arg,
 				     int unset)
 {
+	struct exclude_existing_options *opts = opt->value;
 	BUG_ON_OPT_NEG(unset);
-	exclude_arg = 1;
-	*(const char **)opt->value = arg;
+	opts->enabled = 1;
+	opts->pattern = arg;
 	return 0;
 }
 
-static const struct option show_ref_options[] = {
-	OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
-	OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
-	OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
-		    "requires exact ref path")),
-	OPT_HIDDEN_BOOL('h', NULL, &show_head,
-			N_("show the HEAD reference, even if it would be filtered out")),
-	OPT_BOOL(0, "head", &show_head,
-	  N_("show the HEAD reference, even if it would be filtered out")),
-	OPT_BOOL('d', "dereference", &deref_tags,
-		    N_("dereference tags into object IDs")),
-	OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
-		       N_("only show SHA1 hash using <n> digits"),
-		       PARSE_OPT_OPTARG, &hash_callback),
-	OPT__ABBREV(&abbrev),
-	OPT__QUIET(&quiet,
-		   N_("do not print results to stdout (useful with --verify)")),
-	OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg,
-		       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
-		       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
-	OPT_END()
-};
-
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
+	struct exclude_existing_options exclude_existing_opts = {0};
+	const struct option show_ref_options[] = {
+		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
+		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
+			    "requires exact ref path")),
+		OPT_HIDDEN_BOOL('h', NULL, &show_head,
+				N_("show the HEAD reference, even if it would be filtered out")),
+		OPT_BOOL(0, "head", &show_head,
+		  N_("show the HEAD reference, even if it would be filtered out")),
+		OPT_BOOL('d', "dereference", &deref_tags,
+			    N_("dereference tags into object IDs")),
+		OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+			       N_("only show SHA1 hash using <n> digits"),
+			       PARSE_OPT_OPTARG, &hash_callback),
+		OPT__ABBREV(&abbrev),
+		OPT__QUIET(&quiet,
+			   N_("do not print results to stdout (useful with --verify)")),
+		OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_opts,
+			       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
+			       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
+		OPT_END()
+	};
+
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
-	if (exclude_arg)
-		return cmd_show_ref__exclude_existing(exclude_existing_arg);
+	if (exclude_existing_opts.enabled)
+		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
 		return cmd_show_ref__verify(argv);
 	else
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 06/12] builtin/show-ref: stop using global variable to count matches
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (4 preceding siblings ...)
  2023-10-24 13:10 ` [PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
@ 2023-10-24 13:11 ` Patrick Steinhardt
  2023-10-24 13:11 ` [PATCH 07/12] builtin/show-ref: stop using global vars for `show_one()` Patrick Steinhardt
                   ` (8 subsequent siblings)
  14 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:11 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 1729 bytes --]

When passing patterns to git-show-ref(1) we're checking whether any
reference matches -- if none does, we indicate this condition via an
unsuccessful exit code.

We're using a global variable to count these matches, which is required
because the counter is getting incremented in a callback function. But
now that we have the `struct show_ref_data` in place, we can get rid of
the global variable and put the counter in there instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index e130b999c0b..4c039007dd1 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,7 +18,7 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
+static int deref_tags, show_head, tags_only, heads_only, verify,
 	   quiet, hash_only, abbrev;
 
 static void show_one(const char *refname, const struct object_id *oid)
@@ -50,6 +50,7 @@ static void show_one(const char *refname, const struct object_id *oid)
 
 struct show_ref_data {
 	const char **patterns;
+	int found_match;
 };
 
 static int show_ref(const char *refname, const struct object_id *oid,
@@ -78,7 +79,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 	}
 
 match:
-	found_match++;
+	data->found_match++;
 
 	show_one(refname, oid);
 
@@ -186,7 +187,7 @@ static int cmd_show_ref__patterns(const char **patterns)
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
 	}
-	if (!found_match)
+	if (!show_ref_data.found_match)
 		return 1;
 
 	return 0;
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 07/12] builtin/show-ref: stop using global vars for `show_one()`
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (5 preceding siblings ...)
  2023-10-24 13:11 ` [PATCH 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
@ 2023-10-24 13:11 ` Patrick Steinhardt
  2023-10-24 13:11 ` [PATCH 08/12] builtin/show-ref: refactor options for patterns subcommand Patrick Steinhardt
                   ` (7 subsequent siblings)
  14 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:11 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 6093 bytes --]

The `show_one()` function implicitly receives a bunch of options which
are tracked via global variables. This makes it hard to see which
subcommands of git-show-ref(1) actually make use of these options.

Introduce a `show_one_options` structure that gets passed down to this
function. This allows us to get rid of more global state and makes it
more explicit which subcommands use those options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 59 +++++++++++++++++++++++++++++-----------------
 1 file changed, 38 insertions(+), 21 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 4c039007dd1..589a91f15b9 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,10 +18,17 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int deref_tags, show_head, tags_only, heads_only, verify,
-	   quiet, hash_only, abbrev;
+static int show_head, tags_only, heads_only, verify;
 
-static void show_one(const char *refname, const struct object_id *oid)
+struct show_one_options {
+	int quiet;
+	int hash_only;
+	int abbrev;
+	int deref_tags;
+};
+
+static void show_one(const struct show_one_options *opts,
+		     const char *refname, const struct object_id *oid)
 {
 	const char *hex;
 	struct object_id peeled;
@@ -30,25 +37,26 @@ static void show_one(const char *refname, const struct object_id *oid)
 		die("git show-ref: bad ref %s (%s)", refname,
 		    oid_to_hex(oid));
 
-	if (quiet)
+	if (opts->quiet)
 		return;
 
-	hex = repo_find_unique_abbrev(the_repository, oid, abbrev);
-	if (hash_only)
+	hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
+	if (opts->hash_only)
 		printf("%s\n", hex);
 	else
 		printf("%s %s\n", hex, refname);
 
-	if (!deref_tags)
+	if (!opts->deref_tags)
 		return;
 
 	if (!peel_iterated_oid(oid, &peeled)) {
-		hex = repo_find_unique_abbrev(the_repository, &peeled, abbrev);
+		hex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);
 		printf("%s %s^{}\n", hex, refname);
 	}
 }
 
 struct show_ref_data {
+	const struct show_one_options *show_one_opts;
 	const char **patterns;
 	int found_match;
 };
@@ -81,7 +89,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 match:
 	data->found_match++;
 
-	show_one(refname, oid);
+	show_one(data->show_one_opts, refname, oid);
 
 	return 0;
 }
@@ -149,7 +157,8 @@ static int cmd_show_ref__exclude_existing(const struct exclude_existing_options
 	return 0;
 }
 
-static int cmd_show_ref__verify(const char **refs)
+static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
+				const char **refs)
 {
 	if (!refs || !*refs)
 		die("--verify requires a reference");
@@ -159,9 +168,9 @@ static int cmd_show_ref__verify(const char **refs)
 
 		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
 		    !read_ref(*refs, &oid)) {
-			show_one(*refs, &oid);
+			show_one(show_one_opts, *refs, &oid);
 		}
-		else if (!quiet)
+		else if (!show_one_opts->quiet)
 			die("'%s' - not a valid ref", *refs);
 		else
 			return 1;
@@ -171,9 +180,11 @@ static int cmd_show_ref__verify(const char **refs)
 	return 0;
 }
 
-static int cmd_show_ref__patterns(const char **patterns)
+static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,
+				  const char **patterns)
 {
 	struct show_ref_data show_ref_data = {
+		.show_one_opts = show_one_opts,
 		.patterns = (patterns && *patterns) ? patterns : NULL,
 	};
 
@@ -195,11 +206,16 @@ static int cmd_show_ref__patterns(const char **patterns)
 
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
-	hash_only = 1;
+	struct show_one_options *opts = opt->value;
+	struct option abbrev_opt = *opt;
+
+	opts->hash_only = 1;
 	/* Use full length SHA1 if no argument */
 	if (!arg)
 		return 0;
-	return parse_opt_abbrev_cb(opt, arg, unset);
+
+	abbrev_opt.value = &opts->abbrev;
+	return parse_opt_abbrev_cb(&abbrev_opt, arg, unset);
 }
 
 static int exclude_existing_callback(const struct option *opt, const char *arg,
@@ -215,6 +231,7 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
 	struct exclude_existing_options exclude_existing_opts = {0};
+	struct show_one_options show_one_opts = {0};
 	const struct option show_ref_options[] = {
 		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
 		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
@@ -224,13 +241,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 				N_("show the HEAD reference, even if it would be filtered out")),
 		OPT_BOOL(0, "head", &show_head,
 		  N_("show the HEAD reference, even if it would be filtered out")),
-		OPT_BOOL('d', "dereference", &deref_tags,
+		OPT_BOOL('d', "dereference", &show_one_opts.deref_tags,
 			    N_("dereference tags into object IDs")),
-		OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+		OPT_CALLBACK_F('s', "hash", &show_one_opts, N_("n"),
 			       N_("only show SHA1 hash using <n> digits"),
 			       PARSE_OPT_OPTARG, &hash_callback),
-		OPT__ABBREV(&abbrev),
-		OPT__QUIET(&quiet,
+		OPT__ABBREV(&show_one_opts.abbrev),
+		OPT__QUIET(&show_one_opts.quiet,
 			   N_("do not print results to stdout (useful with --verify)")),
 		OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_opts,
 			       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
@@ -246,7 +263,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
-		return cmd_show_ref__verify(argv);
+		return cmd_show_ref__verify(&show_one_opts, argv);
 	else
-		return cmd_show_ref__patterns(argv);
+		return cmd_show_ref__patterns(&show_one_opts, argv);
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 08/12] builtin/show-ref: refactor options for patterns subcommand
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (6 preceding siblings ...)
  2023-10-24 13:11 ` [PATCH 07/12] builtin/show-ref: stop using global vars for `show_one()` Patrick Steinhardt
@ 2023-10-24 13:11 ` Patrick Steinhardt
  2023-10-24 13:11 ` [PATCH 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
                   ` (6 subsequent siblings)
  14 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:11 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 3943 bytes --]

The patterns subcommand is the last command that still uses global
variables to track its options. Convert it to use a structure instead
with the same motivation as preceding commits.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 35 ++++++++++++++++++++++-------------
 1 file changed, 22 insertions(+), 13 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 589a91f15b9..5d5d7d22ed1 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,8 +18,6 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int show_head, tags_only, heads_only, verify;
-
 struct show_one_options {
 	int quiet;
 	int hash_only;
@@ -59,6 +57,7 @@ struct show_ref_data {
 	const struct show_one_options *show_one_opts;
 	const char **patterns;
 	int found_match;
+	int show_head;
 };
 
 static int show_ref(const char *refname, const struct object_id *oid,
@@ -66,7 +65,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 {
 	struct show_ref_data *data = cbdata;
 
-	if (show_head && !strcmp(refname, "HEAD"))
+	if (data->show_head && !strcmp(refname, "HEAD"))
 		goto match;
 
 	if (data->patterns) {
@@ -180,20 +179,28 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
 	return 0;
 }
 
-static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,
+struct patterns_options {
+	int show_head;
+	int heads_only;
+	int tags_only;
+};
+
+static int cmd_show_ref__patterns(const struct patterns_options *opts,
+				  const struct show_one_options *show_one_opts,
 				  const char **patterns)
 {
 	struct show_ref_data show_ref_data = {
 		.show_one_opts = show_one_opts,
+		.show_head = opts->show_head,
 		.patterns = (patterns && *patterns) ? patterns : NULL,
 	};
 
-	if (show_head)
+	if (opts->show_head)
 		head_ref(show_ref, &show_ref_data);
-	if (heads_only || tags_only) {
-		if (heads_only)
+	if (opts->heads_only || opts->tags_only) {
+		if (opts->heads_only)
 			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
-		if (tags_only)
+		if (opts->tags_only)
 			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
@@ -231,15 +238,17 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
 	struct exclude_existing_options exclude_existing_opts = {0};
+	struct patterns_options patterns_opts = {0};
 	struct show_one_options show_one_opts = {0};
+	int verify = 0;
 	const struct option show_ref_options[] = {
-		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
-		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
+		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
 		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
 			    "requires exact ref path")),
-		OPT_HIDDEN_BOOL('h', NULL, &show_head,
+		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
 				N_("show the HEAD reference, even if it would be filtered out")),
-		OPT_BOOL(0, "head", &show_head,
+		OPT_BOOL(0, "head", &patterns_opts.show_head,
 		  N_("show the HEAD reference, even if it would be filtered out")),
 		OPT_BOOL('d', "dereference", &show_one_opts.deref_tags,
 			    N_("dereference tags into object IDs")),
@@ -265,5 +274,5 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	else if (verify)
 		return cmd_show_ref__verify(&show_one_opts, argv);
 	else
-		return cmd_show_ref__patterns(&show_one_opts, argv);
+		return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (7 preceding siblings ...)
  2023-10-24 13:11 ` [PATCH 08/12] builtin/show-ref: refactor options for patterns subcommand Patrick Steinhardt
@ 2023-10-24 13:11 ` Patrick Steinhardt
  2023-10-24 19:25   ` Eric Sunshine
  2023-10-24 13:11 ` [PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
                   ` (5 subsequent siblings)
  14 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:11 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 1777 bytes --]

The git-show-ref(1) command has three different modes, of which one is
implicit and the other two can be chosen explicitly by passing a flag.
But while these modes are standalone and cause us to execute completely
separate code paths, we gladly accept the case where a user asks for
both `--exclude-existing` and `--verify` at the same time even though it
is not obvious what will happen. Spoiler: we ignore `--verify` and
execute the `--exclude-existing` mode.

Let's explicitly detect this invalid usage and die in case both modes
were requested.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c  | 3 +++
 t/t1403-show-ref.sh | 5 +++++
 2 files changed, 8 insertions(+)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 5d5d7d22ed1..10d0213e687 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -269,6 +269,9 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
+	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
+		die(_("only one of --exclude-existing or --verify can be given"));
+
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 9252a581abf..3a312c8b27c 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -196,4 +196,9 @@ test_expect_success 'show-ref --verify with dangling ref' '
 	)
 '
 
+test_expect_success 'show-ref sub-modes are mutually exclusive' '
+	test_must_fail git show-ref --verify --exclude-existing 2>err &&
+	grep "only one of --exclude-existing or --verify can be given" err
+'
+
 test_done
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (8 preceding siblings ...)
  2023-10-24 13:11 ` [PATCH 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
@ 2023-10-24 13:11 ` Patrick Steinhardt
  2023-10-24 19:39   ` Eric Sunshine
  2023-10-24 13:11 ` [PATCH 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
                   ` (4 subsequent siblings)
  14 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:11 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 2019 bytes --]

The synopsis treats the `--verify` and the implicit mode the same. They
are slightly different though:

    - They accept different sets of flags.

    - The implicit mode accepts patterns while the `--verify` mode
      accepts references.

Split up the synopsis for these two modes such that we can disambiguate
those differences.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-show-ref.txt | 5 ++++-
 builtin/show-ref.c             | 5 ++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index 2fe274b8faa..ab23e0b62e1 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -8,9 +8,12 @@ git-show-ref - List references in a local repository
 SYNOPSIS
 --------
 [verse]
-'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]
+'git show-ref' [-q | --quiet] [--head] [-d | --dereference]
 	     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
 	     [--heads] [--] [<pattern>...]
+'git show-ref' --verify [-q | --quiet] [-d | --dereference]
+	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
+	     [--] [<ref>...]
 'git show-ref' --exclude-existing[=<pattern>]
 
 DESCRIPTION
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 10d0213e687..d0a32d07404 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -11,9 +11,12 @@
 #include "parse-options.h"
 
 static const char * const show_ref_usage[] = {
-	N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference]\n"
+	N_("git show-ref [-q | --quiet] [--head] [-d | --dereference]\n"
 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n"
 	   "             [--heads] [--] [<pattern>...]"),
+	N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n"
+	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
+	   "             [--] [<ref>...]"),
 	N_("git show-ref --exclude-existing[=<pattern>]"),
 	NULL
 };
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 11/12] builtin/show-ref: add new mode to check for reference existence
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (9 preceding siblings ...)
  2023-10-24 13:11 ` [PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
@ 2023-10-24 13:11 ` Patrick Steinhardt
  2023-10-24 21:01   ` Eric Sunshine
  2023-10-24 13:11 ` [PATCH 12/12] t: use git-show-ref(1) to check for ref existence Patrick Steinhardt
                   ` (3 subsequent siblings)
  14 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:11 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 9653 bytes --]

While we have multiple ways to show the value of a given reference, we
do not have any way to check whether a reference exists at all. While
commands like git-rev-parse(1) or git-show-ref(1) can be used to check
for reference existence in case the reference resolves to something
sane, neither of them can be used to check for existence in some other
scenarios where the reference does not resolve cleanly:

    - References which have an invalid name cannot be resolved.

    - References to nonexistent objects cannot be resolved.

    - Dangling symrefs can be resolved via git-symbolic-ref(1), but this
      requires the caller to special case existence checks depending on
      whteher or not a reference is symbolic or direct.

Furthermore, git-rev-list(1) and other commands do not let the caller
distinguish easily between an actually missing reference and a generic
error.

Taken together, this gseems like sufficient motivation to introduce a
separate plumbing command to explicitly check for the existence of a
reference without trying to resolve its contents.

This new command comes in the form of `git show-ref --exists`. This
new mode will exit successfully when the reference exists, with a
specific error code of 2 when it does not exist, or with 1 when there
has been a generic error.

Note that the only way to properly implement this command is by using
the internal `refs_read_raw_ref()` function. While the public function
`refs_resolve_ref_unsafe()` can be made to behave in the same way by
passing various flags, it does not provide any way to obtain the errno
with which the reference backend failed when reading the reference. As
such, it becomes impossible for us to distinguish generic errors from
the explicit case where the reference wasn't found.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-show-ref.txt | 11 ++++++
 builtin/show-ref.c             | 47 ++++++++++++++++++++++--
 t/t1403-show-ref.sh            | 67 +++++++++++++++++++++++++++++++++-
 3 files changed, 120 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index ab23e0b62e1..a7e9374bc2b 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -15,6 +15,7 @@ SYNOPSIS
 	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
 	     [--] [<ref>...]
 'git show-ref' --exclude-existing[=<pattern>]
+'git show-ref' --exists <ref>
 
 DESCRIPTION
 -----------
@@ -30,6 +31,10 @@ The `--exclude-existing` form is a filter that does the inverse. It reads
 refs from stdin, one ref per line, and shows those that don't exist in
 the local repository.
 
+The `--exists` form can be used to check for the existence of a single
+references. This form does not verify whether the reference resolves to an
+actual object.
+
 Use of this utility is encouraged in favor of directly accessing files under
 the `.git` directory.
 
@@ -65,6 +70,12 @@ OPTIONS
 	Aside from returning an error code of 1, it will also print an error
 	message if `--quiet` was not specified.
 
+--exists::
+
+	Check whether the given reference exists. Returns an error code of 0 if
+	it does, 2 if it is missing, and 128 in case looking up the reference
+	failed with an error other than the reference being missing.
+
 --abbrev[=<n>]::
 
 	Abbreviate the object name.  When using `--hash`, you do
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index d0a32d07404..617e754bbed 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -2,7 +2,7 @@
 #include "config.h"
 #include "gettext.h"
 #include "hex.h"
-#include "refs.h"
+#include "refs/refs-internal.h"
 #include "object-name.h"
 #include "object-store-ll.h"
 #include "object.h"
@@ -18,6 +18,7 @@ static const char * const show_ref_usage[] = {
 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
 	   "             [--] [<ref>...]"),
 	N_("git show-ref --exclude-existing[=<pattern>]"),
+	N_("git show-ref --exists <ref>"),
 	NULL
 };
 
@@ -214,6 +215,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
 	return 0;
 }
 
+static int cmd_show_ref__exists(const char **refs)
+{
+	struct strbuf unused_referent = STRBUF_INIT;
+	struct object_id unused_oid;
+	unsigned int unused_type;
+	int failure_errno = 0;
+	const char *ref;
+	int ret = 1;
+
+	if (!refs || !*refs)
+		die("--exists requires a reference");
+	ref = *refs++;
+	if (*refs)
+		die("--exists requires exactly one reference");
+
+	if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
+			      &unused_oid, &unused_referent, &unused_type,
+			      &failure_errno)) {
+		if (failure_errno == ENOENT) {
+			error(_("reference does not exist"));
+			ret = 2;
+		} else {
+			error(_("failed to look up reference: %s"), strerror(failure_errno));
+		}
+
+		goto out;
+	}
+
+	ret = 0;
+
+out:
+	strbuf_release(&unused_referent);
+	return ret;
+}
+
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
 	struct show_one_options *opts = opt->value;
@@ -243,10 +279,11 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	struct exclude_existing_options exclude_existing_opts = {0};
 	struct patterns_options patterns_opts = {0};
 	struct show_one_options show_one_opts = {0};
-	int verify = 0;
+	int verify = 0, exists = 0;
 	const struct option show_ref_options[] = {
 		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
 		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "exists", &exists, N_("check for reference existence without resolving")),
 		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
 			    "requires exact ref path")),
 		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
@@ -272,13 +309,15 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
-	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
-		die(_("only one of --exclude-existing or --verify can be given"));
+	if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)
+		die(_("only one of --exclude-existing, --exists or --verify can be given"));
 
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
 		return cmd_show_ref__verify(&show_one_opts, argv);
+	else if (exists)
+		return cmd_show_ref__exists(argv);
 	else
 		return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);
 }
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 3a312c8b27c..17eba350ce5 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -197,8 +197,73 @@ test_expect_success 'show-ref --verify with dangling ref' '
 '
 
 test_expect_success 'show-ref sub-modes are mutually exclusive' '
+	cat >expect <<-EOF &&
+	fatal: only one of --exclude-existing, --exists or --verify can be given
+	EOF
+
 	test_must_fail git show-ref --verify --exclude-existing 2>err &&
-	grep "only one of --exclude-existing or --verify can be given" err
+	test_cmp expect err &&
+
+	test_must_fail git show-ref --verify --exists 2>err &&
+	test_cmp expect err &&
+
+	test_must_fail git show-ref --exclude-existing --exists 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success '--exists with existing reference' '
+	git show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+'
+
+test_expect_success '--exists with missing reference' '
+	test_expect_code 2 git show-ref --exists refs/heads/does-not-exist
+'
+
+test_expect_success '--exists does not use DWIM' '
+	test_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&
+	grep "reference does not exist" err
+'
+
+test_expect_success '--exists with HEAD' '
+	git show-ref --exists HEAD
+'
+
+test_expect_success '--exists with bad reference name' '
+	test_when_finished "git update-ref -d refs/heads/bad...name" &&
+	new_oid=$(git rev-parse HEAD) &&
+	test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
+	git show-ref --exists refs/heads/bad...name
+'
+
+test_expect_success '--exists with arbitrary symref' '
+	test_when_finished "git symbolic-ref -d refs/symref" &&
+	git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
+	git show-ref --exists refs/symref
+'
+
+test_expect_success '--exists with dangling symref' '
+	test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
+	git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+	git show-ref --exists refs/heads/dangling
+'
+
+test_expect_success '--exists with nonexistent object ID' '
+	test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+	git show-ref --exists refs/heads/missing-oid
+'
+
+test_expect_success '--exists with non-commit object' '
+	tree_oid=$(git rev-parse HEAD^{tree}) &&
+	test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+	git show-ref --exists refs/heads/tree
+'
+
+test_expect_success '--exists with directory fails with generic error' '
+	cat >expect <<-EOF &&
+	error: failed to look up reference: Is a directory
+	EOF
+	test_expect_code 1 git show-ref --exists refs/heads 2>err &&
+	test_cmp expect err
 '
 
 test_done
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH 12/12] t: use git-show-ref(1) to check for ref existence
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (10 preceding siblings ...)
  2023-10-24 13:11 ` [PATCH 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
@ 2023-10-24 13:11 ` Patrick Steinhardt
  2023-10-24 19:17 ` [PATCH 00/12] show-ref: introduce mode " Junio C Hamano
                   ` (2 subsequent siblings)
  14 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-24 13:11 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 14354 bytes --]

Convert tests that use `test_path_is_file` and `test_path_is_missing` to
instead use a set of helpers `test_ref_exists` and `test_ref_missing`.
These helpers are implemented via the newly introduced `git show-ref
--exists` command. Thus, we can avoid intimate knowledge of how the ref
backend stores references on disk.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 t/t1430-bad-ref-name.sh | 27 +++++++++++++-------
 t/t3200-branch.sh       | 33 ++++++++++++++-----------
 t/t5521-pull-options.sh |  4 +--
 t/t5605-clone-local.sh  |  2 +-
 t/test-lib-functions.sh | 55 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 94 insertions(+), 27 deletions(-)

diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh
index ff1c967d550..7b7d6953c62 100755
--- a/t/t1430-bad-ref-name.sh
+++ b/t/t1430-bad-ref-name.sh
@@ -205,8 +205,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref to broken name'
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" &&
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git update-ref --no-deref -d refs/heads/badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -216,8 +217,9 @@ test_expect_success 'branch -d can delete symref to broken name' '
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" &&
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git branch -d badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
 	test_must_be_empty error
 '
@@ -225,8 +227,9 @@ test_expect_success 'branch -d can delete symref to broken name' '
 test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' '
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git update-ref --no-deref -d refs/heads/badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -234,8 +237,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref to brok
 test_expect_success 'branch -d can delete dangling symref to broken name' '
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git branch -d badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
 	test_must_be_empty error
 '
@@ -245,8 +249,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" &&
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/broken...ref &&
 	git update-ref -d refs/heads/badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...ref &&
+	test_ref_missing refs/heads/broken...ref &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -254,8 +259,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '
 test_expect_success 'update-ref --no-deref -d can delete symref with broken name' '
 	printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -263,8 +269,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref with broken name
 test_expect_success 'branch -d can delete symref with broken name' '
 	printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git branch -d broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_i18ngrep "Deleted branch broken...symref (was refs/heads/main)" output &&
 	test_must_be_empty error
 '
@@ -272,8 +279,9 @@ test_expect_success 'branch -d can delete symref with broken name' '
 test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' '
 	printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -281,8 +289,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref with br
 test_expect_success 'branch -d can delete dangling symref with broken name' '
 	printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git branch -d broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_i18ngrep "Deleted branch broken...symref (was refs/heads/idonotexist)" output &&
 	test_must_be_empty error
 '
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 080e4f24a6e..bde4f1485b7 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -25,7 +25,7 @@ test_expect_success 'prepare a trivial repository' '
 
 test_expect_success 'git branch --help should not have created a bogus branch' '
 	test_might_fail git branch --man --help </dev/null >/dev/null 2>&1 &&
-	test_path_is_missing .git/refs/heads/--help
+	test_ref_missing refs/heads/--help
 '
 
 test_expect_success 'branch -h in broken repository' '
@@ -40,7 +40,8 @@ test_expect_success 'branch -h in broken repository' '
 '
 
 test_expect_success 'git branch abc should create a branch' '
-	git branch abc && test_path_is_file .git/refs/heads/abc
+	git branch abc &&
+	test_ref_exists refs/heads/abc
 '
 
 test_expect_success 'git branch abc should fail when abc exists' '
@@ -61,11 +62,13 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '
 '
 
 test_expect_success 'git branch a/b/c should create a branch' '
-	git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c
+	git branch a/b/c &&
+	test_ref_exists refs/heads/a/b/c
 '
 
 test_expect_success 'git branch mb main... should create a branch' '
-	git branch mb main... && test_path_is_file .git/refs/heads/mb
+	git branch mb main... &&
+	test_ref_exists refs/heads/mb
 '
 
 test_expect_success 'git branch HEAD should fail' '
@@ -78,14 +81,14 @@ EOF
 test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' '
 	GIT_COMMITTER_DATE="2005-05-26 23:30" \
 	git -c core.logallrefupdates=false branch --create-reflog d/e/f &&
-	test_path_is_file .git/refs/heads/d/e/f &&
+	test_ref_exists refs/heads/d/e/f &&
 	test_path_is_file .git/logs/refs/heads/d/e/f &&
 	test_cmp expect .git/logs/refs/heads/d/e/f
 '
 
 test_expect_success 'git branch -d d/e/f should delete a branch and a log' '
 	git branch -d d/e/f &&
-	test_path_is_missing .git/refs/heads/d/e/f &&
+	test_ref_missing refs/heads/d/e/f &&
 	test_must_fail git reflog exists refs/heads/d/e/f
 '
 
@@ -213,7 +216,7 @@ test_expect_success 'git branch -M should leave orphaned HEAD alone' '
 		test_commit initial &&
 		git checkout --orphan lonely &&
 		grep lonely .git/HEAD &&
-		test_path_is_missing .git/refs/head/lonely &&
+		test_ref_missing refs/head/lonely &&
 		git branch -M main mistress &&
 		grep lonely .git/HEAD
 	)
@@ -799,8 +802,8 @@ test_expect_success 'deleting a symref' '
 	git symbolic-ref refs/heads/symref refs/heads/target &&
 	echo "Deleted branch symref (was refs/heads/target)." >expect &&
 	git branch -d symref >actual &&
-	test_path_is_file .git/refs/heads/target &&
-	test_path_is_missing .git/refs/heads/symref &&
+	test_ref_exists refs/heads/target &&
+	test_ref_missing refs/heads/symref &&
 	test_cmp expect actual
 '
 
@@ -809,16 +812,16 @@ test_expect_success 'deleting a dangling symref' '
 	test_path_is_file .git/refs/heads/dangling-symref &&
 	echo "Deleted branch dangling-symref (was nowhere)." >expect &&
 	git branch -d dangling-symref >actual &&
-	test_path_is_missing .git/refs/heads/dangling-symref &&
+	test_ref_missing refs/heads/dangling-symref &&
 	test_cmp expect actual
 '
 
 test_expect_success 'deleting a self-referential symref' '
 	git symbolic-ref refs/heads/self-reference refs/heads/self-reference &&
-	test_path_is_file .git/refs/heads/self-reference &&
+	test_ref_exists refs/heads/self-reference &&
 	echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect &&
 	git branch -d self-reference >actual &&
-	test_path_is_missing .git/refs/heads/self-reference &&
+	test_ref_missing refs/heads/self-reference &&
 	test_cmp expect actual
 '
 
@@ -826,8 +829,8 @@ test_expect_success 'renaming a symref is not allowed' '
 	git symbolic-ref refs/heads/topic refs/heads/main &&
 	test_must_fail git branch -m topic new-topic &&
 	git symbolic-ref refs/heads/topic &&
-	test_path_is_file .git/refs/heads/main &&
-	test_path_is_missing .git/refs/heads/new-topic
+	test_ref_exists refs/heads/main &&
+	test_ref_missing refs/heads/new-topic
 '
 
 test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '
@@ -1142,7 +1145,7 @@ EOF
 test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '
 	GIT_COMMITTER_DATE="2005-05-26 23:30" \
 	git checkout -b g/h/i -l main &&
-	test_path_is_file .git/refs/heads/g/h/i &&
+	test_ref_exists refs/heads/g/h/i &&
 	test_path_is_file .git/logs/refs/heads/g/h/i &&
 	test_cmp expect .git/logs/refs/heads/g/h/i
 '
diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh
index 079b2f2536e..3681859f983 100755
--- a/t/t5521-pull-options.sh
+++ b/t/t5521-pull-options.sh
@@ -143,7 +143,7 @@ test_expect_success 'git pull --dry-run' '
 		cd clonedry &&
 		git pull --dry-run ../parent &&
 		test_path_is_missing .git/FETCH_HEAD &&
-		test_path_is_missing .git/refs/heads/main &&
+		test_ref_missing refs/heads/main &&
 		test_path_is_missing .git/index &&
 		test_path_is_missing file
 	)
@@ -157,7 +157,7 @@ test_expect_success 'git pull --all --dry-run' '
 		git remote add origin ../parent &&
 		git pull --all --dry-run &&
 		test_path_is_missing .git/FETCH_HEAD &&
-		test_path_is_missing .git/refs/remotes/origin/main &&
+		test_ref_missing refs/remotes/origin/main &&
 		test_path_is_missing .git/index &&
 		test_path_is_missing file
 	)
diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh
index 1d7b1abda1a..946c5751885 100755
--- a/t/t5605-clone-local.sh
+++ b/t/t5605-clone-local.sh
@@ -69,7 +69,7 @@ test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
 	git clone a d &&
 	(cd d &&
 	git fetch &&
-	test ! -e .git/refs/remotes/origin/HEAD)
+	test_ref_missing refs/remotes/origin/HEAD)
 '
 
 test_expect_success 'bundle clone without .bundle suffix' '
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 2f8868caa17..56b33536ed1 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -251,6 +251,61 @@ debug () {
 	done
 }
 
+# Usage: test_ref_exists [options] <ref>
+#
+#   -C <dir>:
+#      Run all git commands in directory <dir>
+#
+# This helper function checks whether a reference exists. Symrefs or object IDs
+# will not be resolved. Can be used to check references with bad names.
+test_ref_exists () {
+	local indir=
+
+	while test $# != 0
+	do
+		case "$1" in
+		-C)
+			indir="$2"
+			shift
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done &&
+
+	indir=${indir:+"$indir"/} &&
+
+	if test "$#" != 1
+	then
+		BUG "expected exactly one reference"
+	fi &&
+
+	git ${indir:+ -C "$indir"} show-ref --exists "$1"
+}
+
+# Behaves the same as test_ref_exists, except that it checks for the absence of
+# a reference. This is preferable to `! test_ref_exists` as this function is
+# able to distinguish actually-missing references from other, generic errors.
+test_ref_missing () {
+	test_ref_exists "$@"
+	case "$?" in
+	2)
+		# This is the good case.
+		return 0
+		;;
+	0)
+		echo >&4 "test_ref_missing: reference exists"
+		return 1
+		;;
+	*)
+		echo >&4 "test_ref_missing: generic error"
+		return 1
+		;;
+	esac
+}
+
 # Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]
 #   -C <dir>:
 #	Run all git commands in directory <dir>
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 02/12] builtin/show-ref: split up different subcommands
  2023-10-24 13:10 ` [PATCH 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
@ 2023-10-24 17:55   ` Eric Sunshine
  0 siblings, 0 replies; 66+ messages in thread
From: Eric Sunshine @ 2023-10-24 17:55 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Han-Wen Nienhuys

On Tue, Oct 24, 2023 at 9:10 AM Patrick Steinhardt <ps@pks.im> wrote:
> While not immediately obvious, git-show-ref(1) actually implements three
> different subcommands:
>
>     - `git show-ref <patterns>` can be used to list references that
>       match a specific pattern.
>
>     - `git show-ref --verify <refs>` can be used to list references.
>       These are _not_ patterns.
>
>     - `git show-ref --exclude-existing` can be used as a filter that
>       reads references from standard input, performing some conversions
>       on each of them.
>
> Let's make this more explicit in the code by splitting up the three
> subcommands into separate functions. This also allows us to address the
> confusingly named `patterns` variable, which may hold either patterns or
> reference names.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> @@ -142,6 +142,53 @@ static int exclude_existing(const char *match)
> +static int cmd_show_ref__verify(const char **refs)
> +{
> +       if (!refs || !*refs)
> +               die("--verify requires a reference");
> +
> +       while (*refs) {
> +               struct object_id oid;
> +
> +               if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
> +                   !read_ref(*refs, &oid)) {
> +                       show_one(*refs, &oid);
> +               }
> +               else if (!quiet)
> +                       die("'%s' - not a valid ref", *refs);
> +               else
> +                       return 1;
> +               refs++;
> +       }

A couple style-nits here caught my attention...

- "}" and "else" should be cuddled: `} else if`

- coding guidelines these days want braces on all branches if any
branch needs them

However, since this code is merely being relocated from elsewhere in
this file and since these style-nits were already present, moving the
code verbatim without correcting the style problems is more
reviewer-friendly. Okay.

> +       return 0;
> +}
> +
> +static int cmd_show_ref__patterns(const char **patterns)
> +{
> +       struct show_ref_data show_ref_data = {
> +               .patterns = (patterns && *patterns) ? patterns : NULL,
> +       };

Are we allowing non-constant initializers in the codebase? If not,
this should probably initialize .patterns to NULL and then
conditionally assign `patterns` separately in code below the
initializer.

> +       if (show_head)
> +               head_ref(show_ref, &show_ref_data);
> +       if (heads_only || tags_only) {
> +               if (heads_only)
> +                       for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
> +               if (tags_only)
> +                       for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
> +       } else {
> +               for_each_ref(show_ref, &show_ref_data);
> +       }
> +       if (!found_match) {
> +               if (verify && !quiet)
> +                       die("No match");
> +               return 1;
> +       }
> +
> +       return 0;
> +}


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

* Re: [PATCH 04/12] builtin/show-ref: fix dead code when passing patterns
  2023-10-24 13:10 ` [PATCH 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
@ 2023-10-24 18:02   ` Eric Sunshine
  0 siblings, 0 replies; 66+ messages in thread
From: Eric Sunshine @ 2023-10-24 18:02 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Han-Wen Nienhuys

On Tue, Oct 24, 2023 at 9:10 AM Patrick Steinhardt <ps@pks.im> wrote:
> When passing patterns to `git show-ref` we have some code that will
> cause us to die of `verify && !quiet` is true. But because `verify`

s/of/if/

> indicates a different subcommand of git-show-ref(1) that causes us to
> execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the
> condition cannot ever be true.
>
> Let's remove this dead code.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>


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

* Re: [PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options
  2023-10-24 13:10 ` [PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
@ 2023-10-24 18:48   ` Eric Sunshine
  2023-10-25 11:50     ` Patrick Steinhardt
  0 siblings, 1 reply; 66+ messages in thread
From: Eric Sunshine @ 2023-10-24 18:48 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Han-Wen Nienhuys

On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:
> It's not immediately obvious options which options are applicable to
> what subcommand ni git-show-ref(1) because all options exist as global

s/ni/in/

> state. This can easily cause confusion for the reader.
>
> Refactor options for the `--exclude-existing` subcommand to be contained
> in a separate structure. This structure is stored on the stack and
> passed down as required. Consequentially, it clearly delimits the scope

s/Consequentially/Consequently/

> of those options and requires the reader to worry less about global
> state.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> @@ -95,6 +94,11 @@ static int add_existing(const char *refname,
> +struct exclude_existing_options {
> +       int enabled;
> +       const char *pattern;
> +};

Do we need this `enabled` flag? Can't the same be achieved by checking
whether `pattern` is NULL or not (see below)?

> @@ -104,11 +108,11 @@ static int add_existing(const char *refname,
> -static int cmd_show_ref__exclude_existing(const char *match)
> +static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)

Since you're renaming `match` to `opts->pattern`...

>  {
> -       int matchlen = match ? strlen(match) : 0;
> +       int matchlen = opts->pattern ? strlen(opts->pattern) : 0;

... and since you're touching this line anyway, maybe it makes sense
to rename `matchlen` to `patternlen`?

> @@ -124,11 +128,11 @@ static int cmd_show_ref__exclude_existing(const char *match)
> -                       if (strncmp(ref, match, matchlen))
> +                       if (strncmp(ref, opts->pattern, matchlen))

Especially since, as shown in this context, `matchlen` is really the
length of the _pattern_, not the length of the resulting _match_.

> @@ -200,44 +204,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)
>  int cmd_show_ref(int argc, const char **argv, const char *prefix)
>  {
>         [...]
> -       if (exclude_arg)
> -               return cmd_show_ref__exclude_existing(exclude_existing_arg);
> +       if (exclude_existing_opts.enabled)
> +               return cmd_show_ref__exclude_existing(&exclude_existing_opts);

(continued from above) Can't this be handled without a separate `enabled` flag?

    if (exclude_existing_opts.pattern)
        ...


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

* Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (11 preceding siblings ...)
  2023-10-24 13:11 ` [PATCH 12/12] t: use git-show-ref(1) to check for ref existence Patrick Steinhardt
@ 2023-10-24 19:17 ` Junio C Hamano
  2023-10-25 14:26   ` Han-Wen Nienhuys
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
  14 siblings, 1 reply; 66+ messages in thread
From: Junio C Hamano @ 2023-10-24 19:17 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Eric Sunshine, Han-Wen Nienhuys

Patrick Steinhardt <ps@pks.im> writes:

> this patch series introduces a new `--exists` mode to git-show-ref(1) to
> explicitly check for the existence of a reference, only.

I agree that show-ref would be the best place for this feature (not
rev-parse, which is already a kitchen sink).  After all, the command
was designed for validating refs in 358ddb62 (Add "git show-ref"
builtin command, 2006-09-15).

Thanks.  Hopefully I can take a look before I go offline.



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

* Re: [PATCH 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands
  2023-10-24 13:11 ` [PATCH 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
@ 2023-10-24 19:25   ` Eric Sunshine
  0 siblings, 0 replies; 66+ messages in thread
From: Eric Sunshine @ 2023-10-24 19:25 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Han-Wen Nienhuys

On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:
> The git-show-ref(1) command has three different modes, of which one is
> implicit and the other two can be chosen explicitly by passing a flag.
> But while these modes are standalone and cause us to execute completely
> separate code paths, we gladly accept the case where a user asks for
> both `--exclude-existing` and `--verify` at the same time even though it
> is not obvious what will happen. Spoiler: we ignore `--verify` and
> execute the `--exclude-existing` mode.
>
> Let's explicitly detect this invalid usage and die in case both modes
> were requested.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> @@ -269,6 +269,9 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
> +       if ((!!exclude_existing_opts.enabled + !!verify) > 1)
> +               die(_("only one of --exclude-existing or --verify can be given"));

Somewhat recently, work was done to normalize this sort of message.
The result was to instead use the phrasing "options '%s' and '%s'
cannot be used together". See, for instance, 43ea635c35 (i18n:
refactor "foo and bar are mutually exclusive", 2022-01-05).


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

* Re: [PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis
  2023-10-24 13:11 ` [PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
@ 2023-10-24 19:39   ` Eric Sunshine
  2023-10-25 11:50     ` Patrick Steinhardt
  0 siblings, 1 reply; 66+ messages in thread
From: Eric Sunshine @ 2023-10-24 19:39 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Han-Wen Nienhuys

On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:
> The synopsis treats the `--verify` and the implicit mode the same. They
> are slightly different though:
>
>     - They accept different sets of flags.
>
>     - The implicit mode accepts patterns while the `--verify` mode
>       accepts references.
>
> Split up the synopsis for these two modes such that we can disambiguate
> those differences.

Good. When reading [2/12], my immediate thought was that such a
documentation change was needed.

> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
> @@ -8,9 +8,12 @@ git-show-ref - List references in a local repository
>  SYNOPSIS
> -'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]
> +'git show-ref' [-q | --quiet] [--head] [-d | --dereference]
>              [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
>              [--heads] [--] [<pattern>...]
> +'git show-ref' --verify [-q | --quiet] [-d | --dereference]
> +            [-s | --hash[=<n>]] [--abbrev[=<n>]]
> +            [--] [<ref>...]
>  'git show-ref' --exclude-existing[=<pattern>]

What does it mean to request "quiet" for the plain `git show-ref`
mode? That seems pointless and counterintuitive. Even though this mode
may accept --quiet as a quirk of implementation, we probably shouldn't
be promoting its use in the documentation. Moreover, the blurb for
--quiet later in the document:

   Do not print any results to stdout. When combined with --verify,
   this can be used to silently check if a reference exists.

should probably be rephrased since it currently implies that it may be
used with modes other than --verify, but that's not really the case
(implementation quirks aside).

This also raises the question as to whether an interlock should be
added to disallow --quiet with plain `git show-ref`, much like the
interlock preventing --exclude-existing and --verify from being used
together. Ideally, such an interlock ought to be added, but I wouldn't
be surprised to learn that doing so would break someone's existing
tooling which insensibly uses --quiet with plain `git show-ref`.


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

* Re: [PATCH 11/12] builtin/show-ref: add new mode to check for reference existence
  2023-10-24 13:11 ` [PATCH 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
@ 2023-10-24 21:01   ` Eric Sunshine
  2023-10-25 11:50     ` Patrick Steinhardt
  0 siblings, 1 reply; 66+ messages in thread
From: Eric Sunshine @ 2023-10-24 21:01 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Han-Wen Nienhuys

On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:
> While we have multiple ways to show the value of a given reference, we
> do not have any way to check whether a reference exists at all. While
> commands like git-rev-parse(1) or git-show-ref(1) can be used to check
> for reference existence in case the reference resolves to something
> sane, neither of them can be used to check for existence in some other
> scenarios where the reference does not resolve cleanly:
>
>     - References which have an invalid name cannot be resolved.
>
>     - References to nonexistent objects cannot be resolved.
>
>     - Dangling symrefs can be resolved via git-symbolic-ref(1), but this
>       requires the caller to special case existence checks depending on
>       whteher or not a reference is symbolic or direct.

s/whteher/whether/

> Furthermore, git-rev-list(1) and other commands do not let the caller
> distinguish easily between an actually missing reference and a generic
> error.
>
> Taken together, this gseems like sufficient motivation to introduce a

s/gseems/seems/

> separate plumbing command to explicitly check for the existence of a
> reference without trying to resolve its contents.
>
> This new command comes in the form of `git show-ref --exists`. This
> new mode will exit successfully when the reference exists, with a
> specific error code of 2 when it does not exist, or with 1 when there
> has been a generic error.
>
> Note that the only way to properly implement this command is by using
> the internal `refs_read_raw_ref()` function. While the public function
> `refs_resolve_ref_unsafe()` can be made to behave in the same way by
> passing various flags, it does not provide any way to obtain the errno
> with which the reference backend failed when reading the reference. As
> such, it becomes impossible for us to distinguish generic errors from
> the explicit case where the reference wasn't found.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
> @@ -65,6 +70,12 @@ OPTIONS
> +--exists::
> +
> +       Check whether the given reference exists. Returns an error code of 0 if

We probably want to call this "exit code" rather than "error code"
since the latter is unnecessarily scary sounding for the success case
(when the ref does exit).

> +       it does, 2 if it is missing, and 128 in case looking up the reference
> +       failed with an error other than the reference being missing.

The commit message says it returns 1 for a generic error, but this
inconsistently says it returns 128 for that case. The actual
implementation returns 1.

> diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> @@ -214,6 +215,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
> +static int cmd_show_ref__exists(const char **refs)
> +{
> +       struct strbuf unused_referent = STRBUF_INIT;
> +       struct object_id unused_oid;
> +       unsigned int unused_type;
> +       int failure_errno = 0;
> +       const char *ref;
> +       int ret = 1;
> +
> +       if (!refs || !*refs)
> +               die("--exists requires a reference");
> +       ref = *refs++;
> +       if (*refs)
> +               die("--exists requires exactly one reference");
> +
> +       if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
> +                             &unused_oid, &unused_referent, &unused_type,
> +                             &failure_errno)) {
> +               if (failure_errno == ENOENT) {
> +                       error(_("reference does not exist"));

The documentation doesn't mention this printing any output, and indeed
one would intuitively expect a boolean-like operation to not produce
any printed output since its exit code indicates the result (except,
of course, in the case of a real error).

> +                       ret = 2;
> +               } else {
> +                       error(_("failed to look up reference: %s"), strerror(failure_errno));

Or use error_errno():

    errno = failure_errno;
    error_errno(_("failed to look up reference: %s"));

> +               }
> +
> +               goto out;
> +       }
> +
> +       ret = 0;
> +
> +out:
> +       strbuf_release(&unused_referent);
> +       return ret;
> +}

It's a bit odd having `ret` be 1 at the outset rather than 0, thus
making the logic a bit more difficult to reason about. I would have
expected it to be organized like this:

    int ret = 0;
    if (refs_read_raw_ref(...)) {
         if (failure_errno == ENOENT) {
            ret = 2;
        } else {
            ret = 1;
            errno = failure_errno;
            error_errno(_("failed to look up reference: %s"));
       }
    }
    strbuf_release(...);
    return ret;

> @@ -272,13 +309,15 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
> +       if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)
> +               die(_("only one of --exclude-existing, --exists or --verify can be given"));

When reviewing an earlier patch in this series, I forgot to mention
that we can simplify the life of translators by using placeholders:

    die(_("options '%s', '%s' or '%s' cannot be used together"),
        "--exclude-existing", "--exists", "--verify");

which ensures that they don't translate the literal option names, and
makes it possible to reuse the translated message in multiple
locations (since it doesn't mention hard-coded option names).


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

* Re: [PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options
  2023-10-24 18:48   ` Eric Sunshine
@ 2023-10-25 11:50     ` Patrick Steinhardt
  0 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-25 11:50 UTC (permalink / raw
  To: Eric Sunshine; +Cc: git, Junio C Hamano, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 3171 bytes --]

On Tue, Oct 24, 2023 at 02:48:14PM -0400, Eric Sunshine wrote:
> On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:
> > It's not immediately obvious options which options are applicable to
> > what subcommand ni git-show-ref(1) because all options exist as global
> 
> s/ni/in/
> 
> > state. This can easily cause confusion for the reader.
> >
> > Refactor options for the `--exclude-existing` subcommand to be contained
> > in a separate structure. This structure is stored on the stack and
> > passed down as required. Consequentially, it clearly delimits the scope
> 
> s/Consequentially/Consequently/
> 
> > of those options and requires the reader to worry less about global
> > state.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> > diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> > @@ -95,6 +94,11 @@ static int add_existing(const char *refname,
> > +struct exclude_existing_options {
> > +       int enabled;
> > +       const char *pattern;
> > +};
> 
> Do we need this `enabled` flag? Can't the same be achieved by checking
> whether `pattern` is NULL or not (see below)?

Yeah, we do. It's perfectly valid to pass `--exclude-existing` without
the optional pattern argument. We still want to use this mode in that
case, but don't populate the pattern.

An alternative would be to assign something like a sentinel value in
here. But I'd think that it's clearer to instead have an explicit
separate field for this.

> > @@ -104,11 +108,11 @@ static int add_existing(const char *refname,
> > -static int cmd_show_ref__exclude_existing(const char *match)
> > +static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)
> 
> Since you're renaming `match` to `opts->pattern`...
> 
> >  {
> > -       int matchlen = match ? strlen(match) : 0;
> > +       int matchlen = opts->pattern ? strlen(opts->pattern) : 0;
> 
> ... and since you're touching this line anyway, maybe it makes sense
> to rename `matchlen` to `patternlen`?

Yes, let's do it. It's been more of an oversight rather than
intentional to keep the previous name.

> > @@ -124,11 +128,11 @@ static int cmd_show_ref__exclude_existing(const char *match)
> > -                       if (strncmp(ref, match, matchlen))
> > +                       if (strncmp(ref, opts->pattern, matchlen))
> 
> Especially since, as shown in this context, `matchlen` is really the
> length of the _pattern_, not the length of the resulting _match_.
> 
> > @@ -200,44 +204,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)
> >  int cmd_show_ref(int argc, const char **argv, const char *prefix)
> >  {
> >         [...]
> > -       if (exclude_arg)
> > -               return cmd_show_ref__exclude_existing(exclude_existing_arg);
> > +       if (exclude_existing_opts.enabled)
> > +               return cmd_show_ref__exclude_existing(&exclude_existing_opts);
> 
> (continued from above) Can't this be handled without a separate `enabled` flag?
> 
>     if (exclude_existing_opts.pattern)
>         ...

See the explanation above.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis
  2023-10-24 19:39   ` Eric Sunshine
@ 2023-10-25 11:50     ` Patrick Steinhardt
  0 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-25 11:50 UTC (permalink / raw
  To: Eric Sunshine; +Cc: git, Junio C Hamano, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 3246 bytes --]

On Tue, Oct 24, 2023 at 03:39:28PM -0400, Eric Sunshine wrote:
> On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:
> > The synopsis treats the `--verify` and the implicit mode the same. They
> > are slightly different though:
> >
> >     - They accept different sets of flags.
> >
> >     - The implicit mode accepts patterns while the `--verify` mode
> >       accepts references.
> >
> > Split up the synopsis for these two modes such that we can disambiguate
> > those differences.
> 
> Good. When reading [2/12], my immediate thought was that such a
> documentation change was needed.
> 
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> > diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
> > @@ -8,9 +8,12 @@ git-show-ref - List references in a local repository
> >  SYNOPSIS
> > -'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]
> > +'git show-ref' [-q | --quiet] [--head] [-d | --dereference]
> >              [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
> >              [--heads] [--] [<pattern>...]
> > +'git show-ref' --verify [-q | --quiet] [-d | --dereference]
> > +            [-s | --hash[=<n>]] [--abbrev[=<n>]]
> > +            [--] [<ref>...]
> >  'git show-ref' --exclude-existing[=<pattern>]
> 
> What does it mean to request "quiet" for the plain `git show-ref`
> mode? That seems pointless and counterintuitive. Even though this mode
> may accept --quiet as a quirk of implementation, we probably shouldn't
> be promoting its use in the documentation. Moreover, the blurb for
> --quiet later in the document:
> 
>    Do not print any results to stdout. When combined with --verify,
>    this can be used to silently check if a reference exists.
> 
> should probably be rephrased since it currently implies that it may be
> used with modes other than --verify, but that's not really the case
> (implementation quirks aside).

Good point indeed, will change.

> This also raises the question as to whether an interlock should be
> added to disallow --quiet with plain `git show-ref`, much like the
> interlock preventing --exclude-existing and --verify from being used
> together. Ideally, such an interlock ought to be added, but I wouldn't
> be surprised to learn that doing so would break someone's existing
> tooling which insensibly uses --quiet with plain `git show-ref`.

Yeah, I also wouldn't go as far as this. The mutual exclusiveness for
`--exclude-existing` and `--verify` makes sense in my opinion because
the result is extremely misleading and may cause users to assume that
the wrong thing has happened.

I don't think that's necessarily true for `--quiet`. It may not make a
lot of sense to specify `--quiet` here, but it also doesn't quietly do
the wrong thing as in the other case.

Furthermore, we also don't have any interlocks for incompatible other
flags, either: git-show-ref(1) won't complain when passing any of the
mode-specific flags to the other modes. If we want to fix that I'd
rather defer it to a follow up patch series though. And as you said, I
would almost certainly expect there to be some kind of fallout if we did
this change.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 11/12] builtin/show-ref: add new mode to check for reference existence
  2023-10-24 21:01   ` Eric Sunshine
@ 2023-10-25 11:50     ` Patrick Steinhardt
  0 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-25 11:50 UTC (permalink / raw
  To: Eric Sunshine; +Cc: git, Junio C Hamano, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 6936 bytes --]

On Tue, Oct 24, 2023 at 05:01:55PM -0400, Eric Sunshine wrote:
> On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:
> > While we have multiple ways to show the value of a given reference, we
> > do not have any way to check whether a reference exists at all. While
> > commands like git-rev-parse(1) or git-show-ref(1) can be used to check
> > for reference existence in case the reference resolves to something
> > sane, neither of them can be used to check for existence in some other
> > scenarios where the reference does not resolve cleanly:
> >
> >     - References which have an invalid name cannot be resolved.
> >
> >     - References to nonexistent objects cannot be resolved.
> >
> >     - Dangling symrefs can be resolved via git-symbolic-ref(1), but this
> >       requires the caller to special case existence checks depending on
> >       whteher or not a reference is symbolic or direct.
> 
> s/whteher/whether/
> 
> > Furthermore, git-rev-list(1) and other commands do not let the caller
> > distinguish easily between an actually missing reference and a generic
> > error.
> >
> > Taken together, this gseems like sufficient motivation to introduce a
> 
> s/gseems/seems/
> 
> > separate plumbing command to explicitly check for the existence of a
> > reference without trying to resolve its contents.
> >
> > This new command comes in the form of `git show-ref --exists`. This
> > new mode will exit successfully when the reference exists, with a
> > specific error code of 2 when it does not exist, or with 1 when there
> > has been a generic error.
> >
> > Note that the only way to properly implement this command is by using
> > the internal `refs_read_raw_ref()` function. While the public function
> > `refs_resolve_ref_unsafe()` can be made to behave in the same way by
> > passing various flags, it does not provide any way to obtain the errno
> > with which the reference backend failed when reading the reference. As
> > such, it becomes impossible for us to distinguish generic errors from
> > the explicit case where the reference wasn't found.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> > diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
> > @@ -65,6 +70,12 @@ OPTIONS
> > +--exists::
> > +
> > +       Check whether the given reference exists. Returns an error code of 0 if
> 
> We probably want to call this "exit code" rather than "error code"
> since the latter is unnecessarily scary sounding for the success case
> (when the ref does exit).

I was trying to stick to the preexisting style of "error code" in this
manual page. But I think I agree with your argument that we also call it
an error code in the successful case, which is misleading.

> > +       it does, 2 if it is missing, and 128 in case looking up the reference
> > +       failed with an error other than the reference being missing.
> 
> The commit message says it returns 1 for a generic error, but this
> inconsistently says it returns 128 for that case. The actual
> implementation returns 1.

Good catch, fixed.

> > diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> > @@ -214,6 +215,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
> > +static int cmd_show_ref__exists(const char **refs)
> > +{
> > +       struct strbuf unused_referent = STRBUF_INIT;
> > +       struct object_id unused_oid;
> > +       unsigned int unused_type;
> > +       int failure_errno = 0;
> > +       const char *ref;
> > +       int ret = 1;
> > +
> > +       if (!refs || !*refs)
> > +               die("--exists requires a reference");
> > +       ref = *refs++;
> > +       if (*refs)
> > +               die("--exists requires exactly one reference");
> > +
> > +       if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
> > +                             &unused_oid, &unused_referent, &unused_type,
> > +                             &failure_errno)) {
> > +               if (failure_errno == ENOENT) {
> > +                       error(_("reference does not exist"));
> 
> The documentation doesn't mention this printing any output, and indeed
> one would intuitively expect a boolean-like operation to not produce
> any printed output since its exit code indicates the result (except,
> of course, in the case of a real error).

I'm inclined to leave this as-is. While the exit code should be
sufficient, I think it's rather easy to wonder whether it actually did
anything at all and why it failed in more interactive use cases. Not
that I think these will necessarily exist.

I also don't think it's going to hurt to print this error. If it ever
does start to become a problem we might end up honoring the "--quiet"
flag to squelch this case.

> > +                       ret = 2;
> > +               } else {
> > +                       error(_("failed to look up reference: %s"), strerror(failure_errno));
> 
> Or use error_errno():
> 
>     errno = failure_errno;
>     error_errno(_("failed to look up reference: %s"));

Ah, good suggestion.

> > +               }
> > +
> > +               goto out;
> > +       }
> > +
> > +       ret = 0;
> > +
> > +out:
> > +       strbuf_release(&unused_referent);
> > +       return ret;
> > +}
> 
> It's a bit odd having `ret` be 1 at the outset rather than 0, thus
> making the logic a bit more difficult to reason about. I would have
> expected it to be organized like this:
> 
>     int ret = 0;
>     if (refs_read_raw_ref(...)) {
>          if (failure_errno == ENOENT) {
>             ret = 2;
>         } else {
>             ret = 1;
>             errno = failure_errno;
>             error_errno(_("failed to look up reference: %s"));
>        }
>     }
>     strbuf_release(...);
>     return ret;

Fair enough. I've seen both styles used in our codebase, but ultimately
don't care much which of either we use here. Will adapt.

> > @@ -272,13 +309,15 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
> > +       if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)
> > +               die(_("only one of --exclude-existing, --exists or --verify can be given"));
> 
> When reviewing an earlier patch in this series, I forgot to mention
> that we can simplify the life of translators by using placeholders:
> 
>     die(_("options '%s', '%s' or '%s' cannot be used together"),
>         "--exclude-existing", "--exists", "--verify");
> 
> which ensures that they don't translate the literal option names, and
> makes it possible to reuse the translated message in multiple
> locations (since it doesn't mention hard-coded option names).

Done.

Thanks for your review, highly appreciated! I'll wait until tomorrow for
additional feedback and then send out v2.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence
  2023-10-24 19:17 ` [PATCH 00/12] show-ref: introduce mode " Junio C Hamano
@ 2023-10-25 14:26   ` Han-Wen Nienhuys
  2023-10-25 14:44     ` Phillip Wood
  2023-10-26  9:44     ` Patrick Steinhardt
  0 siblings, 2 replies; 66+ messages in thread
From: Han-Wen Nienhuys @ 2023-10-25 14:26 UTC (permalink / raw
  To: Junio C Hamano; +Cc: Patrick Steinhardt, git, Eric Sunshine

On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Patrick Steinhardt <ps@pks.im> writes:
>
> > this patch series introduces a new `--exists` mode to git-show-ref(1) to
> > explicitly check for the existence of a reference, only.
>
> I agree that show-ref would be the best place for this feature (not
> rev-parse, which is already a kitchen sink).  After all, the command
> was designed for validating refs in 358ddb62 (Add "git show-ref"
> builtin command, 2006-09-15).
>
> Thanks.  Hopefully I can take a look before I go offline.

The series description doesn't say why users would care about this.

If this is just to ease testing, I suggest adding functionality to a
suitable test helper. Anything you add to git-show-ref is a publicly
visible API that needs documentation and comes with a stability
guarantee that is more expensive to maintain than test helper
functionality.

-- 
Han-Wen Nienhuys - Google Munich
I work 80%. Don't expect answers from me on Fridays.
--

Google Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich

Registergericht und -nummer: Hamburg, HRB 86891

Sitz der Gesellschaft: Hamburg

Geschäftsführer: Paul Manicle, Liana Sebastian


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

* Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence
  2023-10-25 14:26   ` Han-Wen Nienhuys
@ 2023-10-25 14:44     ` Phillip Wood
  2023-10-26  9:48       ` Patrick Steinhardt
  2023-10-26  9:44     ` Patrick Steinhardt
  1 sibling, 1 reply; 66+ messages in thread
From: Phillip Wood @ 2023-10-25 14:44 UTC (permalink / raw
  To: Han-Wen Nienhuys, Junio C Hamano; +Cc: Patrick Steinhardt, git, Eric Sunshine

On 25/10/2023 15:26, Han-Wen Nienhuys wrote:
> On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> Patrick Steinhardt <ps@pks.im> writes:
>>
>>> this patch series introduces a new `--exists` mode to git-show-ref(1) to
>>> explicitly check for the existence of a reference, only.
>>
>> I agree that show-ref would be the best place for this feature (not
>> rev-parse, which is already a kitchen sink).  After all, the command
>> was designed for validating refs in 358ddb62 (Add "git show-ref"
>> builtin command, 2006-09-15).
>>
>> Thanks.  Hopefully I can take a look before I go offline.
> 
> The series description doesn't say why users would care about this.
> 
> If this is just to ease testing, I suggest adding functionality to a
> suitable test helper. Anything you add to git-show-ref is a publicly
> visible API that needs documentation and comes with a stability
> guarantee that is more expensive to maintain than test helper
> functionality.

Does the new functionality provide a way for scripts to see if a branch 
is unborn (i.e. has not commits yet)? I don't think we have a way to 
distinguish between a ref that points to a missing object and an unborn 
branch at the moment.

Best Wishes

Phillip



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

* Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence
  2023-10-25 14:26   ` Han-Wen Nienhuys
  2023-10-25 14:44     ` Phillip Wood
@ 2023-10-26  9:44     ` Patrick Steinhardt
  1 sibling, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:44 UTC (permalink / raw
  To: Han-Wen Nienhuys; +Cc: Junio C Hamano, git, Eric Sunshine

[-- Attachment #1: Type: text/plain, Size: 3457 bytes --]

On Wed, Oct 25, 2023 at 04:26:35PM +0200, Han-Wen Nienhuys wrote:
> On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > Patrick Steinhardt <ps@pks.im> writes:
> >
> > > this patch series introduces a new `--exists` mode to git-show-ref(1) to
> > > explicitly check for the existence of a reference, only.
> >
> > I agree that show-ref would be the best place for this feature (not
> > rev-parse, which is already a kitchen sink).  After all, the command
> > was designed for validating refs in 358ddb62 (Add "git show-ref"
> > builtin command, 2006-09-15).
> >
> > Thanks.  Hopefully I can take a look before I go offline.
> 
> The series description doesn't say why users would care about this.
> 
> If this is just to ease testing, I suggest adding functionality to a
> suitable test helper. Anything you add to git-show-ref is a publicly
> visible API that needs documentation and comes with a stability
> guarantee that is more expensive to maintain than test helper
> functionality.

The first patch of the original patch series where I split this out from
did exactly that, see [1]. Junio questioned though whether this should
be part of production code instead of being a test helper.

And I tend to agree with him, or otherwise I wouldn't have written this
series. It's actually a bit surprising that we do not have any way to
test for reference existence in any of our helpers in a generic way. All
current tooling that I'm aware of is lacking in some ways:

    - git-rev-parse(1) will fail to parse symbolic refs whose target
      does not exist.

    - git-symbolic-ref(1) can look up such unborn branches, but the
      caller needs to be aware that 

    - git-show-ref(1) tries to resolve symbolic references.

    - git-for-each-ref(1) is simply not an obvious way to check for ref
      existence.

    - All of these will fail to parse references with malformed names.

So the new `git show-ref --exists` mode is a trivial-to-use and generic
way to simply ask "Do you know this reference?". The lack of this option
likely shows that you can most often get away without such a tool, but I
still find it funny that there is no obvious way to perform this query
right now.

At the Contributor's Summit, we've also discussed the issue that our
plumbing layer has become less useful over the years. It is often
missing functionality that exists in user-facing commands. It also has
inherited many of the restrictions of our porcelain tools, like not
being able to look up references with bad names. So this series is a
small step into the direction of making our plumbing more useful again.

I also assume that it's only going to become more important to address
these limitations in our plumbing layer once we have something like the
reftable backend. A user or admin would have been able to fix issues
with misformatted referencen names rather easily in the reffiles backend
even without support in our plumbing layer, as it mostly was a single
"rm .git/refs/$broken_ref" away. But that's not going to be an easy
solution anymore with reftable due to the complexity of its format. So I
also see it as part of the upcoming preparatory work to make sure that
they have all the necessary tools to address such situations, at least
up to a reasonable point.

Patrick

[1]: <e947feb1c77f7e9f3c7f983bbe47137fbce42367.1697607222.git.ps@pks.im>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence
  2023-10-25 14:44     ` Phillip Wood
@ 2023-10-26  9:48       ` Patrick Steinhardt
  2023-10-27 13:06         ` Phillip Wood
  0 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:48 UTC (permalink / raw
  To: phillip.wood; +Cc: Han-Wen Nienhuys, Junio C Hamano, git, Eric Sunshine

[-- Attachment #1: Type: text/plain, Size: 1818 bytes --]

On Wed, Oct 25, 2023 at 03:44:33PM +0100, Phillip Wood wrote:
> On 25/10/2023 15:26, Han-Wen Nienhuys wrote:
> > On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:
> > > 
> > > Patrick Steinhardt <ps@pks.im> writes:
> > > 
> > > > this patch series introduces a new `--exists` mode to git-show-ref(1) to
> > > > explicitly check for the existence of a reference, only.
> > > 
> > > I agree that show-ref would be the best place for this feature (not
> > > rev-parse, which is already a kitchen sink).  After all, the command
> > > was designed for validating refs in 358ddb62 (Add "git show-ref"
> > > builtin command, 2006-09-15).
> > > 
> > > Thanks.  Hopefully I can take a look before I go offline.
> > 
> > The series description doesn't say why users would care about this.
> > 
> > If this is just to ease testing, I suggest adding functionality to a
> > suitable test helper. Anything you add to git-show-ref is a publicly
> > visible API that needs documentation and comes with a stability
> > guarantee that is more expensive to maintain than test helper
> > functionality.
> 
> Does the new functionality provide a way for scripts to see if a branch is
> unborn (i.e. has not commits yet)? I don't think we have a way to
> distinguish between a ref that points to a missing object and an unborn
> branch at the moment.

You could do it with two commands:

```
target=$(git symbolic-ref HEAD)
git show-ref --exists "$target"
case "$?" in
2)
    echo "unborn branch";;
0)
    echo "branch exists";;
*)
    echo "could be anything, dunno";;
esac
```

While you could use git-rev-parse(1) instead of git-show-ref(1), you
wouldn't be able to easily distinguish the case of a missing target
reference and any other kind of error.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 00/12] show-ref: introduce mode to check for ref existence
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (12 preceding siblings ...)
  2023-10-24 19:17 ` [PATCH 00/12] show-ref: introduce mode " Junio C Hamano
@ 2023-10-26  9:56 ` Patrick Steinhardt
  2023-10-26  9:56   ` [PATCH v2 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
                     ` (12 more replies)
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
  14 siblings, 13 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 14503 bytes --]

Hi,

this is the second version of my patch series that introduces a new `git
show-ref --exists` mode to check for reference existence.

Changes compared to v1:

    - Various typo fixes in commit messages.

    - Stopped using non-constant designated initializers.

    - Renamed a varible from `matchlen` to `patternlen` to match another
      renamed variable better.

    - Improved the error message for mutually exclusive modes to be
      easier to handle for translators.

    - "--quiet" is not advertised for the pattern-based mode of
      git-show-ref(1) anymore.

    - Rephrased "error code" to "exit code" in the documentation.

Thanks for the feedback so far!

Patrick

Patrick Steinhardt (12):
  builtin/show-ref: convert pattern to a local variable
  builtin/show-ref: split up different subcommands
  builtin/show-ref: fix leaking string buffer
  builtin/show-ref: fix dead code when passing patterns
  builtin/show-ref: refactor `--exclude-existing` options
  builtin/show-ref: stop using global variable to count matches
  builtin/show-ref: stop using global vars for `show_one()`
  builtin/show-ref: refactor options for patterns subcommand
  builtin/show-ref: ensure mutual exclusiveness of subcommands
  builtin/show-ref: explicitly spell out different modes in synopsis
  builtin/show-ref: add new mode to check for reference existence
  t: use git-show-ref(1) to check for ref existence

 Documentation/git-show-ref.txt |  20 ++-
 builtin/show-ref.c             | 280 ++++++++++++++++++++++-----------
 t/t1403-show-ref.sh            |  70 +++++++++
 t/t1430-bad-ref-name.sh        |  27 ++--
 t/t3200-branch.sh              |  33 ++--
 t/t5521-pull-options.sh        |   4 +-
 t/t5605-clone-local.sh         |   2 +-
 t/test-lib-functions.sh        |  55 +++++++
 8 files changed, 369 insertions(+), 122 deletions(-)

Range-diff against v1:
 1:  78163accbd2 =  1:  78163accbd2 builtin/show-ref: convert pattern to a local variable
 2:  7e6ab5dee23 !  2:  9a234622d99 builtin/show-ref: split up different subcommands
    @@ builtin/show-ref.c: static int exclude_existing(const char *match)
     +
     +static int cmd_show_ref__patterns(const char **patterns)
     +{
    -+	struct show_ref_data show_ref_data = {
    -+		.patterns = (patterns && *patterns) ? patterns : NULL,
    -+	};
    ++	struct show_ref_data show_ref_data = {0};
    ++
    ++	if (patterns && *patterns)
    ++		show_ref_data.patterns = patterns;
     +
     +	if (show_head)
     +		head_ref(show_ref, &show_ref_data);
 3:  ae2e401fbd8 =  3:  bb0d656a0b4 builtin/show-ref: fix leaking string buffer
 4:  29c0c0c6c97 !  4:  87afcee830c builtin/show-ref: fix dead code when passing patterns
    @@ Commit message
         builtin/show-ref: fix dead code when passing patterns
     
         When passing patterns to `git show-ref` we have some code that will
    -    cause us to die of `verify && !quiet` is true. But because `verify`
    +    cause us to die if `verify && !quiet` is true. But because `verify`
         indicates a different subcommand of git-show-ref(1) that causes us to
         execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the
         condition cannot ever be true.
 5:  8d0b0b5700c !  5:  bed2a8a0769 builtin/show-ref: refactor `--exclude-existing` options
    @@ Commit message
         builtin/show-ref: refactor `--exclude-existing` options
     
         It's not immediately obvious options which options are applicable to
    -    what subcommand ni git-show-ref(1) because all options exist as global
    +    what subcommand in git-show-ref(1) because all options exist as global
         state. This can easily cause confusion for the reader.
     
         Refactor options for the `--exclude-existing` subcommand to be contained
         in a separate structure. This structure is stored on the stack and
    -    passed down as required. Consequentially, it clearly delimits the scope
    -    of those options and requires the reader to worry less about global
    -    state.
    +    passed down as required. Consequently, it clearly delimits the scope of
    +    those options and requires the reader to worry less about global state.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
    @@ builtin/show-ref.c: static int add_existing(const char *refname,
      	struct string_list existing_refs = STRING_LIST_INIT_DUP;
      	char buf[1024];
     -	int matchlen = match ? strlen(match) : 0;
    -+	int matchlen = opts->pattern ? strlen(opts->pattern) : 0;
    ++	int patternlen = opts->pattern ? strlen(opts->pattern) : 0;
      
      	for_each_ref(add_existing, &existing_refs);
      	while (fgets(buf, sizeof(buf), stdin)) {
    @@ builtin/show-ref.c: static int cmd_show_ref__exclude_existing(const char *match)
     -		if (match) {
     +		if (opts->pattern) {
      			int reflen = buf + len - ref;
    - 			if (reflen < matchlen)
    +-			if (reflen < matchlen)
    ++			if (reflen < patternlen)
      				continue;
     -			if (strncmp(ref, match, matchlen))
    -+			if (strncmp(ref, opts->pattern, matchlen))
    ++			if (strncmp(ref, opts->pattern, patternlen))
      				continue;
      		}
      		if (check_refname_format(ref, 0)) {
 6:  6e0f3d4e104 =  6:  d52a5e8ced2 builtin/show-ref: stop using global variable to count matches
 7:  2da1e65dd8f !  7:  63f1dadf4c2 builtin/show-ref: stop using global vars for `show_one()`
    @@ builtin/show-ref.c: static int cmd_show_ref__verify(const char **refs)
     +static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,
     +				  const char **patterns)
      {
    - 	struct show_ref_data show_ref_data = {
    +-	struct show_ref_data show_ref_data = {0};
    ++	struct show_ref_data show_ref_data = {
     +		.show_one_opts = show_one_opts,
    - 		.patterns = (patterns && *patterns) ? patterns : NULL,
    - 	};
    ++	};
      
    + 	if (patterns && *patterns)
    + 		show_ref_data.patterns = patterns;
     @@ builtin/show-ref.c: static int cmd_show_ref__patterns(const char **patterns)
      
      static int hash_callback(const struct option *opt, const char *arg, int unset)
 8:  805889eda4c !  8:  88dfeaa4871 builtin/show-ref: refactor options for patterns subcommand
    @@ builtin/show-ref.c: static int cmd_show_ref__verify(const struct show_one_option
      	struct show_ref_data show_ref_data = {
      		.show_one_opts = show_one_opts,
     +		.show_head = opts->show_head,
    - 		.patterns = (patterns && *patterns) ? patterns : NULL,
      	};
      
    + 	if (patterns && *patterns)
    + 		show_ref_data.patterns = patterns;
    + 
     -	if (show_head)
     +	if (opts->show_head)
      		head_ref(show_ref, &show_ref_data);
 9:  d0a991cf4f8 !  9:  5ba566723e8 builtin/show-ref: ensure mutual exclusiveness of subcommands
    @@ builtin/show-ref.c: int cmd_show_ref(int argc, const char **argv, const char *pr
      			     show_ref_usage, 0);
      
     +	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
    -+		die(_("only one of --exclude-existing or --verify can be given"));
    ++		die(_("only one of '%s' or '%s' can be given"),
    ++		    "--exclude-existing", "--verify");
     +
      	if (exclude_existing_opts.enabled)
      		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '
      
     +test_expect_success 'show-ref sub-modes are mutually exclusive' '
     +	test_must_fail git show-ref --verify --exclude-existing 2>err &&
    -+	grep "only one of --exclude-existing or --verify can be given" err
    ++	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
     +'
     +
      test_done
10:  adcfa7a6a9d ! 10:  b78ccc5f692 builtin/show-ref: explicitly spell out different modes in synopsis
    @@ Commit message
         Split up the synopsis for these two modes such that we can disambiguate
         those differences.
     
    +    While at it, drop "--quiet" from the pattern mode's synopsis. It does
    +    not make a lot of sense to list patterns, but squelch the listing output
    +    itself. The description for "--quiet" is adapted accordingly.
    +
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
      ## Documentation/git-show-ref.txt ##
    @@ Documentation/git-show-ref.txt: git-show-ref - List references in a local reposi
      --------
      [verse]
     -'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]
    -+'git show-ref' [-q | --quiet] [--head] [-d | --dereference]
    ++'git show-ref' [--head] [-d | --dereference]
      	     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
      	     [--heads] [--] [<pattern>...]
     +'git show-ref' --verify [-q | --quiet] [-d | --dereference]
    @@ Documentation/git-show-ref.txt: git-show-ref - List references in a local reposi
      'git show-ref' --exclude-existing[=<pattern>]
      
      DESCRIPTION
    +@@ Documentation/git-show-ref.txt: OPTIONS
    + -q::
    + --quiet::
    + 
    +-	Do not print any results to stdout. When combined with `--verify`, this
    +-	can be used to silently check if a reference exists.
    ++	Do not print any results to stdout. Can be used with `--verify` to
    ++	silently check if a reference exists.
    + 
    + --exclude-existing[=<pattern>]::
    + 
     
      ## builtin/show-ref.c ##
     @@
    @@ builtin/show-ref.c
      
      static const char * const show_ref_usage[] = {
     -	N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference]\n"
    -+	N_("git show-ref [-q | --quiet] [--head] [-d | --dereference]\n"
    ++	N_("git show-ref [--head] [-d | --dereference]\n"
      	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n"
      	   "             [--heads] [--] [<pattern>...]"),
     +	N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n"
11:  2f876e61dd3 ! 11:  327942b1162 builtin/show-ref: add new mode to check for reference existence
    @@ Commit message
     
             - Dangling symrefs can be resolved via git-symbolic-ref(1), but this
               requires the caller to special case existence checks depending on
    -          whteher or not a reference is symbolic or direct.
    +          whether or not a reference is symbolic or direct.
     
         Furthermore, git-rev-list(1) and other commands do not let the caller
         distinguish easily between an actually missing reference and a generic
         error.
     
    -    Taken together, this gseems like sufficient motivation to introduce a
    +    Taken together, this seems like sufficient motivation to introduce a
         separate plumbing command to explicitly check for the existence of a
         reference without trying to resolve its contents.
     
         This new command comes in the form of `git show-ref --exists`. This
         new mode will exit successfully when the reference exists, with a
    -    specific error code of 2 when it does not exist, or with 1 when there
    +    specific exit code of 2 when it does not exist, or with 1 when there
         has been a generic error.
     
         Note that the only way to properly implement this command is by using
    @@ Documentation/git-show-ref.txt: OPTIONS
      
     +--exists::
     +
    -+	Check whether the given reference exists. Returns an error code of 0 if
    -+	it does, 2 if it is missing, and 128 in case looking up the reference
    ++	Check whether the given reference exists. Returns an exit code of 0 if
    ++	it does, 2 if it is missing, and 1 in case looking up the reference
     +	failed with an error other than the reference being missing.
     +
      --abbrev[=<n>]::
    @@ builtin/show-ref.c: static int cmd_show_ref__patterns(const struct patterns_opti
     +	unsigned int unused_type;
     +	int failure_errno = 0;
     +	const char *ref;
    -+	int ret = 1;
    ++	int ret = 0;
     +
     +	if (!refs || !*refs)
     +		die("--exists requires a reference");
    @@ builtin/show-ref.c: static int cmd_show_ref__patterns(const struct patterns_opti
     +			error(_("reference does not exist"));
     +			ret = 2;
     +		} else {
    -+			error(_("failed to look up reference: %s"), strerror(failure_errno));
    ++			errno = failure_errno;
    ++			error_errno(_("failed to look up reference"));
    ++			ret = 1;
     +		}
     +
     +		goto out;
     +	}
     +
    -+	ret = 0;
    -+
     +out:
     +	strbuf_release(&unused_referent);
     +	return ret;
    @@ builtin/show-ref.c: int cmd_show_ref(int argc, const char **argv, const char *pr
      			     show_ref_usage, 0);
      
     -	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
    --		die(_("only one of --exclude-existing or --verify can be given"));
    +-		die(_("only one of '%s' or '%s' can be given"),
    +-		    "--exclude-existing", "--verify");
     +	if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)
    -+		die(_("only one of --exclude-existing, --exists or --verify can be given"));
    ++		die(_("only one of '%s', '%s' or '%s' can be given"),
    ++		    "--exclude-existing", "--verify", "--exists");
      
      	if (exclude_existing_opts.enabled)
      		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '
      
      test_expect_success 'show-ref sub-modes are mutually exclusive' '
     +	cat >expect <<-EOF &&
    -+	fatal: only one of --exclude-existing, --exists or --verify can be given
    ++	fatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given
     +	EOF
     +
      	test_must_fail git show-ref --verify --exclude-existing 2>err &&
    --	grep "only one of --exclude-existing or --verify can be given" err
    +-	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
     +	test_cmp expect err &&
     +
     +	test_must_fail git show-ref --verify --exists 2>err &&
12:  a3a43d82e1f = 12:  226731c5f18 t: use git-show-ref(1) to check for ref existence

base-commit: a9ecda2788e229afc9b611acaa26d0d9d4da53ed
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 01/12] builtin/show-ref: convert pattern to a local variable
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
@ 2023-10-26  9:56   ` Patrick Steinhardt
  2023-10-26  9:56   ` [PATCH v2 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
                     ` (11 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 3886 bytes --]

The `pattern` variable is a global variable that tracks either the
reference names (not patterns!) for the `--verify` mode or the patterns
for the non-verify mode. This is a bit confusing due to the slightly
different meanings.

Convert the variable to be local. While this does not yet fix the double
meaning of the variable, this change allows us to address it in a
subsequent patch more easily by explicitly splitting up the different
subcommands of git-show-ref(1).

Note that we introduce a `struct show_ref_data` to pass the patterns to
`show_ref()`. While this is overengineered now, we will extend this
structure in a subsequent patch.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 46 ++++++++++++++++++++++++++++------------------
 1 file changed, 28 insertions(+), 18 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 5110814f796..7efab14b96c 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -20,7 +20,6 @@ static const char * const show_ref_usage[] = {
 
 static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
 	   quiet, hash_only, abbrev, exclude_arg;
-static const char **pattern;
 static const char *exclude_existing_arg;
 
 static void show_one(const char *refname, const struct object_id *oid)
@@ -50,15 +49,21 @@ static void show_one(const char *refname, const struct object_id *oid)
 	}
 }
 
+struct show_ref_data {
+	const char **patterns;
+};
+
 static int show_ref(const char *refname, const struct object_id *oid,
-		    int flag UNUSED, void *cbdata UNUSED)
+		    int flag UNUSED, void *cbdata)
 {
+	struct show_ref_data *data = cbdata;
+
 	if (show_head && !strcmp(refname, "HEAD"))
 		goto match;
 
-	if (pattern) {
+	if (data->patterns) {
 		int reflen = strlen(refname);
-		const char **p = pattern, *m;
+		const char **p = data->patterns, *m;
 		while ((m = *p++) != NULL) {
 			int len = strlen(m);
 			if (len > reflen)
@@ -180,6 +185,9 @@ static const struct option show_ref_options[] = {
 
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
+	struct show_ref_data show_ref_data = {0};
+	const char **patterns;
+
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
@@ -188,38 +196,40 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	if (exclude_arg)
 		return exclude_existing(exclude_existing_arg);
 
-	pattern = argv;
-	if (!*pattern)
-		pattern = NULL;
+	patterns = argv;
+	if (!*patterns)
+		patterns = NULL;
 
 	if (verify) {
-		if (!pattern)
+		if (!patterns)
 			die("--verify requires a reference");
-		while (*pattern) {
+		while (*patterns) {
 			struct object_id oid;
 
-			if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) &&
-			    !read_ref(*pattern, &oid)) {
-				show_one(*pattern, &oid);
+			if ((starts_with(*patterns, "refs/") || !strcmp(*patterns, "HEAD")) &&
+			    !read_ref(*patterns, &oid)) {
+				show_one(*patterns, &oid);
 			}
 			else if (!quiet)
-				die("'%s' - not a valid ref", *pattern);
+				die("'%s' - not a valid ref", *patterns);
 			else
 				return 1;
-			pattern++;
+			patterns++;
 		}
 		return 0;
 	}
 
+	show_ref_data.patterns = patterns;
+
 	if (show_head)
-		head_ref(show_ref, NULL);
+		head_ref(show_ref, &show_ref_data);
 	if (heads_only || tags_only) {
 		if (heads_only)
-			for_each_fullref_in("refs/heads/", show_ref, NULL);
+			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
 		if (tags_only)
-			for_each_fullref_in("refs/tags/", show_ref, NULL);
+			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
 	} else {
-		for_each_ref(show_ref, NULL);
+		for_each_ref(show_ref, &show_ref_data);
 	}
 	if (!found_match) {
 		if (verify && !quiet)
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 02/12] builtin/show-ref: split up different subcommands
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
  2023-10-26  9:56   ` [PATCH v2 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
@ 2023-10-26  9:56   ` Patrick Steinhardt
  2023-10-26  9:56   ` [PATCH v2 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
                     ` (10 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 4259 bytes --]

While not immediately obvious, git-show-ref(1) actually implements three
different subcommands:

    - `git show-ref <patterns>` can be used to list references that
      match a specific pattern.

    - `git show-ref --verify <refs>` can be used to list references.
      These are _not_ patterns.

    - `git show-ref --exclude-existing` can be used as a filter that
      reads references from standard input, performing some conversions
      on each of them.

Let's make this more explicit in the code by splitting up the three
subcommands into separate functions. This also allows us to address the
confusingly named `patterns` variable, which may hold either patterns or
reference names.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 101 ++++++++++++++++++++++++---------------------
 1 file changed, 54 insertions(+), 47 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 7efab14b96c..cad5b8b5066 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -104,7 +104,7 @@ static int add_existing(const char *refname,
  * (4) ignore if refname is a ref that exists in the local repository;
  * (5) otherwise output the line.
  */
-static int exclude_existing(const char *match)
+static int cmd_show_ref__exclude_existing(const char *match)
 {
 	static struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
@@ -142,6 +142,54 @@ static int exclude_existing(const char *match)
 	return 0;
 }
 
+static int cmd_show_ref__verify(const char **refs)
+{
+	if (!refs || !*refs)
+		die("--verify requires a reference");
+
+	while (*refs) {
+		struct object_id oid;
+
+		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
+		    !read_ref(*refs, &oid)) {
+			show_one(*refs, &oid);
+		}
+		else if (!quiet)
+			die("'%s' - not a valid ref", *refs);
+		else
+			return 1;
+		refs++;
+	}
+
+	return 0;
+}
+
+static int cmd_show_ref__patterns(const char **patterns)
+{
+	struct show_ref_data show_ref_data = {0};
+
+	if (patterns && *patterns)
+		show_ref_data.patterns = patterns;
+
+	if (show_head)
+		head_ref(show_ref, &show_ref_data);
+	if (heads_only || tags_only) {
+		if (heads_only)
+			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
+		if (tags_only)
+			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
+	} else {
+		for_each_ref(show_ref, &show_ref_data);
+	}
+	if (!found_match) {
+		if (verify && !quiet)
+			die("No match");
+		return 1;
+	}
+
+	return 0;
+}
+
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
 	hash_only = 1;
@@ -185,56 +233,15 @@ static const struct option show_ref_options[] = {
 
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
-	struct show_ref_data show_ref_data = {0};
-	const char **patterns;
-
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
 	if (exclude_arg)
-		return exclude_existing(exclude_existing_arg);
-
-	patterns = argv;
-	if (!*patterns)
-		patterns = NULL;
-
-	if (verify) {
-		if (!patterns)
-			die("--verify requires a reference");
-		while (*patterns) {
-			struct object_id oid;
-
-			if ((starts_with(*patterns, "refs/") || !strcmp(*patterns, "HEAD")) &&
-			    !read_ref(*patterns, &oid)) {
-				show_one(*patterns, &oid);
-			}
-			else if (!quiet)
-				die("'%s' - not a valid ref", *patterns);
-			else
-				return 1;
-			patterns++;
-		}
-		return 0;
-	}
-
-	show_ref_data.patterns = patterns;
-
-	if (show_head)
-		head_ref(show_ref, &show_ref_data);
-	if (heads_only || tags_only) {
-		if (heads_only)
-			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
-		if (tags_only)
-			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
-	} else {
-		for_each_ref(show_ref, &show_ref_data);
-	}
-	if (!found_match) {
-		if (verify && !quiet)
-			die("No match");
-		return 1;
-	}
-	return 0;
+		return cmd_show_ref__exclude_existing(exclude_existing_arg);
+	else if (verify)
+		return cmd_show_ref__verify(argv);
+	else
+		return cmd_show_ref__patterns(argv);
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 03/12] builtin/show-ref: fix leaking string buffer
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
  2023-10-26  9:56   ` [PATCH v2 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
  2023-10-26  9:56   ` [PATCH v2 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
@ 2023-10-26  9:56   ` Patrick Steinhardt
  2023-10-30 18:10     ` Taylor Blau
  2023-10-26  9:56   ` [PATCH v2 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
                     ` (9 subsequent siblings)
  12 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 1014 bytes --]

Fix a leaking string buffer in `git show-ref --exclude-existing`. While
the buffer is technically not leaking because its variable is declared
as static, there is no inherent reason why it should be.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index cad5b8b5066..e55c38af478 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -106,7 +106,7 @@ static int add_existing(const char *refname,
  */
 static int cmd_show_ref__exclude_existing(const char *match)
 {
-	static struct string_list existing_refs = STRING_LIST_INIT_DUP;
+	struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
 	int matchlen = match ? strlen(match) : 0;
 
@@ -139,6 +139,8 @@ static int cmd_show_ref__exclude_existing(const char *match)
 			printf("%s\n", buf);
 		}
 	}
+
+	string_list_clear(&existing_refs, 0);
 	return 0;
 }
 
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 04/12] builtin/show-ref: fix dead code when passing patterns
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2023-10-26  9:56   ` [PATCH v2 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
@ 2023-10-26  9:56   ` Patrick Steinhardt
  2023-10-30 18:24     ` Taylor Blau
  2023-10-26  9:56   ` [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
                     ` (8 subsequent siblings)
  12 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 914 bytes --]

When passing patterns to `git show-ref` we have some code that will
cause us to die if `verify && !quiet` is true. But because `verify`
indicates a different subcommand of git-show-ref(1) that causes us to
execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the
condition cannot ever be true.

Let's remove this dead code.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index e55c38af478..f95418d3d16 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -183,11 +183,8 @@ static int cmd_show_ref__patterns(const char **patterns)
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
 	}
-	if (!found_match) {
-		if (verify && !quiet)
-			die("No match");
+	if (!found_match)
 		return 1;
-	}
 
 	return 0;
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2023-10-26  9:56   ` [PATCH v2 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
@ 2023-10-26  9:56   ` Patrick Steinhardt
  2023-10-30 18:37     ` Taylor Blau
  2023-10-30 18:55     ` Taylor Blau
  2023-10-26  9:56   ` [PATCH v2 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
                     ` (7 subsequent siblings)
  12 siblings, 2 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 5814 bytes --]

It's not immediately obvious options which options are applicable to
what subcommand in git-show-ref(1) because all options exist as global
state. This can easily cause confusion for the reader.

Refactor options for the `--exclude-existing` subcommand to be contained
in a separate structure. This structure is stored on the stack and
passed down as required. Consequently, it clearly delimits the scope of
those options and requires the reader to worry less about global state.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 74 +++++++++++++++++++++++++---------------------
 1 file changed, 40 insertions(+), 34 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index f95418d3d16..90481c58492 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {
 };
 
 static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
-	   quiet, hash_only, abbrev, exclude_arg;
-static const char *exclude_existing_arg;
+	   quiet, hash_only, abbrev;
 
 static void show_one(const char *refname, const struct object_id *oid)
 {
@@ -95,6 +94,11 @@ static int add_existing(const char *refname,
 	return 0;
 }
 
+struct exclude_existing_options {
+	int enabled;
+	const char *pattern;
+};
+
 /*
  * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
  * and
@@ -104,11 +108,11 @@ static int add_existing(const char *refname,
  * (4) ignore if refname is a ref that exists in the local repository;
  * (5) otherwise output the line.
  */
-static int cmd_show_ref__exclude_existing(const char *match)
+static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)
 {
 	struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
-	int matchlen = match ? strlen(match) : 0;
+	int patternlen = opts->pattern ? strlen(opts->pattern) : 0;
 
 	for_each_ref(add_existing, &existing_refs);
 	while (fgets(buf, sizeof(buf), stdin)) {
@@ -124,11 +128,11 @@ static int cmd_show_ref__exclude_existing(const char *match)
 		for (ref = buf + len; buf < ref; ref--)
 			if (isspace(ref[-1]))
 				break;
-		if (match) {
+		if (opts->pattern) {
 			int reflen = buf + len - ref;
-			if (reflen < matchlen)
+			if (reflen < patternlen)
 				continue;
-			if (strncmp(ref, match, matchlen))
+			if (strncmp(ref, opts->pattern, patternlen))
 				continue;
 		}
 		if (check_refname_format(ref, 0)) {
@@ -201,44 +205,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)
 static int exclude_existing_callback(const struct option *opt, const char *arg,
 				     int unset)
 {
+	struct exclude_existing_options *opts = opt->value;
 	BUG_ON_OPT_NEG(unset);
-	exclude_arg = 1;
-	*(const char **)opt->value = arg;
+	opts->enabled = 1;
+	opts->pattern = arg;
 	return 0;
 }
 
-static const struct option show_ref_options[] = {
-	OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
-	OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
-	OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
-		    "requires exact ref path")),
-	OPT_HIDDEN_BOOL('h', NULL, &show_head,
-			N_("show the HEAD reference, even if it would be filtered out")),
-	OPT_BOOL(0, "head", &show_head,
-	  N_("show the HEAD reference, even if it would be filtered out")),
-	OPT_BOOL('d', "dereference", &deref_tags,
-		    N_("dereference tags into object IDs")),
-	OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
-		       N_("only show SHA1 hash using <n> digits"),
-		       PARSE_OPT_OPTARG, &hash_callback),
-	OPT__ABBREV(&abbrev),
-	OPT__QUIET(&quiet,
-		   N_("do not print results to stdout (useful with --verify)")),
-	OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg,
-		       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
-		       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
-	OPT_END()
-};
-
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
+	struct exclude_existing_options exclude_existing_opts = {0};
+	const struct option show_ref_options[] = {
+		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
+		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
+			    "requires exact ref path")),
+		OPT_HIDDEN_BOOL('h', NULL, &show_head,
+				N_("show the HEAD reference, even if it would be filtered out")),
+		OPT_BOOL(0, "head", &show_head,
+		  N_("show the HEAD reference, even if it would be filtered out")),
+		OPT_BOOL('d', "dereference", &deref_tags,
+			    N_("dereference tags into object IDs")),
+		OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+			       N_("only show SHA1 hash using <n> digits"),
+			       PARSE_OPT_OPTARG, &hash_callback),
+		OPT__ABBREV(&abbrev),
+		OPT__QUIET(&quiet,
+			   N_("do not print results to stdout (useful with --verify)")),
+		OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_opts,
+			       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
+			       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
+		OPT_END()
+	};
+
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
-	if (exclude_arg)
-		return cmd_show_ref__exclude_existing(exclude_existing_arg);
+	if (exclude_existing_opts.enabled)
+		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
 		return cmd_show_ref__verify(argv);
 	else
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 06/12] builtin/show-ref: stop using global variable to count matches
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2023-10-26  9:56   ` [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
@ 2023-10-26  9:56   ` Patrick Steinhardt
  2023-10-30 19:14     ` Taylor Blau
  2023-10-26  9:56   ` [PATCH v2 07/12] builtin/show-ref: stop using global vars for `show_one()` Patrick Steinhardt
                     ` (6 subsequent siblings)
  12 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 1729 bytes --]

When passing patterns to git-show-ref(1) we're checking whether any
reference matches -- if none does, we indicate this condition via an
unsuccessful exit code.

We're using a global variable to count these matches, which is required
because the counter is getting incremented in a callback function. But
now that we have the `struct show_ref_data` in place, we can get rid of
the global variable and put the counter in there instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 90481c58492..c30c01785cc 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,7 +18,7 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
+static int deref_tags, show_head, tags_only, heads_only, verify,
 	   quiet, hash_only, abbrev;
 
 static void show_one(const char *refname, const struct object_id *oid)
@@ -50,6 +50,7 @@ static void show_one(const char *refname, const struct object_id *oid)
 
 struct show_ref_data {
 	const char **patterns;
+	int found_match;
 };
 
 static int show_ref(const char *refname, const struct object_id *oid,
@@ -78,7 +79,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 	}
 
 match:
-	found_match++;
+	data->found_match++;
 
 	show_one(refname, oid);
 
@@ -187,7 +188,7 @@ static int cmd_show_ref__patterns(const char **patterns)
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
 	}
-	if (!found_match)
+	if (!show_ref_data.found_match)
 		return 1;
 
 	return 0;
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 07/12] builtin/show-ref: stop using global vars for `show_one()`
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (5 preceding siblings ...)
  2023-10-26  9:56   ` [PATCH v2 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
@ 2023-10-26  9:56   ` Patrick Steinhardt
  2023-10-26  9:56   ` [PATCH v2 08/12] builtin/show-ref: refactor options for patterns subcommand Patrick Steinhardt
                     ` (5 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 6148 bytes --]

The `show_one()` function implicitly receives a bunch of options which
are tracked via global variables. This makes it hard to see which
subcommands of git-show-ref(1) actually make use of these options.

Introduce a `show_one_options` structure that gets passed down to this
function. This allows us to get rid of more global state and makes it
more explicit which subcommands use those options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 62 ++++++++++++++++++++++++++++++----------------
 1 file changed, 40 insertions(+), 22 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index c30c01785cc..3e6ad6b6a84 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,10 +18,17 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int deref_tags, show_head, tags_only, heads_only, verify,
-	   quiet, hash_only, abbrev;
+static int show_head, tags_only, heads_only, verify;
 
-static void show_one(const char *refname, const struct object_id *oid)
+struct show_one_options {
+	int quiet;
+	int hash_only;
+	int abbrev;
+	int deref_tags;
+};
+
+static void show_one(const struct show_one_options *opts,
+		     const char *refname, const struct object_id *oid)
 {
 	const char *hex;
 	struct object_id peeled;
@@ -30,25 +37,26 @@ static void show_one(const char *refname, const struct object_id *oid)
 		die("git show-ref: bad ref %s (%s)", refname,
 		    oid_to_hex(oid));
 
-	if (quiet)
+	if (opts->quiet)
 		return;
 
-	hex = repo_find_unique_abbrev(the_repository, oid, abbrev);
-	if (hash_only)
+	hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
+	if (opts->hash_only)
 		printf("%s\n", hex);
 	else
 		printf("%s %s\n", hex, refname);
 
-	if (!deref_tags)
+	if (!opts->deref_tags)
 		return;
 
 	if (!peel_iterated_oid(oid, &peeled)) {
-		hex = repo_find_unique_abbrev(the_repository, &peeled, abbrev);
+		hex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);
 		printf("%s %s^{}\n", hex, refname);
 	}
 }
 
 struct show_ref_data {
+	const struct show_one_options *show_one_opts;
 	const char **patterns;
 	int found_match;
 };
@@ -81,7 +89,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 match:
 	data->found_match++;
 
-	show_one(refname, oid);
+	show_one(data->show_one_opts, refname, oid);
 
 	return 0;
 }
@@ -149,7 +157,8 @@ static int cmd_show_ref__exclude_existing(const struct exclude_existing_options
 	return 0;
 }
 
-static int cmd_show_ref__verify(const char **refs)
+static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
+				const char **refs)
 {
 	if (!refs || !*refs)
 		die("--verify requires a reference");
@@ -159,9 +168,9 @@ static int cmd_show_ref__verify(const char **refs)
 
 		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
 		    !read_ref(*refs, &oid)) {
-			show_one(*refs, &oid);
+			show_one(show_one_opts, *refs, &oid);
 		}
-		else if (!quiet)
+		else if (!show_one_opts->quiet)
 			die("'%s' - not a valid ref", *refs);
 		else
 			return 1;
@@ -171,9 +180,12 @@ static int cmd_show_ref__verify(const char **refs)
 	return 0;
 }
 
-static int cmd_show_ref__patterns(const char **patterns)
+static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,
+				  const char **patterns)
 {
-	struct show_ref_data show_ref_data = {0};
+	struct show_ref_data show_ref_data = {
+		.show_one_opts = show_one_opts,
+	};
 
 	if (patterns && *patterns)
 		show_ref_data.patterns = patterns;
@@ -196,11 +208,16 @@ static int cmd_show_ref__patterns(const char **patterns)
 
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
-	hash_only = 1;
+	struct show_one_options *opts = opt->value;
+	struct option abbrev_opt = *opt;
+
+	opts->hash_only = 1;
 	/* Use full length SHA1 if no argument */
 	if (!arg)
 		return 0;
-	return parse_opt_abbrev_cb(opt, arg, unset);
+
+	abbrev_opt.value = &opts->abbrev;
+	return parse_opt_abbrev_cb(&abbrev_opt, arg, unset);
 }
 
 static int exclude_existing_callback(const struct option *opt, const char *arg,
@@ -216,6 +233,7 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
 	struct exclude_existing_options exclude_existing_opts = {0};
+	struct show_one_options show_one_opts = {0};
 	const struct option show_ref_options[] = {
 		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
 		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
@@ -225,13 +243,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 				N_("show the HEAD reference, even if it would be filtered out")),
 		OPT_BOOL(0, "head", &show_head,
 		  N_("show the HEAD reference, even if it would be filtered out")),
-		OPT_BOOL('d', "dereference", &deref_tags,
+		OPT_BOOL('d', "dereference", &show_one_opts.deref_tags,
 			    N_("dereference tags into object IDs")),
-		OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+		OPT_CALLBACK_F('s', "hash", &show_one_opts, N_("n"),
 			       N_("only show SHA1 hash using <n> digits"),
 			       PARSE_OPT_OPTARG, &hash_callback),
-		OPT__ABBREV(&abbrev),
-		OPT__QUIET(&quiet,
+		OPT__ABBREV(&show_one_opts.abbrev),
+		OPT__QUIET(&show_one_opts.quiet,
 			   N_("do not print results to stdout (useful with --verify)")),
 		OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_opts,
 			       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
@@ -247,7 +265,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
-		return cmd_show_ref__verify(argv);
+		return cmd_show_ref__verify(&show_one_opts, argv);
 	else
-		return cmd_show_ref__patterns(argv);
+		return cmd_show_ref__patterns(&show_one_opts, argv);
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 08/12] builtin/show-ref: refactor options for patterns subcommand
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (6 preceding siblings ...)
  2023-10-26  9:56   ` [PATCH v2 07/12] builtin/show-ref: stop using global vars for `show_one()` Patrick Steinhardt
@ 2023-10-26  9:56   ` Patrick Steinhardt
  2023-10-26  9:56   ` [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
                     ` (4 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 3956 bytes --]

The patterns subcommand is the last command that still uses global
variables to track its options. Convert it to use a structure instead
with the same motivation as preceding commits.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 35 ++++++++++++++++++++++-------------
 1 file changed, 22 insertions(+), 13 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 3e6ad6b6a84..87bc45d2d13 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,8 +18,6 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int show_head, tags_only, heads_only, verify;
-
 struct show_one_options {
 	int quiet;
 	int hash_only;
@@ -59,6 +57,7 @@ struct show_ref_data {
 	const struct show_one_options *show_one_opts;
 	const char **patterns;
 	int found_match;
+	int show_head;
 };
 
 static int show_ref(const char *refname, const struct object_id *oid,
@@ -66,7 +65,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 {
 	struct show_ref_data *data = cbdata;
 
-	if (show_head && !strcmp(refname, "HEAD"))
+	if (data->show_head && !strcmp(refname, "HEAD"))
 		goto match;
 
 	if (data->patterns) {
@@ -180,22 +179,30 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
 	return 0;
 }
 
-static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,
+struct patterns_options {
+	int show_head;
+	int heads_only;
+	int tags_only;
+};
+
+static int cmd_show_ref__patterns(const struct patterns_options *opts,
+				  const struct show_one_options *show_one_opts,
 				  const char **patterns)
 {
 	struct show_ref_data show_ref_data = {
 		.show_one_opts = show_one_opts,
+		.show_head = opts->show_head,
 	};
 
 	if (patterns && *patterns)
 		show_ref_data.patterns = patterns;
 
-	if (show_head)
+	if (opts->show_head)
 		head_ref(show_ref, &show_ref_data);
-	if (heads_only || tags_only) {
-		if (heads_only)
+	if (opts->heads_only || opts->tags_only) {
+		if (opts->heads_only)
 			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
-		if (tags_only)
+		if (opts->tags_only)
 			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
@@ -233,15 +240,17 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
 	struct exclude_existing_options exclude_existing_opts = {0};
+	struct patterns_options patterns_opts = {0};
 	struct show_one_options show_one_opts = {0};
+	int verify = 0;
 	const struct option show_ref_options[] = {
-		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
-		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
+		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
 		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
 			    "requires exact ref path")),
-		OPT_HIDDEN_BOOL('h', NULL, &show_head,
+		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
 				N_("show the HEAD reference, even if it would be filtered out")),
-		OPT_BOOL(0, "head", &show_head,
+		OPT_BOOL(0, "head", &patterns_opts.show_head,
 		  N_("show the HEAD reference, even if it would be filtered out")),
 		OPT_BOOL('d', "dereference", &show_one_opts.deref_tags,
 			    N_("dereference tags into object IDs")),
@@ -267,5 +276,5 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	else if (verify)
 		return cmd_show_ref__verify(&show_one_opts, argv);
 	else
-		return cmd_show_ref__patterns(&show_one_opts, argv);
+		return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (7 preceding siblings ...)
  2023-10-26  9:56   ` [PATCH v2 08/12] builtin/show-ref: refactor options for patterns subcommand Patrick Steinhardt
@ 2023-10-26  9:56   ` Patrick Steinhardt
  2023-10-30 19:31     ` Taylor Blau
  2023-10-26  9:57   ` [PATCH v2 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
                     ` (3 subsequent siblings)
  12 siblings, 1 reply; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:56 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 1823 bytes --]

The git-show-ref(1) command has three different modes, of which one is
implicit and the other two can be chosen explicitly by passing a flag.
But while these modes are standalone and cause us to execute completely
separate code paths, we gladly accept the case where a user asks for
both `--exclude-existing` and `--verify` at the same time even though it
is not obvious what will happen. Spoiler: we ignore `--verify` and
execute the `--exclude-existing` mode.

Let's explicitly detect this invalid usage and die in case both modes
were requested.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c  | 4 ++++
 t/t1403-show-ref.sh | 5 +++++
 2 files changed, 9 insertions(+)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 87bc45d2d13..1768aef77b3 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -271,6 +271,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
+	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
+		die(_("only one of '%s' or '%s' can be given"),
+		    "--exclude-existing", "--verify");
+
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 9252a581abf..4a90a88e05d 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -196,4 +196,9 @@ test_expect_success 'show-ref --verify with dangling ref' '
 	)
 '
 
+test_expect_success 'show-ref sub-modes are mutually exclusive' '
+	test_must_fail git show-ref --verify --exclude-existing 2>err &&
+	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
+'
+
 test_done
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 10/12] builtin/show-ref: explicitly spell out different modes in synopsis
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (8 preceding siblings ...)
  2023-10-26  9:56   ` [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
@ 2023-10-26  9:57   ` Patrick Steinhardt
  2023-10-26  9:57   ` [PATCH v2 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
                     ` (2 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:57 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 2536 bytes --]

The synopsis treats the `--verify` and the implicit mode the same. They
are slightly different though:

    - They accept different sets of flags.

    - The implicit mode accepts patterns while the `--verify` mode
      accepts references.

Split up the synopsis for these two modes such that we can disambiguate
those differences.

While at it, drop "--quiet" from the pattern mode's synopsis. It does
not make a lot of sense to list patterns, but squelch the listing output
itself. The description for "--quiet" is adapted accordingly.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-show-ref.txt | 9 ++++++---
 builtin/show-ref.c             | 5 ++++-
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index 2fe274b8faa..22f5ebc6a92 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -8,9 +8,12 @@ git-show-ref - List references in a local repository
 SYNOPSIS
 --------
 [verse]
-'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]
+'git show-ref' [--head] [-d | --dereference]
 	     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
 	     [--heads] [--] [<pattern>...]
+'git show-ref' --verify [-q | --quiet] [-d | --dereference]
+	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
+	     [--] [<ref>...]
 'git show-ref' --exclude-existing[=<pattern>]
 
 DESCRIPTION
@@ -70,8 +73,8 @@ OPTIONS
 -q::
 --quiet::
 
-	Do not print any results to stdout. When combined with `--verify`, this
-	can be used to silently check if a reference exists.
+	Do not print any results to stdout. Can be used with `--verify` to
+	silently check if a reference exists.
 
 --exclude-existing[=<pattern>]::
 
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 1768aef77b3..d4561d7ce1f 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -11,9 +11,12 @@
 #include "parse-options.h"
 
 static const char * const show_ref_usage[] = {
-	N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference]\n"
+	N_("git show-ref [--head] [-d | --dereference]\n"
 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n"
 	   "             [--heads] [--] [<pattern>...]"),
+	N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n"
+	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
+	   "             [--] [<ref>...]"),
 	N_("git show-ref --exclude-existing[=<pattern>]"),
 	NULL
 };
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 11/12] builtin/show-ref: add new mode to check for reference existence
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (9 preceding siblings ...)
  2023-10-26  9:57   ` [PATCH v2 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
@ 2023-10-26  9:57   ` Patrick Steinhardt
  2023-10-26  9:57   ` [PATCH v2 12/12] t: use git-show-ref(1) to check for ref existence Patrick Steinhardt
  2023-10-30 19:32   ` [PATCH v2 00/12] show-ref: introduce mode " Taylor Blau
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:57 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 9759 bytes --]

While we have multiple ways to show the value of a given reference, we
do not have any way to check whether a reference exists at all. While
commands like git-rev-parse(1) or git-show-ref(1) can be used to check
for reference existence in case the reference resolves to something
sane, neither of them can be used to check for existence in some other
scenarios where the reference does not resolve cleanly:

    - References which have an invalid name cannot be resolved.

    - References to nonexistent objects cannot be resolved.

    - Dangling symrefs can be resolved via git-symbolic-ref(1), but this
      requires the caller to special case existence checks depending on
      whether or not a reference is symbolic or direct.

Furthermore, git-rev-list(1) and other commands do not let the caller
distinguish easily between an actually missing reference and a generic
error.

Taken together, this seems like sufficient motivation to introduce a
separate plumbing command to explicitly check for the existence of a
reference without trying to resolve its contents.

This new command comes in the form of `git show-ref --exists`. This
new mode will exit successfully when the reference exists, with a
specific exit code of 2 when it does not exist, or with 1 when there
has been a generic error.

Note that the only way to properly implement this command is by using
the internal `refs_read_raw_ref()` function. While the public function
`refs_resolve_ref_unsafe()` can be made to behave in the same way by
passing various flags, it does not provide any way to obtain the errno
with which the reference backend failed when reading the reference. As
such, it becomes impossible for us to distinguish generic errors from
the explicit case where the reference wasn't found.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-show-ref.txt | 11 ++++++
 builtin/show-ref.c             | 49 ++++++++++++++++++++++---
 t/t1403-show-ref.sh            | 67 +++++++++++++++++++++++++++++++++-
 3 files changed, 121 insertions(+), 6 deletions(-)

diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index 22f5ebc6a92..8fecc9e80f6 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -15,6 +15,7 @@ SYNOPSIS
 	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
 	     [--] [<ref>...]
 'git show-ref' --exclude-existing[=<pattern>]
+'git show-ref' --exists <ref>
 
 DESCRIPTION
 -----------
@@ -30,6 +31,10 @@ The `--exclude-existing` form is a filter that does the inverse. It reads
 refs from stdin, one ref per line, and shows those that don't exist in
 the local repository.
 
+The `--exists` form can be used to check for the existence of a single
+references. This form does not verify whether the reference resolves to an
+actual object.
+
 Use of this utility is encouraged in favor of directly accessing files under
 the `.git` directory.
 
@@ -65,6 +70,12 @@ OPTIONS
 	Aside from returning an error code of 1, it will also print an error
 	message if `--quiet` was not specified.
 
+--exists::
+
+	Check whether the given reference exists. Returns an exit code of 0 if
+	it does, 2 if it is missing, and 1 in case looking up the reference
+	failed with an error other than the reference being missing.
+
 --abbrev[=<n>]::
 
 	Abbreviate the object name.  When using `--hash`, you do
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index d4561d7ce1f..2cbe4e3f721 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -2,7 +2,7 @@
 #include "config.h"
 #include "gettext.h"
 #include "hex.h"
-#include "refs.h"
+#include "refs/refs-internal.h"
 #include "object-name.h"
 #include "object-store-ll.h"
 #include "object.h"
@@ -18,6 +18,7 @@ static const char * const show_ref_usage[] = {
 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
 	   "             [--] [<ref>...]"),
 	N_("git show-ref --exclude-existing[=<pattern>]"),
+	N_("git show-ref --exists <ref>"),
 	NULL
 };
 
@@ -216,6 +217,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
 	return 0;
 }
 
+static int cmd_show_ref__exists(const char **refs)
+{
+	struct strbuf unused_referent = STRBUF_INIT;
+	struct object_id unused_oid;
+	unsigned int unused_type;
+	int failure_errno = 0;
+	const char *ref;
+	int ret = 0;
+
+	if (!refs || !*refs)
+		die("--exists requires a reference");
+	ref = *refs++;
+	if (*refs)
+		die("--exists requires exactly one reference");
+
+	if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
+			      &unused_oid, &unused_referent, &unused_type,
+			      &failure_errno)) {
+		if (failure_errno == ENOENT) {
+			error(_("reference does not exist"));
+			ret = 2;
+		} else {
+			errno = failure_errno;
+			error_errno(_("failed to look up reference"));
+			ret = 1;
+		}
+
+		goto out;
+	}
+
+out:
+	strbuf_release(&unused_referent);
+	return ret;
+}
+
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
 	struct show_one_options *opts = opt->value;
@@ -245,10 +281,11 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	struct exclude_existing_options exclude_existing_opts = {0};
 	struct patterns_options patterns_opts = {0};
 	struct show_one_options show_one_opts = {0};
-	int verify = 0;
+	int verify = 0, exists = 0;
 	const struct option show_ref_options[] = {
 		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
 		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "exists", &exists, N_("check for reference existence without resolving")),
 		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
 			    "requires exact ref path")),
 		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
@@ -274,14 +311,16 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
-	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
-		die(_("only one of '%s' or '%s' can be given"),
-		    "--exclude-existing", "--verify");
+	if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)
+		die(_("only one of '%s', '%s' or '%s' can be given"),
+		    "--exclude-existing", "--verify", "--exists");
 
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
 		return cmd_show_ref__verify(&show_one_opts, argv);
+	else if (exists)
+		return cmd_show_ref__exists(argv);
 	else
 		return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);
 }
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 4a90a88e05d..b50ae6fcf11 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -197,8 +197,73 @@ test_expect_success 'show-ref --verify with dangling ref' '
 '
 
 test_expect_success 'show-ref sub-modes are mutually exclusive' '
+	cat >expect <<-EOF &&
+	fatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given
+	EOF
+
 	test_must_fail git show-ref --verify --exclude-existing 2>err &&
-	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
+	test_cmp expect err &&
+
+	test_must_fail git show-ref --verify --exists 2>err &&
+	test_cmp expect err &&
+
+	test_must_fail git show-ref --exclude-existing --exists 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success '--exists with existing reference' '
+	git show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+'
+
+test_expect_success '--exists with missing reference' '
+	test_expect_code 2 git show-ref --exists refs/heads/does-not-exist
+'
+
+test_expect_success '--exists does not use DWIM' '
+	test_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&
+	grep "reference does not exist" err
+'
+
+test_expect_success '--exists with HEAD' '
+	git show-ref --exists HEAD
+'
+
+test_expect_success '--exists with bad reference name' '
+	test_when_finished "git update-ref -d refs/heads/bad...name" &&
+	new_oid=$(git rev-parse HEAD) &&
+	test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
+	git show-ref --exists refs/heads/bad...name
+'
+
+test_expect_success '--exists with arbitrary symref' '
+	test_when_finished "git symbolic-ref -d refs/symref" &&
+	git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
+	git show-ref --exists refs/symref
+'
+
+test_expect_success '--exists with dangling symref' '
+	test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
+	git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+	git show-ref --exists refs/heads/dangling
+'
+
+test_expect_success '--exists with nonexistent object ID' '
+	test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+	git show-ref --exists refs/heads/missing-oid
+'
+
+test_expect_success '--exists with non-commit object' '
+	tree_oid=$(git rev-parse HEAD^{tree}) &&
+	test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+	git show-ref --exists refs/heads/tree
+'
+
+test_expect_success '--exists with directory fails with generic error' '
+	cat >expect <<-EOF &&
+	error: failed to look up reference: Is a directory
+	EOF
+	test_expect_code 1 git show-ref --exists refs/heads 2>err &&
+	test_cmp expect err
 '
 
 test_done
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v2 12/12] t: use git-show-ref(1) to check for ref existence
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (10 preceding siblings ...)
  2023-10-26  9:57   ` [PATCH v2 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
@ 2023-10-26  9:57   ` Patrick Steinhardt
  2023-10-30 19:32   ` [PATCH v2 00/12] show-ref: introduce mode " Taylor Blau
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-26  9:57 UTC (permalink / raw
  To: git; +Cc: Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 14354 bytes --]

Convert tests that use `test_path_is_file` and `test_path_is_missing` to
instead use a set of helpers `test_ref_exists` and `test_ref_missing`.
These helpers are implemented via the newly introduced `git show-ref
--exists` command. Thus, we can avoid intimate knowledge of how the ref
backend stores references on disk.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 t/t1430-bad-ref-name.sh | 27 +++++++++++++-------
 t/t3200-branch.sh       | 33 ++++++++++++++-----------
 t/t5521-pull-options.sh |  4 +--
 t/t5605-clone-local.sh  |  2 +-
 t/test-lib-functions.sh | 55 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 94 insertions(+), 27 deletions(-)

diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh
index ff1c967d550..7b7d6953c62 100755
--- a/t/t1430-bad-ref-name.sh
+++ b/t/t1430-bad-ref-name.sh
@@ -205,8 +205,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref to broken name'
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" &&
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git update-ref --no-deref -d refs/heads/badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -216,8 +217,9 @@ test_expect_success 'branch -d can delete symref to broken name' '
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" &&
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git branch -d badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
 	test_must_be_empty error
 '
@@ -225,8 +227,9 @@ test_expect_success 'branch -d can delete symref to broken name' '
 test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' '
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git update-ref --no-deref -d refs/heads/badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -234,8 +237,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref to brok
 test_expect_success 'branch -d can delete dangling symref to broken name' '
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git branch -d badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
 	test_must_be_empty error
 '
@@ -245,8 +249,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" &&
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/broken...ref &&
 	git update-ref -d refs/heads/badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...ref &&
+	test_ref_missing refs/heads/broken...ref &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -254,8 +259,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '
 test_expect_success 'update-ref --no-deref -d can delete symref with broken name' '
 	printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -263,8 +269,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref with broken name
 test_expect_success 'branch -d can delete symref with broken name' '
 	printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git branch -d broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_i18ngrep "Deleted branch broken...symref (was refs/heads/main)" output &&
 	test_must_be_empty error
 '
@@ -272,8 +279,9 @@ test_expect_success 'branch -d can delete symref with broken name' '
 test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' '
 	printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -281,8 +289,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref with br
 test_expect_success 'branch -d can delete dangling symref with broken name' '
 	printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git branch -d broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_i18ngrep "Deleted branch broken...symref (was refs/heads/idonotexist)" output &&
 	test_must_be_empty error
 '
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 080e4f24a6e..bde4f1485b7 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -25,7 +25,7 @@ test_expect_success 'prepare a trivial repository' '
 
 test_expect_success 'git branch --help should not have created a bogus branch' '
 	test_might_fail git branch --man --help </dev/null >/dev/null 2>&1 &&
-	test_path_is_missing .git/refs/heads/--help
+	test_ref_missing refs/heads/--help
 '
 
 test_expect_success 'branch -h in broken repository' '
@@ -40,7 +40,8 @@ test_expect_success 'branch -h in broken repository' '
 '
 
 test_expect_success 'git branch abc should create a branch' '
-	git branch abc && test_path_is_file .git/refs/heads/abc
+	git branch abc &&
+	test_ref_exists refs/heads/abc
 '
 
 test_expect_success 'git branch abc should fail when abc exists' '
@@ -61,11 +62,13 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '
 '
 
 test_expect_success 'git branch a/b/c should create a branch' '
-	git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c
+	git branch a/b/c &&
+	test_ref_exists refs/heads/a/b/c
 '
 
 test_expect_success 'git branch mb main... should create a branch' '
-	git branch mb main... && test_path_is_file .git/refs/heads/mb
+	git branch mb main... &&
+	test_ref_exists refs/heads/mb
 '
 
 test_expect_success 'git branch HEAD should fail' '
@@ -78,14 +81,14 @@ EOF
 test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' '
 	GIT_COMMITTER_DATE="2005-05-26 23:30" \
 	git -c core.logallrefupdates=false branch --create-reflog d/e/f &&
-	test_path_is_file .git/refs/heads/d/e/f &&
+	test_ref_exists refs/heads/d/e/f &&
 	test_path_is_file .git/logs/refs/heads/d/e/f &&
 	test_cmp expect .git/logs/refs/heads/d/e/f
 '
 
 test_expect_success 'git branch -d d/e/f should delete a branch and a log' '
 	git branch -d d/e/f &&
-	test_path_is_missing .git/refs/heads/d/e/f &&
+	test_ref_missing refs/heads/d/e/f &&
 	test_must_fail git reflog exists refs/heads/d/e/f
 '
 
@@ -213,7 +216,7 @@ test_expect_success 'git branch -M should leave orphaned HEAD alone' '
 		test_commit initial &&
 		git checkout --orphan lonely &&
 		grep lonely .git/HEAD &&
-		test_path_is_missing .git/refs/head/lonely &&
+		test_ref_missing refs/head/lonely &&
 		git branch -M main mistress &&
 		grep lonely .git/HEAD
 	)
@@ -799,8 +802,8 @@ test_expect_success 'deleting a symref' '
 	git symbolic-ref refs/heads/symref refs/heads/target &&
 	echo "Deleted branch symref (was refs/heads/target)." >expect &&
 	git branch -d symref >actual &&
-	test_path_is_file .git/refs/heads/target &&
-	test_path_is_missing .git/refs/heads/symref &&
+	test_ref_exists refs/heads/target &&
+	test_ref_missing refs/heads/symref &&
 	test_cmp expect actual
 '
 
@@ -809,16 +812,16 @@ test_expect_success 'deleting a dangling symref' '
 	test_path_is_file .git/refs/heads/dangling-symref &&
 	echo "Deleted branch dangling-symref (was nowhere)." >expect &&
 	git branch -d dangling-symref >actual &&
-	test_path_is_missing .git/refs/heads/dangling-symref &&
+	test_ref_missing refs/heads/dangling-symref &&
 	test_cmp expect actual
 '
 
 test_expect_success 'deleting a self-referential symref' '
 	git symbolic-ref refs/heads/self-reference refs/heads/self-reference &&
-	test_path_is_file .git/refs/heads/self-reference &&
+	test_ref_exists refs/heads/self-reference &&
 	echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect &&
 	git branch -d self-reference >actual &&
-	test_path_is_missing .git/refs/heads/self-reference &&
+	test_ref_missing refs/heads/self-reference &&
 	test_cmp expect actual
 '
 
@@ -826,8 +829,8 @@ test_expect_success 'renaming a symref is not allowed' '
 	git symbolic-ref refs/heads/topic refs/heads/main &&
 	test_must_fail git branch -m topic new-topic &&
 	git symbolic-ref refs/heads/topic &&
-	test_path_is_file .git/refs/heads/main &&
-	test_path_is_missing .git/refs/heads/new-topic
+	test_ref_exists refs/heads/main &&
+	test_ref_missing refs/heads/new-topic
 '
 
 test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '
@@ -1142,7 +1145,7 @@ EOF
 test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '
 	GIT_COMMITTER_DATE="2005-05-26 23:30" \
 	git checkout -b g/h/i -l main &&
-	test_path_is_file .git/refs/heads/g/h/i &&
+	test_ref_exists refs/heads/g/h/i &&
 	test_path_is_file .git/logs/refs/heads/g/h/i &&
 	test_cmp expect .git/logs/refs/heads/g/h/i
 '
diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh
index 079b2f2536e..3681859f983 100755
--- a/t/t5521-pull-options.sh
+++ b/t/t5521-pull-options.sh
@@ -143,7 +143,7 @@ test_expect_success 'git pull --dry-run' '
 		cd clonedry &&
 		git pull --dry-run ../parent &&
 		test_path_is_missing .git/FETCH_HEAD &&
-		test_path_is_missing .git/refs/heads/main &&
+		test_ref_missing refs/heads/main &&
 		test_path_is_missing .git/index &&
 		test_path_is_missing file
 	)
@@ -157,7 +157,7 @@ test_expect_success 'git pull --all --dry-run' '
 		git remote add origin ../parent &&
 		git pull --all --dry-run &&
 		test_path_is_missing .git/FETCH_HEAD &&
-		test_path_is_missing .git/refs/remotes/origin/main &&
+		test_ref_missing refs/remotes/origin/main &&
 		test_path_is_missing .git/index &&
 		test_path_is_missing file
 	)
diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh
index 1d7b1abda1a..946c5751885 100755
--- a/t/t5605-clone-local.sh
+++ b/t/t5605-clone-local.sh
@@ -69,7 +69,7 @@ test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
 	git clone a d &&
 	(cd d &&
 	git fetch &&
-	test ! -e .git/refs/remotes/origin/HEAD)
+	test_ref_missing refs/remotes/origin/HEAD)
 '
 
 test_expect_success 'bundle clone without .bundle suffix' '
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 2f8868caa17..56b33536ed1 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -251,6 +251,61 @@ debug () {
 	done
 }
 
+# Usage: test_ref_exists [options] <ref>
+#
+#   -C <dir>:
+#      Run all git commands in directory <dir>
+#
+# This helper function checks whether a reference exists. Symrefs or object IDs
+# will not be resolved. Can be used to check references with bad names.
+test_ref_exists () {
+	local indir=
+
+	while test $# != 0
+	do
+		case "$1" in
+		-C)
+			indir="$2"
+			shift
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done &&
+
+	indir=${indir:+"$indir"/} &&
+
+	if test "$#" != 1
+	then
+		BUG "expected exactly one reference"
+	fi &&
+
+	git ${indir:+ -C "$indir"} show-ref --exists "$1"
+}
+
+# Behaves the same as test_ref_exists, except that it checks for the absence of
+# a reference. This is preferable to `! test_ref_exists` as this function is
+# able to distinguish actually-missing references from other, generic errors.
+test_ref_missing () {
+	test_ref_exists "$@"
+	case "$?" in
+	2)
+		# This is the good case.
+		return 0
+		;;
+	0)
+		echo >&4 "test_ref_missing: reference exists"
+		return 1
+		;;
+	*)
+		echo >&4 "test_ref_missing: generic error"
+		return 1
+		;;
+	esac
+}
+
 # Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]
 #   -C <dir>:
 #	Run all git commands in directory <dir>
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence
  2023-10-26  9:48       ` Patrick Steinhardt
@ 2023-10-27 13:06         ` Phillip Wood
  0 siblings, 0 replies; 66+ messages in thread
From: Phillip Wood @ 2023-10-27 13:06 UTC (permalink / raw
  To: Patrick Steinhardt, phillip.wood
  Cc: Han-Wen Nienhuys, Junio C Hamano, git, Eric Sunshine

Hi Patrick

On 26/10/2023 10:48, Patrick Steinhardt wrote:
> On Wed, Oct 25, 2023 at 03:44:33PM +0100, Phillip Wood wrote:
>> On 25/10/2023 15:26, Han-Wen Nienhuys wrote:
>>> On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:
>>>>
>>>> Patrick Steinhardt <ps@pks.im> writes:
>>>>
>>>>> this patch series introduces a new `--exists` mode to git-show-ref(1) to
>>>>> explicitly check for the existence of a reference, only.
>>>>
>>>> I agree that show-ref would be the best place for this feature (not
>>>> rev-parse, which is already a kitchen sink).  After all, the command
>>>> was designed for validating refs in 358ddb62 (Add "git show-ref"
>>>> builtin command, 2006-09-15).
>>>>
>>>> Thanks.  Hopefully I can take a look before I go offline.
>>>
>>> The series description doesn't say why users would care about this.
>>>
>>> If this is just to ease testing, I suggest adding functionality to a
>>> suitable test helper. Anything you add to git-show-ref is a publicly
>>> visible API that needs documentation and comes with a stability
>>> guarantee that is more expensive to maintain than test helper
>>> functionality.
>>
>> Does the new functionality provide a way for scripts to see if a branch is
>> unborn (i.e. has not commits yet)? I don't think we have a way to
>> distinguish between a ref that points to a missing object and an unborn
>> branch at the moment.
> 
> You could do it with two commands:
> 
> ```
> target=$(git symbolic-ref HEAD)
> git show-ref --exists "$target"
> case "$?" in
> 2)
>      echo "unborn branch";;
> 0)
>      echo "branch exists";;
> *)
>      echo "could be anything, dunno";;
> esac
> ```
> 
> While you could use git-rev-parse(1) instead of git-show-ref(1), you
> wouldn't be able to easily distinguish the case of a missing target
> reference and any other kind of error.

Thanks, it is helpful to have an example use outside of the test suite.

Best Wishes

Phillip


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

* Re: [PATCH v2 03/12] builtin/show-ref: fix leaking string buffer
  2023-10-26  9:56   ` [PATCH v2 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
@ 2023-10-30 18:10     ` Taylor Blau
  0 siblings, 0 replies; 66+ messages in thread
From: Taylor Blau @ 2023-10-30 18:10 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

On Thu, Oct 26, 2023 at 11:56:29AM +0200, Patrick Steinhardt wrote:
> Fix a leaking string buffer in `git show-ref --exclude-existing`. While
> the buffer is technically not leaking because its variable is declared
> as static, there is no inherent reason why it should be.

Well spotted and fixed. I ran the test suite in GIT_TEST_PASSING_SANITIZE_LEAK's
"check" mode and didn't find anything that was made leak-free by this
patch not already marked as such. So this (and the rest of the series up
to this point) LGTM.

Thanks,
Taylor


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

* Re: [PATCH v2 04/12] builtin/show-ref: fix dead code when passing patterns
  2023-10-26  9:56   ` [PATCH v2 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
@ 2023-10-30 18:24     ` Taylor Blau
  0 siblings, 0 replies; 66+ messages in thread
From: Taylor Blau @ 2023-10-30 18:24 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

On Thu, Oct 26, 2023 at 11:56:33AM +0200, Patrick Steinhardt wrote:
> When passing patterns to `git show-ref` we have some code that will
> cause us to die if `verify && !quiet` is true. But because `verify`
> indicates a different subcommand of git-show-ref(1) that causes us to
> execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the
> condition cannot ever be true.
>
> Let's remove this dead code.

Makes sense. Let's read on...

Thanks,
Taylor


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

* Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options
  2023-10-26  9:56   ` [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
@ 2023-10-30 18:37     ` Taylor Blau
  2023-10-31  8:10       ` Patrick Steinhardt
  2023-10-30 18:55     ` Taylor Blau
  1 sibling, 1 reply; 66+ messages in thread
From: Taylor Blau @ 2023-10-30 18:37 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:
> It's not immediately obvious options which options are applicable to
> what subcommand in git-show-ref(1) because all options exist as global
> state. This can easily cause confusion for the reader.
>
> Refactor options for the `--exclude-existing` subcommand to be contained
> in a separate structure. This structure is stored on the stack and
> passed down as required. Consequently, it clearly delimits the scope of
> those options and requires the reader to worry less about global state.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>

All makes sense, but...

> @@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {
>  };
>
>  static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
> -	   quiet, hash_only, abbrev, exclude_arg;
> -static const char *exclude_existing_arg;
> +	   quiet, hash_only, abbrev;
>
>  static void show_one(const char *refname, const struct object_id *oid)
>  {
> @@ -95,6 +94,11 @@ static int add_existing(const char *refname,
>  	return 0;
>  }
>
> +struct exclude_existing_options {
> +	int enabled;

...do we need an .enabled here? I think checking whether or not .pattern
is NULL is sufficient, but perhaps there is another use of .enabled
later on in the series...

Thanks,
Taylor


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

* Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options
  2023-10-26  9:56   ` [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
  2023-10-30 18:37     ` Taylor Blau
@ 2023-10-30 18:55     ` Taylor Blau
  2023-10-31  8:10       ` Patrick Steinhardt
  1 sibling, 1 reply; 66+ messages in thread
From: Taylor Blau @ 2023-10-30 18:55 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:
> @@ -95,6 +94,11 @@ static int add_existing(const char *refname,
>  	return 0;
>  }
>
> +struct exclude_existing_options {
> +	int enabled;
> +	const char *pattern;
> +};
> +

Thinking on my earlier suggestion more, I wondered if using the
OPT_SUBCOMMAND() function might make things easier to organize and
eliminate the need for things like .enabled or having to define structs
for each of the sub-commands.

But I don't think that this is (easily) possible to do, since
`--exclude-existing` is behind a command-line option, not a separate
mode (e.g. "commit-graph verify", not "commit-graph --verify"). So I
think you *could* make it work with some combination of OPT_SUBCOMMAND
and callbacks to set the function pointer yourself when given the
`--exclude-existing` option. But I think that's sufficiently gross as to
not be worth it.

Thanks,
Taylor


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

* Re: [PATCH v2 06/12] builtin/show-ref: stop using global variable to count matches
  2023-10-26  9:56   ` [PATCH v2 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
@ 2023-10-30 19:14     ` Taylor Blau
  0 siblings, 0 replies; 66+ messages in thread
From: Taylor Blau @ 2023-10-30 19:14 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

On Thu, Oct 26, 2023 at 11:56:42AM +0200, Patrick Steinhardt wrote:
> When passing patterns to git-show-ref(1) we're checking whether any
> reference matches -- if none does, we indicate this condition via an
> unsuccessful exit code.

s/does/do, but not a big enough deal to reroll IMHO.

> We're using a global variable to count these matches, which is required
> because the counter is getting incremented in a callback function. But
> now that we have the `struct show_ref_data` in place, we can get rid of
> the global variable and put the counter in there instead.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/show-ref.c | 7 ++++---
>  1 file changed, 4 insertions(+), 3 deletions(-)

Looks all good to me!

Thanks,
Taylor


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

* Re: [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands
  2023-10-26  9:56   ` [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
@ 2023-10-30 19:31     ` Taylor Blau
  2023-10-31  8:10       ` Patrick Steinhardt
  0 siblings, 1 reply; 66+ messages in thread
From: Taylor Blau @ 2023-10-30 19:31 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

On Thu, Oct 26, 2023 at 11:56:57AM +0200, Patrick Steinhardt wrote:
> The git-show-ref(1) command has three different modes, of which one is
> implicit and the other two can be chosen explicitly by passing a flag.
> But while these modes are standalone and cause us to execute completely
> separate code paths, we gladly accept the case where a user asks for
> both `--exclude-existing` and `--verify` at the same time even though it
> is not obvious what will happen. Spoiler: we ignore `--verify` and
> execute the `--exclude-existing` mode.
>
> Let's explicitly detect this invalid usage and die in case both modes
> were requested.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/show-ref.c  | 4 ++++
>  t/t1403-show-ref.sh | 5 +++++
>  2 files changed, 9 insertions(+)
>
> diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> index 87bc45d2d13..1768aef77b3 100644
> --- a/builtin/show-ref.c
> +++ b/builtin/show-ref.c
> @@ -271,6 +271,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
>  	argc = parse_options(argc, argv, prefix, show_ref_options,
>  			     show_ref_usage, 0);
>
> +	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
> +		die(_("only one of '%s' or '%s' can be given"),
> +		    "--exclude-existing", "--verify");
> +

This is technically correct, but I was surprised to see it written this
way instead of

    if (exclude_existing_opts.enabled && verify)
        die(...);

I don't think it's a big deal either way, I was just curious why you
chose one over the other.

> +test_expect_success 'show-ref sub-modes are mutually exclusive' '
> +	test_must_fail git show-ref --verify --exclude-existing 2>err &&
> +	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
> +'

grepping is fine here, but since you have the exact error message, it
may be worth switching to test_cmp.

Thanks,
Taylor


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

* Re: [PATCH v2 00/12] show-ref: introduce mode to check for ref existence
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
                     ` (11 preceding siblings ...)
  2023-10-26  9:57   ` [PATCH v2 12/12] t: use git-show-ref(1) to check for ref existence Patrick Steinhardt
@ 2023-10-30 19:32   ` Taylor Blau
  2023-10-31  2:26     ` Junio C Hamano
  12 siblings, 1 reply; 66+ messages in thread
From: Taylor Blau @ 2023-10-30 19:32 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

On Thu, Oct 26, 2023 at 11:56:16AM +0200, Patrick Steinhardt wrote:
> Hi,
>
> this is the second version of my patch series that introduces a new `git
> show-ref --exists` mode to check for reference existence.

All looks quite reasonable to me. I'm happy with this series as-is,
though I left a few minor comments and suggestions throughout.

Thanks,
Taylor


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

* Re: [PATCH v2 00/12] show-ref: introduce mode to check for ref existence
  2023-10-30 19:32   ` [PATCH v2 00/12] show-ref: introduce mode " Taylor Blau
@ 2023-10-31  2:26     ` Junio C Hamano
  0 siblings, 0 replies; 66+ messages in thread
From: Junio C Hamano @ 2023-10-31  2:26 UTC (permalink / raw
  To: Taylor Blau; +Cc: Patrick Steinhardt, git, Eric Sunshine, Han-Wen Nienhuys

Taylor Blau <me@ttaylorr.com> writes:

> On Thu, Oct 26, 2023 at 11:56:16AM +0200, Patrick Steinhardt wrote:
>> Hi,
>>
>> this is the second version of my patch series that introduces a new `git
>> show-ref --exists` mode to check for reference existence.
>
> All looks quite reasonable to me. I'm happy with this series as-is,
> though I left a few minor comments and suggestions throughout.

Thanks.  I'll wait for hearing from Patrick and then decide ;-)



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

* Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options
  2023-10-30 18:37     ` Taylor Blau
@ 2023-10-31  8:10       ` Patrick Steinhardt
  0 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:10 UTC (permalink / raw
  To: Taylor Blau; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 2138 bytes --]

On Mon, Oct 30, 2023 at 02:37:14PM -0400, Taylor Blau wrote:
> On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:
> > It's not immediately obvious options which options are applicable to
> > what subcommand in git-show-ref(1) because all options exist as global
> > state. This can easily cause confusion for the reader.
> >
> > Refactor options for the `--exclude-existing` subcommand to be contained
> > in a separate structure. This structure is stored on the stack and
> > passed down as required. Consequently, it clearly delimits the scope of
> > those options and requires the reader to worry less about global state.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> 
> All makes sense, but...
> 
> > @@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {
> >  };
> >
> >  static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
> > -	   quiet, hash_only, abbrev, exclude_arg;
> > -static const char *exclude_existing_arg;
> > +	   quiet, hash_only, abbrev;
> >
> >  static void show_one(const char *refname, const struct object_id *oid)
> >  {
> > @@ -95,6 +94,11 @@ static int add_existing(const char *refname,
> >  	return 0;
> >  }
> >
> > +struct exclude_existing_options {
> > +	int enabled;
> 
> ...do we need an .enabled here? I think checking whether or not .pattern
> is NULL is sufficient, but perhaps there is another use of .enabled
> later on in the series...

This is the second time that this question comes up, which is likely not
all that surprising. Quoting my first reply:

On Wed, Oct 25, 2023 at 01:50:44PM +0200, Patrick Steinhardt wrote:
> Yeah, we do. It's perfectly valid to pass `--exclude-existing` without
> the optional pattern argument. We still want to use this mode in that
> case, but don't populate the pattern.
> 
> An alternative would be to assign something like a sentinel value in
> here. But I'd think that it's clearer to instead have an explicit
> separate field for this.

Anyway, the fact that this question comes up again indicates that I need
to comment this better.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options
  2023-10-30 18:55     ` Taylor Blau
@ 2023-10-31  8:10       ` Patrick Steinhardt
  0 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:10 UTC (permalink / raw
  To: Taylor Blau; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 2389 bytes --]

On Mon, Oct 30, 2023 at 02:55:21PM -0400, Taylor Blau wrote:
> On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:
> > @@ -95,6 +94,11 @@ static int add_existing(const char *refname,
> >  	return 0;
> >  }
> >
> > +struct exclude_existing_options {
> > +	int enabled;
> > +	const char *pattern;
> > +};
> > +
> 
> Thinking on my earlier suggestion more, I wondered if using the
> OPT_SUBCOMMAND() function might make things easier to organize and
> eliminate the need for things like .enabled or having to define structs
> for each of the sub-commands.
> 
> But I don't think that this is (easily) possible to do, since
> `--exclude-existing` is behind a command-line option, not a separate
> mode (e.g. "commit-graph verify", not "commit-graph --verify"). So I
> think you *could* make it work with some combination of OPT_SUBCOMMAND
> and callbacks to set the function pointer yourself when given the
> `--exclude-existing` option. But I think that's sufficiently gross as to
> not be worth it.

Yeah, agreed. Honestly, while working on this series I had the dream of
just discarding git-show-ref(1) in favor of a new command with proper
subcommands as we tend to use them nowadays:

    - `git references list <patterns>...` replaces `git show-ref
      <pattern>`.

    - `git references filter <pattern>` replaces `git show-ref
      --exclude-existing` and filters references from stdin.

    - `git references exists <ref>` checks whether a reference exists or
      not and replaces `git show-ref --exists`.

This would make for a much more enjoyable UX. It'd also be a more
natural home for potential future additions:

    - `git references show <ref>` allows you to show the contents of the
      reference without resolving it, regardless of whether it's a
      direct or a symbolic reference.

    - `git references count <patterns>...` allows you to count refs
      patching the pattern.

I shied away though because it would be a much more controversial topic
that would potentially result in lots of bikeshedding. Now if everyone
was enthusiastic about this idea I'd still be happy to do it, even
though it derails the topic even further from its original intent to
just fix a bunch of tests. But unless that happens, I'll continue to
stick with the mediocre UI we have in git-show-ref(1).

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands
  2023-10-30 19:31     ` Taylor Blau
@ 2023-10-31  8:10       ` Patrick Steinhardt
  0 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:10 UTC (permalink / raw
  To: Taylor Blau; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 2342 bytes --]

On Mon, Oct 30, 2023 at 03:31:32PM -0400, Taylor Blau wrote:
> On Thu, Oct 26, 2023 at 11:56:57AM +0200, Patrick Steinhardt wrote:
> > The git-show-ref(1) command has three different modes, of which one is
> > implicit and the other two can be chosen explicitly by passing a flag.
> > But while these modes are standalone and cause us to execute completely
> > separate code paths, we gladly accept the case where a user asks for
> > both `--exclude-existing` and `--verify` at the same time even though it
> > is not obvious what will happen. Spoiler: we ignore `--verify` and
> > execute the `--exclude-existing` mode.
> >
> > Let's explicitly detect this invalid usage and die in case both modes
> > were requested.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> >  builtin/show-ref.c  | 4 ++++
> >  t/t1403-show-ref.sh | 5 +++++
> >  2 files changed, 9 insertions(+)
> >
> > diff --git a/builtin/show-ref.c b/builtin/show-ref.c
> > index 87bc45d2d13..1768aef77b3 100644
> > --- a/builtin/show-ref.c
> > +++ b/builtin/show-ref.c
> > @@ -271,6 +271,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
> >  	argc = parse_options(argc, argv, prefix, show_ref_options,
> >  			     show_ref_usage, 0);
> >
> > +	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
> > +		die(_("only one of '%s' or '%s' can be given"),
> > +		    "--exclude-existing", "--verify");
> > +
> 
> This is technically correct, but I was surprised to see it written this
> way instead of
> 
>     if (exclude_existing_opts.enabled && verify)
>         die(...);
> 
> I don't think it's a big deal either way, I was just curious why you
> chose one over the other.

Here it doesn't make a lot of sense yet, agreed. But once we add
`exists` as a third mutually-exclusive option it does because of
combinatorial explosion.

> > +test_expect_success 'show-ref sub-modes are mutually exclusive' '
> > +	test_must_fail git show-ref --verify --exclude-existing 2>err &&
> > +	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
> > +'
> 
> grepping is fine here, but since you have the exact error message, it
> may be worth switching to test_cmp.

Good point. Doubly so because I switch to `test_cmp` in a later patch.
Will change.

Patrick

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 00/12] builtin/show-ref: introduce mode to check for ref existence
  2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
                   ` (13 preceding siblings ...)
  2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
@ 2023-10-31  8:16 ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
                     ` (12 more replies)
  14 siblings, 13 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 5399 bytes --]

Hi,

this is the third version of my patch series that introduces a new `git
show-ref --exists` mode to check for reference existence.

Changes compared to v2:

    - Patch 5: Document why we need `exclude_existing_options.enabled`,
      which isn't exactly obvious.

    - Patch 6: Fix a grammar issue in the commit message.

    - Patch 9: Switch to `test_cmp` instead of grep(1).

Thanks!

Patrick

Patrick Steinhardt (12):
  builtin/show-ref: convert pattern to a local variable
  builtin/show-ref: split up different subcommands
  builtin/show-ref: fix leaking string buffer
  builtin/show-ref: fix dead code when passing patterns
  builtin/show-ref: refactor `--exclude-existing` options
  builtin/show-ref: stop using global variable to count matches
  builtin/show-ref: stop using global vars for `show_one()`
  builtin/show-ref: refactor options for patterns subcommand
  builtin/show-ref: ensure mutual exclusiveness of subcommands
  builtin/show-ref: explicitly spell out different modes in synopsis
  builtin/show-ref: add new mode to check for reference existence
  t: use git-show-ref(1) to check for ref existence

 Documentation/git-show-ref.txt |  20 ++-
 builtin/show-ref.c             | 284 ++++++++++++++++++++++-----------
 t/t1403-show-ref.sh            |  70 ++++++++
 t/t1430-bad-ref-name.sh        |  27 ++--
 t/t3200-branch.sh              |  33 ++--
 t/t5521-pull-options.sh        |   4 +-
 t/t5605-clone-local.sh         |   2 +-
 t/test-lib-functions.sh        |  55 +++++++
 8 files changed, 373 insertions(+), 122 deletions(-)

Range-diff against v2:
 1:  78163accbd2 =  1:  9570ad63924 builtin/show-ref: convert pattern to a local variable
 2:  9a234622d99 =  2:  773c6119750 builtin/show-ref: split up different subcommands
 3:  bb0d656a0b4 =  3:  b6f4c0325bf builtin/show-ref: fix leaking string buffer
 4:  87afcee830c =  4:  4605c6f0ac9 builtin/show-ref: fix dead code when passing patterns
 5:  bed2a8a0769 !  5:  b47440089b6 builtin/show-ref: refactor `--exclude-existing` options
    @@ builtin/show-ref.c: static int add_existing(const char *refname,
      }
      
     +struct exclude_existing_options {
    ++	/*
    ++	 * We need an explicit `enabled` field because it is perfectly valid
    ++	 * for `pattern` to be `NULL` even if `--exclude-existing` was given.
    ++	 */
     +	int enabled;
     +	const char *pattern;
     +};
 6:  d52a5e8ced2 !  6:  6172888e465 builtin/show-ref: stop using global variable to count matches
    @@ Commit message
         builtin/show-ref: stop using global variable to count matches
     
         When passing patterns to git-show-ref(1) we're checking whether any
    -    reference matches -- if none does, we indicate this condition via an
    +    reference matches -- if none do, we indicate this condition via an
         unsuccessful exit code.
     
         We're using a global variable to count these matches, which is required
 7:  63f1dadf4c2 =  7:  bc528db7667 builtin/show-ref: stop using global vars for `show_one()`
 8:  88dfeaa4871 =  8:  e3882c07dfc builtin/show-ref: refactor options for patterns subcommand
 9:  5ba566723e8 !  9:  a095decd778 builtin/show-ref: ensure mutual exclusiveness of subcommands
    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '
      '
      
     +test_expect_success 'show-ref sub-modes are mutually exclusive' '
    ++	cat >expect <<-EOF &&
    ++	fatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given
    ++	EOF
    ++
     +	test_must_fail git show-ref --verify --exclude-existing 2>err &&
    -+	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
    ++	test_cmp expect err
     +'
     +
      test_done
10:  b78ccc5f692 = 10:  087384fd2fd builtin/show-ref: explicitly spell out different modes in synopsis
11:  327942b1162 ! 11:  ca5187bb18a builtin/show-ref: add new mode to check for reference existence
    @@ builtin/show-ref.c: int cmd_show_ref(int argc, const char **argv, const char *pr
     
      ## t/t1403-show-ref.sh ##
     @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '
    - '
      
      test_expect_success 'show-ref sub-modes are mutually exclusive' '
    -+	cat >expect <<-EOF &&
    + 	cat >expect <<-EOF &&
    +-	fatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given
     +	fatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given
    -+	EOF
    -+
    + 	EOF
    + 
      	test_must_fail git show-ref --verify --exclude-existing 2>err &&
    --	grep "only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given" err
     +	test_cmp expect err &&
     +
     +	test_must_fail git show-ref --verify --exists 2>err &&
    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '
     +	error: failed to look up reference: Is a directory
     +	EOF
     +	test_expect_code 1 git show-ref --exists refs/heads 2>err &&
    -+	test_cmp expect err
    + 	test_cmp expect err
      '
      
    - test_done
12:  226731c5f18 = 12:  ea9919fe899 t: use git-show-ref(1) to check for ref existence

base-commit: a9ecda2788e229afc9b611acaa26d0d9d4da53ed
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 01/12] builtin/show-ref: convert pattern to a local variable
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
                     ` (11 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 3886 bytes --]

The `pattern` variable is a global variable that tracks either the
reference names (not patterns!) for the `--verify` mode or the patterns
for the non-verify mode. This is a bit confusing due to the slightly
different meanings.

Convert the variable to be local. While this does not yet fix the double
meaning of the variable, this change allows us to address it in a
subsequent patch more easily by explicitly splitting up the different
subcommands of git-show-ref(1).

Note that we introduce a `struct show_ref_data` to pass the patterns to
`show_ref()`. While this is overengineered now, we will extend this
structure in a subsequent patch.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 46 ++++++++++++++++++++++++++++------------------
 1 file changed, 28 insertions(+), 18 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 5110814f796..7efab14b96c 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -20,7 +20,6 @@ static const char * const show_ref_usage[] = {
 
 static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
 	   quiet, hash_only, abbrev, exclude_arg;
-static const char **pattern;
 static const char *exclude_existing_arg;
 
 static void show_one(const char *refname, const struct object_id *oid)
@@ -50,15 +49,21 @@ static void show_one(const char *refname, const struct object_id *oid)
 	}
 }
 
+struct show_ref_data {
+	const char **patterns;
+};
+
 static int show_ref(const char *refname, const struct object_id *oid,
-		    int flag UNUSED, void *cbdata UNUSED)
+		    int flag UNUSED, void *cbdata)
 {
+	struct show_ref_data *data = cbdata;
+
 	if (show_head && !strcmp(refname, "HEAD"))
 		goto match;
 
-	if (pattern) {
+	if (data->patterns) {
 		int reflen = strlen(refname);
-		const char **p = pattern, *m;
+		const char **p = data->patterns, *m;
 		while ((m = *p++) != NULL) {
 			int len = strlen(m);
 			if (len > reflen)
@@ -180,6 +185,9 @@ static const struct option show_ref_options[] = {
 
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
+	struct show_ref_data show_ref_data = {0};
+	const char **patterns;
+
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
@@ -188,38 +196,40 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	if (exclude_arg)
 		return exclude_existing(exclude_existing_arg);
 
-	pattern = argv;
-	if (!*pattern)
-		pattern = NULL;
+	patterns = argv;
+	if (!*patterns)
+		patterns = NULL;
 
 	if (verify) {
-		if (!pattern)
+		if (!patterns)
 			die("--verify requires a reference");
-		while (*pattern) {
+		while (*patterns) {
 			struct object_id oid;
 
-			if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) &&
-			    !read_ref(*pattern, &oid)) {
-				show_one(*pattern, &oid);
+			if ((starts_with(*patterns, "refs/") || !strcmp(*patterns, "HEAD")) &&
+			    !read_ref(*patterns, &oid)) {
+				show_one(*patterns, &oid);
 			}
 			else if (!quiet)
-				die("'%s' - not a valid ref", *pattern);
+				die("'%s' - not a valid ref", *patterns);
 			else
 				return 1;
-			pattern++;
+			patterns++;
 		}
 		return 0;
 	}
 
+	show_ref_data.patterns = patterns;
+
 	if (show_head)
-		head_ref(show_ref, NULL);
+		head_ref(show_ref, &show_ref_data);
 	if (heads_only || tags_only) {
 		if (heads_only)
-			for_each_fullref_in("refs/heads/", show_ref, NULL);
+			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
 		if (tags_only)
-			for_each_fullref_in("refs/tags/", show_ref, NULL);
+			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
 	} else {
-		for_each_ref(show_ref, NULL);
+		for_each_ref(show_ref, &show_ref_data);
 	}
 	if (!found_match) {
 		if (verify && !quiet)
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 02/12] builtin/show-ref: split up different subcommands
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
                     ` (10 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 4259 bytes --]

While not immediately obvious, git-show-ref(1) actually implements three
different subcommands:

    - `git show-ref <patterns>` can be used to list references that
      match a specific pattern.

    - `git show-ref --verify <refs>` can be used to list references.
      These are _not_ patterns.

    - `git show-ref --exclude-existing` can be used as a filter that
      reads references from standard input, performing some conversions
      on each of them.

Let's make this more explicit in the code by splitting up the three
subcommands into separate functions. This also allows us to address the
confusingly named `patterns` variable, which may hold either patterns or
reference names.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 101 ++++++++++++++++++++++++---------------------
 1 file changed, 54 insertions(+), 47 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 7efab14b96c..cad5b8b5066 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -104,7 +104,7 @@ static int add_existing(const char *refname,
  * (4) ignore if refname is a ref that exists in the local repository;
  * (5) otherwise output the line.
  */
-static int exclude_existing(const char *match)
+static int cmd_show_ref__exclude_existing(const char *match)
 {
 	static struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
@@ -142,6 +142,54 @@ static int exclude_existing(const char *match)
 	return 0;
 }
 
+static int cmd_show_ref__verify(const char **refs)
+{
+	if (!refs || !*refs)
+		die("--verify requires a reference");
+
+	while (*refs) {
+		struct object_id oid;
+
+		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
+		    !read_ref(*refs, &oid)) {
+			show_one(*refs, &oid);
+		}
+		else if (!quiet)
+			die("'%s' - not a valid ref", *refs);
+		else
+			return 1;
+		refs++;
+	}
+
+	return 0;
+}
+
+static int cmd_show_ref__patterns(const char **patterns)
+{
+	struct show_ref_data show_ref_data = {0};
+
+	if (patterns && *patterns)
+		show_ref_data.patterns = patterns;
+
+	if (show_head)
+		head_ref(show_ref, &show_ref_data);
+	if (heads_only || tags_only) {
+		if (heads_only)
+			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
+		if (tags_only)
+			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
+	} else {
+		for_each_ref(show_ref, &show_ref_data);
+	}
+	if (!found_match) {
+		if (verify && !quiet)
+			die("No match");
+		return 1;
+	}
+
+	return 0;
+}
+
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
 	hash_only = 1;
@@ -185,56 +233,15 @@ static const struct option show_ref_options[] = {
 
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
-	struct show_ref_data show_ref_data = {0};
-	const char **patterns;
-
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
 	if (exclude_arg)
-		return exclude_existing(exclude_existing_arg);
-
-	patterns = argv;
-	if (!*patterns)
-		patterns = NULL;
-
-	if (verify) {
-		if (!patterns)
-			die("--verify requires a reference");
-		while (*patterns) {
-			struct object_id oid;
-
-			if ((starts_with(*patterns, "refs/") || !strcmp(*patterns, "HEAD")) &&
-			    !read_ref(*patterns, &oid)) {
-				show_one(*patterns, &oid);
-			}
-			else if (!quiet)
-				die("'%s' - not a valid ref", *patterns);
-			else
-				return 1;
-			patterns++;
-		}
-		return 0;
-	}
-
-	show_ref_data.patterns = patterns;
-
-	if (show_head)
-		head_ref(show_ref, &show_ref_data);
-	if (heads_only || tags_only) {
-		if (heads_only)
-			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
-		if (tags_only)
-			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
-	} else {
-		for_each_ref(show_ref, &show_ref_data);
-	}
-	if (!found_match) {
-		if (verify && !quiet)
-			die("No match");
-		return 1;
-	}
-	return 0;
+		return cmd_show_ref__exclude_existing(exclude_existing_arg);
+	else if (verify)
+		return cmd_show_ref__verify(argv);
+	else
+		return cmd_show_ref__patterns(argv);
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 03/12] builtin/show-ref: fix leaking string buffer
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
                     ` (9 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 1014 bytes --]

Fix a leaking string buffer in `git show-ref --exclude-existing`. While
the buffer is technically not leaking because its variable is declared
as static, there is no inherent reason why it should be.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index cad5b8b5066..e55c38af478 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -106,7 +106,7 @@ static int add_existing(const char *refname,
  */
 static int cmd_show_ref__exclude_existing(const char *match)
 {
-	static struct string_list existing_refs = STRING_LIST_INIT_DUP;
+	struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
 	int matchlen = match ? strlen(match) : 0;
 
@@ -139,6 +139,8 @@ static int cmd_show_ref__exclude_existing(const char *match)
 			printf("%s\n", buf);
 		}
 	}
+
+	string_list_clear(&existing_refs, 0);
 	return 0;
 }
 
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 04/12] builtin/show-ref: fix dead code when passing patterns
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
                     ` (8 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 914 bytes --]

When passing patterns to `git show-ref` we have some code that will
cause us to die if `verify && !quiet` is true. But because `verify`
indicates a different subcommand of git-show-ref(1) that causes us to
execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the
condition cannot ever be true.

Let's remove this dead code.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index e55c38af478..f95418d3d16 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -183,11 +183,8 @@ static int cmd_show_ref__patterns(const char **patterns)
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
 	}
-	if (!found_match) {
-		if (verify && !quiet)
-			die("No match");
+	if (!found_match)
 		return 1;
-	}
 
 	return 0;
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 05/12] builtin/show-ref: refactor `--exclude-existing` options
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
                     ` (7 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 5972 bytes --]

It's not immediately obvious options which options are applicable to
what subcommand in git-show-ref(1) because all options exist as global
state. This can easily cause confusion for the reader.

Refactor options for the `--exclude-existing` subcommand to be contained
in a separate structure. This structure is stored on the stack and
passed down as required. Consequently, it clearly delimits the scope of
those options and requires the reader to worry less about global state.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 78 ++++++++++++++++++++++++++--------------------
 1 file changed, 44 insertions(+), 34 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index f95418d3d16..5aa6016376a 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {
 };
 
 static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
-	   quiet, hash_only, abbrev, exclude_arg;
-static const char *exclude_existing_arg;
+	   quiet, hash_only, abbrev;
 
 static void show_one(const char *refname, const struct object_id *oid)
 {
@@ -95,6 +94,15 @@ static int add_existing(const char *refname,
 	return 0;
 }
 
+struct exclude_existing_options {
+	/*
+	 * We need an explicit `enabled` field because it is perfectly valid
+	 * for `pattern` to be `NULL` even if `--exclude-existing` was given.
+	 */
+	int enabled;
+	const char *pattern;
+};
+
 /*
  * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
  * and
@@ -104,11 +112,11 @@ static int add_existing(const char *refname,
  * (4) ignore if refname is a ref that exists in the local repository;
  * (5) otherwise output the line.
  */
-static int cmd_show_ref__exclude_existing(const char *match)
+static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)
 {
 	struct string_list existing_refs = STRING_LIST_INIT_DUP;
 	char buf[1024];
-	int matchlen = match ? strlen(match) : 0;
+	int patternlen = opts->pattern ? strlen(opts->pattern) : 0;
 
 	for_each_ref(add_existing, &existing_refs);
 	while (fgets(buf, sizeof(buf), stdin)) {
@@ -124,11 +132,11 @@ static int cmd_show_ref__exclude_existing(const char *match)
 		for (ref = buf + len; buf < ref; ref--)
 			if (isspace(ref[-1]))
 				break;
-		if (match) {
+		if (opts->pattern) {
 			int reflen = buf + len - ref;
-			if (reflen < matchlen)
+			if (reflen < patternlen)
 				continue;
-			if (strncmp(ref, match, matchlen))
+			if (strncmp(ref, opts->pattern, patternlen))
 				continue;
 		}
 		if (check_refname_format(ref, 0)) {
@@ -201,44 +209,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)
 static int exclude_existing_callback(const struct option *opt, const char *arg,
 				     int unset)
 {
+	struct exclude_existing_options *opts = opt->value;
 	BUG_ON_OPT_NEG(unset);
-	exclude_arg = 1;
-	*(const char **)opt->value = arg;
+	opts->enabled = 1;
+	opts->pattern = arg;
 	return 0;
 }
 
-static const struct option show_ref_options[] = {
-	OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
-	OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
-	OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
-		    "requires exact ref path")),
-	OPT_HIDDEN_BOOL('h', NULL, &show_head,
-			N_("show the HEAD reference, even if it would be filtered out")),
-	OPT_BOOL(0, "head", &show_head,
-	  N_("show the HEAD reference, even if it would be filtered out")),
-	OPT_BOOL('d', "dereference", &deref_tags,
-		    N_("dereference tags into object IDs")),
-	OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
-		       N_("only show SHA1 hash using <n> digits"),
-		       PARSE_OPT_OPTARG, &hash_callback),
-	OPT__ABBREV(&abbrev),
-	OPT__QUIET(&quiet,
-		   N_("do not print results to stdout (useful with --verify)")),
-	OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg,
-		       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
-		       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
-	OPT_END()
-};
-
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
+	struct exclude_existing_options exclude_existing_opts = {0};
+	const struct option show_ref_options[] = {
+		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
+		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
+			    "requires exact ref path")),
+		OPT_HIDDEN_BOOL('h', NULL, &show_head,
+				N_("show the HEAD reference, even if it would be filtered out")),
+		OPT_BOOL(0, "head", &show_head,
+		  N_("show the HEAD reference, even if it would be filtered out")),
+		OPT_BOOL('d', "dereference", &deref_tags,
+			    N_("dereference tags into object IDs")),
+		OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+			       N_("only show SHA1 hash using <n> digits"),
+			       PARSE_OPT_OPTARG, &hash_callback),
+		OPT__ABBREV(&abbrev),
+		OPT__QUIET(&quiet,
+			   N_("do not print results to stdout (useful with --verify)")),
+		OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_opts,
+			       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
+			       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
+		OPT_END()
+	};
+
 	git_config(git_default_config, NULL);
 
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
-	if (exclude_arg)
-		return cmd_show_ref__exclude_existing(exclude_existing_arg);
+	if (exclude_existing_opts.enabled)
+		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
 		return cmd_show_ref__verify(argv);
 	else
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 06/12] builtin/show-ref: stop using global variable to count matches
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 07/12] builtin/show-ref: stop using global vars for `show_one()` Patrick Steinhardt
                     ` (6 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 1727 bytes --]

When passing patterns to git-show-ref(1) we're checking whether any
reference matches -- if none do, we indicate this condition via an
unsuccessful exit code.

We're using a global variable to count these matches, which is required
because the counter is getting incremented in a callback function. But
now that we have the `struct show_ref_data` in place, we can get rid of
the global variable and put the counter in there instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 5aa6016376a..d0de69e29dd 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,7 +18,7 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int deref_tags, show_head, tags_only, heads_only, found_match, verify,
+static int deref_tags, show_head, tags_only, heads_only, verify,
 	   quiet, hash_only, abbrev;
 
 static void show_one(const char *refname, const struct object_id *oid)
@@ -50,6 +50,7 @@ static void show_one(const char *refname, const struct object_id *oid)
 
 struct show_ref_data {
 	const char **patterns;
+	int found_match;
 };
 
 static int show_ref(const char *refname, const struct object_id *oid,
@@ -78,7 +79,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 	}
 
 match:
-	found_match++;
+	data->found_match++;
 
 	show_one(refname, oid);
 
@@ -191,7 +192,7 @@ static int cmd_show_ref__patterns(const char **patterns)
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
 	}
-	if (!found_match)
+	if (!show_ref_data.found_match)
 		return 1;
 
 	return 0;
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 07/12] builtin/show-ref: stop using global vars for `show_one()`
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (5 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 08/12] builtin/show-ref: refactor options for patterns subcommand Patrick Steinhardt
                     ` (5 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 6148 bytes --]

The `show_one()` function implicitly receives a bunch of options which
are tracked via global variables. This makes it hard to see which
subcommands of git-show-ref(1) actually make use of these options.

Introduce a `show_one_options` structure that gets passed down to this
function. This allows us to get rid of more global state and makes it
more explicit which subcommands use those options.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 62 ++++++++++++++++++++++++++++++----------------
 1 file changed, 40 insertions(+), 22 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index d0de69e29dd..fb0960ac507 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,10 +18,17 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int deref_tags, show_head, tags_only, heads_only, verify,
-	   quiet, hash_only, abbrev;
+static int show_head, tags_only, heads_only, verify;
 
-static void show_one(const char *refname, const struct object_id *oid)
+struct show_one_options {
+	int quiet;
+	int hash_only;
+	int abbrev;
+	int deref_tags;
+};
+
+static void show_one(const struct show_one_options *opts,
+		     const char *refname, const struct object_id *oid)
 {
 	const char *hex;
 	struct object_id peeled;
@@ -30,25 +37,26 @@ static void show_one(const char *refname, const struct object_id *oid)
 		die("git show-ref: bad ref %s (%s)", refname,
 		    oid_to_hex(oid));
 
-	if (quiet)
+	if (opts->quiet)
 		return;
 
-	hex = repo_find_unique_abbrev(the_repository, oid, abbrev);
-	if (hash_only)
+	hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
+	if (opts->hash_only)
 		printf("%s\n", hex);
 	else
 		printf("%s %s\n", hex, refname);
 
-	if (!deref_tags)
+	if (!opts->deref_tags)
 		return;
 
 	if (!peel_iterated_oid(oid, &peeled)) {
-		hex = repo_find_unique_abbrev(the_repository, &peeled, abbrev);
+		hex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);
 		printf("%s %s^{}\n", hex, refname);
 	}
 }
 
 struct show_ref_data {
+	const struct show_one_options *show_one_opts;
 	const char **patterns;
 	int found_match;
 };
@@ -81,7 +89,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 match:
 	data->found_match++;
 
-	show_one(refname, oid);
+	show_one(data->show_one_opts, refname, oid);
 
 	return 0;
 }
@@ -153,7 +161,8 @@ static int cmd_show_ref__exclude_existing(const struct exclude_existing_options
 	return 0;
 }
 
-static int cmd_show_ref__verify(const char **refs)
+static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
+				const char **refs)
 {
 	if (!refs || !*refs)
 		die("--verify requires a reference");
@@ -163,9 +172,9 @@ static int cmd_show_ref__verify(const char **refs)
 
 		if ((starts_with(*refs, "refs/") || !strcmp(*refs, "HEAD")) &&
 		    !read_ref(*refs, &oid)) {
-			show_one(*refs, &oid);
+			show_one(show_one_opts, *refs, &oid);
 		}
-		else if (!quiet)
+		else if (!show_one_opts->quiet)
 			die("'%s' - not a valid ref", *refs);
 		else
 			return 1;
@@ -175,9 +184,12 @@ static int cmd_show_ref__verify(const char **refs)
 	return 0;
 }
 
-static int cmd_show_ref__patterns(const char **patterns)
+static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,
+				  const char **patterns)
 {
-	struct show_ref_data show_ref_data = {0};
+	struct show_ref_data show_ref_data = {
+		.show_one_opts = show_one_opts,
+	};
 
 	if (patterns && *patterns)
 		show_ref_data.patterns = patterns;
@@ -200,11 +212,16 @@ static int cmd_show_ref__patterns(const char **patterns)
 
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
-	hash_only = 1;
+	struct show_one_options *opts = opt->value;
+	struct option abbrev_opt = *opt;
+
+	opts->hash_only = 1;
 	/* Use full length SHA1 if no argument */
 	if (!arg)
 		return 0;
-	return parse_opt_abbrev_cb(opt, arg, unset);
+
+	abbrev_opt.value = &opts->abbrev;
+	return parse_opt_abbrev_cb(&abbrev_opt, arg, unset);
 }
 
 static int exclude_existing_callback(const struct option *opt, const char *arg,
@@ -220,6 +237,7 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
 	struct exclude_existing_options exclude_existing_opts = {0};
+	struct show_one_options show_one_opts = {0};
 	const struct option show_ref_options[] = {
 		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
 		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
@@ -229,13 +247,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 				N_("show the HEAD reference, even if it would be filtered out")),
 		OPT_BOOL(0, "head", &show_head,
 		  N_("show the HEAD reference, even if it would be filtered out")),
-		OPT_BOOL('d', "dereference", &deref_tags,
+		OPT_BOOL('d', "dereference", &show_one_opts.deref_tags,
 			    N_("dereference tags into object IDs")),
-		OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"),
+		OPT_CALLBACK_F('s', "hash", &show_one_opts, N_("n"),
 			       N_("only show SHA1 hash using <n> digits"),
 			       PARSE_OPT_OPTARG, &hash_callback),
-		OPT__ABBREV(&abbrev),
-		OPT__QUIET(&quiet,
+		OPT__ABBREV(&show_one_opts.abbrev),
+		OPT__QUIET(&show_one_opts.quiet,
 			   N_("do not print results to stdout (useful with --verify)")),
 		OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_opts,
 			       N_("pattern"), N_("show refs from stdin that aren't in local repository"),
@@ -251,7 +269,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
-		return cmd_show_ref__verify(argv);
+		return cmd_show_ref__verify(&show_one_opts, argv);
 	else
-		return cmd_show_ref__patterns(argv);
+		return cmd_show_ref__patterns(&show_one_opts, argv);
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 08/12] builtin/show-ref: refactor options for patterns subcommand
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (6 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 07/12] builtin/show-ref: stop using global vars for `show_one()` Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
                     ` (4 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 3956 bytes --]

The patterns subcommand is the last command that still uses global
variables to track its options. Convert it to use a structure instead
with the same motivation as preceding commits.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c | 35 ++++++++++++++++++++++-------------
 1 file changed, 22 insertions(+), 13 deletions(-)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index fb0960ac507..36ac10551da 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -18,8 +18,6 @@ static const char * const show_ref_usage[] = {
 	NULL
 };
 
-static int show_head, tags_only, heads_only, verify;
-
 struct show_one_options {
 	int quiet;
 	int hash_only;
@@ -59,6 +57,7 @@ struct show_ref_data {
 	const struct show_one_options *show_one_opts;
 	const char **patterns;
 	int found_match;
+	int show_head;
 };
 
 static int show_ref(const char *refname, const struct object_id *oid,
@@ -66,7 +65,7 @@ static int show_ref(const char *refname, const struct object_id *oid,
 {
 	struct show_ref_data *data = cbdata;
 
-	if (show_head && !strcmp(refname, "HEAD"))
+	if (data->show_head && !strcmp(refname, "HEAD"))
 		goto match;
 
 	if (data->patterns) {
@@ -184,22 +183,30 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
 	return 0;
 }
 
-static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,
+struct patterns_options {
+	int show_head;
+	int heads_only;
+	int tags_only;
+};
+
+static int cmd_show_ref__patterns(const struct patterns_options *opts,
+				  const struct show_one_options *show_one_opts,
 				  const char **patterns)
 {
 	struct show_ref_data show_ref_data = {
 		.show_one_opts = show_one_opts,
+		.show_head = opts->show_head,
 	};
 
 	if (patterns && *patterns)
 		show_ref_data.patterns = patterns;
 
-	if (show_head)
+	if (opts->show_head)
 		head_ref(show_ref, &show_ref_data);
-	if (heads_only || tags_only) {
-		if (heads_only)
+	if (opts->heads_only || opts->tags_only) {
+		if (opts->heads_only)
 			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
-		if (tags_only)
+		if (opts->tags_only)
 			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
 	} else {
 		for_each_ref(show_ref, &show_ref_data);
@@ -237,15 +244,17 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,
 int cmd_show_ref(int argc, const char **argv, const char *prefix)
 {
 	struct exclude_existing_options exclude_existing_opts = {0};
+	struct patterns_options patterns_opts = {0};
 	struct show_one_options show_one_opts = {0};
+	int verify = 0;
 	const struct option show_ref_options[] = {
-		OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")),
-		OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
+		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
 		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
 			    "requires exact ref path")),
-		OPT_HIDDEN_BOOL('h', NULL, &show_head,
+		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
 				N_("show the HEAD reference, even if it would be filtered out")),
-		OPT_BOOL(0, "head", &show_head,
+		OPT_BOOL(0, "head", &patterns_opts.show_head,
 		  N_("show the HEAD reference, even if it would be filtered out")),
 		OPT_BOOL('d', "dereference", &show_one_opts.deref_tags,
 			    N_("dereference tags into object IDs")),
@@ -271,5 +280,5 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	else if (verify)
 		return cmd_show_ref__verify(&show_one_opts, argv);
 	else
-		return cmd_show_ref__patterns(&show_one_opts, argv);
+		return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);
 }
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (7 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 08/12] builtin/show-ref: refactor options for patterns subcommand Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
                     ` (3 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 1883 bytes --]

The git-show-ref(1) command has three different modes, of which one is
implicit and the other two can be chosen explicitly by passing a flag.
But while these modes are standalone and cause us to execute completely
separate code paths, we gladly accept the case where a user asks for
both `--exclude-existing` and `--verify` at the same time even though it
is not obvious what will happen. Spoiler: we ignore `--verify` and
execute the `--exclude-existing` mode.

Let's explicitly detect this invalid usage and die in case both modes
were requested.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/show-ref.c  | 4 ++++
 t/t1403-show-ref.sh | 9 +++++++++
 2 files changed, 13 insertions(+)

diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 36ac10551da..6685495dd2c 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -275,6 +275,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
+	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
+		die(_("only one of '%s' or '%s' can be given"),
+		    "--exclude-existing", "--verify");
+
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 9252a581abf..f1e0388324e 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -196,4 +196,13 @@ test_expect_success 'show-ref --verify with dangling ref' '
 	)
 '
 
+test_expect_success 'show-ref sub-modes are mutually exclusive' '
+	cat >expect <<-EOF &&
+	fatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given
+	EOF
+
+	test_must_fail git show-ref --verify --exclude-existing 2>err &&
+	test_cmp expect err
+'
+
 test_done
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 10/12] builtin/show-ref: explicitly spell out different modes in synopsis
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (8 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
                     ` (2 subsequent siblings)
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 2536 bytes --]

The synopsis treats the `--verify` and the implicit mode the same. They
are slightly different though:

    - They accept different sets of flags.

    - The implicit mode accepts patterns while the `--verify` mode
      accepts references.

Split up the synopsis for these two modes such that we can disambiguate
those differences.

While at it, drop "--quiet" from the pattern mode's synopsis. It does
not make a lot of sense to list patterns, but squelch the listing output
itself. The description for "--quiet" is adapted accordingly.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-show-ref.txt | 9 ++++++---
 builtin/show-ref.c             | 5 ++++-
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index 2fe274b8faa..22f5ebc6a92 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -8,9 +8,12 @@ git-show-ref - List references in a local repository
 SYNOPSIS
 --------
 [verse]
-'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]
+'git show-ref' [--head] [-d | --dereference]
 	     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
 	     [--heads] [--] [<pattern>...]
+'git show-ref' --verify [-q | --quiet] [-d | --dereference]
+	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
+	     [--] [<ref>...]
 'git show-ref' --exclude-existing[=<pattern>]
 
 DESCRIPTION
@@ -70,8 +73,8 @@ OPTIONS
 -q::
 --quiet::
 
-	Do not print any results to stdout. When combined with `--verify`, this
-	can be used to silently check if a reference exists.
+	Do not print any results to stdout. Can be used with `--verify` to
+	silently check if a reference exists.
 
 --exclude-existing[=<pattern>]::
 
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 6685495dd2c..460f238a62d 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -11,9 +11,12 @@
 #include "parse-options.h"
 
 static const char * const show_ref_usage[] = {
-	N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference]\n"
+	N_("git show-ref [--head] [-d | --dereference]\n"
 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n"
 	   "             [--heads] [--] [<pattern>...]"),
+	N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n"
+	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
+	   "             [--] [<ref>...]"),
 	N_("git show-ref --exclude-existing[=<pattern>]"),
 	NULL
 };
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 11/12] builtin/show-ref: add new mode to check for reference existence
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (9 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31  8:16   ` [PATCH v3 12/12] t: use git-show-ref(1) to check for ref existence Patrick Steinhardt
  2023-10-31 19:06   ` [PATCH v3 00/12] builtin/show-ref: introduce mode " Taylor Blau
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 9741 bytes --]

While we have multiple ways to show the value of a given reference, we
do not have any way to check whether a reference exists at all. While
commands like git-rev-parse(1) or git-show-ref(1) can be used to check
for reference existence in case the reference resolves to something
sane, neither of them can be used to check for existence in some other
scenarios where the reference does not resolve cleanly:

    - References which have an invalid name cannot be resolved.

    - References to nonexistent objects cannot be resolved.

    - Dangling symrefs can be resolved via git-symbolic-ref(1), but this
      requires the caller to special case existence checks depending on
      whether or not a reference is symbolic or direct.

Furthermore, git-rev-list(1) and other commands do not let the caller
distinguish easily between an actually missing reference and a generic
error.

Taken together, this seems like sufficient motivation to introduce a
separate plumbing command to explicitly check for the existence of a
reference without trying to resolve its contents.

This new command comes in the form of `git show-ref --exists`. This
new mode will exit successfully when the reference exists, with a
specific exit code of 2 when it does not exist, or with 1 when there
has been a generic error.

Note that the only way to properly implement this command is by using
the internal `refs_read_raw_ref()` function. While the public function
`refs_resolve_ref_unsafe()` can be made to behave in the same way by
passing various flags, it does not provide any way to obtain the errno
with which the reference backend failed when reading the reference. As
such, it becomes impossible for us to distinguish generic errors from
the explicit case where the reference wasn't found.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-show-ref.txt | 11 ++++++
 builtin/show-ref.c             | 49 +++++++++++++++++++++++---
 t/t1403-show-ref.sh            | 63 +++++++++++++++++++++++++++++++++-
 3 files changed, 117 insertions(+), 6 deletions(-)

diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index 22f5ebc6a92..8fecc9e80f6 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -15,6 +15,7 @@ SYNOPSIS
 	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
 	     [--] [<ref>...]
 'git show-ref' --exclude-existing[=<pattern>]
+'git show-ref' --exists <ref>
 
 DESCRIPTION
 -----------
@@ -30,6 +31,10 @@ The `--exclude-existing` form is a filter that does the inverse. It reads
 refs from stdin, one ref per line, and shows those that don't exist in
 the local repository.
 
+The `--exists` form can be used to check for the existence of a single
+references. This form does not verify whether the reference resolves to an
+actual object.
+
 Use of this utility is encouraged in favor of directly accessing files under
 the `.git` directory.
 
@@ -65,6 +70,12 @@ OPTIONS
 	Aside from returning an error code of 1, it will also print an error
 	message if `--quiet` was not specified.
 
+--exists::
+
+	Check whether the given reference exists. Returns an exit code of 0 if
+	it does, 2 if it is missing, and 1 in case looking up the reference
+	failed with an error other than the reference being missing.
+
 --abbrev[=<n>]::
 
 	Abbreviate the object name.  When using `--hash`, you do
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 460f238a62d..7aac525a878 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -2,7 +2,7 @@
 #include "config.h"
 #include "gettext.h"
 #include "hex.h"
-#include "refs.h"
+#include "refs/refs-internal.h"
 #include "object-name.h"
 #include "object-store-ll.h"
 #include "object.h"
@@ -18,6 +18,7 @@ static const char * const show_ref_usage[] = {
 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
 	   "             [--] [<ref>...]"),
 	N_("git show-ref --exclude-existing[=<pattern>]"),
+	N_("git show-ref --exists <ref>"),
 	NULL
 };
 
@@ -220,6 +221,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
 	return 0;
 }
 
+static int cmd_show_ref__exists(const char **refs)
+{
+	struct strbuf unused_referent = STRBUF_INIT;
+	struct object_id unused_oid;
+	unsigned int unused_type;
+	int failure_errno = 0;
+	const char *ref;
+	int ret = 0;
+
+	if (!refs || !*refs)
+		die("--exists requires a reference");
+	ref = *refs++;
+	if (*refs)
+		die("--exists requires exactly one reference");
+
+	if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
+			      &unused_oid, &unused_referent, &unused_type,
+			      &failure_errno)) {
+		if (failure_errno == ENOENT) {
+			error(_("reference does not exist"));
+			ret = 2;
+		} else {
+			errno = failure_errno;
+			error_errno(_("failed to look up reference"));
+			ret = 1;
+		}
+
+		goto out;
+	}
+
+out:
+	strbuf_release(&unused_referent);
+	return ret;
+}
+
 static int hash_callback(const struct option *opt, const char *arg, int unset)
 {
 	struct show_one_options *opts = opt->value;
@@ -249,10 +285,11 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	struct exclude_existing_options exclude_existing_opts = {0};
 	struct patterns_options patterns_opts = {0};
 	struct show_one_options show_one_opts = {0};
-	int verify = 0;
+	int verify = 0, exists = 0;
 	const struct option show_ref_options[] = {
 		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
 		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
+		OPT_BOOL(0, "exists", &exists, N_("check for reference existence without resolving")),
 		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
 			    "requires exact ref path")),
 		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
@@ -278,14 +315,16 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
 	argc = parse_options(argc, argv, prefix, show_ref_options,
 			     show_ref_usage, 0);
 
-	if ((!!exclude_existing_opts.enabled + !!verify) > 1)
-		die(_("only one of '%s' or '%s' can be given"),
-		    "--exclude-existing", "--verify");
+	if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)
+		die(_("only one of '%s', '%s' or '%s' can be given"),
+		    "--exclude-existing", "--verify", "--exists");
 
 	if (exclude_existing_opts.enabled)
 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
 	else if (verify)
 		return cmd_show_ref__verify(&show_one_opts, argv);
+	else if (exists)
+		return cmd_show_ref__exists(argv);
 	else
 		return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);
 }
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index f1e0388324e..b50ae6fcf11 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -198,10 +198,71 @@ test_expect_success 'show-ref --verify with dangling ref' '
 
 test_expect_success 'show-ref sub-modes are mutually exclusive' '
 	cat >expect <<-EOF &&
-	fatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given
+	fatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given
 	EOF
 
 	test_must_fail git show-ref --verify --exclude-existing 2>err &&
+	test_cmp expect err &&
+
+	test_must_fail git show-ref --verify --exists 2>err &&
+	test_cmp expect err &&
+
+	test_must_fail git show-ref --exclude-existing --exists 2>err &&
+	test_cmp expect err
+'
+
+test_expect_success '--exists with existing reference' '
+	git show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+'
+
+test_expect_success '--exists with missing reference' '
+	test_expect_code 2 git show-ref --exists refs/heads/does-not-exist
+'
+
+test_expect_success '--exists does not use DWIM' '
+	test_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&
+	grep "reference does not exist" err
+'
+
+test_expect_success '--exists with HEAD' '
+	git show-ref --exists HEAD
+'
+
+test_expect_success '--exists with bad reference name' '
+	test_when_finished "git update-ref -d refs/heads/bad...name" &&
+	new_oid=$(git rev-parse HEAD) &&
+	test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
+	git show-ref --exists refs/heads/bad...name
+'
+
+test_expect_success '--exists with arbitrary symref' '
+	test_when_finished "git symbolic-ref -d refs/symref" &&
+	git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
+	git show-ref --exists refs/symref
+'
+
+test_expect_success '--exists with dangling symref' '
+	test_when_finished "git symbolic-ref -d refs/heads/dangling" &&
+	git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+	git show-ref --exists refs/heads/dangling
+'
+
+test_expect_success '--exists with nonexistent object ID' '
+	test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+	git show-ref --exists refs/heads/missing-oid
+'
+
+test_expect_success '--exists with non-commit object' '
+	tree_oid=$(git rev-parse HEAD^{tree}) &&
+	test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&
+	git show-ref --exists refs/heads/tree
+'
+
+test_expect_success '--exists with directory fails with generic error' '
+	cat >expect <<-EOF &&
+	error: failed to look up reference: Is a directory
+	EOF
+	test_expect_code 1 git show-ref --exists refs/heads 2>err &&
 	test_cmp expect err
 '
 
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [PATCH v3 12/12] t: use git-show-ref(1) to check for ref existence
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (10 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
@ 2023-10-31  8:16   ` Patrick Steinhardt
  2023-10-31 19:06   ` [PATCH v3 00/12] builtin/show-ref: introduce mode " Taylor Blau
  12 siblings, 0 replies; 66+ messages in thread
From: Patrick Steinhardt @ 2023-10-31  8:16 UTC (permalink / raw
  To: git; +Cc: Taylor Blau, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

[-- Attachment #1: Type: text/plain, Size: 14354 bytes --]

Convert tests that use `test_path_is_file` and `test_path_is_missing` to
instead use a set of helpers `test_ref_exists` and `test_ref_missing`.
These helpers are implemented via the newly introduced `git show-ref
--exists` command. Thus, we can avoid intimate knowledge of how the ref
backend stores references on disk.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 t/t1430-bad-ref-name.sh | 27 +++++++++++++-------
 t/t3200-branch.sh       | 33 ++++++++++++++-----------
 t/t5521-pull-options.sh |  4 +--
 t/t5605-clone-local.sh  |  2 +-
 t/test-lib-functions.sh | 55 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 94 insertions(+), 27 deletions(-)

diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh
index ff1c967d550..7b7d6953c62 100755
--- a/t/t1430-bad-ref-name.sh
+++ b/t/t1430-bad-ref-name.sh
@@ -205,8 +205,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref to broken name'
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" &&
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git update-ref --no-deref -d refs/heads/badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -216,8 +217,9 @@ test_expect_success 'branch -d can delete symref to broken name' '
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" &&
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git branch -d badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
 	test_must_be_empty error
 '
@@ -225,8 +227,9 @@ test_expect_success 'branch -d can delete symref to broken name' '
 test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' '
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git update-ref --no-deref -d refs/heads/badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -234,8 +237,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref to brok
 test_expect_success 'branch -d can delete dangling symref to broken name' '
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/badname &&
 	git branch -d badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/badname &&
+	test_ref_missing refs/heads/badname &&
 	test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
 	test_must_be_empty error
 '
@@ -245,8 +249,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" &&
 	test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" &&
+	test_ref_exists refs/heads/broken...ref &&
 	git update-ref -d refs/heads/badname >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...ref &&
+	test_ref_missing refs/heads/broken...ref &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -254,8 +259,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '
 test_expect_success 'update-ref --no-deref -d can delete symref with broken name' '
 	printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -263,8 +269,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref with broken name
 test_expect_success 'branch -d can delete symref with broken name' '
 	printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git branch -d broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_i18ngrep "Deleted branch broken...symref (was refs/heads/main)" output &&
 	test_must_be_empty error
 '
@@ -272,8 +279,9 @@ test_expect_success 'branch -d can delete symref with broken name' '
 test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' '
 	printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_must_be_empty output &&
 	test_must_be_empty error
 '
@@ -281,8 +289,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref with br
 test_expect_success 'branch -d can delete dangling symref with broken name' '
 	printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref &&
 	test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" &&
+	test_ref_exists refs/heads/broken...symref &&
 	git branch -d broken...symref >output 2>error &&
-	test_path_is_missing .git/refs/heads/broken...symref &&
+	test_ref_missing refs/heads/broken...symref &&
 	test_i18ngrep "Deleted branch broken...symref (was refs/heads/idonotexist)" output &&
 	test_must_be_empty error
 '
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 080e4f24a6e..bde4f1485b7 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -25,7 +25,7 @@ test_expect_success 'prepare a trivial repository' '
 
 test_expect_success 'git branch --help should not have created a bogus branch' '
 	test_might_fail git branch --man --help </dev/null >/dev/null 2>&1 &&
-	test_path_is_missing .git/refs/heads/--help
+	test_ref_missing refs/heads/--help
 '
 
 test_expect_success 'branch -h in broken repository' '
@@ -40,7 +40,8 @@ test_expect_success 'branch -h in broken repository' '
 '
 
 test_expect_success 'git branch abc should create a branch' '
-	git branch abc && test_path_is_file .git/refs/heads/abc
+	git branch abc &&
+	test_ref_exists refs/heads/abc
 '
 
 test_expect_success 'git branch abc should fail when abc exists' '
@@ -61,11 +62,13 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '
 '
 
 test_expect_success 'git branch a/b/c should create a branch' '
-	git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c
+	git branch a/b/c &&
+	test_ref_exists refs/heads/a/b/c
 '
 
 test_expect_success 'git branch mb main... should create a branch' '
-	git branch mb main... && test_path_is_file .git/refs/heads/mb
+	git branch mb main... &&
+	test_ref_exists refs/heads/mb
 '
 
 test_expect_success 'git branch HEAD should fail' '
@@ -78,14 +81,14 @@ EOF
 test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' '
 	GIT_COMMITTER_DATE="2005-05-26 23:30" \
 	git -c core.logallrefupdates=false branch --create-reflog d/e/f &&
-	test_path_is_file .git/refs/heads/d/e/f &&
+	test_ref_exists refs/heads/d/e/f &&
 	test_path_is_file .git/logs/refs/heads/d/e/f &&
 	test_cmp expect .git/logs/refs/heads/d/e/f
 '
 
 test_expect_success 'git branch -d d/e/f should delete a branch and a log' '
 	git branch -d d/e/f &&
-	test_path_is_missing .git/refs/heads/d/e/f &&
+	test_ref_missing refs/heads/d/e/f &&
 	test_must_fail git reflog exists refs/heads/d/e/f
 '
 
@@ -213,7 +216,7 @@ test_expect_success 'git branch -M should leave orphaned HEAD alone' '
 		test_commit initial &&
 		git checkout --orphan lonely &&
 		grep lonely .git/HEAD &&
-		test_path_is_missing .git/refs/head/lonely &&
+		test_ref_missing refs/head/lonely &&
 		git branch -M main mistress &&
 		grep lonely .git/HEAD
 	)
@@ -799,8 +802,8 @@ test_expect_success 'deleting a symref' '
 	git symbolic-ref refs/heads/symref refs/heads/target &&
 	echo "Deleted branch symref (was refs/heads/target)." >expect &&
 	git branch -d symref >actual &&
-	test_path_is_file .git/refs/heads/target &&
-	test_path_is_missing .git/refs/heads/symref &&
+	test_ref_exists refs/heads/target &&
+	test_ref_missing refs/heads/symref &&
 	test_cmp expect actual
 '
 
@@ -809,16 +812,16 @@ test_expect_success 'deleting a dangling symref' '
 	test_path_is_file .git/refs/heads/dangling-symref &&
 	echo "Deleted branch dangling-symref (was nowhere)." >expect &&
 	git branch -d dangling-symref >actual &&
-	test_path_is_missing .git/refs/heads/dangling-symref &&
+	test_ref_missing refs/heads/dangling-symref &&
 	test_cmp expect actual
 '
 
 test_expect_success 'deleting a self-referential symref' '
 	git symbolic-ref refs/heads/self-reference refs/heads/self-reference &&
-	test_path_is_file .git/refs/heads/self-reference &&
+	test_ref_exists refs/heads/self-reference &&
 	echo "Deleted branch self-reference (was refs/heads/self-reference)." >expect &&
 	git branch -d self-reference >actual &&
-	test_path_is_missing .git/refs/heads/self-reference &&
+	test_ref_missing refs/heads/self-reference &&
 	test_cmp expect actual
 '
 
@@ -826,8 +829,8 @@ test_expect_success 'renaming a symref is not allowed' '
 	git symbolic-ref refs/heads/topic refs/heads/main &&
 	test_must_fail git branch -m topic new-topic &&
 	git symbolic-ref refs/heads/topic &&
-	test_path_is_file .git/refs/heads/main &&
-	test_path_is_missing .git/refs/heads/new-topic
+	test_ref_exists refs/heads/main &&
+	test_ref_missing refs/heads/new-topic
 '
 
 test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '
@@ -1142,7 +1145,7 @@ EOF
 test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '
 	GIT_COMMITTER_DATE="2005-05-26 23:30" \
 	git checkout -b g/h/i -l main &&
-	test_path_is_file .git/refs/heads/g/h/i &&
+	test_ref_exists refs/heads/g/h/i &&
 	test_path_is_file .git/logs/refs/heads/g/h/i &&
 	test_cmp expect .git/logs/refs/heads/g/h/i
 '
diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh
index 079b2f2536e..3681859f983 100755
--- a/t/t5521-pull-options.sh
+++ b/t/t5521-pull-options.sh
@@ -143,7 +143,7 @@ test_expect_success 'git pull --dry-run' '
 		cd clonedry &&
 		git pull --dry-run ../parent &&
 		test_path_is_missing .git/FETCH_HEAD &&
-		test_path_is_missing .git/refs/heads/main &&
+		test_ref_missing refs/heads/main &&
 		test_path_is_missing .git/index &&
 		test_path_is_missing file
 	)
@@ -157,7 +157,7 @@ test_expect_success 'git pull --all --dry-run' '
 		git remote add origin ../parent &&
 		git pull --all --dry-run &&
 		test_path_is_missing .git/FETCH_HEAD &&
-		test_path_is_missing .git/refs/remotes/origin/main &&
+		test_ref_missing refs/remotes/origin/main &&
 		test_path_is_missing .git/index &&
 		test_path_is_missing file
 	)
diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh
index 1d7b1abda1a..946c5751885 100755
--- a/t/t5605-clone-local.sh
+++ b/t/t5605-clone-local.sh
@@ -69,7 +69,7 @@ test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
 	git clone a d &&
 	(cd d &&
 	git fetch &&
-	test ! -e .git/refs/remotes/origin/HEAD)
+	test_ref_missing refs/remotes/origin/HEAD)
 '
 
 test_expect_success 'bundle clone without .bundle suffix' '
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 2f8868caa17..56b33536ed1 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -251,6 +251,61 @@ debug () {
 	done
 }
 
+# Usage: test_ref_exists [options] <ref>
+#
+#   -C <dir>:
+#      Run all git commands in directory <dir>
+#
+# This helper function checks whether a reference exists. Symrefs or object IDs
+# will not be resolved. Can be used to check references with bad names.
+test_ref_exists () {
+	local indir=
+
+	while test $# != 0
+	do
+		case "$1" in
+		-C)
+			indir="$2"
+			shift
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done &&
+
+	indir=${indir:+"$indir"/} &&
+
+	if test "$#" != 1
+	then
+		BUG "expected exactly one reference"
+	fi &&
+
+	git ${indir:+ -C "$indir"} show-ref --exists "$1"
+}
+
+# Behaves the same as test_ref_exists, except that it checks for the absence of
+# a reference. This is preferable to `! test_ref_exists` as this function is
+# able to distinguish actually-missing references from other, generic errors.
+test_ref_missing () {
+	test_ref_exists "$@"
+	case "$?" in
+	2)
+		# This is the good case.
+		return 0
+		;;
+	0)
+		echo >&4 "test_ref_missing: reference exists"
+		return 1
+		;;
+	*)
+		echo >&4 "test_ref_missing: generic error"
+		return 1
+		;;
+	esac
+}
+
 # Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]
 #   -C <dir>:
 #	Run all git commands in directory <dir>
-- 
2.42.0


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* Re: [PATCH v3 00/12] builtin/show-ref: introduce mode to check for ref existence
  2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
                     ` (11 preceding siblings ...)
  2023-10-31  8:16   ` [PATCH v3 12/12] t: use git-show-ref(1) to check for ref existence Patrick Steinhardt
@ 2023-10-31 19:06   ` Taylor Blau
  12 siblings, 0 replies; 66+ messages in thread
From: Taylor Blau @ 2023-10-31 19:06 UTC (permalink / raw
  To: Patrick Steinhardt; +Cc: git, Junio C Hamano, Eric Sunshine, Han-Wen Nienhuys

On Tue, Oct 31, 2023 at 09:16:08AM +0100, Patrick Steinhardt wrote:
> Hi,
>
> this is the third version of my patch series that introduces a new `git
> show-ref --exists` mode to check for reference existence.
>
> Changes compared to v2:
>
>     - Patch 5: Document why we need `exclude_existing_options.enabled`,
>       which isn't exactly obvious.
>
>     - Patch 6: Fix a grammar issue in the commit message.
>
>     - Patch 9: Switch to `test_cmp` instead of grep(1).
>
> Thanks!

Thanks for the updated round. I took a look at the range-diff and didn't
see anything surprising. This version looks great to me, thanks for
working on this!

Thanks,
Taylor


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

end of thread, other threads:[~2023-10-31 19:06 UTC | newest]

Thread overview: 66+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-10-24 13:10 [PATCH 00/12] show-ref: introduce mode to check for ref existence Patrick Steinhardt
2023-10-24 13:10 ` [PATCH 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
2023-10-24 13:10 ` [PATCH 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
2023-10-24 17:55   ` Eric Sunshine
2023-10-24 13:10 ` [PATCH 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
2023-10-24 13:10 ` [PATCH 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
2023-10-24 18:02   ` Eric Sunshine
2023-10-24 13:10 ` [PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
2023-10-24 18:48   ` Eric Sunshine
2023-10-25 11:50     ` Patrick Steinhardt
2023-10-24 13:11 ` [PATCH 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
2023-10-24 13:11 ` [PATCH 07/12] builtin/show-ref: stop using global vars for `show_one()` Patrick Steinhardt
2023-10-24 13:11 ` [PATCH 08/12] builtin/show-ref: refactor options for patterns subcommand Patrick Steinhardt
2023-10-24 13:11 ` [PATCH 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
2023-10-24 19:25   ` Eric Sunshine
2023-10-24 13:11 ` [PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
2023-10-24 19:39   ` Eric Sunshine
2023-10-25 11:50     ` Patrick Steinhardt
2023-10-24 13:11 ` [PATCH 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
2023-10-24 21:01   ` Eric Sunshine
2023-10-25 11:50     ` Patrick Steinhardt
2023-10-24 13:11 ` [PATCH 12/12] t: use git-show-ref(1) to check for ref existence Patrick Steinhardt
2023-10-24 19:17 ` [PATCH 00/12] show-ref: introduce mode " Junio C Hamano
2023-10-25 14:26   ` Han-Wen Nienhuys
2023-10-25 14:44     ` Phillip Wood
2023-10-26  9:48       ` Patrick Steinhardt
2023-10-27 13:06         ` Phillip Wood
2023-10-26  9:44     ` Patrick Steinhardt
2023-10-26  9:56 ` [PATCH v2 " Patrick Steinhardt
2023-10-26  9:56   ` [PATCH v2 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
2023-10-26  9:56   ` [PATCH v2 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
2023-10-26  9:56   ` [PATCH v2 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
2023-10-30 18:10     ` Taylor Blau
2023-10-26  9:56   ` [PATCH v2 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
2023-10-30 18:24     ` Taylor Blau
2023-10-26  9:56   ` [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
2023-10-30 18:37     ` Taylor Blau
2023-10-31  8:10       ` Patrick Steinhardt
2023-10-30 18:55     ` Taylor Blau
2023-10-31  8:10       ` Patrick Steinhardt
2023-10-26  9:56   ` [PATCH v2 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
2023-10-30 19:14     ` Taylor Blau
2023-10-26  9:56   ` [PATCH v2 07/12] builtin/show-ref: stop using global vars for `show_one()` Patrick Steinhardt
2023-10-26  9:56   ` [PATCH v2 08/12] builtin/show-ref: refactor options for patterns subcommand Patrick Steinhardt
2023-10-26  9:56   ` [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
2023-10-30 19:31     ` Taylor Blau
2023-10-31  8:10       ` Patrick Steinhardt
2023-10-26  9:57   ` [PATCH v2 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
2023-10-26  9:57   ` [PATCH v2 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
2023-10-26  9:57   ` [PATCH v2 12/12] t: use git-show-ref(1) to check for ref existence Patrick Steinhardt
2023-10-30 19:32   ` [PATCH v2 00/12] show-ref: introduce mode " Taylor Blau
2023-10-31  2:26     ` Junio C Hamano
2023-10-31  8:16 ` [PATCH v3 00/12] builtin/show-ref: " Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 01/12] builtin/show-ref: convert pattern to a local variable Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 02/12] builtin/show-ref: split up different subcommands Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 03/12] builtin/show-ref: fix leaking string buffer Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 04/12] builtin/show-ref: fix dead code when passing patterns Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 05/12] builtin/show-ref: refactor `--exclude-existing` options Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 06/12] builtin/show-ref: stop using global variable to count matches Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 07/12] builtin/show-ref: stop using global vars for `show_one()` Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 08/12] builtin/show-ref: refactor options for patterns subcommand Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 10/12] builtin/show-ref: explicitly spell out different modes in synopsis Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 11/12] builtin/show-ref: add new mode to check for reference existence Patrick Steinhardt
2023-10-31  8:16   ` [PATCH v3 12/12] t: use git-show-ref(1) to check for ref existence Patrick Steinhardt
2023-10-31 19:06   ` [PATCH v3 00/12] builtin/show-ref: introduce mode " Taylor Blau

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

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 read-only IMAP folder(s) and NNTP newsgroup(s).