{"thread":{"id":"60421","subject":"[PATCH 00/12] show-ref: introduce mode to check for ref existence","startedAt":"2023-10-24T13:10:46Z","lastAt":"2023-10-31T19:06:34Z","messageCount":66,"participants":["Patrick Steinhardt","Eric Sunshine","Junio C Hamano","Han-Wen Nienhuys","Phillip Wood","Taylor Blau"],"isPatch":true,"patchVersion":1,"patchTotal":12},"messages":[{"id":"483770","messageId":"cover.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":null,"subject":"[PATCH 00/12] show-ref: introduce mode to check for ref existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:10:36Z","receivedAt":"2023-10-24T13:10:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis patch series introduces a new `--exists` mode to git-show-ref(1) to\nexplicitly check for the existence of a reference, only. It tries to\naddress a gap in our plumbing tools: while we have a plethora of tools\nto resolve revisions and thus also references, we do not have any tool\nthat can generically check for the existence of both direct and symoblic\nreferences without resolving its contents.\n\nThis series has been split out of my other patch series that refactors\nour test suite to reduce direct access to on-disk data structures. It is\nstructured as follows:\n\n    - Patches 1-8 refactor the code to stop relying on global variables,\n      addressing some smaller issues that surface. Furthermore, the\n      different modes that git-show-ref(1) has are made more explicit\n      such that the command becomes more extensible.\n\n    - Patch 9 ensures that the user does not request mutually exclusive\n      modes.\n\n    - Patch 10 updates the documentation to better reflect how the modes\n      are to be used.\n\n    - Patch 11 introduces the new `--exists` mode as well as a bunch of\n      tests for it.\n\n    - Patch 12 introduces two test helpers `test_ref_exists` and\n      `test_ref_missing` and updates many of our tests to use those\n      instead.\n\nI admittedly may have went a bit overboard with this series. But I had a\nhard time understanding git-show-ref(1) and how the global state affects\nthe different modes.\n\nPatrick\n\n[1]: <cover.1697607222.git.ps@pks.im>\n\nPatrick Steinhardt (12):\n  builtin/show-ref: convert pattern to a local variable\n  builtin/show-ref: split up different subcommands\n  builtin/show-ref: fix leaking string buffer\n  builtin/show-ref: fix dead code when passing patterns\n  builtin/show-ref: refactor `--exclude-existing` options\n  builtin/show-ref: stop using global variable to count matches\n  builtin/show-ref: stop using global vars for `show_one()`\n  builtin/show-ref: refactor options for patterns subcommand\n  builtin/show-ref: ensure mutual exclusiveness of subcommands\n  builtin/show-ref: explicitly spell out different modes in synopsis\n  builtin/show-ref: add new mode to check for reference existence\n  t: use git-show-ref(1) to check for ref existence\n\n Documentation/git-show-ref.txt |  16 +-\n builtin/show-ref.c             | 275 ++++++++++++++++++++++-----------\n t/t1403-show-ref.sh            |  70 +++++++++\n t/t1430-bad-ref-name.sh        |  27 ++--\n t/t3200-branch.sh              |  33 ++--\n t/t5521-pull-options.sh        |   4 +-\n t/t5605-clone-local.sh         |   2 +-\n t/test-lib-functions.sh        |  55 +++++++\n 8 files changed, 363 insertions(+), 119 deletions(-)\n\n\nbase-commit: a9ecda2788e229afc9b611acaa26d0d9d4da53ed\n-- \n2.42.0\n\n"},{"id":"483771","messageId":"78163accbd2c39721249474512dedd8ed2f0fe4e.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 01/12] builtin/show-ref: convert pattern to a local variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:10:40Z","receivedAt":"2023-10-24T13:10:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `pattern` variable is a global variable that tracks either the\nreference names (not patterns!) for the `--verify` mode or the patterns\nfor the non-verify mode. This is a bit confusing due to the slightly\ndifferent meanings.\n\nConvert the variable to be local. While this does not yet fix the double\nmeaning of the variable, this change allows us to address it in a\nsubsequent patch more easily by explicitly splitting up the different\nsubcommands of git-show-ref(1).\n\nNote that we introduce a `struct show_ref_data` to pass the patterns to\n`show_ref()`. While this is overengineered now, we will extend this\nstructure in a subsequent patch.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 46 ++++++++++++++++++++++++++++------------------\n 1 file changed, 28 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 5110814f796..7efab14b96c 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -20,7 +20,6 @@ static const char * const show_ref_usage[] = {\n \n static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n \t   quiet, hash_only, abbrev, exclude_arg;\n-static const char **pattern;\n static const char *exclude_existing_arg;\n \n static void show_one(const char *refname, const struct object_id *oid)\n@@ -50,15 +49,21 @@ static void show_one(const char *refname, const struct object_id *oid)\n \t}\n }\n \n+struct show_ref_data {\n+\tconst char **patterns;\n+};\n+\n static int show_ref(const char *refname, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cbdata UNUSED)\n+\t\t    int flag UNUSED, void *cbdata)\n {\n+\tstruct show_ref_data *data = cbdata;\n+\n \tif (show_head && !strcmp(refname, \"HEAD\"))\n \t\tgoto match;\n \n-\tif (pattern) {\n+\tif (data->patterns) {\n \t\tint reflen = strlen(refname);\n-\t\tconst char **p = pattern, *m;\n+\t\tconst char **p = data->patterns, *m;\n \t\twhile ((m = *p++) != NULL) {\n \t\t\tint len = strlen(m);\n \t\t\tif (len > reflen)\n@@ -180,6 +185,9 @@ static const struct option show_ref_options[] = {\n \n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n+\tstruct show_ref_data show_ref_data = {0};\n+\tconst char **patterns;\n+\n \tgit_config(git_default_config, NULL);\n \n \targc = parse_options(argc, argv, prefix, show_ref_options,\n@@ -188,38 +196,40 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \tif (exclude_arg)\n \t\treturn exclude_existing(exclude_existing_arg);\n \n-\tpattern = argv;\n-\tif (!*pattern)\n-\t\tpattern = NULL;\n+\tpatterns = argv;\n+\tif (!*patterns)\n+\t\tpatterns = NULL;\n \n \tif (verify) {\n-\t\tif (!pattern)\n+\t\tif (!patterns)\n \t\t\tdie(\"--verify requires a reference\");\n-\t\twhile (*pattern) {\n+\t\twhile (*patterns) {\n \t\t\tstruct object_id oid;\n \n-\t\t\tif ((starts_with(*pattern, \"refs/\") || !strcmp(*pattern, \"HEAD\")) &&\n-\t\t\t    !read_ref(*pattern, &oid)) {\n-\t\t\t\tshow_one(*pattern, &oid);\n+\t\t\tif ((starts_with(*patterns, \"refs/\") || !strcmp(*patterns, \"HEAD\")) &&\n+\t\t\t    !read_ref(*patterns, &oid)) {\n+\t\t\t\tshow_one(*patterns, &oid);\n \t\t\t}\n \t\t\telse if (!quiet)\n-\t\t\t\tdie(\"'%s' - not a valid ref\", *pattern);\n+\t\t\t\tdie(\"'%s' - not a valid ref\", *patterns);\n \t\t\telse\n \t\t\t\treturn 1;\n-\t\t\tpattern++;\n+\t\t\tpatterns++;\n \t\t}\n \t\treturn 0;\n \t}\n \n+\tshow_ref_data.patterns = patterns;\n+\n \tif (show_head)\n-\t\thead_ref(show_ref, NULL);\n+\t\thead_ref(show_ref, &show_ref_data);\n \tif (heads_only || tags_only) {\n \t\tif (heads_only)\n-\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, NULL);\n+\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n \t\tif (tags_only)\n-\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, NULL);\n+\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n \t} else {\n-\t\tfor_each_ref(show_ref, NULL);\n+\t\tfor_each_ref(show_ref, &show_ref_data);\n \t}\n \tif (!found_match) {\n \t\tif (verify && !quiet)\n-- \n2.42.0\n\n"},{"id":"483772","messageId":"7e6ab5dee230dcb66cb8adfe4a8114a06c805802.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 02/12] builtin/show-ref: split up different subcommands","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:10:44Z","receivedAt":"2023-10-24T13:10:49Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While not immediately obvious, git-show-ref(1) actually implements three\ndifferent subcommands:\n\n    - `git show-ref <patterns>` can be used to list references that\n      match a specific pattern.\n\n    - `git show-ref --verify <refs>` can be used to list references.\n      These are _not_ patterns.\n\n    - `git show-ref --exclude-existing` can be used as a filter that\n      reads references from standard input, performing some conversions\n      on each of them.\n\nLet's make this more explicit in the code by splitting up the three\nsubcommands into separate functions. This also allows us to address the\nconfusingly named `patterns` variable, which may hold either patterns or\nreference names.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 100 ++++++++++++++++++++++++---------------------\n 1 file changed, 53 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 7efab14b96c..56ee3250c5f 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -104,7 +104,7 @@ static int add_existing(const char *refname,\n  * (4) ignore if refname is a ref that exists in the local repository;\n  * (5) otherwise output the line.\n  */\n-static int exclude_existing(const char *match)\n+static int cmd_show_ref__exclude_existing(const char *match)\n {\n \tstatic struct string_list existing_refs = STRING_LIST_INIT_DUP;\n \tchar buf[1024];\n@@ -142,6 +142,53 @@ static int exclude_existing(const char *match)\n \treturn 0;\n }\n \n+static int cmd_show_ref__verify(const char **refs)\n+{\n+\tif (!refs || !*refs)\n+\t\tdie(\"--verify requires a reference\");\n+\n+\twhile (*refs) {\n+\t\tstruct object_id oid;\n+\n+\t\tif ((starts_with(*refs, \"refs/\") || !strcmp(*refs, \"HEAD\")) &&\n+\t\t    !read_ref(*refs, &oid)) {\n+\t\t\tshow_one(*refs, &oid);\n+\t\t}\n+\t\telse if (!quiet)\n+\t\t\tdie(\"'%s' - not a valid ref\", *refs);\n+\t\telse\n+\t\t\treturn 1;\n+\t\trefs++;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int cmd_show_ref__patterns(const char **patterns)\n+{\n+\tstruct show_ref_data show_ref_data = {\n+\t\t.patterns = (patterns && *patterns) ? patterns : NULL,\n+\t};\n+\n+\tif (show_head)\n+\t\thead_ref(show_ref, &show_ref_data);\n+\tif (heads_only || tags_only) {\n+\t\tif (heads_only)\n+\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n+\t\tif (tags_only)\n+\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n+\t} else {\n+\t\tfor_each_ref(show_ref, &show_ref_data);\n+\t}\n+\tif (!found_match) {\n+\t\tif (verify && !quiet)\n+\t\t\tdie(\"No match\");\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int hash_callback(const struct option *opt, const char *arg, int unset)\n {\n \thash_only = 1;\n@@ -185,56 +232,15 @@ static const struct option show_ref_options[] = {\n \n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n-\tstruct show_ref_data show_ref_data = {0};\n-\tconst char **patterns;\n-\n \tgit_config(git_default_config, NULL);\n \n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n \tif (exclude_arg)\n-\t\treturn exclude_existing(exclude_existing_arg);\n-\n-\tpatterns = argv;\n-\tif (!*patterns)\n-\t\tpatterns = NULL;\n-\n-\tif (verify) {\n-\t\tif (!patterns)\n-\t\t\tdie(\"--verify requires a reference\");\n-\t\twhile (*patterns) {\n-\t\t\tstruct object_id oid;\n-\n-\t\t\tif ((starts_with(*patterns, \"refs/\") || !strcmp(*patterns, \"HEAD\")) &&\n-\t\t\t    !read_ref(*patterns, &oid)) {\n-\t\t\t\tshow_one(*patterns, &oid);\n-\t\t\t}\n-\t\t\telse if (!quiet)\n-\t\t\t\tdie(\"'%s' - not a valid ref\", *patterns);\n-\t\t\telse\n-\t\t\t\treturn 1;\n-\t\t\tpatterns++;\n-\t\t}\n-\t\treturn 0;\n-\t}\n-\n-\tshow_ref_data.patterns = patterns;\n-\n-\tif (show_head)\n-\t\thead_ref(show_ref, &show_ref_data);\n-\tif (heads_only || tags_only) {\n-\t\tif (heads_only)\n-\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n-\t\tif (tags_only)\n-\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n-\t} else {\n-\t\tfor_each_ref(show_ref, &show_ref_data);\n-\t}\n-\tif (!found_match) {\n-\t\tif (verify && !quiet)\n-\t\t\tdie(\"No match\");\n-\t\treturn 1;\n-\t}\n-\treturn 0;\n+\t\treturn cmd_show_ref__exclude_existing(exclude_existing_arg);\n+\telse if (verify)\n+\t\treturn cmd_show_ref__verify(argv);\n+\telse\n+\t\treturn cmd_show_ref__patterns(argv);\n }\n-- \n2.42.0\n\n"},{"id":"483773","messageId":"ae2e401fbd86500381ca9e579d40ebc4fd683f89.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 03/12] builtin/show-ref: fix leaking string buffer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:10:48Z","receivedAt":"2023-10-24T13:10:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Fix a leaking string buffer in `git show-ref --exclude-existing`. While\nthe buffer is technically not leaking because its variable is declared\nas static, there is no inherent reason why it should be.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 56ee3250c5f..761669d28de 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -106,7 +106,7 @@ static int add_existing(const char *refname,\n  */\n static int cmd_show_ref__exclude_existing(const char *match)\n {\n-\tstatic struct string_list existing_refs = STRING_LIST_INIT_DUP;\n+\tstruct string_list existing_refs = STRING_LIST_INIT_DUP;\n \tchar buf[1024];\n \tint matchlen = match ? strlen(match) : 0;\n \n@@ -139,6 +139,8 @@ static int cmd_show_ref__exclude_existing(const char *match)\n \t\t\tprintf(\"%s\\n\", buf);\n \t\t}\n \t}\n+\n+\tstring_list_clear(&existing_refs, 0);\n \treturn 0;\n }\n \n-- \n2.42.0\n\n"},{"id":"483774","messageId":"29c0c0c6c9728f286efd0b78996f9a1394957862.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 04/12] builtin/show-ref: fix dead code when passing patterns","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:10:53Z","receivedAt":"2023-10-24T13:10:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When passing patterns to `git show-ref` we have some code that will\ncause us to die of `verify && !quiet` is true. But because `verify`\nindicates a different subcommand of git-show-ref(1) that causes us to\nexecute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the\ncondition cannot ever be true.\n\nLet's remove this dead code.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 5 +----\n 1 file changed, 1 insertion(+), 4 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 761669d28de..eb60f940a3c 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -182,11 +182,8 @@ static int cmd_show_ref__patterns(const char **patterns)\n \t} else {\n \t\tfor_each_ref(show_ref, &show_ref_data);\n \t}\n-\tif (!found_match) {\n-\t\tif (verify && !quiet)\n-\t\t\tdie(\"No match\");\n+\tif (!found_match)\n \t\treturn 1;\n-\t}\n \n \treturn 0;\n }\n-- \n2.42.0\n\n"},{"id":"483775","messageId":"8d0b0b5700c7ffed6b3a74760d0d9155b404bb4f.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:10:57Z","receivedAt":"2023-10-24T13:11:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"It's not immediately obvious options which options are applicable to\nwhat subcommand ni git-show-ref(1) because all options exist as global\nstate. This can easily cause confusion for the reader.\n\nRefactor options for the `--exclude-existing` subcommand to be contained\nin a separate structure. This structure is stored on the stack and\npassed down as required. Consequentially, it clearly delimits the scope\nof those options and requires the reader to worry less about global\nstate.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 72 +++++++++++++++++++++++++---------------------\n 1 file changed, 39 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex eb60f940a3c..e130b999c0b 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {\n };\n \n static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n-\t   quiet, hash_only, abbrev, exclude_arg;\n-static const char *exclude_existing_arg;\n+\t   quiet, hash_only, abbrev;\n \n static void show_one(const char *refname, const struct object_id *oid)\n {\n@@ -95,6 +94,11 @@ static int add_existing(const char *refname,\n \treturn 0;\n }\n \n+struct exclude_existing_options {\n+\tint enabled;\n+\tconst char *pattern;\n+};\n+\n /*\n  * read \"^(?:<anything>\\s)?<refname>(?:\\^\\{\\})?$\" from the standard input,\n  * and\n@@ -104,11 +108,11 @@ static int add_existing(const char *refname,\n  * (4) ignore if refname is a ref that exists in the local repository;\n  * (5) otherwise output the line.\n  */\n-static int cmd_show_ref__exclude_existing(const char *match)\n+static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)\n {\n \tstruct string_list existing_refs = STRING_LIST_INIT_DUP;\n \tchar buf[1024];\n-\tint matchlen = match ? strlen(match) : 0;\n+\tint matchlen = opts->pattern ? strlen(opts->pattern) : 0;\n \n \tfor_each_ref(add_existing, &existing_refs);\n \twhile (fgets(buf, sizeof(buf), stdin)) {\n@@ -124,11 +128,11 @@ static int cmd_show_ref__exclude_existing(const char *match)\n \t\tfor (ref = buf + len; buf < ref; ref--)\n \t\t\tif (isspace(ref[-1]))\n \t\t\t\tbreak;\n-\t\tif (match) {\n+\t\tif (opts->pattern) {\n \t\t\tint reflen = buf + len - ref;\n \t\t\tif (reflen < matchlen)\n \t\t\t\tcontinue;\n-\t\t\tif (strncmp(ref, match, matchlen))\n+\t\t\tif (strncmp(ref, opts->pattern, matchlen))\n \t\t\t\tcontinue;\n \t\t}\n \t\tif (check_refname_format(ref, 0)) {\n@@ -200,44 +204,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)\n static int exclude_existing_callback(const struct option *opt, const char *arg,\n \t\t\t\t     int unset)\n {\n+\tstruct exclude_existing_options *opts = opt->value;\n \tBUG_ON_OPT_NEG(unset);\n-\texclude_arg = 1;\n-\t*(const char **)opt->value = arg;\n+\topts->enabled = 1;\n+\topts->pattern = arg;\n \treturn 0;\n }\n \n-static const struct option show_ref_options[] = {\n-\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n-\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n-\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n-\t\t    \"requires exact ref path\")),\n-\tOPT_HIDDEN_BOOL('h', NULL, &show_head,\n-\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n-\tOPT_BOOL(0, \"head\", &show_head,\n-\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n-\tOPT_BOOL('d', \"dereference\", &deref_tags,\n-\t\t    N_(\"dereference tags into object IDs\")),\n-\tOPT_CALLBACK_F('s', \"hash\", &abbrev, N_(\"n\"),\n-\t\t       N_(\"only show SHA1 hash using <n> digits\"),\n-\t\t       PARSE_OPT_OPTARG, &hash_callback),\n-\tOPT__ABBREV(&abbrev),\n-\tOPT__QUIET(&quiet,\n-\t\t   N_(\"do not print results to stdout (useful with --verify)\")),\n-\tOPT_CALLBACK_F(0, \"exclude-existing\", &exclude_existing_arg,\n-\t\t       N_(\"pattern\"), N_(\"show refs from stdin that aren't in local repository\"),\n-\t\t       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),\n-\tOPT_END()\n-};\n-\n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n+\tstruct exclude_existing_options exclude_existing_opts = {0};\n+\tconst struct option show_ref_options[] = {\n+\t\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n+\t\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n+\t\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n+\t\t\t    \"requires exact ref path\")),\n+\t\tOPT_HIDDEN_BOOL('h', NULL, &show_head,\n+\t\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n+\t\tOPT_BOOL(0, \"head\", &show_head,\n+\t\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n+\t\tOPT_BOOL('d', \"dereference\", &deref_tags,\n+\t\t\t    N_(\"dereference tags into object IDs\")),\n+\t\tOPT_CALLBACK_F('s', \"hash\", &abbrev, N_(\"n\"),\n+\t\t\t       N_(\"only show SHA1 hash using <n> digits\"),\n+\t\t\t       PARSE_OPT_OPTARG, &hash_callback),\n+\t\tOPT__ABBREV(&abbrev),\n+\t\tOPT__QUIET(&quiet,\n+\t\t\t   N_(\"do not print results to stdout (useful with --verify)\")),\n+\t\tOPT_CALLBACK_F(0, \"exclude-existing\", &exclude_existing_opts,\n+\t\t\t       N_(\"pattern\"), N_(\"show refs from stdin that aren't in local repository\"),\n+\t\t\t       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),\n+\t\tOPT_END()\n+\t};\n+\n \tgit_config(git_default_config, NULL);\n \n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n-\tif (exclude_arg)\n-\t\treturn cmd_show_ref__exclude_existing(exclude_existing_arg);\n+\tif (exclude_existing_opts.enabled)\n+\t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\n \t\treturn cmd_show_ref__verify(argv);\n \telse\n-- \n2.42.0\n\n"},{"id":"483776","messageId":"6e0f3d4e104372157da1ebd4509a5a7b0ac47636.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 06/12] builtin/show-ref: stop using global variable to count matches","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:11:01Z","receivedAt":"2023-10-24T13:11:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When passing patterns to git-show-ref(1) we're checking whether any\nreference matches -- if none does, we indicate this condition via an\nunsuccessful exit code.\n\nWe're using a global variable to count these matches, which is required\nbecause the counter is getting incremented in a callback function. But\nnow that we have the `struct show_ref_data` in place, we can get rid of\nthe global variable and put the counter in there instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex e130b999c0b..4c039007dd1 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -18,7 +18,7 @@ static const char * const show_ref_usage[] = {\n \tNULL\n };\n \n-static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n+static int deref_tags, show_head, tags_only, heads_only, verify,\n \t   quiet, hash_only, abbrev;\n \n static void show_one(const char *refname, const struct object_id *oid)\n@@ -50,6 +50,7 @@ static void show_one(const char *refname, const struct object_id *oid)\n \n struct show_ref_data {\n \tconst char **patterns;\n+\tint found_match;\n };\n \n static int show_ref(const char *refname, const struct object_id *oid,\n@@ -78,7 +79,7 @@ static int show_ref(const char *refname, const struct object_id *oid,\n \t}\n \n match:\n-\tfound_match++;\n+\tdata->found_match++;\n \n \tshow_one(refname, oid);\n \n@@ -186,7 +187,7 @@ static int cmd_show_ref__patterns(const char **patterns)\n \t} else {\n \t\tfor_each_ref(show_ref, &show_ref_data);\n \t}\n-\tif (!found_match)\n+\tif (!show_ref_data.found_match)\n \t\treturn 1;\n \n \treturn 0;\n-- \n2.42.0\n\n"},{"id":"483777","messageId":"2da1e65dd8f92b3b6955321abf6b497cca75ce0d.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 07/12] builtin/show-ref: stop using global vars for `show_one()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:11:05Z","receivedAt":"2023-10-24T13:11:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `show_one()` function implicitly receives a bunch of options which\nare tracked via global variables. This makes it hard to see which\nsubcommands of git-show-ref(1) actually make use of these options.\n\nIntroduce a `show_one_options` structure that gets passed down to this\nfunction. This allows us to get rid of more global state and makes it\nmore explicit which subcommands use those options.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 59 +++++++++++++++++++++++++++++-----------------\n 1 file changed, 38 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 4c039007dd1..589a91f15b9 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -18,10 +18,17 @@ static const char * const show_ref_usage[] = {\n \tNULL\n };\n \n-static int deref_tags, show_head, tags_only, heads_only, verify,\n-\t   quiet, hash_only, abbrev;\n+static int show_head, tags_only, heads_only, verify;\n \n-static void show_one(const char *refname, const struct object_id *oid)\n+struct show_one_options {\n+\tint quiet;\n+\tint hash_only;\n+\tint abbrev;\n+\tint deref_tags;\n+};\n+\n+static void show_one(const struct show_one_options *opts,\n+\t\t     const char *refname, const struct object_id *oid)\n {\n \tconst char *hex;\n \tstruct object_id peeled;\n@@ -30,25 +37,26 @@ static void show_one(const char *refname, const struct object_id *oid)\n \t\tdie(\"git show-ref: bad ref %s (%s)\", refname,\n \t\t    oid_to_hex(oid));\n \n-\tif (quiet)\n+\tif (opts->quiet)\n \t\treturn;\n \n-\thex = repo_find_unique_abbrev(the_repository, oid, abbrev);\n-\tif (hash_only)\n+\thex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);\n+\tif (opts->hash_only)\n \t\tprintf(\"%s\\n\", hex);\n \telse\n \t\tprintf(\"%s %s\\n\", hex, refname);\n \n-\tif (!deref_tags)\n+\tif (!opts->deref_tags)\n \t\treturn;\n \n \tif (!peel_iterated_oid(oid, &peeled)) {\n-\t\thex = repo_find_unique_abbrev(the_repository, &peeled, abbrev);\n+\t\thex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);\n \t\tprintf(\"%s %s^{}\\n\", hex, refname);\n \t}\n }\n \n struct show_ref_data {\n+\tconst struct show_one_options *show_one_opts;\n \tconst char **patterns;\n \tint found_match;\n };\n@@ -81,7 +89,7 @@ static int show_ref(const char *refname, const struct object_id *oid,\n match:\n \tdata->found_match++;\n \n-\tshow_one(refname, oid);\n+\tshow_one(data->show_one_opts, refname, oid);\n \n \treturn 0;\n }\n@@ -149,7 +157,8 @@ static int cmd_show_ref__exclude_existing(const struct exclude_existing_options\n \treturn 0;\n }\n \n-static int cmd_show_ref__verify(const char **refs)\n+static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n+\t\t\t\tconst char **refs)\n {\n \tif (!refs || !*refs)\n \t\tdie(\"--verify requires a reference\");\n@@ -159,9 +168,9 @@ static int cmd_show_ref__verify(const char **refs)\n \n \t\tif ((starts_with(*refs, \"refs/\") || !strcmp(*refs, \"HEAD\")) &&\n \t\t    !read_ref(*refs, &oid)) {\n-\t\t\tshow_one(*refs, &oid);\n+\t\t\tshow_one(show_one_opts, *refs, &oid);\n \t\t}\n-\t\telse if (!quiet)\n+\t\telse if (!show_one_opts->quiet)\n \t\t\tdie(\"'%s' - not a valid ref\", *refs);\n \t\telse\n \t\t\treturn 1;\n@@ -171,9 +180,11 @@ static int cmd_show_ref__verify(const char **refs)\n \treturn 0;\n }\n \n-static int cmd_show_ref__patterns(const char **patterns)\n+static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,\n+\t\t\t\t  const char **patterns)\n {\n \tstruct show_ref_data show_ref_data = {\n+\t\t.show_one_opts = show_one_opts,\n \t\t.patterns = (patterns && *patterns) ? patterns : NULL,\n \t};\n \n@@ -195,11 +206,16 @@ static int cmd_show_ref__patterns(const char **patterns)\n \n static int hash_callback(const struct option *opt, const char *arg, int unset)\n {\n-\thash_only = 1;\n+\tstruct show_one_options *opts = opt->value;\n+\tstruct option abbrev_opt = *opt;\n+\n+\topts->hash_only = 1;\n \t/* Use full length SHA1 if no argument */\n \tif (!arg)\n \t\treturn 0;\n-\treturn parse_opt_abbrev_cb(opt, arg, unset);\n+\n+\tabbrev_opt.value = &opts->abbrev;\n+\treturn parse_opt_abbrev_cb(&abbrev_opt, arg, unset);\n }\n \n static int exclude_existing_callback(const struct option *opt, const char *arg,\n@@ -215,6 +231,7 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,\n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n \tstruct exclude_existing_options exclude_existing_opts = {0};\n+\tstruct show_one_options show_one_opts = {0};\n \tconst struct option show_ref_options[] = {\n \t\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n \t\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n@@ -224,13 +241,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \t\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n \t\tOPT_BOOL(0, \"head\", &show_head,\n \t\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n-\t\tOPT_BOOL('d', \"dereference\", &deref_tags,\n+\t\tOPT_BOOL('d', \"dereference\", &show_one_opts.deref_tags,\n \t\t\t    N_(\"dereference tags into object IDs\")),\n-\t\tOPT_CALLBACK_F('s', \"hash\", &abbrev, N_(\"n\"),\n+\t\tOPT_CALLBACK_F('s', \"hash\", &show_one_opts, N_(\"n\"),\n \t\t\t       N_(\"only show SHA1 hash using <n> digits\"),\n \t\t\t       PARSE_OPT_OPTARG, &hash_callback),\n-\t\tOPT__ABBREV(&abbrev),\n-\t\tOPT__QUIET(&quiet,\n+\t\tOPT__ABBREV(&show_one_opts.abbrev),\n+\t\tOPT__QUIET(&show_one_opts.quiet,\n \t\t\t   N_(\"do not print results to stdout (useful with --verify)\")),\n \t\tOPT_CALLBACK_F(0, \"exclude-existing\", &exclude_existing_opts,\n \t\t\t       N_(\"pattern\"), N_(\"show refs from stdin that aren't in local repository\"),\n@@ -246,7 +263,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \tif (exclude_existing_opts.enabled)\n \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\n-\t\treturn cmd_show_ref__verify(argv);\n+\t\treturn cmd_show_ref__verify(&show_one_opts, argv);\n \telse\n-\t\treturn cmd_show_ref__patterns(argv);\n+\t\treturn cmd_show_ref__patterns(&show_one_opts, argv);\n }\n-- \n2.42.0\n\n"},{"id":"483778","messageId":"805889eda4cf89e6aebf8a408ceb2a5c60551526.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 08/12] builtin/show-ref: refactor options for patterns subcommand","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:11:10Z","receivedAt":"2023-10-24T13:11:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The patterns subcommand is the last command that still uses global\nvariables to track its options. Convert it to use a structure instead\nwith the same motivation as preceding commits.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 35 ++++++++++++++++++++++-------------\n 1 file changed, 22 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 589a91f15b9..5d5d7d22ed1 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -18,8 +18,6 @@ static const char * const show_ref_usage[] = {\n \tNULL\n };\n \n-static int show_head, tags_only, heads_only, verify;\n-\n struct show_one_options {\n \tint quiet;\n \tint hash_only;\n@@ -59,6 +57,7 @@ struct show_ref_data {\n \tconst struct show_one_options *show_one_opts;\n \tconst char **patterns;\n \tint found_match;\n+\tint show_head;\n };\n \n static int show_ref(const char *refname, const struct object_id *oid,\n@@ -66,7 +65,7 @@ static int show_ref(const char *refname, const struct object_id *oid,\n {\n \tstruct show_ref_data *data = cbdata;\n \n-\tif (show_head && !strcmp(refname, \"HEAD\"))\n+\tif (data->show_head && !strcmp(refname, \"HEAD\"))\n \t\tgoto match;\n \n \tif (data->patterns) {\n@@ -180,20 +179,28 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n \treturn 0;\n }\n \n-static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,\n+struct patterns_options {\n+\tint show_head;\n+\tint heads_only;\n+\tint tags_only;\n+};\n+\n+static int cmd_show_ref__patterns(const struct patterns_options *opts,\n+\t\t\t\t  const struct show_one_options *show_one_opts,\n \t\t\t\t  const char **patterns)\n {\n \tstruct show_ref_data show_ref_data = {\n \t\t.show_one_opts = show_one_opts,\n+\t\t.show_head = opts->show_head,\n \t\t.patterns = (patterns && *patterns) ? patterns : NULL,\n \t};\n \n-\tif (show_head)\n+\tif (opts->show_head)\n \t\thead_ref(show_ref, &show_ref_data);\n-\tif (heads_only || tags_only) {\n-\t\tif (heads_only)\n+\tif (opts->heads_only || opts->tags_only) {\n+\t\tif (opts->heads_only)\n \t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n-\t\tif (tags_only)\n+\t\tif (opts->tags_only)\n \t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n \t} else {\n \t\tfor_each_ref(show_ref, &show_ref_data);\n@@ -231,15 +238,17 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,\n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n \tstruct exclude_existing_options exclude_existing_opts = {0};\n+\tstruct patterns_options patterns_opts = {0};\n \tstruct show_one_options show_one_opts = {0};\n+\tint verify = 0;\n \tconst struct option show_ref_options[] = {\n-\t\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n-\t\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n+\t\tOPT_BOOL(0, \"tags\", &patterns_opts.tags_only, N_(\"only show tags (can be combined with heads)\")),\n+\t\tOPT_BOOL(0, \"heads\", &patterns_opts.heads_only, N_(\"only show heads (can be combined with tags)\")),\n \t\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n \t\t\t    \"requires exact ref path\")),\n-\t\tOPT_HIDDEN_BOOL('h', NULL, &show_head,\n+\t\tOPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,\n \t\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n-\t\tOPT_BOOL(0, \"head\", &show_head,\n+\t\tOPT_BOOL(0, \"head\", &patterns_opts.show_head,\n \t\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n \t\tOPT_BOOL('d', \"dereference\", &show_one_opts.deref_tags,\n \t\t\t    N_(\"dereference tags into object IDs\")),\n@@ -265,5 +274,5 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \telse if (verify)\n \t\treturn cmd_show_ref__verify(&show_one_opts, argv);\n \telse\n-\t\treturn cmd_show_ref__patterns(&show_one_opts, argv);\n+\t\treturn cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);\n }\n-- \n2.42.0\n\n"},{"id":"483779","messageId":"d0a991cf4f892e73e4fd62ef3fdae3fa73277321.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:11:14Z","receivedAt":"2023-10-24T13:11:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The git-show-ref(1) command has three different modes, of which one is\nimplicit and the other two can be chosen explicitly by passing a flag.\nBut while these modes are standalone and cause us to execute completely\nseparate code paths, we gladly accept the case where a user asks for\nboth `--exclude-existing` and `--verify` at the same time even though it\nis not obvious what will happen. Spoiler: we ignore `--verify` and\nexecute the `--exclude-existing` mode.\n\nLet's explicitly detect this invalid usage and die in case both modes\nwere requested.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c  | 3 +++\n t/t1403-show-ref.sh | 5 +++++\n 2 files changed, 8 insertions(+)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 5d5d7d22ed1..10d0213e687 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -269,6 +269,9 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n+\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n+\t\tdie(_(\"only one of --exclude-existing or --verify can be given\"));\n+\n \tif (exclude_existing_opts.enabled)\n \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\ndiff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh\nindex 9252a581abf..3a312c8b27c 100755\n--- a/t/t1403-show-ref.sh\n+++ b/t/t1403-show-ref.sh\n@@ -196,4 +196,9 @@ test_expect_success 'show-ref --verify with dangling ref' '\n \t)\n '\n \n+test_expect_success 'show-ref sub-modes are mutually exclusive' '\n+\ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n+\tgrep \"only one of --exclude-existing or --verify can be given\" err\n+'\n+\n test_done\n-- \n2.42.0\n\n"},{"id":"483780","messageId":"adcfa7a6a9d8fb6f915faf77df52362544cd590e.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:11:20Z","receivedAt":"2023-10-24T13:11:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The synopsis treats the `--verify` and the implicit mode the same. They\nare slightly different though:\n\n    - They accept different sets of flags.\n\n    - The implicit mode accepts patterns while the `--verify` mode\n      accepts references.\n\nSplit up the synopsis for these two modes such that we can disambiguate\nthose differences.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-show-ref.txt | 5 ++++-\n builtin/show-ref.c             | 5 ++++-\n 2 files changed, 8 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\nindex 2fe274b8faa..ab23e0b62e1 100644\n--- a/Documentation/git-show-ref.txt\n+++ b/Documentation/git-show-ref.txt\n@@ -8,9 +8,12 @@ git-show-ref - List references in a local repository\n SYNOPSIS\n --------\n [verse]\n-'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]\n+'git show-ref' [-q | --quiet] [--head] [-d | --dereference]\n \t     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n \t     [--heads] [--] [<pattern>...]\n+'git show-ref' --verify [-q | --quiet] [-d | --dereference]\n+\t     [-s | --hash[=<n>]] [--abbrev[=<n>]]\n+\t     [--] [<ref>...]\n 'git show-ref' --exclude-existing[=<pattern>]\n \n DESCRIPTION\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 10d0213e687..d0a32d07404 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -11,9 +11,12 @@\n #include \"parse-options.h\"\n \n static const char * const show_ref_usage[] = {\n-\tN_(\"git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference]\\n\"\n+\tN_(\"git show-ref [-q | --quiet] [--head] [-d | --dereference]\\n\"\n \t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\\n\"\n \t   \"             [--heads] [--] [<pattern>...]\"),\n+\tN_(\"git show-ref --verify [-q | --quiet] [-d | --dereference]\\n\"\n+\t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]]\\n\"\n+\t   \"             [--] [<ref>...]\"),\n \tN_(\"git show-ref --exclude-existing[=<pattern>]\"),\n \tNULL\n };\n-- \n2.42.0\n\n"},{"id":"483781","messageId":"2f876e61dd36a8887a1286bb8db9fb6577c55c9b.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 11/12] builtin/show-ref: add new mode to check for reference existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:11:24Z","receivedAt":"2023-10-24T13:11:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we have multiple ways to show the value of a given reference, we\ndo not have any way to check whether a reference exists at all. While\ncommands like git-rev-parse(1) or git-show-ref(1) can be used to check\nfor reference existence in case the reference resolves to something\nsane, neither of them can be used to check for existence in some other\nscenarios where the reference does not resolve cleanly:\n\n    - References which have an invalid name cannot be resolved.\n\n    - References to nonexistent objects cannot be resolved.\n\n    - Dangling symrefs can be resolved via git-symbolic-ref(1), but this\n      requires the caller to special case existence checks depending on\n      whteher or not a reference is symbolic or direct.\n\nFurthermore, git-rev-list(1) and other commands do not let the caller\ndistinguish easily between an actually missing reference and a generic\nerror.\n\nTaken together, this gseems like sufficient motivation to introduce a\nseparate plumbing command to explicitly check for the existence of a\nreference without trying to resolve its contents.\n\nThis new command comes in the form of `git show-ref --exists`. This\nnew mode will exit successfully when the reference exists, with a\nspecific error code of 2 when it does not exist, or with 1 when there\nhas been a generic error.\n\nNote that the only way to properly implement this command is by using\nthe internal `refs_read_raw_ref()` function. While the public function\n`refs_resolve_ref_unsafe()` can be made to behave in the same way by\npassing various flags, it does not provide any way to obtain the errno\nwith which the reference backend failed when reading the reference. As\nsuch, it becomes impossible for us to distinguish generic errors from\nthe explicit case where the reference wasn't found.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-show-ref.txt | 11 ++++++\n builtin/show-ref.c             | 47 ++++++++++++++++++++++--\n t/t1403-show-ref.sh            | 67 +++++++++++++++++++++++++++++++++-\n 3 files changed, 120 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\nindex ab23e0b62e1..a7e9374bc2b 100644\n--- a/Documentation/git-show-ref.txt\n+++ b/Documentation/git-show-ref.txt\n@@ -15,6 +15,7 @@ SYNOPSIS\n \t     [-s | --hash[=<n>]] [--abbrev[=<n>]]\n \t     [--] [<ref>...]\n 'git show-ref' --exclude-existing[=<pattern>]\n+'git show-ref' --exists <ref>\n \n DESCRIPTION\n -----------\n@@ -30,6 +31,10 @@ The `--exclude-existing` form is a filter that does the inverse. It reads\n refs from stdin, one ref per line, and shows those that don't exist in\n the local repository.\n \n+The `--exists` form can be used to check for the existence of a single\n+references. This form does not verify whether the reference resolves to an\n+actual object.\n+\n Use of this utility is encouraged in favor of directly accessing files under\n the `.git` directory.\n \n@@ -65,6 +70,12 @@ OPTIONS\n \tAside from returning an error code of 1, it will also print an error\n \tmessage if `--quiet` was not specified.\n \n+--exists::\n+\n+\tCheck whether the given reference exists. Returns an error code of 0 if\n+\tit does, 2 if it is missing, and 128 in case looking up the reference\n+\tfailed with an error other than the reference being missing.\n+\n --abbrev[=<n>]::\n \n \tAbbreviate the object name.  When using `--hash`, you do\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex d0a32d07404..617e754bbed 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -2,7 +2,7 @@\n #include \"config.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n-#include \"refs.h\"\n+#include \"refs/refs-internal.h\"\n #include \"object-name.h\"\n #include \"object-store-ll.h\"\n #include \"object.h\"\n@@ -18,6 +18,7 @@ static const char * const show_ref_usage[] = {\n \t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]]\\n\"\n \t   \"             [--] [<ref>...]\"),\n \tN_(\"git show-ref --exclude-existing[=<pattern>]\"),\n+\tN_(\"git show-ref --exists <ref>\"),\n \tNULL\n };\n \n@@ -214,6 +215,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,\n \treturn 0;\n }\n \n+static int cmd_show_ref__exists(const char **refs)\n+{\n+\tstruct strbuf unused_referent = STRBUF_INIT;\n+\tstruct object_id unused_oid;\n+\tunsigned int unused_type;\n+\tint failure_errno = 0;\n+\tconst char *ref;\n+\tint ret = 1;\n+\n+\tif (!refs || !*refs)\n+\t\tdie(\"--exists requires a reference\");\n+\tref = *refs++;\n+\tif (*refs)\n+\t\tdie(\"--exists requires exactly one reference\");\n+\n+\tif (refs_read_raw_ref(get_main_ref_store(the_repository), ref,\n+\t\t\t      &unused_oid, &unused_referent, &unused_type,\n+\t\t\t      &failure_errno)) {\n+\t\tif (failure_errno == ENOENT) {\n+\t\t\terror(_(\"reference does not exist\"));\n+\t\t\tret = 2;\n+\t\t} else {\n+\t\t\terror(_(\"failed to look up reference: %s\"), strerror(failure_errno));\n+\t\t}\n+\n+\t\tgoto out;\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\tstrbuf_release(&unused_referent);\n+\treturn ret;\n+}\n+\n static int hash_callback(const struct option *opt, const char *arg, int unset)\n {\n \tstruct show_one_options *opts = opt->value;\n@@ -243,10 +279,11 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \tstruct exclude_existing_options exclude_existing_opts = {0};\n \tstruct patterns_options patterns_opts = {0};\n \tstruct show_one_options show_one_opts = {0};\n-\tint verify = 0;\n+\tint verify = 0, exists = 0;\n \tconst struct option show_ref_options[] = {\n \t\tOPT_BOOL(0, \"tags\", &patterns_opts.tags_only, N_(\"only show tags (can be combined with heads)\")),\n \t\tOPT_BOOL(0, \"heads\", &patterns_opts.heads_only, N_(\"only show heads (can be combined with tags)\")),\n+\t\tOPT_BOOL(0, \"exists\", &exists, N_(\"check for reference existence without resolving\")),\n \t\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n \t\t\t    \"requires exact ref path\")),\n \t\tOPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,\n@@ -272,13 +309,15 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n-\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n-\t\tdie(_(\"only one of --exclude-existing or --verify can be given\"));\n+\tif ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)\n+\t\tdie(_(\"only one of --exclude-existing, --exists or --verify can be given\"));\n \n \tif (exclude_existing_opts.enabled)\n \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\n \t\treturn cmd_show_ref__verify(&show_one_opts, argv);\n+\telse if (exists)\n+\t\treturn cmd_show_ref__exists(argv);\n \telse\n \t\treturn cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);\n }\ndiff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh\nindex 3a312c8b27c..17eba350ce5 100755\n--- a/t/t1403-show-ref.sh\n+++ b/t/t1403-show-ref.sh\n@@ -197,8 +197,73 @@ test_expect_success 'show-ref --verify with dangling ref' '\n '\n \n test_expect_success 'show-ref sub-modes are mutually exclusive' '\n+\tcat >expect <<-EOF &&\n+\tfatal: only one of --exclude-existing, --exists or --verify can be given\n+\tEOF\n+\n \ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n-\tgrep \"only one of --exclude-existing or --verify can be given\" err\n+\ttest_cmp expect err &&\n+\n+\ttest_must_fail git show-ref --verify --exists 2>err &&\n+\ttest_cmp expect err &&\n+\n+\ttest_must_fail git show-ref --exclude-existing --exists 2>err &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success '--exists with existing reference' '\n+\tgit show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+'\n+\n+test_expect_success '--exists with missing reference' '\n+\ttest_expect_code 2 git show-ref --exists refs/heads/does-not-exist\n+'\n+\n+test_expect_success '--exists does not use DWIM' '\n+\ttest_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&\n+\tgrep \"reference does not exist\" err\n+'\n+\n+test_expect_success '--exists with HEAD' '\n+\tgit show-ref --exists HEAD\n+'\n+\n+test_expect_success '--exists with bad reference name' '\n+\ttest_when_finished \"git update-ref -d refs/heads/bad...name\" &&\n+\tnew_oid=$(git rev-parse HEAD) &&\n+\ttest-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&\n+\tgit show-ref --exists refs/heads/bad...name\n+'\n+\n+test_expect_success '--exists with arbitrary symref' '\n+\ttest_when_finished \"git symbolic-ref -d refs/symref\" &&\n+\tgit symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&\n+\tgit show-ref --exists refs/symref\n+'\n+\n+test_expect_success '--exists with dangling symref' '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/dangling\" &&\n+\tgit symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&\n+\tgit show-ref --exists refs/heads/dangling\n+'\n+\n+test_expect_success '--exists with nonexistent object ID' '\n+\ttest-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&\n+\tgit show-ref --exists refs/heads/missing-oid\n+'\n+\n+test_expect_success '--exists with non-commit object' '\n+\ttree_oid=$(git rev-parse HEAD^{tree}) &&\n+\ttest-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&\n+\tgit show-ref --exists refs/heads/tree\n+'\n+\n+test_expect_success '--exists with directory fails with generic error' '\n+\tcat >expect <<-EOF &&\n+\terror: failed to look up reference: Is a directory\n+\tEOF\n+\ttest_expect_code 1 git show-ref --exists refs/heads 2>err &&\n+\ttest_cmp expect err\n '\n \n test_done\n-- \n2.42.0\n\n"},{"id":"483782","messageId":"a3a43d82e1f713c51d2ef7ee332dbf653cdbbf1d.1698152926.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH 12/12] t: use git-show-ref(1) to check for ref existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-24T13:11:29Z","receivedAt":"2023-10-24T13:11:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Convert tests that use `test_path_is_file` and `test_path_is_missing` to\ninstead use a set of helpers `test_ref_exists` and `test_ref_missing`.\nThese helpers are implemented via the newly introduced `git show-ref\n--exists` command. Thus, we can avoid intimate knowledge of how the ref\nbackend stores references on disk.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t1430-bad-ref-name.sh | 27 +++++++++++++-------\n t/t3200-branch.sh       | 33 ++++++++++++++-----------\n t/t5521-pull-options.sh |  4 +--\n t/t5605-clone-local.sh  |  2 +-\n t/test-lib-functions.sh | 55 +++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 94 insertions(+), 27 deletions(-)\n\ndiff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh\nindex ff1c967d550..7b7d6953c62 100755\n--- a/t/t1430-bad-ref-name.sh\n+++ b/t/t1430-bad-ref-name.sh\n@@ -205,8 +205,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref to broken name'\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref\" &&\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit update-ref --no-deref -d refs/heads/badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -216,8 +217,9 @@ test_expect_success 'branch -d can delete symref to broken name' '\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref\" &&\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit branch -d badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_i18ngrep \"Deleted branch badname (was refs/heads/broken\\.\\.\\.ref)\" output &&\n \ttest_must_be_empty error\n '\n@@ -225,8 +227,9 @@ test_expect_success 'branch -d can delete symref to broken name' '\n test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' '\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit update-ref --no-deref -d refs/heads/badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -234,8 +237,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref to brok\n test_expect_success 'branch -d can delete dangling symref to broken name' '\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit branch -d badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_i18ngrep \"Deleted branch badname (was refs/heads/broken\\.\\.\\.ref)\" output &&\n \ttest_must_be_empty error\n '\n@@ -245,8 +249,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref\" &&\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/broken...ref &&\n \tgit update-ref -d refs/heads/badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...ref &&\n+\ttest_ref_missing refs/heads/broken...ref &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -254,8 +259,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '\n test_expect_success 'update-ref --no-deref -d can delete symref with broken name' '\n \tprintf \"ref: refs/heads/main\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -263,8 +269,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref with broken name\n test_expect_success 'branch -d can delete symref with broken name' '\n \tprintf \"ref: refs/heads/main\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit branch -d broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_i18ngrep \"Deleted branch broken...symref (was refs/heads/main)\" output &&\n \ttest_must_be_empty error\n '\n@@ -272,8 +279,9 @@ test_expect_success 'branch -d can delete symref with broken name' '\n test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' '\n \tprintf \"ref: refs/heads/idonotexist\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -281,8 +289,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref with br\n test_expect_success 'branch -d can delete dangling symref with broken name' '\n \tprintf \"ref: refs/heads/idonotexist\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit branch -d broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_i18ngrep \"Deleted branch broken...symref (was refs/heads/idonotexist)\" output &&\n \ttest_must_be_empty error\n '\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 080e4f24a6e..bde4f1485b7 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -25,7 +25,7 @@ test_expect_success 'prepare a trivial repository' '\n \n test_expect_success 'git branch --help should not have created a bogus branch' '\n \ttest_might_fail git branch --man --help </dev/null >/dev/null 2>&1 &&\n-\ttest_path_is_missing .git/refs/heads/--help\n+\ttest_ref_missing refs/heads/--help\n '\n \n test_expect_success 'branch -h in broken repository' '\n@@ -40,7 +40,8 @@ test_expect_success 'branch -h in broken repository' '\n '\n \n test_expect_success 'git branch abc should create a branch' '\n-\tgit branch abc && test_path_is_file .git/refs/heads/abc\n+\tgit branch abc &&\n+\ttest_ref_exists refs/heads/abc\n '\n \n test_expect_success 'git branch abc should fail when abc exists' '\n@@ -61,11 +62,13 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '\n '\n \n test_expect_success 'git branch a/b/c should create a branch' '\n-\tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n+\tgit branch a/b/c &&\n+\ttest_ref_exists refs/heads/a/b/c\n '\n \n test_expect_success 'git branch mb main... should create a branch' '\n-\tgit branch mb main... && test_path_is_file .git/refs/heads/mb\n+\tgit branch mb main... &&\n+\ttest_ref_exists refs/heads/mb\n '\n \n test_expect_success 'git branch HEAD should fail' '\n@@ -78,14 +81,14 @@ EOF\n test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' '\n \tGIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n \tgit -c core.logallrefupdates=false branch --create-reflog d/e/f &&\n-\ttest_path_is_file .git/refs/heads/d/e/f &&\n+\ttest_ref_exists refs/heads/d/e/f &&\n \ttest_path_is_file .git/logs/refs/heads/d/e/f &&\n \ttest_cmp expect .git/logs/refs/heads/d/e/f\n '\n \n test_expect_success 'git branch -d d/e/f should delete a branch and a log' '\n \tgit branch -d d/e/f &&\n-\ttest_path_is_missing .git/refs/heads/d/e/f &&\n+\ttest_ref_missing refs/heads/d/e/f &&\n \ttest_must_fail git reflog exists refs/heads/d/e/f\n '\n \n@@ -213,7 +216,7 @@ test_expect_success 'git branch -M should leave orphaned HEAD alone' '\n \t\ttest_commit initial &&\n \t\tgit checkout --orphan lonely &&\n \t\tgrep lonely .git/HEAD &&\n-\t\ttest_path_is_missing .git/refs/head/lonely &&\n+\t\ttest_ref_missing refs/head/lonely &&\n \t\tgit branch -M main mistress &&\n \t\tgrep lonely .git/HEAD\n \t)\n@@ -799,8 +802,8 @@ test_expect_success 'deleting a symref' '\n \tgit symbolic-ref refs/heads/symref refs/heads/target &&\n \techo \"Deleted branch symref (was refs/heads/target).\" >expect &&\n \tgit branch -d symref >actual &&\n-\ttest_path_is_file .git/refs/heads/target &&\n-\ttest_path_is_missing .git/refs/heads/symref &&\n+\ttest_ref_exists refs/heads/target &&\n+\ttest_ref_missing refs/heads/symref &&\n \ttest_cmp expect actual\n '\n \n@@ -809,16 +812,16 @@ test_expect_success 'deleting a dangling symref' '\n \ttest_path_is_file .git/refs/heads/dangling-symref &&\n \techo \"Deleted branch dangling-symref (was nowhere).\" >expect &&\n \tgit branch -d dangling-symref >actual &&\n-\ttest_path_is_missing .git/refs/heads/dangling-symref &&\n+\ttest_ref_missing refs/heads/dangling-symref &&\n \ttest_cmp expect actual\n '\n \n test_expect_success 'deleting a self-referential symref' '\n \tgit symbolic-ref refs/heads/self-reference refs/heads/self-reference &&\n-\ttest_path_is_file .git/refs/heads/self-reference &&\n+\ttest_ref_exists refs/heads/self-reference &&\n \techo \"Deleted branch self-reference (was refs/heads/self-reference).\" >expect &&\n \tgit branch -d self-reference >actual &&\n-\ttest_path_is_missing .git/refs/heads/self-reference &&\n+\ttest_ref_missing refs/heads/self-reference &&\n \ttest_cmp expect actual\n '\n \n@@ -826,8 +829,8 @@ test_expect_success 'renaming a symref is not allowed' '\n \tgit symbolic-ref refs/heads/topic refs/heads/main &&\n \ttest_must_fail git branch -m topic new-topic &&\n \tgit symbolic-ref refs/heads/topic &&\n-\ttest_path_is_file .git/refs/heads/main &&\n-\ttest_path_is_missing .git/refs/heads/new-topic\n+\ttest_ref_exists refs/heads/main &&\n+\ttest_ref_missing refs/heads/new-topic\n '\n \n test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '\n@@ -1142,7 +1145,7 @@ EOF\n test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '\n \tGIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n \tgit checkout -b g/h/i -l main &&\n-\ttest_path_is_file .git/refs/heads/g/h/i &&\n+\ttest_ref_exists refs/heads/g/h/i &&\n \ttest_path_is_file .git/logs/refs/heads/g/h/i &&\n \ttest_cmp expect .git/logs/refs/heads/g/h/i\n '\ndiff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh\nindex 079b2f2536e..3681859f983 100755\n--- a/t/t5521-pull-options.sh\n+++ b/t/t5521-pull-options.sh\n@@ -143,7 +143,7 @@ test_expect_success 'git pull --dry-run' '\n \t\tcd clonedry &&\n \t\tgit pull --dry-run ../parent &&\n \t\ttest_path_is_missing .git/FETCH_HEAD &&\n-\t\ttest_path_is_missing .git/refs/heads/main &&\n+\t\ttest_ref_missing refs/heads/main &&\n \t\ttest_path_is_missing .git/index &&\n \t\ttest_path_is_missing file\n \t)\n@@ -157,7 +157,7 @@ test_expect_success 'git pull --all --dry-run' '\n \t\tgit remote add origin ../parent &&\n \t\tgit pull --all --dry-run &&\n \t\ttest_path_is_missing .git/FETCH_HEAD &&\n-\t\ttest_path_is_missing .git/refs/remotes/origin/main &&\n+\t\ttest_ref_missing refs/remotes/origin/main &&\n \t\ttest_path_is_missing .git/index &&\n \t\ttest_path_is_missing file\n \t)\ndiff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh\nindex 1d7b1abda1a..946c5751885 100755\n--- a/t/t5605-clone-local.sh\n+++ b/t/t5605-clone-local.sh\n@@ -69,7 +69,7 @@ test_expect_success 'local clone of repo with nonexistent ref in HEAD' '\n \tgit clone a d &&\n \t(cd d &&\n \tgit fetch &&\n-\ttest ! -e .git/refs/remotes/origin/HEAD)\n+\ttest_ref_missing refs/remotes/origin/HEAD)\n '\n \n test_expect_success 'bundle clone without .bundle suffix' '\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 2f8868caa17..56b33536ed1 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -251,6 +251,61 @@ debug () {\n \tdone\n }\n \n+# Usage: test_ref_exists [options] <ref>\n+#\n+#   -C <dir>:\n+#      Run all git commands in directory <dir>\n+#\n+# This helper function checks whether a reference exists. Symrefs or object IDs\n+# will not be resolved. Can be used to check references with bad names.\n+test_ref_exists () {\n+\tlocal indir=\n+\n+\twhile test $# != 0\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t-C)\n+\t\t\tindir=\"$2\"\n+\t\t\tshift\n+\t\t\t;;\n+\t\t*)\n+\t\t\tbreak\n+\t\t\t;;\n+\t\tesac\n+\t\tshift\n+\tdone &&\n+\n+\tindir=${indir:+\"$indir\"/} &&\n+\n+\tif test \"$#\" != 1\n+\tthen\n+\t\tBUG \"expected exactly one reference\"\n+\tfi &&\n+\n+\tgit ${indir:+ -C \"$indir\"} show-ref --exists \"$1\"\n+}\n+\n+# Behaves the same as test_ref_exists, except that it checks for the absence of\n+# a reference. This is preferable to `! test_ref_exists` as this function is\n+# able to distinguish actually-missing references from other, generic errors.\n+test_ref_missing () {\n+\ttest_ref_exists \"$@\"\n+\tcase \"$?\" in\n+\t2)\n+\t\t# This is the good case.\n+\t\treturn 0\n+\t\t;;\n+\t0)\n+\t\techo >&4 \"test_ref_missing: reference exists\"\n+\t\treturn 1\n+\t\t;;\n+\t*)\n+\t\techo >&4 \"test_ref_missing: generic error\"\n+\t\treturn 1\n+\t\t;;\n+\tesac\n+}\n+\n # Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]\n #   -C <dir>:\n #\tRun all git commands in directory <dir>\n-- \n2.42.0\n\n"},{"id":"483801","messageId":"CAPig+cQ2MgR_1Z+zscC+Acy8PVe4ZNLtMVDpCSK6SDm+4e968g@mail.gmail.com","threadId":"60421","inReplyTo":"7e6ab5dee230dcb66cb8adfe4a8114a06c805802.1698152926.git.ps@pks.im","subject":"Re: [PATCH 02/12] builtin/show-ref: split up different subcommands","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-10-24T17:55:38Z","receivedAt":"2023-10-24T17:55:51Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Oct 24, 2023 at 9:10 AM Patrick Steinhardt <ps@pks.im> wrote:\n> While not immediately obvious, git-show-ref(1) actually implements three\n> different subcommands:\n>\n>     - `git show-ref <patterns>` can be used to list references that\n>       match a specific pattern.\n>\n>     - `git show-ref --verify <refs>` can be used to list references.\n>       These are _not_ patterns.\n>\n>     - `git show-ref --exclude-existing` can be used as a filter that\n>       reads references from standard input, performing some conversions\n>       on each of them.\n>\n> Let's make this more explicit in the code by splitting up the three\n> subcommands into separate functions. This also allows us to address the\n> confusingly named `patterns` variable, which may hold either patterns or\n> reference names.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n> @@ -142,6 +142,53 @@ static int exclude_existing(const char *match)\n> +static int cmd_show_ref__verify(const char **refs)\n> +{\n> +       if (!refs || !*refs)\n> +               die(\"--verify requires a reference\");\n> +\n> +       while (*refs) {\n> +               struct object_id oid;\n> +\n> +               if ((starts_with(*refs, \"refs/\") || !strcmp(*refs, \"HEAD\")) &&\n> +                   !read_ref(*refs, &oid)) {\n> +                       show_one(*refs, &oid);\n> +               }\n> +               else if (!quiet)\n> +                       die(\"'%s' - not a valid ref\", *refs);\n> +               else\n> +                       return 1;\n> +               refs++;\n> +       }\n\nA couple style-nits here caught my attention...\n\n- \"}\" and \"else\" should be cuddled: `} else if`\n\n- coding guidelines these days want braces on all branches if any\nbranch needs them\n\nHowever, since this code is merely being relocated from elsewhere in\nthis file and since these style-nits were already present, moving the\ncode verbatim without correcting the style problems is more\nreviewer-friendly. Okay.\n\n> +       return 0;\n> +}\n> +\n> +static int cmd_show_ref__patterns(const char **patterns)\n> +{\n> +       struct show_ref_data show_ref_data = {\n> +               .patterns = (patterns && *patterns) ? patterns : NULL,\n> +       };\n\nAre we allowing non-constant initializers in the codebase? If not,\nthis should probably initialize .patterns to NULL and then\nconditionally assign `patterns` separately in code below the\ninitializer.\n\n> +       if (show_head)\n> +               head_ref(show_ref, &show_ref_data);\n> +       if (heads_only || tags_only) {\n> +               if (heads_only)\n> +                       for_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n> +               if (tags_only)\n> +                       for_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n> +       } else {\n> +               for_each_ref(show_ref, &show_ref_data);\n> +       }\n> +       if (!found_match) {\n> +               if (verify && !quiet)\n> +                       die(\"No match\");\n> +               return 1;\n> +       }\n> +\n> +       return 0;\n> +}\n"},{"id":"483802","messageId":"CAPig+cQH4cV1KnwUWjFz-dRn1dQ9OWdmwaUpTMSjYwJGtnX5Gg@mail.gmail.com","threadId":"60421","inReplyTo":"29c0c0c6c9728f286efd0b78996f9a1394957862.1698152926.git.ps@pks.im","subject":"Re: [PATCH 04/12] builtin/show-ref: fix dead code when passing patterns","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-10-24T18:02:17Z","receivedAt":"2023-10-24T18:02:30Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Oct 24, 2023 at 9:10 AM Patrick Steinhardt <ps@pks.im> wrote:\n> When passing patterns to `git show-ref` we have some code that will\n> cause us to die of `verify && !quiet` is true. But because `verify`\n\ns/of/if/\n\n> indicates a different subcommand of git-show-ref(1) that causes us to\n> execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the\n> condition cannot ever be true.\n>\n> Let's remove this dead code.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n"},{"id":"483804","messageId":"CAPig+cQrD6uh65UzaKbwryv=wcdymKrjqXsAKgrKHYpQNWqSYQ@mail.gmail.com","threadId":"60421","inReplyTo":"8d0b0b5700c7ffed6b3a74760d0d9155b404bb4f.1698152926.git.ps@pks.im","subject":"Re: [PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-10-24T18:48:14Z","receivedAt":"2023-10-24T18:48:27Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:\n> It's not immediately obvious options which options are applicable to\n> what subcommand ni git-show-ref(1) because all options exist as global\n\ns/ni/in/\n\n> state. This can easily cause confusion for the reader.\n>\n> Refactor options for the `--exclude-existing` subcommand to be contained\n> in a separate structure. This structure is stored on the stack and\n> passed down as required. Consequentially, it clearly delimits the scope\n\ns/Consequentially/Consequently/\n\n> of those options and requires the reader to worry less about global\n> state.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n> diff --git a/builtin/show-ref.c b/builtin/show-ref.c\n> @@ -95,6 +94,11 @@ static int add_existing(const char *refname,\n> +struct exclude_existing_options {\n> +       int enabled;\n> +       const char *pattern;\n> +};\n\nDo we need this `enabled` flag? Can't the same be achieved by checking\nwhether `pattern` is NULL or not (see below)?\n\n> @@ -104,11 +108,11 @@ static int add_existing(const char *refname,\n> -static int cmd_show_ref__exclude_existing(const char *match)\n> +static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)\n\nSince you're renaming `match` to `opts->pattern`...\n\n>  {\n> -       int matchlen = match ? strlen(match) : 0;\n> +       int matchlen = opts->pattern ? strlen(opts->pattern) : 0;\n\n... and since you're touching this line anyway, maybe it makes sense\nto rename `matchlen` to `patternlen`?\n\n> @@ -124,11 +128,11 @@ static int cmd_show_ref__exclude_existing(const char *match)\n> -                       if (strncmp(ref, match, matchlen))\n> +                       if (strncmp(ref, opts->pattern, matchlen))\n\nEspecially since, as shown in this context, `matchlen` is really the\nlength of the _pattern_, not the length of the resulting _match_.\n\n> @@ -200,44 +204,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)\n>  int cmd_show_ref(int argc, const char **argv, const char *prefix)\n>  {\n>         [...]\n> -       if (exclude_arg)\n> -               return cmd_show_ref__exclude_existing(exclude_existing_arg);\n> +       if (exclude_existing_opts.enabled)\n> +               return cmd_show_ref__exclude_existing(&exclude_existing_opts);\n\n(continued from above) Can't this be handled without a separate `enabled` flag?\n\n    if (exclude_existing_opts.pattern)\n        ...\n"},{"id":"483807","messageId":"xmqqttqf3k5a.fsf@gitster.g","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-10-24T19:17:21Z","receivedAt":"2023-10-24T19:17:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> this patch series introduces a new `--exists` mode to git-show-ref(1) to\n> explicitly check for the existence of a reference, only.\n\nI agree that show-ref would be the best place for this feature (not\nrev-parse, which is already a kitchen sink).  After all, the command\nwas designed for validating refs in 358ddb62 (Add \"git show-ref\"\nbuiltin command, 2006-09-15).\n\nThanks.  Hopefully I can take a look before I go offline.\n\n"},{"id":"483808","messageId":"CAPig+cSrp7vZuy7D_ENHKZKZzF4OSmCtfYNHPGMtS1Hj6gArDw@mail.gmail.com","threadId":"60421","inReplyTo":"d0a991cf4f892e73e4fd62ef3fdae3fa73277321.1698152926.git.ps@pks.im","subject":"Re: [PATCH 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-10-24T19:25:52Z","receivedAt":"2023-10-24T19:26:06Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:\n> The git-show-ref(1) command has three different modes, of which one is\n> implicit and the other two can be chosen explicitly by passing a flag.\n> But while these modes are standalone and cause us to execute completely\n> separate code paths, we gladly accept the case where a user asks for\n> both `--exclude-existing` and `--verify` at the same time even though it\n> is not obvious what will happen. Spoiler: we ignore `--verify` and\n> execute the `--exclude-existing` mode.\n>\n> Let's explicitly detect this invalid usage and die in case both modes\n> were requested.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n> diff --git a/builtin/show-ref.c b/builtin/show-ref.c\n> @@ -269,6 +269,9 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n> +       if ((!!exclude_existing_opts.enabled + !!verify) > 1)\n> +               die(_(\"only one of --exclude-existing or --verify can be given\"));\n\nSomewhat recently, work was done to normalize this sort of message.\nThe result was to instead use the phrasing \"options '%s' and '%s'\ncannot be used together\". See, for instance, 43ea635c35 (i18n:\nrefactor \"foo and bar are mutually exclusive\", 2022-01-05).\n"},{"id":"483809","messageId":"CAPig+cQ=VhRm40oW=TQYzy2NXKNJm4QVQhtw3FAKsFRn12qYLA@mail.gmail.com","threadId":"60421","inReplyTo":"adcfa7a6a9d8fb6f915faf77df52362544cd590e.1698152926.git.ps@pks.im","subject":"Re: [PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-10-24T19:39:28Z","receivedAt":"2023-10-24T19:39:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:\n> The synopsis treats the `--verify` and the implicit mode the same. They\n> are slightly different though:\n>\n>     - They accept different sets of flags.\n>\n>     - The implicit mode accepts patterns while the `--verify` mode\n>       accepts references.\n>\n> Split up the synopsis for these two modes such that we can disambiguate\n> those differences.\n\nGood. When reading [2/12], my immediate thought was that such a\ndocumentation change was needed.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n> diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\n> @@ -8,9 +8,12 @@ git-show-ref - List references in a local repository\n>  SYNOPSIS\n> -'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]\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>]\n\nWhat does it mean to request \"quiet\" for the plain `git show-ref`\nmode? That seems pointless and counterintuitive. Even though this mode\nmay accept --quiet as a quirk of implementation, we probably shouldn't\nbe promoting its use in the documentation. Moreover, the blurb for\n--quiet later in the document:\n\n   Do not print any results to stdout. When combined with --verify,\n   this can be used to silently check if a reference exists.\n\nshould probably be rephrased since it currently implies that it may be\nused with modes other than --verify, but that's not really the case\n(implementation quirks aside).\n\nThis also raises the question as to whether an interlock should be\nadded to disallow --quiet with plain `git show-ref`, much like the\ninterlock preventing --exclude-existing and --verify from being used\ntogether. Ideally, such an interlock ought to be added, but I wouldn't\nbe surprised to learn that doing so would break someone's existing\ntooling which insensibly uses --quiet with plain `git show-ref`.\n"},{"id":"483819","messageId":"CAPig+cRTOMie0rUf=Mhbo9e2EXf-_2kQyMeqpB9OCRB1MZZ1rw@mail.gmail.com","threadId":"60421","inReplyTo":"2f876e61dd36a8887a1286bb8db9fb6577c55c9b.1698152926.git.ps@pks.im","subject":"Re: [PATCH 11/12] builtin/show-ref: add new mode to check for reference existence","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2023-10-24T21:01:55Z","receivedAt":"2023-10-24T21:02:09Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:\n> While we have multiple ways to show the value of a given reference, we\n> do not have any way to check whether a reference exists at all. While\n> commands like git-rev-parse(1) or git-show-ref(1) can be used to check\n> for reference existence in case the reference resolves to something\n> sane, neither of them can be used to check for existence in some other\n> scenarios where the reference does not resolve cleanly:\n>\n>     - References which have an invalid name cannot be resolved.\n>\n>     - References to nonexistent objects cannot be resolved.\n>\n>     - Dangling symrefs can be resolved via git-symbolic-ref(1), but this\n>       requires the caller to special case existence checks depending on\n>       whteher or not a reference is symbolic or direct.\n\ns/whteher/whether/\n\n> Furthermore, git-rev-list(1) and other commands do not let the caller\n> distinguish easily between an actually missing reference and a generic\n> error.\n>\n> Taken together, this gseems like sufficient motivation to introduce a\n\ns/gseems/seems/\n\n> separate plumbing command to explicitly check for the existence of a\n> reference without trying to resolve its contents.\n>\n> This new command comes in the form of `git show-ref --exists`. This\n> new mode will exit successfully when the reference exists, with a\n> specific error code of 2 when it does not exist, or with 1 when there\n> has been a generic error.\n>\n> Note that the only way to properly implement this command is by using\n> the internal `refs_read_raw_ref()` function. While the public function\n> `refs_resolve_ref_unsafe()` can be made to behave in the same way by\n> passing various flags, it does not provide any way to obtain the errno\n> with which the reference backend failed when reading the reference. As\n> such, it becomes impossible for us to distinguish generic errors from\n> the explicit case where the reference wasn't found.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n> diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\n> @@ -65,6 +70,12 @@ OPTIONS\n> +--exists::\n> +\n> +       Check whether the given reference exists. Returns an error code of 0 if\n\nWe probably want to call this \"exit code\" rather than \"error code\"\nsince the latter is unnecessarily scary sounding for the success case\n(when the ref does exit).\n\n> +       it does, 2 if it is missing, and 128 in case looking up the reference\n> +       failed with an error other than the reference being missing.\n\nThe commit message says it returns 1 for a generic error, but this\ninconsistently says it returns 128 for that case. The actual\nimplementation returns 1.\n\n> diff --git a/builtin/show-ref.c b/builtin/show-ref.c\n> @@ -214,6 +215,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,\n> +static int cmd_show_ref__exists(const char **refs)\n> +{\n> +       struct strbuf unused_referent = STRBUF_INIT;\n> +       struct object_id unused_oid;\n> +       unsigned int unused_type;\n> +       int failure_errno = 0;\n> +       const char *ref;\n> +       int ret = 1;\n> +\n> +       if (!refs || !*refs)\n> +               die(\"--exists requires a reference\");\n> +       ref = *refs++;\n> +       if (*refs)\n> +               die(\"--exists requires exactly one reference\");\n> +\n> +       if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,\n> +                             &unused_oid, &unused_referent, &unused_type,\n> +                             &failure_errno)) {\n> +               if (failure_errno == ENOENT) {\n> +                       error(_(\"reference does not exist\"));\n\nThe documentation doesn't mention this printing any output, and indeed\none would intuitively expect a boolean-like operation to not produce\nany printed output since its exit code indicates the result (except,\nof course, in the case of a real error).\n\n> +                       ret = 2;\n> +               } else {\n> +                       error(_(\"failed to look up reference: %s\"), strerror(failure_errno));\n\nOr use error_errno():\n\n    errno = failure_errno;\n    error_errno(_(\"failed to look up reference: %s\"));\n\n> +               }\n> +\n> +               goto out;\n> +       }\n> +\n> +       ret = 0;\n> +\n> +out:\n> +       strbuf_release(&unused_referent);\n> +       return ret;\n> +}\n\nIt's a bit odd having `ret` be 1 at the outset rather than 0, thus\nmaking the logic a bit more difficult to reason about. I would have\nexpected it to be organized like this:\n\n    int ret = 0;\n    if (refs_read_raw_ref(...)) {\n         if (failure_errno == ENOENT) {\n            ret = 2;\n        } else {\n            ret = 1;\n            errno = failure_errno;\n            error_errno(_(\"failed to look up reference: %s\"));\n       }\n    }\n    strbuf_release(...);\n    return ret;\n\n> @@ -272,13 +309,15 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n> +       if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)\n> +               die(_(\"only one of --exclude-existing, --exists or --verify can be given\"));\n\nWhen reviewing an earlier patch in this series, I forgot to mention\nthat we can simplify the life of translators by using placeholders:\n\n    die(_(\"options '%s', '%s' or '%s' cannot be used together\"),\n        \"--exclude-existing\", \"--exists\", \"--verify\");\n\nwhich ensures that they don't translate the literal option names, and\nmakes it possible to reuse the translated message in multiple\nlocations (since it doesn't mention hard-coded option names).\n"},{"id":"483850","messageId":"ZTkBFD7Iffe2bTOE@tanuki","threadId":"60421","inReplyTo":"CAPig+cQrD6uh65UzaKbwryv=wcdymKrjqXsAKgrKHYpQNWqSYQ@mail.gmail.com","subject":"Re: [PATCH 05/12] builtin/show-ref: refactor `--exclude-existing` options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-25T11:50:44Z","receivedAt":"2023-10-25T11:50:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 24, 2023 at 02:48:14PM -0400, Eric Sunshine wrote:\n> On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > It's not immediately obvious options which options are applicable to\n> > what subcommand ni git-show-ref(1) because all options exist as global\n> \n> s/ni/in/\n> \n> > state. This can easily cause confusion for the reader.\n> >\n> > Refactor options for the `--exclude-existing` subcommand to be contained\n> > in a separate structure. This structure is stored on the stack and\n> > passed down as required. Consequentially, it clearly delimits the scope\n> \n> s/Consequentially/Consequently/\n> \n> > of those options and requires the reader to worry less about global\n> > state.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> > diff --git a/builtin/show-ref.c b/builtin/show-ref.c\n> > @@ -95,6 +94,11 @@ static int add_existing(const char *refname,\n> > +struct exclude_existing_options {\n> > +       int enabled;\n> > +       const char *pattern;\n> > +};\n> \n> Do we need this `enabled` flag? Can't the same be achieved by checking\n> whether `pattern` is NULL or not (see below)?\n\nYeah, we do. It's perfectly valid to pass `--exclude-existing` without\nthe optional pattern argument. We still want to use this mode in that\ncase, but don't populate the pattern.\n\nAn alternative would be to assign something like a sentinel value in\nhere. But I'd think that it's clearer to instead have an explicit\nseparate field for this.\n\n> > @@ -104,11 +108,11 @@ static int add_existing(const char *refname,\n> > -static int cmd_show_ref__exclude_existing(const char *match)\n> > +static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)\n> \n> Since you're renaming `match` to `opts->pattern`...\n> \n> >  {\n> > -       int matchlen = match ? strlen(match) : 0;\n> > +       int matchlen = opts->pattern ? strlen(opts->pattern) : 0;\n> \n> ... and since you're touching this line anyway, maybe it makes sense\n> to rename `matchlen` to `patternlen`?\n\nYes, let's do it. It's been more of an oversight rather than\nintentional to keep the previous name.\n\n> > @@ -124,11 +128,11 @@ static int cmd_show_ref__exclude_existing(const char *match)\n> > -                       if (strncmp(ref, match, matchlen))\n> > +                       if (strncmp(ref, opts->pattern, matchlen))\n> \n> Especially since, as shown in this context, `matchlen` is really the\n> length of the _pattern_, not the length of the resulting _match_.\n> \n> > @@ -200,44 +204,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)\n> >  int cmd_show_ref(int argc, const char **argv, const char *prefix)\n> >  {\n> >         [...]\n> > -       if (exclude_arg)\n> > -               return cmd_show_ref__exclude_existing(exclude_existing_arg);\n> > +       if (exclude_existing_opts.enabled)\n> > +               return cmd_show_ref__exclude_existing(&exclude_existing_opts);\n> \n> (continued from above) Can't this be handled without a separate `enabled` flag?\n> \n>     if (exclude_existing_opts.pattern)\n>         ...\n\nSee the explanation above.\n\nPatrick\n"},{"id":"483851","messageId":"ZTkBGob0IU8BE-_h@tanuki","threadId":"60421","inReplyTo":"CAPig+cQ=VhRm40oW=TQYzy2NXKNJm4QVQhtw3FAKsFRn12qYLA@mail.gmail.com","subject":"Re: [PATCH 10/12] builtin/show-ref: explicitly spell out different modes in synopsis","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-25T11:50:50Z","receivedAt":"2023-10-25T11:50:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 24, 2023 at 03:39:28PM -0400, Eric Sunshine wrote:\n> On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > The synopsis treats the `--verify` and the implicit mode the same. They\n> > are slightly different though:\n> >\n> >     - They accept different sets of flags.\n> >\n> >     - The implicit mode accepts patterns while the `--verify` mode\n> >       accepts references.\n> >\n> > Split up the synopsis for these two modes such that we can disambiguate\n> > those differences.\n> \n> Good. When reading [2/12], my immediate thought was that such a\n> documentation change was needed.\n> \n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> > diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\n> > @@ -8,9 +8,12 @@ git-show-ref - List references in a local repository\n> >  SYNOPSIS\n> > -'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]\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>]\n> \n> What does it mean to request \"quiet\" for the plain `git show-ref`\n> mode? That seems pointless and counterintuitive. Even though this mode\n> may accept --quiet as a quirk of implementation, we probably shouldn't\n> be promoting its use in the documentation. Moreover, the blurb for\n> --quiet later in the document:\n> \n>    Do not print any results to stdout. When combined with --verify,\n>    this can be used to silently check if a reference exists.\n> \n> should probably be rephrased since it currently implies that it may be\n> used with modes other than --verify, but that's not really the case\n> (implementation quirks aside).\n\nGood point indeed, will change.\n\n> This also raises the question as to whether an interlock should be\n> added to disallow --quiet with plain `git show-ref`, much like the\n> interlock preventing --exclude-existing and --verify from being used\n> together. Ideally, such an interlock ought to be added, but I wouldn't\n> be surprised to learn that doing so would break someone's existing\n> tooling which insensibly uses --quiet with plain `git show-ref`.\n\nYeah, I also wouldn't go as far as this. The mutual exclusiveness for\n`--exclude-existing` and `--verify` makes sense in my opinion because\nthe result is extremely misleading and may cause users to assume that\nthe wrong thing has happened.\n\nI don't think that's necessarily true for `--quiet`. It may not make a\nlot of sense to specify `--quiet` here, but it also doesn't quietly do\nthe wrong thing as in the other case.\n\nFurthermore, we also don't have any interlocks for incompatible other\nflags, either: git-show-ref(1) won't complain when passing any of the\nmode-specific flags to the other modes. If we want to fix that I'd\nrather defer it to a follow up patch series though. And as you said, I\nwould almost certainly expect there to be some kind of fallout if we did\nthis change.\n\nPatrick\n"},{"id":"483852","messageId":"ZTkBIVODFzX8Jr5P@tanuki","threadId":"60421","inReplyTo":"CAPig+cRTOMie0rUf=Mhbo9e2EXf-_2kQyMeqpB9OCRB1MZZ1rw@mail.gmail.com","subject":"Re: [PATCH 11/12] builtin/show-ref: add new mode to check for reference existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-25T11:50:57Z","receivedAt":"2023-10-25T11:51:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 24, 2023 at 05:01:55PM -0400, Eric Sunshine wrote:\n> On Tue, Oct 24, 2023 at 9:11 AM Patrick Steinhardt <ps@pks.im> wrote:\n> > While we have multiple ways to show the value of a given reference, we\n> > do not have any way to check whether a reference exists at all. While\n> > commands like git-rev-parse(1) or git-show-ref(1) can be used to check\n> > for reference existence in case the reference resolves to something\n> > sane, neither of them can be used to check for existence in some other\n> > scenarios where the reference does not resolve cleanly:\n> >\n> >     - References which have an invalid name cannot be resolved.\n> >\n> >     - References to nonexistent objects cannot be resolved.\n> >\n> >     - Dangling symrefs can be resolved via git-symbolic-ref(1), but this\n> >       requires the caller to special case existence checks depending on\n> >       whteher or not a reference is symbolic or direct.\n> \n> s/whteher/whether/\n> \n> > Furthermore, git-rev-list(1) and other commands do not let the caller\n> > distinguish easily between an actually missing reference and a generic\n> > error.\n> >\n> > Taken together, this gseems like sufficient motivation to introduce a\n> \n> s/gseems/seems/\n> \n> > separate plumbing command to explicitly check for the existence of a\n> > reference without trying to resolve its contents.\n> >\n> > This new command comes in the form of `git show-ref --exists`. This\n> > new mode will exit successfully when the reference exists, with a\n> > specific error code of 2 when it does not exist, or with 1 when there\n> > has been a generic error.\n> >\n> > Note that the only way to properly implement this command is by using\n> > the internal `refs_read_raw_ref()` function. While the public function\n> > `refs_resolve_ref_unsafe()` can be made to behave in the same way by\n> > passing various flags, it does not provide any way to obtain the errno\n> > with which the reference backend failed when reading the reference. As\n> > such, it becomes impossible for us to distinguish generic errors from\n> > the explicit case where the reference wasn't found.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> > diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\n> > @@ -65,6 +70,12 @@ OPTIONS\n> > +--exists::\n> > +\n> > +       Check whether the given reference exists. Returns an error code of 0 if\n> \n> We probably want to call this \"exit code\" rather than \"error code\"\n> since the latter is unnecessarily scary sounding for the success case\n> (when the ref does exit).\n\nI was trying to stick to the preexisting style of \"error code\" in this\nmanual page. But I think I agree with your argument that we also call it\nan error code in the successful case, which is misleading.\n\n> > +       it does, 2 if it is missing, and 128 in case looking up the reference\n> > +       failed with an error other than the reference being missing.\n> \n> The commit message says it returns 1 for a generic error, but this\n> inconsistently says it returns 128 for that case. The actual\n> implementation returns 1.\n\nGood catch, fixed.\n\n> > diff --git a/builtin/show-ref.c b/builtin/show-ref.c\n> > @@ -214,6 +215,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,\n> > +static int cmd_show_ref__exists(const char **refs)\n> > +{\n> > +       struct strbuf unused_referent = STRBUF_INIT;\n> > +       struct object_id unused_oid;\n> > +       unsigned int unused_type;\n> > +       int failure_errno = 0;\n> > +       const char *ref;\n> > +       int ret = 1;\n> > +\n> > +       if (!refs || !*refs)\n> > +               die(\"--exists requires a reference\");\n> > +       ref = *refs++;\n> > +       if (*refs)\n> > +               die(\"--exists requires exactly one reference\");\n> > +\n> > +       if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,\n> > +                             &unused_oid, &unused_referent, &unused_type,\n> > +                             &failure_errno)) {\n> > +               if (failure_errno == ENOENT) {\n> > +                       error(_(\"reference does not exist\"));\n> \n> The documentation doesn't mention this printing any output, and indeed\n> one would intuitively expect a boolean-like operation to not produce\n> any printed output since its exit code indicates the result (except,\n> of course, in the case of a real error).\n\nI'm inclined to leave this as-is. While the exit code should be\nsufficient, I think it's rather easy to wonder whether it actually did\nanything at all and why it failed in more interactive use cases. Not\nthat I think these will necessarily exist.\n\nI also don't think it's going to hurt to print this error. If it ever\ndoes start to become a problem we might end up honoring the \"--quiet\"\nflag to squelch this case.\n\n> > +                       ret = 2;\n> > +               } else {\n> > +                       error(_(\"failed to look up reference: %s\"), strerror(failure_errno));\n> \n> Or use error_errno():\n> \n>     errno = failure_errno;\n>     error_errno(_(\"failed to look up reference: %s\"));\n\nAh, good suggestion.\n\n> > +               }\n> > +\n> > +               goto out;\n> > +       }\n> > +\n> > +       ret = 0;\n> > +\n> > +out:\n> > +       strbuf_release(&unused_referent);\n> > +       return ret;\n> > +}\n> \n> It's a bit odd having `ret` be 1 at the outset rather than 0, thus\n> making the logic a bit more difficult to reason about. I would have\n> expected it to be organized like this:\n> \n>     int ret = 0;\n>     if (refs_read_raw_ref(...)) {\n>          if (failure_errno == ENOENT) {\n>             ret = 2;\n>         } else {\n>             ret = 1;\n>             errno = failure_errno;\n>             error_errno(_(\"failed to look up reference: %s\"));\n>        }\n>     }\n>     strbuf_release(...);\n>     return ret;\n\nFair enough. I've seen both styles used in our codebase, but ultimately\ndon't care much which of either we use here. Will adapt.\n\n> > @@ -272,13 +309,15 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n> > +       if ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)\n> > +               die(_(\"only one of --exclude-existing, --exists or --verify can be given\"));\n> \n> When reviewing an earlier patch in this series, I forgot to mention\n> that we can simplify the life of translators by using placeholders:\n> \n>     die(_(\"options '%s', '%s' or '%s' cannot be used together\"),\n>         \"--exclude-existing\", \"--exists\", \"--verify\");\n> \n> which ensures that they don't translate the literal option names, and\n> makes it possible to reuse the translated message in multiple\n> locations (since it doesn't mention hard-coded option names).\n\nDone.\n\nThanks for your review, highly appreciated! I'll wait until tomorrow for\nadditional feedback and then send out v2.\n\nPatrick\n"},{"id":"483855","messageId":"CAFQ2z_PqNsz+zycSxz=q2cUVOpJS-AEjwHxEM-fiafxd3dxc9g@mail.gmail.com","threadId":"60421","inReplyTo":"xmqqttqf3k5a.fsf@gitster.g","subject":"Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence","fromName":"Han-Wen Nienhuys","fromEmail":"hanwen@google.com","sentAt":"2023-10-25T14:26:35Z","receivedAt":"2023-10-25T14:26:58Z","isPatch":true,"sender":{"key":"hanwen@google.com","avatar":"https://avatars.githubusercontent.com/u/31547?v=4"},"body":"On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Patrick Steinhardt <ps@pks.im> writes:\n>\n> > this patch series introduces a new `--exists` mode to git-show-ref(1) to\n> > explicitly check for the existence of a reference, only.\n>\n> I agree that show-ref would be the best place for this feature (not\n> rev-parse, which is already a kitchen sink).  After all, the command\n> was designed for validating refs in 358ddb62 (Add \"git show-ref\"\n> builtin command, 2006-09-15).\n>\n> Thanks.  Hopefully I can take a look before I go offline.\n\nThe series description doesn't say why users would care about this.\n\nIf this is just to ease testing, I suggest adding functionality to a\nsuitable test helper. Anything you add to git-show-ref is a publicly\nvisible API that needs documentation and comes with a stability\nguarantee that is more expensive to maintain than test helper\nfunctionality.\n\n-- \nHan-Wen Nienhuys - Google Munich\nI work 80%. Don't expect answers from me on Fridays.\n--\n\nGoogle Germany GmbH, Erika-Mann-Strasse 33, 80636 Munich\n\nRegistergericht und -nummer: Hamburg, HRB 86891\n\nSitz der Gesellschaft: Hamburg\n\nGeschäftsführer: Paul Manicle, Liana Sebastian\n"},{"id":"483856","messageId":"f87c95d6-b1e5-45c7-b380-bdc8b8143ab9@gmail.com","threadId":"60421","inReplyTo":"CAFQ2z_PqNsz+zycSxz=q2cUVOpJS-AEjwHxEM-fiafxd3dxc9g@mail.gmail.com","subject":"Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-10-25T14:44:33Z","receivedAt":"2023-10-25T14:44:37Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 25/10/2023 15:26, Han-Wen Nienhuys wrote:\n> On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>>> this patch series introduces a new `--exists` mode to git-show-ref(1) to\n>>> explicitly check for the existence of a reference, only.\n>>\n>> I agree that show-ref would be the best place for this feature (not\n>> rev-parse, which is already a kitchen sink).  After all, the command\n>> was designed for validating refs in 358ddb62 (Add \"git show-ref\"\n>> builtin command, 2006-09-15).\n>>\n>> Thanks.  Hopefully I can take a look before I go offline.\n> \n> The series description doesn't say why users would care about this.\n> \n> If this is just to ease testing, I suggest adding functionality to a\n> suitable test helper. Anything you add to git-show-ref is a publicly\n> visible API that needs documentation and comes with a stability\n> guarantee that is more expensive to maintain than test helper\n> functionality.\n\nDoes the new functionality provide a way for scripts to see if a branch \nis unborn (i.e. has not commits yet)? I don't think we have a way to \ndistinguish between a ref that points to a missing object and an unborn \nbranch at the moment.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"483894","messageId":"ZTo1Af8JKaovhJtk@tanuki","threadId":"60421","inReplyTo":"CAFQ2z_PqNsz+zycSxz=q2cUVOpJS-AEjwHxEM-fiafxd3dxc9g@mail.gmail.com","subject":"Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:44:33Z","receivedAt":"2023-10-26T09:44:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 25, 2023 at 04:26:35PM +0200, Han-Wen Nienhuys wrote:\n> On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > Patrick Steinhardt <ps@pks.im> writes:\n> >\n> > > this patch series introduces a new `--exists` mode to git-show-ref(1) to\n> > > explicitly check for the existence of a reference, only.\n> >\n> > I agree that show-ref would be the best place for this feature (not\n> > rev-parse, which is already a kitchen sink).  After all, the command\n> > was designed for validating refs in 358ddb62 (Add \"git show-ref\"\n> > builtin command, 2006-09-15).\n> >\n> > Thanks.  Hopefully I can take a look before I go offline.\n> \n> The series description doesn't say why users would care about this.\n> \n> If this is just to ease testing, I suggest adding functionality to a\n> suitable test helper. Anything you add to git-show-ref is a publicly\n> visible API that needs documentation and comes with a stability\n> guarantee that is more expensive to maintain than test helper\n> functionality.\n\nThe first patch of the original patch series where I split this out from\ndid exactly that, see [1]. Junio questioned though whether this should\nbe part of production code instead of being a test helper.\n\nAnd I tend to agree with him, or otherwise I wouldn't have written this\nseries. It's actually a bit surprising that we do not have any way to\ntest for reference existence in any of our helpers in a generic way. All\ncurrent tooling that I'm aware of is lacking in some ways:\n\n    - git-rev-parse(1) will fail to parse symbolic refs whose target\n      does not exist.\n\n    - git-symbolic-ref(1) can look up such unborn branches, but the\n      caller needs to be aware that \n\n    - git-show-ref(1) tries to resolve symbolic references.\n\n    - git-for-each-ref(1) is simply not an obvious way to check for ref\n      existence.\n\n    - All of these will fail to parse references with malformed names.\n\nSo the new `git show-ref --exists` mode is a trivial-to-use and generic\nway to simply ask \"Do you know this reference?\". The lack of this option\nlikely shows that you can most often get away without such a tool, but I\nstill find it funny that there is no obvious way to perform this query\nright now.\n\nAt the Contributor's Summit, we've also discussed the issue that our\nplumbing layer has become less useful over the years. It is often\nmissing functionality that exists in user-facing commands. It also has\ninherited many of the restrictions of our porcelain tools, like not\nbeing able to look up references with bad names. So this series is a\nsmall step into the direction of making our plumbing more useful again.\n\nI also assume that it's only going to become more important to address\nthese limitations in our plumbing layer once we have something like the\nreftable backend. A user or admin would have been able to fix issues\nwith misformatted referencen names rather easily in the reffiles backend\neven without support in our plumbing layer, as it mostly was a single\n\"rm .git/refs/$broken_ref\" away. But that's not going to be an easy\nsolution anymore with reftable due to the complexity of its format. So I\nalso see it as part of the upcoming preparatory work to make sure that\nthey have all the necessary tools to address such situations, at least\nup to a reasonable point.\n\nPatrick\n\n[1]: <e947feb1c77f7e9f3c7f983bbe47137fbce42367.1697607222.git.ps@pks.im>\n"},{"id":"483895","messageId":"ZTo2Aq2wAxx_6Laz@tanuki","threadId":"60421","inReplyTo":"f87c95d6-b1e5-45c7-b380-bdc8b8143ab9@gmail.com","subject":"Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:48:50Z","receivedAt":"2023-10-26T09:49:00Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 25, 2023 at 03:44:33PM +0100, Phillip Wood wrote:\n> On 25/10/2023 15:26, Han-Wen Nienhuys wrote:\n> > On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:\n> > > \n> > > Patrick Steinhardt <ps@pks.im> writes:\n> > > \n> > > > this patch series introduces a new `--exists` mode to git-show-ref(1) to\n> > > > explicitly check for the existence of a reference, only.\n> > > \n> > > I agree that show-ref would be the best place for this feature (not\n> > > rev-parse, which is already a kitchen sink).  After all, the command\n> > > was designed for validating refs in 358ddb62 (Add \"git show-ref\"\n> > > builtin command, 2006-09-15).\n> > > \n> > > Thanks.  Hopefully I can take a look before I go offline.\n> > \n> > The series description doesn't say why users would care about this.\n> > \n> > If this is just to ease testing, I suggest adding functionality to a\n> > suitable test helper. Anything you add to git-show-ref is a publicly\n> > visible API that needs documentation and comes with a stability\n> > guarantee that is more expensive to maintain than test helper\n> > functionality.\n> \n> Does the new functionality provide a way for scripts to see if a branch is\n> unborn (i.e. has not commits yet)? I don't think we have a way to\n> distinguish between a ref that points to a missing object and an unborn\n> branch at the moment.\n\nYou could do it with two commands:\n\n```\ntarget=$(git symbolic-ref HEAD)\ngit show-ref --exists \"$target\"\ncase \"$?\" in\n2)\n    echo \"unborn branch\";;\n0)\n    echo \"branch exists\";;\n*)\n    echo \"could be anything, dunno\";;\nesac\n```\n\nWhile you could use git-rev-parse(1) instead of git-show-ref(1), you\nwouldn't be able to easily distinguish the case of a missing target\nreference and any other kind of error.\n\nPatrick\n"},{"id":"483896","messageId":"cover.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH v2 00/12] show-ref: introduce mode to check for ref existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:16Z","receivedAt":"2023-10-26T09:56:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the second version of my patch series that introduces a new `git\nshow-ref --exists` mode to check for reference existence.\n\nChanges compared to v1:\n\n    - Various typo fixes in commit messages.\n\n    - Stopped using non-constant designated initializers.\n\n    - Renamed a varible from `matchlen` to `patternlen` to match another\n      renamed variable better.\n\n    - Improved the error message for mutually exclusive modes to be\n      easier to handle for translators.\n\n    - \"--quiet\" is not advertised for the pattern-based mode of\n      git-show-ref(1) anymore.\n\n    - Rephrased \"error code\" to \"exit code\" in the documentation.\n\nThanks for the feedback so far!\n\nPatrick\n\nPatrick Steinhardt (12):\n  builtin/show-ref: convert pattern to a local variable\n  builtin/show-ref: split up different subcommands\n  builtin/show-ref: fix leaking string buffer\n  builtin/show-ref: fix dead code when passing patterns\n  builtin/show-ref: refactor `--exclude-existing` options\n  builtin/show-ref: stop using global variable to count matches\n  builtin/show-ref: stop using global vars for `show_one()`\n  builtin/show-ref: refactor options for patterns subcommand\n  builtin/show-ref: ensure mutual exclusiveness of subcommands\n  builtin/show-ref: explicitly spell out different modes in synopsis\n  builtin/show-ref: add new mode to check for reference existence\n  t: use git-show-ref(1) to check for ref existence\n\n Documentation/git-show-ref.txt |  20 ++-\n builtin/show-ref.c             | 280 ++++++++++++++++++++++-----------\n t/t1403-show-ref.sh            |  70 +++++++++\n t/t1430-bad-ref-name.sh        |  27 ++--\n t/t3200-branch.sh              |  33 ++--\n t/t5521-pull-options.sh        |   4 +-\n t/t5605-clone-local.sh         |   2 +-\n t/test-lib-functions.sh        |  55 +++++++\n 8 files changed, 369 insertions(+), 122 deletions(-)\n\nRange-diff against v1:\n 1:  78163accbd2 =  1:  78163accbd2 builtin/show-ref: convert pattern to a local variable\n 2:  7e6ab5dee23 !  2:  9a234622d99 builtin/show-ref: split up different subcommands\n    @@ builtin/show-ref.c: static int exclude_existing(const char *match)\n     +\n     +static int cmd_show_ref__patterns(const char **patterns)\n     +{\n    -+\tstruct show_ref_data show_ref_data = {\n    -+\t\t.patterns = (patterns && *patterns) ? patterns : NULL,\n    -+\t};\n    ++\tstruct show_ref_data show_ref_data = {0};\n    ++\n    ++\tif (patterns && *patterns)\n    ++\t\tshow_ref_data.patterns = patterns;\n     +\n     +\tif (show_head)\n     +\t\thead_ref(show_ref, &show_ref_data);\n 3:  ae2e401fbd8 =  3:  bb0d656a0b4 builtin/show-ref: fix leaking string buffer\n 4:  29c0c0c6c97 !  4:  87afcee830c builtin/show-ref: fix dead code when passing patterns\n    @@ Commit message\n         builtin/show-ref: fix dead code when passing patterns\n     \n         When passing patterns to `git show-ref` we have some code that will\n    -    cause us to die of `verify && !quiet` is true. But because `verify`\n    +    cause us to die if `verify && !quiet` is true. But because `verify`\n         indicates a different subcommand of git-show-ref(1) that causes us to\n         execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the\n         condition cannot ever be true.\n 5:  8d0b0b5700c !  5:  bed2a8a0769 builtin/show-ref: refactor `--exclude-existing` options\n    @@ Commit message\n         builtin/show-ref: refactor `--exclude-existing` options\n     \n         It's not immediately obvious options which options are applicable to\n    -    what subcommand ni git-show-ref(1) because all options exist as global\n    +    what subcommand in git-show-ref(1) because all options exist as global\n         state. This can easily cause confusion for the reader.\n     \n         Refactor options for the `--exclude-existing` subcommand to be contained\n         in a separate structure. This structure is stored on the stack and\n    -    passed down as required. Consequentially, it clearly delimits the scope\n    -    of those options and requires the reader to worry less about global\n    -    state.\n    +    passed down as required. Consequently, it clearly delimits the scope of\n    +    those options and requires the reader to worry less about global state.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    @@ builtin/show-ref.c: static int add_existing(const char *refname,\n      \tstruct string_list existing_refs = STRING_LIST_INIT_DUP;\n      \tchar buf[1024];\n     -\tint matchlen = match ? strlen(match) : 0;\n    -+\tint matchlen = opts->pattern ? strlen(opts->pattern) : 0;\n    ++\tint patternlen = opts->pattern ? strlen(opts->pattern) : 0;\n      \n      \tfor_each_ref(add_existing, &existing_refs);\n      \twhile (fgets(buf, sizeof(buf), stdin)) {\n    @@ builtin/show-ref.c: static int cmd_show_ref__exclude_existing(const char *match)\n     -\t\tif (match) {\n     +\t\tif (opts->pattern) {\n      \t\t\tint reflen = buf + len - ref;\n    - \t\t\tif (reflen < matchlen)\n    +-\t\t\tif (reflen < matchlen)\n    ++\t\t\tif (reflen < patternlen)\n      \t\t\t\tcontinue;\n     -\t\t\tif (strncmp(ref, match, matchlen))\n    -+\t\t\tif (strncmp(ref, opts->pattern, matchlen))\n    ++\t\t\tif (strncmp(ref, opts->pattern, patternlen))\n      \t\t\t\tcontinue;\n      \t\t}\n      \t\tif (check_refname_format(ref, 0)) {\n 6:  6e0f3d4e104 =  6:  d52a5e8ced2 builtin/show-ref: stop using global variable to count matches\n 7:  2da1e65dd8f !  7:  63f1dadf4c2 builtin/show-ref: stop using global vars for `show_one()`\n    @@ builtin/show-ref.c: static int cmd_show_ref__verify(const char **refs)\n     +static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,\n     +\t\t\t\t  const char **patterns)\n      {\n    - \tstruct show_ref_data show_ref_data = {\n    +-\tstruct show_ref_data show_ref_data = {0};\n    ++\tstruct show_ref_data show_ref_data = {\n     +\t\t.show_one_opts = show_one_opts,\n    - \t\t.patterns = (patterns && *patterns) ? patterns : NULL,\n    - \t};\n    ++\t};\n      \n    + \tif (patterns && *patterns)\n    + \t\tshow_ref_data.patterns = patterns;\n     @@ builtin/show-ref.c: static int cmd_show_ref__patterns(const char **patterns)\n      \n      static int hash_callback(const struct option *opt, const char *arg, int unset)\n 8:  805889eda4c !  8:  88dfeaa4871 builtin/show-ref: refactor options for patterns subcommand\n    @@ builtin/show-ref.c: static int cmd_show_ref__verify(const struct show_one_option\n      \tstruct show_ref_data show_ref_data = {\n      \t\t.show_one_opts = show_one_opts,\n     +\t\t.show_head = opts->show_head,\n    - \t\t.patterns = (patterns && *patterns) ? patterns : NULL,\n      \t};\n      \n    + \tif (patterns && *patterns)\n    + \t\tshow_ref_data.patterns = patterns;\n    + \n     -\tif (show_head)\n     +\tif (opts->show_head)\n      \t\thead_ref(show_ref, &show_ref_data);\n 9:  d0a991cf4f8 !  9:  5ba566723e8 builtin/show-ref: ensure mutual exclusiveness of subcommands\n    @@ builtin/show-ref.c: int cmd_show_ref(int argc, const char **argv, const char *pr\n      \t\t\t     show_ref_usage, 0);\n      \n     +\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n    -+\t\tdie(_(\"only one of --exclude-existing or --verify can be given\"));\n    ++\t\tdie(_(\"only one of '%s' or '%s' can be given\"),\n    ++\t\t    \"--exclude-existing\", \"--verify\");\n     +\n      \tif (exclude_existing_opts.enabled)\n      \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '\n      \n     +test_expect_success 'show-ref sub-modes are mutually exclusive' '\n     +\ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n    -+\tgrep \"only one of --exclude-existing or --verify can be given\" err\n    ++\tgrep \"only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\" err\n     +'\n     +\n      test_done\n10:  adcfa7a6a9d ! 10:  b78ccc5f692 builtin/show-ref: explicitly spell out different modes in synopsis\n    @@ Commit message\n         Split up the synopsis for these two modes such that we can disambiguate\n         those differences.\n     \n    +    While at it, drop \"--quiet\" from the pattern mode's synopsis. It does\n    +    not make a lot of sense to list patterns, but squelch the listing output\n    +    itself. The description for \"--quiet\" is adapted accordingly.\n    +\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## Documentation/git-show-ref.txt ##\n    @@ Documentation/git-show-ref.txt: git-show-ref - List references in a local reposi\n      --------\n      [verse]\n     -'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]\n    -+'git show-ref' [-q | --quiet] [--head] [-d | --dereference]\n    ++'git show-ref' [--head] [-d | --dereference]\n      \t     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n      \t     [--heads] [--] [<pattern>...]\n     +'git show-ref' --verify [-q | --quiet] [-d | --dereference]\n    @@ Documentation/git-show-ref.txt: git-show-ref - List references in a local reposi\n      'git show-ref' --exclude-existing[=<pattern>]\n      \n      DESCRIPTION\n    +@@ Documentation/git-show-ref.txt: OPTIONS\n    + -q::\n    + --quiet::\n    + \n    +-\tDo not print any results to stdout. When combined with `--verify`, this\n    +-\tcan be used to silently check if a reference exists.\n    ++\tDo not print any results to stdout. Can be used with `--verify` to\n    ++\tsilently check if a reference exists.\n    + \n    + --exclude-existing[=<pattern>]::\n    + \n     \n      ## builtin/show-ref.c ##\n     @@\n    @@ builtin/show-ref.c\n      \n      static const char * const show_ref_usage[] = {\n     -\tN_(\"git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference]\\n\"\n    -+\tN_(\"git show-ref [-q | --quiet] [--head] [-d | --dereference]\\n\"\n    ++\tN_(\"git show-ref [--head] [-d | --dereference]\\n\"\n      \t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\\n\"\n      \t   \"             [--heads] [--] [<pattern>...]\"),\n     +\tN_(\"git show-ref --verify [-q | --quiet] [-d | --dereference]\\n\"\n11:  2f876e61dd3 ! 11:  327942b1162 builtin/show-ref: add new mode to check for reference existence\n    @@ Commit message\n     \n             - Dangling symrefs can be resolved via git-symbolic-ref(1), but this\n               requires the caller to special case existence checks depending on\n    -          whteher or not a reference is symbolic or direct.\n    +          whether or not a reference is symbolic or direct.\n     \n         Furthermore, git-rev-list(1) and other commands do not let the caller\n         distinguish easily between an actually missing reference and a generic\n         error.\n     \n    -    Taken together, this gseems like sufficient motivation to introduce a\n    +    Taken together, this seems like sufficient motivation to introduce a\n         separate plumbing command to explicitly check for the existence of a\n         reference without trying to resolve its contents.\n     \n         This new command comes in the form of `git show-ref --exists`. This\n         new mode will exit successfully when the reference exists, with a\n    -    specific error code of 2 when it does not exist, or with 1 when there\n    +    specific exit code of 2 when it does not exist, or with 1 when there\n         has been a generic error.\n     \n         Note that the only way to properly implement this command is by using\n    @@ Documentation/git-show-ref.txt: OPTIONS\n      \n     +--exists::\n     +\n    -+\tCheck whether the given reference exists. Returns an error code of 0 if\n    -+\tit does, 2 if it is missing, and 128 in case looking up the reference\n    ++\tCheck whether the given reference exists. Returns an exit code of 0 if\n    ++\tit does, 2 if it is missing, and 1 in case looking up the reference\n     +\tfailed with an error other than the reference being missing.\n     +\n      --abbrev[=<n>]::\n    @@ builtin/show-ref.c: static int cmd_show_ref__patterns(const struct patterns_opti\n     +\tunsigned int unused_type;\n     +\tint failure_errno = 0;\n     +\tconst char *ref;\n    -+\tint ret = 1;\n    ++\tint ret = 0;\n     +\n     +\tif (!refs || !*refs)\n     +\t\tdie(\"--exists requires a reference\");\n    @@ builtin/show-ref.c: static int cmd_show_ref__patterns(const struct patterns_opti\n     +\t\t\terror(_(\"reference does not exist\"));\n     +\t\t\tret = 2;\n     +\t\t} else {\n    -+\t\t\terror(_(\"failed to look up reference: %s\"), strerror(failure_errno));\n    ++\t\t\terrno = failure_errno;\n    ++\t\t\terror_errno(_(\"failed to look up reference\"));\n    ++\t\t\tret = 1;\n     +\t\t}\n     +\n     +\t\tgoto out;\n     +\t}\n     +\n    -+\tret = 0;\n    -+\n     +out:\n     +\tstrbuf_release(&unused_referent);\n     +\treturn ret;\n    @@ builtin/show-ref.c: int cmd_show_ref(int argc, const char **argv, const char *pr\n      \t\t\t     show_ref_usage, 0);\n      \n     -\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n    --\t\tdie(_(\"only one of --exclude-existing or --verify can be given\"));\n    +-\t\tdie(_(\"only one of '%s' or '%s' can be given\"),\n    +-\t\t    \"--exclude-existing\", \"--verify\");\n     +\tif ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)\n    -+\t\tdie(_(\"only one of --exclude-existing, --exists or --verify can be given\"));\n    ++\t\tdie(_(\"only one of '%s', '%s' or '%s' can be given\"),\n    ++\t\t    \"--exclude-existing\", \"--verify\", \"--exists\");\n      \n      \tif (exclude_existing_opts.enabled)\n      \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '\n      \n      test_expect_success 'show-ref sub-modes are mutually exclusive' '\n     +\tcat >expect <<-EOF &&\n    -+\tfatal: only one of --exclude-existing, --exists or --verify can be given\n    ++\tfatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given\n     +\tEOF\n     +\n      \ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n    --\tgrep \"only one of --exclude-existing or --verify can be given\" err\n    +-\tgrep \"only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\" err\n     +\ttest_cmp expect err &&\n     +\n     +\ttest_must_fail git show-ref --verify --exists 2>err &&\n12:  a3a43d82e1f = 12:  226731c5f18 t: use git-show-ref(1) to check for ref existence\n\nbase-commit: a9ecda2788e229afc9b611acaa26d0d9d4da53ed\n-- \n2.42.0\n\n"},{"id":"483897","messageId":"78163accbd2c39721249474512dedd8ed2f0fe4e.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 01/12] builtin/show-ref: convert pattern to a local variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:21Z","receivedAt":"2023-10-26T09:56:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `pattern` variable is a global variable that tracks either the\nreference names (not patterns!) for the `--verify` mode or the patterns\nfor the non-verify mode. This is a bit confusing due to the slightly\ndifferent meanings.\n\nConvert the variable to be local. While this does not yet fix the double\nmeaning of the variable, this change allows us to address it in a\nsubsequent patch more easily by explicitly splitting up the different\nsubcommands of git-show-ref(1).\n\nNote that we introduce a `struct show_ref_data` to pass the patterns to\n`show_ref()`. While this is overengineered now, we will extend this\nstructure in a subsequent patch.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 46 ++++++++++++++++++++++++++++------------------\n 1 file changed, 28 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 5110814f796..7efab14b96c 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -20,7 +20,6 @@ static const char * const show_ref_usage[] = {\n \n static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n \t   quiet, hash_only, abbrev, exclude_arg;\n-static const char **pattern;\n static const char *exclude_existing_arg;\n \n static void show_one(const char *refname, const struct object_id *oid)\n@@ -50,15 +49,21 @@ static void show_one(const char *refname, const struct object_id *oid)\n \t}\n }\n \n+struct show_ref_data {\n+\tconst char **patterns;\n+};\n+\n static int show_ref(const char *refname, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cbdata UNUSED)\n+\t\t    int flag UNUSED, void *cbdata)\n {\n+\tstruct show_ref_data *data = cbdata;\n+\n \tif (show_head && !strcmp(refname, \"HEAD\"))\n \t\tgoto match;\n \n-\tif (pattern) {\n+\tif (data->patterns) {\n \t\tint reflen = strlen(refname);\n-\t\tconst char **p = pattern, *m;\n+\t\tconst char **p = data->patterns, *m;\n \t\twhile ((m = *p++) != NULL) {\n \t\t\tint len = strlen(m);\n \t\t\tif (len > reflen)\n@@ -180,6 +185,9 @@ static const struct option show_ref_options[] = {\n \n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n+\tstruct show_ref_data show_ref_data = {0};\n+\tconst char **patterns;\n+\n \tgit_config(git_default_config, NULL);\n \n \targc = parse_options(argc, argv, prefix, show_ref_options,\n@@ -188,38 +196,40 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \tif (exclude_arg)\n \t\treturn exclude_existing(exclude_existing_arg);\n \n-\tpattern = argv;\n-\tif (!*pattern)\n-\t\tpattern = NULL;\n+\tpatterns = argv;\n+\tif (!*patterns)\n+\t\tpatterns = NULL;\n \n \tif (verify) {\n-\t\tif (!pattern)\n+\t\tif (!patterns)\n \t\t\tdie(\"--verify requires a reference\");\n-\t\twhile (*pattern) {\n+\t\twhile (*patterns) {\n \t\t\tstruct object_id oid;\n \n-\t\t\tif ((starts_with(*pattern, \"refs/\") || !strcmp(*pattern, \"HEAD\")) &&\n-\t\t\t    !read_ref(*pattern, &oid)) {\n-\t\t\t\tshow_one(*pattern, &oid);\n+\t\t\tif ((starts_with(*patterns, \"refs/\") || !strcmp(*patterns, \"HEAD\")) &&\n+\t\t\t    !read_ref(*patterns, &oid)) {\n+\t\t\t\tshow_one(*patterns, &oid);\n \t\t\t}\n \t\t\telse if (!quiet)\n-\t\t\t\tdie(\"'%s' - not a valid ref\", *pattern);\n+\t\t\t\tdie(\"'%s' - not a valid ref\", *patterns);\n \t\t\telse\n \t\t\t\treturn 1;\n-\t\t\tpattern++;\n+\t\t\tpatterns++;\n \t\t}\n \t\treturn 0;\n \t}\n \n+\tshow_ref_data.patterns = patterns;\n+\n \tif (show_head)\n-\t\thead_ref(show_ref, NULL);\n+\t\thead_ref(show_ref, &show_ref_data);\n \tif (heads_only || tags_only) {\n \t\tif (heads_only)\n-\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, NULL);\n+\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n \t\tif (tags_only)\n-\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, NULL);\n+\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n \t} else {\n-\t\tfor_each_ref(show_ref, NULL);\n+\t\tfor_each_ref(show_ref, &show_ref_data);\n \t}\n \tif (!found_match) {\n \t\tif (verify && !quiet)\n-- \n2.42.0\n\n"},{"id":"483898","messageId":"9a234622d991186bf7a618bffbf15a936aba34c4.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 02/12] builtin/show-ref: split up different subcommands","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:25Z","receivedAt":"2023-10-26T09:56:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While not immediately obvious, git-show-ref(1) actually implements three\ndifferent subcommands:\n\n    - `git show-ref <patterns>` can be used to list references that\n      match a specific pattern.\n\n    - `git show-ref --verify <refs>` can be used to list references.\n      These are _not_ patterns.\n\n    - `git show-ref --exclude-existing` can be used as a filter that\n      reads references from standard input, performing some conversions\n      on each of them.\n\nLet's make this more explicit in the code by splitting up the three\nsubcommands into separate functions. This also allows us to address the\nconfusingly named `patterns` variable, which may hold either patterns or\nreference names.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 101 ++++++++++++++++++++++++---------------------\n 1 file changed, 54 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 7efab14b96c..cad5b8b5066 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -104,7 +104,7 @@ static int add_existing(const char *refname,\n  * (4) ignore if refname is a ref that exists in the local repository;\n  * (5) otherwise output the line.\n  */\n-static int exclude_existing(const char *match)\n+static int cmd_show_ref__exclude_existing(const char *match)\n {\n \tstatic struct string_list existing_refs = STRING_LIST_INIT_DUP;\n \tchar buf[1024];\n@@ -142,6 +142,54 @@ static int exclude_existing(const char *match)\n \treturn 0;\n }\n \n+static int cmd_show_ref__verify(const char **refs)\n+{\n+\tif (!refs || !*refs)\n+\t\tdie(\"--verify requires a reference\");\n+\n+\twhile (*refs) {\n+\t\tstruct object_id oid;\n+\n+\t\tif ((starts_with(*refs, \"refs/\") || !strcmp(*refs, \"HEAD\")) &&\n+\t\t    !read_ref(*refs, &oid)) {\n+\t\t\tshow_one(*refs, &oid);\n+\t\t}\n+\t\telse if (!quiet)\n+\t\t\tdie(\"'%s' - not a valid ref\", *refs);\n+\t\telse\n+\t\t\treturn 1;\n+\t\trefs++;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int cmd_show_ref__patterns(const char **patterns)\n+{\n+\tstruct show_ref_data show_ref_data = {0};\n+\n+\tif (patterns && *patterns)\n+\t\tshow_ref_data.patterns = patterns;\n+\n+\tif (show_head)\n+\t\thead_ref(show_ref, &show_ref_data);\n+\tif (heads_only || tags_only) {\n+\t\tif (heads_only)\n+\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n+\t\tif (tags_only)\n+\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n+\t} else {\n+\t\tfor_each_ref(show_ref, &show_ref_data);\n+\t}\n+\tif (!found_match) {\n+\t\tif (verify && !quiet)\n+\t\t\tdie(\"No match\");\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int hash_callback(const struct option *opt, const char *arg, int unset)\n {\n \thash_only = 1;\n@@ -185,56 +233,15 @@ static const struct option show_ref_options[] = {\n \n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n-\tstruct show_ref_data show_ref_data = {0};\n-\tconst char **patterns;\n-\n \tgit_config(git_default_config, NULL);\n \n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n \tif (exclude_arg)\n-\t\treturn exclude_existing(exclude_existing_arg);\n-\n-\tpatterns = argv;\n-\tif (!*patterns)\n-\t\tpatterns = NULL;\n-\n-\tif (verify) {\n-\t\tif (!patterns)\n-\t\t\tdie(\"--verify requires a reference\");\n-\t\twhile (*patterns) {\n-\t\t\tstruct object_id oid;\n-\n-\t\t\tif ((starts_with(*patterns, \"refs/\") || !strcmp(*patterns, \"HEAD\")) &&\n-\t\t\t    !read_ref(*patterns, &oid)) {\n-\t\t\t\tshow_one(*patterns, &oid);\n-\t\t\t}\n-\t\t\telse if (!quiet)\n-\t\t\t\tdie(\"'%s' - not a valid ref\", *patterns);\n-\t\t\telse\n-\t\t\t\treturn 1;\n-\t\t\tpatterns++;\n-\t\t}\n-\t\treturn 0;\n-\t}\n-\n-\tshow_ref_data.patterns = patterns;\n-\n-\tif (show_head)\n-\t\thead_ref(show_ref, &show_ref_data);\n-\tif (heads_only || tags_only) {\n-\t\tif (heads_only)\n-\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n-\t\tif (tags_only)\n-\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n-\t} else {\n-\t\tfor_each_ref(show_ref, &show_ref_data);\n-\t}\n-\tif (!found_match) {\n-\t\tif (verify && !quiet)\n-\t\t\tdie(\"No match\");\n-\t\treturn 1;\n-\t}\n-\treturn 0;\n+\t\treturn cmd_show_ref__exclude_existing(exclude_existing_arg);\n+\telse if (verify)\n+\t\treturn cmd_show_ref__verify(argv);\n+\telse\n+\t\treturn cmd_show_ref__patterns(argv);\n }\n-- \n2.42.0\n\n"},{"id":"483899","messageId":"bb0d656a0b40b79c90dc2505239976a93f18f432.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 03/12] builtin/show-ref: fix leaking string buffer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:29Z","receivedAt":"2023-10-26T09:56:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Fix a leaking string buffer in `git show-ref --exclude-existing`. While\nthe buffer is technically not leaking because its variable is declared\nas static, there is no inherent reason why it should be.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex cad5b8b5066..e55c38af478 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -106,7 +106,7 @@ static int add_existing(const char *refname,\n  */\n static int cmd_show_ref__exclude_existing(const char *match)\n {\n-\tstatic struct string_list existing_refs = STRING_LIST_INIT_DUP;\n+\tstruct string_list existing_refs = STRING_LIST_INIT_DUP;\n \tchar buf[1024];\n \tint matchlen = match ? strlen(match) : 0;\n \n@@ -139,6 +139,8 @@ static int cmd_show_ref__exclude_existing(const char *match)\n \t\t\tprintf(\"%s\\n\", buf);\n \t\t}\n \t}\n+\n+\tstring_list_clear(&existing_refs, 0);\n \treturn 0;\n }\n \n-- \n2.42.0\n\n"},{"id":"483900","messageId":"87afcee830caab2782ce693c1f961df6fea6e7b5.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 04/12] builtin/show-ref: fix dead code when passing patterns","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:33Z","receivedAt":"2023-10-26T09:56:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When passing patterns to `git show-ref` we have some code that will\ncause us to die if `verify && !quiet` is true. But because `verify`\nindicates a different subcommand of git-show-ref(1) that causes us to\nexecute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the\ncondition cannot ever be true.\n\nLet's remove this dead code.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 5 +----\n 1 file changed, 1 insertion(+), 4 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex e55c38af478..f95418d3d16 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -183,11 +183,8 @@ static int cmd_show_ref__patterns(const char **patterns)\n \t} else {\n \t\tfor_each_ref(show_ref, &show_ref_data);\n \t}\n-\tif (!found_match) {\n-\t\tif (verify && !quiet)\n-\t\t\tdie(\"No match\");\n+\tif (!found_match)\n \t\treturn 1;\n-\t}\n \n \treturn 0;\n }\n-- \n2.42.0\n\n"},{"id":"483901","messageId":"bed2a8a07696371e07c0b2d1282ed51c0b1b9fee.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:37Z","receivedAt":"2023-10-26T09:56:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"It's not immediately obvious options which options are applicable to\nwhat subcommand in git-show-ref(1) because all options exist as global\nstate. This can easily cause confusion for the reader.\n\nRefactor options for the `--exclude-existing` subcommand to be contained\nin a separate structure. This structure is stored on the stack and\npassed down as required. Consequently, it clearly delimits the scope of\nthose options and requires the reader to worry less about global state.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 74 +++++++++++++++++++++++++---------------------\n 1 file changed, 40 insertions(+), 34 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex f95418d3d16..90481c58492 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {\n };\n \n static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n-\t   quiet, hash_only, abbrev, exclude_arg;\n-static const char *exclude_existing_arg;\n+\t   quiet, hash_only, abbrev;\n \n static void show_one(const char *refname, const struct object_id *oid)\n {\n@@ -95,6 +94,11 @@ static int add_existing(const char *refname,\n \treturn 0;\n }\n \n+struct exclude_existing_options {\n+\tint enabled;\n+\tconst char *pattern;\n+};\n+\n /*\n  * read \"^(?:<anything>\\s)?<refname>(?:\\^\\{\\})?$\" from the standard input,\n  * and\n@@ -104,11 +108,11 @@ static int add_existing(const char *refname,\n  * (4) ignore if refname is a ref that exists in the local repository;\n  * (5) otherwise output the line.\n  */\n-static int cmd_show_ref__exclude_existing(const char *match)\n+static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)\n {\n \tstruct string_list existing_refs = STRING_LIST_INIT_DUP;\n \tchar buf[1024];\n-\tint matchlen = match ? strlen(match) : 0;\n+\tint patternlen = opts->pattern ? strlen(opts->pattern) : 0;\n \n \tfor_each_ref(add_existing, &existing_refs);\n \twhile (fgets(buf, sizeof(buf), stdin)) {\n@@ -124,11 +128,11 @@ static int cmd_show_ref__exclude_existing(const char *match)\n \t\tfor (ref = buf + len; buf < ref; ref--)\n \t\t\tif (isspace(ref[-1]))\n \t\t\t\tbreak;\n-\t\tif (match) {\n+\t\tif (opts->pattern) {\n \t\t\tint reflen = buf + len - ref;\n-\t\t\tif (reflen < matchlen)\n+\t\t\tif (reflen < patternlen)\n \t\t\t\tcontinue;\n-\t\t\tif (strncmp(ref, match, matchlen))\n+\t\t\tif (strncmp(ref, opts->pattern, patternlen))\n \t\t\t\tcontinue;\n \t\t}\n \t\tif (check_refname_format(ref, 0)) {\n@@ -201,44 +205,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)\n static int exclude_existing_callback(const struct option *opt, const char *arg,\n \t\t\t\t     int unset)\n {\n+\tstruct exclude_existing_options *opts = opt->value;\n \tBUG_ON_OPT_NEG(unset);\n-\texclude_arg = 1;\n-\t*(const char **)opt->value = arg;\n+\topts->enabled = 1;\n+\topts->pattern = arg;\n \treturn 0;\n }\n \n-static const struct option show_ref_options[] = {\n-\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n-\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n-\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n-\t\t    \"requires exact ref path\")),\n-\tOPT_HIDDEN_BOOL('h', NULL, &show_head,\n-\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n-\tOPT_BOOL(0, \"head\", &show_head,\n-\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n-\tOPT_BOOL('d', \"dereference\", &deref_tags,\n-\t\t    N_(\"dereference tags into object IDs\")),\n-\tOPT_CALLBACK_F('s', \"hash\", &abbrev, N_(\"n\"),\n-\t\t       N_(\"only show SHA1 hash using <n> digits\"),\n-\t\t       PARSE_OPT_OPTARG, &hash_callback),\n-\tOPT__ABBREV(&abbrev),\n-\tOPT__QUIET(&quiet,\n-\t\t   N_(\"do not print results to stdout (useful with --verify)\")),\n-\tOPT_CALLBACK_F(0, \"exclude-existing\", &exclude_existing_arg,\n-\t\t       N_(\"pattern\"), N_(\"show refs from stdin that aren't in local repository\"),\n-\t\t       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),\n-\tOPT_END()\n-};\n-\n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n+\tstruct exclude_existing_options exclude_existing_opts = {0};\n+\tconst struct option show_ref_options[] = {\n+\t\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n+\t\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n+\t\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n+\t\t\t    \"requires exact ref path\")),\n+\t\tOPT_HIDDEN_BOOL('h', NULL, &show_head,\n+\t\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n+\t\tOPT_BOOL(0, \"head\", &show_head,\n+\t\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n+\t\tOPT_BOOL('d', \"dereference\", &deref_tags,\n+\t\t\t    N_(\"dereference tags into object IDs\")),\n+\t\tOPT_CALLBACK_F('s', \"hash\", &abbrev, N_(\"n\"),\n+\t\t\t       N_(\"only show SHA1 hash using <n> digits\"),\n+\t\t\t       PARSE_OPT_OPTARG, &hash_callback),\n+\t\tOPT__ABBREV(&abbrev),\n+\t\tOPT__QUIET(&quiet,\n+\t\t\t   N_(\"do not print results to stdout (useful with --verify)\")),\n+\t\tOPT_CALLBACK_F(0, \"exclude-existing\", &exclude_existing_opts,\n+\t\t\t       N_(\"pattern\"), N_(\"show refs from stdin that aren't in local repository\"),\n+\t\t\t       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),\n+\t\tOPT_END()\n+\t};\n+\n \tgit_config(git_default_config, NULL);\n \n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n-\tif (exclude_arg)\n-\t\treturn cmd_show_ref__exclude_existing(exclude_existing_arg);\n+\tif (exclude_existing_opts.enabled)\n+\t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\n \t\treturn cmd_show_ref__verify(argv);\n \telse\n-- \n2.42.0\n\n"},{"id":"483902","messageId":"d52a5e8ced2adc5c9315edea9fc497d1ffa30125.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 06/12] builtin/show-ref: stop using global variable to count matches","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:42Z","receivedAt":"2023-10-26T09:56:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When passing patterns to git-show-ref(1) we're checking whether any\nreference matches -- if none does, we indicate this condition via an\nunsuccessful exit code.\n\nWe're using a global variable to count these matches, which is required\nbecause the counter is getting incremented in a callback function. But\nnow that we have the `struct show_ref_data` in place, we can get rid of\nthe global variable and put the counter in there instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 90481c58492..c30c01785cc 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -18,7 +18,7 @@ static const char * const show_ref_usage[] = {\n \tNULL\n };\n \n-static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n+static int deref_tags, show_head, tags_only, heads_only, verify,\n \t   quiet, hash_only, abbrev;\n \n static void show_one(const char *refname, const struct object_id *oid)\n@@ -50,6 +50,7 @@ static void show_one(const char *refname, const struct object_id *oid)\n \n struct show_ref_data {\n \tconst char **patterns;\n+\tint found_match;\n };\n \n static int show_ref(const char *refname, const struct object_id *oid,\n@@ -78,7 +79,7 @@ static int show_ref(const char *refname, const struct object_id *oid,\n \t}\n \n match:\n-\tfound_match++;\n+\tdata->found_match++;\n \n \tshow_one(refname, oid);\n \n@@ -187,7 +188,7 @@ static int cmd_show_ref__patterns(const char **patterns)\n \t} else {\n \t\tfor_each_ref(show_ref, &show_ref_data);\n \t}\n-\tif (!found_match)\n+\tif (!show_ref_data.found_match)\n \t\treturn 1;\n \n \treturn 0;\n-- \n2.42.0\n\n"},{"id":"483903","messageId":"63f1dadf4c2e58439fde6489ee1ae33819f83462.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 07/12] builtin/show-ref: stop using global vars for `show_one()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:48Z","receivedAt":"2023-10-26T09:56:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `show_one()` function implicitly receives a bunch of options which\nare tracked via global variables. This makes it hard to see which\nsubcommands of git-show-ref(1) actually make use of these options.\n\nIntroduce a `show_one_options` structure that gets passed down to this\nfunction. This allows us to get rid of more global state and makes it\nmore explicit which subcommands use those options.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 62 ++++++++++++++++++++++++++++++----------------\n 1 file changed, 40 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex c30c01785cc..3e6ad6b6a84 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -18,10 +18,17 @@ static const char * const show_ref_usage[] = {\n \tNULL\n };\n \n-static int deref_tags, show_head, tags_only, heads_only, verify,\n-\t   quiet, hash_only, abbrev;\n+static int show_head, tags_only, heads_only, verify;\n \n-static void show_one(const char *refname, const struct object_id *oid)\n+struct show_one_options {\n+\tint quiet;\n+\tint hash_only;\n+\tint abbrev;\n+\tint deref_tags;\n+};\n+\n+static void show_one(const struct show_one_options *opts,\n+\t\t     const char *refname, const struct object_id *oid)\n {\n \tconst char *hex;\n \tstruct object_id peeled;\n@@ -30,25 +37,26 @@ static void show_one(const char *refname, const struct object_id *oid)\n \t\tdie(\"git show-ref: bad ref %s (%s)\", refname,\n \t\t    oid_to_hex(oid));\n \n-\tif (quiet)\n+\tif (opts->quiet)\n \t\treturn;\n \n-\thex = repo_find_unique_abbrev(the_repository, oid, abbrev);\n-\tif (hash_only)\n+\thex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);\n+\tif (opts->hash_only)\n \t\tprintf(\"%s\\n\", hex);\n \telse\n \t\tprintf(\"%s %s\\n\", hex, refname);\n \n-\tif (!deref_tags)\n+\tif (!opts->deref_tags)\n \t\treturn;\n \n \tif (!peel_iterated_oid(oid, &peeled)) {\n-\t\thex = repo_find_unique_abbrev(the_repository, &peeled, abbrev);\n+\t\thex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);\n \t\tprintf(\"%s %s^{}\\n\", hex, refname);\n \t}\n }\n \n struct show_ref_data {\n+\tconst struct show_one_options *show_one_opts;\n \tconst char **patterns;\n \tint found_match;\n };\n@@ -81,7 +89,7 @@ static int show_ref(const char *refname, const struct object_id *oid,\n match:\n \tdata->found_match++;\n \n-\tshow_one(refname, oid);\n+\tshow_one(data->show_one_opts, refname, oid);\n \n \treturn 0;\n }\n@@ -149,7 +157,8 @@ static int cmd_show_ref__exclude_existing(const struct exclude_existing_options\n \treturn 0;\n }\n \n-static int cmd_show_ref__verify(const char **refs)\n+static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n+\t\t\t\tconst char **refs)\n {\n \tif (!refs || !*refs)\n \t\tdie(\"--verify requires a reference\");\n@@ -159,9 +168,9 @@ static int cmd_show_ref__verify(const char **refs)\n \n \t\tif ((starts_with(*refs, \"refs/\") || !strcmp(*refs, \"HEAD\")) &&\n \t\t    !read_ref(*refs, &oid)) {\n-\t\t\tshow_one(*refs, &oid);\n+\t\t\tshow_one(show_one_opts, *refs, &oid);\n \t\t}\n-\t\telse if (!quiet)\n+\t\telse if (!show_one_opts->quiet)\n \t\t\tdie(\"'%s' - not a valid ref\", *refs);\n \t\telse\n \t\t\treturn 1;\n@@ -171,9 +180,12 @@ static int cmd_show_ref__verify(const char **refs)\n \treturn 0;\n }\n \n-static int cmd_show_ref__patterns(const char **patterns)\n+static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,\n+\t\t\t\t  const char **patterns)\n {\n-\tstruct show_ref_data show_ref_data = {0};\n+\tstruct show_ref_data show_ref_data = {\n+\t\t.show_one_opts = show_one_opts,\n+\t};\n \n \tif (patterns && *patterns)\n \t\tshow_ref_data.patterns = patterns;\n@@ -196,11 +208,16 @@ static int cmd_show_ref__patterns(const char **patterns)\n \n static int hash_callback(const struct option *opt, const char *arg, int unset)\n {\n-\thash_only = 1;\n+\tstruct show_one_options *opts = opt->value;\n+\tstruct option abbrev_opt = *opt;\n+\n+\topts->hash_only = 1;\n \t/* Use full length SHA1 if no argument */\n \tif (!arg)\n \t\treturn 0;\n-\treturn parse_opt_abbrev_cb(opt, arg, unset);\n+\n+\tabbrev_opt.value = &opts->abbrev;\n+\treturn parse_opt_abbrev_cb(&abbrev_opt, arg, unset);\n }\n \n static int exclude_existing_callback(const struct option *opt, const char *arg,\n@@ -216,6 +233,7 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,\n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n \tstruct exclude_existing_options exclude_existing_opts = {0};\n+\tstruct show_one_options show_one_opts = {0};\n \tconst struct option show_ref_options[] = {\n \t\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n \t\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n@@ -225,13 +243,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \t\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n \t\tOPT_BOOL(0, \"head\", &show_head,\n \t\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n-\t\tOPT_BOOL('d', \"dereference\", &deref_tags,\n+\t\tOPT_BOOL('d', \"dereference\", &show_one_opts.deref_tags,\n \t\t\t    N_(\"dereference tags into object IDs\")),\n-\t\tOPT_CALLBACK_F('s', \"hash\", &abbrev, N_(\"n\"),\n+\t\tOPT_CALLBACK_F('s', \"hash\", &show_one_opts, N_(\"n\"),\n \t\t\t       N_(\"only show SHA1 hash using <n> digits\"),\n \t\t\t       PARSE_OPT_OPTARG, &hash_callback),\n-\t\tOPT__ABBREV(&abbrev),\n-\t\tOPT__QUIET(&quiet,\n+\t\tOPT__ABBREV(&show_one_opts.abbrev),\n+\t\tOPT__QUIET(&show_one_opts.quiet,\n \t\t\t   N_(\"do not print results to stdout (useful with --verify)\")),\n \t\tOPT_CALLBACK_F(0, \"exclude-existing\", &exclude_existing_opts,\n \t\t\t       N_(\"pattern\"), N_(\"show refs from stdin that aren't in local repository\"),\n@@ -247,7 +265,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \tif (exclude_existing_opts.enabled)\n \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\n-\t\treturn cmd_show_ref__verify(argv);\n+\t\treturn cmd_show_ref__verify(&show_one_opts, argv);\n \telse\n-\t\treturn cmd_show_ref__patterns(argv);\n+\t\treturn cmd_show_ref__patterns(&show_one_opts, argv);\n }\n-- \n2.42.0\n\n"},{"id":"483904","messageId":"88dfeaa4871be057eba2625ee562cdd079e540c8.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 08/12] builtin/show-ref: refactor options for patterns subcommand","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:52Z","receivedAt":"2023-10-26T09:56:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The patterns subcommand is the last command that still uses global\nvariables to track its options. Convert it to use a structure instead\nwith the same motivation as preceding commits.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 35 ++++++++++++++++++++++-------------\n 1 file changed, 22 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 3e6ad6b6a84..87bc45d2d13 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -18,8 +18,6 @@ static const char * const show_ref_usage[] = {\n \tNULL\n };\n \n-static int show_head, tags_only, heads_only, verify;\n-\n struct show_one_options {\n \tint quiet;\n \tint hash_only;\n@@ -59,6 +57,7 @@ struct show_ref_data {\n \tconst struct show_one_options *show_one_opts;\n \tconst char **patterns;\n \tint found_match;\n+\tint show_head;\n };\n \n static int show_ref(const char *refname, const struct object_id *oid,\n@@ -66,7 +65,7 @@ static int show_ref(const char *refname, const struct object_id *oid,\n {\n \tstruct show_ref_data *data = cbdata;\n \n-\tif (show_head && !strcmp(refname, \"HEAD\"))\n+\tif (data->show_head && !strcmp(refname, \"HEAD\"))\n \t\tgoto match;\n \n \tif (data->patterns) {\n@@ -180,22 +179,30 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n \treturn 0;\n }\n \n-static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,\n+struct patterns_options {\n+\tint show_head;\n+\tint heads_only;\n+\tint tags_only;\n+};\n+\n+static int cmd_show_ref__patterns(const struct patterns_options *opts,\n+\t\t\t\t  const struct show_one_options *show_one_opts,\n \t\t\t\t  const char **patterns)\n {\n \tstruct show_ref_data show_ref_data = {\n \t\t.show_one_opts = show_one_opts,\n+\t\t.show_head = opts->show_head,\n \t};\n \n \tif (patterns && *patterns)\n \t\tshow_ref_data.patterns = patterns;\n \n-\tif (show_head)\n+\tif (opts->show_head)\n \t\thead_ref(show_ref, &show_ref_data);\n-\tif (heads_only || tags_only) {\n-\t\tif (heads_only)\n+\tif (opts->heads_only || opts->tags_only) {\n+\t\tif (opts->heads_only)\n \t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n-\t\tif (tags_only)\n+\t\tif (opts->tags_only)\n \t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n \t} else {\n \t\tfor_each_ref(show_ref, &show_ref_data);\n@@ -233,15 +240,17 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,\n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n \tstruct exclude_existing_options exclude_existing_opts = {0};\n+\tstruct patterns_options patterns_opts = {0};\n \tstruct show_one_options show_one_opts = {0};\n+\tint verify = 0;\n \tconst struct option show_ref_options[] = {\n-\t\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n-\t\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n+\t\tOPT_BOOL(0, \"tags\", &patterns_opts.tags_only, N_(\"only show tags (can be combined with heads)\")),\n+\t\tOPT_BOOL(0, \"heads\", &patterns_opts.heads_only, N_(\"only show heads (can be combined with tags)\")),\n \t\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n \t\t\t    \"requires exact ref path\")),\n-\t\tOPT_HIDDEN_BOOL('h', NULL, &show_head,\n+\t\tOPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,\n \t\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n-\t\tOPT_BOOL(0, \"head\", &show_head,\n+\t\tOPT_BOOL(0, \"head\", &patterns_opts.show_head,\n \t\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n \t\tOPT_BOOL('d', \"dereference\", &show_one_opts.deref_tags,\n \t\t\t    N_(\"dereference tags into object IDs\")),\n@@ -267,5 +276,5 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \telse if (verify)\n \t\treturn cmd_show_ref__verify(&show_one_opts, argv);\n \telse\n-\t\treturn cmd_show_ref__patterns(&show_one_opts, argv);\n+\t\treturn cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);\n }\n-- \n2.42.0\n\n"},{"id":"483905","messageId":"5ba566723e8742e6df150b12f1d044089ff62b59.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:56:57Z","receivedAt":"2023-10-26T09:57:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The git-show-ref(1) command has three different modes, of which one is\nimplicit and the other two can be chosen explicitly by passing a flag.\nBut while these modes are standalone and cause us to execute completely\nseparate code paths, we gladly accept the case where a user asks for\nboth `--exclude-existing` and `--verify` at the same time even though it\nis not obvious what will happen. Spoiler: we ignore `--verify` and\nexecute the `--exclude-existing` mode.\n\nLet's explicitly detect this invalid usage and die in case both modes\nwere requested.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c  | 4 ++++\n t/t1403-show-ref.sh | 5 +++++\n 2 files changed, 9 insertions(+)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 87bc45d2d13..1768aef77b3 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -271,6 +271,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n+\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n+\t\tdie(_(\"only one of '%s' or '%s' can be given\"),\n+\t\t    \"--exclude-existing\", \"--verify\");\n+\n \tif (exclude_existing_opts.enabled)\n \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\ndiff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh\nindex 9252a581abf..4a90a88e05d 100755\n--- a/t/t1403-show-ref.sh\n+++ b/t/t1403-show-ref.sh\n@@ -196,4 +196,9 @@ test_expect_success 'show-ref --verify with dangling ref' '\n \t)\n '\n \n+test_expect_success 'show-ref sub-modes are mutually exclusive' '\n+\ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n+\tgrep \"only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\" err\n+'\n+\n test_done\n-- \n2.42.0\n\n"},{"id":"483906","messageId":"b78ccc5f69297fec7807c077ccdcf722ee12edf3.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 10/12] builtin/show-ref: explicitly spell out different modes in synopsis","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:57:01Z","receivedAt":"2023-10-26T09:57:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The synopsis treats the `--verify` and the implicit mode the same. They\nare slightly different though:\n\n    - They accept different sets of flags.\n\n    - The implicit mode accepts patterns while the `--verify` mode\n      accepts references.\n\nSplit up the synopsis for these two modes such that we can disambiguate\nthose differences.\n\nWhile at it, drop \"--quiet\" from the pattern mode's synopsis. It does\nnot make a lot of sense to list patterns, but squelch the listing output\nitself. The description for \"--quiet\" is adapted accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-show-ref.txt | 9 ++++++---\n builtin/show-ref.c             | 5 ++++-\n 2 files changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\nindex 2fe274b8faa..22f5ebc6a92 100644\n--- a/Documentation/git-show-ref.txt\n+++ b/Documentation/git-show-ref.txt\n@@ -8,9 +8,12 @@ git-show-ref - List references in a local repository\n SYNOPSIS\n --------\n [verse]\n-'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]\n+'git show-ref' [--head] [-d | --dereference]\n \t     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n \t     [--heads] [--] [<pattern>...]\n+'git show-ref' --verify [-q | --quiet] [-d | --dereference]\n+\t     [-s | --hash[=<n>]] [--abbrev[=<n>]]\n+\t     [--] [<ref>...]\n 'git show-ref' --exclude-existing[=<pattern>]\n \n DESCRIPTION\n@@ -70,8 +73,8 @@ OPTIONS\n -q::\n --quiet::\n \n-\tDo not print any results to stdout. When combined with `--verify`, this\n-\tcan be used to silently check if a reference exists.\n+\tDo not print any results to stdout. Can be used with `--verify` to\n+\tsilently check if a reference exists.\n \n --exclude-existing[=<pattern>]::\n \ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 1768aef77b3..d4561d7ce1f 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -11,9 +11,12 @@\n #include \"parse-options.h\"\n \n static const char * const show_ref_usage[] = {\n-\tN_(\"git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference]\\n\"\n+\tN_(\"git show-ref [--head] [-d | --dereference]\\n\"\n \t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\\n\"\n \t   \"             [--heads] [--] [<pattern>...]\"),\n+\tN_(\"git show-ref --verify [-q | --quiet] [-d | --dereference]\\n\"\n+\t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]]\\n\"\n+\t   \"             [--] [<ref>...]\"),\n \tN_(\"git show-ref --exclude-existing[=<pattern>]\"),\n \tNULL\n };\n-- \n2.42.0\n\n"},{"id":"483907","messageId":"327942b116293593deca668842a4f32db7acb0e5.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 11/12] builtin/show-ref: add new mode to check for reference existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:57:06Z","receivedAt":"2023-10-26T09:57:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we have multiple ways to show the value of a given reference, we\ndo not have any way to check whether a reference exists at all. While\ncommands like git-rev-parse(1) or git-show-ref(1) can be used to check\nfor reference existence in case the reference resolves to something\nsane, neither of them can be used to check for existence in some other\nscenarios where the reference does not resolve cleanly:\n\n    - References which have an invalid name cannot be resolved.\n\n    - References to nonexistent objects cannot be resolved.\n\n    - Dangling symrefs can be resolved via git-symbolic-ref(1), but this\n      requires the caller to special case existence checks depending on\n      whether or not a reference is symbolic or direct.\n\nFurthermore, git-rev-list(1) and other commands do not let the caller\ndistinguish easily between an actually missing reference and a generic\nerror.\n\nTaken together, this seems like sufficient motivation to introduce a\nseparate plumbing command to explicitly check for the existence of a\nreference without trying to resolve its contents.\n\nThis new command comes in the form of `git show-ref --exists`. This\nnew mode will exit successfully when the reference exists, with a\nspecific exit code of 2 when it does not exist, or with 1 when there\nhas been a generic error.\n\nNote that the only way to properly implement this command is by using\nthe internal `refs_read_raw_ref()` function. While the public function\n`refs_resolve_ref_unsafe()` can be made to behave in the same way by\npassing various flags, it does not provide any way to obtain the errno\nwith which the reference backend failed when reading the reference. As\nsuch, it becomes impossible for us to distinguish generic errors from\nthe explicit case where the reference wasn't found.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-show-ref.txt | 11 ++++++\n builtin/show-ref.c             | 49 ++++++++++++++++++++++---\n t/t1403-show-ref.sh            | 67 +++++++++++++++++++++++++++++++++-\n 3 files changed, 121 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\nindex 22f5ebc6a92..8fecc9e80f6 100644\n--- a/Documentation/git-show-ref.txt\n+++ b/Documentation/git-show-ref.txt\n@@ -15,6 +15,7 @@ SYNOPSIS\n \t     [-s | --hash[=<n>]] [--abbrev[=<n>]]\n \t     [--] [<ref>...]\n 'git show-ref' --exclude-existing[=<pattern>]\n+'git show-ref' --exists <ref>\n \n DESCRIPTION\n -----------\n@@ -30,6 +31,10 @@ The `--exclude-existing` form is a filter that does the inverse. It reads\n refs from stdin, one ref per line, and shows those that don't exist in\n the local repository.\n \n+The `--exists` form can be used to check for the existence of a single\n+references. This form does not verify whether the reference resolves to an\n+actual object.\n+\n Use of this utility is encouraged in favor of directly accessing files under\n the `.git` directory.\n \n@@ -65,6 +70,12 @@ OPTIONS\n \tAside from returning an error code of 1, it will also print an error\n \tmessage if `--quiet` was not specified.\n \n+--exists::\n+\n+\tCheck whether the given reference exists. Returns an exit code of 0 if\n+\tit does, 2 if it is missing, and 1 in case looking up the reference\n+\tfailed with an error other than the reference being missing.\n+\n --abbrev[=<n>]::\n \n \tAbbreviate the object name.  When using `--hash`, you do\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex d4561d7ce1f..2cbe4e3f721 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -2,7 +2,7 @@\n #include \"config.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n-#include \"refs.h\"\n+#include \"refs/refs-internal.h\"\n #include \"object-name.h\"\n #include \"object-store-ll.h\"\n #include \"object.h\"\n@@ -18,6 +18,7 @@ static const char * const show_ref_usage[] = {\n \t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]]\\n\"\n \t   \"             [--] [<ref>...]\"),\n \tN_(\"git show-ref --exclude-existing[=<pattern>]\"),\n+\tN_(\"git show-ref --exists <ref>\"),\n \tNULL\n };\n \n@@ -216,6 +217,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,\n \treturn 0;\n }\n \n+static int cmd_show_ref__exists(const char **refs)\n+{\n+\tstruct strbuf unused_referent = STRBUF_INIT;\n+\tstruct object_id unused_oid;\n+\tunsigned int unused_type;\n+\tint failure_errno = 0;\n+\tconst char *ref;\n+\tint ret = 0;\n+\n+\tif (!refs || !*refs)\n+\t\tdie(\"--exists requires a reference\");\n+\tref = *refs++;\n+\tif (*refs)\n+\t\tdie(\"--exists requires exactly one reference\");\n+\n+\tif (refs_read_raw_ref(get_main_ref_store(the_repository), ref,\n+\t\t\t      &unused_oid, &unused_referent, &unused_type,\n+\t\t\t      &failure_errno)) {\n+\t\tif (failure_errno == ENOENT) {\n+\t\t\terror(_(\"reference does not exist\"));\n+\t\t\tret = 2;\n+\t\t} else {\n+\t\t\terrno = failure_errno;\n+\t\t\terror_errno(_(\"failed to look up reference\"));\n+\t\t\tret = 1;\n+\t\t}\n+\n+\t\tgoto out;\n+\t}\n+\n+out:\n+\tstrbuf_release(&unused_referent);\n+\treturn ret;\n+}\n+\n static int hash_callback(const struct option *opt, const char *arg, int unset)\n {\n \tstruct show_one_options *opts = opt->value;\n@@ -245,10 +281,11 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \tstruct exclude_existing_options exclude_existing_opts = {0};\n \tstruct patterns_options patterns_opts = {0};\n \tstruct show_one_options show_one_opts = {0};\n-\tint verify = 0;\n+\tint verify = 0, exists = 0;\n \tconst struct option show_ref_options[] = {\n \t\tOPT_BOOL(0, \"tags\", &patterns_opts.tags_only, N_(\"only show tags (can be combined with heads)\")),\n \t\tOPT_BOOL(0, \"heads\", &patterns_opts.heads_only, N_(\"only show heads (can be combined with tags)\")),\n+\t\tOPT_BOOL(0, \"exists\", &exists, N_(\"check for reference existence without resolving\")),\n \t\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n \t\t\t    \"requires exact ref path\")),\n \t\tOPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,\n@@ -274,14 +311,16 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n-\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n-\t\tdie(_(\"only one of '%s' or '%s' can be given\"),\n-\t\t    \"--exclude-existing\", \"--verify\");\n+\tif ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)\n+\t\tdie(_(\"only one of '%s', '%s' or '%s' can be given\"),\n+\t\t    \"--exclude-existing\", \"--verify\", \"--exists\");\n \n \tif (exclude_existing_opts.enabled)\n \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\n \t\treturn cmd_show_ref__verify(&show_one_opts, argv);\n+\telse if (exists)\n+\t\treturn cmd_show_ref__exists(argv);\n \telse\n \t\treturn cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);\n }\ndiff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh\nindex 4a90a88e05d..b50ae6fcf11 100755\n--- a/t/t1403-show-ref.sh\n+++ b/t/t1403-show-ref.sh\n@@ -197,8 +197,73 @@ test_expect_success 'show-ref --verify with dangling ref' '\n '\n \n test_expect_success 'show-ref sub-modes are mutually exclusive' '\n+\tcat >expect <<-EOF &&\n+\tfatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given\n+\tEOF\n+\n \ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n-\tgrep \"only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\" err\n+\ttest_cmp expect err &&\n+\n+\ttest_must_fail git show-ref --verify --exists 2>err &&\n+\ttest_cmp expect err &&\n+\n+\ttest_must_fail git show-ref --exclude-existing --exists 2>err &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success '--exists with existing reference' '\n+\tgit show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+'\n+\n+test_expect_success '--exists with missing reference' '\n+\ttest_expect_code 2 git show-ref --exists refs/heads/does-not-exist\n+'\n+\n+test_expect_success '--exists does not use DWIM' '\n+\ttest_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&\n+\tgrep \"reference does not exist\" err\n+'\n+\n+test_expect_success '--exists with HEAD' '\n+\tgit show-ref --exists HEAD\n+'\n+\n+test_expect_success '--exists with bad reference name' '\n+\ttest_when_finished \"git update-ref -d refs/heads/bad...name\" &&\n+\tnew_oid=$(git rev-parse HEAD) &&\n+\ttest-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&\n+\tgit show-ref --exists refs/heads/bad...name\n+'\n+\n+test_expect_success '--exists with arbitrary symref' '\n+\ttest_when_finished \"git symbolic-ref -d refs/symref\" &&\n+\tgit symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&\n+\tgit show-ref --exists refs/symref\n+'\n+\n+test_expect_success '--exists with dangling symref' '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/dangling\" &&\n+\tgit symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&\n+\tgit show-ref --exists refs/heads/dangling\n+'\n+\n+test_expect_success '--exists with nonexistent object ID' '\n+\ttest-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&\n+\tgit show-ref --exists refs/heads/missing-oid\n+'\n+\n+test_expect_success '--exists with non-commit object' '\n+\ttree_oid=$(git rev-parse HEAD^{tree}) &&\n+\ttest-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&\n+\tgit show-ref --exists refs/heads/tree\n+'\n+\n+test_expect_success '--exists with directory fails with generic error' '\n+\tcat >expect <<-EOF &&\n+\terror: failed to look up reference: Is a directory\n+\tEOF\n+\ttest_expect_code 1 git show-ref --exists refs/heads 2>err &&\n+\ttest_cmp expect err\n '\n \n test_done\n-- \n2.42.0\n\n"},{"id":"483908","messageId":"226731c5f18abbbff8fd23cc0f1263b3970ff5eb.1698314128.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"[PATCH v2 12/12] t: use git-show-ref(1) to check for ref existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-26T09:57:10Z","receivedAt":"2023-10-26T09:57:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Convert tests that use `test_path_is_file` and `test_path_is_missing` to\ninstead use a set of helpers `test_ref_exists` and `test_ref_missing`.\nThese helpers are implemented via the newly introduced `git show-ref\n--exists` command. Thus, we can avoid intimate knowledge of how the ref\nbackend stores references on disk.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t1430-bad-ref-name.sh | 27 +++++++++++++-------\n t/t3200-branch.sh       | 33 ++++++++++++++-----------\n t/t5521-pull-options.sh |  4 +--\n t/t5605-clone-local.sh  |  2 +-\n t/test-lib-functions.sh | 55 +++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 94 insertions(+), 27 deletions(-)\n\ndiff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh\nindex ff1c967d550..7b7d6953c62 100755\n--- a/t/t1430-bad-ref-name.sh\n+++ b/t/t1430-bad-ref-name.sh\n@@ -205,8 +205,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref to broken name'\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref\" &&\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit update-ref --no-deref -d refs/heads/badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -216,8 +217,9 @@ test_expect_success 'branch -d can delete symref to broken name' '\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref\" &&\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit branch -d badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_i18ngrep \"Deleted branch badname (was refs/heads/broken\\.\\.\\.ref)\" output &&\n \ttest_must_be_empty error\n '\n@@ -225,8 +227,9 @@ test_expect_success 'branch -d can delete symref to broken name' '\n test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' '\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit update-ref --no-deref -d refs/heads/badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -234,8 +237,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref to brok\n test_expect_success 'branch -d can delete dangling symref to broken name' '\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit branch -d badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_i18ngrep \"Deleted branch badname (was refs/heads/broken\\.\\.\\.ref)\" output &&\n \ttest_must_be_empty error\n '\n@@ -245,8 +249,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref\" &&\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/broken...ref &&\n \tgit update-ref -d refs/heads/badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...ref &&\n+\ttest_ref_missing refs/heads/broken...ref &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -254,8 +259,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '\n test_expect_success 'update-ref --no-deref -d can delete symref with broken name' '\n \tprintf \"ref: refs/heads/main\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -263,8 +269,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref with broken name\n test_expect_success 'branch -d can delete symref with broken name' '\n \tprintf \"ref: refs/heads/main\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit branch -d broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_i18ngrep \"Deleted branch broken...symref (was refs/heads/main)\" output &&\n \ttest_must_be_empty error\n '\n@@ -272,8 +279,9 @@ test_expect_success 'branch -d can delete symref with broken name' '\n test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' '\n \tprintf \"ref: refs/heads/idonotexist\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -281,8 +289,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref with br\n test_expect_success 'branch -d can delete dangling symref with broken name' '\n \tprintf \"ref: refs/heads/idonotexist\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit branch -d broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_i18ngrep \"Deleted branch broken...symref (was refs/heads/idonotexist)\" output &&\n \ttest_must_be_empty error\n '\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 080e4f24a6e..bde4f1485b7 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -25,7 +25,7 @@ test_expect_success 'prepare a trivial repository' '\n \n test_expect_success 'git branch --help should not have created a bogus branch' '\n \ttest_might_fail git branch --man --help </dev/null >/dev/null 2>&1 &&\n-\ttest_path_is_missing .git/refs/heads/--help\n+\ttest_ref_missing refs/heads/--help\n '\n \n test_expect_success 'branch -h in broken repository' '\n@@ -40,7 +40,8 @@ test_expect_success 'branch -h in broken repository' '\n '\n \n test_expect_success 'git branch abc should create a branch' '\n-\tgit branch abc && test_path_is_file .git/refs/heads/abc\n+\tgit branch abc &&\n+\ttest_ref_exists refs/heads/abc\n '\n \n test_expect_success 'git branch abc should fail when abc exists' '\n@@ -61,11 +62,13 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '\n '\n \n test_expect_success 'git branch a/b/c should create a branch' '\n-\tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n+\tgit branch a/b/c &&\n+\ttest_ref_exists refs/heads/a/b/c\n '\n \n test_expect_success 'git branch mb main... should create a branch' '\n-\tgit branch mb main... && test_path_is_file .git/refs/heads/mb\n+\tgit branch mb main... &&\n+\ttest_ref_exists refs/heads/mb\n '\n \n test_expect_success 'git branch HEAD should fail' '\n@@ -78,14 +81,14 @@ EOF\n test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' '\n \tGIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n \tgit -c core.logallrefupdates=false branch --create-reflog d/e/f &&\n-\ttest_path_is_file .git/refs/heads/d/e/f &&\n+\ttest_ref_exists refs/heads/d/e/f &&\n \ttest_path_is_file .git/logs/refs/heads/d/e/f &&\n \ttest_cmp expect .git/logs/refs/heads/d/e/f\n '\n \n test_expect_success 'git branch -d d/e/f should delete a branch and a log' '\n \tgit branch -d d/e/f &&\n-\ttest_path_is_missing .git/refs/heads/d/e/f &&\n+\ttest_ref_missing refs/heads/d/e/f &&\n \ttest_must_fail git reflog exists refs/heads/d/e/f\n '\n \n@@ -213,7 +216,7 @@ test_expect_success 'git branch -M should leave orphaned HEAD alone' '\n \t\ttest_commit initial &&\n \t\tgit checkout --orphan lonely &&\n \t\tgrep lonely .git/HEAD &&\n-\t\ttest_path_is_missing .git/refs/head/lonely &&\n+\t\ttest_ref_missing refs/head/lonely &&\n \t\tgit branch -M main mistress &&\n \t\tgrep lonely .git/HEAD\n \t)\n@@ -799,8 +802,8 @@ test_expect_success 'deleting a symref' '\n \tgit symbolic-ref refs/heads/symref refs/heads/target &&\n \techo \"Deleted branch symref (was refs/heads/target).\" >expect &&\n \tgit branch -d symref >actual &&\n-\ttest_path_is_file .git/refs/heads/target &&\n-\ttest_path_is_missing .git/refs/heads/symref &&\n+\ttest_ref_exists refs/heads/target &&\n+\ttest_ref_missing refs/heads/symref &&\n \ttest_cmp expect actual\n '\n \n@@ -809,16 +812,16 @@ test_expect_success 'deleting a dangling symref' '\n \ttest_path_is_file .git/refs/heads/dangling-symref &&\n \techo \"Deleted branch dangling-symref (was nowhere).\" >expect &&\n \tgit branch -d dangling-symref >actual &&\n-\ttest_path_is_missing .git/refs/heads/dangling-symref &&\n+\ttest_ref_missing refs/heads/dangling-symref &&\n \ttest_cmp expect actual\n '\n \n test_expect_success 'deleting a self-referential symref' '\n \tgit symbolic-ref refs/heads/self-reference refs/heads/self-reference &&\n-\ttest_path_is_file .git/refs/heads/self-reference &&\n+\ttest_ref_exists refs/heads/self-reference &&\n \techo \"Deleted branch self-reference (was refs/heads/self-reference).\" >expect &&\n \tgit branch -d self-reference >actual &&\n-\ttest_path_is_missing .git/refs/heads/self-reference &&\n+\ttest_ref_missing refs/heads/self-reference &&\n \ttest_cmp expect actual\n '\n \n@@ -826,8 +829,8 @@ test_expect_success 'renaming a symref is not allowed' '\n \tgit symbolic-ref refs/heads/topic refs/heads/main &&\n \ttest_must_fail git branch -m topic new-topic &&\n \tgit symbolic-ref refs/heads/topic &&\n-\ttest_path_is_file .git/refs/heads/main &&\n-\ttest_path_is_missing .git/refs/heads/new-topic\n+\ttest_ref_exists refs/heads/main &&\n+\ttest_ref_missing refs/heads/new-topic\n '\n \n test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '\n@@ -1142,7 +1145,7 @@ EOF\n test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '\n \tGIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n \tgit checkout -b g/h/i -l main &&\n-\ttest_path_is_file .git/refs/heads/g/h/i &&\n+\ttest_ref_exists refs/heads/g/h/i &&\n \ttest_path_is_file .git/logs/refs/heads/g/h/i &&\n \ttest_cmp expect .git/logs/refs/heads/g/h/i\n '\ndiff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh\nindex 079b2f2536e..3681859f983 100755\n--- a/t/t5521-pull-options.sh\n+++ b/t/t5521-pull-options.sh\n@@ -143,7 +143,7 @@ test_expect_success 'git pull --dry-run' '\n \t\tcd clonedry &&\n \t\tgit pull --dry-run ../parent &&\n \t\ttest_path_is_missing .git/FETCH_HEAD &&\n-\t\ttest_path_is_missing .git/refs/heads/main &&\n+\t\ttest_ref_missing refs/heads/main &&\n \t\ttest_path_is_missing .git/index &&\n \t\ttest_path_is_missing file\n \t)\n@@ -157,7 +157,7 @@ test_expect_success 'git pull --all --dry-run' '\n \t\tgit remote add origin ../parent &&\n \t\tgit pull --all --dry-run &&\n \t\ttest_path_is_missing .git/FETCH_HEAD &&\n-\t\ttest_path_is_missing .git/refs/remotes/origin/main &&\n+\t\ttest_ref_missing refs/remotes/origin/main &&\n \t\ttest_path_is_missing .git/index &&\n \t\ttest_path_is_missing file\n \t)\ndiff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh\nindex 1d7b1abda1a..946c5751885 100755\n--- a/t/t5605-clone-local.sh\n+++ b/t/t5605-clone-local.sh\n@@ -69,7 +69,7 @@ test_expect_success 'local clone of repo with nonexistent ref in HEAD' '\n \tgit clone a d &&\n \t(cd d &&\n \tgit fetch &&\n-\ttest ! -e .git/refs/remotes/origin/HEAD)\n+\ttest_ref_missing refs/remotes/origin/HEAD)\n '\n \n test_expect_success 'bundle clone without .bundle suffix' '\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 2f8868caa17..56b33536ed1 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -251,6 +251,61 @@ debug () {\n \tdone\n }\n \n+# Usage: test_ref_exists [options] <ref>\n+#\n+#   -C <dir>:\n+#      Run all git commands in directory <dir>\n+#\n+# This helper function checks whether a reference exists. Symrefs or object IDs\n+# will not be resolved. Can be used to check references with bad names.\n+test_ref_exists () {\n+\tlocal indir=\n+\n+\twhile test $# != 0\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t-C)\n+\t\t\tindir=\"$2\"\n+\t\t\tshift\n+\t\t\t;;\n+\t\t*)\n+\t\t\tbreak\n+\t\t\t;;\n+\t\tesac\n+\t\tshift\n+\tdone &&\n+\n+\tindir=${indir:+\"$indir\"/} &&\n+\n+\tif test \"$#\" != 1\n+\tthen\n+\t\tBUG \"expected exactly one reference\"\n+\tfi &&\n+\n+\tgit ${indir:+ -C \"$indir\"} show-ref --exists \"$1\"\n+}\n+\n+# Behaves the same as test_ref_exists, except that it checks for the absence of\n+# a reference. This is preferable to `! test_ref_exists` as this function is\n+# able to distinguish actually-missing references from other, generic errors.\n+test_ref_missing () {\n+\ttest_ref_exists \"$@\"\n+\tcase \"$?\" in\n+\t2)\n+\t\t# This is the good case.\n+\t\treturn 0\n+\t\t;;\n+\t0)\n+\t\techo >&4 \"test_ref_missing: reference exists\"\n+\t\treturn 1\n+\t\t;;\n+\t*)\n+\t\techo >&4 \"test_ref_missing: generic error\"\n+\t\treturn 1\n+\t\t;;\n+\tesac\n+}\n+\n # Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]\n #   -C <dir>:\n #\tRun all git commands in directory <dir>\n-- \n2.42.0\n\n"},{"id":"483984","messageId":"dc2b850b-8b58-414b-ad99-4c2e0cb1d63a@gmail.com","threadId":"60421","inReplyTo":"ZTo2Aq2wAxx_6Laz@tanuki","subject":"Re: [PATCH 00/12] show-ref: introduce mode to check for ref existence","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-10-27T13:06:34Z","receivedAt":"2023-10-27T13:06:39Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 26/10/2023 10:48, Patrick Steinhardt wrote:\n> On Wed, Oct 25, 2023 at 03:44:33PM +0100, Phillip Wood wrote:\n>> On 25/10/2023 15:26, Han-Wen Nienhuys wrote:\n>>> On Tue, Oct 24, 2023 at 9:17 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>>>\n>>>> Patrick Steinhardt <ps@pks.im> writes:\n>>>>\n>>>>> this patch series introduces a new `--exists` mode to git-show-ref(1) to\n>>>>> explicitly check for the existence of a reference, only.\n>>>>\n>>>> I agree that show-ref would be the best place for this feature (not\n>>>> rev-parse, which is already a kitchen sink).  After all, the command\n>>>> was designed for validating refs in 358ddb62 (Add \"git show-ref\"\n>>>> builtin command, 2006-09-15).\n>>>>\n>>>> Thanks.  Hopefully I can take a look before I go offline.\n>>>\n>>> The series description doesn't say why users would care about this.\n>>>\n>>> If this is just to ease testing, I suggest adding functionality to a\n>>> suitable test helper. Anything you add to git-show-ref is a publicly\n>>> visible API that needs documentation and comes with a stability\n>>> guarantee that is more expensive to maintain than test helper\n>>> functionality.\n>>\n>> Does the new functionality provide a way for scripts to see if a branch is\n>> unborn (i.e. has not commits yet)? I don't think we have a way to\n>> distinguish between a ref that points to a missing object and an unborn\n>> branch at the moment.\n> \n> You could do it with two commands:\n> \n> ```\n> target=$(git symbolic-ref HEAD)\n> git show-ref --exists \"$target\"\n> case \"$?\" in\n> 2)\n>      echo \"unborn branch\";;\n> 0)\n>      echo \"branch exists\";;\n> *)\n>      echo \"could be anything, dunno\";;\n> esac\n> ```\n> \n> While you could use git-rev-parse(1) instead of git-show-ref(1), you\n> wouldn't be able to easily distinguish the case of a missing target\n> reference and any other kind of error.\n\nThanks, it is helpful to have an example use outside of the test suite.\n\nBest Wishes\n\nPhillip\n"},{"id":"484136","messageId":"ZT/xnsoAs5cK+bsU@nand.local","threadId":"60421","inReplyTo":"bb0d656a0b40b79c90dc2505239976a93f18f432.1698314128.git.ps@pks.im","subject":"Re: [PATCH v2 03/12] builtin/show-ref: fix leaking string buffer","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-30T18:10:38Z","receivedAt":"2023-10-30T18:10:42Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Oct 26, 2023 at 11:56:29AM +0200, Patrick Steinhardt wrote:\n> Fix a leaking string buffer in `git show-ref --exclude-existing`. While\n> the buffer is technically not leaking because its variable is declared\n> as static, there is no inherent reason why it should be.\n\nWell spotted and fixed. I ran the test suite in GIT_TEST_PASSING_SANITIZE_LEAK's\n\"check\" mode and didn't find anything that was made leak-free by this\npatch not already marked as such. So this (and the rest of the series up\nto this point) LGTM.\n\nThanks,\nTaylor\n"},{"id":"484138","messageId":"ZT/01cjmdWrSXs+c@nand.local","threadId":"60421","inReplyTo":"87afcee830caab2782ce693c1f961df6fea6e7b5.1698314128.git.ps@pks.im","subject":"Re: [PATCH v2 04/12] builtin/show-ref: fix dead code when passing patterns","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-30T18:24:21Z","receivedAt":"2023-10-30T18:24:24Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Oct 26, 2023 at 11:56:33AM +0200, Patrick Steinhardt wrote:\n> When passing patterns to `git show-ref` we have some code that will\n> cause us to die if `verify && !quiet` is true. But because `verify`\n> indicates a different subcommand of git-show-ref(1) that causes us to\n> execute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the\n> condition cannot ever be true.\n>\n> Let's remove this dead code.\n\nMakes sense. Let's read on...\n\nThanks,\nTaylor\n"},{"id":"484139","messageId":"ZT/32jI62GQKPlcp@nand.local","threadId":"60421","inReplyTo":"bed2a8a07696371e07c0b2d1282ed51c0b1b9fee.1698314128.git.ps@pks.im","subject":"Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-30T18:37:14Z","receivedAt":"2023-10-30T18:37:20Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:\n> It's not immediately obvious options which options are applicable to\n> what subcommand in git-show-ref(1) because all options exist as global\n> state. This can easily cause confusion for the reader.\n>\n> Refactor options for the `--exclude-existing` subcommand to be contained\n> in a separate structure. This structure is stored on the stack and\n> passed down as required. Consequently, it clearly delimits the scope of\n> those options and requires the reader to worry less about global state.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n\nAll makes sense, but...\n\n> @@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {\n>  };\n>\n>  static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n> -\t   quiet, hash_only, abbrev, exclude_arg;\n> -static const char *exclude_existing_arg;\n> +\t   quiet, hash_only, abbrev;\n>\n>  static void show_one(const char *refname, const struct object_id *oid)\n>  {\n> @@ -95,6 +94,11 @@ static int add_existing(const char *refname,\n>  \treturn 0;\n>  }\n>\n> +struct exclude_existing_options {\n> +\tint enabled;\n\n...do we need an .enabled here? I think checking whether or not .pattern\nis NULL is sufficient, but perhaps there is another use of .enabled\nlater on in the series...\n\nThanks,\nTaylor\n"},{"id":"484140","messageId":"ZT/8GbzAmBq0aRIK@nand.local","threadId":"60421","inReplyTo":"bed2a8a07696371e07c0b2d1282ed51c0b1b9fee.1698314128.git.ps@pks.im","subject":"Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-30T18:55:21Z","receivedAt":"2023-10-30T18:55:28Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:\n> @@ -95,6 +94,11 @@ static int add_existing(const char *refname,\n>  \treturn 0;\n>  }\n>\n> +struct exclude_existing_options {\n> +\tint enabled;\n> +\tconst char *pattern;\n> +};\n> +\n\nThinking on my earlier suggestion more, I wondered if using the\nOPT_SUBCOMMAND() function might make things easier to organize and\neliminate the need for things like .enabled or having to define structs\nfor each of the sub-commands.\n\nBut I don't think that this is (easily) possible to do, since\n`--exclude-existing` is behind a command-line option, not a separate\nmode (e.g. \"commit-graph verify\", not \"commit-graph --verify\"). So I\nthink you *could* make it work with some combination of OPT_SUBCOMMAND\nand callbacks to set the function pointer yourself when given the\n`--exclude-existing` option. But I think that's sufficiently gross as to\nnot be worth it.\n\nThanks,\nTaylor\n"},{"id":"484141","messageId":"ZUAAmY4aKGrvSDp8@nand.local","threadId":"60421","inReplyTo":"d52a5e8ced2adc5c9315edea9fc497d1ffa30125.1698314128.git.ps@pks.im","subject":"Re: [PATCH v2 06/12] builtin/show-ref: stop using global variable to count matches","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-30T19:14:33Z","receivedAt":"2023-10-30T19:14:39Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Oct 26, 2023 at 11:56:42AM +0200, Patrick Steinhardt wrote:\n> When passing patterns to git-show-ref(1) we're checking whether any\n> reference matches -- if none does, we indicate this condition via an\n> unsuccessful exit code.\n\ns/does/do, but not a big enough deal to reroll IMHO.\n\n> We're using a global variable to count these matches, which is required\n> because the counter is getting incremented in a callback function. But\n> now that we have the `struct show_ref_data` in place, we can get rid of\n> the global variable and put the counter in there instead.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/show-ref.c | 7 ++++---\n>  1 file changed, 4 insertions(+), 3 deletions(-)\n\nLooks all good to me!\n\nThanks,\nTaylor\n"},{"id":"484142","messageId":"ZUAElIb7mjoBBRcn@nand.local","threadId":"60421","inReplyTo":"5ba566723e8742e6df150b12f1d044089ff62b59.1698314128.git.ps@pks.im","subject":"Re: [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-30T19:31:32Z","receivedAt":"2023-10-30T19:31:35Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Oct 26, 2023 at 11:56:57AM +0200, Patrick Steinhardt wrote:\n> The git-show-ref(1) command has three different modes, of which one is\n> implicit and the other two can be chosen explicitly by passing a flag.\n> But while these modes are standalone and cause us to execute completely\n> separate code paths, we gladly accept the case where a user asks for\n> both `--exclude-existing` and `--verify` at the same time even though it\n> is not obvious what will happen. Spoiler: we ignore `--verify` and\n> execute the `--exclude-existing` mode.\n>\n> Let's explicitly detect this invalid usage and die in case both modes\n> were requested.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/show-ref.c  | 4 ++++\n>  t/t1403-show-ref.sh | 5 +++++\n>  2 files changed, 9 insertions(+)\n>\n> diff --git a/builtin/show-ref.c b/builtin/show-ref.c\n> index 87bc45d2d13..1768aef77b3 100644\n> --- a/builtin/show-ref.c\n> +++ b/builtin/show-ref.c\n> @@ -271,6 +271,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n>  \targc = parse_options(argc, argv, prefix, show_ref_options,\n>  \t\t\t     show_ref_usage, 0);\n>\n> +\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n> +\t\tdie(_(\"only one of '%s' or '%s' can be given\"),\n> +\t\t    \"--exclude-existing\", \"--verify\");\n> +\n\nThis is technically correct, but I was surprised to see it written this\nway instead of\n\n    if (exclude_existing_opts.enabled && verify)\n        die(...);\n\nI don't think it's a big deal either way, I was just curious why you\nchose one over the other.\n\n> +test_expect_success 'show-ref sub-modes are mutually exclusive' '\n> +\ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n> +\tgrep \"only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\" err\n> +'\n\ngrepping is fine here, but since you have the exact error message, it\nmay be worth switching to test_cmp.\n\nThanks,\nTaylor\n"},{"id":"484143","messageId":"ZUAEyc0i6iISekiM@nand.local","threadId":"60421","inReplyTo":"cover.1698314128.git.ps@pks.im","subject":"Re: [PATCH v2 00/12] show-ref: introduce mode to check for ref existence","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-30T19:32:25Z","receivedAt":"2023-10-30T19:32:29Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Oct 26, 2023 at 11:56:16AM +0200, Patrick Steinhardt wrote:\n> Hi,\n>\n> this is the second version of my patch series that introduces a new `git\n> show-ref --exists` mode to check for reference existence.\n\nAll looks quite reasonable to me. I'm happy with this series as-is,\nthough I left a few minor comments and suggestions throughout.\n\nThanks,\nTaylor\n"},{"id":"484174","messageId":"xmqqv8an5xyf.fsf@gitster.g","threadId":"60421","inReplyTo":"ZUAEyc0i6iISekiM@nand.local","subject":"Re: [PATCH v2 00/12] show-ref: introduce mode to check for ref existence","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-10-31T02:26:32Z","receivedAt":"2023-10-31T02:26:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n> On Thu, Oct 26, 2023 at 11:56:16AM +0200, Patrick Steinhardt wrote:\n>> Hi,\n>>\n>> this is the second version of my patch series that introduces a new `git\n>> show-ref --exists` mode to check for reference existence.\n>\n> All looks quite reasonable to me. I'm happy with this series as-is,\n> though I left a few minor comments and suggestions throughout.\n\nThanks.  I'll wait for hearing from Patrick and then decide ;-)\n\n"},{"id":"484206","messageId":"ZUC2cXiS949_7r4s@tanuki","threadId":"60421","inReplyTo":"ZT/32jI62GQKPlcp@nand.local","subject":"Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:10:25Z","receivedAt":"2023-10-31T08:10:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Oct 30, 2023 at 02:37:14PM -0400, Taylor Blau wrote:\n> On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:\n> > It's not immediately obvious options which options are applicable to\n> > what subcommand in git-show-ref(1) because all options exist as global\n> > state. This can easily cause confusion for the reader.\n> >\n> > Refactor options for the `--exclude-existing` subcommand to be contained\n> > in a separate structure. This structure is stored on the stack and\n> > passed down as required. Consequently, it clearly delimits the scope of\n> > those options and requires the reader to worry less about global state.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> \n> All makes sense, but...\n> \n> > @@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {\n> >  };\n> >\n> >  static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n> > -\t   quiet, hash_only, abbrev, exclude_arg;\n> > -static const char *exclude_existing_arg;\n> > +\t   quiet, hash_only, abbrev;\n> >\n> >  static void show_one(const char *refname, const struct object_id *oid)\n> >  {\n> > @@ -95,6 +94,11 @@ static int add_existing(const char *refname,\n> >  \treturn 0;\n> >  }\n> >\n> > +struct exclude_existing_options {\n> > +\tint enabled;\n> \n> ...do we need an .enabled here? I think checking whether or not .pattern\n> is NULL is sufficient, but perhaps there is another use of .enabled\n> later on in the series...\n\nThis is the second time that this question comes up, which is likely not\nall that surprising. Quoting my first reply:\n\nOn Wed, Oct 25, 2023 at 01:50:44PM +0200, Patrick Steinhardt wrote:\n> Yeah, we do. It's perfectly valid to pass `--exclude-existing` without\n> the optional pattern argument. We still want to use this mode in that\n> case, but don't populate the pattern.\n> \n> An alternative would be to assign something like a sentinel value in\n> here. But I'd think that it's clearer to instead have an explicit\n> separate field for this.\n\nAnyway, the fact that this question comes up again indicates that I need\nto comment this better.\n\nPatrick\n"},{"id":"484207","messageId":"ZUC2ePXa4t8OElhu@tanuki","threadId":"60421","inReplyTo":"ZT/8GbzAmBq0aRIK@nand.local","subject":"Re: [PATCH v2 05/12] builtin/show-ref: refactor `--exclude-existing` options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:10:32Z","receivedAt":"2023-10-31T08:10:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Oct 30, 2023 at 02:55:21PM -0400, Taylor Blau wrote:\n> On Thu, Oct 26, 2023 at 11:56:37AM +0200, Patrick Steinhardt wrote:\n> > @@ -95,6 +94,11 @@ static int add_existing(const char *refname,\n> >  \treturn 0;\n> >  }\n> >\n> > +struct exclude_existing_options {\n> > +\tint enabled;\n> > +\tconst char *pattern;\n> > +};\n> > +\n> \n> Thinking on my earlier suggestion more, I wondered if using the\n> OPT_SUBCOMMAND() function might make things easier to organize and\n> eliminate the need for things like .enabled or having to define structs\n> for each of the sub-commands.\n> \n> But I don't think that this is (easily) possible to do, since\n> `--exclude-existing` is behind a command-line option, not a separate\n> mode (e.g. \"commit-graph verify\", not \"commit-graph --verify\"). So I\n> think you *could* make it work with some combination of OPT_SUBCOMMAND\n> and callbacks to set the function pointer yourself when given the\n> `--exclude-existing` option. But I think that's sufficiently gross as to\n> not be worth it.\n\nYeah, agreed. Honestly, while working on this series I had the dream of\njust discarding git-show-ref(1) in favor of a new command with proper\nsubcommands as we tend to use them nowadays:\n\n    - `git references list <patterns>...` replaces `git show-ref\n      <pattern>`.\n\n    - `git references filter <pattern>` replaces `git show-ref\n      --exclude-existing` and filters references from stdin.\n\n    - `git references exists <ref>` checks whether a reference exists or\n      not and replaces `git show-ref --exists`.\n\nThis would make for a much more enjoyable UX. It'd also be a more\nnatural home for potential future additions:\n\n    - `git references show <ref>` allows you to show the contents of the\n      reference without resolving it, regardless of whether it's a\n      direct or a symbolic reference.\n\n    - `git references count <patterns>...` allows you to count refs\n      patching the pattern.\n\nI shied away though because it would be a much more controversial topic\nthat would potentially result in lots of bikeshedding. Now if everyone\nwas enthusiastic about this idea I'd still be happy to do it, even\nthough it derails the topic even further from its original intent to\njust fix a bunch of tests. But unless that happens, I'll continue to\nstick with the mediocre UI we have in git-show-ref(1).\n\nPatrick\n"},{"id":"484208","messageId":"ZUC2hhxOkWnzRWzC@tanuki","threadId":"60421","inReplyTo":"ZUAElIb7mjoBBRcn@nand.local","subject":"Re: [PATCH v2 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:10:46Z","receivedAt":"2023-10-31T08:10:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Oct 30, 2023 at 03:31:32PM -0400, Taylor Blau wrote:\n> On Thu, Oct 26, 2023 at 11:56:57AM +0200, Patrick Steinhardt wrote:\n> > The git-show-ref(1) command has three different modes, of which one is\n> > implicit and the other two can be chosen explicitly by passing a flag.\n> > But while these modes are standalone and cause us to execute completely\n> > separate code paths, we gladly accept the case where a user asks for\n> > both `--exclude-existing` and `--verify` at the same time even though it\n> > is not obvious what will happen. Spoiler: we ignore `--verify` and\n> > execute the `--exclude-existing` mode.\n> >\n> > Let's explicitly detect this invalid usage and die in case both modes\n> > were requested.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> >  builtin/show-ref.c  | 4 ++++\n> >  t/t1403-show-ref.sh | 5 +++++\n> >  2 files changed, 9 insertions(+)\n> >\n> > diff --git a/builtin/show-ref.c b/builtin/show-ref.c\n> > index 87bc45d2d13..1768aef77b3 100644\n> > --- a/builtin/show-ref.c\n> > +++ b/builtin/show-ref.c\n> > @@ -271,6 +271,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n> >  \targc = parse_options(argc, argv, prefix, show_ref_options,\n> >  \t\t\t     show_ref_usage, 0);\n> >\n> > +\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n> > +\t\tdie(_(\"only one of '%s' or '%s' can be given\"),\n> > +\t\t    \"--exclude-existing\", \"--verify\");\n> > +\n> \n> This is technically correct, but I was surprised to see it written this\n> way instead of\n> \n>     if (exclude_existing_opts.enabled && verify)\n>         die(...);\n> \n> I don't think it's a big deal either way, I was just curious why you\n> chose one over the other.\n\nHere it doesn't make a lot of sense yet, agreed. But once we add\n`exists` as a third mutually-exclusive option it does because of\ncombinatorial explosion.\n\n> > +test_expect_success 'show-ref sub-modes are mutually exclusive' '\n> > +\ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n> > +\tgrep \"only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\" err\n> > +'\n> \n> grepping is fine here, but since you have the exact error message, it\n> may be worth switching to test_cmp.\n\nGood point. Doubly so because I switch to `test_cmp` in a later patch.\nWill change.\n\nPatrick\n"},{"id":"484209","messageId":"cover.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698152926.git.ps@pks.im","subject":"[PATCH v3 00/12] builtin/show-ref: introduce mode to check for ref existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:08Z","receivedAt":"2023-10-31T08:16:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the third version of my patch series that introduces a new `git\nshow-ref --exists` mode to check for reference existence.\n\nChanges compared to v2:\n\n    - Patch 5: Document why we need `exclude_existing_options.enabled`,\n      which isn't exactly obvious.\n\n    - Patch 6: Fix a grammar issue in the commit message.\n\n    - Patch 9: Switch to `test_cmp` instead of grep(1).\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (12):\n  builtin/show-ref: convert pattern to a local variable\n  builtin/show-ref: split up different subcommands\n  builtin/show-ref: fix leaking string buffer\n  builtin/show-ref: fix dead code when passing patterns\n  builtin/show-ref: refactor `--exclude-existing` options\n  builtin/show-ref: stop using global variable to count matches\n  builtin/show-ref: stop using global vars for `show_one()`\n  builtin/show-ref: refactor options for patterns subcommand\n  builtin/show-ref: ensure mutual exclusiveness of subcommands\n  builtin/show-ref: explicitly spell out different modes in synopsis\n  builtin/show-ref: add new mode to check for reference existence\n  t: use git-show-ref(1) to check for ref existence\n\n Documentation/git-show-ref.txt |  20 ++-\n builtin/show-ref.c             | 284 ++++++++++++++++++++++-----------\n t/t1403-show-ref.sh            |  70 ++++++++\n t/t1430-bad-ref-name.sh        |  27 ++--\n t/t3200-branch.sh              |  33 ++--\n t/t5521-pull-options.sh        |   4 +-\n t/t5605-clone-local.sh         |   2 +-\n t/test-lib-functions.sh        |  55 +++++++\n 8 files changed, 373 insertions(+), 122 deletions(-)\n\nRange-diff against v2:\n 1:  78163accbd2 =  1:  9570ad63924 builtin/show-ref: convert pattern to a local variable\n 2:  9a234622d99 =  2:  773c6119750 builtin/show-ref: split up different subcommands\n 3:  bb0d656a0b4 =  3:  b6f4c0325bf builtin/show-ref: fix leaking string buffer\n 4:  87afcee830c =  4:  4605c6f0ac9 builtin/show-ref: fix dead code when passing patterns\n 5:  bed2a8a0769 !  5:  b47440089b6 builtin/show-ref: refactor `--exclude-existing` options\n    @@ builtin/show-ref.c: static int add_existing(const char *refname,\n      }\n      \n     +struct exclude_existing_options {\n    ++\t/*\n    ++\t * We need an explicit `enabled` field because it is perfectly valid\n    ++\t * for `pattern` to be `NULL` even if `--exclude-existing` was given.\n    ++\t */\n     +\tint enabled;\n     +\tconst char *pattern;\n     +};\n 6:  d52a5e8ced2 !  6:  6172888e465 builtin/show-ref: stop using global variable to count matches\n    @@ Commit message\n         builtin/show-ref: stop using global variable to count matches\n     \n         When passing patterns to git-show-ref(1) we're checking whether any\n    -    reference matches -- if none does, we indicate this condition via an\n    +    reference matches -- if none do, we indicate this condition via an\n         unsuccessful exit code.\n     \n         We're using a global variable to count these matches, which is required\n 7:  63f1dadf4c2 =  7:  bc528db7667 builtin/show-ref: stop using global vars for `show_one()`\n 8:  88dfeaa4871 =  8:  e3882c07dfc builtin/show-ref: refactor options for patterns subcommand\n 9:  5ba566723e8 !  9:  a095decd778 builtin/show-ref: ensure mutual exclusiveness of subcommands\n    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '\n      '\n      \n     +test_expect_success 'show-ref sub-modes are mutually exclusive' '\n    ++\tcat >expect <<-EOF &&\n    ++\tfatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\n    ++\tEOF\n    ++\n     +\ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n    -+\tgrep \"only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\" err\n    ++\ttest_cmp expect err\n     +'\n     +\n      test_done\n10:  b78ccc5f692 = 10:  087384fd2fd builtin/show-ref: explicitly spell out different modes in synopsis\n11:  327942b1162 ! 11:  ca5187bb18a builtin/show-ref: add new mode to check for reference existence\n    @@ builtin/show-ref.c: int cmd_show_ref(int argc, const char **argv, const char *pr\n     \n      ## t/t1403-show-ref.sh ##\n     @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '\n    - '\n      \n      test_expect_success 'show-ref sub-modes are mutually exclusive' '\n    -+\tcat >expect <<-EOF &&\n    + \tcat >expect <<-EOF &&\n    +-\tfatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\n     +\tfatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given\n    -+\tEOF\n    -+\n    + \tEOF\n    + \n      \ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n    --\tgrep \"only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\" err\n     +\ttest_cmp expect err &&\n     +\n     +\ttest_must_fail git show-ref --verify --exists 2>err &&\n    @@ t/t1403-show-ref.sh: test_expect_success 'show-ref --verify with dangling ref' '\n     +\terror: failed to look up reference: Is a directory\n     +\tEOF\n     +\ttest_expect_code 1 git show-ref --exists refs/heads 2>err &&\n    -+\ttest_cmp expect err\n    + \ttest_cmp expect err\n      '\n      \n    - test_done\n12:  226731c5f18 = 12:  ea9919fe899 t: use git-show-ref(1) to check for ref existence\n\nbase-commit: a9ecda2788e229afc9b611acaa26d0d9d4da53ed\n-- \n2.42.0\n\n"},{"id":"484210","messageId":"9570ad639244066802958ea52069ffcad05959ce.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 01/12] builtin/show-ref: convert pattern to a local variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:12Z","receivedAt":"2023-10-31T08:16:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `pattern` variable is a global variable that tracks either the\nreference names (not patterns!) for the `--verify` mode or the patterns\nfor the non-verify mode. This is a bit confusing due to the slightly\ndifferent meanings.\n\nConvert the variable to be local. While this does not yet fix the double\nmeaning of the variable, this change allows us to address it in a\nsubsequent patch more easily by explicitly splitting up the different\nsubcommands of git-show-ref(1).\n\nNote that we introduce a `struct show_ref_data` to pass the patterns to\n`show_ref()`. While this is overengineered now, we will extend this\nstructure in a subsequent patch.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 46 ++++++++++++++++++++++++++++------------------\n 1 file changed, 28 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 5110814f796..7efab14b96c 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -20,7 +20,6 @@ static const char * const show_ref_usage[] = {\n \n static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n \t   quiet, hash_only, abbrev, exclude_arg;\n-static const char **pattern;\n static const char *exclude_existing_arg;\n \n static void show_one(const char *refname, const struct object_id *oid)\n@@ -50,15 +49,21 @@ static void show_one(const char *refname, const struct object_id *oid)\n \t}\n }\n \n+struct show_ref_data {\n+\tconst char **patterns;\n+};\n+\n static int show_ref(const char *refname, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cbdata UNUSED)\n+\t\t    int flag UNUSED, void *cbdata)\n {\n+\tstruct show_ref_data *data = cbdata;\n+\n \tif (show_head && !strcmp(refname, \"HEAD\"))\n \t\tgoto match;\n \n-\tif (pattern) {\n+\tif (data->patterns) {\n \t\tint reflen = strlen(refname);\n-\t\tconst char **p = pattern, *m;\n+\t\tconst char **p = data->patterns, *m;\n \t\twhile ((m = *p++) != NULL) {\n \t\t\tint len = strlen(m);\n \t\t\tif (len > reflen)\n@@ -180,6 +185,9 @@ static const struct option show_ref_options[] = {\n \n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n+\tstruct show_ref_data show_ref_data = {0};\n+\tconst char **patterns;\n+\n \tgit_config(git_default_config, NULL);\n \n \targc = parse_options(argc, argv, prefix, show_ref_options,\n@@ -188,38 +196,40 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \tif (exclude_arg)\n \t\treturn exclude_existing(exclude_existing_arg);\n \n-\tpattern = argv;\n-\tif (!*pattern)\n-\t\tpattern = NULL;\n+\tpatterns = argv;\n+\tif (!*patterns)\n+\t\tpatterns = NULL;\n \n \tif (verify) {\n-\t\tif (!pattern)\n+\t\tif (!patterns)\n \t\t\tdie(\"--verify requires a reference\");\n-\t\twhile (*pattern) {\n+\t\twhile (*patterns) {\n \t\t\tstruct object_id oid;\n \n-\t\t\tif ((starts_with(*pattern, \"refs/\") || !strcmp(*pattern, \"HEAD\")) &&\n-\t\t\t    !read_ref(*pattern, &oid)) {\n-\t\t\t\tshow_one(*pattern, &oid);\n+\t\t\tif ((starts_with(*patterns, \"refs/\") || !strcmp(*patterns, \"HEAD\")) &&\n+\t\t\t    !read_ref(*patterns, &oid)) {\n+\t\t\t\tshow_one(*patterns, &oid);\n \t\t\t}\n \t\t\telse if (!quiet)\n-\t\t\t\tdie(\"'%s' - not a valid ref\", *pattern);\n+\t\t\t\tdie(\"'%s' - not a valid ref\", *patterns);\n \t\t\telse\n \t\t\t\treturn 1;\n-\t\t\tpattern++;\n+\t\t\tpatterns++;\n \t\t}\n \t\treturn 0;\n \t}\n \n+\tshow_ref_data.patterns = patterns;\n+\n \tif (show_head)\n-\t\thead_ref(show_ref, NULL);\n+\t\thead_ref(show_ref, &show_ref_data);\n \tif (heads_only || tags_only) {\n \t\tif (heads_only)\n-\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, NULL);\n+\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n \t\tif (tags_only)\n-\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, NULL);\n+\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n \t} else {\n-\t\tfor_each_ref(show_ref, NULL);\n+\t\tfor_each_ref(show_ref, &show_ref_data);\n \t}\n \tif (!found_match) {\n \t\tif (verify && !quiet)\n-- \n2.42.0\n\n"},{"id":"484211","messageId":"773c611975011dd9db83f900dedff2f3eeb5d473.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 02/12] builtin/show-ref: split up different subcommands","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:17Z","receivedAt":"2023-10-31T08:16:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While not immediately obvious, git-show-ref(1) actually implements three\ndifferent subcommands:\n\n    - `git show-ref <patterns>` can be used to list references that\n      match a specific pattern.\n\n    - `git show-ref --verify <refs>` can be used to list references.\n      These are _not_ patterns.\n\n    - `git show-ref --exclude-existing` can be used as a filter that\n      reads references from standard input, performing some conversions\n      on each of them.\n\nLet's make this more explicit in the code by splitting up the three\nsubcommands into separate functions. This also allows us to address the\nconfusingly named `patterns` variable, which may hold either patterns or\nreference names.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 101 ++++++++++++++++++++++++---------------------\n 1 file changed, 54 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 7efab14b96c..cad5b8b5066 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -104,7 +104,7 @@ static int add_existing(const char *refname,\n  * (4) ignore if refname is a ref that exists in the local repository;\n  * (5) otherwise output the line.\n  */\n-static int exclude_existing(const char *match)\n+static int cmd_show_ref__exclude_existing(const char *match)\n {\n \tstatic struct string_list existing_refs = STRING_LIST_INIT_DUP;\n \tchar buf[1024];\n@@ -142,6 +142,54 @@ static int exclude_existing(const char *match)\n \treturn 0;\n }\n \n+static int cmd_show_ref__verify(const char **refs)\n+{\n+\tif (!refs || !*refs)\n+\t\tdie(\"--verify requires a reference\");\n+\n+\twhile (*refs) {\n+\t\tstruct object_id oid;\n+\n+\t\tif ((starts_with(*refs, \"refs/\") || !strcmp(*refs, \"HEAD\")) &&\n+\t\t    !read_ref(*refs, &oid)) {\n+\t\t\tshow_one(*refs, &oid);\n+\t\t}\n+\t\telse if (!quiet)\n+\t\t\tdie(\"'%s' - not a valid ref\", *refs);\n+\t\telse\n+\t\t\treturn 1;\n+\t\trefs++;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int cmd_show_ref__patterns(const char **patterns)\n+{\n+\tstruct show_ref_data show_ref_data = {0};\n+\n+\tif (patterns && *patterns)\n+\t\tshow_ref_data.patterns = patterns;\n+\n+\tif (show_head)\n+\t\thead_ref(show_ref, &show_ref_data);\n+\tif (heads_only || tags_only) {\n+\t\tif (heads_only)\n+\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n+\t\tif (tags_only)\n+\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n+\t} else {\n+\t\tfor_each_ref(show_ref, &show_ref_data);\n+\t}\n+\tif (!found_match) {\n+\t\tif (verify && !quiet)\n+\t\t\tdie(\"No match\");\n+\t\treturn 1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int hash_callback(const struct option *opt, const char *arg, int unset)\n {\n \thash_only = 1;\n@@ -185,56 +233,15 @@ static const struct option show_ref_options[] = {\n \n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n-\tstruct show_ref_data show_ref_data = {0};\n-\tconst char **patterns;\n-\n \tgit_config(git_default_config, NULL);\n \n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n \tif (exclude_arg)\n-\t\treturn exclude_existing(exclude_existing_arg);\n-\n-\tpatterns = argv;\n-\tif (!*patterns)\n-\t\tpatterns = NULL;\n-\n-\tif (verify) {\n-\t\tif (!patterns)\n-\t\t\tdie(\"--verify requires a reference\");\n-\t\twhile (*patterns) {\n-\t\t\tstruct object_id oid;\n-\n-\t\t\tif ((starts_with(*patterns, \"refs/\") || !strcmp(*patterns, \"HEAD\")) &&\n-\t\t\t    !read_ref(*patterns, &oid)) {\n-\t\t\t\tshow_one(*patterns, &oid);\n-\t\t\t}\n-\t\t\telse if (!quiet)\n-\t\t\t\tdie(\"'%s' - not a valid ref\", *patterns);\n-\t\t\telse\n-\t\t\t\treturn 1;\n-\t\t\tpatterns++;\n-\t\t}\n-\t\treturn 0;\n-\t}\n-\n-\tshow_ref_data.patterns = patterns;\n-\n-\tif (show_head)\n-\t\thead_ref(show_ref, &show_ref_data);\n-\tif (heads_only || tags_only) {\n-\t\tif (heads_only)\n-\t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n-\t\tif (tags_only)\n-\t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n-\t} else {\n-\t\tfor_each_ref(show_ref, &show_ref_data);\n-\t}\n-\tif (!found_match) {\n-\t\tif (verify && !quiet)\n-\t\t\tdie(\"No match\");\n-\t\treturn 1;\n-\t}\n-\treturn 0;\n+\t\treturn cmd_show_ref__exclude_existing(exclude_existing_arg);\n+\telse if (verify)\n+\t\treturn cmd_show_ref__verify(argv);\n+\telse\n+\t\treturn cmd_show_ref__patterns(argv);\n }\n-- \n2.42.0\n\n"},{"id":"484212","messageId":"b6f4c0325bf19ffe9fb67be043bb02bf86ee7276.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 03/12] builtin/show-ref: fix leaking string buffer","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:21Z","receivedAt":"2023-10-31T08:16:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Fix a leaking string buffer in `git show-ref --exclude-existing`. While\nthe buffer is technically not leaking because its variable is declared\nas static, there is no inherent reason why it should be.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex cad5b8b5066..e55c38af478 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -106,7 +106,7 @@ static int add_existing(const char *refname,\n  */\n static int cmd_show_ref__exclude_existing(const char *match)\n {\n-\tstatic struct string_list existing_refs = STRING_LIST_INIT_DUP;\n+\tstruct string_list existing_refs = STRING_LIST_INIT_DUP;\n \tchar buf[1024];\n \tint matchlen = match ? strlen(match) : 0;\n \n@@ -139,6 +139,8 @@ static int cmd_show_ref__exclude_existing(const char *match)\n \t\t\tprintf(\"%s\\n\", buf);\n \t\t}\n \t}\n+\n+\tstring_list_clear(&existing_refs, 0);\n \treturn 0;\n }\n \n-- \n2.42.0\n\n"},{"id":"484213","messageId":"4605c6f0ac989b03d25ab02403d6b94836595c43.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 04/12] builtin/show-ref: fix dead code when passing patterns","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:25Z","receivedAt":"2023-10-31T08:16:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When passing patterns to `git show-ref` we have some code that will\ncause us to die if `verify && !quiet` is true. But because `verify`\nindicates a different subcommand of git-show-ref(1) that causes us to\nexecute `cmd_show_ref__verify()` and not `cmd_show_ref__patterns()`, the\ncondition cannot ever be true.\n\nLet's remove this dead code.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 5 +----\n 1 file changed, 1 insertion(+), 4 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex e55c38af478..f95418d3d16 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -183,11 +183,8 @@ static int cmd_show_ref__patterns(const char **patterns)\n \t} else {\n \t\tfor_each_ref(show_ref, &show_ref_data);\n \t}\n-\tif (!found_match) {\n-\t\tif (verify && !quiet)\n-\t\t\tdie(\"No match\");\n+\tif (!found_match)\n \t\treturn 1;\n-\t}\n \n \treturn 0;\n }\n-- \n2.42.0\n\n"},{"id":"484214","messageId":"b47440089b6701061786b5729501bbbbda8387be.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 05/12] builtin/show-ref: refactor `--exclude-existing` options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:29Z","receivedAt":"2023-10-31T08:16:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"It's not immediately obvious options which options are applicable to\nwhat subcommand in git-show-ref(1) because all options exist as global\nstate. This can easily cause confusion for the reader.\n\nRefactor options for the `--exclude-existing` subcommand to be contained\nin a separate structure. This structure is stored on the stack and\npassed down as required. Consequently, it clearly delimits the scope of\nthose options and requires the reader to worry less about global state.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 78 ++++++++++++++++++++++++++--------------------\n 1 file changed, 44 insertions(+), 34 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex f95418d3d16..5aa6016376a 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -19,8 +19,7 @@ static const char * const show_ref_usage[] = {\n };\n \n static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n-\t   quiet, hash_only, abbrev, exclude_arg;\n-static const char *exclude_existing_arg;\n+\t   quiet, hash_only, abbrev;\n \n static void show_one(const char *refname, const struct object_id *oid)\n {\n@@ -95,6 +94,15 @@ static int add_existing(const char *refname,\n \treturn 0;\n }\n \n+struct exclude_existing_options {\n+\t/*\n+\t * We need an explicit `enabled` field because it is perfectly valid\n+\t * for `pattern` to be `NULL` even if `--exclude-existing` was given.\n+\t */\n+\tint enabled;\n+\tconst char *pattern;\n+};\n+\n /*\n  * read \"^(?:<anything>\\s)?<refname>(?:\\^\\{\\})?$\" from the standard input,\n  * and\n@@ -104,11 +112,11 @@ static int add_existing(const char *refname,\n  * (4) ignore if refname is a ref that exists in the local repository;\n  * (5) otherwise output the line.\n  */\n-static int cmd_show_ref__exclude_existing(const char *match)\n+static int cmd_show_ref__exclude_existing(const struct exclude_existing_options *opts)\n {\n \tstruct string_list existing_refs = STRING_LIST_INIT_DUP;\n \tchar buf[1024];\n-\tint matchlen = match ? strlen(match) : 0;\n+\tint patternlen = opts->pattern ? strlen(opts->pattern) : 0;\n \n \tfor_each_ref(add_existing, &existing_refs);\n \twhile (fgets(buf, sizeof(buf), stdin)) {\n@@ -124,11 +132,11 @@ static int cmd_show_ref__exclude_existing(const char *match)\n \t\tfor (ref = buf + len; buf < ref; ref--)\n \t\t\tif (isspace(ref[-1]))\n \t\t\t\tbreak;\n-\t\tif (match) {\n+\t\tif (opts->pattern) {\n \t\t\tint reflen = buf + len - ref;\n-\t\t\tif (reflen < matchlen)\n+\t\t\tif (reflen < patternlen)\n \t\t\t\tcontinue;\n-\t\t\tif (strncmp(ref, match, matchlen))\n+\t\t\tif (strncmp(ref, opts->pattern, patternlen))\n \t\t\t\tcontinue;\n \t\t}\n \t\tif (check_refname_format(ref, 0)) {\n@@ -201,44 +209,46 @@ static int hash_callback(const struct option *opt, const char *arg, int unset)\n static int exclude_existing_callback(const struct option *opt, const char *arg,\n \t\t\t\t     int unset)\n {\n+\tstruct exclude_existing_options *opts = opt->value;\n \tBUG_ON_OPT_NEG(unset);\n-\texclude_arg = 1;\n-\t*(const char **)opt->value = arg;\n+\topts->enabled = 1;\n+\topts->pattern = arg;\n \treturn 0;\n }\n \n-static const struct option show_ref_options[] = {\n-\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n-\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n-\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n-\t\t    \"requires exact ref path\")),\n-\tOPT_HIDDEN_BOOL('h', NULL, &show_head,\n-\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n-\tOPT_BOOL(0, \"head\", &show_head,\n-\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n-\tOPT_BOOL('d', \"dereference\", &deref_tags,\n-\t\t    N_(\"dereference tags into object IDs\")),\n-\tOPT_CALLBACK_F('s', \"hash\", &abbrev, N_(\"n\"),\n-\t\t       N_(\"only show SHA1 hash using <n> digits\"),\n-\t\t       PARSE_OPT_OPTARG, &hash_callback),\n-\tOPT__ABBREV(&abbrev),\n-\tOPT__QUIET(&quiet,\n-\t\t   N_(\"do not print results to stdout (useful with --verify)\")),\n-\tOPT_CALLBACK_F(0, \"exclude-existing\", &exclude_existing_arg,\n-\t\t       N_(\"pattern\"), N_(\"show refs from stdin that aren't in local repository\"),\n-\t\t       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),\n-\tOPT_END()\n-};\n-\n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n+\tstruct exclude_existing_options exclude_existing_opts = {0};\n+\tconst struct option show_ref_options[] = {\n+\t\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n+\t\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n+\t\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n+\t\t\t    \"requires exact ref path\")),\n+\t\tOPT_HIDDEN_BOOL('h', NULL, &show_head,\n+\t\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n+\t\tOPT_BOOL(0, \"head\", &show_head,\n+\t\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n+\t\tOPT_BOOL('d', \"dereference\", &deref_tags,\n+\t\t\t    N_(\"dereference tags into object IDs\")),\n+\t\tOPT_CALLBACK_F('s', \"hash\", &abbrev, N_(\"n\"),\n+\t\t\t       N_(\"only show SHA1 hash using <n> digits\"),\n+\t\t\t       PARSE_OPT_OPTARG, &hash_callback),\n+\t\tOPT__ABBREV(&abbrev),\n+\t\tOPT__QUIET(&quiet,\n+\t\t\t   N_(\"do not print results to stdout (useful with --verify)\")),\n+\t\tOPT_CALLBACK_F(0, \"exclude-existing\", &exclude_existing_opts,\n+\t\t\t       N_(\"pattern\"), N_(\"show refs from stdin that aren't in local repository\"),\n+\t\t\t       PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),\n+\t\tOPT_END()\n+\t};\n+\n \tgit_config(git_default_config, NULL);\n \n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n-\tif (exclude_arg)\n-\t\treturn cmd_show_ref__exclude_existing(exclude_existing_arg);\n+\tif (exclude_existing_opts.enabled)\n+\t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\n \t\treturn cmd_show_ref__verify(argv);\n \telse\n-- \n2.42.0\n\n"},{"id":"484215","messageId":"6172888e46514298ab8ca5a77346fee82c746c9c.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 06/12] builtin/show-ref: stop using global variable to count matches","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:33Z","receivedAt":"2023-10-31T08:16:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When passing patterns to git-show-ref(1) we're checking whether any\nreference matches -- if none do, we indicate this condition via an\nunsuccessful exit code.\n\nWe're using a global variable to count these matches, which is required\nbecause the counter is getting incremented in a callback function. But\nnow that we have the `struct show_ref_data` in place, we can get rid of\nthe global variable and put the counter in there instead.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 5aa6016376a..d0de69e29dd 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -18,7 +18,7 @@ static const char * const show_ref_usage[] = {\n \tNULL\n };\n \n-static int deref_tags, show_head, tags_only, heads_only, found_match, verify,\n+static int deref_tags, show_head, tags_only, heads_only, verify,\n \t   quiet, hash_only, abbrev;\n \n static void show_one(const char *refname, const struct object_id *oid)\n@@ -50,6 +50,7 @@ static void show_one(const char *refname, const struct object_id *oid)\n \n struct show_ref_data {\n \tconst char **patterns;\n+\tint found_match;\n };\n \n static int show_ref(const char *refname, const struct object_id *oid,\n@@ -78,7 +79,7 @@ static int show_ref(const char *refname, const struct object_id *oid,\n \t}\n \n match:\n-\tfound_match++;\n+\tdata->found_match++;\n \n \tshow_one(refname, oid);\n \n@@ -191,7 +192,7 @@ static int cmd_show_ref__patterns(const char **patterns)\n \t} else {\n \t\tfor_each_ref(show_ref, &show_ref_data);\n \t}\n-\tif (!found_match)\n+\tif (!show_ref_data.found_match)\n \t\treturn 1;\n \n \treturn 0;\n-- \n2.42.0\n\n"},{"id":"484216","messageId":"bc528db7667cbe9323162b80ad47bc9d46c4b275.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 07/12] builtin/show-ref: stop using global vars for `show_one()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:38Z","receivedAt":"2023-10-31T08:16:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `show_one()` function implicitly receives a bunch of options which\nare tracked via global variables. This makes it hard to see which\nsubcommands of git-show-ref(1) actually make use of these options.\n\nIntroduce a `show_one_options` structure that gets passed down to this\nfunction. This allows us to get rid of more global state and makes it\nmore explicit which subcommands use those options.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 62 ++++++++++++++++++++++++++++++----------------\n 1 file changed, 40 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex d0de69e29dd..fb0960ac507 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -18,10 +18,17 @@ static const char * const show_ref_usage[] = {\n \tNULL\n };\n \n-static int deref_tags, show_head, tags_only, heads_only, verify,\n-\t   quiet, hash_only, abbrev;\n+static int show_head, tags_only, heads_only, verify;\n \n-static void show_one(const char *refname, const struct object_id *oid)\n+struct show_one_options {\n+\tint quiet;\n+\tint hash_only;\n+\tint abbrev;\n+\tint deref_tags;\n+};\n+\n+static void show_one(const struct show_one_options *opts,\n+\t\t     const char *refname, const struct object_id *oid)\n {\n \tconst char *hex;\n \tstruct object_id peeled;\n@@ -30,25 +37,26 @@ static void show_one(const char *refname, const struct object_id *oid)\n \t\tdie(\"git show-ref: bad ref %s (%s)\", refname,\n \t\t    oid_to_hex(oid));\n \n-\tif (quiet)\n+\tif (opts->quiet)\n \t\treturn;\n \n-\thex = repo_find_unique_abbrev(the_repository, oid, abbrev);\n-\tif (hash_only)\n+\thex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);\n+\tif (opts->hash_only)\n \t\tprintf(\"%s\\n\", hex);\n \telse\n \t\tprintf(\"%s %s\\n\", hex, refname);\n \n-\tif (!deref_tags)\n+\tif (!opts->deref_tags)\n \t\treturn;\n \n \tif (!peel_iterated_oid(oid, &peeled)) {\n-\t\thex = repo_find_unique_abbrev(the_repository, &peeled, abbrev);\n+\t\thex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);\n \t\tprintf(\"%s %s^{}\\n\", hex, refname);\n \t}\n }\n \n struct show_ref_data {\n+\tconst struct show_one_options *show_one_opts;\n \tconst char **patterns;\n \tint found_match;\n };\n@@ -81,7 +89,7 @@ static int show_ref(const char *refname, const struct object_id *oid,\n match:\n \tdata->found_match++;\n \n-\tshow_one(refname, oid);\n+\tshow_one(data->show_one_opts, refname, oid);\n \n \treturn 0;\n }\n@@ -153,7 +161,8 @@ static int cmd_show_ref__exclude_existing(const struct exclude_existing_options\n \treturn 0;\n }\n \n-static int cmd_show_ref__verify(const char **refs)\n+static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n+\t\t\t\tconst char **refs)\n {\n \tif (!refs || !*refs)\n \t\tdie(\"--verify requires a reference\");\n@@ -163,9 +172,9 @@ static int cmd_show_ref__verify(const char **refs)\n \n \t\tif ((starts_with(*refs, \"refs/\") || !strcmp(*refs, \"HEAD\")) &&\n \t\t    !read_ref(*refs, &oid)) {\n-\t\t\tshow_one(*refs, &oid);\n+\t\t\tshow_one(show_one_opts, *refs, &oid);\n \t\t}\n-\t\telse if (!quiet)\n+\t\telse if (!show_one_opts->quiet)\n \t\t\tdie(\"'%s' - not a valid ref\", *refs);\n \t\telse\n \t\t\treturn 1;\n@@ -175,9 +184,12 @@ static int cmd_show_ref__verify(const char **refs)\n \treturn 0;\n }\n \n-static int cmd_show_ref__patterns(const char **patterns)\n+static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,\n+\t\t\t\t  const char **patterns)\n {\n-\tstruct show_ref_data show_ref_data = {0};\n+\tstruct show_ref_data show_ref_data = {\n+\t\t.show_one_opts = show_one_opts,\n+\t};\n \n \tif (patterns && *patterns)\n \t\tshow_ref_data.patterns = patterns;\n@@ -200,11 +212,16 @@ static int cmd_show_ref__patterns(const char **patterns)\n \n static int hash_callback(const struct option *opt, const char *arg, int unset)\n {\n-\thash_only = 1;\n+\tstruct show_one_options *opts = opt->value;\n+\tstruct option abbrev_opt = *opt;\n+\n+\topts->hash_only = 1;\n \t/* Use full length SHA1 if no argument */\n \tif (!arg)\n \t\treturn 0;\n-\treturn parse_opt_abbrev_cb(opt, arg, unset);\n+\n+\tabbrev_opt.value = &opts->abbrev;\n+\treturn parse_opt_abbrev_cb(&abbrev_opt, arg, unset);\n }\n \n static int exclude_existing_callback(const struct option *opt, const char *arg,\n@@ -220,6 +237,7 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,\n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n \tstruct exclude_existing_options exclude_existing_opts = {0};\n+\tstruct show_one_options show_one_opts = {0};\n \tconst struct option show_ref_options[] = {\n \t\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n \t\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n@@ -229,13 +247,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \t\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n \t\tOPT_BOOL(0, \"head\", &show_head,\n \t\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n-\t\tOPT_BOOL('d', \"dereference\", &deref_tags,\n+\t\tOPT_BOOL('d', \"dereference\", &show_one_opts.deref_tags,\n \t\t\t    N_(\"dereference tags into object IDs\")),\n-\t\tOPT_CALLBACK_F('s', \"hash\", &abbrev, N_(\"n\"),\n+\t\tOPT_CALLBACK_F('s', \"hash\", &show_one_opts, N_(\"n\"),\n \t\t\t       N_(\"only show SHA1 hash using <n> digits\"),\n \t\t\t       PARSE_OPT_OPTARG, &hash_callback),\n-\t\tOPT__ABBREV(&abbrev),\n-\t\tOPT__QUIET(&quiet,\n+\t\tOPT__ABBREV(&show_one_opts.abbrev),\n+\t\tOPT__QUIET(&show_one_opts.quiet,\n \t\t\t   N_(\"do not print results to stdout (useful with --verify)\")),\n \t\tOPT_CALLBACK_F(0, \"exclude-existing\", &exclude_existing_opts,\n \t\t\t       N_(\"pattern\"), N_(\"show refs from stdin that aren't in local repository\"),\n@@ -251,7 +269,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \tif (exclude_existing_opts.enabled)\n \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\n-\t\treturn cmd_show_ref__verify(argv);\n+\t\treturn cmd_show_ref__verify(&show_one_opts, argv);\n \telse\n-\t\treturn cmd_show_ref__patterns(argv);\n+\t\treturn cmd_show_ref__patterns(&show_one_opts, argv);\n }\n-- \n2.42.0\n\n"},{"id":"484217","messageId":"e3882c07dfc9e114c0317e7a82462607b0eb8d43.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 08/12] builtin/show-ref: refactor options for patterns subcommand","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:42Z","receivedAt":"2023-10-31T08:16:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The patterns subcommand is the last command that still uses global\nvariables to track its options. Convert it to use a structure instead\nwith the same motivation as preceding commits.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 35 ++++++++++++++++++++++-------------\n 1 file changed, 22 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex fb0960ac507..36ac10551da 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -18,8 +18,6 @@ static const char * const show_ref_usage[] = {\n \tNULL\n };\n \n-static int show_head, tags_only, heads_only, verify;\n-\n struct show_one_options {\n \tint quiet;\n \tint hash_only;\n@@ -59,6 +57,7 @@ struct show_ref_data {\n \tconst struct show_one_options *show_one_opts;\n \tconst char **patterns;\n \tint found_match;\n+\tint show_head;\n };\n \n static int show_ref(const char *refname, const struct object_id *oid,\n@@ -66,7 +65,7 @@ static int show_ref(const char *refname, const struct object_id *oid,\n {\n \tstruct show_ref_data *data = cbdata;\n \n-\tif (show_head && !strcmp(refname, \"HEAD\"))\n+\tif (data->show_head && !strcmp(refname, \"HEAD\"))\n \t\tgoto match;\n \n \tif (data->patterns) {\n@@ -184,22 +183,30 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n \treturn 0;\n }\n \n-static int cmd_show_ref__patterns(const struct show_one_options *show_one_opts,\n+struct patterns_options {\n+\tint show_head;\n+\tint heads_only;\n+\tint tags_only;\n+};\n+\n+static int cmd_show_ref__patterns(const struct patterns_options *opts,\n+\t\t\t\t  const struct show_one_options *show_one_opts,\n \t\t\t\t  const char **patterns)\n {\n \tstruct show_ref_data show_ref_data = {\n \t\t.show_one_opts = show_one_opts,\n+\t\t.show_head = opts->show_head,\n \t};\n \n \tif (patterns && *patterns)\n \t\tshow_ref_data.patterns = patterns;\n \n-\tif (show_head)\n+\tif (opts->show_head)\n \t\thead_ref(show_ref, &show_ref_data);\n-\tif (heads_only || tags_only) {\n-\t\tif (heads_only)\n+\tif (opts->heads_only || opts->tags_only) {\n+\t\tif (opts->heads_only)\n \t\t\tfor_each_fullref_in(\"refs/heads/\", show_ref, &show_ref_data);\n-\t\tif (tags_only)\n+\t\tif (opts->tags_only)\n \t\t\tfor_each_fullref_in(\"refs/tags/\", show_ref, &show_ref_data);\n \t} else {\n \t\tfor_each_ref(show_ref, &show_ref_data);\n@@ -237,15 +244,17 @@ static int exclude_existing_callback(const struct option *opt, const char *arg,\n int cmd_show_ref(int argc, const char **argv, const char *prefix)\n {\n \tstruct exclude_existing_options exclude_existing_opts = {0};\n+\tstruct patterns_options patterns_opts = {0};\n \tstruct show_one_options show_one_opts = {0};\n+\tint verify = 0;\n \tconst struct option show_ref_options[] = {\n-\t\tOPT_BOOL(0, \"tags\", &tags_only, N_(\"only show tags (can be combined with heads)\")),\n-\t\tOPT_BOOL(0, \"heads\", &heads_only, N_(\"only show heads (can be combined with tags)\")),\n+\t\tOPT_BOOL(0, \"tags\", &patterns_opts.tags_only, N_(\"only show tags (can be combined with heads)\")),\n+\t\tOPT_BOOL(0, \"heads\", &patterns_opts.heads_only, N_(\"only show heads (can be combined with tags)\")),\n \t\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n \t\t\t    \"requires exact ref path\")),\n-\t\tOPT_HIDDEN_BOOL('h', NULL, &show_head,\n+\t\tOPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,\n \t\t\t\tN_(\"show the HEAD reference, even if it would be filtered out\")),\n-\t\tOPT_BOOL(0, \"head\", &show_head,\n+\t\tOPT_BOOL(0, \"head\", &patterns_opts.show_head,\n \t\t  N_(\"show the HEAD reference, even if it would be filtered out\")),\n \t\tOPT_BOOL('d', \"dereference\", &show_one_opts.deref_tags,\n \t\t\t    N_(\"dereference tags into object IDs\")),\n@@ -271,5 +280,5 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \telse if (verify)\n \t\treturn cmd_show_ref__verify(&show_one_opts, argv);\n \telse\n-\t\treturn cmd_show_ref__patterns(&show_one_opts, argv);\n+\t\treturn cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);\n }\n-- \n2.42.0\n\n"},{"id":"484218","messageId":"a095decd7783fa55299903608d306f09388d1d86.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 09/12] builtin/show-ref: ensure mutual exclusiveness of subcommands","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:46Z","receivedAt":"2023-10-31T08:16:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The git-show-ref(1) command has three different modes, of which one is\nimplicit and the other two can be chosen explicitly by passing a flag.\nBut while these modes are standalone and cause us to execute completely\nseparate code paths, we gladly accept the case where a user asks for\nboth `--exclude-existing` and `--verify` at the same time even though it\nis not obvious what will happen. Spoiler: we ignore `--verify` and\nexecute the `--exclude-existing` mode.\n\nLet's explicitly detect this invalid usage and die in case both modes\nwere requested.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c  | 4 ++++\n t/t1403-show-ref.sh | 9 +++++++++\n 2 files changed, 13 insertions(+)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 36ac10551da..6685495dd2c 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -275,6 +275,10 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n+\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n+\t\tdie(_(\"only one of '%s' or '%s' can be given\"),\n+\t\t    \"--exclude-existing\", \"--verify\");\n+\n \tif (exclude_existing_opts.enabled)\n \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\ndiff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh\nindex 9252a581abf..f1e0388324e 100755\n--- a/t/t1403-show-ref.sh\n+++ b/t/t1403-show-ref.sh\n@@ -196,4 +196,13 @@ test_expect_success 'show-ref --verify with dangling ref' '\n \t)\n '\n \n+test_expect_success 'show-ref sub-modes are mutually exclusive' '\n+\tcat >expect <<-EOF &&\n+\tfatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\n+\tEOF\n+\n+\ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n+\ttest_cmp expect err\n+'\n+\n test_done\n-- \n2.42.0\n\n"},{"id":"484219","messageId":"087384fd2fd984974344c58f981b837381291d5e.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 10/12] builtin/show-ref: explicitly spell out different modes in synopsis","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:50Z","receivedAt":"2023-10-31T08:16:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The synopsis treats the `--verify` and the implicit mode the same. They\nare slightly different though:\n\n    - They accept different sets of flags.\n\n    - The implicit mode accepts patterns while the `--verify` mode\n      accepts references.\n\nSplit up the synopsis for these two modes such that we can disambiguate\nthose differences.\n\nWhile at it, drop \"--quiet\" from the pattern mode's synopsis. It does\nnot make a lot of sense to list patterns, but squelch the listing output\nitself. The description for \"--quiet\" is adapted accordingly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-show-ref.txt | 9 ++++++---\n builtin/show-ref.c             | 5 ++++-\n 2 files changed, 10 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\nindex 2fe274b8faa..22f5ebc6a92 100644\n--- a/Documentation/git-show-ref.txt\n+++ b/Documentation/git-show-ref.txt\n@@ -8,9 +8,12 @@ git-show-ref - List references in a local repository\n SYNOPSIS\n --------\n [verse]\n-'git show-ref' [-q | --quiet] [--verify] [--head] [-d | --dereference]\n+'git show-ref' [--head] [-d | --dereference]\n \t     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n \t     [--heads] [--] [<pattern>...]\n+'git show-ref' --verify [-q | --quiet] [-d | --dereference]\n+\t     [-s | --hash[=<n>]] [--abbrev[=<n>]]\n+\t     [--] [<ref>...]\n 'git show-ref' --exclude-existing[=<pattern>]\n \n DESCRIPTION\n@@ -70,8 +73,8 @@ OPTIONS\n -q::\n --quiet::\n \n-\tDo not print any results to stdout. When combined with `--verify`, this\n-\tcan be used to silently check if a reference exists.\n+\tDo not print any results to stdout. Can be used with `--verify` to\n+\tsilently check if a reference exists.\n \n --exclude-existing[=<pattern>]::\n \ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 6685495dd2c..460f238a62d 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -11,9 +11,12 @@\n #include \"parse-options.h\"\n \n static const char * const show_ref_usage[] = {\n-\tN_(\"git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference]\\n\"\n+\tN_(\"git show-ref [--head] [-d | --dereference]\\n\"\n \t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\\n\"\n \t   \"             [--heads] [--] [<pattern>...]\"),\n+\tN_(\"git show-ref --verify [-q | --quiet] [-d | --dereference]\\n\"\n+\t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]]\\n\"\n+\t   \"             [--] [<ref>...]\"),\n \tN_(\"git show-ref --exclude-existing[=<pattern>]\"),\n \tNULL\n };\n-- \n2.42.0\n\n"},{"id":"484220","messageId":"ca5187bb18af9aa37835d2e6b6c17e024fbe6dbf.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 11/12] builtin/show-ref: add new mode to check for reference existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:54Z","receivedAt":"2023-10-31T08:17:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we have multiple ways to show the value of a given reference, we\ndo not have any way to check whether a reference exists at all. While\ncommands like git-rev-parse(1) or git-show-ref(1) can be used to check\nfor reference existence in case the reference resolves to something\nsane, neither of them can be used to check for existence in some other\nscenarios where the reference does not resolve cleanly:\n\n    - References which have an invalid name cannot be resolved.\n\n    - References to nonexistent objects cannot be resolved.\n\n    - Dangling symrefs can be resolved via git-symbolic-ref(1), but this\n      requires the caller to special case existence checks depending on\n      whether or not a reference is symbolic or direct.\n\nFurthermore, git-rev-list(1) and other commands do not let the caller\ndistinguish easily between an actually missing reference and a generic\nerror.\n\nTaken together, this seems like sufficient motivation to introduce a\nseparate plumbing command to explicitly check for the existence of a\nreference without trying to resolve its contents.\n\nThis new command comes in the form of `git show-ref --exists`. This\nnew mode will exit successfully when the reference exists, with a\nspecific exit code of 2 when it does not exist, or with 1 when there\nhas been a generic error.\n\nNote that the only way to properly implement this command is by using\nthe internal `refs_read_raw_ref()` function. While the public function\n`refs_resolve_ref_unsafe()` can be made to behave in the same way by\npassing various flags, it does not provide any way to obtain the errno\nwith which the reference backend failed when reading the reference. As\nsuch, it becomes impossible for us to distinguish generic errors from\nthe explicit case where the reference wasn't found.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-show-ref.txt | 11 ++++++\n builtin/show-ref.c             | 49 +++++++++++++++++++++++---\n t/t1403-show-ref.sh            | 63 +++++++++++++++++++++++++++++++++-\n 3 files changed, 117 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt\nindex 22f5ebc6a92..8fecc9e80f6 100644\n--- a/Documentation/git-show-ref.txt\n+++ b/Documentation/git-show-ref.txt\n@@ -15,6 +15,7 @@ SYNOPSIS\n \t     [-s | --hash[=<n>]] [--abbrev[=<n>]]\n \t     [--] [<ref>...]\n 'git show-ref' --exclude-existing[=<pattern>]\n+'git show-ref' --exists <ref>\n \n DESCRIPTION\n -----------\n@@ -30,6 +31,10 @@ The `--exclude-existing` form is a filter that does the inverse. It reads\n refs from stdin, one ref per line, and shows those that don't exist in\n the local repository.\n \n+The `--exists` form can be used to check for the existence of a single\n+references. This form does not verify whether the reference resolves to an\n+actual object.\n+\n Use of this utility is encouraged in favor of directly accessing files under\n the `.git` directory.\n \n@@ -65,6 +70,12 @@ OPTIONS\n \tAside from returning an error code of 1, it will also print an error\n \tmessage if `--quiet` was not specified.\n \n+--exists::\n+\n+\tCheck whether the given reference exists. Returns an exit code of 0 if\n+\tit does, 2 if it is missing, and 1 in case looking up the reference\n+\tfailed with an error other than the reference being missing.\n+\n --abbrev[=<n>]::\n \n \tAbbreviate the object name.  When using `--hash`, you do\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 460f238a62d..7aac525a878 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -2,7 +2,7 @@\n #include \"config.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n-#include \"refs.h\"\n+#include \"refs/refs-internal.h\"\n #include \"object-name.h\"\n #include \"object-store-ll.h\"\n #include \"object.h\"\n@@ -18,6 +18,7 @@ static const char * const show_ref_usage[] = {\n \t   \"             [-s | --hash[=<n>]] [--abbrev[=<n>]]\\n\"\n \t   \"             [--] [<ref>...]\"),\n \tN_(\"git show-ref --exclude-existing[=<pattern>]\"),\n+\tN_(\"git show-ref --exists <ref>\"),\n \tNULL\n };\n \n@@ -220,6 +221,41 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,\n \treturn 0;\n }\n \n+static int cmd_show_ref__exists(const char **refs)\n+{\n+\tstruct strbuf unused_referent = STRBUF_INIT;\n+\tstruct object_id unused_oid;\n+\tunsigned int unused_type;\n+\tint failure_errno = 0;\n+\tconst char *ref;\n+\tint ret = 0;\n+\n+\tif (!refs || !*refs)\n+\t\tdie(\"--exists requires a reference\");\n+\tref = *refs++;\n+\tif (*refs)\n+\t\tdie(\"--exists requires exactly one reference\");\n+\n+\tif (refs_read_raw_ref(get_main_ref_store(the_repository), ref,\n+\t\t\t      &unused_oid, &unused_referent, &unused_type,\n+\t\t\t      &failure_errno)) {\n+\t\tif (failure_errno == ENOENT) {\n+\t\t\terror(_(\"reference does not exist\"));\n+\t\t\tret = 2;\n+\t\t} else {\n+\t\t\terrno = failure_errno;\n+\t\t\terror_errno(_(\"failed to look up reference\"));\n+\t\t\tret = 1;\n+\t\t}\n+\n+\t\tgoto out;\n+\t}\n+\n+out:\n+\tstrbuf_release(&unused_referent);\n+\treturn ret;\n+}\n+\n static int hash_callback(const struct option *opt, const char *arg, int unset)\n {\n \tstruct show_one_options *opts = opt->value;\n@@ -249,10 +285,11 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \tstruct exclude_existing_options exclude_existing_opts = {0};\n \tstruct patterns_options patterns_opts = {0};\n \tstruct show_one_options show_one_opts = {0};\n-\tint verify = 0;\n+\tint verify = 0, exists = 0;\n \tconst struct option show_ref_options[] = {\n \t\tOPT_BOOL(0, \"tags\", &patterns_opts.tags_only, N_(\"only show tags (can be combined with heads)\")),\n \t\tOPT_BOOL(0, \"heads\", &patterns_opts.heads_only, N_(\"only show heads (can be combined with tags)\")),\n+\t\tOPT_BOOL(0, \"exists\", &exists, N_(\"check for reference existence without resolving\")),\n \t\tOPT_BOOL(0, \"verify\", &verify, N_(\"stricter reference checking, \"\n \t\t\t    \"requires exact ref path\")),\n \t\tOPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,\n@@ -278,14 +315,16 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, show_ref_options,\n \t\t\t     show_ref_usage, 0);\n \n-\tif ((!!exclude_existing_opts.enabled + !!verify) > 1)\n-\t\tdie(_(\"only one of '%s' or '%s' can be given\"),\n-\t\t    \"--exclude-existing\", \"--verify\");\n+\tif ((!!exclude_existing_opts.enabled + !!verify + !!exists) > 1)\n+\t\tdie(_(\"only one of '%s', '%s' or '%s' can be given\"),\n+\t\t    \"--exclude-existing\", \"--verify\", \"--exists\");\n \n \tif (exclude_existing_opts.enabled)\n \t\treturn cmd_show_ref__exclude_existing(&exclude_existing_opts);\n \telse if (verify)\n \t\treturn cmd_show_ref__verify(&show_one_opts, argv);\n+\telse if (exists)\n+\t\treturn cmd_show_ref__exists(argv);\n \telse\n \t\treturn cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);\n }\ndiff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh\nindex f1e0388324e..b50ae6fcf11 100755\n--- a/t/t1403-show-ref.sh\n+++ b/t/t1403-show-ref.sh\n@@ -198,10 +198,71 @@ test_expect_success 'show-ref --verify with dangling ref' '\n \n test_expect_success 'show-ref sub-modes are mutually exclusive' '\n \tcat >expect <<-EOF &&\n-\tfatal: only one of ${SQ}--exclude-existing${SQ} or ${SQ}--verify${SQ} can be given\n+\tfatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given\n \tEOF\n \n \ttest_must_fail git show-ref --verify --exclude-existing 2>err &&\n+\ttest_cmp expect err &&\n+\n+\ttest_must_fail git show-ref --verify --exists 2>err &&\n+\ttest_cmp expect err &&\n+\n+\ttest_must_fail git show-ref --exclude-existing --exists 2>err &&\n+\ttest_cmp expect err\n+'\n+\n+test_expect_success '--exists with existing reference' '\n+\tgit show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+'\n+\n+test_expect_success '--exists with missing reference' '\n+\ttest_expect_code 2 git show-ref --exists refs/heads/does-not-exist\n+'\n+\n+test_expect_success '--exists does not use DWIM' '\n+\ttest_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&\n+\tgrep \"reference does not exist\" err\n+'\n+\n+test_expect_success '--exists with HEAD' '\n+\tgit show-ref --exists HEAD\n+'\n+\n+test_expect_success '--exists with bad reference name' '\n+\ttest_when_finished \"git update-ref -d refs/heads/bad...name\" &&\n+\tnew_oid=$(git rev-parse HEAD) &&\n+\ttest-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&\n+\tgit show-ref --exists refs/heads/bad...name\n+'\n+\n+test_expect_success '--exists with arbitrary symref' '\n+\ttest_when_finished \"git symbolic-ref -d refs/symref\" &&\n+\tgit symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&\n+\tgit show-ref --exists refs/symref\n+'\n+\n+test_expect_success '--exists with dangling symref' '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/dangling\" &&\n+\tgit symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&\n+\tgit show-ref --exists refs/heads/dangling\n+'\n+\n+test_expect_success '--exists with nonexistent object ID' '\n+\ttest-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION &&\n+\tgit show-ref --exists refs/heads/missing-oid\n+'\n+\n+test_expect_success '--exists with non-commit object' '\n+\ttree_oid=$(git rev-parse HEAD^{tree}) &&\n+\ttest-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION &&\n+\tgit show-ref --exists refs/heads/tree\n+'\n+\n+test_expect_success '--exists with directory fails with generic error' '\n+\tcat >expect <<-EOF &&\n+\terror: failed to look up reference: Is a directory\n+\tEOF\n+\ttest_expect_code 1 git show-ref --exists refs/heads 2>err &&\n \ttest_cmp expect err\n '\n \n-- \n2.42.0\n\n"},{"id":"484221","messageId":"ea9919fe8991676788e37d07e8e25c475f13ed6f.1698739941.git.ps@pks.im","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"[PATCH v3 12/12] t: use git-show-ref(1) to check for ref existence","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-10-31T08:16:59Z","receivedAt":"2023-10-31T08:17:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Convert tests that use `test_path_is_file` and `test_path_is_missing` to\ninstead use a set of helpers `test_ref_exists` and `test_ref_missing`.\nThese helpers are implemented via the newly introduced `git show-ref\n--exists` command. Thus, we can avoid intimate knowledge of how the ref\nbackend stores references on disk.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t1430-bad-ref-name.sh | 27 +++++++++++++-------\n t/t3200-branch.sh       | 33 ++++++++++++++-----------\n t/t5521-pull-options.sh |  4 +--\n t/t5605-clone-local.sh  |  2 +-\n t/test-lib-functions.sh | 55 +++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 94 insertions(+), 27 deletions(-)\n\ndiff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh\nindex ff1c967d550..7b7d6953c62 100755\n--- a/t/t1430-bad-ref-name.sh\n+++ b/t/t1430-bad-ref-name.sh\n@@ -205,8 +205,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref to broken name'\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref\" &&\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit update-ref --no-deref -d refs/heads/badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -216,8 +217,9 @@ test_expect_success 'branch -d can delete symref to broken name' '\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref\" &&\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit branch -d badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_i18ngrep \"Deleted branch badname (was refs/heads/broken\\.\\.\\.ref)\" output &&\n \ttest_must_be_empty error\n '\n@@ -225,8 +227,9 @@ test_expect_success 'branch -d can delete symref to broken name' '\n test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' '\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit update-ref --no-deref -d refs/heads/badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -234,8 +237,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref to brok\n test_expect_success 'branch -d can delete dangling symref to broken name' '\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/badname &&\n \tgit branch -d badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/badname &&\n+\ttest_ref_missing refs/heads/badname &&\n \ttest_i18ngrep \"Deleted branch badname (was refs/heads/broken\\.\\.\\.ref)\" output &&\n \ttest_must_be_empty error\n '\n@@ -245,8 +249,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref\" &&\n \ttest-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref msg &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname\" &&\n+\ttest_ref_exists refs/heads/broken...ref &&\n \tgit update-ref -d refs/heads/badname >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...ref &&\n+\ttest_ref_missing refs/heads/broken...ref &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -254,8 +259,9 @@ test_expect_success 'update-ref -d can delete broken name through symref' '\n test_expect_success 'update-ref --no-deref -d can delete symref with broken name' '\n \tprintf \"ref: refs/heads/main\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -263,8 +269,9 @@ test_expect_success 'update-ref --no-deref -d can delete symref with broken name\n test_expect_success 'branch -d can delete symref with broken name' '\n \tprintf \"ref: refs/heads/main\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit branch -d broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_i18ngrep \"Deleted branch broken...symref (was refs/heads/main)\" output &&\n \ttest_must_be_empty error\n '\n@@ -272,8 +279,9 @@ test_expect_success 'branch -d can delete symref with broken name' '\n test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' '\n \tprintf \"ref: refs/heads/idonotexist\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_must_be_empty output &&\n \ttest_must_be_empty error\n '\n@@ -281,8 +289,9 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref with br\n test_expect_success 'branch -d can delete dangling symref with broken name' '\n \tprintf \"ref: refs/heads/idonotexist\\n\" >.git/refs/heads/broken...symref &&\n \ttest_when_finished \"test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref\" &&\n+\ttest_ref_exists refs/heads/broken...symref &&\n \tgit branch -d broken...symref >output 2>error &&\n-\ttest_path_is_missing .git/refs/heads/broken...symref &&\n+\ttest_ref_missing refs/heads/broken...symref &&\n \ttest_i18ngrep \"Deleted branch broken...symref (was refs/heads/idonotexist)\" output &&\n \ttest_must_be_empty error\n '\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 080e4f24a6e..bde4f1485b7 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -25,7 +25,7 @@ test_expect_success 'prepare a trivial repository' '\n \n test_expect_success 'git branch --help should not have created a bogus branch' '\n \ttest_might_fail git branch --man --help </dev/null >/dev/null 2>&1 &&\n-\ttest_path_is_missing .git/refs/heads/--help\n+\ttest_ref_missing refs/heads/--help\n '\n \n test_expect_success 'branch -h in broken repository' '\n@@ -40,7 +40,8 @@ test_expect_success 'branch -h in broken repository' '\n '\n \n test_expect_success 'git branch abc should create a branch' '\n-\tgit branch abc && test_path_is_file .git/refs/heads/abc\n+\tgit branch abc &&\n+\ttest_ref_exists refs/heads/abc\n '\n \n test_expect_success 'git branch abc should fail when abc exists' '\n@@ -61,11 +62,13 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '\n '\n \n test_expect_success 'git branch a/b/c should create a branch' '\n-\tgit branch a/b/c && test_path_is_file .git/refs/heads/a/b/c\n+\tgit branch a/b/c &&\n+\ttest_ref_exists refs/heads/a/b/c\n '\n \n test_expect_success 'git branch mb main... should create a branch' '\n-\tgit branch mb main... && test_path_is_file .git/refs/heads/mb\n+\tgit branch mb main... &&\n+\ttest_ref_exists refs/heads/mb\n '\n \n test_expect_success 'git branch HEAD should fail' '\n@@ -78,14 +81,14 @@ EOF\n test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' '\n \tGIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n \tgit -c core.logallrefupdates=false branch --create-reflog d/e/f &&\n-\ttest_path_is_file .git/refs/heads/d/e/f &&\n+\ttest_ref_exists refs/heads/d/e/f &&\n \ttest_path_is_file .git/logs/refs/heads/d/e/f &&\n \ttest_cmp expect .git/logs/refs/heads/d/e/f\n '\n \n test_expect_success 'git branch -d d/e/f should delete a branch and a log' '\n \tgit branch -d d/e/f &&\n-\ttest_path_is_missing .git/refs/heads/d/e/f &&\n+\ttest_ref_missing refs/heads/d/e/f &&\n \ttest_must_fail git reflog exists refs/heads/d/e/f\n '\n \n@@ -213,7 +216,7 @@ test_expect_success 'git branch -M should leave orphaned HEAD alone' '\n \t\ttest_commit initial &&\n \t\tgit checkout --orphan lonely &&\n \t\tgrep lonely .git/HEAD &&\n-\t\ttest_path_is_missing .git/refs/head/lonely &&\n+\t\ttest_ref_missing refs/head/lonely &&\n \t\tgit branch -M main mistress &&\n \t\tgrep lonely .git/HEAD\n \t)\n@@ -799,8 +802,8 @@ test_expect_success 'deleting a symref' '\n \tgit symbolic-ref refs/heads/symref refs/heads/target &&\n \techo \"Deleted branch symref (was refs/heads/target).\" >expect &&\n \tgit branch -d symref >actual &&\n-\ttest_path_is_file .git/refs/heads/target &&\n-\ttest_path_is_missing .git/refs/heads/symref &&\n+\ttest_ref_exists refs/heads/target &&\n+\ttest_ref_missing refs/heads/symref &&\n \ttest_cmp expect actual\n '\n \n@@ -809,16 +812,16 @@ test_expect_success 'deleting a dangling symref' '\n \ttest_path_is_file .git/refs/heads/dangling-symref &&\n \techo \"Deleted branch dangling-symref (was nowhere).\" >expect &&\n \tgit branch -d dangling-symref >actual &&\n-\ttest_path_is_missing .git/refs/heads/dangling-symref &&\n+\ttest_ref_missing refs/heads/dangling-symref &&\n \ttest_cmp expect actual\n '\n \n test_expect_success 'deleting a self-referential symref' '\n \tgit symbolic-ref refs/heads/self-reference refs/heads/self-reference &&\n-\ttest_path_is_file .git/refs/heads/self-reference &&\n+\ttest_ref_exists refs/heads/self-reference &&\n \techo \"Deleted branch self-reference (was refs/heads/self-reference).\" >expect &&\n \tgit branch -d self-reference >actual &&\n-\ttest_path_is_missing .git/refs/heads/self-reference &&\n+\ttest_ref_missing refs/heads/self-reference &&\n \ttest_cmp expect actual\n '\n \n@@ -826,8 +829,8 @@ test_expect_success 'renaming a symref is not allowed' '\n \tgit symbolic-ref refs/heads/topic refs/heads/main &&\n \ttest_must_fail git branch -m topic new-topic &&\n \tgit symbolic-ref refs/heads/topic &&\n-\ttest_path_is_file .git/refs/heads/main &&\n-\ttest_path_is_missing .git/refs/heads/new-topic\n+\ttest_ref_exists refs/heads/main &&\n+\ttest_ref_missing refs/heads/new-topic\n '\n \n test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '\n@@ -1142,7 +1145,7 @@ EOF\n test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '\n \tGIT_COMMITTER_DATE=\"2005-05-26 23:30\" \\\n \tgit checkout -b g/h/i -l main &&\n-\ttest_path_is_file .git/refs/heads/g/h/i &&\n+\ttest_ref_exists refs/heads/g/h/i &&\n \ttest_path_is_file .git/logs/refs/heads/g/h/i &&\n \ttest_cmp expect .git/logs/refs/heads/g/h/i\n '\ndiff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh\nindex 079b2f2536e..3681859f983 100755\n--- a/t/t5521-pull-options.sh\n+++ b/t/t5521-pull-options.sh\n@@ -143,7 +143,7 @@ test_expect_success 'git pull --dry-run' '\n \t\tcd clonedry &&\n \t\tgit pull --dry-run ../parent &&\n \t\ttest_path_is_missing .git/FETCH_HEAD &&\n-\t\ttest_path_is_missing .git/refs/heads/main &&\n+\t\ttest_ref_missing refs/heads/main &&\n \t\ttest_path_is_missing .git/index &&\n \t\ttest_path_is_missing file\n \t)\n@@ -157,7 +157,7 @@ test_expect_success 'git pull --all --dry-run' '\n \t\tgit remote add origin ../parent &&\n \t\tgit pull --all --dry-run &&\n \t\ttest_path_is_missing .git/FETCH_HEAD &&\n-\t\ttest_path_is_missing .git/refs/remotes/origin/main &&\n+\t\ttest_ref_missing refs/remotes/origin/main &&\n \t\ttest_path_is_missing .git/index &&\n \t\ttest_path_is_missing file\n \t)\ndiff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh\nindex 1d7b1abda1a..946c5751885 100755\n--- a/t/t5605-clone-local.sh\n+++ b/t/t5605-clone-local.sh\n@@ -69,7 +69,7 @@ test_expect_success 'local clone of repo with nonexistent ref in HEAD' '\n \tgit clone a d &&\n \t(cd d &&\n \tgit fetch &&\n-\ttest ! -e .git/refs/remotes/origin/HEAD)\n+\ttest_ref_missing refs/remotes/origin/HEAD)\n '\n \n test_expect_success 'bundle clone without .bundle suffix' '\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 2f8868caa17..56b33536ed1 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -251,6 +251,61 @@ debug () {\n \tdone\n }\n \n+# Usage: test_ref_exists [options] <ref>\n+#\n+#   -C <dir>:\n+#      Run all git commands in directory <dir>\n+#\n+# This helper function checks whether a reference exists. Symrefs or object IDs\n+# will not be resolved. Can be used to check references with bad names.\n+test_ref_exists () {\n+\tlocal indir=\n+\n+\twhile test $# != 0\n+\tdo\n+\t\tcase \"$1\" in\n+\t\t-C)\n+\t\t\tindir=\"$2\"\n+\t\t\tshift\n+\t\t\t;;\n+\t\t*)\n+\t\t\tbreak\n+\t\t\t;;\n+\t\tesac\n+\t\tshift\n+\tdone &&\n+\n+\tindir=${indir:+\"$indir\"/} &&\n+\n+\tif test \"$#\" != 1\n+\tthen\n+\t\tBUG \"expected exactly one reference\"\n+\tfi &&\n+\n+\tgit ${indir:+ -C \"$indir\"} show-ref --exists \"$1\"\n+}\n+\n+# Behaves the same as test_ref_exists, except that it checks for the absence of\n+# a reference. This is preferable to `! test_ref_exists` as this function is\n+# able to distinguish actually-missing references from other, generic errors.\n+test_ref_missing () {\n+\ttest_ref_exists \"$@\"\n+\tcase \"$?\" in\n+\t2)\n+\t\t# This is the good case.\n+\t\treturn 0\n+\t\t;;\n+\t0)\n+\t\techo >&4 \"test_ref_missing: reference exists\"\n+\t\treturn 1\n+\t\t;;\n+\t*)\n+\t\techo >&4 \"test_ref_missing: generic error\"\n+\t\treturn 1\n+\t\t;;\n+\tesac\n+}\n+\n # Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]\n #   -C <dir>:\n #\tRun all git commands in directory <dir>\n-- \n2.42.0\n\n"},{"id":"484244","messageId":"ZUFQN8wbMEDyyfF2@nand.local","threadId":"60421","inReplyTo":"cover.1698739941.git.ps@pks.im","subject":"Re: [PATCH v3 00/12] builtin/show-ref: introduce mode to check for ref existence","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-31T19:06:31Z","receivedAt":"2023-10-31T19:06:34Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Oct 31, 2023 at 09:16:08AM +0100, Patrick Steinhardt wrote:\n> Hi,\n>\n> this is the third version of my patch series that introduces a new `git\n> show-ref --exists` mode to check for reference existence.\n>\n> Changes compared to v2:\n>\n>     - Patch 5: Document why we need `exclude_existing_options.enabled`,\n>       which isn't exactly obvious.\n>\n>     - Patch 6: Fix a grammar issue in the commit message.\n>\n>     - Patch 9: Switch to `test_cmp` instead of grep(1).\n>\n> Thanks!\n\nThanks for the updated round. I took a look at the range-diff and didn't\nsee anything surprising. This version looks great to me, thanks for\nworking on this!\n\nThanks,\nTaylor\n"}]}