{"thread":{"id":"61387","subject":"[PATCH 0/3] Clarify pseudo-ref terminology","startedAt":"2024-04-29T13:41:27Z","lastAt":"2024-05-16T04:13:53Z","messageCount":93,"participants":["Patrick Steinhardt","Phillip Wood","Junio C Hamano","Karthik Nayak","Justin Tobler","Jeff King","Kristoffer Haugsbakk","Jean-Noël AVILA"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"493627","messageId":"cover.1714398019.git.ps@pks.im","threadId":"61387","inReplyTo":null,"subject":"[PATCH 0/3] Clarify pseudo-ref terminology","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-29T13:41:19Z","receivedAt":"2024-04-29T13:41:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthere is quite some confusion around what is a pseudo ref and what is a\nspecial ref, and the documentation in gitglossary(7) overlaps. This\npatch series clarifies that:\n\n  - A pseudo ref is anything in the root directory that conforms to the\n    pseudo ref syntax (all uppercase, must end with _HEAD), except for\n    special refs. There are some exceptions that are now listed\n    explicitly, and these only exist due to historic reasons.\n\n  - Special refs are really only either FETCH_HEAD or MERGE_HEAD, where\n    the reason is that those aren't really refs, but can sometimes be\n    used as such.\n\nI was very much pressed to go a completely different way: drop the\nspecial ref term again that we have recently introduced and replace the\npseudo ref term with it. This would return pseudo refs to their original\nmeaning, namely something that is not a ref, but behaves like one at\ntimes.\n\nThe current class of pseudo refs I would then drop completely -- in my\nopinion there is just no need for it. Everything should be a ref, and\nwhat we currently call pseudo refs (things in the root hierarchy) is\nreally just a naming policy. Refs must either have pseudoref syntax or\nthey must start with \"refs/\". It doesn't help in my opinion that we give\nrefs which conform to that naming policy but happen to live in the root\ndirectory a separate name. They behave no different than a normal ref\nanyway: they are stored in the refdb and can be read and written as any\nother ref starting with \"refs/\".\n\nSo, if we went down that road, we would:\n\n  - Not have special refs anymore.\n\n  - Have two pseudo refs, FETCH_HEAD and MERGE_HEAD. There is no other\n    pseudo ref.\n\n  - Clarify that refs must either start with \"refs/\", or have an\n    all-uppercase name ending with \"_HEAD\". Exceptions to this rule are\n    \"HEAD\" and a couple of others that we wrote due to historic reasons.\n    All refs that match these rules behave the same, there is no\n    difference between root refs and refs living in \"refs/\".\n\nI think that this would be quite a lot easier to understand than the\ncurrent state of affairs we have, and also return us to the original\nmeaning of pseudorefs.\n\nIf people agree with that line of thought I'll happily revise this patch\nseries. I didn't do that yet because it would be quite a lot more work,\nso I first wanted to get some buy-in.\n\nPatrick\n\nPatrick Steinhardt (3):\n  refs: move `is_special_ref()`\n  refs: do not label special refs as pseudo refs\n  refs: fix segfault in `is_pseudoref()` when ref cannot be resolved\n\n Documentation/glossary-content.txt | 36 +++++++------\n refs.c                             | 81 ++++++++++++++----------------\n t/t6302-for-each-ref-filter.sh     | 17 +++++++\n 3 files changed, 76 insertions(+), 58 deletions(-)\n\n-- \n2.45.0-rc1\n\n"},{"id":"493628","messageId":"ded4ffabbc77e62e720e3dd50c6a1738da0f3cf7.1714398019.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714398019.git.ps@pks.im","subject":"[PATCH 1/3] refs: move `is_special_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-29T13:41:24Z","receivedAt":"2024-04-29T13:41:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Move `is_special_ref()` further up so that we can start using it in\n`is_pseudo_ref()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 62 +++++++++++++++++++++++++++++-----------------------------\n 1 file changed, 31 insertions(+), 31 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..c64f66bff9 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,6 +844,37 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n+static int is_special_ref(const char *refname)\n+{\n+\t/*\n+\t * Special references are refs that have different semantics compared\n+\t * to \"normal\" refs. These refs can thus not be stored in the ref\n+\t * backend, but must always be accessed via the filesystem. The\n+\t * following refs are special:\n+\t *\n+\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n+\t *   carries additional metadata like where it came from.\n+\t *\n+\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n+\t *   heads.\n+\t *\n+\t * Reading, writing or deleting references must consistently go either\n+\t * through the filesystem (special refs) or through the reference\n+\t * backend (normal ones).\n+\t */\n+\tstatic const char * const special_refs[] = {\n+\t\t\"FETCH_HEAD\",\n+\t\t\"MERGE_HEAD\",\n+\t};\n+\tsize_t i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(special_refs); i++)\n+\t\tif (!strcmp(refname, special_refs[i]))\n+\t\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n static int is_pseudoref_syntax(const char *refname)\n {\n \tconst char *c;\n@@ -1876,37 +1907,6 @@ static int refs_read_special_head(struct ref_store *ref_store,\n \treturn result;\n }\n \n-static int is_special_ref(const char *refname)\n-{\n-\t/*\n-\t * Special references are refs that have different semantics compared\n-\t * to \"normal\" refs. These refs can thus not be stored in the ref\n-\t * backend, but must always be accessed via the filesystem. The\n-\t * following refs are special:\n-\t *\n-\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n-\t *   carries additional metadata like where it came from.\n-\t *\n-\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n-\t *   heads.\n-\t *\n-\t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (special refs) or through the reference\n-\t * backend (normal ones).\n-\t */\n-\tstatic const char * const special_refs[] = {\n-\t\t\"FETCH_HEAD\",\n-\t\t\"MERGE_HEAD\",\n-\t};\n-\tsize_t i;\n-\n-\tfor (i = 0; i < ARRAY_SIZE(special_refs); i++)\n-\t\tif (!strcmp(refname, special_refs[i]))\n-\t\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,\n \t\t      struct object_id *oid, struct strbuf *referent,\n \t\t      unsigned int *type, int *failure_errno)\n-- \n2.45.0-rc1\n\n"},{"id":"493629","messageId":"b5e7ddb1e30acb7e3871a189beb2c828b18f9e73.1714398019.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714398019.git.ps@pks.im","subject":"[PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-29T13:41:28Z","receivedAt":"2024-04-29T13:41:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have two refs which almost behave like a ref in many contexts, but\naren't really:\n\n  - MERGE_HEAD contains the list of parents during a merge.\n\n  - FETCH_HEAD contains the list of fetched references after\n    git-fetch(1) with some annotations.\n\nThese references have been declared \"special refs\" in 8df4c5d205\n(Documentation: add \"special refs\" to the glossary, 2024-01-19).\n\nDue to their \"_HEAD\" suffix, those special refs also almost look like a\npseudo ref, even though they aren't. But because `is_pseudoref()` labels\nanything as a pseudo ref that ends with the `_HEAD` suffix, it will also\nhappily label both of the above special refs as pseudo refs.\n\nThis mis-labeling creates some weirdness and inconsistent behaviour\nacross ref backends. As special refs are never stored via a ref backend,\nthey theoretically speaking cannot know about special refs. But with the\nrecent introduction of the `--include-root-refs` flag this isn't quite\ntrue anymore: the \"files\" backend will yield all refs that look like a\npseudo ref or \"HEAD\" stored in the root directory. And given that both\nof the above look like pseudo refs, the \"files\" backend will list those,\ntoo. The \"reftable\" backend naturally cannot know about those, and\nteaching it to parse and yield these special refs very much feels like\nthe wrong way to go. So, arguably, the better direction to go is to mark\nthe \"files\" behaviour as a bug and stop yielding special refs there.\n\nConceptually, this feels like the right thing to do, too. Special refs\nreally aren't refs, they are a different file format that for some part\nmay behave like a ref. If we were designing these special refs from\nscratch, we would have likely never named it anything like a \"ref\" at\nall.\n\nSo let's double down on the path that the mentioned commit has started,\nwhich is to cleanly distinguish special refs and pseudo refs.\n\nIdeally, the proper way would be to return to the original meaning that\npseudo refs really had: a ref that behaves like a ref for most of the\npart, but isn't really a ref. We would essentially replace the current\n\"pseudoref\" term with the \"special ref\" term. The consequence is that\nall refs except for FETCH_HEAD and MERGE_HEAD would be normal refs,\nregardless of whether they live in the root hierarchy or not. The way\nthat pseudorefs are enforced now would then change to be a naming policy\nfor refs, only. It's unclear though how sensible it would be to do such\na large change to terminology now, which is why this commit does the\nnext best thing.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 36 ++++++++++++++++++------------\n refs.c                             |  2 ++\n t/t6302-for-each-ref-filter.sh     | 17 ++++++++++++++\n 3 files changed, 41 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex d71b199955..4275918fa0 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -497,20 +497,28 @@ exclude;;\n \tunusual refs.\n \n [[def_pseudoref]]pseudoref::\n-\tPseudorefs are a class of files under `$GIT_DIR` which behave\n-\tlike refs for the purposes of rev-parse, but which are treated\n-\tspecially by git.  Pseudorefs both have names that are all-caps,\n-\tand always start with a line consisting of a\n-\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n-\tpseudoref, because it is sometimes a symbolic ref.  They might\n-\toptionally contain some additional data.  `MERGE_HEAD` and\n-\t`CHERRY_PICK_HEAD` are examples.  Unlike\n-\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n-\tbe symbolic refs, and never have reflogs.  They also cannot be\n-\tupdated through the normal ref update machinery.  Instead,\n-\tthey are updated by directly writing to the files.  However,\n-\tthey can be read as if they were refs, so `git rev-parse\n-\tMERGE_HEAD` will work.\n+\tPseudorefs are references that live in the root of the reference\n+\thierarchy, outside of the usual \"refs/\" hierarchy. Pseudorefs have an\n+\tall-uppercase name and must end with a \"_HEAD\" suffix, for example\n+\t\"`BISECT_HEAD`\". Other than that, pseudorefs behave the exact same as\n+\tany other reference and can be both read and written via regular Git\n+\ttooling.\n++\n+<<def_special_ref>,Special refs>> are not pseudorefs.\n++\n+Due to historic reasons, Git has several irregular pseudo refs that do not\n+follow above rules. The following list of irregular pseudo refs is exhaustive\n+and shall not be extended in the future:\n+\n+ - \"`AUTO_MERGE`\"\n+\n+ - \"`BISECT_EXPECTED_REV`\"\n+\n+ - \"`NOTES_MERGE_PARTIAL`\"\n+\n+ - \"`NOTES_MERGE_REF`\"\n+\n+ - \"`MERGE_AUTOSTASH`\"\n \n [[def_pull]]pull::\n \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\ndiff --git a/refs.c b/refs.c\nindex c64f66bff9..567c6fc6ff 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -905,6 +905,8 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \n \tif (!is_pseudoref_syntax(refname))\n \t\treturn 0;\n+\tif (is_special_ref(refname))\n+\t\treturn 0;\n \n \tif (ends_with(refname, \"_HEAD\")) {\n \t\trefs_resolve_ref_unsafe(refs, refname,\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 948f1bb5f4..8c92fbde79 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--include-root-refs pattern does not print special refs' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit initial &&\n+\t\tgit rev-parse HEAD >.git/MERGE_HEAD &&\n+\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tHEAD\n+\t\t$(git symbolic-ref HEAD)\n+\t\trefs/tags/initial\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success '--include-root-refs with other patterns' '\n \tcat >expect <<-\\EOF &&\n \tHEAD\n-- \n2.45.0-rc1\n\n"},{"id":"493630","messageId":"88822afe950318c0312de5541a411942a163b139.1714398019.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714398019.git.ps@pks.im","subject":"[PATCH 3/3] refs: fix segfault in `is_pseudoref()` when ref cannot be resolved","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-29T13:41:33Z","receivedAt":"2024-04-29T13:41:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `is_pseudoref()` function has somewhat weird behaviour in that it\nboth checks whether a reference looks like a pseudoref, but also that\nthe reference actually resolves to an object ID.\n\nIn case a reference does not resolve though we can run into a segfault\nbecause we never initialize the local `struct object_id` variable. Thus,\nwhen `refs_resolve_ref_unsafe()` is unable to resolve the reference, the\nvariable will stay uninitialize. We then try to look up the hash algo\nvia the uninitialized value when calling `is_null_oid()`, which causes\nus to segfault.\n\nIt is somewhat questionable in the first place that we declare a ref to\nbe a pseudorefe depending on whether it resolves to an object ID or not.\nAnd to make things even worse, a symbolic ref is currently considered to\nnot be a pseudo ref either because of `RRESOLVE_REF_NO_RECURSE`, which\nwill cause us to not resolve them to an object ID. Last but not least,\nit also is inconsistent with `is_headref()`, which only checks for the\nreference to exist via `refs_ref_exists()`.\n\nRefactor the code to do the same. While that still feels somewhat fishy,\nit at least fixes the segfault for now. I have not been able to come up\nwith a reproducible test case that does not rely on other bugs and very\nintricate state.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 17 ++++-------------\n 1 file changed, 4 insertions(+), 13 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 567c6fc6ff..b35485f150 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -900,7 +900,6 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \t\t\"NOTES_MERGE_REF\",\n \t\t\"MERGE_AUTOSTASH\",\n \t};\n-\tstruct object_id oid;\n \tsize_t i;\n \n \tif (!is_pseudoref_syntax(refname))\n@@ -908,20 +907,12 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \tif (is_special_ref(refname))\n \t\treturn 0;\n \n-\tif (ends_with(refname, \"_HEAD\")) {\n-\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t&oid, NULL);\n-\t\treturn !is_null_oid(&oid);\n-\t}\n+\tif (ends_with(refname, \"_HEAD\"))\n+\t\treturn refs_ref_exists(refs, refname);\n \n \tfor (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)\n-\t\tif (!strcmp(refname, irregular_pseudorefs[i])) {\n-\t\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t\t&oid, NULL);\n-\t\t\treturn !is_null_oid(&oid);\n-\t\t}\n+\t\tif (!strcmp(refname, irregular_pseudorefs[i]))\n+\t\t\treturn refs_ref_exists(refs, refname);\n \n \treturn 0;\n }\n-- \n2.45.0-rc1\n\n"},{"id":"493639","messageId":"e57ed591-5006-453e-bc93-6c53fcab2aec@gmail.com","threadId":"61387","inReplyTo":"b5e7ddb1e30acb7e3871a189beb2c828b18f9e73.1714398019.git.ps@pks.im","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-04-29T15:12:37Z","receivedAt":"2024-04-29T15:12:40Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 29/04/2024 14:41, Patrick Steinhardt wrote:\n> diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\n> index d71b199955..4275918fa0 100644\n> --- a/Documentation/glossary-content.txt\n> +++ b/Documentation/glossary-content.txt\n> @@ -497,20 +497,28 @@ exclude;;\n>   \tunusual refs.\n>   \n>   [[def_pseudoref]]pseudoref::\n> -\tPseudorefs are a class of files under `$GIT_DIR` which behave\n> -\tlike refs for the purposes of rev-parse, but which are treated\n> -\tspecially by git.  Pseudorefs both have names that are all-caps,\n> -\tand always start with a line consisting of a\n> -\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n> -\tpseudoref, because it is sometimes a symbolic ref.  They might\n> -\toptionally contain some additional data.  `MERGE_HEAD` and\n> -\t`CHERRY_PICK_HEAD` are examples.  Unlike\n> -\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n> -\tbe symbolic refs, and never have reflogs.  They also cannot be\n> -\tupdated through the normal ref update machinery.  Instead,\n> -\tthey are updated by directly writing to the files.  However,\n> -\tthey can be read as if they were refs, so `git rev-parse\n> -\tMERGE_HEAD` will work.\n> +\tPseudorefs are references that live in the root of the reference\n> +\thierarchy, outside of the usual \"refs/\" hierarchy. Pseudorefs have an\n> +\tall-uppercase name and must end with a \"_HEAD\" suffix, for example\n> +\t\"`BISECT_HEAD`\". Other than that, pseudorefs behave the exact same as\n> +\tany other reference and can be both read and written via regular Git\n> +\ttooling.\n\nThis changes the definition to allow pseudorefs to by symbolic refs. \nWhen is_pseudoref() was introduced Junio and I had a brief discussion \nabout this restriction and he was not in favor of allowing pseudorefs to \nbe symbolic refs [1].\n\nAre there any practical implications of the changes in this patch for \nusers running commands like \"git log FETCH_HEAD\" (I can't think of any \noff the top of my head but it would be good to have some reassurance on \nthat point in the commit message)\n\nBest Wishes\n\nPhillip\n\n[1] https://lore.kernel.org/git/xmqq34u2q3zs.fsf@gitster.g/\n\n> +<<def_special_ref>,Special refs>> are not pseudorefs.\n> ++\n> +Due to historic reasons, Git has several irregular pseudo refs that do not\n> +follow above rules. The following list of irregular pseudo refs is exhaustive\n> +and shall not be extended in the future:\n> +\n> + - \"`AUTO_MERGE`\"\n> +\n> + - \"`BISECT_EXPECTED_REV`\"\n> +\n> + - \"`NOTES_MERGE_PARTIAL`\"\n> +\n> + - \"`NOTES_MERGE_REF`\"\n> +\n> + - \"`MERGE_AUTOSTASH`\"\n>   \n>   [[def_pull]]pull::\n>   \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n> diff --git a/refs.c b/refs.c\n> index c64f66bff9..567c6fc6ff 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -905,6 +905,8 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n>   \n>   \tif (!is_pseudoref_syntax(refname))\n>   \t\treturn 0;\n> +\tif (is_special_ref(refname))\n> +\t\treturn 0;\n>   \n>   \tif (ends_with(refname, \"_HEAD\")) {\n>   \t\trefs_resolve_ref_unsafe(refs, refname,\n> diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\n> index 948f1bb5f4..8c92fbde79 100755\n> --- a/t/t6302-for-each-ref-filter.sh\n> +++ b/t/t6302-for-each-ref-filter.sh\n> @@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' '\n>   \ttest_cmp expect actual\n>   '\n>   \n> +test_expect_success '--include-root-refs pattern does not print special refs' '\n> +\ttest_when_finished \"rm -rf repo\" &&\n> +\tgit init repo &&\n> +\t(\n> +\t\tcd repo &&\n> +\t\ttest_commit initial &&\n> +\t\tgit rev-parse HEAD >.git/MERGE_HEAD &&\n> +\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n> +\t\tcat >expect <<-EOF &&\n> +\t\tHEAD\n> +\t\t$(git symbolic-ref HEAD)\n> +\t\trefs/tags/initial\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n>   test_expect_success '--include-root-refs with other patterns' '\n>   \tcat >expect <<-\\EOF &&\n>   \tHEAD\n"},{"id":"493640","messageId":"dbe3283c-7814-48ca-af47-2246965b2794@gmail.com","threadId":"61387","inReplyTo":"88822afe950318c0312de5541a411942a163b139.1714398019.git.ps@pks.im","subject":"Re: [PATCH 3/3] refs: fix segfault in `is_pseudoref()` when ref cannot be resolved","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-04-29T15:25:31Z","receivedAt":"2024-04-29T15:25:33Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 29/04/2024 14:41, Patrick Steinhardt wrote:\n> The `is_pseudoref()` function has somewhat weird behaviour in that it\n> both checks whether a reference looks like a pseudoref, but also that\n> the reference actually resolves to an object ID.\n> \n> In case a reference does not resolve though we can run into a segfault\n> because we never initialize the local `struct object_id` variable. Thus,\n> when `refs_resolve_ref_unsafe()` is unable to resolve the reference, the\n> variable will stay uninitialize. We then try to look up the hash algo\n\ns/uninitialize/uninitialized/\n\n> via the uninitialized value when calling `is_null_oid()`, which causes\n> us to segfault.\n> \n> It is somewhat questionable in the first place that we declare a ref to\n> be a pseudorefe depending on whether it resolves to an object ID or not.\n\nIf I remember rightly Karthik added that check to avoid the files \nbackend calling a file with a name that matched the pseudoref syntax a \npseudoref when it wasn't actually a pseudoref.\n\n> And to make things even worse, a symbolic ref is currently considered to\n> not be a pseudo ref either because of `RRESOLVE_REF_NO_RECURSE`,\n\ns/RR/R/\n\nThat was a deliberate choice to fit with the definition of pseudorefs \nexcluding symbolic refs.\n\n> which\n> will cause us to not resolve them to an object ID. Last but not least,\n> it also is inconsistent with `is_headref()`, which only checks for the\n> reference to exist via `refs_ref_exists()`.\n> \n> Refactor the code to do the same. While that still feels somewhat fishy,\n> it at least fixes the segfault for now.\n\nAlternatively we could call oidclr() when refs_resolve_refs_unsafe() \nreturns NULL\n\nBest Wishes\n\nPhillip\n\n> I have not been able to come up\n> with a reproducible test case that does not rely on other bugs and very\n> intricate state.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>   refs.c | 17 ++++-------------\n>   1 file changed, 4 insertions(+), 13 deletions(-)\n> \n> diff --git a/refs.c b/refs.c\n> index 567c6fc6ff..b35485f150 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -900,7 +900,6 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n>   \t\t\"NOTES_MERGE_REF\",\n>   \t\t\"MERGE_AUTOSTASH\",\n>   \t};\n> -\tstruct object_id oid;\n>   \tsize_t i;\n>   \n>   \tif (!is_pseudoref_syntax(refname))\n> @@ -908,20 +907,12 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n>   \tif (is_special_ref(refname))\n>   \t\treturn 0;\n>   \n> -\tif (ends_with(refname, \"_HEAD\")) {\n> -\t\trefs_resolve_ref_unsafe(refs, refname,\n> -\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n> -\t\t\t\t\t&oid, NULL);\n> -\t\treturn !is_null_oid(&oid);\n> -\t}\n> +\tif (ends_with(refname, \"_HEAD\"))\n> +\t\treturn refs_ref_exists(refs, refname);\n>   \n>   \tfor (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)\n> -\t\tif (!strcmp(refname, irregular_pseudorefs[i])) {\n> -\t\t\trefs_resolve_ref_unsafe(refs, refname,\n> -\t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n> -\t\t\t\t\t\t&oid, NULL);\n> -\t\t\treturn !is_null_oid(&oid);\n> -\t\t}\n> +\t\tif (!strcmp(refname, irregular_pseudorefs[i]))\n> +\t\t\treturn refs_ref_exists(refs, refname);\n>   \n>   \treturn 0;\n>   }\n"},{"id":"493648","messageId":"xmqqh6fkxhk6.fsf@gitster.g","threadId":"61387","inReplyTo":"b5e7ddb1e30acb7e3871a189beb2c828b18f9e73.1714398019.git.ps@pks.im","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-29T16:24:57Z","receivedAt":"2024-04-29T16:25:01Z","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> +Due to historic reasons, Git has several irregular pseudo refs that do not\n> +follow above rules. The following list of irregular pseudo refs is exhaustive\n> +and shall not be extended in the future:\n\nI like this part of the patch the most ;-).\n"},{"id":"493675","messageId":"CAOLa=ZTCCgtQiQbm_GU--1+Yq-qYJANzTvRmvVrqNmzchkVevw@mail.gmail.com","threadId":"61387","inReplyTo":"88822afe950318c0312de5541a411942a163b139.1714398019.git.ps@pks.im","subject":"Re: [PATCH 3/3] refs: fix segfault in `is_pseudoref()` when ref cannot be resolved","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-29T18:57:53Z","receivedAt":"2024-04-29T18:57:55Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"> It is somewhat questionable in the first place that we declare a ref to\n> be a pseudorefe depending on whether it resolves to an object ID or not.\n\ns/pseudorefe/pseudoref\n\n[snip]\n\nPhillip Wood <phillip.wood123@gmail.com> writes:\n>> via the uninitialized value when calling `is_null_oid()`, which causes\n>> us to segfault.\n>>\n>> It is somewhat questionable in the first place that we declare a ref to\n>> be a pseudorefe depending on whether it resolves to an object ID or not.\n>\n> If I remember rightly Karthik added that check to avoid the files\n> backend calling a file with a name that matched the pseudoref syntax a\n> pseudoref when it wasn't actually a pseudoref.\n\nNot sure I follow. I think it was strictly done to ensure we don't\nconsider symrefs as pseudorefs [1].\n\n[1]: https://lore.kernel.org/git/xmqqfrymeega.fsf@gitster.g/\n"},{"id":"493680","messageId":"b43e51d0-2814-4800-b8d5-cea5f79ce318@gmail.com","threadId":"61387","inReplyTo":"CAOLa=ZTCCgtQiQbm_GU--1+Yq-qYJANzTvRmvVrqNmzchkVevw@mail.gmail.com","subject":"Re: [PATCH 3/3] refs: fix segfault in `is_pseudoref()` when ref cannot be resolved","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-04-29T19:47:13Z","receivedAt":"2024-04-29T19:47:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nOn 29/04/2024 19:57, Karthik Nayak wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>> via the uninitialized value when calling `is_null_oid()`, which causes\n>>> us to segfault.\n>>>\n>>> It is somewhat questionable in the first place that we declare a ref to\n>>> be a pseudorefe depending on whether it resolves to an object ID or not.\n>>\n>> If I remember rightly Karthik added that check to avoid the files\n>> backend calling a file with a name that matched the pseudoref syntax a\n>> pseudoref when it wasn't actually a pseudoref.\n> \n> Not sure I follow. I think it was strictly done to ensure we don't\n> consider symrefs as pseudorefs [1].\n\nJunio suggested using refs_read_ref_unsafe() to ensure we don't consider \nsymrefs as pseudorefs but your patch was already reading the ref to \nensure it was not some random file whose name matches the pseudoref syntax.\n\nBest Wishes\n\nPhillip\n"},{"id":"493707","messageId":"CAOLa=ZSqtGRVVHma-=XzErVdZ04sY_bGvk8qiabNdYzojS2ZhA@mail.gmail.com","threadId":"61387","inReplyTo":"b43e51d0-2814-4800-b8d5-cea5f79ce318@gmail.com","subject":"Re: [PATCH 3/3] refs: fix segfault in `is_pseudoref()` when ref cannot be resolved","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-29T20:44:44Z","receivedAt":"2024-04-29T20:44:46Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Hey Phillip,\n\nPhillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Karthik\n>\n> On 29/04/2024 19:57, Karthik Nayak wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>>> via the uninitialized value when calling `is_null_oid()`, which causes\n>>>> us to segfault.\n>>>>\n>>>> It is somewhat questionable in the first place that we declare a ref to\n>>>> be a pseudorefe depending on whether it resolves to an object ID or not.\n>>>\n>>> If I remember rightly Karthik added that check to avoid the files\n>>> backend calling a file with a name that matched the pseudoref syntax a\n>>> pseudoref when it wasn't actually a pseudoref.\n>>\n>> Not sure I follow. I think it was strictly done to ensure we don't\n>> consider symrefs as pseudorefs [1].\n>\n> Junio suggested using refs_read_ref_unsafe() to ensure we don't consider\n> symrefs as pseudorefs but your patch was already reading the ref to\n> ensure it was not some random file whose name matches the pseudoref syntax.\n>\n> Best Wishes\n>\n> Phillip\n\nOh yes. You're absolutely correct. I just didn't understand what you\nwere referring to :)\n"},{"id":"493712","messageId":"5322n7eqkov3fqw2zw2seiqyqcmmrlo35camq76n546dkgycru@njuj32gmbogh","threadId":"61387","inReplyTo":"b5e7ddb1e30acb7e3871a189beb2c828b18f9e73.1714398019.git.ps@pks.im","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2024-04-29T22:52:41Z","receivedAt":"2024-04-29T22:53:57Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 24/04/29 03:41PM, Patrick Steinhardt wrote:\n> diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\n> index d71b199955..4275918fa0 100644\n> --- a/Documentation/glossary-content.txt\n> +++ b/Documentation/glossary-content.txt\n> @@ -497,20 +497,28 @@ exclude;;\n>  \tunusual refs.\n>  \n>  [[def_pseudoref]]pseudoref::\n> -\tPseudorefs are a class of files under `$GIT_DIR` which behave\n> -\tlike refs for the purposes of rev-parse, but which are treated\n> -\tspecially by git.  Pseudorefs both have names that are all-caps,\n> -\tand always start with a line consisting of a\n> -\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n> -\tpseudoref, because it is sometimes a symbolic ref.  They might\n\nWe remove the example here about HEAD not being a pseudoref. This\nexample seems helpful to indicate that a pseudoref cannot be a symbolic\nref. Is this no longer the case and the change intended?\n\n> -\toptionally contain some additional data.  `MERGE_HEAD` and\n> -\t`CHERRY_PICK_HEAD` are examples.  Unlike\n> -\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n> -\tbe symbolic refs, and never have reflogs.  They also cannot be\n> -\tupdated through the normal ref update machinery.  Instead,\n> -\tthey are updated by directly writing to the files.  However,\n> -\tthey can be read as if they were refs, so `git rev-parse\n> -\tMERGE_HEAD` will work.\n> +\tPseudorefs are references that live in the root of the reference\n> +\thierarchy, outside of the usual \"refs/\" hierarchy. Pseudorefs have an\n> +\tall-uppercase name and must end with a \"_HEAD\" suffix, for example\n> +\t\"`BISECT_HEAD`\". Other than that, pseudorefs behave the exact same as\n> +\tany other reference and can be both read and written via regular Git\n> +\ttooling.\n\nPseudorefs behaving the same and using the same tooling seems to\ncontridict the previous documentation. I assume the previous information\nwas out-of-date, but it might be nice to explain this in the message.\n\n> ++\n> +<<def_special_ref>,Special refs>> are not pseudorefs.\n> ++\n> +Due to historic reasons, Git has several irregular pseudo refs that do not\n> +follow above rules. The following list of irregular pseudo refs is exhaustive\n\nWe seem to be inconsistent between using \"pseudoref\" and \"pseudo ref\".\nNot sure it we want to be consistent here. \n\n-Justin\n\n> +and shall not be extended in the future:\n> +\n> + - \"`AUTO_MERGE`\"\n> +\n> + - \"`BISECT_EXPECTED_REV`\"\n> +\n> + - \"`NOTES_MERGE_PARTIAL`\"\n> +\n> + - \"`NOTES_MERGE_REF`\"\n> +\n> + - \"`MERGE_AUTOSTASH`\"\n>  \n>  [[def_pull]]pull::\n>  \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n> diff --git a/refs.c b/refs.c\n> index c64f66bff9..567c6fc6ff 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -905,6 +905,8 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n>  \n>  \tif (!is_pseudoref_syntax(refname))\n>  \t\treturn 0;\n> +\tif (is_special_ref(refname))\n> +\t\treturn 0;\n>  \n>  \tif (ends_with(refname, \"_HEAD\")) {\n>  \t\trefs_resolve_ref_unsafe(refs, refname,\n> diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\n> index 948f1bb5f4..8c92fbde79 100755\n> --- a/t/t6302-for-each-ref-filter.sh\n> +++ b/t/t6302-for-each-ref-filter.sh\n> @@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success '--include-root-refs pattern does not print special refs' '\n> +\ttest_when_finished \"rm -rf repo\" &&\n> +\tgit init repo &&\n> +\t(\n> +\t\tcd repo &&\n> +\t\ttest_commit initial &&\n> +\t\tgit rev-parse HEAD >.git/MERGE_HEAD &&\n> +\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n> +\t\tcat >expect <<-EOF &&\n> +\t\tHEAD\n> +\t\t$(git symbolic-ref HEAD)\n> +\t\trefs/tags/initial\n> +\t\tEOF\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n>  test_expect_success '--include-root-refs with other patterns' '\n>  \tcat >expect <<-\\EOF &&\n>  \tHEAD\n> -- \n> 2.45.0-rc1\n> \n\n\n"},{"id":"493737","messageId":"ZjCd97dqiHfkm1UG@tanuki","threadId":"61387","inReplyTo":"5322n7eqkov3fqw2zw2seiqyqcmmrlo35camq76n546dkgycru@njuj32gmbogh","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T07:29:59Z","receivedAt":"2024-04-30T07:30:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Apr 29, 2024 at 05:52:41PM -0500, Justin Tobler wrote:\n> On 24/04/29 03:41PM, Patrick Steinhardt wrote:\n> > diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\n> > index d71b199955..4275918fa0 100644\n> > --- a/Documentation/glossary-content.txt\n> > +++ b/Documentation/glossary-content.txt\n> > @@ -497,20 +497,28 @@ exclude;;\n> >  \tunusual refs.\n> >  \n> >  [[def_pseudoref]]pseudoref::\n> > -\tPseudorefs are a class of files under `$GIT_DIR` which behave\n> > -\tlike refs for the purposes of rev-parse, but which are treated\n> > -\tspecially by git.  Pseudorefs both have names that are all-caps,\n> > -\tand always start with a line consisting of a\n> > -\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n> > -\tpseudoref, because it is sometimes a symbolic ref.  They might\n> \n> We remove the example here about HEAD not being a pseudoref. This\n> example seems helpful to indicate that a pseudoref cannot be a symbolic\n> ref. Is this no longer the case and the change intended?\n\nI just don't see why we would want to have this restriction. Honestly,\nthe more I think about this whole topic the more I want to go into the\ndirection I've hinted at in the cover letter: drop \"special refs\" and\ndefine pseudo refs as either FETCH_HEAD or MERGE_HEAD. Everything else\nis just a normal ref, even though some of those may live in the root\ndirectory if they conform to a set of strict rules:\n\n  - All upppercase characters plus underscores.\n\n  - Must end with \"_HEAD\", except a list of known irregular root refs.\n\nI feel like the world would be better like this.\n\n> > -\toptionally contain some additional data.  `MERGE_HEAD` and\n> > -\t`CHERRY_PICK_HEAD` are examples.  Unlike\n> > -\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n> > -\tbe symbolic refs, and never have reflogs.  They also cannot be\n> > -\tupdated through the normal ref update machinery.  Instead,\n> > -\tthey are updated by directly writing to the files.  However,\n> > -\tthey can be read as if they were refs, so `git rev-parse\n> > -\tMERGE_HEAD` will work.\n> > +\tPseudorefs are references that live in the root of the reference\n> > +\thierarchy, outside of the usual \"refs/\" hierarchy. Pseudorefs have an\n> > +\tall-uppercase name and must end with a \"_HEAD\" suffix, for example\n> > +\t\"`BISECT_HEAD`\". Other than that, pseudorefs behave the exact same as\n> > +\tany other reference and can be both read and written via regular Git\n> > +\ttooling.\n> \n> Pseudorefs behaving the same and using the same tooling seems to\n> contridict the previous documentation. I assume the previous information\n> was out-of-date, but it might be nice to explain this in the message.\n\nYes, and I actually want to change this. We never enforced restrictions\nfor pseudorefs anyway, they can be symrefs just fine. And neither would\nI see any reason why that should be the case in the first place.\n\n> > ++\n> > +<<def_special_ref>,Special refs>> are not pseudorefs.\n> > ++\n> > +Due to historic reasons, Git has several irregular pseudo refs that do not\n> > +follow above rules. The following list of irregular pseudo refs is exhaustive\n> \n> We seem to be inconsistent between using \"pseudoref\" and \"pseudo ref\".\n> Not sure it we want to be consistent here. \n\nMakes sense.\n\nPatrick\n\n> -Justin\n> \n> > +and shall not be extended in the future:\n> > +\n> > + - \"`AUTO_MERGE`\"\n> > +\n> > + - \"`BISECT_EXPECTED_REV`\"\n> > +\n> > + - \"`NOTES_MERGE_PARTIAL`\"\n> > +\n> > + - \"`NOTES_MERGE_REF`\"\n> > +\n> > + - \"`MERGE_AUTOSTASH`\"\n> >  \n> >  [[def_pull]]pull::\n> >  \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n> > diff --git a/refs.c b/refs.c\n> > index c64f66bff9..567c6fc6ff 100644\n> > --- a/refs.c\n> > +++ b/refs.c\n> > @@ -905,6 +905,8 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n> >  \n> >  \tif (!is_pseudoref_syntax(refname))\n> >  \t\treturn 0;\n> > +\tif (is_special_ref(refname))\n> > +\t\treturn 0;\n> >  \n> >  \tif (ends_with(refname, \"_HEAD\")) {\n> >  \t\trefs_resolve_ref_unsafe(refs, refname,\n> > diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\n> > index 948f1bb5f4..8c92fbde79 100755\n> > --- a/t/t6302-for-each-ref-filter.sh\n> > +++ b/t/t6302-for-each-ref-filter.sh\n> > @@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' '\n> >  \ttest_cmp expect actual\n> >  '\n> >  \n> > +test_expect_success '--include-root-refs pattern does not print special refs' '\n> > +\ttest_when_finished \"rm -rf repo\" &&\n> > +\tgit init repo &&\n> > +\t(\n> > +\t\tcd repo &&\n> > +\t\ttest_commit initial &&\n> > +\t\tgit rev-parse HEAD >.git/MERGE_HEAD &&\n> > +\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n> > +\t\tcat >expect <<-EOF &&\n> > +\t\tHEAD\n> > +\t\t$(git symbolic-ref HEAD)\n> > +\t\trefs/tags/initial\n> > +\t\tEOF\n> > +\t\ttest_cmp expect actual\n> > +\t)\n> > +'\n> > +\n> >  test_expect_success '--include-root-refs with other patterns' '\n> >  \tcat >expect <<-\\EOF &&\n> >  \tHEAD\n> > -- \n> > 2.45.0-rc1\n> > \n> \n> \n"},{"id":"493738","messageId":"ZjCd_drNT6kyjNa6@tanuki","threadId":"61387","inReplyTo":"e57ed591-5006-453e-bc93-6c53fcab2aec@gmail.com","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T07:30:05Z","receivedAt":"2024-04-30T07:30:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Apr 29, 2024 at 04:12:37PM +0100, Phillip Wood wrote:\n> Hi Patrick\n> \n> On 29/04/2024 14:41, Patrick Steinhardt wrote:\n> > diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\n> > index d71b199955..4275918fa0 100644\n> > --- a/Documentation/glossary-content.txt\n> > +++ b/Documentation/glossary-content.txt\n> > @@ -497,20 +497,28 @@ exclude;;\n> >   \tunusual refs.\n> >   [[def_pseudoref]]pseudoref::\n> > -\tPseudorefs are a class of files under `$GIT_DIR` which behave\n> > -\tlike refs for the purposes of rev-parse, but which are treated\n> > -\tspecially by git.  Pseudorefs both have names that are all-caps,\n> > -\tand always start with a line consisting of a\n> > -\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n> > -\tpseudoref, because it is sometimes a symbolic ref.  They might\n> > -\toptionally contain some additional data.  `MERGE_HEAD` and\n> > -\t`CHERRY_PICK_HEAD` are examples.  Unlike\n> > -\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n> > -\tbe symbolic refs, and never have reflogs.  They also cannot be\n> > -\tupdated through the normal ref update machinery.  Instead,\n> > -\tthey are updated by directly writing to the files.  However,\n> > -\tthey can be read as if they were refs, so `git rev-parse\n> > -\tMERGE_HEAD` will work.\n> > +\tPseudorefs are references that live in the root of the reference\n> > +\thierarchy, outside of the usual \"refs/\" hierarchy. Pseudorefs have an\n> > +\tall-uppercase name and must end with a \"_HEAD\" suffix, for example\n> > +\t\"`BISECT_HEAD`\". Other than that, pseudorefs behave the exact same as\n> > +\tany other reference and can be both read and written via regular Git\n> > +\ttooling.\n> \n> This changes the definition to allow pseudorefs to by symbolic refs. When\n> is_pseudoref() was introduced Junio and I had a brief discussion about this\n> restriction and he was not in favor of allowing pseudorefs to be symbolic\n> refs [1].\n\nSo the reason why pseudorefs exist is that some refs behave like a ref\nsometimes, but not always. And in my book that really only applies to\nMERGE_HEAD and FETCH_HEAD, because those contain additional metadata\nthat makes them not-a-ref. And for those I very much see that they\nshould not ever be a symref.\n\nBut everyhing else living in the root of the ref hierarchy is not\nspecial in any way, at least not in my opinion. We have never enforced\nthat those cannot be symrefs, and it makes our terminology needlessly\nconfusing.\n\nI think I'm going to reroll this patch series and go down the nuclear\npath that I've hinted at in the cover letter:\n\n  - Pseudo refs can only be either FETCH_HEAD or MERGE_HEAD.\n\n  - Refs starting with \"refs/\" are just plain normal refs.\n\n  - Refs living in the root of the ref hierarchy need to conform to a\n    set of strict rules, as Peff is starting to enforce in a separate\n    patch series. These are just normal refs, as well, even though we\n    may call them \"root ref\" in our tooling as they live in the root of\n    the ref hierarchy.\n\nI just don't think that the current state makes sense to anybody. It's\nmajorly confusing -- I've spent the last 8 months working in our refs\ncode almost exclusively and still forget what's what. How are our users\nexpected to understand this?\n\n> Are there any practical implications of the changes in this patch for users\n> running commands like \"git log FETCH_HEAD\" (I can't think of any off the top\n> of my head but it would be good to have some reassurance on that point in\n> the commit message)\n\nNot really, no. We have never been doing a good job at enforcing the\ndifference between pseudo refs or normal refs anyway. Pseudo refs can be\nsymrefs just fine, and our tooling won't complain. The only exception\nwhere I want us to become stricter is in how we enforce the syntax rules\nfor root refs (which is handled by Peff in a separate patch series), and\nthat we start to not treat FETCH_HEAD and MERGE_HEAD as proper refs.\nThey should still resolve when you ask git-rev-parse(1), but when you\niterate through refs they should not be surfaced as they _aren't_ refs.\n\nPatrick\n"},{"id":"493739","messageId":"ZjCeAxbL9bufwGEN@tanuki","threadId":"61387","inReplyTo":"CAOLa=ZTCCgtQiQbm_GU--1+Yq-qYJANzTvRmvVrqNmzchkVevw@mail.gmail.com","subject":"Re: [PATCH 3/3] refs: fix segfault in `is_pseudoref()` when ref cannot be resolved","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T07:30:11Z","receivedAt":"2024-04-30T07:30:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Apr 29, 2024 at 11:57:53AM -0700, Karthik Nayak wrote:\n> > It is somewhat questionable in the first place that we declare a ref to\n> > be a pseudorefe depending on whether it resolves to an object ID or not.\n> \n> s/pseudorefe/pseudoref\n> \n> [snip]\n> \n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> >> via the uninitialized value when calling `is_null_oid()`, which causes\n> >> us to segfault.\n> >>\n> >> It is somewhat questionable in the first place that we declare a ref to\n> >> be a pseudorefe depending on whether it resolves to an object ID or not.\n> >\n> > If I remember rightly Karthik added that check to avoid the files\n> > backend calling a file with a name that matched the pseudoref syntax a\n> > pseudoref when it wasn't actually a pseudoref.\n> \n> Not sure I follow. I think it was strictly done to ensure we don't\n> consider symrefs as pseudorefs [1].\n> \n> [1]: https://lore.kernel.org/git/xmqqfrymeega.fsf@gitster.g/\n\nAnd that's fair from a terminology perspective. But honestly, I really\ndoubt that any user will understand that REBASE_HEAD is a pseudoref when\nit contains an object ID, but is not a pseudoref when it is a symref.\n\nAnyway, as I've said in parallel mails, I want to change the definition\nof what a pseudoref is. I just think that the current mess is understood\nby nobody and doesn't make any sense.\n\nI'll thus implicitly address this in my v2.\n\nPatrick\n"},{"id":"493742","messageId":"e9c70778-3414-48ae-8a7d-6b6f0dfaad63@gmail.com","threadId":"61387","inReplyTo":"ZjCd_drNT6kyjNa6@tanuki","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-04-30T09:59:36Z","receivedAt":"2024-04-30T09:59:39Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 30/04/2024 08:30, Patrick Steinhardt wrote:\n> On Mon, Apr 29, 2024 at 04:12:37PM +0100, Phillip Wood wrote:\n>\n>> This changes the definition to allow pseudorefs to by symbolic refs. When\n>> is_pseudoref() was introduced Junio and I had a brief discussion about this\n>> restriction and he was not in favor of allowing pseudorefs to be symbolic\n>> refs [1].\n> \n> So the reason why pseudorefs exist is that some refs behave like a ref\n> sometimes, but not always. And in my book that really only applies to\n> MERGE_HEAD and FETCH_HEAD, because those contain additional metadata\n> that makes them not-a-ref. And for those I very much see that they\n> should not ever be a symref.\n> \n> But everyhing else living in the root of the ref hierarchy is not\n> special in any way, at least not in my opinion. We have never enforced\n> that those cannot be symrefs, and it makes our terminology needlessly\n> confusing.\n\nI agree HEAD not being a pseudoref and having special refs as well as \npseudorefs refs is confusing. I do have some sympathy for the argument \nthat pseudorefs should not be symbolic refs though as AUTO_MERGE, \nCHERRY_PICK_HEAD, ORIG_HEAD etc. are all pointers to a commit and it \nwould be a bug for them to be a symbolic ref. It is unfortunate that in \nthe move away from assessing those refs as files we lost the check that \nthey are not symbolic refs.\n\n> I think I'm going to reroll this patch series and go down the nuclear\n> path that I've hinted at in the cover letter:\n> \n>    - Pseudo refs can only be either FETCH_HEAD or MERGE_HEAD.\n> \n>    - Refs starting with \"refs/\" are just plain normal refs.\n> \n>    - Refs living in the root of the ref hierarchy need to conform to a\n>      set of strict rules, as Peff is starting to enforce in a separate\n>      patch series. These are just normal refs, as well, even though we\n>      may call them \"root ref\" in our tooling as they live in the root of\n>      the ref hierarchy.\n\nThat would certainly be simpler.\n\n> I just don't think that the current state makes sense to anybody. It's\n> majorly confusing -- I've spent the last 8 months working in our refs\n> code almost exclusively and still forget what's what. How are our users\n> expected to understand this?\n\nThe current state is confusing but arguably there is a logic to the \nvarious distinctions - whether those distinctions are useful in practice \nis open to debate though. I wonder how much users really care about \nthese distinctions and whether it affects their use of git. I was \nunaware of the distinction between HEAD and pseudorefs until I reviewed \nKarthik's for-each-ref series a couple of months ago and I don't think \nthat lack of knowledge had caused me any trouble when using git.\n\n>> Are there any practical implications of the changes in this patch for users\n>> running commands like \"git log FETCH_HEAD\" (I can't think of any off the top\n>> of my head but it would be good to have some reassurance on that point in\n>> the commit message)\n> \n> Not really, no. We have never been doing a good job at enforcing the\n> difference between pseudo refs or normal refs anyway. Pseudo refs can be\n> symrefs just fine, and our tooling won't complain. The only exception\n> where I want us to become stricter is in how we enforce the syntax rules\n> for root refs (which is handled by Peff in a separate patch series), and\n> that we start to not treat FETCH_HEAD and MERGE_HEAD as proper refs.\n> They should still resolve when you ask git-rev-parse(1), but when you\n> iterate through refs they should not be surfaced as they _aren't_ refs.\n\nThat's good\n\nThanks\n\nPhillip\n\n"},{"id":"493747","messageId":"20240430102310.GC1279403@coredump.intra.peff.net","threadId":"61387","inReplyTo":"ZjCd_drNT6kyjNa6@tanuki","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-04-30T10:23:10Z","receivedAt":"2024-04-30T10:23:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Apr 30, 2024 at 09:30:05AM +0200, Patrick Steinhardt wrote:\n\n> So the reason why pseudorefs exist is that some refs behave like a ref\n> sometimes, but not always. And in my book that really only applies to\n> MERGE_HEAD and FETCH_HEAD, because those contain additional metadata\n> that makes them not-a-ref. And for those I very much see that they\n> should not ever be a symref.\n> \n> But everyhing else living in the root of the ref hierarchy is not\n> special in any way, at least not in my opinion. We have never enforced\n> that those cannot be symrefs, and it makes our terminology needlessly\n> confusing.\n> \n> I think I'm going to reroll this patch series and go down the nuclear\n> path that I've hinted at in the cover letter:\n> \n>   - Pseudo refs can only be either FETCH_HEAD or MERGE_HEAD.\n> \n>   - Refs starting with \"refs/\" are just plain normal refs.\n> \n>   - Refs living in the root of the ref hierarchy need to conform to a\n>     set of strict rules, as Peff is starting to enforce in a separate\n>     patch series. These are just normal refs, as well, even though we\n>     may call them \"root ref\" in our tooling as they live in the root of\n>     the ref hierarchy.\n> \n> I just don't think that the current state makes sense to anybody. It's\n> majorly confusing -- I've spent the last 8 months working in our refs\n> code almost exclusively and still forget what's what. How are our users\n> expected to understand this?\n\nYes, I very much agree with your final paragraph. I have been working on\nGit for 18 years, and am learning new things about pseudo and special\nrefs in this thread. ;) (Admittedly, I think that distinction is new in\nthe past few months).\n\nI think the \"everything is a ref, even at the root\" is the simplest\nthing for users. And the only rules they need to know are the syntactic\nones: names start with \"refs/\" or are all-caps and underscore. But I do\nnot see the value in them caring that HEAD can be a symref or that\nMERGE_HEAD cannot (nor the value in the code making such a distinction).\n\nMy series does not enforce the \"_HEAD\" suffix (plus special cases) as a\nsyntactic rule, but we could do that easily on top. That would help\nprotect case-insensitive filesystems from the same shenanigans that my\nseries aims for (e.g., \"CONFIG\" on such a system will still look at the\n\"config\" file).\n\nIt is unfortunate to me that we even need to call out FETCH_HEAD and\nMERGE_HEAD. I know they are special within Git, and probably ref\nbackends need to be aware (because they have to be able to carry extra\ndata). But from a user's perspective they resolve in the normal way\n(unless you are trying to look at them in their special non-ref way).\nI guess the user must care that they will always be in the filesystem in\norder to access them in that special way, though.\n\n> > Are there any practical implications of the changes in this patch for users\n> > running commands like \"git log FETCH_HEAD\" (I can't think of any off the top\n> > of my head but it would be good to have some reassurance on that point in\n> > the commit message)\n> \n> Not really, no. We have never been doing a good job at enforcing the\n> difference between pseudo refs or normal refs anyway. Pseudo refs can be\n> symrefs just fine, and our tooling won't complain. The only exception\n> where I want us to become stricter is in how we enforce the syntax rules\n> for root refs (which is handled by Peff in a separate patch series), and\n> that we start to not treat FETCH_HEAD and MERGE_HEAD as proper refs.\n> They should still resolve when you ask git-rev-parse(1), but when you\n> iterate through refs they should not be surfaced as they _aren't_ refs.\n\nI actually would not even mind if they are surfaced when iterating with\n--include-root-refs. But then I am a little skeptical of the purpose of\nthat feature in the first place. After all, the reason code shoves stuff\ninto .git/FOO_HEAD is precisely because we don't want other stuff\niterating over them, using them for reachability, and so on. That is why\n\"--all\" does not include them, for example.\n\nBut I did not follow the development of the feature, so maybe I am\nmissing some cool use case.\n\n-Peff\n"},{"id":"493756","messageId":"CAOLa=ZSLx3BQa8zHyDZp5CDuQc4rgSk1LjSX=uQViWXsiv1LZQ@mail.gmail.com","threadId":"61387","inReplyTo":"20240430102310.GC1279403@coredump.intra.peff.net","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-30T12:07:19Z","receivedAt":"2024-04-30T12:07:22Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Tue, Apr 30, 2024 at 09:30:05AM +0200, Patrick Steinhardt wrote:\n>\n>> So the reason why pseudorefs exist is that some refs behave like a ref\n>> sometimes, but not always. And in my book that really only applies to\n>> MERGE_HEAD and FETCH_HEAD, because those contain additional metadata\n>> that makes them not-a-ref. And for those I very much see that they\n>> should not ever be a symref.\n>>\n>> But everyhing else living in the root of the ref hierarchy is not\n>> special in any way, at least not in my opinion. We have never enforced\n>> that those cannot be symrefs, and it makes our terminology needlessly\n>> confusing.\n>>\n>> I think I'm going to reroll this patch series and go down the nuclear\n>> path that I've hinted at in the cover letter:\n>>\n>>   - Pseudo refs can only be either FETCH_HEAD or MERGE_HEAD.\n>>\n>>   - Refs starting with \"refs/\" are just plain normal refs.\n>>\n>>   - Refs living in the root of the ref hierarchy need to conform to a\n>>     set of strict rules, as Peff is starting to enforce in a separate\n>>     patch series. These are just normal refs, as well, even though we\n>>     may call them \"root ref\" in our tooling as they live in the root of\n>>     the ref hierarchy.\n>>\n>> I just don't think that the current state makes sense to anybody. It's\n>> majorly confusing -- I've spent the last 8 months working in our refs\n>> code almost exclusively and still forget what's what. How are our users\n>> expected to understand this?\n>\n> Yes, I very much agree with your final paragraph. I have been working on\n> Git for 18 years, and am learning new things about pseudo and special\n> refs in this thread. ;) (Admittedly, I think that distinction is new in\n> the past few months).\n>\n> I think the \"everything is a ref, even at the root\" is the simplest\n> thing for users. And the only rules they need to know are the syntactic\n> ones: names start with \"refs/\" or are all-caps and underscore. But I do\n> not see the value in them caring that HEAD can be a symref or that\n> MERGE_HEAD cannot (nor the value in the code making such a distinction).\n>\n> My series does not enforce the \"_HEAD\" suffix (plus special cases) as a\n> syntactic rule, but we could do that easily on top. That would help\n> protect case-insensitive filesystems from the same shenanigans that my\n> series aims for (e.g., \"CONFIG\" on such a system will still look at the\n> \"config\" file).\n>\n> It is unfortunate to me that we even need to call out FETCH_HEAD and\n> MERGE_HEAD. I know they are special within Git, and probably ref\n> backends need to be aware (because they have to be able to carry extra\n> data). But from a user's perspective they resolve in the normal way\n> (unless you are trying to look at them in their special non-ref way).\n> I guess the user must care that they will always be in the filesystem in\n> order to access them in that special way, though.\n>\n>> > Are there any practical implications of the changes in this patch for users\n>> > running commands like \"git log FETCH_HEAD\" (I can't think of any off the top\n>> > of my head but it would be good to have some reassurance on that point in\n>> > the commit message)\n>>\n>> Not really, no. We have never been doing a good job at enforcing the\n>> difference between pseudo refs or normal refs anyway. Pseudo refs can be\n>> symrefs just fine, and our tooling won't complain. The only exception\n>> where I want us to become stricter is in how we enforce the syntax rules\n>> for root refs (which is handled by Peff in a separate patch series), and\n>> that we start to not treat FETCH_HEAD and MERGE_HEAD as proper refs.\n>> They should still resolve when you ask git-rev-parse(1), but when you\n>> iterate through refs they should not be surfaced as they _aren't_ refs.\n>\n> I actually would not even mind if they are surfaced when iterating with\n> --include-root-refs. But then I am a little skeptical of the purpose of\n> that feature in the first place. After all, the reason code shoves stuff\n> into .git/FOO_HEAD is precisely because we don't want other stuff\n> iterating over them, using them for reachability, and so on. That is why\n> \"--all\" does not include them, for example.\n>\n> But I did not follow the development of the feature, so maybe I am\n> missing some cool use case.\n>\n\nThe use case was to allow us to look at these refs when working with\nthe reftable backend. Currently there is no way to do that, with the\nfiles backend, well you could just read the files. So mostly a debugging\nusecase.\n"},{"id":"493757","messageId":"ZjDf1TIqTTRMqZ1E@tanuki","threadId":"61387","inReplyTo":"e9c70778-3414-48ae-8a7d-6b6f0dfaad63@gmail.com","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:11:01Z","receivedAt":"2024-04-30T12:11:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 30, 2024 at 10:59:36AM +0100, Phillip Wood wrote:\n> Hi Patrick\n> \n> On 30/04/2024 08:30, Patrick Steinhardt wrote:\n> > On Mon, Apr 29, 2024 at 04:12:37PM +0100, Phillip Wood wrote:\n> > \n> > > This changes the definition to allow pseudorefs to by symbolic refs. When\n> > > is_pseudoref() was introduced Junio and I had a brief discussion about this\n> > > restriction and he was not in favor of allowing pseudorefs to be symbolic\n> > > refs [1].\n> > \n> > So the reason why pseudorefs exist is that some refs behave like a ref\n> > sometimes, but not always. And in my book that really only applies to\n> > MERGE_HEAD and FETCH_HEAD, because those contain additional metadata\n> > that makes them not-a-ref. And for those I very much see that they\n> > should not ever be a symref.\n> > \n> > But everyhing else living in the root of the ref hierarchy is not\n> > special in any way, at least not in my opinion. We have never enforced\n> > that those cannot be symrefs, and it makes our terminology needlessly\n> > confusing.\n> \n> I agree HEAD not being a pseudoref and having special refs as well as\n> pseudorefs refs is confusing. I do have some sympathy for the argument that\n> pseudorefs should not be symbolic refs though as AUTO_MERGE,\n> CHERRY_PICK_HEAD, ORIG_HEAD etc. are all pointers to a commit and it would\n> be a bug for them to be a symbolic ref. It is unfortunate that in the move\n> away from assessing those refs as files we lost the check that they are not\n> symbolic refs.\n\nWhile I agree that conceptually these should always be \"regular\" refs, I\nfeel like that is higher-level logic that belongs into the respective\nsubsystems that write those. I just don't see why the ref backend should\ncare about the particular usecases that those higher-level subsystems\nhave, and I can very much see that there might eventually be another\nsubsystem that actually wants a specific ref to be a symref.\n\nNo we could of course start to hard code all kinds of refs into the ref\nlayer. But I think that this is the wrong way to go, and treating the\nref store as just that, a generic store where you can store refs,\nwithout attaching specific meaning to any of the refs, is the proper way\nto go.\n\n> > I think I'm going to reroll this patch series and go down the nuclear\n> > path that I've hinted at in the cover letter:\n> > \n> >    - Pseudo refs can only be either FETCH_HEAD or MERGE_HEAD.\n> > \n> >    - Refs starting with \"refs/\" are just plain normal refs.\n> > \n> >    - Refs living in the root of the ref hierarchy need to conform to a\n> >      set of strict rules, as Peff is starting to enforce in a separate\n> >      patch series. These are just normal refs, as well, even though we\n> >      may call them \"root ref\" in our tooling as they live in the root of\n> >      the ref hierarchy.\n> \n> That would certainly be simpler.\n> \n> > I just don't think that the current state makes sense to anybody. It's\n> > majorly confusing -- I've spent the last 8 months working in our refs\n> > code almost exclusively and still forget what's what. How are our users\n> > expected to understand this?\n> \n> The current state is confusing but arguably there is a logic to the various\n> distinctions - whether those distinctions are useful in practice is open to\n> debate though. I wonder how much users really care about these distinctions\n> and whether it affects their use of git. I was unaware of the distinction\n> between HEAD and pseudorefs until I reviewed Karthik's for-each-ref series a\n> couple of months ago and I don't think that lack of knowledge had caused me\n> any trouble when using git.\n\nThere is some logic, that's true enough. I just don't think that anybody\nunderstands the logic.\n\nPatrick\n\n> > > Are there any practical implications of the changes in this patch for users\n> > > running commands like \"git log FETCH_HEAD\" (I can't think of any off the top\n> > > of my head but it would be good to have some reassurance on that point in\n> > > the commit message)\n> > \n> > Not really, no. We have never been doing a good job at enforcing the\n> > difference between pseudo refs or normal refs anyway. Pseudo refs can be\n> > symrefs just fine, and our tooling won't complain. The only exception\n> > where I want us to become stricter is in how we enforce the syntax rules\n> > for root refs (which is handled by Peff in a separate patch series), and\n> > that we start to not treat FETCH_HEAD and MERGE_HEAD as proper refs.\n> > They should still resolve when you ask git-rev-parse(1), but when you\n> > iterate through refs they should not be surfaced as they _aren't_ refs.\n> \n> That's good\n> \n> Thanks\n> \n> Phillip\n> \n"},{"id":"493759","messageId":"ZjDhEKbTFx2O-gcD@tanuki","threadId":"61387","inReplyTo":"20240430102310.GC1279403@coredump.intra.peff.net","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:16:16Z","receivedAt":"2024-04-30T12:16:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 30, 2024 at 06:23:10AM -0400, Jeff King wrote:\n> On Tue, Apr 30, 2024 at 09:30:05AM +0200, Patrick Steinhardt wrote:\n[snip]\n> > > Are there any practical implications of the changes in this patch for users\n> > > running commands like \"git log FETCH_HEAD\" (I can't think of any off the top\n> > > of my head but it would be good to have some reassurance on that point in\n> > > the commit message)\n> > \n> > Not really, no. We have never been doing a good job at enforcing the\n> > difference between pseudo refs or normal refs anyway. Pseudo refs can be\n> > symrefs just fine, and our tooling won't complain. The only exception\n> > where I want us to become stricter is in how we enforce the syntax rules\n> > for root refs (which is handled by Peff in a separate patch series), and\n> > that we start to not treat FETCH_HEAD and MERGE_HEAD as proper refs.\n> > They should still resolve when you ask git-rev-parse(1), but when you\n> > iterate through refs they should not be surfaced as they _aren't_ refs.\n> \n> I actually would not even mind if they are surfaced when iterating with\n> --include-root-refs. But then I am a little skeptical of the purpose of\n> that feature in the first place. After all, the reason code shoves stuff\n> into .git/FOO_HEAD is precisely because we don't want other stuff\n> iterating over them, using them for reachability, and so on. That is why\n> \"--all\" does not include them, for example.\n> \n> But I did not follow the development of the feature, so maybe I am\n> missing some cool use case.\n\nThe thing is that once we start to surface pseudorefs (in the sense of\nthese _really_ aren't refs) in ref-related tooling, users will want to\ntreat them as a ref, as well. And that's just bound to happen with\nplumbing like `git for-each-ref`, where a user may rightfully expect\nthat all output here can be treated like a normal ref.\n\nIn fact though, I want to double down on restrictions regarding the\npseudorefs FETCH_HEAD and MERGE_HEAD. While it's fair enough that those\ncan be read like a ref, writing to them is a totally different thing. It\ndoes not make any sense to try and write such refs, and our abstractions\naren't even prepared to write them correctly. They go through the ref\nbackend, and thus the \"reftable\" backend would write them into the\nreftable stack instead of into the filesystem. Now you could argue that\nthis should be fixed, but I don't think it is reasonable to expect the\nreftable backend to start writing loose refs for those pseudorefs.\n\nSo I'd really like to stick with the current explanation that we have in\nthe \"special ref\" glossary: pseudorefs must be written via the\nfilesystem and can't ever go through the ref backends.\n\nPatrick\n"},{"id":"493760","messageId":"cover.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714398019.git.ps@pks.im","subject":"[PATCH v2 00/10] Clarify pseudo-ref terminology","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:26:19Z","receivedAt":"2024-04-30T12:26:26Z","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 tries to clarify the\npseudoref terminology.\n\nAs I have alluded to in my first version of this patch series, I'd\nreally like to return the pseudoref terminology back to its original\nroots. Namely, a pseudoref is not a ref and does not conform to the\nformat of refs as they may contain additional metadata. With the new\ndefinition, we really only have two pseudorefs: FETCH_HEAD and\nMERGE_HEAD.\n\nThis has multiple consequences:\n\n  - Pseudorefs are never stored via the ref backend.\n\n  - Pseudorefs can be read via tools like git-rev-parse(1).\n\n  - Pseudorefs are not surfaced by tools like git-for-each-ref(1). They\n    are not refs, so a tool that goes through all refs should not\n    surface them.\n\n  - Pseudorefs cannot be written via tools like git-update-ref(1). They\n    are always written by the respective subsystems that create them via\n    the filesystem directly.\n\n  - All other refs in the root hierarchy are just plain refs. They are\n    not special. They can be symbolic or regular refs. The only thing of\n    notice here is a bunch of restrictions that they have in place\n    regarding their naming.\n\n  - Special refs are no more. Or rather, special refs are the new\n    pseudorefs.\n\nOverall, this significantly simplifies our whole terminology around\nrefs that most people didn't really understand in the first place,\nincluding myself. Furthermore, it makes it so that the ref backends\ndon't need to know about any policy except for what is a proper ref\nname. Whether refs should be symbolic or direct refs is higher-level\nlogic that belongs in the respective subsystems, and the ref backends\nshould not stand in the way as generic vessels for refs.\n\nThis patch series makes the necessary changes to out glossary as well as\nthe code.\n\nPatrick\n\nPatrick Steinhardt (10):\n  Documentation/glossary: redefine pseudorefs as special refs\n  Documentation/glossary: clarify limitations of pseudorefs\n  Documentation/glossary: define root refs as refs\n  refs: rename `is_pseudoref()` to `is_root_ref()`\n  refs: refname `is_special_ref()` to `is_pseudo_ref()`\n  refs: classify HEAD as a root ref\n  refs: root refs can be symbolic refs\n  refs: pseudorefs are no refs\n  ref-filter: properly distinuish pseudo and root refs\n  refs: refuse to write pseudorefs\n\n Documentation/glossary-content.txt |  75 +++++++++---------\n builtin/for-each-ref.c             |   2 +-\n ref-filter.c                       |  16 ++--\n ref-filter.h                       |   4 +-\n refs.c                             | 120 ++++++++++++++++-------------\n refs.h                             |  50 +++++++++++-\n refs/files-backend.c               |   3 +-\n refs/reftable-backend.c            |   3 +-\n t/t5510-fetch.sh                   |   6 +-\n t/t6302-for-each-ref-filter.sh     |  34 ++++++++\n 10 files changed, 207 insertions(+), 106 deletions(-)\n\n-- \n2.45.0\n\n"},{"id":"493761","messageId":"2489bb558543d66592fb0f3eb4d4696ba8e31fea.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 01/10] Documentation/glossary: redefine pseudorefs as special refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:26:23Z","receivedAt":"2024-04-30T12:26:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Nowadays, Git knows about three different kinds of refs. As defined in\ngitglossary(7):\n\n  - Regular refs that start with \"refs/\", like \"refs/heads/main\".\n\n  - Pseudorefs, which live in the root directory. These must have\n    all-caps names and must be a file that start with an object hash.\n    Consequently, symbolic refs are not pseudorefs because they do not\n    start with an object hash.\n\n  - Special refs, of which we only have \"FETCH_HEAD\" and \"MERGE_HEAD\".\n\nThis state is extremely confusing, and I would claim that most folks\ndon't fully understand what is what here. The current definitions also\nhave several problems:\n\n  - Where does \"HEAD\" fit in? It's not a pseudoref because it can be\n    a symbolic ref. It's not a regular ref because it does not start\n    with \"refs/\". And it's not a special ref, either.\n\n  - There is a strong overlap between pseudorefs and special refs. The\n    pseudoref section for example mentions \"MERGE_HEAD\", even though it\n    is a special ref. Is it thus both a pseudoref and a special ref?\n\n  - Why do we even need to distinguish refs that live in the root from\n    other refs when they behave just like a regular ref anyway?\n\nIn other words, the current state is quite a mess and leads to wild\ninconsistencies without much of a good reason.\n\nThe original reason why pseudorefs were introduced is that there are\nsome refs that sometimes behave like a ref, even though they aren't a\nref. And we really only have two of these nowadads, namely \"MERGE_HEAD\"\nand \"FETCH_HEAD\". Those files are never written via the ref backends,\nbut are instead written by git-fetch(1), git-pull(1) and git-merge(1).\nThey contain additional metadata that hihlights where a ref has been\nfetched from or the list of commits that have been merged.\n\nThis original intent in fact matches the definition of special refs that\nwe have recently introduced in 8df4c5d205 (Documentation: add \"special\nrefs\" to the glossary, 2024-01-19). Due to the introduction of the new\nreftable backend we were forced to distinguish those refs more clearly\nsuch that we don't ever try to read or write them via the reftable\nbackend. In the same series, we also addressed all the other cases where\nwe used to write those special refs via the filesystem directly, thus\ncircumventing the ref backend, to instead write them via the backends.\nConsequently, there are no other refs left anymore which are special.\n\nLet's address this mess and return the pseudoref terminology back to its\noriginal intent: a ref that sometimes behave like a ref, but which isn't\nreally a ref because it gets written to the filesystem directly. Or in\nother words, let's redefine pseudorefs to match the current definition\nof special refs. As special refs and pseudorefs are now the same per\ndefinition, we can drop the \"special refs\" term again. It's not exposed\nto our users and thus they wouldn't ever encounter that term anyway.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 42 +++++++++---------------------\n 1 file changed, 13 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex d71b199955..f5c0f49150 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -496,21 +496,19 @@ exclude;;\n \tthat start with `refs/bisect/`, but might later include other\n \tunusual refs.\n \n-[[def_pseudoref]]pseudoref::\n-\tPseudorefs are a class of files under `$GIT_DIR` which behave\n-\tlike refs for the purposes of rev-parse, but which are treated\n-\tspecially by git.  Pseudorefs both have names that are all-caps,\n-\tand always start with a line consisting of a\n-\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n-\tpseudoref, because it is sometimes a symbolic ref.  They might\n-\toptionally contain some additional data.  `MERGE_HEAD` and\n-\t`CHERRY_PICK_HEAD` are examples.  Unlike\n-\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n-\tbe symbolic refs, and never have reflogs.  They also cannot be\n-\tupdated through the normal ref update machinery.  Instead,\n-\tthey are updated by directly writing to the files.  However,\n-\tthey can be read as if they were refs, so `git rev-parse\n-\tMERGE_HEAD` will work.\n+[[def_pseudoref]]pseudoref ref::\n+\tA ref that has different semantics than normal refs. These refs can be\n+\taccessed via normal Git commands but may not behave the same as a\n+\tnormal ref in some cases.\n++\n+The following pseudorefs are known to Git:\n+\n+ - \"`FETCH_HEAD`\" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n+   may refer to multiple object IDs. Each object ID is annotated with metadata\n+   indicating where it was fetched from and its fetch status.\n+\n+ - \"`MERGE_HEAD`\" is written by linkgit:git-merge[1] when resolving merge\n+   conflicts. It contains all commit IDs which are being merged.\n \n [[def_pull]]pull::\n \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n@@ -638,20 +636,6 @@ The most notable example is `HEAD`.\n \tAn <<def_object,object>> used to temporarily store the contents of a\n \t<<def_dirty,dirty>> working directory and the index for future reuse.\n \n-[[def_special_ref]]special ref::\n-\tA ref that has different semantics than normal refs. These refs can be\n-\taccessed via normal Git commands but may not behave the same as a\n-\tnormal ref in some cases.\n-+\n-The following special refs are known to Git:\n-\n- - \"`FETCH_HEAD`\" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n-   may refer to multiple object IDs. Each object ID is annotated with metadata\n-   indicating where it was fetched from and its fetch status.\n-\n- - \"`MERGE_HEAD`\" is written by linkgit:git-merge[1] when resolving merge\n-   conflicts. It contains all commit IDs which are being merged.\n-\n [[def_submodule]]submodule::\n \tA <<def_repository,repository>> that holds the history of a\n \tseparate project inside another repository (the latter of\n-- \n2.45.0\n\n"},{"id":"493762","messageId":"1f2f8cf3f2cfba65787551ce6571376275ef3a8d.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 02/10] Documentation/glossary: clarify limitations of pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:26:28Z","receivedAt":"2024-04-30T12:26:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Clarify limitations that pseudorefs have:\n\n  - They can be read via git-rev-parse(1) and similar tools.\n\n  - They are not surfaced when iterating through refs, like when using\n    git-for-each-ref(1). They are no ref, so iterating through refs\n    should not surface them.\n\n  - They cannot be written via git-update-ref(1) and related commands.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex f5c0f49150..13e1aa63ab 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -498,8 +498,8 @@ exclude;;\n \n [[def_pseudoref]]pseudoref ref::\n \tA ref that has different semantics than normal refs. These refs can be\n-\taccessed via normal Git commands but may not behave the same as a\n-\tnormal ref in some cases.\n+\tread via normal Git commands, but cannot be written to by commands like\n+\tlinkgit:git-update-ref[1].\n +\n The following pseudorefs are known to Git:\n \n-- \n2.45.0\n\n"},{"id":"493763","messageId":"9659d7da3f38e706af22b4cef0a3e77f780493fd.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 03/10] Documentation/glossary: define root refs as refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:26:33Z","receivedAt":"2024-04-30T12:26:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Except for the pseudorefs MERGE_HEAD and FETCH_HEAD, all refs that live\nin the root of the ref hierarchy behave the exact same as normal refs.\nThey can be symbolic refs or direct refs and can be read, iterated over\nand written via normal tooling. All of these refs are stored in the ref\nbackends, which further demonstrates that they are just normal refs.\n\nExtend the definition of \"ref\" to also cover such root refs. The only\nadditional restriction for root refs is that they must conform to a\nspecific naming schema.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 33 +++++++++++++++++++++++-------\n 1 file changed, 26 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex 13e1aa63ab..683b727349 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -550,20 +550,39 @@ The following pseudorefs are known to Git:\n \tto the result.\n \n [[def_ref]]ref::\n-\tA name that begins with `refs/` (e.g. `refs/heads/master`)\n-\tthat points to an <<def_object_name,object name>> or another\n-\tref (the latter is called a <<def_symref,symbolic ref>>).\n+\tA name that that points to an <<def_object_name,object name>> or\n+\tanother ref (the latter is called a <<def_symref,symbolic ref>>).\n \tFor convenience, a ref can sometimes be abbreviated when used\n \tas an argument to a Git command; see linkgit:gitrevisions[7]\n \tfor details.\n \tRefs are stored in the <<def_repository,repository>>.\n +\n The ref namespace is hierarchical.\n-Different subhierarchies are used for different purposes (e.g. the\n-`refs/heads/` hierarchy is used to represent local branches).\n+Ref names must either start with `refs/` or be located in the root of\n+the hierarchy. In that case, their name must conform to the following\n+rules:\n +\n-There are a few special-purpose refs that do not begin with `refs/`.\n-The most notable example is `HEAD`.\n+ - The name consists of only upper-case characters or underscores.\n+\n+ - The name ends with \"`_HEAD`\" or is equal to \"`HEAD`\".\n++\n+There are some irregular refs in the root of the hierarchy that do not\n+match these rules. The following list is exhaustive and shall not be\n+extended in the future:\n++\n+ - AUTO_MERGE\n+\n+ - BISECT_EXPECTED_REV\n+\n+ - NOTES_MERGE_PARTIAL\n+\n+ - NOTES_MERGE_REF\n+\n+ - MERGE_AUTOSTASH\n++\n+Different subhierarchies are used for different purposes. For example,\n+the `refs/heads/` hierarchy is used to represent local branches whereas\n+the `refs/tags/` hierarchy is used to represent local tags..\n \n [[def_reflog]]reflog::\n \tA reflog shows the local \"history\" of a ref.  In other words,\n-- \n2.45.0\n\n"},{"id":"493764","messageId":"3d7ea704177c84e5afeceea5848f5606f11e99f4.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 04/10] refs: rename `is_pseudoref()` to `is_root_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:26:37Z","receivedAt":"2024-04-30T12:26:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename `is_pseudoref()` to `is_root_ref()` to adapt to the newly defined\nterminology in our gitglossary(7).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c            |  2 +-\n refs.c                  | 14 +++++++-------\n refs.h                  | 28 +++++++++++++++++++++++++++-\n refs/files-backend.c    |  2 +-\n refs/reftable-backend.c |  2 +-\n 5 files changed, 37 insertions(+), 11 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 59ad6f54dd..361beb6619 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2756,7 +2756,7 @@ static int ref_kind_from_refname(const char *refname)\n \t\t\treturn ref_kind[i].kind;\n \t}\n \n-\tif (is_pseudoref(get_main_ref_store(the_repository), refname))\n+\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n \t\treturn FILTER_REFS_PSEUDOREFS;\n \n \treturn FILTER_REFS_OTHERS;\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..0a4acde3ca 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,7 +844,7 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n-static int is_pseudoref_syntax(const char *refname)\n+static int is_root_ref_syntax(const char *refname)\n {\n \tconst char *c;\n \n@@ -860,9 +860,9 @@ static int is_pseudoref_syntax(const char *refname)\n \treturn 1;\n }\n \n-int is_pseudoref(struct ref_store *refs, const char *refname)\n+int is_root_ref(struct ref_store *refs, const char *refname)\n {\n-\tstatic const char *const irregular_pseudorefs[] = {\n+\tstatic const char *const irregular_root_refs[] = {\n \t\t\"AUTO_MERGE\",\n \t\t\"BISECT_EXPECTED_REV\",\n \t\t\"NOTES_MERGE_PARTIAL\",\n@@ -872,7 +872,7 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \tstruct object_id oid;\n \tsize_t i;\n \n-\tif (!is_pseudoref_syntax(refname))\n+\tif (!is_root_ref_syntax(refname))\n \t\treturn 0;\n \n \tif (ends_with(refname, \"_HEAD\")) {\n@@ -882,8 +882,8 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \t\treturn !is_null_oid(&oid);\n \t}\n \n-\tfor (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)\n-\t\tif (!strcmp(refname, irregular_pseudorefs[i])) {\n+\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n+\t\tif (!strcmp(refname, irregular_root_refs[i])) {\n \t\t\trefs_resolve_ref_unsafe(refs, refname,\n \t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n \t\t\t\t\t\t&oid, NULL);\n@@ -902,7 +902,7 @@ int is_headref(struct ref_store *refs, const char *refname)\n }\n \n static int is_current_worktree_ref(const char *ref) {\n-\treturn is_pseudoref_syntax(ref) || is_per_worktree_ref(ref);\n+\treturn is_root_ref_syntax(ref) || is_per_worktree_ref(ref);\n }\n \n enum ref_worktree_type parse_worktree_ref(const char *maybe_worktree_ref,\ndiff --git a/refs.h b/refs.h\nindex d278775e08..d0374c3275 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1051,7 +1051,33 @@ extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];\n  */\n void update_ref_namespace(enum ref_namespace namespace, char *ref);\n \n-int is_pseudoref(struct ref_store *refs, const char *refname);\n+/*\n+ * Check whether the reference is an existing root reference.\n+ *\n+ * A root ref is a reference that lives in the root of the reference hierarchy.\n+ * These references must conform to special syntax:\n+ *\n+ *   - Their name must be all-uppercase or underscores (\"_\").\n+ *\n+ *   - Their name must end with \"_HEAD\".\n+ *\n+ *   - Their name may not contain a slash.\n+ *\n+ * There is a special set of irregular root refs that exist due to historic\n+ * reasons, only. This list shall not be expanded in the future:\n+ *\n+ *   - AUTO_MERGE\n+ *\n+ *   - BISECT_EXPECTED_REV\n+ *\n+ *   - NOTES_MERGE_PARTIAL\n+ *\n+ *   - NOTES_MERGE_REF\n+ *\n+ *   - MERGE_AUTOSTASH\n+ */\n+int is_root_ref(struct ref_store *refs, const char *refname);\n+\n int is_headref(struct ref_store *refs, const char *refname);\n \n #endif /* REFS_H */\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..0fcb601444 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -351,7 +351,7 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n \t\tstrbuf_addstr(&refname, de->d_name);\n \n \t\tdtype = get_dtype(de, &path, 1);\n-\t\tif (dtype == DT_REG && (is_pseudoref(ref_store, de->d_name) ||\n+\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n \t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1cda48c504..5a5e64fe69 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -356,7 +356,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n-\t\t     (is_pseudoref(&iter->refs->base, iter->ref.refname) ||\n+\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n \t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n \t\t\tcontinue;\n \t\t}\n-- \n2.45.0\n\n"},{"id":"493765","messageId":"e6b6db972d8009fa2a4e17f9b46e69d94391feb5.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 05/10] refs: refname `is_special_ref()` to `is_pseudo_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:26:42Z","receivedAt":"2024-04-30T12:26:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename `is_special_ref()` to `is_pseudo_ref()` to adapt to the newly\ndefined terminology in our gitglossary(7). Note that in the preceding\ncommit we have just renamed `is_pseudoref()` to `is_root_ref()`, where\nthere may be confusion for in-flight patch series that add new calls to\n`is_pseudoref()`. In order to intentionall break such patch series we\nhave thus picked `is_pseudo_ref()` instead of `is_pseudoref()` as the\nnew name.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 0a4acde3ca..6266f77474 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1876,13 +1876,13 @@ static int refs_read_special_head(struct ref_store *ref_store,\n \treturn result;\n }\n \n-static int is_special_ref(const char *refname)\n+static int is_pseudo_ref(const char *refname)\n {\n \t/*\n-\t * Special references are refs that have different semantics compared\n-\t * to \"normal\" refs. These refs can thus not be stored in the ref\n-\t * backend, but must always be accessed via the filesystem. The\n-\t * following refs are special:\n+\t * Pseudorefs are refs that have different semantics compared to\n+\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+\t * but must always be accessed via the filesystem. The following refs\n+\t * are pseudorefs:\n \t *\n \t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n \t *   carries additional metadata like where it came from.\n@@ -1891,17 +1891,17 @@ static int is_special_ref(const char *refname)\n \t *   heads.\n \t *\n \t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (special refs) or through the reference\n+\t * through the filesystem (pseudorefs) or through the reference\n \t * backend (normal ones).\n \t */\n-\tstatic const char * const special_refs[] = {\n+\tstatic const char * const pseudo_refs[] = {\n \t\t\"FETCH_HEAD\",\n \t\t\"MERGE_HEAD\",\n \t};\n \tsize_t i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(special_refs); i++)\n-\t\tif (!strcmp(refname, special_refs[i]))\n+\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n+\t\tif (!strcmp(refname, pseudo_refs[i]))\n \t\t\treturn 1;\n \n \treturn 0;\n@@ -1912,7 +1912,7 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,\n \t\t      unsigned int *type, int *failure_errno)\n {\n \tassert(failure_errno);\n-\tif (is_special_ref(refname))\n+\tif (is_pseudo_ref(refname))\n \t\treturn refs_read_special_head(ref_store, refname, oid, referent,\n \t\t\t\t\t      type, failure_errno);\n \n-- \n2.45.0\n\n"},{"id":"493766","messageId":"44f72a7baf22c833549bdfbbf3c960b4104dcb1b.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 06/10] refs: classify HEAD as a root ref","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:26:47Z","receivedAt":"2024-04-30T12:26:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Root refs are those refs that live in the root of the ref hierarchy.\nOur old and venerable \"HEAD\" reference falls into this category, but we\ndon't yet classify it as such in `is_root_ref()`.\n\nAdapt the function to also treat \"HEAD\" as a root ref. This change is\nsafe to do for all current callers:\n\n- `ref_kind_from_refname()` already handles \"HEAD\" explicitly before\n  calling `is_root_ref()`.\n\n- The \"files\" and \"reftable\" backends explicitly called both\n  `is_root_ref()` and `is_headref()`.\n\nThis change should thus essentially be a no-op.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                  | 2 ++\n refs.h                  | 6 +++++-\n refs/files-backend.c    | 3 +--\n refs/reftable-backend.c | 3 +--\n 4 files changed, 9 insertions(+), 5 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 6266f77474..5b89e83ad7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -874,6 +874,8 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \n \tif (!is_root_ref_syntax(refname))\n \t\treturn 0;\n+\tif (is_headref(refs, refname))\n+\t\treturn 1;\n \n \tif (ends_with(refname, \"_HEAD\")) {\n \t\trefs_resolve_ref_unsafe(refs, refname,\ndiff --git a/refs.h b/refs.h\nindex d0374c3275..4ac454b0c3 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1059,7 +1059,8 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  *\n  *   - Their name must be all-uppercase or underscores (\"_\").\n  *\n- *   - Their name must end with \"_HEAD\".\n+ *   - Their name must end with \"_HEAD\". As a special rule, \"HEAD\" is a root\n+ *     ref, as well.\n  *\n  *   - Their name may not contain a slash.\n  *\n@@ -1078,6 +1079,9 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  */\n int is_root_ref(struct ref_store *refs, const char *refname);\n \n+/*\n+ * Check whether the reference is \"HEAD\" and whether it exists.\n+ */\n int is_headref(struct ref_store *refs, const char *refname);\n \n #endif /* REFS_H */\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0fcb601444..ea927c516d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -351,8 +351,7 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n \t\tstrbuf_addstr(&refname, de->d_name);\n \n \t\tdtype = get_dtype(de, &path, 1);\n-\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n-\t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n+\t\tif (dtype == DT_REG && is_root_ref(ref_store, de->d_name))\n \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n \n \t\tstrbuf_setlen(&refname, dirnamelen);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 5a5e64fe69..41555fcf64 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -356,8 +356,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n-\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n-\t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n+\t\t      is_root_ref(&iter->refs->base, iter->ref.refname))) {\n \t\t\tcontinue;\n \t\t}\n \n-- \n2.45.0\n\n"},{"id":"493767","messageId":"e90b2f8aa98493e9cd3f2c04cb58318780f9f6e5.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 07/10] refs: root refs can be symbolic refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:26:52Z","receivedAt":"2024-04-30T12:26:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before this patch series, root refs except for \"HEAD\" and our special\nrefs were classified as pseudorefs. Furthermore, our terminology\nclarified that pseudorefs must not be symbolic refs. This restriction\nis enforced in `is_root_ref()`, which explicitly checks that a supposed\nroot ref resolves to an object ID without recursing.\n\nThis has been extremely confusing right from the start because (in old\nterminology) a ref name may sometimes be a pseudoref and sometimes not\ndepending on whether it is a symbolic or regular ref. This behaviour\ndoes not seem reasonable at all and I very much doubt that it results in\nanything sane.\n\nFurthermore, the behaviour is different to `is_headref()`, which only\nchecks for the ref to exist. While that is in line with our glossary,\nthis inconsistency only adds to the confusion.\n\nLast but not least, the current behaviour can actually lead to a\nsegfault when calling `is_root_ref()` with a reference that either does\nnot exist or that is a symbolic ref because we never intialized `oid`.\n\nLet's loosen the restrictions in accordance to the new definition of\nroot refs, which are simply plain refs that may as well be a symbolic\nref. Consequently, we can just check for the ref to exist instead of\nrequiring it to be a regular ref.\n\nAdd a test that verifies that this does not change user-visible\nbehaviour. Namely, we still don't want to show broken refs to the user\nby default in git-for-each-ref(1). What this does allow though is for\ninternal callers to surface dangling root refs when they pass in the\n`DO_FOR_EACH_INCLUDE_BROKEN` flag.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                         | 50 ++++++++++++++++++++++++----------\n t/t6302-for-each-ref-filter.sh | 17 ++++++++++++\n 2 files changed, 53 insertions(+), 14 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5b89e83ad7..ca9844bc3e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -869,7 +869,10 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \t\t\"NOTES_MERGE_REF\",\n \t\t\"MERGE_AUTOSTASH\",\n \t};\n-\tstruct object_id oid;\n+\tstruct strbuf referent = STRBUF_INIT;\n+\tstruct object_id oid = { 0 };\n+\tint failure_errno, ret = 0;\n+\tunsigned int flags;\n \tsize_t i;\n \n \tif (!is_root_ref_syntax(refname))\n@@ -877,30 +880,49 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \tif (is_headref(refs, refname))\n \t\treturn 1;\n \n+\t/*\n+\t * Note that we cannot use `refs_ref_exists()` here because that also\n+\t * checks whether its target ref exists in case refname is a symbolic\n+\t * ref.\n+\t */\n \tif (ends_with(refname, \"_HEAD\")) {\n-\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t&oid, NULL);\n-\t\treturn !is_null_oid(&oid);\n+\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n+\t\t\t\t\t &flags, &failure_errno);\n+\t\tgoto done;\n \t}\n \n-\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n+\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++) {\n \t\tif (!strcmp(refname, irregular_root_refs[i])) {\n-\t\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t\t&oid, NULL);\n-\t\t\treturn !is_null_oid(&oid);\n+\t\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n+\t\t\t\t\t\t &flags, &failure_errno);\n+\t\t\tgoto done;\n \t\t}\n+\t}\n \n-\treturn 0;\n+done:\n+\tstrbuf_release(&referent);\n+\treturn ret;\n }\n \n int is_headref(struct ref_store *refs, const char *refname)\n {\n-\tif (!strcmp(refname, \"HEAD\"))\n-\t\treturn refs_ref_exists(refs, refname);\n+\tstruct strbuf referent = STRBUF_INIT;\n+\tstruct object_id oid = { 0 };\n+\tint failure_errno, ret = 0;\n+\tunsigned int flags;\n \n-\treturn 0;\n+\t/*\n+\t * Note that we cannot use `refs_ref_exists()` here because that also\n+\t * checks whether its target ref exists in case refname is a symbolic\n+\t * ref.\n+\t */\n+\tif (!strcmp(refname, \"HEAD\")) {\n+\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n+\t\t\t\t\t &flags, &failure_errno);\n+\t}\n+\n+\tstrbuf_release(&referent);\n+\treturn ret;\n }\n \n static int is_current_worktree_ref(const char *ref) {\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 948f1bb5f4..92ed8957c8 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -62,6 +62,23 @@ test_expect_success '--include-root-refs with other patterns' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--include-root-refs omits dangling symrefs' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit initial &&\n+\t\tgit symbolic-ref DANGLING_HEAD refs/heads/missing &&\n+\t\tcat >expect <<-EOF &&\n+\t\tHEAD\n+\t\t$(git symbolic-ref HEAD)\n+\t\trefs/tags/initial\n+\t\tEOF\n+\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success 'filtering with --points-at' '\n \tcat >expect <<-\\EOF &&\n \trefs/heads/main\n-- \n2.45.0\n\n"},{"id":"493768","messageId":"bc82d7ae65bb38017834121bf1fe86a3656e4c58.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 08/10] refs: pseudorefs are no refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:26:56Z","receivedAt":"2024-04-30T12:27:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `is_root_ref()` function will happily clarify a pseudoref as a root\nref, even though pseudorefs are no refs. Next to being wrong, it also\nleads to inconsistent behaviour across ref backends: while the \"files\"\nbackend accidentally knows to parse those pseudorefs and thus yields\nthem to the caller, the \"reftable\" backend won't ever see the pseudoref\nat all because they are never stored in the \"reftable\" backend.\n\nFix this issue by filtering out pseudorefs in `is_root_ref()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                         | 65 +++++++++++++++++-----------------\n t/t6302-for-each-ref-filter.sh | 17 +++++++++\n 2 files changed, 50 insertions(+), 32 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ca9844bc3e..dec9dbdc2d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,6 +844,37 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n+static int is_pseudo_ref(const char *refname)\n+{\n+\t/*\n+\t * Pseudorefs are refs that have different semantics compared to\n+\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+\t * but must always be accessed via the filesystem. The following refs\n+\t * are pseudorefs:\n+\t *\n+\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n+\t *   carries additional metadata like where it came from.\n+\t *\n+\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n+\t *   heads.\n+\t *\n+\t * Reading, writing or deleting references must consistently go either\n+\t * through the filesystem (pseudorefs) or through the reference\n+\t * backend (normal ones).\n+\t */\n+\tstatic const char * const pseudo_refs[] = {\n+\t\t\"FETCH_HEAD\",\n+\t\t\"MERGE_HEAD\",\n+\t};\n+\tsize_t i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n+\t\tif (!strcmp(refname, pseudo_refs[i]))\n+\t\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n static int is_root_ref_syntax(const char *refname)\n {\n \tconst char *c;\n@@ -875,7 +906,8 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \tunsigned int flags;\n \tsize_t i;\n \n-\tif (!is_root_ref_syntax(refname))\n+\tif (!is_root_ref_syntax(refname) ||\n+\t    is_pseudo_ref(refname))\n \t\treturn 0;\n \tif (is_headref(refs, refname))\n \t\treturn 1;\n@@ -1900,37 +1932,6 @@ static int refs_read_special_head(struct ref_store *ref_store,\n \treturn result;\n }\n \n-static int is_pseudo_ref(const char *refname)\n-{\n-\t/*\n-\t * Pseudorefs are refs that have different semantics compared to\n-\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n-\t * but must always be accessed via the filesystem. The following refs\n-\t * are pseudorefs:\n-\t *\n-\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n-\t *   carries additional metadata like where it came from.\n-\t *\n-\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n-\t *   heads.\n-\t *\n-\t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (pseudorefs) or through the reference\n-\t * backend (normal ones).\n-\t */\n-\tstatic const char * const pseudo_refs[] = {\n-\t\t\"FETCH_HEAD\",\n-\t\t\"MERGE_HEAD\",\n-\t};\n-\tsize_t i;\n-\n-\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n-\t\tif (!strcmp(refname, pseudo_refs[i]))\n-\t\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,\n \t\t      struct object_id *oid, struct strbuf *referent,\n \t\t      unsigned int *type, int *failure_errno)\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 92ed8957c8..163c378cfd 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--include-root-refs pattern does not print special refs' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit initial &&\n+\t\tgit rev-parse HEAD >.git/MERGE_HEAD &&\n+\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tHEAD\n+\t\t$(git symbolic-ref HEAD)\n+\t\trefs/tags/initial\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success '--include-root-refs with other patterns' '\n \tcat >expect <<-\\EOF &&\n \tHEAD\n-- \n2.45.0\n\n"},{"id":"493769","messageId":"95d7547b2e8c5305e76888f7dc0a41d2b9e2f558.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 09/10] ref-filter: properly distinuish pseudo and root refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:27:01Z","receivedAt":"2024-04-30T12:27:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The ref-filter interfaces currently define root refs as either a\ndetached HEAD or a pseudo ref. Pseudo refs aren't root refs though, so\nlet's properly distinguish those ref types.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/for-each-ref.c |  2 +-\n ref-filter.c           | 16 +++++++++-------\n ref-filter.h           |  4 ++--\n refs.c                 | 18 +-----------------\n refs.h                 | 18 ++++++++++++++++++\n 5 files changed, 31 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 919282e12a..5517a4a1c0 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -98,7 +98,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (include_root_refs)\n-\t\tflags |= FILTER_REFS_ROOT_REFS;\n+\t\tflags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD;\n \n \tfilter.match_as_path = 1;\n \tfilter_and_format_refs(&filter, flags, sorting, &format);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 361beb6619..d72113edfe 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2628,7 +2628,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,\n \t\t\t\t       each_ref_fn cb,\n \t\t\t\t       void *cb_data)\n {\n-\tif (filter->kind == FILTER_REFS_KIND_MASK) {\n+\tif (filter->kind & FILTER_REFS_ROOT_REFS) {\n \t\t/* In this case, we want to print all refs including root refs. */\n \t\treturn refs_for_each_include_root_refs(get_main_ref_store(the_repository),\n \t\t\t\t\t\t       cb, cb_data);\n@@ -2756,8 +2756,10 @@ static int ref_kind_from_refname(const char *refname)\n \t\t\treturn ref_kind[i].kind;\n \t}\n \n-\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n+\tif (is_pseudo_ref(refname))\n \t\treturn FILTER_REFS_PSEUDOREFS;\n+\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n+\t\treturn FILTER_REFS_ROOT_REFS;\n \n \treturn FILTER_REFS_OTHERS;\n }\n@@ -2794,11 +2796,11 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const struct\n \t/*\n \t * Generally HEAD refs are printed with special description denoting a rebase,\n \t * detached state and so forth. This is useful when only printing the HEAD ref\n-\t * But when it is being printed along with other pseudorefs, it makes sense to\n-\t * keep the formatting consistent. So we mask the type to act like a pseudoref.\n+\t * But when it is being printed along with other root refs, it makes sense to\n+\t * keep the formatting consistent. So we mask the type to act like a root ref.\n \t */\n-\tif (filter->kind == FILTER_REFS_KIND_MASK && kind == FILTER_REFS_DETACHED_HEAD)\n-\t\tkind = FILTER_REFS_PSEUDOREFS;\n+\tif (filter->kind & FILTER_REFS_ROOT_REFS && kind == FILTER_REFS_DETACHED_HEAD)\n+\t\tkind = FILTER_REFS_ROOT_REFS;\n \telse if (!(kind & filter->kind))\n \t\treturn NULL;\n \n@@ -3072,7 +3074,7 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref\n \t\t * When printing all ref types, HEAD is already included,\n \t\t * so we don't want to print HEAD again.\n \t\t */\n-\t\tif (!ret && (filter->kind != FILTER_REFS_KIND_MASK) &&\n+\t\tif (!ret && !(filter->kind & FILTER_REFS_ROOT_REFS) &&\n \t\t    (filter->kind & FILTER_REFS_DETACHED_HEAD))\n \t\t\thead_ref(fn, cb_data);\n \t}\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 0ca28d2bba..27ae1aa0d1 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -23,9 +23,9 @@\n \t\t\t\t    FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)\n #define FILTER_REFS_DETACHED_HEAD  0x0020\n #define FILTER_REFS_PSEUDOREFS     0x0040\n-#define FILTER_REFS_ROOT_REFS      (FILTER_REFS_DETACHED_HEAD | FILTER_REFS_PSEUDOREFS)\n+#define FILTER_REFS_ROOT_REFS      0x0080\n #define FILTER_REFS_KIND_MASK      (FILTER_REFS_REGULAR | FILTER_REFS_DETACHED_HEAD | \\\n-\t\t\t\t    FILTER_REFS_PSEUDOREFS)\n+\t\t\t\t    FILTER_REFS_PSEUDOREFS | FILTER_REFS_ROOT_REFS)\n \n struct atom_value;\n struct ref_sorting;\ndiff --git a/refs.c b/refs.c\nindex dec9dbdc2d..50d679b7e7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,24 +844,8 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n-static int is_pseudo_ref(const char *refname)\n+int is_pseudo_ref(const char *refname)\n {\n-\t/*\n-\t * Pseudorefs are refs that have different semantics compared to\n-\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n-\t * but must always be accessed via the filesystem. The following refs\n-\t * are pseudorefs:\n-\t *\n-\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n-\t *   carries additional metadata like where it came from.\n-\t *\n-\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n-\t *   heads.\n-\t *\n-\t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (pseudorefs) or through the reference\n-\t * backend (normal ones).\n-\t */\n \tstatic const char * const pseudo_refs[] = {\n \t\t\"FETCH_HEAD\",\n \t\t\"MERGE_HEAD\",\ndiff --git a/refs.h b/refs.h\nindex 4ac454b0c3..8255989e7e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1084,4 +1084,22 @@ int is_root_ref(struct ref_store *refs, const char *refname);\n  */\n int is_headref(struct ref_store *refs, const char *refname);\n \n+/*\n+ * Pseudorefs are refs that have different semantics compared to\n+ * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+ * but must always be accessed via the filesystem. The following refs\n+ * are pseudorefs:\n+ *\n+ * - FETCH_HEAD may contain multiple object IDs, and each one of them\n+ *   carries additional metadata like where it came from.\n+ *\n+ * - MERGE_HEAD may contain multiple object IDs when merging multiple\n+ *   heads.\n+ *\n+ * Reading, writing or deleting references must consistently go either\n+ * through the filesystem (pseudorefs) or through the reference\n+ * backend (normal ones).\n+ */\n+int is_pseudo_ref(const char *refname);\n+\n #endif /* REFS_H */\n-- \n2.45.0\n\n"},{"id":"493770","messageId":"b2029612ddd097d600a5601eb35b3d85786b911f.1714479928.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714479928.git.ps@pks.im","subject":"[PATCH v2 10/10] refs: refuse to write pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:27:06Z","receivedAt":"2024-04-30T12:27:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Pseudorefs are not stored in the ref database as by definition, they\ncarry additional metadata that essentially makes them not a ref. As\nsuch, writing pseudorefs via the ref backend does not make any sense\nwhatsoever as the ref backend wouldn't know how exactly to store the\ndata.\n\nRestrict writing pseudorefs via the ref backend.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c           | 7 +++++++\n t/t5510-fetch.sh | 6 +++---\n 2 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 50d679b7e7..7c3c7465a4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1307,6 +1307,13 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\treturn -1;\n \t}\n \n+\tif (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&\n+\t    is_pseudo_ref(refname)) {\n+\t\tstrbuf_addf(err, _(\"refusing to update pseudoref '%s'\"),\n+\t\t\t    refname);\n+\t\treturn -1;\n+\t}\n+\n \tif (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)\n \t\tBUG(\"illegal flags 0x%x passed to ref_transaction_update()\", flags);\n \ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 33d34d5ae9..4eb569f4df 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -518,7 +518,7 @@ test_expect_success 'fetch with a non-applying branch.<name>.merge' '\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [1]' '\n \tone_head=$(cd one && git rev-parse HEAD) &&\n \tthis_head=$(git rev-parse HEAD) &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -530,7 +530,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n \tone_ref=$(cd one && git symbolic-ref HEAD) &&\n \tgit config branch.main.remote blub &&\n \tgit config branch.main.merge \"$one_ref\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -540,7 +540,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n # the merge spec does not match the branch the remote HEAD points to\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [3]' '\n \tgit config branch.main.merge \"${one_ref}_not\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n-- \n2.45.0\n\n"},{"id":"493771","messageId":"ZjDlMs3Zm1JQybAx@tanuki","threadId":"61387","inReplyTo":"CAOLa=ZSLx3BQa8zHyDZp5CDuQc4rgSk1LjSX=uQViWXsiv1LZQ@mail.gmail.com","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-30T12:33:54Z","receivedAt":"2024-04-30T12:34:00Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 30, 2024 at 05:07:19AM -0700, Karthik Nayak wrote:\n> Jeff King <peff@peff.net> writes:\n> \n> > On Tue, Apr 30, 2024 at 09:30:05AM +0200, Patrick Steinhardt wrote:\n[snip]\n> >> > Are there any practical implications of the changes in this patch for users\n> >> > running commands like \"git log FETCH_HEAD\" (I can't think of any off the top\n> >> > of my head but it would be good to have some reassurance on that point in\n> >> > the commit message)\n> >>\n> >> Not really, no. We have never been doing a good job at enforcing the\n> >> difference between pseudo refs or normal refs anyway. Pseudo refs can be\n> >> symrefs just fine, and our tooling won't complain. The only exception\n> >> where I want us to become stricter is in how we enforce the syntax rules\n> >> for root refs (which is handled by Peff in a separate patch series), and\n> >> that we start to not treat FETCH_HEAD and MERGE_HEAD as proper refs.\n> >> They should still resolve when you ask git-rev-parse(1), but when you\n> >> iterate through refs they should not be surfaced as they _aren't_ refs.\n> >\n> > I actually would not even mind if they are surfaced when iterating with\n> > --include-root-refs. But then I am a little skeptical of the purpose of\n> > that feature in the first place. After all, the reason code shoves stuff\n> > into .git/FOO_HEAD is precisely because we don't want other stuff\n> > iterating over them, using them for reachability, and so on. That is why\n> > \"--all\" does not include them, for example.\n> >\n> > But I did not follow the development of the feature, so maybe I am\n> > missing some cool use case.\n> >\n> \n> The use case was to allow us to look at these refs when working with\n> the reftable backend. Currently there is no way to do that, with the\n> files backend, well you could just read the files. So mostly a debugging\n> usecase.\n\nThat's true for normal root refs, only, though. The pseudorefs (current\nspecial refs) can still be surfaced even if the for-each-ref machinery\ndoesn't surface them because by definition, they always live in the\nfilesystem.\n\nPatrick\n"},{"id":"493772","messageId":"CAOLa=ZRLrLc6ZyEFSmkfg7zMKVXfngCDqpP3FDt-qA7dHR10Ww@mail.gmail.com","threadId":"61387","inReplyTo":"2489bb558543d66592fb0f3eb4d4696ba8e31fea.1714479928.git.ps@pks.im","subject":"Re: [PATCH v2 01/10] Documentation/glossary: redefine pseudorefs as special refs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-30T12:49:19Z","receivedAt":"2024-04-30T12:49:21Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Nowadays, Git knows about three different kinds of refs. As defined in\n> gitglossary(7):\n>\n>   - Regular refs that start with \"refs/\", like \"refs/heads/main\".\n>\n>   - Pseudorefs, which live in the root directory. These must have\n>     all-caps names and must be a file that start with an object hash.\n>     Consequently, symbolic refs are not pseudorefs because they do not\n>     start with an object hash.\n>\n>   - Special refs, of which we only have \"FETCH_HEAD\" and \"MERGE_HEAD\".\n>\n\nNit: but since you go into explaining what the _old_ pseudoref is,\nperhaps you should also add a line about why \"FETCH_HEAD\" and\n\"MERGE_HEAD\" were called special refs.\n\n> This state is extremely confusing, and I would claim that most folks\n> don't fully understand what is what here. The current definitions also\n> have several problems:\n>\n>   - Where does \"HEAD\" fit in? It's not a pseudoref because it can be\n>     a symbolic ref. It's not a regular ref because it does not start\n>     with \"refs/\". And it's not a special ref, either.\n>\n>   - There is a strong overlap between pseudorefs and special refs. The\n>     pseudoref section for example mentions \"MERGE_HEAD\", even though it\n>     is a special ref. Is it thus both a pseudoref and a special ref?\n>\n>   - Why do we even need to distinguish refs that live in the root from\n>     other refs when they behave just like a regular ref anyway?\n>\n> In other words, the current state is quite a mess and leads to wild\n> inconsistencies without much of a good reason.\n>\n> The original reason why pseudorefs were introduced is that there are\n> some refs that sometimes behave like a ref, even though they aren't a\n> ref. And we really only have two of these nowadads, namely \"MERGE_HEAD\"\n> and \"FETCH_HEAD\". Those files are never written via the ref backends,\n> but are instead written by git-fetch(1), git-pull(1) and git-merge(1).\n> They contain additional metadata that hihlights where a ref has been\n\ns/hihlights/highlights\n\n> fetched from or the list of commits that have been merged.\n\nThis is good detail and I guess you can skip my earlier suggestion.\n\n> This original intent in fact matches the definition of special refs that\n> we have recently introduced in 8df4c5d205 (Documentation: add \"special\n> refs\" to the glossary, 2024-01-19). Due to the introduction of the new\n> reftable backend we were forced to distinguish those refs more clearly\n> such that we don't ever try to read or write them via the reftable\n> backend. In the same series, we also addressed all the other cases where\n> we used to write those special refs via the filesystem directly, thus\n> circumventing the ref backend, to instead write them via the backends.\n> Consequently, there are no other refs left anymore which are special.\n>\n> Let's address this mess and return the pseudoref terminology back to its\n> original intent: a ref that sometimes behave like a ref, but which isn't\n> really a ref because it gets written to the filesystem directly. Or in\n> other words, let's redefine pseudorefs to match the current definition\n> of special refs. As special refs and pseudorefs are now the same per\n> definition, we can drop the \"special refs\" term again. It's not exposed\n> to our users and thus they wouldn't ever encounter that term anyway.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  Documentation/glossary-content.txt | 42 +++++++++---------------------\n>  1 file changed, 13 insertions(+), 29 deletions(-)\n>\n> diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\n> index d71b199955..f5c0f49150 100644\n> --- a/Documentation/glossary-content.txt\n> +++ b/Documentation/glossary-content.txt\n> @@ -496,21 +496,19 @@ exclude;;\n>  \tthat start with `refs/bisect/`, but might later include other\n>  \tunusual refs.\n>\n> -[[def_pseudoref]]pseudoref::\n> -\tPseudorefs are a class of files under `$GIT_DIR` which behave\n> -\tlike refs for the purposes of rev-parse, but which are treated\n> -\tspecially by git.  Pseudorefs both have names that are all-caps,\n> -\tand always start with a line consisting of a\n> -\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n> -\tpseudoref, because it is sometimes a symbolic ref.  They might\n> -\toptionally contain some additional data.  `MERGE_HEAD` and\n> -\t`CHERRY_PICK_HEAD` are examples.  Unlike\n> -\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n> -\tbe symbolic refs, and never have reflogs.  They also cannot be\n> -\tupdated through the normal ref update machinery.  Instead,\n> -\tthey are updated by directly writing to the files.  However,\n> -\tthey can be read as if they were refs, so `git rev-parse\n> -\tMERGE_HEAD` will work.\n> +[[def_pseudoref]]pseudoref ref::\n\nshouldn't this just be 'pseudoref'?\n\n> +\tA ref that has different semantics than normal refs. These refs can be\n> +\taccessed via normal Git commands but may not behave the same as a\n> +\tnormal ref in some cases.\n> ++\n> +The following pseudorefs are known to Git:\n> +\n> + - \"`FETCH_HEAD`\" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n> +   may refer to multiple object IDs. Each object ID is annotated with metadata\n> +   indicating where it was fetched from and its fetch status.\n> +\n> + - \"`MERGE_HEAD`\" is written by linkgit:git-merge[1] when resolving merge\n> +   conflicts. It contains all commit IDs which are being merged.\n>\n>  [[def_pull]]pull::\n>  \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n> @@ -638,20 +636,6 @@ The most notable example is `HEAD`.\n>  \tAn <<def_object,object>> used to temporarily store the contents of a\n>  \t<<def_dirty,dirty>> working directory and the index for future reuse.\n>\n> -[[def_special_ref]]special ref::\n> -\tA ref that has different semantics than normal refs. These refs can be\n> -\taccessed via normal Git commands but may not behave the same as a\n> -\tnormal ref in some cases.\n> -+\n> -The following special refs are known to Git:\n> -\n> - - \"`FETCH_HEAD`\" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n> -   may refer to multiple object IDs. Each object ID is annotated with metadata\n> -   indicating where it was fetched from and its fetch status.\n> -\n> - - \"`MERGE_HEAD`\" is written by linkgit:git-merge[1] when resolving merge\n> -   conflicts. It contains all commit IDs which are being merged.\n> -\n>  [[def_submodule]]submodule::\n>  \tA <<def_repository,repository>> that holds the history of a\n>  \tseparate project inside another repository (the latter of\n> --\n> 2.45.0\n"},{"id":"493773","messageId":"CAOLa=ZT4rFEc0qWOA0iCTgXRC6Bcbzj0v-_ntoKZs3eAdt7vCQ@mail.gmail.com","threadId":"61387","inReplyTo":"9659d7da3f38e706af22b4cef0a3e77f780493fd.1714479928.git.ps@pks.im","subject":"Re: [PATCH v2 03/10] Documentation/glossary: define root refs as refs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-30T12:56:17Z","receivedAt":"2024-04-30T12:56:19Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Except for the pseudorefs MERGE_HEAD and FETCH_HEAD, all refs that live\n> in the root of the ref hierarchy behave the exact same as normal refs.\n> They can be symbolic refs or direct refs and can be read, iterated over\n> and written via normal tooling. All of these refs are stored in the ref\n> backends, which further demonstrates that they are just normal refs.\n>\n> Extend the definition of \"ref\" to also cover such root refs. The only\n> additional restriction for root refs is that they must conform to a\n> specific naming schema.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  Documentation/glossary-content.txt | 33 +++++++++++++++++++++++-------\n>  1 file changed, 26 insertions(+), 7 deletions(-)\n>\n> diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\n> index 13e1aa63ab..683b727349 100644\n> --- a/Documentation/glossary-content.txt\n> +++ b/Documentation/glossary-content.txt\n> @@ -550,20 +550,39 @@ The following pseudorefs are known to Git:\n>  \tto the result.\n>\n>  [[def_ref]]ref::\n> -\tA name that begins with `refs/` (e.g. `refs/heads/master`)\n> -\tthat points to an <<def_object_name,object name>> or another\n> -\tref (the latter is called a <<def_symref,symbolic ref>>).\n> +\tA name that that points to an <<def_object_name,object name>> or\n> +\tanother ref (the latter is called a <<def_symref,symbolic ref>>).\n>  \tFor convenience, a ref can sometimes be abbreviated when used\n>  \tas an argument to a Git command; see linkgit:gitrevisions[7]\n>  \tfor details.\n>  \tRefs are stored in the <<def_repository,repository>>.\n>  +\n>  The ref namespace is hierarchical.\n> -Different subhierarchies are used for different purposes (e.g. the\n> -`refs/heads/` hierarchy is used to represent local branches).\n> +Ref names must either start with `refs/` or be located in the root of\n> +the hierarchy. In that case, their name must conform to the following\n> +rules:\n>  +\n\nThe last sentence here doesn't clarify what it is referring to, perhaps\nsomething like 'For the latter, their name must follow these rules:'\ninstead?\n\n> -There are a few special-purpose refs that do not begin with `refs/`.\n> -The most notable example is `HEAD`.\n> + - The name consists of only upper-case characters or underscores.\n> +\n> + - The name ends with \"`_HEAD`\" or is equal to \"`HEAD`\".\n> ++\n> +There are some irregular refs in the root of the hierarchy that do not\n> +match these rules. The following list is exhaustive and shall not be\n> +extended in the future:\n> ++\n> + - AUTO_MERGE\n> +\n> + - BISECT_EXPECTED_REV\n> +\n> + - NOTES_MERGE_PARTIAL\n> +\n> + - NOTES_MERGE_REF\n> +\n> + - MERGE_AUTOSTASH\n> ++\n> +Different subhierarchies are used for different purposes. For example,\n> +the `refs/heads/` hierarchy is used to represent local branches whereas\n> +the `refs/tags/` hierarchy is used to represent local tags..\n>\n>  [[def_reflog]]reflog::\n>  \tA reflog shows the local \"history\" of a ref.  In other words,\n> --\n> 2.45.0\n"},{"id":"493774","messageId":"CAOLa=ZTgsH-dvhmmohByaujBoD78kUQDi95cE0Bs9a7nziZKrw@mail.gmail.com","threadId":"61387","inReplyTo":"e6b6db972d8009fa2a4e17f9b46e69d94391feb5.1714479928.git.ps@pks.im","subject":"Re: [PATCH v2 05/10] refs: refname `is_special_ref()` to `is_pseudo_ref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-30T12:58:28Z","receivedAt":"2024-04-30T12:58:29Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Rename `is_special_ref()` to `is_pseudo_ref()` to adapt to the newly\n> defined terminology in our gitglossary(7). Note that in the preceding\n> commit we have just renamed `is_pseudoref()` to `is_root_ref()`, where\n> there may be confusion for in-flight patch series that add new calls to\n> `is_pseudoref()`. In order to intentionall break such patch series we\n\ns/intentionall/intentionally\n\n\n[snip]\n"},{"id":"493775","messageId":"CAOLa=ZQY5rdUqcy0yTB0haduf9EK7KVLeoAYLyaST4fMn6UyFw@mail.gmail.com","threadId":"61387","inReplyTo":"95d7547b2e8c5305e76888f7dc0a41d2b9e2f558.1714479928.git.ps@pks.im","subject":"Re: [PATCH v2 09/10] ref-filter: properly distinuish pseudo and root refs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-30T13:11:05Z","receivedAt":"2024-04-30T13:11:06Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"In the subject: s/distinuish/distinguish\n\nPatrick Steinhardt <ps@pks.im> writes:\n\n> The ref-filter interfaces currently define root refs as either a\n> detached HEAD or a pseudo ref. Pseudo refs aren't root refs though, so\n> let's properly distinguish those ref types.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  builtin/for-each-ref.c |  2 +-\n>  ref-filter.c           | 16 +++++++++-------\n>  ref-filter.h           |  4 ++--\n>  refs.c                 | 18 +-----------------\n>  refs.h                 | 18 ++++++++++++++++++\n>  5 files changed, 31 insertions(+), 27 deletions(-)\n>\n> diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\n> index 919282e12a..5517a4a1c0 100644\n> --- a/builtin/for-each-ref.c\n> +++ b/builtin/for-each-ref.c\n> @@ -98,7 +98,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n>  \t}\n>\n>  \tif (include_root_refs)\n> -\t\tflags |= FILTER_REFS_ROOT_REFS;\n> +\t\tflags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD;\n\nThe only issue I see with this patch is that it makes me think that HEAD\nis not a root ref anymore. I get that this is the best way to define the\ndirectives because otherwise you'd need a new flag something like\n`FILTER_REFS_ROOT_REFS_WITHOUT_HEAD` and `FILTER_REFS_ROOT_REFS` would\nbe the summation of that and the HEAD flag.\n\nApart from this, the patch looks good.\n"},{"id":"493776","messageId":"777fa7d3-17f8-4883-9f9a-c4077866d394@app.fastmail.com","threadId":"61387","inReplyTo":"1f2f8cf3f2cfba65787551ce6571376275ef3a8d.1714479928.git.ps@pks.im","subject":"Re: [PATCH v2 02/10] Documentation/glossary: clarify limitations of pseudorefs","fromName":"Kristoffer Haugsbakk","fromEmail":"code@khaugsbakk.name","sentAt":"2024-04-30T13:35:13Z","receivedAt":"2024-04-30T13:36:03Z","isPatch":true,"sender":{"key":"code@khaugsbakk.name","avatar":"https://avatars.githubusercontent.com/u/2229597?v=4"},"body":"On Tue, Apr 30, 2024, at 14:26, Patrick Steinhardt wrote:\n>   - They are not surfaced when iterating through refs, like when using\n>     git-for-each-ref(1). They are no ref, so iterating through refs\n>     should not surface them.\n\ns/They are no ref/They are not refs\n\n-- \nKristoffer Haugsbakk\n\n"},{"id":"493799","messageId":"zmnute2cow2rbrv3cj5cq4roieyzssaxcnorxhorzyp3wfgllt@ubhsw6dpi5js","threadId":"61387","inReplyTo":"e90b2f8aa98493e9cd3f2c04cb58318780f9f6e5.1714479928.git.ps@pks.im","subject":"Re: [PATCH v2 07/10] refs: root refs can be symbolic refs","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2024-04-30T17:09:57Z","receivedAt":"2024-04-30T17:11:13Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 24/04/30 02:26PM, Patrick Steinhardt wrote:\n> Before this patch series, root refs except for \"HEAD\" and our special\n> refs were classified as pseudorefs. Furthermore, our terminology\n> clarified that pseudorefs must not be symbolic refs. This restriction\n> is enforced in `is_root_ref()`, which explicitly checks that a supposed\n> root ref resolves to an object ID without recursing.\n> \n> This has been extremely confusing right from the start because (in old\n> terminology) a ref name may sometimes be a pseudoref and sometimes not\n> depending on whether it is a symbolic or regular ref. This behaviour\n> does not seem reasonable at all and I very much doubt that it results in\n> anything sane.\n> \n> Furthermore, the behaviour is different to `is_headref()`, which only\n> checks for the ref to exist. While that is in line with our glossary,\n> this inconsistency only adds to the confusion.\n> \n> Last but not least, the current behaviour can actually lead to a\n> segfault when calling `is_root_ref()` with a reference that either does\n> not exist or that is a symbolic ref because we never intialized `oid`.\n\ns/intialized/initialized/\n\n> Let's loosen the restrictions in accordance to the new definition of\n> root refs, which are simply plain refs that may as well be a symbolic\n> ref. Consequently, we can just check for the ref to exist instead of\n> requiring it to be a regular ref.\n> \n> Add a test that verifies that this does not change user-visible\n> behaviour. Namely, we still don't want to show broken refs to the user\n> by default in git-for-each-ref(1). What this does allow though is for\n> internal callers to surface dangling root refs when they pass in the\n> `DO_FOR_EACH_INCLUDE_BROKEN` flag.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  refs.c                         | 50 ++++++++++++++++++++++++----------\n>  t/t6302-for-each-ref-filter.sh | 17 ++++++++++++\n>  2 files changed, 53 insertions(+), 14 deletions(-)\n> \n> diff --git a/refs.c b/refs.c\n> index 5b89e83ad7..ca9844bc3e 100644\n> --- a/refs.c\n> +++ b/refs.c\n...  \n>  int is_headref(struct ref_store *refs, const char *refname)\n>  {\n> -\tif (!strcmp(refname, \"HEAD\"))\n> -\t\treturn refs_ref_exists(refs, refname);\n> +\tstruct strbuf referent = STRBUF_INIT;\n> +\tstruct object_id oid = { 0 };\n> +\tint failure_errno, ret = 0;\n> +\tunsigned int flags;\n>  \n> -\treturn 0;\n> +\t/*\n> +\t * Note that we cannot use `refs_ref_exists()` here because that also\n> +\t * checks whether its target ref exists in case refname is a symbolic\n> +\t * ref.\n> +\t */\n> +\tif (!strcmp(refname, \"HEAD\")) {\n> +\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n> +\t\t\t\t\t &flags, &failure_errno);\n> +\t}\n> +\n> +\tstrbuf_release(&referent);\n> +\treturn ret;\n>  }\n\nI'm not quite sure I understand why we are changing the behavior of\n`is_headref()` here. Do we no longer want to validate the ref exists if\nit is symbolic?\n\nIn a prior commit, `is_headref()` is commented to mention that we check\nwhether the reference exists. Maybe that could use some additional\nclarification?\n\n-Justin\n"},{"id":"493802","messageId":"m5vhgv53zuwi3wp3g4nvh4biq567piqvyyapbzc6bw5kmnqedb@f5z77afgnubi","threadId":"61387","inReplyTo":"2489bb558543d66592fb0f3eb4d4696ba8e31fea.1714479928.git.ps@pks.im","subject":"Re: [PATCH v2 01/10] Documentation/glossary: redefine pseudorefs as special refs","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2024-04-30T17:17:21Z","receivedAt":"2024-04-30T17:18:37Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 24/04/30 02:26PM, Patrick Steinhardt wrote:\n> Nowadays, Git knows about three different kinds of refs. As defined in\n> gitglossary(7):\n> \n>   - Regular refs that start with \"refs/\", like \"refs/heads/main\".\n> \n>   - Pseudorefs, which live in the root directory. These must have\n>     all-caps names and must be a file that start with an object hash.\n>     Consequently, symbolic refs are not pseudorefs because they do not\n>     start with an object hash.\n> \n>   - Special refs, of which we only have \"FETCH_HEAD\" and \"MERGE_HEAD\".\n> \n> This state is extremely confusing, and I would claim that most folks\n> don't fully understand what is what here. The current definitions also\n> have several problems:\n> \n>   - Where does \"HEAD\" fit in? It's not a pseudoref because it can be\n>     a symbolic ref. It's not a regular ref because it does not start\n>     with \"refs/\". And it's not a special ref, either.\n> \n>   - There is a strong overlap between pseudorefs and special refs. The\n>     pseudoref section for example mentions \"MERGE_HEAD\", even though it\n>     is a special ref. Is it thus both a pseudoref and a special ref?\n> \n>   - Why do we even need to distinguish refs that live in the root from\n>     other refs when they behave just like a regular ref anyway?\n> \n> In other words, the current state is quite a mess and leads to wild\n> inconsistencies without much of a good reason.\n> \n> The original reason why pseudorefs were introduced is that there are\n> some refs that sometimes behave like a ref, even though they aren't a\n> ref. And we really only have two of these nowadads, namely \"MERGE_HEAD\"\n\ns/nowadads/nowadays/\n\n-Justin\n"},{"id":"493817","messageId":"xmqqttjiy5hq.fsf@gitster.g","threadId":"61387","inReplyTo":"2489bb558543d66592fb0f3eb4d4696ba8e31fea.1714479928.git.ps@pks.im","subject":"Re: [PATCH v2 01/10] Documentation/glossary: redefine pseudorefs as special refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-30T20:12:33Z","receivedAt":"2024-04-30T20:12:36Z","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> Let's address this mess and return the pseudoref terminology back to its\n> original intent: a ref that sometimes behave like a ref, but which isn't\n> really a ref because it gets written to the filesystem directly. Or in\n> other words, let's redefine pseudorefs to match the current definition\n> of special refs. As special refs and pseudorefs are now the same per\n> definition, we can drop the \"special refs\" term again. It's not exposed\n> to our users and thus they wouldn't ever encounter that term anyway.\n\nGood intentions.\n\nI do not agree with \"the ones at the root should not be special\" at\nall, though.  We need to reject names like 'config' somehow, as long\nas there are users who use files backend.\n"},{"id":"493818","messageId":"xmqqplu6y552.fsf@gitster.g","threadId":"61387","inReplyTo":"3d7ea704177c84e5afeceea5848f5606f11e99f4.1714479928.git.ps@pks.im","subject":"Re: [PATCH v2 04/10] refs: rename `is_pseudoref()` to `is_root_ref()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-30T20:20:09Z","receivedAt":"2024-04-30T20:20:17Z","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> Rename `is_pseudoref()` to `is_root_ref()` to adapt to the newly defined\n> terminology in our gitglossary(7).\n\nOK.  root-ref is a good name that is not yet tainted.\n"},{"id":"493895","messageId":"ZjNJ1w-hPyAxh2i9@tanuki","threadId":"61387","inReplyTo":"xmqqttjiy5hq.fsf@gitster.g","subject":"Re: [PATCH v2 01/10] Documentation/glossary: redefine pseudorefs as special refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:07:51Z","receivedAt":"2024-05-02T08:07:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 30, 2024 at 01:12:33PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Let's address this mess and return the pseudoref terminology back to its\n> > original intent: a ref that sometimes behave like a ref, but which isn't\n> > really a ref because it gets written to the filesystem directly. Or in\n> > other words, let's redefine pseudorefs to match the current definition\n> > of special refs. As special refs and pseudorefs are now the same per\n> > definition, we can drop the \"special refs\" term again. It's not exposed\n> > to our users and thus they wouldn't ever encounter that term anyway.\n> \n> Good intentions.\n> \n> I do not agree with \"the ones at the root should not be special\" at\n> all, though.  We need to reject names like 'config' somehow, as long\n> as there are users who use files backend.\n\nOh, yes, I totally agree and thought I'd mentioned this in the message.\nBut it seems like I only mention this in a subsequent message. Let me\nadd a hint to the commit message that mentions that a subsequent commit\nwill clearly define \"root refs\".\n\nIn any case, root refs should not be special regarding their behaviour,\nbut should have a strict naming schema:\n\n    - Only uppercase letters or underscores.\n\n    - Must end with \"_HEAD\" or be called \"HEAD\".\n\n    - There is an exhaustive list of legacy root refs that don't conform\n      to this naming schema, like \"AUTO_MERGE\". This list shall not be\n      extended in the future.\n\nThis explanation is added in patch 3.\n\nPatrick\n"},{"id":"493896","messageId":"ZjNJ3j2wSgui_cG8@tanuki","threadId":"61387","inReplyTo":"zmnute2cow2rbrv3cj5cq4roieyzssaxcnorxhorzyp3wfgllt@ubhsw6dpi5js","subject":"Re: [PATCH v2 07/10] refs: root refs can be symbolic refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:07:58Z","receivedAt":"2024-05-02T08:08:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 30, 2024 at 12:09:57PM -0500, Justin Tobler wrote:\n> On 24/04/30 02:26PM, Patrick Steinhardt wrote:\n[snip]\n> > diff --git a/refs.c b/refs.c\n> > index 5b89e83ad7..ca9844bc3e 100644\n> > --- a/refs.c\n> > +++ b/refs.c\n> ...  \n> >  int is_headref(struct ref_store *refs, const char *refname)\n> >  {\n> > -\tif (!strcmp(refname, \"HEAD\"))\n> > -\t\treturn refs_ref_exists(refs, refname);\n> > +\tstruct strbuf referent = STRBUF_INIT;\n> > +\tstruct object_id oid = { 0 };\n> > +\tint failure_errno, ret = 0;\n> > +\tunsigned int flags;\n> >  \n> > -\treturn 0;\n> > +\t/*\n> > +\t * Note that we cannot use `refs_ref_exists()` here because that also\n> > +\t * checks whether its target ref exists in case refname is a symbolic\n> > +\t * ref.\n> > +\t */\n> > +\tif (!strcmp(refname, \"HEAD\")) {\n> > +\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n> > +\t\t\t\t\t &flags, &failure_errno);\n> > +\t}\n> > +\n> > +\tstrbuf_release(&referent);\n> > +\treturn ret;\n> >  }\n> \n> I'm not quite sure I understand why we are changing the behavior of\n> `is_headref()` here. Do we no longer want to validate the ref exists if\n> it is symbolic?\n\nThe implementation does not conform to the definition of a \"HEAD\" ref.\nEven before this patch series, a \"HEAD\" ref could either be a symbolic\nor a regular ref. So to answer the question of \"Is this a HEAD ref?\" you\nonly need to check whether the ref exists, not whether its target\nexists.\n\n> In a prior commit, `is_headref()` is commented to mention that we check\n> whether the reference exists. Maybe that could use some additional\n> clarification?\n\nWhich particular commit do you refer to? It's not part of this series,\nis it?\n\nPatrick\n"},{"id":"493897","messageId":"ZjNJ5EPMYxLi1QR0@tanuki","threadId":"61387","inReplyTo":"CAOLa=ZQY5rdUqcy0yTB0haduf9EK7KVLeoAYLyaST4fMn6UyFw@mail.gmail.com","subject":"Re: [PATCH v2 09/10] ref-filter: properly distinuish pseudo and root refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:08:04Z","receivedAt":"2024-05-02T08:08:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 30, 2024 at 06:11:05AM -0700, Karthik Nayak wrote:\n> In the subject: s/distinuish/distinguish\n> \n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The ref-filter interfaces currently define root refs as either a\n> > detached HEAD or a pseudo ref. Pseudo refs aren't root refs though, so\n> > let's properly distinguish those ref types.\n> >\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> > ---\n> >  builtin/for-each-ref.c |  2 +-\n> >  ref-filter.c           | 16 +++++++++-------\n> >  ref-filter.h           |  4 ++--\n> >  refs.c                 | 18 +-----------------\n> >  refs.h                 | 18 ++++++++++++++++++\n> >  5 files changed, 31 insertions(+), 27 deletions(-)\n> >\n> > diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\n> > index 919282e12a..5517a4a1c0 100644\n> > --- a/builtin/for-each-ref.c\n> > +++ b/builtin/for-each-ref.c\n> > @@ -98,7 +98,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n> >  \t}\n> >\n> >  \tif (include_root_refs)\n> > -\t\tflags |= FILTER_REFS_ROOT_REFS;\n> > +\t\tflags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD;\n> \n> The only issue I see with this patch is that it makes me think that HEAD\n> is not a root ref anymore. I get that this is the best way to define the\n> directives because otherwise you'd need a new flag something like\n> `FILTER_REFS_ROOT_REFS_WITHOUT_HEAD` and `FILTER_REFS_ROOT_REFS` would\n> be the summation of that and the HEAD flag.\n> \n> Apart from this, the patch looks good.\n\nWell, it is a root ref, but we treat it differently in the ref-filter\ninterface because it's rendered differently than any other root ref.\nFurthermore, the ref-filter interfaces allow you to _only_ list the HEAD\nref, which is another reason why it's singled out.\n\nRenaming this to FILTER_REFS_ROOT_REFS_WITHOUT_HEAD ould be quite\nmisleading, too, because we have the following snippet:\n\n@@ -2794,11 +2796,11 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const struct\n        /*\n         * Generally HEAD refs are printed with special description denoting a rebase,\n         * detached state and so forth. This is useful when only printing the HEAD ref\n         * But when it is being printed along with other root refs, it makes sense to\n         * keep the formatting consistent. So we mask the type to act like a root ref.\n         */\n        if (filter->kind & FILTER_REFS_ROOT_REFS && kind == FILTER_REFS_DETACHED_HEAD)\n                kind = FILTER_REFS_ROOT_REFS;\n        else if (!(kind & filter->kind))\n                return NULL;\n\nIf we named this FILTER_REFS_ROOT_REFS_WITHOUT_HEAD then the above code\nwould be even more surprising.\n\nSo yeah, it's a bit weird, but I think it's more sensible to retain the\ncode as proposed.\n\nPatrick\n"},{"id":"493898","messageId":"cover.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714398019.git.ps@pks.im","subject":"[PATCH v3 00/10] Clarify pseudo-ref terminology","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:08Z","receivedAt":"2024-05-02T08:17:14Z","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 aims to clarify the\npseudo-ref terminology.\n\nChanges compared to v2:\n\n    - Various typo fixes.\n\n    - Added a note in the first commit that we're about to clearly\n      define rules around \"root refs\" in a subsequent commit. While this\n      patch series will make root refs act like \"just a normal ref\", we\n      will still have strict limits around the naming policy for them.\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (10):\n  Documentation/glossary: redefine pseudorefs as special refs\n  Documentation/glossary: clarify limitations of pseudorefs\n  Documentation/glossary: define root refs as refs\n  refs: rename `is_pseudoref()` to `is_root_ref()`\n  refs: refname `is_special_ref()` to `is_pseudo_ref()`\n  refs: classify HEAD as a root ref\n  refs: root refs can be symbolic refs\n  refs: pseudorefs are no refs\n  ref-filter: properly distinuish pseudo and root refs\n  refs: refuse to write pseudorefs\n\n Documentation/glossary-content.txt |  72 ++++++++---------\n builtin/for-each-ref.c             |   2 +-\n ref-filter.c                       |  16 ++--\n ref-filter.h                       |   4 +-\n refs.c                             | 120 ++++++++++++++++-------------\n refs.h                             |  50 +++++++++++-\n refs/files-backend.c               |   3 +-\n refs/reftable-backend.c            |   3 +-\n t/t5510-fetch.sh                   |   6 +-\n t/t6302-for-each-ref-filter.sh     |  34 ++++++++\n 10 files changed, 205 insertions(+), 105 deletions(-)\n\nRange-diff against v2:\n 1:  2489bb5585 !  1:  e651bae690 Documentation/glossary: redefine pseudorefs as special refs\n    @@ Commit message\n     \n         The original reason why pseudorefs were introduced is that there are\n         some refs that sometimes behave like a ref, even though they aren't a\n    -    ref. And we really only have two of these nowadads, namely \"MERGE_HEAD\"\n    +    ref. And we really only have two of these nowadays, namely \"MERGE_HEAD\"\n         and \"FETCH_HEAD\". Those files are never written via the ref backends,\n         but are instead written by git-fetch(1), git-pull(1) and git-merge(1).\n    -    They contain additional metadata that hihlights where a ref has been\n    +    They contain additional metadata that highlights where a ref has been\n         fetched from or the list of commits that have been merged.\n     \n         This original intent in fact matches the definition of special refs that\n    @@ Commit message\n         definition, we can drop the \"special refs\" term again. It's not exposed\n         to our users and thus they wouldn't ever encounter that term anyway.\n     \n    +    Refs that live in the root of the ref hierarchy but which are not\n    +    pseudorefs will be further defined in a subsequent commit.\n    +\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## Documentation/glossary-content.txt ##\n     @@ Documentation/glossary-content.txt: exclude;;\n    - \tthat start with `refs/bisect/`, but might later include other\n      \tunusual refs.\n      \n    --[[def_pseudoref]]pseudoref::\n    + [[def_pseudoref]]pseudoref::\n     -\tPseudorefs are a class of files under `$GIT_DIR` which behave\n     -\tlike refs for the purposes of rev-parse, but which are treated\n     -\tspecially by git.  Pseudorefs both have names that are all-caps,\n    @@ Documentation/glossary-content.txt: exclude;;\n     -\tthey are updated by directly writing to the files.  However,\n     -\tthey can be read as if they were refs, so `git rev-parse\n     -\tMERGE_HEAD` will work.\n    -+[[def_pseudoref]]pseudoref ref::\n     +\tA ref that has different semantics than normal refs. These refs can be\n     +\taccessed via normal Git commands but may not behave the same as a\n     +\tnormal ref in some cases.\n 2:  1f2f8cf3f2 !  2:  66ac046132 Documentation/glossary: clarify limitations of pseudorefs\n    @@ Commit message\n           - They can be read via git-rev-parse(1) and similar tools.\n     \n           - They are not surfaced when iterating through refs, like when using\n    -        git-for-each-ref(1). They are no ref, so iterating through refs\n    +        git-for-each-ref(1). They are not refs, so iterating through refs\n             should not surface them.\n     \n           - They cannot be written via git-update-ref(1) and related commands.\n    @@ Commit message\n      ## Documentation/glossary-content.txt ##\n     @@ Documentation/glossary-content.txt: exclude;;\n      \n    - [[def_pseudoref]]pseudoref ref::\n    + [[def_pseudoref]]pseudoref::\n      \tA ref that has different semantics than normal refs. These refs can be\n     -\taccessed via normal Git commands but may not behave the same as a\n     -\tnormal ref in some cases.\n 3:  9659d7da3f !  3:  243d616101 Documentation/glossary: define root refs as refs\n    @@ Documentation/glossary-content.txt: The following pseudorefs are known to Git:\n     -Different subhierarchies are used for different purposes (e.g. the\n     -`refs/heads/` hierarchy is used to represent local branches).\n     +Ref names must either start with `refs/` or be located in the root of\n    -+the hierarchy. In that case, their name must conform to the following\n    -+rules:\n    ++the hierarchy. For the latter, their name must follow these rules:\n      +\n     -There are a few special-purpose refs that do not begin with `refs/`.\n     -The most notable example is `HEAD`.\n 4:  3d7ea70417 =  4:  0a116f9d11 refs: rename `is_pseudoref()` to `is_root_ref()`\n 5:  e6b6db972d !  5:  484a0856bc refs: refname `is_special_ref()` to `is_pseudo_ref()`\n    @@ Commit message\n         defined terminology in our gitglossary(7). Note that in the preceding\n         commit we have just renamed `is_pseudoref()` to `is_root_ref()`, where\n         there may be confusion for in-flight patch series that add new calls to\n    -    `is_pseudoref()`. In order to intentionall break such patch series we\n    +    `is_pseudoref()`. In order to intentionally break such patch series we\n         have thus picked `is_pseudo_ref()` instead of `is_pseudoref()` as the\n         new name.\n     \n 6:  44f72a7baf =  6:  c196fe3c45 refs: classify HEAD as a root ref\n 7:  e90b2f8aa9 !  7:  92a71222e1 refs: root refs can be symbolic refs\n    @@ Commit message\n     \n         Last but not least, the current behaviour can actually lead to a\n         segfault when calling `is_root_ref()` with a reference that either does\n    -    not exist or that is a symbolic ref because we never intialized `oid`.\n    +    not exist or that is a symbolic ref because we never initialized `oid`.\n     \n         Let's loosen the restrictions in accordance to the new definition of\n         root refs, which are simply plain refs that may as well be a symbolic\n 8:  bc82d7ae65 =  8:  8bd52e5363 refs: pseudorefs are no refs\n 9:  95d7547b2e =  9:  cd6d745a01 ref-filter: properly distinuish pseudo and root refs\n10:  b2029612dd = 10:  6956fccced refs: refuse to write pseudorefs\n-- \n2.45.0\n\n"},{"id":"493899","messageId":"e651bae690e5390ae2f4e906d8edcc34b4f25003.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 01/10] Documentation/glossary: redefine pseudorefs as special refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:13Z","receivedAt":"2024-05-02T08:17:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Nowadays, Git knows about three different kinds of refs. As defined in\ngitglossary(7):\n\n  - Regular refs that start with \"refs/\", like \"refs/heads/main\".\n\n  - Pseudorefs, which live in the root directory. These must have\n    all-caps names and must be a file that start with an object hash.\n    Consequently, symbolic refs are not pseudorefs because they do not\n    start with an object hash.\n\n  - Special refs, of which we only have \"FETCH_HEAD\" and \"MERGE_HEAD\".\n\nThis state is extremely confusing, and I would claim that most folks\ndon't fully understand what is what here. The current definitions also\nhave several problems:\n\n  - Where does \"HEAD\" fit in? It's not a pseudoref because it can be\n    a symbolic ref. It's not a regular ref because it does not start\n    with \"refs/\". And it's not a special ref, either.\n\n  - There is a strong overlap between pseudorefs and special refs. The\n    pseudoref section for example mentions \"MERGE_HEAD\", even though it\n    is a special ref. Is it thus both a pseudoref and a special ref?\n\n  - Why do we even need to distinguish refs that live in the root from\n    other refs when they behave just like a regular ref anyway?\n\nIn other words, the current state is quite a mess and leads to wild\ninconsistencies without much of a good reason.\n\nThe original reason why pseudorefs were introduced is that there are\nsome refs that sometimes behave like a ref, even though they aren't a\nref. And we really only have two of these nowadays, namely \"MERGE_HEAD\"\nand \"FETCH_HEAD\". Those files are never written via the ref backends,\nbut are instead written by git-fetch(1), git-pull(1) and git-merge(1).\nThey contain additional metadata that highlights where a ref has been\nfetched from or the list of commits that have been merged.\n\nThis original intent in fact matches the definition of special refs that\nwe have recently introduced in 8df4c5d205 (Documentation: add \"special\nrefs\" to the glossary, 2024-01-19). Due to the introduction of the new\nreftable backend we were forced to distinguish those refs more clearly\nsuch that we don't ever try to read or write them via the reftable\nbackend. In the same series, we also addressed all the other cases where\nwe used to write those special refs via the filesystem directly, thus\ncircumventing the ref backend, to instead write them via the backends.\nConsequently, there are no other refs left anymore which are special.\n\nLet's address this mess and return the pseudoref terminology back to its\noriginal intent: a ref that sometimes behave like a ref, but which isn't\nreally a ref because it gets written to the filesystem directly. Or in\nother words, let's redefine pseudorefs to match the current definition\nof special refs. As special refs and pseudorefs are now the same per\ndefinition, we can drop the \"special refs\" term again. It's not exposed\nto our users and thus they wouldn't ever encounter that term anyway.\n\nRefs that live in the root of the ref hierarchy but which are not\npseudorefs will be further defined in a subsequent commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 40 +++++++++---------------------\n 1 file changed, 12 insertions(+), 28 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex d71b199955..ca04768e3b 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -497,20 +497,18 @@ exclude;;\n \tunusual refs.\n \n [[def_pseudoref]]pseudoref::\n-\tPseudorefs are a class of files under `$GIT_DIR` which behave\n-\tlike refs for the purposes of rev-parse, but which are treated\n-\tspecially by git.  Pseudorefs both have names that are all-caps,\n-\tand always start with a line consisting of a\n-\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n-\tpseudoref, because it is sometimes a symbolic ref.  They might\n-\toptionally contain some additional data.  `MERGE_HEAD` and\n-\t`CHERRY_PICK_HEAD` are examples.  Unlike\n-\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n-\tbe symbolic refs, and never have reflogs.  They also cannot be\n-\tupdated through the normal ref update machinery.  Instead,\n-\tthey are updated by directly writing to the files.  However,\n-\tthey can be read as if they were refs, so `git rev-parse\n-\tMERGE_HEAD` will work.\n+\tA ref that has different semantics than normal refs. These refs can be\n+\taccessed via normal Git commands but may not behave the same as a\n+\tnormal ref in some cases.\n++\n+The following pseudorefs are known to Git:\n+\n+ - \"`FETCH_HEAD`\" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n+   may refer to multiple object IDs. Each object ID is annotated with metadata\n+   indicating where it was fetched from and its fetch status.\n+\n+ - \"`MERGE_HEAD`\" is written by linkgit:git-merge[1] when resolving merge\n+   conflicts. It contains all commit IDs which are being merged.\n \n [[def_pull]]pull::\n \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n@@ -638,20 +636,6 @@ The most notable example is `HEAD`.\n \tAn <<def_object,object>> used to temporarily store the contents of a\n \t<<def_dirty,dirty>> working directory and the index for future reuse.\n \n-[[def_special_ref]]special ref::\n-\tA ref that has different semantics than normal refs. These refs can be\n-\taccessed via normal Git commands but may not behave the same as a\n-\tnormal ref in some cases.\n-+\n-The following special refs are known to Git:\n-\n- - \"`FETCH_HEAD`\" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n-   may refer to multiple object IDs. Each object ID is annotated with metadata\n-   indicating where it was fetched from and its fetch status.\n-\n- - \"`MERGE_HEAD`\" is written by linkgit:git-merge[1] when resolving merge\n-   conflicts. It contains all commit IDs which are being merged.\n-\n [[def_submodule]]submodule::\n \tA <<def_repository,repository>> that holds the history of a\n \tseparate project inside another repository (the latter of\n-- \n2.45.0\n\n"},{"id":"493900","messageId":"66ac0461328409fd880ab1dce92dae5c012ceea8.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 02/10] Documentation/glossary: clarify limitations of pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:18Z","receivedAt":"2024-05-02T08:17:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Clarify limitations that pseudorefs have:\n\n  - They can be read via git-rev-parse(1) and similar tools.\n\n  - They are not surfaced when iterating through refs, like when using\n    git-for-each-ref(1). They are not refs, so iterating through refs\n    should not surface them.\n\n  - They cannot be written via git-update-ref(1) and related commands.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex ca04768e3b..b464b926d5 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -498,8 +498,8 @@ exclude;;\n \n [[def_pseudoref]]pseudoref::\n \tA ref that has different semantics than normal refs. These refs can be\n-\taccessed via normal Git commands but may not behave the same as a\n-\tnormal ref in some cases.\n+\tread via normal Git commands, but cannot be written to by commands like\n+\tlinkgit:git-update-ref[1].\n +\n The following pseudorefs are known to Git:\n \n-- \n2.45.0\n\n"},{"id":"493901","messageId":"243d616101350c03b1b536f9e26d05ca749d08d5.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 03/10] Documentation/glossary: define root refs as refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:22Z","receivedAt":"2024-05-02T08:17:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Except for the pseudorefs MERGE_HEAD and FETCH_HEAD, all refs that live\nin the root of the ref hierarchy behave the exact same as normal refs.\nThey can be symbolic refs or direct refs and can be read, iterated over\nand written via normal tooling. All of these refs are stored in the ref\nbackends, which further demonstrates that they are just normal refs.\n\nExtend the definition of \"ref\" to also cover such root refs. The only\nadditional restriction for root refs is that they must conform to a\nspecific naming schema.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 32 +++++++++++++++++++++++-------\n 1 file changed, 25 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex b464b926d5..d6cf907a19 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -550,20 +550,38 @@ The following pseudorefs are known to Git:\n \tto the result.\n \n [[def_ref]]ref::\n-\tA name that begins with `refs/` (e.g. `refs/heads/master`)\n-\tthat points to an <<def_object_name,object name>> or another\n-\tref (the latter is called a <<def_symref,symbolic ref>>).\n+\tA name that that points to an <<def_object_name,object name>> or\n+\tanother ref (the latter is called a <<def_symref,symbolic ref>>).\n \tFor convenience, a ref can sometimes be abbreviated when used\n \tas an argument to a Git command; see linkgit:gitrevisions[7]\n \tfor details.\n \tRefs are stored in the <<def_repository,repository>>.\n +\n The ref namespace is hierarchical.\n-Different subhierarchies are used for different purposes (e.g. the\n-`refs/heads/` hierarchy is used to represent local branches).\n+Ref names must either start with `refs/` or be located in the root of\n+the hierarchy. For the latter, their name must follow these rules:\n +\n-There are a few special-purpose refs that do not begin with `refs/`.\n-The most notable example is `HEAD`.\n+ - The name consists of only upper-case characters or underscores.\n+\n+ - The name ends with \"`_HEAD`\" or is equal to \"`HEAD`\".\n++\n+There are some irregular refs in the root of the hierarchy that do not\n+match these rules. The following list is exhaustive and shall not be\n+extended in the future:\n++\n+ - AUTO_MERGE\n+\n+ - BISECT_EXPECTED_REV\n+\n+ - NOTES_MERGE_PARTIAL\n+\n+ - NOTES_MERGE_REF\n+\n+ - MERGE_AUTOSTASH\n++\n+Different subhierarchies are used for different purposes. For example,\n+the `refs/heads/` hierarchy is used to represent local branches whereas\n+the `refs/tags/` hierarchy is used to represent local tags..\n \n [[def_reflog]]reflog::\n \tA reflog shows the local \"history\" of a ref.  In other words,\n-- \n2.45.0\n\n"},{"id":"493902","messageId":"0a116f9d11413e0f16d60d963d5b41b8fc6b7420.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 04/10] refs: rename `is_pseudoref()` to `is_root_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:27Z","receivedAt":"2024-05-02T08:17:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename `is_pseudoref()` to `is_root_ref()` to adapt to the newly defined\nterminology in our gitglossary(7).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c            |  2 +-\n refs.c                  | 14 +++++++-------\n refs.h                  | 28 +++++++++++++++++++++++++++-\n refs/files-backend.c    |  2 +-\n refs/reftable-backend.c |  2 +-\n 5 files changed, 37 insertions(+), 11 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 59ad6f54dd..361beb6619 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2756,7 +2756,7 @@ static int ref_kind_from_refname(const char *refname)\n \t\t\treturn ref_kind[i].kind;\n \t}\n \n-\tif (is_pseudoref(get_main_ref_store(the_repository), refname))\n+\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n \t\treturn FILTER_REFS_PSEUDOREFS;\n \n \treturn FILTER_REFS_OTHERS;\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..0a4acde3ca 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,7 +844,7 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n-static int is_pseudoref_syntax(const char *refname)\n+static int is_root_ref_syntax(const char *refname)\n {\n \tconst char *c;\n \n@@ -860,9 +860,9 @@ static int is_pseudoref_syntax(const char *refname)\n \treturn 1;\n }\n \n-int is_pseudoref(struct ref_store *refs, const char *refname)\n+int is_root_ref(struct ref_store *refs, const char *refname)\n {\n-\tstatic const char *const irregular_pseudorefs[] = {\n+\tstatic const char *const irregular_root_refs[] = {\n \t\t\"AUTO_MERGE\",\n \t\t\"BISECT_EXPECTED_REV\",\n \t\t\"NOTES_MERGE_PARTIAL\",\n@@ -872,7 +872,7 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \tstruct object_id oid;\n \tsize_t i;\n \n-\tif (!is_pseudoref_syntax(refname))\n+\tif (!is_root_ref_syntax(refname))\n \t\treturn 0;\n \n \tif (ends_with(refname, \"_HEAD\")) {\n@@ -882,8 +882,8 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \t\treturn !is_null_oid(&oid);\n \t}\n \n-\tfor (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)\n-\t\tif (!strcmp(refname, irregular_pseudorefs[i])) {\n+\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n+\t\tif (!strcmp(refname, irregular_root_refs[i])) {\n \t\t\trefs_resolve_ref_unsafe(refs, refname,\n \t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n \t\t\t\t\t\t&oid, NULL);\n@@ -902,7 +902,7 @@ int is_headref(struct ref_store *refs, const char *refname)\n }\n \n static int is_current_worktree_ref(const char *ref) {\n-\treturn is_pseudoref_syntax(ref) || is_per_worktree_ref(ref);\n+\treturn is_root_ref_syntax(ref) || is_per_worktree_ref(ref);\n }\n \n enum ref_worktree_type parse_worktree_ref(const char *maybe_worktree_ref,\ndiff --git a/refs.h b/refs.h\nindex d278775e08..d0374c3275 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1051,7 +1051,33 @@ extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];\n  */\n void update_ref_namespace(enum ref_namespace namespace, char *ref);\n \n-int is_pseudoref(struct ref_store *refs, const char *refname);\n+/*\n+ * Check whether the reference is an existing root reference.\n+ *\n+ * A root ref is a reference that lives in the root of the reference hierarchy.\n+ * These references must conform to special syntax:\n+ *\n+ *   - Their name must be all-uppercase or underscores (\"_\").\n+ *\n+ *   - Their name must end with \"_HEAD\".\n+ *\n+ *   - Their name may not contain a slash.\n+ *\n+ * There is a special set of irregular root refs that exist due to historic\n+ * reasons, only. This list shall not be expanded in the future:\n+ *\n+ *   - AUTO_MERGE\n+ *\n+ *   - BISECT_EXPECTED_REV\n+ *\n+ *   - NOTES_MERGE_PARTIAL\n+ *\n+ *   - NOTES_MERGE_REF\n+ *\n+ *   - MERGE_AUTOSTASH\n+ */\n+int is_root_ref(struct ref_store *refs, const char *refname);\n+\n int is_headref(struct ref_store *refs, const char *refname);\n \n #endif /* REFS_H */\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..0fcb601444 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -351,7 +351,7 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n \t\tstrbuf_addstr(&refname, de->d_name);\n \n \t\tdtype = get_dtype(de, &path, 1);\n-\t\tif (dtype == DT_REG && (is_pseudoref(ref_store, de->d_name) ||\n+\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n \t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1cda48c504..5a5e64fe69 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -356,7 +356,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n-\t\t     (is_pseudoref(&iter->refs->base, iter->ref.refname) ||\n+\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n \t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n \t\t\tcontinue;\n \t\t}\n-- \n2.45.0\n\n"},{"id":"493903","messageId":"484a0856bc947c3b0b05bf1ad3a9379ba4feeff3.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 05/10] refs: refname `is_special_ref()` to `is_pseudo_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:32Z","receivedAt":"2024-05-02T08:17:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename `is_special_ref()` to `is_pseudo_ref()` to adapt to the newly\ndefined terminology in our gitglossary(7). Note that in the preceding\ncommit we have just renamed `is_pseudoref()` to `is_root_ref()`, where\nthere may be confusion for in-flight patch series that add new calls to\n`is_pseudoref()`. In order to intentionally break such patch series we\nhave thus picked `is_pseudo_ref()` instead of `is_pseudoref()` as the\nnew name.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 0a4acde3ca..6266f77474 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1876,13 +1876,13 @@ static int refs_read_special_head(struct ref_store *ref_store,\n \treturn result;\n }\n \n-static int is_special_ref(const char *refname)\n+static int is_pseudo_ref(const char *refname)\n {\n \t/*\n-\t * Special references are refs that have different semantics compared\n-\t * to \"normal\" refs. These refs can thus not be stored in the ref\n-\t * backend, but must always be accessed via the filesystem. The\n-\t * following refs are special:\n+\t * Pseudorefs are refs that have different semantics compared to\n+\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+\t * but must always be accessed via the filesystem. The following refs\n+\t * are pseudorefs:\n \t *\n \t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n \t *   carries additional metadata like where it came from.\n@@ -1891,17 +1891,17 @@ static int is_special_ref(const char *refname)\n \t *   heads.\n \t *\n \t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (special refs) or through the reference\n+\t * through the filesystem (pseudorefs) or through the reference\n \t * backend (normal ones).\n \t */\n-\tstatic const char * const special_refs[] = {\n+\tstatic const char * const pseudo_refs[] = {\n \t\t\"FETCH_HEAD\",\n \t\t\"MERGE_HEAD\",\n \t};\n \tsize_t i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(special_refs); i++)\n-\t\tif (!strcmp(refname, special_refs[i]))\n+\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n+\t\tif (!strcmp(refname, pseudo_refs[i]))\n \t\t\treturn 1;\n \n \treturn 0;\n@@ -1912,7 +1912,7 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,\n \t\t      unsigned int *type, int *failure_errno)\n {\n \tassert(failure_errno);\n-\tif (is_special_ref(refname))\n+\tif (is_pseudo_ref(refname))\n \t\treturn refs_read_special_head(ref_store, refname, oid, referent,\n \t\t\t\t\t      type, failure_errno);\n \n-- \n2.45.0\n\n"},{"id":"493904","messageId":"c196fe3c45eced5aeaee3afb59d42ba3d16ff9d9.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 06/10] refs: classify HEAD as a root ref","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:37Z","receivedAt":"2024-05-02T08:17:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Root refs are those refs that live in the root of the ref hierarchy.\nOur old and venerable \"HEAD\" reference falls into this category, but we\ndon't yet classify it as such in `is_root_ref()`.\n\nAdapt the function to also treat \"HEAD\" as a root ref. This change is\nsafe to do for all current callers:\n\n- `ref_kind_from_refname()` already handles \"HEAD\" explicitly before\n  calling `is_root_ref()`.\n\n- The \"files\" and \"reftable\" backends explicitly called both\n  `is_root_ref()` and `is_headref()`.\n\nThis change should thus essentially be a no-op.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                  | 2 ++\n refs.h                  | 6 +++++-\n refs/files-backend.c    | 3 +--\n refs/reftable-backend.c | 3 +--\n 4 files changed, 9 insertions(+), 5 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 6266f77474..5b89e83ad7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -874,6 +874,8 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \n \tif (!is_root_ref_syntax(refname))\n \t\treturn 0;\n+\tif (is_headref(refs, refname))\n+\t\treturn 1;\n \n \tif (ends_with(refname, \"_HEAD\")) {\n \t\trefs_resolve_ref_unsafe(refs, refname,\ndiff --git a/refs.h b/refs.h\nindex d0374c3275..4ac454b0c3 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1059,7 +1059,8 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  *\n  *   - Their name must be all-uppercase or underscores (\"_\").\n  *\n- *   - Their name must end with \"_HEAD\".\n+ *   - Their name must end with \"_HEAD\". As a special rule, \"HEAD\" is a root\n+ *     ref, as well.\n  *\n  *   - Their name may not contain a slash.\n  *\n@@ -1078,6 +1079,9 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  */\n int is_root_ref(struct ref_store *refs, const char *refname);\n \n+/*\n+ * Check whether the reference is \"HEAD\" and whether it exists.\n+ */\n int is_headref(struct ref_store *refs, const char *refname);\n \n #endif /* REFS_H */\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0fcb601444..ea927c516d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -351,8 +351,7 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n \t\tstrbuf_addstr(&refname, de->d_name);\n \n \t\tdtype = get_dtype(de, &path, 1);\n-\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n-\t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n+\t\tif (dtype == DT_REG && is_root_ref(ref_store, de->d_name))\n \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n \n \t\tstrbuf_setlen(&refname, dirnamelen);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 5a5e64fe69..41555fcf64 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -356,8 +356,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n-\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n-\t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n+\t\t      is_root_ref(&iter->refs->base, iter->ref.refname))) {\n \t\t\tcontinue;\n \t\t}\n \n-- \n2.45.0\n\n"},{"id":"493905","messageId":"92a71222e1067ca4ce9ecaaa555d78d0cce0d9d0.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 07/10] refs: root refs can be symbolic refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:42Z","receivedAt":"2024-05-02T08:17:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before this patch series, root refs except for \"HEAD\" and our special\nrefs were classified as pseudorefs. Furthermore, our terminology\nclarified that pseudorefs must not be symbolic refs. This restriction\nis enforced in `is_root_ref()`, which explicitly checks that a supposed\nroot ref resolves to an object ID without recursing.\n\nThis has been extremely confusing right from the start because (in old\nterminology) a ref name may sometimes be a pseudoref and sometimes not\ndepending on whether it is a symbolic or regular ref. This behaviour\ndoes not seem reasonable at all and I very much doubt that it results in\nanything sane.\n\nFurthermore, the behaviour is different to `is_headref()`, which only\nchecks for the ref to exist. While that is in line with our glossary,\nthis inconsistency only adds to the confusion.\n\nLast but not least, the current behaviour can actually lead to a\nsegfault when calling `is_root_ref()` with a reference that either does\nnot exist or that is a symbolic ref because we never initialized `oid`.\n\nLet's loosen the restrictions in accordance to the new definition of\nroot refs, which are simply plain refs that may as well be a symbolic\nref. Consequently, we can just check for the ref to exist instead of\nrequiring it to be a regular ref.\n\nAdd a test that verifies that this does not change user-visible\nbehaviour. Namely, we still don't want to show broken refs to the user\nby default in git-for-each-ref(1). What this does allow though is for\ninternal callers to surface dangling root refs when they pass in the\n`DO_FOR_EACH_INCLUDE_BROKEN` flag.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                         | 50 ++++++++++++++++++++++++----------\n t/t6302-for-each-ref-filter.sh | 17 ++++++++++++\n 2 files changed, 53 insertions(+), 14 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5b89e83ad7..ca9844bc3e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -869,7 +869,10 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \t\t\"NOTES_MERGE_REF\",\n \t\t\"MERGE_AUTOSTASH\",\n \t};\n-\tstruct object_id oid;\n+\tstruct strbuf referent = STRBUF_INIT;\n+\tstruct object_id oid = { 0 };\n+\tint failure_errno, ret = 0;\n+\tunsigned int flags;\n \tsize_t i;\n \n \tif (!is_root_ref_syntax(refname))\n@@ -877,30 +880,49 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \tif (is_headref(refs, refname))\n \t\treturn 1;\n \n+\t/*\n+\t * Note that we cannot use `refs_ref_exists()` here because that also\n+\t * checks whether its target ref exists in case refname is a symbolic\n+\t * ref.\n+\t */\n \tif (ends_with(refname, \"_HEAD\")) {\n-\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t&oid, NULL);\n-\t\treturn !is_null_oid(&oid);\n+\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n+\t\t\t\t\t &flags, &failure_errno);\n+\t\tgoto done;\n \t}\n \n-\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n+\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++) {\n \t\tif (!strcmp(refname, irregular_root_refs[i])) {\n-\t\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t\t&oid, NULL);\n-\t\t\treturn !is_null_oid(&oid);\n+\t\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n+\t\t\t\t\t\t &flags, &failure_errno);\n+\t\t\tgoto done;\n \t\t}\n+\t}\n \n-\treturn 0;\n+done:\n+\tstrbuf_release(&referent);\n+\treturn ret;\n }\n \n int is_headref(struct ref_store *refs, const char *refname)\n {\n-\tif (!strcmp(refname, \"HEAD\"))\n-\t\treturn refs_ref_exists(refs, refname);\n+\tstruct strbuf referent = STRBUF_INIT;\n+\tstruct object_id oid = { 0 };\n+\tint failure_errno, ret = 0;\n+\tunsigned int flags;\n \n-\treturn 0;\n+\t/*\n+\t * Note that we cannot use `refs_ref_exists()` here because that also\n+\t * checks whether its target ref exists in case refname is a symbolic\n+\t * ref.\n+\t */\n+\tif (!strcmp(refname, \"HEAD\")) {\n+\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n+\t\t\t\t\t &flags, &failure_errno);\n+\t}\n+\n+\tstrbuf_release(&referent);\n+\treturn ret;\n }\n \n static int is_current_worktree_ref(const char *ref) {\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 948f1bb5f4..92ed8957c8 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -62,6 +62,23 @@ test_expect_success '--include-root-refs with other patterns' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--include-root-refs omits dangling symrefs' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit initial &&\n+\t\tgit symbolic-ref DANGLING_HEAD refs/heads/missing &&\n+\t\tcat >expect <<-EOF &&\n+\t\tHEAD\n+\t\t$(git symbolic-ref HEAD)\n+\t\trefs/tags/initial\n+\t\tEOF\n+\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success 'filtering with --points-at' '\n \tcat >expect <<-\\EOF &&\n \trefs/heads/main\n-- \n2.45.0\n\n"},{"id":"493906","messageId":"8bd52e536329bb3ad1e5dfc34b42de0902511275.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 08/10] refs: pseudorefs are no refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:46Z","receivedAt":"2024-05-02T08:17:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `is_root_ref()` function will happily clarify a pseudoref as a root\nref, even though pseudorefs are no refs. Next to being wrong, it also\nleads to inconsistent behaviour across ref backends: while the \"files\"\nbackend accidentally knows to parse those pseudorefs and thus yields\nthem to the caller, the \"reftable\" backend won't ever see the pseudoref\nat all because they are never stored in the \"reftable\" backend.\n\nFix this issue by filtering out pseudorefs in `is_root_ref()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                         | 65 +++++++++++++++++-----------------\n t/t6302-for-each-ref-filter.sh | 17 +++++++++\n 2 files changed, 50 insertions(+), 32 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex ca9844bc3e..dec9dbdc2d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,6 +844,37 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n+static int is_pseudo_ref(const char *refname)\n+{\n+\t/*\n+\t * Pseudorefs are refs that have different semantics compared to\n+\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+\t * but must always be accessed via the filesystem. The following refs\n+\t * are pseudorefs:\n+\t *\n+\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n+\t *   carries additional metadata like where it came from.\n+\t *\n+\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n+\t *   heads.\n+\t *\n+\t * Reading, writing or deleting references must consistently go either\n+\t * through the filesystem (pseudorefs) or through the reference\n+\t * backend (normal ones).\n+\t */\n+\tstatic const char * const pseudo_refs[] = {\n+\t\t\"FETCH_HEAD\",\n+\t\t\"MERGE_HEAD\",\n+\t};\n+\tsize_t i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n+\t\tif (!strcmp(refname, pseudo_refs[i]))\n+\t\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n static int is_root_ref_syntax(const char *refname)\n {\n \tconst char *c;\n@@ -875,7 +906,8 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \tunsigned int flags;\n \tsize_t i;\n \n-\tif (!is_root_ref_syntax(refname))\n+\tif (!is_root_ref_syntax(refname) ||\n+\t    is_pseudo_ref(refname))\n \t\treturn 0;\n \tif (is_headref(refs, refname))\n \t\treturn 1;\n@@ -1900,37 +1932,6 @@ static int refs_read_special_head(struct ref_store *ref_store,\n \treturn result;\n }\n \n-static int is_pseudo_ref(const char *refname)\n-{\n-\t/*\n-\t * Pseudorefs are refs that have different semantics compared to\n-\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n-\t * but must always be accessed via the filesystem. The following refs\n-\t * are pseudorefs:\n-\t *\n-\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n-\t *   carries additional metadata like where it came from.\n-\t *\n-\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n-\t *   heads.\n-\t *\n-\t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (pseudorefs) or through the reference\n-\t * backend (normal ones).\n-\t */\n-\tstatic const char * const pseudo_refs[] = {\n-\t\t\"FETCH_HEAD\",\n-\t\t\"MERGE_HEAD\",\n-\t};\n-\tsize_t i;\n-\n-\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n-\t\tif (!strcmp(refname, pseudo_refs[i]))\n-\t\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,\n \t\t      struct object_id *oid, struct strbuf *referent,\n \t\t      unsigned int *type, int *failure_errno)\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 92ed8957c8..163c378cfd 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--include-root-refs pattern does not print special refs' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit initial &&\n+\t\tgit rev-parse HEAD >.git/MERGE_HEAD &&\n+\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tHEAD\n+\t\t$(git symbolic-ref HEAD)\n+\t\trefs/tags/initial\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success '--include-root-refs with other patterns' '\n \tcat >expect <<-\\EOF &&\n \tHEAD\n-- \n2.45.0\n\n"},{"id":"493907","messageId":"cd6d745a0107245072317d7f67ca6e0b2aebc972.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 09/10] ref-filter: properly distinuish pseudo and root refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:51Z","receivedAt":"2024-05-02T08:17:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The ref-filter interfaces currently define root refs as either a\ndetached HEAD or a pseudo ref. Pseudo refs aren't root refs though, so\nlet's properly distinguish those ref types.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/for-each-ref.c |  2 +-\n ref-filter.c           | 16 +++++++++-------\n ref-filter.h           |  4 ++--\n refs.c                 | 18 +-----------------\n refs.h                 | 18 ++++++++++++++++++\n 5 files changed, 31 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 919282e12a..5517a4a1c0 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -98,7 +98,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (include_root_refs)\n-\t\tflags |= FILTER_REFS_ROOT_REFS;\n+\t\tflags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD;\n \n \tfilter.match_as_path = 1;\n \tfilter_and_format_refs(&filter, flags, sorting, &format);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 361beb6619..d72113edfe 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2628,7 +2628,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,\n \t\t\t\t       each_ref_fn cb,\n \t\t\t\t       void *cb_data)\n {\n-\tif (filter->kind == FILTER_REFS_KIND_MASK) {\n+\tif (filter->kind & FILTER_REFS_ROOT_REFS) {\n \t\t/* In this case, we want to print all refs including root refs. */\n \t\treturn refs_for_each_include_root_refs(get_main_ref_store(the_repository),\n \t\t\t\t\t\t       cb, cb_data);\n@@ -2756,8 +2756,10 @@ static int ref_kind_from_refname(const char *refname)\n \t\t\treturn ref_kind[i].kind;\n \t}\n \n-\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n+\tif (is_pseudo_ref(refname))\n \t\treturn FILTER_REFS_PSEUDOREFS;\n+\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n+\t\treturn FILTER_REFS_ROOT_REFS;\n \n \treturn FILTER_REFS_OTHERS;\n }\n@@ -2794,11 +2796,11 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const struct\n \t/*\n \t * Generally HEAD refs are printed with special description denoting a rebase,\n \t * detached state and so forth. This is useful when only printing the HEAD ref\n-\t * But when it is being printed along with other pseudorefs, it makes sense to\n-\t * keep the formatting consistent. So we mask the type to act like a pseudoref.\n+\t * But when it is being printed along with other root refs, it makes sense to\n+\t * keep the formatting consistent. So we mask the type to act like a root ref.\n \t */\n-\tif (filter->kind == FILTER_REFS_KIND_MASK && kind == FILTER_REFS_DETACHED_HEAD)\n-\t\tkind = FILTER_REFS_PSEUDOREFS;\n+\tif (filter->kind & FILTER_REFS_ROOT_REFS && kind == FILTER_REFS_DETACHED_HEAD)\n+\t\tkind = FILTER_REFS_ROOT_REFS;\n \telse if (!(kind & filter->kind))\n \t\treturn NULL;\n \n@@ -3072,7 +3074,7 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref\n \t\t * When printing all ref types, HEAD is already included,\n \t\t * so we don't want to print HEAD again.\n \t\t */\n-\t\tif (!ret && (filter->kind != FILTER_REFS_KIND_MASK) &&\n+\t\tif (!ret && !(filter->kind & FILTER_REFS_ROOT_REFS) &&\n \t\t    (filter->kind & FILTER_REFS_DETACHED_HEAD))\n \t\t\thead_ref(fn, cb_data);\n \t}\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 0ca28d2bba..27ae1aa0d1 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -23,9 +23,9 @@\n \t\t\t\t    FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)\n #define FILTER_REFS_DETACHED_HEAD  0x0020\n #define FILTER_REFS_PSEUDOREFS     0x0040\n-#define FILTER_REFS_ROOT_REFS      (FILTER_REFS_DETACHED_HEAD | FILTER_REFS_PSEUDOREFS)\n+#define FILTER_REFS_ROOT_REFS      0x0080\n #define FILTER_REFS_KIND_MASK      (FILTER_REFS_REGULAR | FILTER_REFS_DETACHED_HEAD | \\\n-\t\t\t\t    FILTER_REFS_PSEUDOREFS)\n+\t\t\t\t    FILTER_REFS_PSEUDOREFS | FILTER_REFS_ROOT_REFS)\n \n struct atom_value;\n struct ref_sorting;\ndiff --git a/refs.c b/refs.c\nindex dec9dbdc2d..50d679b7e7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,24 +844,8 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n-static int is_pseudo_ref(const char *refname)\n+int is_pseudo_ref(const char *refname)\n {\n-\t/*\n-\t * Pseudorefs are refs that have different semantics compared to\n-\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n-\t * but must always be accessed via the filesystem. The following refs\n-\t * are pseudorefs:\n-\t *\n-\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n-\t *   carries additional metadata like where it came from.\n-\t *\n-\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n-\t *   heads.\n-\t *\n-\t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (pseudorefs) or through the reference\n-\t * backend (normal ones).\n-\t */\n \tstatic const char * const pseudo_refs[] = {\n \t\t\"FETCH_HEAD\",\n \t\t\"MERGE_HEAD\",\ndiff --git a/refs.h b/refs.h\nindex 4ac454b0c3..8255989e7e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1084,4 +1084,22 @@ int is_root_ref(struct ref_store *refs, const char *refname);\n  */\n int is_headref(struct ref_store *refs, const char *refname);\n \n+/*\n+ * Pseudorefs are refs that have different semantics compared to\n+ * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+ * but must always be accessed via the filesystem. The following refs\n+ * are pseudorefs:\n+ *\n+ * - FETCH_HEAD may contain multiple object IDs, and each one of them\n+ *   carries additional metadata like where it came from.\n+ *\n+ * - MERGE_HEAD may contain multiple object IDs when merging multiple\n+ *   heads.\n+ *\n+ * Reading, writing or deleting references must consistently go either\n+ * through the filesystem (pseudorefs) or through the reference\n+ * backend (normal ones).\n+ */\n+int is_pseudo_ref(const char *refname);\n+\n #endif /* REFS_H */\n-- \n2.45.0\n\n"},{"id":"493908","messageId":"6956fccced4d082c7fc304fb154147838ec43205.1714637671.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714637671.git.ps@pks.im","subject":"[PATCH v3 10/10] refs: refuse to write pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T08:17:55Z","receivedAt":"2024-05-02T08:18:00Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Pseudorefs are not stored in the ref database as by definition, they\ncarry additional metadata that essentially makes them not a ref. As\nsuch, writing pseudorefs via the ref backend does not make any sense\nwhatsoever as the ref backend wouldn't know how exactly to store the\ndata.\n\nRestrict writing pseudorefs via the ref backend.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c           | 7 +++++++\n t/t5510-fetch.sh | 6 +++---\n 2 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 50d679b7e7..7c3c7465a4 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1307,6 +1307,13 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\treturn -1;\n \t}\n \n+\tif (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&\n+\t    is_pseudo_ref(refname)) {\n+\t\tstrbuf_addf(err, _(\"refusing to update pseudoref '%s'\"),\n+\t\t\t    refname);\n+\t\treturn -1;\n+\t}\n+\n \tif (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)\n \t\tBUG(\"illegal flags 0x%x passed to ref_transaction_update()\", flags);\n \ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 33d34d5ae9..4eb569f4df 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -518,7 +518,7 @@ test_expect_success 'fetch with a non-applying branch.<name>.merge' '\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [1]' '\n \tone_head=$(cd one && git rev-parse HEAD) &&\n \tthis_head=$(git rev-parse HEAD) &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -530,7 +530,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n \tone_ref=$(cd one && git symbolic-ref HEAD) &&\n \tgit config branch.main.remote blub &&\n \tgit config branch.main.merge \"$one_ref\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -540,7 +540,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n # the merge spec does not match the branch the remote HEAD points to\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [3]' '\n \tgit config branch.main.merge \"${one_ref}_not\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n-- \n2.45.0\n\n"},{"id":"493909","messageId":"CAOLa=ZTB70QJfX2YSdqRtA+5WaWz9+qRXqw76u36ouZ-vC_qZA@mail.gmail.com","threadId":"61387","inReplyTo":"ZjNJ5EPMYxLi1QR0@tanuki","subject":"Re: [PATCH v2 09/10] ref-filter: properly distinuish pseudo and root refs","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-02T10:03:36Z","receivedAt":"2024-05-02T10:03:38Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Tue, Apr 30, 2024 at 06:11:05AM -0700, Karthik Nayak wrote:\n>> In the subject: s/distinuish/distinguish\n>>\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>> > The ref-filter interfaces currently define root refs as either a\n>> > detached HEAD or a pseudo ref. Pseudo refs aren't root refs though, so\n>> > let's properly distinguish those ref types.\n>> >\n>> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n>> > ---\n>> >  builtin/for-each-ref.c |  2 +-\n>> >  ref-filter.c           | 16 +++++++++-------\n>> >  ref-filter.h           |  4 ++--\n>> >  refs.c                 | 18 +-----------------\n>> >  refs.h                 | 18 ++++++++++++++++++\n>> >  5 files changed, 31 insertions(+), 27 deletions(-)\n>> >\n>> > diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\n>> > index 919282e12a..5517a4a1c0 100644\n>> > --- a/builtin/for-each-ref.c\n>> > +++ b/builtin/for-each-ref.c\n>> > @@ -98,7 +98,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n>> >  \t}\n>> >\n>> >  \tif (include_root_refs)\n>> > -\t\tflags |= FILTER_REFS_ROOT_REFS;\n>> > +\t\tflags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD;\n>>\n>> The only issue I see with this patch is that it makes me think that HEAD\n>> is not a root ref anymore. I get that this is the best way to define the\n>> directives because otherwise you'd need a new flag something like\n>> `FILTER_REFS_ROOT_REFS_WITHOUT_HEAD` and `FILTER_REFS_ROOT_REFS` would\n>> be the summation of that and the HEAD flag.\n>>\n>> Apart from this, the patch looks good.\n>\n> Well, it is a root ref, but we treat it differently in the ref-filter\n> interface because it's rendered differently than any other root ref.\n> Furthermore, the ref-filter interfaces allow you to _only_ list the HEAD\n> ref, which is another reason why it's singled out.\n>\n> Renaming this to FILTER_REFS_ROOT_REFS_WITHOUT_HEAD ould be quite\n> misleading, too, because we have the following snippet:\n>\n> @@ -2794,11 +2796,11 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const struct\n>         /*\n>          * Generally HEAD refs are printed with special description denoting a rebase,\n>          * detached state and so forth. This is useful when only printing the HEAD ref\n>          * But when it is being printed along with other root refs, it makes sense to\n>          * keep the formatting consistent. So we mask the type to act like a root ref.\n>          */\n>         if (filter->kind & FILTER_REFS_ROOT_REFS && kind == FILTER_REFS_DETACHED_HEAD)\n>                 kind = FILTER_REFS_ROOT_REFS;\n>         else if (!(kind & filter->kind))\n>                 return NULL;\n>\n\nRight..\n\n> If we named this FILTER_REFS_ROOT_REFS_WITHOUT_HEAD then the above code\n> would be even more surprising.\n>\n> So yeah, it's a bit weird, but I think it's more sensible to retain the\n> code as proposed.\n\nAgreed. Let's keep it as is!\n"},{"id":"494038","messageId":"20240503181339.GH3631237@coredump.intra.peff.net","threadId":"61387","inReplyTo":"92a71222e1067ca4ce9ecaaa555d78d0cce0d9d0.1714637671.git.ps@pks.im","subject":"Re: [PATCH v3 07/10] refs: root refs can be symbolic refs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-05-03T18:13:39Z","receivedAt":"2024-05-03T18:13:41Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, May 02, 2024 at 10:17:42AM +0200, Patrick Steinhardt wrote:\n\n> Before this patch series, root refs except for \"HEAD\" and our special\n> refs were classified as pseudorefs. Furthermore, our terminology\n> clarified that pseudorefs must not be symbolic refs. This restriction\n> is enforced in `is_root_ref()`, which explicitly checks that a supposed\n> root ref resolves to an object ID without recursing.\n> \n> This has been extremely confusing right from the start because (in old\n> terminology) a ref name may sometimes be a pseudoref and sometimes not\n> depending on whether it is a symbolic or regular ref. This behaviour\n> does not seem reasonable at all and I very much doubt that it results in\n> anything sane.\n> \n> Furthermore, the behaviour is different to `is_headref()`, which only\n> checks for the ref to exist. While that is in line with our glossary,\n> this inconsistency only adds to the confusion.\n> \n> Last but not least, the current behaviour can actually lead to a\n> segfault when calling `is_root_ref()` with a reference that either does\n> not exist or that is a symbolic ref because we never initialized `oid`.\n> \n> Let's loosen the restrictions in accordance to the new definition of\n> root refs, which are simply plain refs that may as well be a symbolic\n> ref. Consequently, we can just check for the ref to exist instead of\n> requiring it to be a regular ref.\n\nIt's not clear to me that this existence check is particularly useful.\nSomething that fails read_raw_ref() will fail if:\n\n  - the file does not exist at all. But then how did somebody find out\n    about it at all to ask is_pseudoref()?\n\n  - it does exist, but does not look like a ref. Is this important? If I\n    do \"echo foo >.git/CHERRY_PICK_HEAD\", does it become not a root ref\n    anymore? Or is it a root ref that is broken? I'd have thought the\n    latter, and the syntax is what distinguishes it.\n\nMaking the classification purely syntactic based on the name feels\nsimpler to me to reason about. You'll never run into confusing cases\nwhere repo state changes how commands may behave.\n\nAnd arguably is_pseudoref_syntax() should be taking into account the\n\"_HEAD\" restriction and special names anyway. It is a bit weird that\neven if we tighten up the refname checking to use is_pseudoref_syntax(),\nyou'd still be able to \"git update-ref FOO\" but then not see it as a\nroot ref!\n\n-Peff\n"},{"id":"494064","messageId":"maqra7gbokanvptimdk57gaaitdxdpaxse725odzcvqieqfhbc@sgdmfeuhi5hj","threadId":"61387","inReplyTo":"ZjNJ3j2wSgui_cG8@tanuki","subject":"Re: [PATCH v2 07/10] refs: root refs can be symbolic refs","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2024-05-03T20:49:25Z","receivedAt":"2024-05-03T20:50:43Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 24/05/02 10:07AM, Patrick Steinhardt wrote:\n> On Tue, Apr 30, 2024 at 12:09:57PM -0500, Justin Tobler wrote:\n\n> > I'm not quite sure I understand why we are changing the behavior of\n> > `is_headref()` here. Do we no longer want to validate the ref exists if\n> > it is symbolic?\n> \n> The implementation does not conform to the definition of a \"HEAD\" ref.\n> Even before this patch series, a \"HEAD\" ref could either be a symbolic\n> or a regular ref. So to answer the question of \"Is this a HEAD ref?\" you\n> only need to check whether the ref exists, not whether its target\n> exists.\n\nThanks Patrick! I think this explantion might be good to add to the\ncommit message.\n\n> > In a prior commit, `is_headref()` is commented to mention that we check\n> > whether the reference exists. Maybe that could use some additional\n> > clarification?\n> \n> Which particular commit do you refer to? It's not part of this series,\n> is it?\n\nI'm refering to the comment added above `is_headref()` in\n(refs: classify HEAD as a root ref, 2024-04-30):\n\n\"Check whether the reference is \"HEAD\" and whether it exists.\"\n\nMaybe I misunderstand its intent though.\n\n-Justin\n"},{"id":"494265","messageId":"ZjoDQTe8APWgyzll@tanuki","threadId":"61387","inReplyTo":"maqra7gbokanvptimdk57gaaitdxdpaxse725odzcvqieqfhbc@sgdmfeuhi5hj","subject":"Re: [PATCH v2 07/10] refs: root refs can be symbolic refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-07T10:32:33Z","receivedAt":"2024-05-07T10:32:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, May 03, 2024 at 03:49:25PM -0500, Justin Tobler wrote:\n> On 24/05/02 10:07AM, Patrick Steinhardt wrote:\n> > On Tue, Apr 30, 2024 at 12:09:57PM -0500, Justin Tobler wrote:\n> \n> > > I'm not quite sure I understand why we are changing the behavior of\n> > > `is_headref()` here. Do we no longer want to validate the ref exists if\n> > > it is symbolic?\n> > \n> > The implementation does not conform to the definition of a \"HEAD\" ref.\n> > Even before this patch series, a \"HEAD\" ref could either be a symbolic\n> > or a regular ref. So to answer the question of \"Is this a HEAD ref?\" you\n> > only need to check whether the ref exists, not whether its target\n> > exists.\n> \n> Thanks Patrick! I think this explantion might be good to add to the\n> commit message.\n\nI'll restructure this a bit. In fact, we can even get rid of\n`is_headref()` completely as it is now covered by `is_root_ref()`, and\nthere are no callers of `is_headref()`.\n\n> > > In a prior commit, `is_headref()` is commented to mention that we check\n> > > whether the reference exists. Maybe that could use some additional\n> > > clarification?\n> > \n> > Which particular commit do you refer to? It's not part of this series,\n> > is it?\n> \n> I'm refering to the comment added above `is_headref()` in\n> (refs: classify HEAD as a root ref, 2024-04-30):\n> \n> \"Check whether the reference is \"HEAD\" and whether it exists.\"\n> \n> Maybe I misunderstand its intent though.\n\nAh, now I get what you're saying. Yeah, this could indeed use a\nclarification. I'll add it to `is_root_ref()` though given that\n`is_headref()` will go away.\n\nPatrick\n"},{"id":"494370","messageId":"3886895.kQq0lBPeGt@cayenne","threadId":"61387","inReplyTo":"b5e7ddb1e30acb7e3871a189beb2c828b18f9e73.1714398019.git.ps@pks.im","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Jean-Noël AVILA","fromEmail":"avila.jn@gmail.com","sentAt":"2024-05-09T17:29:18Z","receivedAt":"2024-05-09T17:29:23Z","isPatch":true,"sender":{"key":"jn.avila@free.fr","avatar":"https://avatars.githubusercontent.com/u/156172?v=4"},"body":"Hello,\n\nOn Monday, 29 April 2024 15:41:28 CEST Patrick Steinhardt wrote:\n> ---\n>  Documentation/glossary-content.txt | 36 ++++++++++++++++++------------\n>  refs.c                             |  2 ++\n>  t/t6302-for-each-ref-filter.sh     | 17 ++++++++++++++\n>  3 files changed, 41 insertions(+), 14 deletions(-)\n> \n> diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-\ncontent.txt\n> index d71b199955..4275918fa0 100644\n> --- a/Documentation/glossary-content.txt\n> +++ b/Documentation/glossary-content.txt\n> @@ -497,20 +497,28 @@ exclude;;\n...\n> +\n> + - \"`AUTO_MERGE`\"\n> +\n> + - \"`BISECT_EXPECTED_REV`\"\n> +\n> + - \"`NOTES_MERGE_PARTIAL`\"\n> +\n> + - \"`NOTES_MERGE_REF`\"\n> +\n> + - \"`MERGE_AUTOSTASH`\"\n\nQuoting the names seems overkill here, as they are already formatted with the \nback-quotes. The rendering as bold or monospace is enough.\n\nRegards,\n\nJean-Noël\n\n\n\n\n"},{"id":"494399","messageId":"Zj3bvtbMrhe1Mmsk@tanuki","threadId":"61387","inReplyTo":"3886895.kQq0lBPeGt@cayenne","subject":"Re: [PATCH 2/3] refs: do not label special refs as pseudo refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:33:02Z","receivedAt":"2024-05-10T08:33:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, May 09, 2024 at 07:29:18PM +0200, Jean-Noël AVILA wrote:\n> Hello,\n> \n> On Monday, 29 April 2024 15:41:28 CEST Patrick Steinhardt wrote:\n> > ---\n> >  Documentation/glossary-content.txt | 36 ++++++++++++++++++------------\n> >  refs.c                             |  2 ++\n> >  t/t6302-for-each-ref-filter.sh     | 17 ++++++++++++++\n> >  3 files changed, 41 insertions(+), 14 deletions(-)\n> > \n> > diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-\n> content.txt\n> > index d71b199955..4275918fa0 100644\n> > --- a/Documentation/glossary-content.txt\n> > +++ b/Documentation/glossary-content.txt\n> > @@ -497,20 +497,28 @@ exclude;;\n> ...\n> > +\n> > + - \"`AUTO_MERGE`\"\n> > +\n> > + - \"`BISECT_EXPECTED_REV`\"\n> > +\n> > + - \"`NOTES_MERGE_PARTIAL`\"\n> > +\n> > + - \"`NOTES_MERGE_REF`\"\n> > +\n> > + - \"`MERGE_AUTOSTASH`\"\n> \n> Quoting the names seems overkill here, as they are already formatted with the \n> back-quotes. The rendering as bold or monospace is enough.\n> \n> Regards,\n> \n> Jean-Noël\n\nThese don't exist in later versions of this patch series anymore. But\nthere are other cases where I did the same in the current version, so\nlet me drop that.\n\nThanks!\n\nPatrick\n"},{"id":"494400","messageId":"cover.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714398019.git.ps@pks.im","subject":"[PATCH v4 00/10] Clarify pseudo-ref terminology","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:01Z","receivedAt":"2024-05-10T08:48:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthi sis the fourth version of my patch series that aims to clarify the\npseudo-ref terminology. Changes compared to v3:\n\n  - Render refs in \"Documentation/glossary-context.txt\" more\n    consistently with backticks, only.\n\n  - Reorder patches 6 and 7 such that we first correct `is_root_ref()`,\n    and then adapt `is_headref()`.\n\n  - Furthermore, I have inlined `is_headref()` into `is_root_ref()`\n    completely now as it didn't have any users anymore.\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (10):\n  Documentation/glossary: redefine pseudorefs as special refs\n  Documentation/glossary: clarify limitations of pseudorefs\n  Documentation/glossary: define root refs as refs\n  refs: rename `is_pseudoref()` to `is_root_ref()`\n  refs: refname `is_special_ref()` to `is_pseudo_ref()`\n  refs: root refs can be symbolic refs\n  refs: classify HEAD as a root ref\n  refs: pseudorefs are no refs\n  ref-filter: properly distinuish pseudo and root refs\n  refs: refuse to write pseudorefs\n\n Documentation/glossary-content.txt |  72 +++++++++----------\n builtin/for-each-ref.c             |   2 +-\n ref-filter.c                       |  16 +++--\n ref-filter.h                       |   4 +-\n refs.c                             | 108 ++++++++++++++---------------\n refs.h                             |  48 ++++++++++++-\n refs/files-backend.c               |   3 +-\n refs/reftable-backend.c            |   3 +-\n t/t5510-fetch.sh                   |   6 +-\n t/t6302-for-each-ref-filter.sh     |  34 +++++++++\n 10 files changed, 185 insertions(+), 111 deletions(-)\n\nRange-diff against v3:\n 1:  e651bae690 !  1:  b1fc4c1ac7 Documentation/glossary: redefine pseudorefs as special refs\n    @@ Documentation/glossary-content.txt: exclude;;\n     ++\n     +The following pseudorefs are known to Git:\n     +\n    -+ - \"`FETCH_HEAD`\" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n    ++ - `FETCH_HEAD` is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n     +   may refer to multiple object IDs. Each object ID is annotated with metadata\n     +   indicating where it was fetched from and its fetch status.\n     +\n    -+ - \"`MERGE_HEAD`\" is written by linkgit:git-merge[1] when resolving merge\n    ++ - `MERGE_HEAD` is written by linkgit:git-merge[1] when resolving merge\n     +   conflicts. It contains all commit IDs which are being merged.\n      \n      [[def_pull]]pull::\n 2:  66ac046132 =  2:  dce3a0fa7e Documentation/glossary: clarify limitations of pseudorefs\n 3:  243d616101 !  3:  79249962f5 Documentation/glossary: define root refs as refs\n    @@ Documentation/glossary-content.txt: The following pseudorefs are known to Git:\n     +match these rules. The following list is exhaustive and shall not be\n     +extended in the future:\n     ++\n    -+ - AUTO_MERGE\n    ++ - `AUTO_MERGE`\n     +\n    -+ - BISECT_EXPECTED_REV\n    ++ - `BISECT_EXPECTED_REV`\n     +\n    -+ - NOTES_MERGE_PARTIAL\n    ++ - `NOTES_MERGE_PARTIAL`\n     +\n    -+ - NOTES_MERGE_REF\n    ++ - `NOTES_MERGE_REF`\n     +\n    -+ - MERGE_AUTOSTASH\n    ++ - `MERGE_AUTOSTASH`\n     ++\n     +Different subhierarchies are used for different purposes. For example,\n     +the `refs/heads/` hierarchy is used to represent local branches whereas\n 4:  0a116f9d11 =  4:  ee2b090f75 refs: rename `is_pseudoref()` to `is_root_ref()`\n 5:  484a0856bc =  5:  2c09bc7690 refs: refname `is_special_ref()` to `is_pseudo_ref()`\n 7:  92a71222e1 !  6:  5e402811a6 refs: root refs can be symbolic refs\n    @@ Commit message\n         does not seem reasonable at all and I very much doubt that it results in\n         anything sane.\n     \n    -    Furthermore, the behaviour is different to `is_headref()`, which only\n    -    checks for the ref to exist. While that is in line with our glossary,\n    -    this inconsistency only adds to the confusion.\n    -\n         Last but not least, the current behaviour can actually lead to a\n         segfault when calling `is_root_ref()` with a reference that either does\n         not exist or that is a symbolic ref because we never initialized `oid`.\n    @@ refs.c: int is_root_ref(struct ref_store *refs, const char *refname)\n      \tsize_t i;\n      \n      \tif (!is_root_ref_syntax(refname))\n    -@@ refs.c: int is_root_ref(struct ref_store *refs, const char *refname)\n    - \tif (is_headref(refs, refname))\n    - \t\treturn 1;\n    + \t\treturn 0;\n      \n     +\t/*\n     +\t * Note that we cannot use `refs_ref_exists()` here because that also\n    @@ refs.c: int is_root_ref(struct ref_store *refs, const char *refname)\n      }\n      \n      int is_headref(struct ref_store *refs, const char *refname)\n    - {\n    --\tif (!strcmp(refname, \"HEAD\"))\n    --\t\treturn refs_ref_exists(refs, refname);\n    -+\tstruct strbuf referent = STRBUF_INIT;\n    -+\tstruct object_id oid = { 0 };\n    -+\tint failure_errno, ret = 0;\n    -+\tunsigned int flags;\n    - \n    --\treturn 0;\n    -+\t/*\n    -+\t * Note that we cannot use `refs_ref_exists()` here because that also\n    -+\t * checks whether its target ref exists in case refname is a symbolic\n    -+\t * ref.\n    -+\t */\n    -+\tif (!strcmp(refname, \"HEAD\")) {\n    -+\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n    -+\t\t\t\t\t &flags, &failure_errno);\n    -+\t}\n    -+\n    -+\tstrbuf_release(&referent);\n    -+\treturn ret;\n    - }\n    +\n    + ## refs.h ##\n    +@@ refs.h: extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];\n    + void update_ref_namespace(enum ref_namespace namespace, char *ref);\n      \n    - static int is_current_worktree_ref(const char *ref) {\n    + /*\n    +- * Check whether the reference is an existing root reference.\n    ++ * Check whether the reference is an existing root reference. A root reference\n    ++ * that is a dangling symbolic ref is considered to exist.\n    +  *\n    +  * A root ref is a reference that lives in the root of the reference hierarchy.\n    +  * These references must conform to special syntax:\n     \n      ## t/t6302-for-each-ref-filter.sh ##\n     @@ t/t6302-for-each-ref-filter.sh: test_expect_success '--include-root-refs with other patterns' '\n 6:  c196fe3c45 !  7:  b32c56afcb refs: classify HEAD as a root ref\n    @@ Commit message\n         - The \"files\" and \"reftable\" backends explicitly called both\n           `is_root_ref()` and `is_headref()`.\n     \n    -    This change should thus essentially be a no-op.\n    +    This also aligns behaviour or `is_root_ref()` and `is_headref()` such\n    +    that we also return a trueish value when the ref is a dangling symbolic\n    +    ref. As there are no callers of `is_headref()` left afer the refactoring\n    +    we absorb it completely into `is_root_ref()`.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## refs.c ##\n    +@@ refs.c: static int is_root_ref_syntax(const char *refname)\n    + int is_root_ref(struct ref_store *refs, const char *refname)\n    + {\n    + \tstatic const char *const irregular_root_refs[] = {\n    ++\t\t\"HEAD\",\n    + \t\t\"AUTO_MERGE\",\n    + \t\t\"BISECT_EXPECTED_REV\",\n    + \t\t\"NOTES_MERGE_PARTIAL\",\n     @@ refs.c: int is_root_ref(struct ref_store *refs, const char *refname)\n    + \treturn ret;\n    + }\n      \n    - \tif (!is_root_ref_syntax(refname))\n    - \t\treturn 0;\n    -+\tif (is_headref(refs, refname))\n    -+\t\treturn 1;\n    - \n    - \tif (ends_with(refname, \"_HEAD\")) {\n    - \t\trefs_resolve_ref_unsafe(refs, refname,\n    +-int is_headref(struct ref_store *refs, const char *refname)\n    +-{\n    +-\tif (!strcmp(refname, \"HEAD\"))\n    +-\t\treturn refs_ref_exists(refs, refname);\n    +-\n    +-\treturn 0;\n    +-}\n    +-\n    + static int is_current_worktree_ref(const char *ref) {\n    + \treturn is_root_ref_syntax(ref) || is_per_worktree_ref(ref);\n    + }\n     \n      ## refs.h ##\n     @@ refs.h: void update_ref_namespace(enum ref_namespace namespace, char *ref);\n    @@ refs.h: void update_ref_namespace(enum ref_namespace namespace, char *ref);\n       */\n      int is_root_ref(struct ref_store *refs, const char *refname);\n      \n    -+/*\n    -+ * Check whether the reference is \"HEAD\" and whether it exists.\n    -+ */\n    - int is_headref(struct ref_store *refs, const char *refname);\n    - \n    +-int is_headref(struct ref_store *refs, const char *refname);\n    +-\n      #endif /* REFS_H */\n     \n      ## refs/files-backend.c ##\n 8:  8bd52e5363 !  8:  19af8c754c refs: pseudorefs are no refs\n    @@ refs.c: int is_root_ref(struct ref_store *refs, const char *refname)\n     +\tif (!is_root_ref_syntax(refname) ||\n     +\t    is_pseudo_ref(refname))\n      \t\treturn 0;\n    - \tif (is_headref(refs, refname))\n    - \t\treturn 1;\n    + \n    + \t/*\n     @@ refs.c: static int refs_read_special_head(struct ref_store *ref_store,\n      \treturn result;\n      }\n 9:  cd6d745a01 !  9:  86f7f2d2d8 ref-filter: properly distinuish pseudo and root refs\n    @@ refs.c: int is_per_worktree_ref(const char *refname)\n      \t\t\"MERGE_HEAD\",\n     \n      ## refs.h ##\n    -@@ refs.h: int is_root_ref(struct ref_store *refs, const char *refname);\n    +@@ refs.h: void update_ref_namespace(enum ref_namespace namespace, char *ref);\n       */\n    - int is_headref(struct ref_store *refs, const char *refname);\n    + int is_root_ref(struct ref_store *refs, const char *refname);\n      \n     +/*\n     + * Pseudorefs are refs that have different semantics compared to\n10:  6956fccced = 10:  640d3b169f refs: refuse to write pseudorefs\n\nbase-commit: 0f3415f1f8478b05e64db11eb8aaa2915e48fef6\n-- \n2.45.0\n\n"},{"id":"494401","messageId":"b1fc4c1ac7cd14eb72355739adce274894aaaff4.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 01/10] Documentation/glossary: redefine pseudorefs as special refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:06Z","receivedAt":"2024-05-10T08:48:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Nowadays, Git knows about three different kinds of refs. As defined in\ngitglossary(7):\n\n  - Regular refs that start with \"refs/\", like \"refs/heads/main\".\n\n  - Pseudorefs, which live in the root directory. These must have\n    all-caps names and must be a file that start with an object hash.\n    Consequently, symbolic refs are not pseudorefs because they do not\n    start with an object hash.\n\n  - Special refs, of which we only have \"FETCH_HEAD\" and \"MERGE_HEAD\".\n\nThis state is extremely confusing, and I would claim that most folks\ndon't fully understand what is what here. The current definitions also\nhave several problems:\n\n  - Where does \"HEAD\" fit in? It's not a pseudoref because it can be\n    a symbolic ref. It's not a regular ref because it does not start\n    with \"refs/\". And it's not a special ref, either.\n\n  - There is a strong overlap between pseudorefs and special refs. The\n    pseudoref section for example mentions \"MERGE_HEAD\", even though it\n    is a special ref. Is it thus both a pseudoref and a special ref?\n\n  - Why do we even need to distinguish refs that live in the root from\n    other refs when they behave just like a regular ref anyway?\n\nIn other words, the current state is quite a mess and leads to wild\ninconsistencies without much of a good reason.\n\nThe original reason why pseudorefs were introduced is that there are\nsome refs that sometimes behave like a ref, even though they aren't a\nref. And we really only have two of these nowadays, namely \"MERGE_HEAD\"\nand \"FETCH_HEAD\". Those files are never written via the ref backends,\nbut are instead written by git-fetch(1), git-pull(1) and git-merge(1).\nThey contain additional metadata that highlights where a ref has been\nfetched from or the list of commits that have been merged.\n\nThis original intent in fact matches the definition of special refs that\nwe have recently introduced in 8df4c5d205 (Documentation: add \"special\nrefs\" to the glossary, 2024-01-19). Due to the introduction of the new\nreftable backend we were forced to distinguish those refs more clearly\nsuch that we don't ever try to read or write them via the reftable\nbackend. In the same series, we also addressed all the other cases where\nwe used to write those special refs via the filesystem directly, thus\ncircumventing the ref backend, to instead write them via the backends.\nConsequently, there are no other refs left anymore which are special.\n\nLet's address this mess and return the pseudoref terminology back to its\noriginal intent: a ref that sometimes behave like a ref, but which isn't\nreally a ref because it gets written to the filesystem directly. Or in\nother words, let's redefine pseudorefs to match the current definition\nof special refs. As special refs and pseudorefs are now the same per\ndefinition, we can drop the \"special refs\" term again. It's not exposed\nto our users and thus they wouldn't ever encounter that term anyway.\n\nRefs that live in the root of the ref hierarchy but which are not\npseudorefs will be further defined in a subsequent commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 40 +++++++++---------------------\n 1 file changed, 12 insertions(+), 28 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex d71b199955..e686c83026 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -497,20 +497,18 @@ exclude;;\n \tunusual refs.\n \n [[def_pseudoref]]pseudoref::\n-\tPseudorefs are a class of files under `$GIT_DIR` which behave\n-\tlike refs for the purposes of rev-parse, but which are treated\n-\tspecially by git.  Pseudorefs both have names that are all-caps,\n-\tand always start with a line consisting of a\n-\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n-\tpseudoref, because it is sometimes a symbolic ref.  They might\n-\toptionally contain some additional data.  `MERGE_HEAD` and\n-\t`CHERRY_PICK_HEAD` are examples.  Unlike\n-\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n-\tbe symbolic refs, and never have reflogs.  They also cannot be\n-\tupdated through the normal ref update machinery.  Instead,\n-\tthey are updated by directly writing to the files.  However,\n-\tthey can be read as if they were refs, so `git rev-parse\n-\tMERGE_HEAD` will work.\n+\tA ref that has different semantics than normal refs. These refs can be\n+\taccessed via normal Git commands but may not behave the same as a\n+\tnormal ref in some cases.\n++\n+The following pseudorefs are known to Git:\n+\n+ - `FETCH_HEAD` is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n+   may refer to multiple object IDs. Each object ID is annotated with metadata\n+   indicating where it was fetched from and its fetch status.\n+\n+ - `MERGE_HEAD` is written by linkgit:git-merge[1] when resolving merge\n+   conflicts. It contains all commit IDs which are being merged.\n \n [[def_pull]]pull::\n \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n@@ -638,20 +636,6 @@ The most notable example is `HEAD`.\n \tAn <<def_object,object>> used to temporarily store the contents of a\n \t<<def_dirty,dirty>> working directory and the index for future reuse.\n \n-[[def_special_ref]]special ref::\n-\tA ref that has different semantics than normal refs. These refs can be\n-\taccessed via normal Git commands but may not behave the same as a\n-\tnormal ref in some cases.\n-+\n-The following special refs are known to Git:\n-\n- - \"`FETCH_HEAD`\" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n-   may refer to multiple object IDs. Each object ID is annotated with metadata\n-   indicating where it was fetched from and its fetch status.\n-\n- - \"`MERGE_HEAD`\" is written by linkgit:git-merge[1] when resolving merge\n-   conflicts. It contains all commit IDs which are being merged.\n-\n [[def_submodule]]submodule::\n \tA <<def_repository,repository>> that holds the history of a\n \tseparate project inside another repository (the latter of\n-- \n2.45.0\n\n"},{"id":"494402","messageId":"dce3a0fa7e7b6ab822671d5c250e09ea482ab34d.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 02/10] Documentation/glossary: clarify limitations of pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:10Z","receivedAt":"2024-05-10T08:48:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Clarify limitations that pseudorefs have:\n\n  - They can be read via git-rev-parse(1) and similar tools.\n\n  - They are not surfaced when iterating through refs, like when using\n    git-for-each-ref(1). They are not refs, so iterating through refs\n    should not surface them.\n\n  - They cannot be written via git-update-ref(1) and related commands.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex e686c83026..d8c04b37be 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -498,8 +498,8 @@ exclude;;\n \n [[def_pseudoref]]pseudoref::\n \tA ref that has different semantics than normal refs. These refs can be\n-\taccessed via normal Git commands but may not behave the same as a\n-\tnormal ref in some cases.\n+\tread via normal Git commands, but cannot be written to by commands like\n+\tlinkgit:git-update-ref[1].\n +\n The following pseudorefs are known to Git:\n \n-- \n2.45.0\n\n"},{"id":"494403","messageId":"79249962f5798ac31724d32233ce814d99afd649.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 03/10] Documentation/glossary: define root refs as refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:15Z","receivedAt":"2024-05-10T08:48:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Except for the pseudorefs MERGE_HEAD and FETCH_HEAD, all refs that live\nin the root of the ref hierarchy behave the exact same as normal refs.\nThey can be symbolic refs or direct refs and can be read, iterated over\nand written via normal tooling. All of these refs are stored in the ref\nbackends, which further demonstrates that they are just normal refs.\n\nExtend the definition of \"ref\" to also cover such root refs. The only\nadditional restriction for root refs is that they must conform to a\nspecific naming schema.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 32 +++++++++++++++++++++++-------\n 1 file changed, 25 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex d8c04b37be..c434387186 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -550,20 +550,38 @@ The following pseudorefs are known to Git:\n \tto the result.\n \n [[def_ref]]ref::\n-\tA name that begins with `refs/` (e.g. `refs/heads/master`)\n-\tthat points to an <<def_object_name,object name>> or another\n-\tref (the latter is called a <<def_symref,symbolic ref>>).\n+\tA name that that points to an <<def_object_name,object name>> or\n+\tanother ref (the latter is called a <<def_symref,symbolic ref>>).\n \tFor convenience, a ref can sometimes be abbreviated when used\n \tas an argument to a Git command; see linkgit:gitrevisions[7]\n \tfor details.\n \tRefs are stored in the <<def_repository,repository>>.\n +\n The ref namespace is hierarchical.\n-Different subhierarchies are used for different purposes (e.g. the\n-`refs/heads/` hierarchy is used to represent local branches).\n+Ref names must either start with `refs/` or be located in the root of\n+the hierarchy. For the latter, their name must follow these rules:\n +\n-There are a few special-purpose refs that do not begin with `refs/`.\n-The most notable example is `HEAD`.\n+ - The name consists of only upper-case characters or underscores.\n+\n+ - The name ends with \"`_HEAD`\" or is equal to \"`HEAD`\".\n++\n+There are some irregular refs in the root of the hierarchy that do not\n+match these rules. The following list is exhaustive and shall not be\n+extended in the future:\n++\n+ - `AUTO_MERGE`\n+\n+ - `BISECT_EXPECTED_REV`\n+\n+ - `NOTES_MERGE_PARTIAL`\n+\n+ - `NOTES_MERGE_REF`\n+\n+ - `MERGE_AUTOSTASH`\n++\n+Different subhierarchies are used for different purposes. For example,\n+the `refs/heads/` hierarchy is used to represent local branches whereas\n+the `refs/tags/` hierarchy is used to represent local tags..\n \n [[def_reflog]]reflog::\n \tA reflog shows the local \"history\" of a ref.  In other words,\n-- \n2.45.0\n\n"},{"id":"494404","messageId":"ee2b090f75ebbca2bf6cb22500d2a3e23b8a7718.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 04/10] refs: rename `is_pseudoref()` to `is_root_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:21Z","receivedAt":"2024-05-10T08:48:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename `is_pseudoref()` to `is_root_ref()` to adapt to the newly defined\nterminology in our gitglossary(7).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c            |  2 +-\n refs.c                  | 14 +++++++-------\n refs.h                  | 28 +++++++++++++++++++++++++++-\n refs/files-backend.c    |  2 +-\n refs/reftable-backend.c |  2 +-\n 5 files changed, 37 insertions(+), 11 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 59ad6f54dd..361beb6619 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2756,7 +2756,7 @@ static int ref_kind_from_refname(const char *refname)\n \t\t\treturn ref_kind[i].kind;\n \t}\n \n-\tif (is_pseudoref(get_main_ref_store(the_repository), refname))\n+\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n \t\treturn FILTER_REFS_PSEUDOREFS;\n \n \treturn FILTER_REFS_OTHERS;\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..0a4acde3ca 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,7 +844,7 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n-static int is_pseudoref_syntax(const char *refname)\n+static int is_root_ref_syntax(const char *refname)\n {\n \tconst char *c;\n \n@@ -860,9 +860,9 @@ static int is_pseudoref_syntax(const char *refname)\n \treturn 1;\n }\n \n-int is_pseudoref(struct ref_store *refs, const char *refname)\n+int is_root_ref(struct ref_store *refs, const char *refname)\n {\n-\tstatic const char *const irregular_pseudorefs[] = {\n+\tstatic const char *const irregular_root_refs[] = {\n \t\t\"AUTO_MERGE\",\n \t\t\"BISECT_EXPECTED_REV\",\n \t\t\"NOTES_MERGE_PARTIAL\",\n@@ -872,7 +872,7 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \tstruct object_id oid;\n \tsize_t i;\n \n-\tif (!is_pseudoref_syntax(refname))\n+\tif (!is_root_ref_syntax(refname))\n \t\treturn 0;\n \n \tif (ends_with(refname, \"_HEAD\")) {\n@@ -882,8 +882,8 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \t\treturn !is_null_oid(&oid);\n \t}\n \n-\tfor (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)\n-\t\tif (!strcmp(refname, irregular_pseudorefs[i])) {\n+\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n+\t\tif (!strcmp(refname, irregular_root_refs[i])) {\n \t\t\trefs_resolve_ref_unsafe(refs, refname,\n \t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n \t\t\t\t\t\t&oid, NULL);\n@@ -902,7 +902,7 @@ int is_headref(struct ref_store *refs, const char *refname)\n }\n \n static int is_current_worktree_ref(const char *ref) {\n-\treturn is_pseudoref_syntax(ref) || is_per_worktree_ref(ref);\n+\treturn is_root_ref_syntax(ref) || is_per_worktree_ref(ref);\n }\n \n enum ref_worktree_type parse_worktree_ref(const char *maybe_worktree_ref,\ndiff --git a/refs.h b/refs.h\nindex d278775e08..d0374c3275 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1051,7 +1051,33 @@ extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];\n  */\n void update_ref_namespace(enum ref_namespace namespace, char *ref);\n \n-int is_pseudoref(struct ref_store *refs, const char *refname);\n+/*\n+ * Check whether the reference is an existing root reference.\n+ *\n+ * A root ref is a reference that lives in the root of the reference hierarchy.\n+ * These references must conform to special syntax:\n+ *\n+ *   - Their name must be all-uppercase or underscores (\"_\").\n+ *\n+ *   - Their name must end with \"_HEAD\".\n+ *\n+ *   - Their name may not contain a slash.\n+ *\n+ * There is a special set of irregular root refs that exist due to historic\n+ * reasons, only. This list shall not be expanded in the future:\n+ *\n+ *   - AUTO_MERGE\n+ *\n+ *   - BISECT_EXPECTED_REV\n+ *\n+ *   - NOTES_MERGE_PARTIAL\n+ *\n+ *   - NOTES_MERGE_REF\n+ *\n+ *   - MERGE_AUTOSTASH\n+ */\n+int is_root_ref(struct ref_store *refs, const char *refname);\n+\n int is_headref(struct ref_store *refs, const char *refname);\n \n #endif /* REFS_H */\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..0fcb601444 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -351,7 +351,7 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n \t\tstrbuf_addstr(&refname, de->d_name);\n \n \t\tdtype = get_dtype(de, &path, 1);\n-\t\tif (dtype == DT_REG && (is_pseudoref(ref_store, de->d_name) ||\n+\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n \t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 010ef811b6..36ab3357a7 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -354,7 +354,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n-\t\t     (is_pseudoref(&iter->refs->base, iter->ref.refname) ||\n+\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n \t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n \t\t\tcontinue;\n \t\t}\n-- \n2.45.0\n\n"},{"id":"494405","messageId":"2c09bc7690f1126d5f72ae809fad5e4832beb8b4.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 05/10] refs: refname `is_special_ref()` to `is_pseudo_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:26Z","receivedAt":"2024-05-10T08:48:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename `is_special_ref()` to `is_pseudo_ref()` to adapt to the newly\ndefined terminology in our gitglossary(7). Note that in the preceding\ncommit we have just renamed `is_pseudoref()` to `is_root_ref()`, where\nthere may be confusion for in-flight patch series that add new calls to\n`is_pseudoref()`. In order to intentionally break such patch series we\nhave thus picked `is_pseudo_ref()` instead of `is_pseudoref()` as the\nnew name.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 0a4acde3ca..6266f77474 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1876,13 +1876,13 @@ static int refs_read_special_head(struct ref_store *ref_store,\n \treturn result;\n }\n \n-static int is_special_ref(const char *refname)\n+static int is_pseudo_ref(const char *refname)\n {\n \t/*\n-\t * Special references are refs that have different semantics compared\n-\t * to \"normal\" refs. These refs can thus not be stored in the ref\n-\t * backend, but must always be accessed via the filesystem. The\n-\t * following refs are special:\n+\t * Pseudorefs are refs that have different semantics compared to\n+\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+\t * but must always be accessed via the filesystem. The following refs\n+\t * are pseudorefs:\n \t *\n \t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n \t *   carries additional metadata like where it came from.\n@@ -1891,17 +1891,17 @@ static int is_special_ref(const char *refname)\n \t *   heads.\n \t *\n \t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (special refs) or through the reference\n+\t * through the filesystem (pseudorefs) or through the reference\n \t * backend (normal ones).\n \t */\n-\tstatic const char * const special_refs[] = {\n+\tstatic const char * const pseudo_refs[] = {\n \t\t\"FETCH_HEAD\",\n \t\t\"MERGE_HEAD\",\n \t};\n \tsize_t i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(special_refs); i++)\n-\t\tif (!strcmp(refname, special_refs[i]))\n+\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n+\t\tif (!strcmp(refname, pseudo_refs[i]))\n \t\t\treturn 1;\n \n \treturn 0;\n@@ -1912,7 +1912,7 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,\n \t\t      unsigned int *type, int *failure_errno)\n {\n \tassert(failure_errno);\n-\tif (is_special_ref(refname))\n+\tif (is_pseudo_ref(refname))\n \t\treturn refs_read_special_head(ref_store, refname, oid, referent,\n \t\t\t\t\t      type, failure_errno);\n \n-- \n2.45.0\n\n"},{"id":"494406","messageId":"5e402811a668ae7659175c6f91da6bbb982bacf1.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 06/10] refs: root refs can be symbolic refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:30Z","receivedAt":"2024-05-10T08:48:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before this patch series, root refs except for \"HEAD\" and our special\nrefs were classified as pseudorefs. Furthermore, our terminology\nclarified that pseudorefs must not be symbolic refs. This restriction\nis enforced in `is_root_ref()`, which explicitly checks that a supposed\nroot ref resolves to an object ID without recursing.\n\nThis has been extremely confusing right from the start because (in old\nterminology) a ref name may sometimes be a pseudoref and sometimes not\ndepending on whether it is a symbolic or regular ref. This behaviour\ndoes not seem reasonable at all and I very much doubt that it results in\nanything sane.\n\nLast but not least, the current behaviour can actually lead to a\nsegfault when calling `is_root_ref()` with a reference that either does\nnot exist or that is a symbolic ref because we never initialized `oid`.\n\nLet's loosen the restrictions in accordance to the new definition of\nroot refs, which are simply plain refs that may as well be a symbolic\nref. Consequently, we can just check for the ref to exist instead of\nrequiring it to be a regular ref.\n\nAdd a test that verifies that this does not change user-visible\nbehaviour. Namely, we still don't want to show broken refs to the user\nby default in git-for-each-ref(1). What this does allow though is for\ninternal callers to surface dangling root refs when they pass in the\n`DO_FOR_EACH_INCLUDE_BROKEN` flag.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                         | 31 ++++++++++++++++++++-----------\n refs.h                         |  3 ++-\n t/t6302-for-each-ref-filter.sh | 17 +++++++++++++++++\n 3 files changed, 39 insertions(+), 12 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 6266f77474..d63d60a0dc 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -869,28 +869,37 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \t\t\"NOTES_MERGE_REF\",\n \t\t\"MERGE_AUTOSTASH\",\n \t};\n-\tstruct object_id oid;\n+\tstruct strbuf referent = STRBUF_INIT;\n+\tstruct object_id oid = { 0 };\n+\tint failure_errno, ret = 0;\n+\tunsigned int flags;\n \tsize_t i;\n \n \tif (!is_root_ref_syntax(refname))\n \t\treturn 0;\n \n+\t/*\n+\t * Note that we cannot use `refs_ref_exists()` here because that also\n+\t * checks whether its target ref exists in case refname is a symbolic\n+\t * ref.\n+\t */\n \tif (ends_with(refname, \"_HEAD\")) {\n-\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t&oid, NULL);\n-\t\treturn !is_null_oid(&oid);\n+\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n+\t\t\t\t\t &flags, &failure_errno);\n+\t\tgoto done;\n \t}\n \n-\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n+\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++) {\n \t\tif (!strcmp(refname, irregular_root_refs[i])) {\n-\t\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t\t&oid, NULL);\n-\t\t\treturn !is_null_oid(&oid);\n+\t\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n+\t\t\t\t\t\t &flags, &failure_errno);\n+\t\t\tgoto done;\n \t\t}\n+\t}\n \n-\treturn 0;\n+done:\n+\tstrbuf_release(&referent);\n+\treturn ret;\n }\n \n int is_headref(struct ref_store *refs, const char *refname)\ndiff --git a/refs.h b/refs.h\nindex d0374c3275..b15ac3835e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1052,7 +1052,8 @@ extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];\n void update_ref_namespace(enum ref_namespace namespace, char *ref);\n \n /*\n- * Check whether the reference is an existing root reference.\n+ * Check whether the reference is an existing root reference. A root reference\n+ * that is a dangling symbolic ref is considered to exist.\n  *\n  * A root ref is a reference that lives in the root of the reference hierarchy.\n  * These references must conform to special syntax:\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 948f1bb5f4..92ed8957c8 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -62,6 +62,23 @@ test_expect_success '--include-root-refs with other patterns' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--include-root-refs omits dangling symrefs' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit initial &&\n+\t\tgit symbolic-ref DANGLING_HEAD refs/heads/missing &&\n+\t\tcat >expect <<-EOF &&\n+\t\tHEAD\n+\t\t$(git symbolic-ref HEAD)\n+\t\trefs/tags/initial\n+\t\tEOF\n+\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success 'filtering with --points-at' '\n \tcat >expect <<-\\EOF &&\n \trefs/heads/main\n-- \n2.45.0\n\n"},{"id":"494407","messageId":"b32c56afcb22b6d7e30e5ee0d9f6af3386edab39.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 07/10] refs: classify HEAD as a root ref","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:35Z","receivedAt":"2024-05-10T08:48:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Root refs are those refs that live in the root of the ref hierarchy.\nOur old and venerable \"HEAD\" reference falls into this category, but we\ndon't yet classify it as such in `is_root_ref()`.\n\nAdapt the function to also treat \"HEAD\" as a root ref. This change is\nsafe to do for all current callers:\n\n- `ref_kind_from_refname()` already handles \"HEAD\" explicitly before\n  calling `is_root_ref()`.\n\n- The \"files\" and \"reftable\" backends explicitly called both\n  `is_root_ref()` and `is_headref()`.\n\nThis also aligns behaviour or `is_root_ref()` and `is_headref()` such\nthat we also return a trueish value when the ref is a dangling symbolic\nref. As there are no callers of `is_headref()` left afer the refactoring\nwe absorb it completely into `is_root_ref()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                  | 9 +--------\n refs.h                  | 5 ++---\n refs/files-backend.c    | 3 +--\n refs/reftable-backend.c | 3 +--\n 4 files changed, 5 insertions(+), 15 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex d63d60a0dc..077ae5756a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -863,6 +863,7 @@ static int is_root_ref_syntax(const char *refname)\n int is_root_ref(struct ref_store *refs, const char *refname)\n {\n \tstatic const char *const irregular_root_refs[] = {\n+\t\t\"HEAD\",\n \t\t\"AUTO_MERGE\",\n \t\t\"BISECT_EXPECTED_REV\",\n \t\t\"NOTES_MERGE_PARTIAL\",\n@@ -902,14 +903,6 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \treturn ret;\n }\n \n-int is_headref(struct ref_store *refs, const char *refname)\n-{\n-\tif (!strcmp(refname, \"HEAD\"))\n-\t\treturn refs_ref_exists(refs, refname);\n-\n-\treturn 0;\n-}\n-\n static int is_current_worktree_ref(const char *ref) {\n \treturn is_root_ref_syntax(ref) || is_per_worktree_ref(ref);\n }\ndiff --git a/refs.h b/refs.h\nindex b15ac3835e..f6f4d61e1b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1060,7 +1060,8 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  *\n  *   - Their name must be all-uppercase or underscores (\"_\").\n  *\n- *   - Their name must end with \"_HEAD\".\n+ *   - Their name must end with \"_HEAD\". As a special rule, \"HEAD\" is a root\n+ *     ref, as well.\n  *\n  *   - Their name may not contain a slash.\n  *\n@@ -1079,6 +1080,4 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  */\n int is_root_ref(struct ref_store *refs, const char *refname);\n \n-int is_headref(struct ref_store *refs, const char *refname);\n-\n #endif /* REFS_H */\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0fcb601444..ea927c516d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -351,8 +351,7 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n \t\tstrbuf_addstr(&refname, de->d_name);\n \n \t\tdtype = get_dtype(de, &path, 1);\n-\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n-\t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n+\t\tif (dtype == DT_REG && is_root_ref(ref_store, de->d_name))\n \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n \n \t\tstrbuf_setlen(&refname, dirnamelen);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 36ab3357a7..7ad4337229 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -354,8 +354,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n-\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n-\t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n+\t\t      is_root_ref(&iter->refs->base, iter->ref.refname))) {\n \t\t\tcontinue;\n \t\t}\n \n-- \n2.45.0\n\n"},{"id":"494408","messageId":"19af8c754c5d88cfdf6c86d5a8136a97e6094185.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 08/10] refs: pseudorefs are no refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:40Z","receivedAt":"2024-05-10T08:48:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `is_root_ref()` function will happily clarify a pseudoref as a root\nref, even though pseudorefs are no refs. Next to being wrong, it also\nleads to inconsistent behaviour across ref backends: while the \"files\"\nbackend accidentally knows to parse those pseudorefs and thus yields\nthem to the caller, the \"reftable\" backend won't ever see the pseudoref\nat all because they are never stored in the \"reftable\" backend.\n\nFix this issue by filtering out pseudorefs in `is_root_ref()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                         | 65 +++++++++++++++++-----------------\n t/t6302-for-each-ref-filter.sh | 17 +++++++++\n 2 files changed, 50 insertions(+), 32 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 077ae5756a..f5e98e5b46 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,6 +844,37 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n+static int is_pseudo_ref(const char *refname)\n+{\n+\t/*\n+\t * Pseudorefs are refs that have different semantics compared to\n+\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+\t * but must always be accessed via the filesystem. The following refs\n+\t * are pseudorefs:\n+\t *\n+\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n+\t *   carries additional metadata like where it came from.\n+\t *\n+\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n+\t *   heads.\n+\t *\n+\t * Reading, writing or deleting references must consistently go either\n+\t * through the filesystem (pseudorefs) or through the reference\n+\t * backend (normal ones).\n+\t */\n+\tstatic const char * const pseudo_refs[] = {\n+\t\t\"FETCH_HEAD\",\n+\t\t\"MERGE_HEAD\",\n+\t};\n+\tsize_t i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n+\t\tif (!strcmp(refname, pseudo_refs[i]))\n+\t\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n static int is_root_ref_syntax(const char *refname)\n {\n \tconst char *c;\n@@ -876,7 +907,8 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \tunsigned int flags;\n \tsize_t i;\n \n-\tif (!is_root_ref_syntax(refname))\n+\tif (!is_root_ref_syntax(refname) ||\n+\t    is_pseudo_ref(refname))\n \t\treturn 0;\n \n \t/*\n@@ -1878,37 +1910,6 @@ static int refs_read_special_head(struct ref_store *ref_store,\n \treturn result;\n }\n \n-static int is_pseudo_ref(const char *refname)\n-{\n-\t/*\n-\t * Pseudorefs are refs that have different semantics compared to\n-\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n-\t * but must always be accessed via the filesystem. The following refs\n-\t * are pseudorefs:\n-\t *\n-\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n-\t *   carries additional metadata like where it came from.\n-\t *\n-\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n-\t *   heads.\n-\t *\n-\t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (pseudorefs) or through the reference\n-\t * backend (normal ones).\n-\t */\n-\tstatic const char * const pseudo_refs[] = {\n-\t\t\"FETCH_HEAD\",\n-\t\t\"MERGE_HEAD\",\n-\t};\n-\tsize_t i;\n-\n-\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n-\t\tif (!strcmp(refname, pseudo_refs[i]))\n-\t\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,\n \t\t      struct object_id *oid, struct strbuf *referent,\n \t\t      unsigned int *type, int *failure_errno)\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 92ed8957c8..163c378cfd 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--include-root-refs pattern does not print special refs' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit initial &&\n+\t\tgit rev-parse HEAD >.git/MERGE_HEAD &&\n+\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tHEAD\n+\t\t$(git symbolic-ref HEAD)\n+\t\trefs/tags/initial\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success '--include-root-refs with other patterns' '\n \tcat >expect <<-\\EOF &&\n \tHEAD\n-- \n2.45.0\n\n"},{"id":"494409","messageId":"86f7f2d2d83774544e3f2fb1c2ec37cbd41e26be.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 09/10] ref-filter: properly distinuish pseudo and root refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:45Z","receivedAt":"2024-05-10T08:48:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The ref-filter interfaces currently define root refs as either a\ndetached HEAD or a pseudo ref. Pseudo refs aren't root refs though, so\nlet's properly distinguish those ref types.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/for-each-ref.c |  2 +-\n ref-filter.c           | 16 +++++++++-------\n ref-filter.h           |  4 ++--\n refs.c                 | 18 +-----------------\n refs.h                 | 18 ++++++++++++++++++\n 5 files changed, 31 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 919282e12a..5517a4a1c0 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -98,7 +98,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (include_root_refs)\n-\t\tflags |= FILTER_REFS_ROOT_REFS;\n+\t\tflags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD;\n \n \tfilter.match_as_path = 1;\n \tfilter_and_format_refs(&filter, flags, sorting, &format);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 361beb6619..d72113edfe 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2628,7 +2628,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,\n \t\t\t\t       each_ref_fn cb,\n \t\t\t\t       void *cb_data)\n {\n-\tif (filter->kind == FILTER_REFS_KIND_MASK) {\n+\tif (filter->kind & FILTER_REFS_ROOT_REFS) {\n \t\t/* In this case, we want to print all refs including root refs. */\n \t\treturn refs_for_each_include_root_refs(get_main_ref_store(the_repository),\n \t\t\t\t\t\t       cb, cb_data);\n@@ -2756,8 +2756,10 @@ static int ref_kind_from_refname(const char *refname)\n \t\t\treturn ref_kind[i].kind;\n \t}\n \n-\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n+\tif (is_pseudo_ref(refname))\n \t\treturn FILTER_REFS_PSEUDOREFS;\n+\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n+\t\treturn FILTER_REFS_ROOT_REFS;\n \n \treturn FILTER_REFS_OTHERS;\n }\n@@ -2794,11 +2796,11 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const struct\n \t/*\n \t * Generally HEAD refs are printed with special description denoting a rebase,\n \t * detached state and so forth. This is useful when only printing the HEAD ref\n-\t * But when it is being printed along with other pseudorefs, it makes sense to\n-\t * keep the formatting consistent. So we mask the type to act like a pseudoref.\n+\t * But when it is being printed along with other root refs, it makes sense to\n+\t * keep the formatting consistent. So we mask the type to act like a root ref.\n \t */\n-\tif (filter->kind == FILTER_REFS_KIND_MASK && kind == FILTER_REFS_DETACHED_HEAD)\n-\t\tkind = FILTER_REFS_PSEUDOREFS;\n+\tif (filter->kind & FILTER_REFS_ROOT_REFS && kind == FILTER_REFS_DETACHED_HEAD)\n+\t\tkind = FILTER_REFS_ROOT_REFS;\n \telse if (!(kind & filter->kind))\n \t\treturn NULL;\n \n@@ -3072,7 +3074,7 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref\n \t\t * When printing all ref types, HEAD is already included,\n \t\t * so we don't want to print HEAD again.\n \t\t */\n-\t\tif (!ret && (filter->kind != FILTER_REFS_KIND_MASK) &&\n+\t\tif (!ret && !(filter->kind & FILTER_REFS_ROOT_REFS) &&\n \t\t    (filter->kind & FILTER_REFS_DETACHED_HEAD))\n \t\t\thead_ref(fn, cb_data);\n \t}\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 0ca28d2bba..27ae1aa0d1 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -23,9 +23,9 @@\n \t\t\t\t    FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)\n #define FILTER_REFS_DETACHED_HEAD  0x0020\n #define FILTER_REFS_PSEUDOREFS     0x0040\n-#define FILTER_REFS_ROOT_REFS      (FILTER_REFS_DETACHED_HEAD | FILTER_REFS_PSEUDOREFS)\n+#define FILTER_REFS_ROOT_REFS      0x0080\n #define FILTER_REFS_KIND_MASK      (FILTER_REFS_REGULAR | FILTER_REFS_DETACHED_HEAD | \\\n-\t\t\t\t    FILTER_REFS_PSEUDOREFS)\n+\t\t\t\t    FILTER_REFS_PSEUDOREFS | FILTER_REFS_ROOT_REFS)\n \n struct atom_value;\n struct ref_sorting;\ndiff --git a/refs.c b/refs.c\nindex f5e98e5b46..c882ece6e7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,24 +844,8 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n-static int is_pseudo_ref(const char *refname)\n+int is_pseudo_ref(const char *refname)\n {\n-\t/*\n-\t * Pseudorefs are refs that have different semantics compared to\n-\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n-\t * but must always be accessed via the filesystem. The following refs\n-\t * are pseudorefs:\n-\t *\n-\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n-\t *   carries additional metadata like where it came from.\n-\t *\n-\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n-\t *   heads.\n-\t *\n-\t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (pseudorefs) or through the reference\n-\t * backend (normal ones).\n-\t */\n \tstatic const char * const pseudo_refs[] = {\n \t\t\"FETCH_HEAD\",\n \t\t\"MERGE_HEAD\",\ndiff --git a/refs.h b/refs.h\nindex f6f4d61e1b..815dc514c7 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1080,4 +1080,22 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  */\n int is_root_ref(struct ref_store *refs, const char *refname);\n \n+/*\n+ * Pseudorefs are refs that have different semantics compared to\n+ * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+ * but must always be accessed via the filesystem. The following refs\n+ * are pseudorefs:\n+ *\n+ * - FETCH_HEAD may contain multiple object IDs, and each one of them\n+ *   carries additional metadata like where it came from.\n+ *\n+ * - MERGE_HEAD may contain multiple object IDs when merging multiple\n+ *   heads.\n+ *\n+ * Reading, writing or deleting references must consistently go either\n+ * through the filesystem (pseudorefs) or through the reference\n+ * backend (normal ones).\n+ */\n+int is_pseudo_ref(const char *refname);\n+\n #endif /* REFS_H */\n-- \n2.45.0\n\n"},{"id":"494410","messageId":"640d3b169fc6abaaf121b90f43f5f4c4145ec08d.1715330206.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"[PATCH v4 10/10] refs: refuse to write pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-10T08:48:51Z","receivedAt":"2024-05-10T08:48:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Pseudorefs are not stored in the ref database as by definition, they\ncarry additional metadata that essentially makes them not a ref. As\nsuch, writing pseudorefs via the ref backend does not make any sense\nwhatsoever as the ref backend wouldn't know how exactly to store the\ndata.\n\nRestrict writing pseudorefs via the ref backend.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c           | 7 +++++++\n t/t5510-fetch.sh | 6 +++---\n 2 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex c882ece6e7..f2507c5a74 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1285,6 +1285,13 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\treturn -1;\n \t}\n \n+\tif (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&\n+\t    is_pseudo_ref(refname)) {\n+\t\tstrbuf_addf(err, _(\"refusing to update pseudoref '%s'\"),\n+\t\t\t    refname);\n+\t\treturn -1;\n+\t}\n+\n \tif (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)\n \t\tBUG(\"illegal flags 0x%x passed to ref_transaction_update()\", flags);\n \ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 33d34d5ae9..4eb569f4df 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -518,7 +518,7 @@ test_expect_success 'fetch with a non-applying branch.<name>.merge' '\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [1]' '\n \tone_head=$(cd one && git rev-parse HEAD) &&\n \tthis_head=$(git rev-parse HEAD) &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -530,7 +530,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n \tone_ref=$(cd one && git symbolic-ref HEAD) &&\n \tgit config branch.main.remote blub &&\n \tgit config branch.main.merge \"$one_ref\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -540,7 +540,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n # the merge spec does not match the branch the remote HEAD points to\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [3]' '\n \tgit config branch.main.merge \"${one_ref}_not\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n-- \n2.45.0\n\n"},{"id":"494489","messageId":"xmqqpltt4hny.fsf@gitster.g","threadId":"61387","inReplyTo":"cover.1715330206.git.ps@pks.im","subject":"Re: [PATCH v4 00/10] Clarify pseudo-ref terminology","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-10T18:59:29Z","receivedAt":"2024-05-10T18:59:31Z","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>   - Reorder patches 6 and 7 such that we first correct `is_root_ref()`,\n>     and then adapt `is_headref()`.\n>\n>   - Furthermore, I have inlined `is_headref()` into `is_root_ref()`\n>     completely now as it didn't have any users anymore.\n\nThis does look like a good change, relative to the previous\niteration.  The code paths gets greatly simplified.\n\n"},{"id":"494754","messageId":"ZkQ3EiL1OY10Y2JP@tanuki","threadId":"61387","inReplyTo":"20240503181339.GH3631237@coredump.intra.peff.net","subject":"Re: [PATCH v3 07/10] refs: root refs can be symbolic refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T04:16:18Z","receivedAt":"2024-05-15T04:16:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, May 03, 2024 at 02:13:39PM -0400, Jeff King wrote:\n> On Thu, May 02, 2024 at 10:17:42AM +0200, Patrick Steinhardt wrote:\n> \n> > Before this patch series, root refs except for \"HEAD\" and our special\n> > refs were classified as pseudorefs. Furthermore, our terminology\n> > clarified that pseudorefs must not be symbolic refs. This restriction\n> > is enforced in `is_root_ref()`, which explicitly checks that a supposed\n> > root ref resolves to an object ID without recursing.\n> > \n> > This has been extremely confusing right from the start because (in old\n> > terminology) a ref name may sometimes be a pseudoref and sometimes not\n> > depending on whether it is a symbolic or regular ref. This behaviour\n> > does not seem reasonable at all and I very much doubt that it results in\n> > anything sane.\n> > \n> > Furthermore, the behaviour is different to `is_headref()`, which only\n> > checks for the ref to exist. While that is in line with our glossary,\n> > this inconsistency only adds to the confusion.\n> > \n> > Last but not least, the current behaviour can actually lead to a\n> > segfault when calling `is_root_ref()` with a reference that either does\n> > not exist or that is a symbolic ref because we never initialized `oid`.\n> > \n> > Let's loosen the restrictions in accordance to the new definition of\n> > root refs, which are simply plain refs that may as well be a symbolic\n> > ref. Consequently, we can just check for the ref to exist instead of\n> > requiring it to be a regular ref.\n> \n> It's not clear to me that this existence check is particularly useful.\n> Something that fails read_raw_ref() will fail if:\n> \n>   - the file does not exist at all. But then how did somebody find out\n>     about it at all to ask is_pseudoref()?\n> \n>   - it does exist, but does not look like a ref. Is this important? If I\n>     do \"echo foo >.git/CHERRY_PICK_HEAD\", does it become not a root ref\n>     anymore? Or is it a root ref that is broken? I'd have thought the\n>     latter, and the syntax is what distinguishes it.\n> \n> Making the classification purely syntactic based on the name feels\n> simpler to me to reason about. You'll never run into confusing cases\n> where repo state changes how commands may behave.\n\nI certainly agree and have been complaining about that in the past, too.\nI didn't dare to change the semantics this far yet. Let's have a look at\nthe callers:\n\n  - \"ref-filter.c:ref_kind_from_refname()\" uses it to classify refs.\n    It's clear that the intent is to classify based on the ref name,\n    only.\n\n  - \"refs/files_backend.c:add_pseudoref_and_head_entries()\" uses it to\n    determine whether it should add a ref to the root directory. It\n    feels fishy that this uses ref existence checks to do that.\n\n  - \"refs/reftable_backend.c:reftable_ref_iterator_advance()\" uses it to\n    filter root refs. Again, using existence checks is pointless here as\n    the iterator has just surfaced the ref, so it does exist.\n\n  - \"refs.c:is_current_worktree_ref()\" uses it. Fishy as well, as the\n    call to `is_per_worktree_ref()` also only checks for the refname.\n\nSo let's remove these existence checks altogether and make this a check\nthat purely checks semantics.\n\n> And arguably is_pseudoref_syntax() should be taking into account the\n> \"_HEAD\" restriction and special names anyway. It is a bit weird that\n> even if we tighten up the refname checking to use is_pseudoref_syntax(),\n> you'd still be able to \"git update-ref FOO\" but then not see it as a\n> root ref!\n\nTrue, as well. I'm less comfortable with doing that change in this\nseries though as it does impose a major restriction that did not exist\npreviously. We probably want some escape hatches so that it would still\nbe possible to modify those refs when really required, for example to\ndelete such broken refs.\n\nI would thus like to defer this to a follow up patch series, if you\ndon't mind.\n\nPatrick\n"},{"id":"494755","messageId":"ZkQ8mDrBB2eGg8Ns@tanuki","threadId":"61387","inReplyTo":"ZkQ3EiL1OY10Y2JP@tanuki","subject":"Re: [PATCH v3 07/10] refs: root refs can be symbolic refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T04:39:52Z","receivedAt":"2024-05-15T04:39:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 15, 2024 at 06:16:18AM +0200, Patrick Steinhardt wrote:\n> On Fri, May 03, 2024 at 02:13:39PM -0400, Jeff King wrote:\n> > On Thu, May 02, 2024 at 10:17:42AM +0200, Patrick Steinhardt wrote:\n[snip]\n> > And arguably is_pseudoref_syntax() should be taking into account the\n> > \"_HEAD\" restriction and special names anyway. It is a bit weird that\n> > even if we tighten up the refname checking to use is_pseudoref_syntax(),\n> > you'd still be able to \"git update-ref FOO\" but then not see it as a\n> > root ref!\n> \n> True, as well. I'm less comfortable with doing that change in this\n> series though as it does impose a major restriction that did not exist\n> previously. We probably want some escape hatches so that it would still\n> be possible to modify those refs when really required, for example to\n> delete such broken refs.\n> \n> I would thus like to defer this to a follow up patch series, if you\n> don't mind.\n\nArguably, we don't need `is_pseudoref_syntax()` (which is being renamed\nto `is_root_ref_syntax()`) at all anymore after this series lands\nbecause it can be neatly rolled into `is_root_ref()`. The only caller,\n`is_current_worktree_ref()`, should really call `is_roof_ref()` and not\n`is_root_ref_syntax()`.\n\nBut again, I'll defer this to a follow-up patch series.\n\nPatrick\n"},{"id":"494761","messageId":"20240515062023.GB110841@coredump.intra.peff.net","threadId":"61387","inReplyTo":"ZkQ3EiL1OY10Y2JP@tanuki","subject":"Re: [PATCH v3 07/10] refs: root refs can be symbolic refs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-05-15T06:20:23Z","receivedAt":"2024-05-15T06:20:25Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, May 15, 2024 at 06:16:18AM +0200, Patrick Steinhardt wrote:\n\n> > Making the classification purely syntactic based on the name feels\n> > simpler to me to reason about. You'll never run into confusing cases\n> > where repo state changes how commands may behave.\n> \n> I certainly agree and have been complaining about that in the past, too.\n> I didn't dare to change the semantics this far yet. Let's have a look at\n> the callers:\n> \n>   - \"ref-filter.c:ref_kind_from_refname()\" uses it to classify refs.\n>     It's clear that the intent is to classify based on the ref name,\n>     only.\n> \n>   - \"refs/files_backend.c:add_pseudoref_and_head_entries()\" uses it to\n>     determine whether it should add a ref to the root directory. It\n>     feels fishy that this uses ref existence checks to do that.\n> \n>   - \"refs/reftable_backend.c:reftable_ref_iterator_advance()\" uses it to\n>     filter root refs. Again, using existence checks is pointless here as\n>     the iterator has just surfaced the ref, so it does exist.\n> \n>   - \"refs.c:is_current_worktree_ref()\" uses it. Fishy as well, as the\n>     call to `is_per_worktree_ref()` also only checks for the refname.\n> \n> So let's remove these existence checks altogether and make this a check\n> that purely checks semantics.\n\nThanks for doing the digging on the callers. That matches my intuition /\nlight analysis, which is good. ;)\n\n> > And arguably is_pseudoref_syntax() should be taking into account the\n> > \"_HEAD\" restriction and special names anyway. It is a bit weird that\n> > even if we tighten up the refname checking to use is_pseudoref_syntax(),\n> > you'd still be able to \"git update-ref FOO\" but then not see it as a\n> > root ref!\n> \n> True, as well. I'm less comfortable with doing that change in this\n> series though as it does impose a major restriction that did not exist\n> previously. We probably want some escape hatches so that it would still\n> be possible to modify those refs when really required, for example to\n> delete such broken refs.\n> \n> I would thus like to defer this to a follow up patch series, if you\n> don't mind.\n\nI don't mind deferring. I thought it might make the simplifications\nyou're doing in this series easier to reason about. But TBH I haven't\nhad the chance to look through your series very carefully yet (and I'm\nstill a bit back-logged), so I'm happy to go with your judgement on how\nto split it up.\n\n-Peff\n"},{"id":"494762","messageId":"20240515062220.GC110841@coredump.intra.peff.net","threadId":"61387","inReplyTo":"ZkQ8mDrBB2eGg8Ns@tanuki","subject":"Re: [PATCH v3 07/10] refs: root refs can be symbolic refs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-05-15T06:22:20Z","receivedAt":"2024-05-15T06:22:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, May 15, 2024 at 06:39:52AM +0200, Patrick Steinhardt wrote:\n\n> On Wed, May 15, 2024 at 06:16:18AM +0200, Patrick Steinhardt wrote:\n> > On Fri, May 03, 2024 at 02:13:39PM -0400, Jeff King wrote:\n> > > On Thu, May 02, 2024 at 10:17:42AM +0200, Patrick Steinhardt wrote:\n> [snip]\n> > > And arguably is_pseudoref_syntax() should be taking into account the\n> > > \"_HEAD\" restriction and special names anyway. It is a bit weird that\n> > > even if we tighten up the refname checking to use is_pseudoref_syntax(),\n> > > you'd still be able to \"git update-ref FOO\" but then not see it as a\n> > > root ref!\n> > \n> > True, as well. I'm less comfortable with doing that change in this\n> > series though as it does impose a major restriction that did not exist\n> > previously. We probably want some escape hatches so that it would still\n> > be possible to modify those refs when really required, for example to\n> > delete such broken refs.\n> > \n> > I would thus like to defer this to a follow up patch series, if you\n> > don't mind.\n> \n> Arguably, we don't need `is_pseudoref_syntax()` (which is being renamed\n> to `is_root_ref_syntax()`) at all anymore after this series lands\n> because it can be neatly rolled into `is_root_ref()`. The only caller,\n> `is_current_worktree_ref()`, should really call `is_roof_ref()` and not\n> `is_root_ref_syntax()`.\n\nYeah, and I'd expect that the more-strict check_refname_format() that I\nproposed elsewhere would be in the same boat. The only reason I used the\n\"_syntax()\" variant is that it was obviously wrong to do existence\nchecks there. Once those are gone, then naturally it should be able to\nrely on is_root_ref() itself.\n\n-Peff\n"},{"id":"494764","messageId":"ZkRXyH0dX3At3vDm@tanuki","threadId":"61387","inReplyTo":"20240515062220.GC110841@coredump.intra.peff.net","subject":"Re: [PATCH v3 07/10] refs: root refs can be symbolic refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:35:52Z","receivedAt":"2024-05-15T06:35:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 15, 2024 at 02:22:20AM -0400, Jeff King wrote:\n> On Wed, May 15, 2024 at 06:39:52AM +0200, Patrick Steinhardt wrote:\n> \n> > On Wed, May 15, 2024 at 06:16:18AM +0200, Patrick Steinhardt wrote:\n> > > On Fri, May 03, 2024 at 02:13:39PM -0400, Jeff King wrote:\n> > > > On Thu, May 02, 2024 at 10:17:42AM +0200, Patrick Steinhardt wrote:\n> > [snip]\n> > > > And arguably is_pseudoref_syntax() should be taking into account the\n> > > > \"_HEAD\" restriction and special names anyway. It is a bit weird that\n> > > > even if we tighten up the refname checking to use is_pseudoref_syntax(),\n> > > > you'd still be able to \"git update-ref FOO\" but then not see it as a\n> > > > root ref!\n> > > \n> > > True, as well. I'm less comfortable with doing that change in this\n> > > series though as it does impose a major restriction that did not exist\n> > > previously. We probably want some escape hatches so that it would still\n> > > be possible to modify those refs when really required, for example to\n> > > delete such broken refs.\n> > > \n> > > I would thus like to defer this to a follow up patch series, if you\n> > > don't mind.\n> > \n> > Arguably, we don't need `is_pseudoref_syntax()` (which is being renamed\n> > to `is_root_ref_syntax()`) at all anymore after this series lands\n> > because it can be neatly rolled into `is_root_ref()`. The only caller,\n> > `is_current_worktree_ref()`, should really call `is_roof_ref()` and not\n> > `is_root_ref_syntax()`.\n> \n> Yeah, and I'd expect that the more-strict check_refname_format() that I\n> proposed elsewhere would be in the same boat. The only reason I used the\n> \"_syntax()\" variant is that it was obviously wrong to do existence\n> checks there. Once those are gone, then naturally it should be able to\n> rely on is_root_ref() itself.\n\nThis series hasn't been queued/merged yet, right? Do you plan to reroll\nit? I think that the changes in there are a good complementary addition\nto the clarifications in my patch series.\n\nPatrick\n"},{"id":"494788","messageId":"20240515064912.GF110841@coredump.intra.peff.net","threadId":"61387","inReplyTo":"ZkRXyH0dX3At3vDm@tanuki","subject":"Re: [PATCH v3 07/10] refs: root refs can be symbolic refs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-05-15T06:49:12Z","receivedAt":"2024-05-15T06:49:13Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, May 15, 2024 at 08:35:52AM +0200, Patrick Steinhardt wrote:\n\n> > Yeah, and I'd expect that the more-strict check_refname_format() that I\n> > proposed elsewhere would be in the same boat. The only reason I used the\n> > \"_syntax()\" variant is that it was obviously wrong to do existence\n> > checks there. Once those are gone, then naturally it should be able to\n> > rely on is_root_ref() itself.\n> \n> This series hasn't been queued/merged yet, right? Do you plan to reroll\n> it? I think that the changes in there are a good complementary addition\n> to the clarifications in my patch series.\n\nCorrect, I don't think Junio picked it up. It needed a re-roll anyway,\nso I'd plan to do it on top of your patches, assuming they are on track\nto get merged (and it sounds like there are no real objections).\n\n-Peff\n"},{"id":"494789","messageId":"cover.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1714398019.git.ps@pks.im","subject":"[PATCH v5 00/10] Clarify pseudo-ref terminology","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:50:17Z","receivedAt":"2024-05-15T06:50:23Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the fifth version of my patch series that aims to clarify the\npseudo-ref terminology. Changes compared to v4:\n\n  - Dropped a now-unneeded comment in `is_head_ref_syntax()` which\n    claims that \"HEAD is not a pseudoref\". Due to the rename of the\n    function this comment no longer applies.\n\n  - Adapted `is_root_ref()` so that it does not check the ref for\n    existence at all anymore, as proposed by Peff. This makes the\n    function's behaviour way less confusing overall. I also added some\n    explanations to the commit message to explain why this is okay to\n    do.\n\n  - Adapted the commit message of `is_headref()` to explain some of the\n    subtleties that result from the removed ref existence check.\n\nThanks!\n\nPatrick Steinhardt (10):\n  Documentation/glossary: redefine pseudorefs as special refs\n  Documentation/glossary: clarify limitations of pseudorefs\n  Documentation/glossary: define root refs as refs\n  refs: rename `is_pseudoref()` to `is_root_ref()`\n  refs: rename `is_special_ref()` to `is_pseudo_ref()`\n  refs: do not check ref existence in `is_root_ref()`\n  refs: classify HEAD as a root ref\n  refs: pseudorefs are no refs\n  ref-filter: properly distinuish pseudo and root refs\n  refs: refuse to write pseudorefs\n\n Documentation/glossary-content.txt | 72 +++++++++++-----------\n builtin/for-each-ref.c             |  2 +-\n ref-filter.c                       | 16 ++---\n ref-filter.h                       |  4 +-\n refs.c                             | 98 +++++++++++-------------------\n refs.h                             | 48 ++++++++++++++-\n refs/files-backend.c               |  3 +-\n refs/reftable-backend.c            |  3 +-\n t/t5510-fetch.sh                   |  6 +-\n t/t6302-for-each-ref-filter.sh     | 34 +++++++++++\n 10 files changed, 169 insertions(+), 117 deletions(-)\n\nRange-diff against v4:\n 1:  b1fc4c1ac7 =  1:  1f2445b95b Documentation/glossary: redefine pseudorefs as special refs\n 2:  dce3a0fa7e =  2:  d328081c52 Documentation/glossary: clarify limitations of pseudorefs\n 3:  79249962f5 =  3:  0d185e6479 Documentation/glossary: define root refs as refs\n 4:  ee2b090f75 !  4:  33b221b248 refs: rename `is_pseudoref()` to `is_root_ref()`\n    @@ refs.c: int is_per_worktree_ref(const char *refname)\n      \tconst char *c;\n      \n     @@ refs.c: static int is_pseudoref_syntax(const char *refname)\n    + \t\t\treturn 0;\n    + \t}\n    + \n    +-\t/*\n    +-\t * HEAD is not a pseudoref, but it certainly uses the\n    +-\t * pseudoref syntax.\n    +-\t */\n      \treturn 1;\n      }\n      \n 5:  2c09bc7690 !  5:  9087696d82 refs: refname `is_special_ref()` to `is_pseudo_ref()`\n    @@ Metadata\n     Author: Patrick Steinhardt <ps@pks.im>\n     \n      ## Commit message ##\n    -    refs: refname `is_special_ref()` to `is_pseudo_ref()`\n    +    refs: rename `is_special_ref()` to `is_pseudo_ref()`\n     \n         Rename `is_special_ref()` to `is_pseudo_ref()` to adapt to the newly\n         defined terminology in our gitglossary(7). Note that in the preceding\n 6:  5e402811a6 !  6:  af22581c22 refs: root refs can be symbolic refs\n    @@ Metadata\n     Author: Patrick Steinhardt <ps@pks.im>\n     \n      ## Commit message ##\n    -    refs: root refs can be symbolic refs\n    +    refs: do not check ref existence in `is_root_ref()`\n     \n         Before this patch series, root refs except for \"HEAD\" and our special\n         refs were classified as pseudorefs. Furthermore, our terminology\n    @@ Commit message\n     \n         Last but not least, the current behaviour can actually lead to a\n         segfault when calling `is_root_ref()` with a reference that either does\n    -    not exist or that is a symbolic ref because we never initialized `oid`.\n    +    not exist or that is a symbolic ref because we never initialized `oid`,\n    +    but then read it via `is_null_oid()`.\n     \n    -    Let's loosen the restrictions in accordance to the new definition of\n    -    root refs, which are simply plain refs that may as well be a symbolic\n    -    ref. Consequently, we can just check for the ref to exist instead of\n    -    requiring it to be a regular ref.\n    +    We have now changed terminology to clarify that pseudorefs are really\n    +    only \"MERGE_HEAD\" and \"FETCH_HEAD\", whereas all the other refs that live\n    +    in the root of the ref hierarchy are just plain refs. Thus, we do not\n    +    need to check whether the ref is symbolic or not. In fact, we can now\n    +    avoid looking up the ref completely as the name is sufficient for us to\n    +    figure out whether something would be a root ref or not.\n    +\n    +    This change of course changes semantics for our callers. As there are\n    +    only three of them we can assess each of them individually:\n    +\n    +      - \"ref-filter.c:ref_kind_from_refname()\" uses it to classify refs.\n    +        It's clear that the intent is to classify based on the ref name,\n    +        only.\n    +\n    +      - \"refs/reftable_backend.c:reftable_ref_iterator_advance()\" uses it to\n    +        filter root refs. Again, using existence checks is pointless here as\n    +        the iterator has just surfaced the ref, so we know it does exist.\n    +\n    +      - \"refs/files_backend.c:add_pseudoref_and_head_entries()\" uses it to\n    +        determine whether it should add a ref to the root directory of its\n    +        iterator. This had the effect that we skipped over any files that\n    +        are either a symbolic ref, or which are not a ref at all.\n    +\n    +        The new behaviour is to include symbolic refs know, which aligns us\n    +        with the adapted terminology. Furthermore, files which look like\n    +        root refs but aren't are now mark those as \"broken\". As broken refs\n    +        are not surfaced by our tooling, this should not lead to a change in\n    +        user-visible behaviour, but may cause us to emit warnings. This\n    +        feels like the right thing to do as we would otherwise just silently\n    +        ignore corrupted root refs completely.\n    +\n    +    So in all cases the existence check was either superfluous, not in line\n    +    with the adapted terminology or masked potential issues. This commit\n    +    thus changes the behaviour as proposed and drops the existence check\n    +    altogether.\n     \n         Add a test that verifies that this does not change user-visible\n         behaviour. Namely, we still don't want to show broken refs to the user\n    @@ Commit message\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n    + ## ref-filter.c ##\n    +@@ ref-filter.c: static int ref_kind_from_refname(const char *refname)\n    + \t\t\treturn ref_kind[i].kind;\n    + \t}\n    + \n    +-\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n    ++\tif (is_root_ref(refname))\n    + \t\treturn FILTER_REFS_PSEUDOREFS;\n    + \n    + \treturn FILTER_REFS_OTHERS;\n    +\n      ## refs.c ##\n    +@@ refs.c: static int is_root_ref_syntax(const char *refname)\n    + \treturn 1;\n    + }\n    + \n    +-int is_root_ref(struct ref_store *refs, const char *refname)\n    ++int is_root_ref(const char *refname)\n    + {\n    + \tstatic const char *const irregular_root_refs[] = {\n    + \t\t\"AUTO_MERGE\",\n     @@ refs.c: int is_root_ref(struct ref_store *refs, const char *refname)\n      \t\t\"NOTES_MERGE_REF\",\n      \t\t\"MERGE_AUTOSTASH\",\n      \t};\n     -\tstruct object_id oid;\n    -+\tstruct strbuf referent = STRBUF_INIT;\n    -+\tstruct object_id oid = { 0 };\n    -+\tint failure_errno, ret = 0;\n    -+\tunsigned int flags;\n      \tsize_t i;\n      \n      \tif (!is_root_ref_syntax(refname))\n      \t\treturn 0;\n      \n    -+\t/*\n    -+\t * Note that we cannot use `refs_ref_exists()` here because that also\n    -+\t * checks whether its target ref exists in case refname is a symbolic\n    -+\t * ref.\n    -+\t */\n    - \tif (ends_with(refname, \"_HEAD\")) {\n    +-\tif (ends_with(refname, \"_HEAD\")) {\n     -\t\trefs_resolve_ref_unsafe(refs, refname,\n     -\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n     -\t\t\t\t\t&oid, NULL);\n     -\t\treturn !is_null_oid(&oid);\n    -+\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n    -+\t\t\t\t\t &flags, &failure_errno);\n    -+\t\tgoto done;\n    - \t}\n    +-\t}\n    ++\tif (ends_with(refname, \"_HEAD\"))\n    ++\t\treturn 1;\n      \n    --\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n    -+\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++) {\n    - \t\tif (!strcmp(refname, irregular_root_refs[i])) {\n    + \tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n    +-\t\tif (!strcmp(refname, irregular_root_refs[i])) {\n     -\t\t\trefs_resolve_ref_unsafe(refs, refname,\n     -\t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n     -\t\t\t\t\t\t&oid, NULL);\n     -\t\t\treturn !is_null_oid(&oid);\n    -+\t\t\tret = !refs_read_raw_ref(refs, refname, &oid, &referent,\n    -+\t\t\t\t\t\t &flags, &failure_errno);\n    -+\t\t\tgoto done;\n    - \t\t}\n    -+\t}\n    +-\t\t}\n    ++\t\tif (!strcmp(refname, irregular_root_refs[i]))\n    ++\t\t\treturn 1;\n      \n    --\treturn 0;\n    -+done:\n    -+\tstrbuf_release(&referent);\n    -+\treturn ret;\n    + \treturn 0;\n      }\n    - \n    - int is_headref(struct ref_store *refs, const char *refname)\n     \n      ## refs.h ##\n     @@ refs.h: extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];\n    @@ refs.h: extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];\n      \n      /*\n     - * Check whether the reference is an existing root reference.\n    -+ * Check whether the reference is an existing root reference. A root reference\n    -+ * that is a dangling symbolic ref is considered to exist.\n    ++ * Check whether the provided name names a root reference. This function only\n    ++ * performs a syntactic check.\n       *\n       * A root ref is a reference that lives in the root of the reference hierarchy.\n       * These references must conform to special syntax:\n    +@@ refs.h: void update_ref_namespace(enum ref_namespace namespace, char *ref);\n    +  *\n    +  *   - MERGE_AUTOSTASH\n    +  */\n    +-int is_root_ref(struct ref_store *refs, const char *refname);\n    ++int is_root_ref(const char *refname);\n    + \n    + int is_headref(struct ref_store *refs, const char *refname);\n    + \n    +\n    + ## refs/files-backend.c ##\n    +@@ refs/files-backend.c: static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n    + \t\tstrbuf_addstr(&refname, de->d_name);\n    + \n    + \t\tdtype = get_dtype(de, &path, 1);\n    +-\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n    +-\t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n    ++\t\tif (dtype == DT_REG && (is_root_ref(de->d_name) ||\n    ++\t\t\t\t\tis_headref(ref_store, de->d_name)))\n    + \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n    + \n    + \t\tstrbuf_setlen(&refname, dirnamelen);\n    +\n    + ## refs/reftable-backend.c ##\n    +@@ refs/reftable-backend.c: static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n    + \t\t */\n    + \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n    + \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n    +-\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n    ++\t\t     (is_root_ref(iter->ref.refname) ||\n    + \t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n    + \t\t\tcontinue;\n    + \t\t}\n     \n      ## t/t6302-for-each-ref-filter.sh ##\n     @@ t/t6302-for-each-ref-filter.sh: test_expect_success '--include-root-refs with other patterns' '\n 7:  b32c56afcb !  7:  b719fb7110 refs: classify HEAD as a root ref\n    @@ Commit message\n         Adapt the function to also treat \"HEAD\" as a root ref. This change is\n         safe to do for all current callers:\n     \n    -    - `ref_kind_from_refname()` already handles \"HEAD\" explicitly before\n    -      calling `is_root_ref()`.\n    +      - `ref_kind_from_refname()` already handles \"HEAD\" explicitly before\n    +        calling `is_root_ref()`.\n     \n    -    - The \"files\" and \"reftable\" backends explicitly called both\n    -      `is_root_ref()` and `is_headref()`.\n    +      - The \"files\" and \"reftable\" backends explicitly call both\n    +        `is_root_ref()` and `is_headref()` together.\n     \n         This also aligns behaviour or `is_root_ref()` and `is_headref()` such\n    -    that we also return a trueish value when the ref is a dangling symbolic\n    -    ref. As there are no callers of `is_headref()` left afer the refactoring\n    -    we absorb it completely into `is_root_ref()`.\n    +    that we stop checking for ref existence. This changes semantics for our\n    +    backends:\n    +\n    +      - In the reftable backend we already know that the ref must exist\n    +        because `is_headref()` is called as part of the ref iterator. The\n    +        existence check is thus redundant, and the change is safe to do.\n    +\n    +      - In the files backend we use it when populating root refs, where we\n    +        would skip adding the \"HEAD\" file if it was not possible to resolve\n    +        it. The new behaviour is to instead mark \"HEAD\" as broken, which\n    +        will cause us to emit warnings in various places.\n    +\n    +    As there are no callers of `is_headref()` left afer the refactoring, we\n    +    can absorb it completely into `is_root_ref()`.\n     \n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## refs.c ##\n     @@ refs.c: static int is_root_ref_syntax(const char *refname)\n    - int is_root_ref(struct ref_store *refs, const char *refname)\n    + int is_root_ref(const char *refname)\n      {\n      \tstatic const char *const irregular_root_refs[] = {\n     +\t\t\"HEAD\",\n      \t\t\"AUTO_MERGE\",\n      \t\t\"BISECT_EXPECTED_REV\",\n      \t\t\"NOTES_MERGE_PARTIAL\",\n    -@@ refs.c: int is_root_ref(struct ref_store *refs, const char *refname)\n    - \treturn ret;\n    +@@ refs.c: int is_root_ref(const char *refname)\n    + \treturn 0;\n      }\n      \n     -int is_headref(struct ref_store *refs, const char *refname)\n    @@ refs.h: void update_ref_namespace(enum ref_namespace namespace, char *ref);\n       *\n     @@ refs.h: void update_ref_namespace(enum ref_namespace namespace, char *ref);\n       */\n    - int is_root_ref(struct ref_store *refs, const char *refname);\n    + int is_root_ref(const char *refname);\n      \n     -int is_headref(struct ref_store *refs, const char *refname);\n     -\n    @@ refs/files-backend.c: static void add_pseudoref_and_head_entries(struct ref_stor\n      \t\tstrbuf_addstr(&refname, de->d_name);\n      \n      \t\tdtype = get_dtype(de, &path, 1);\n    --\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n    --\t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n    -+\t\tif (dtype == DT_REG && is_root_ref(ref_store, de->d_name))\n    +-\t\tif (dtype == DT_REG && (is_root_ref(de->d_name) ||\n    +-\t\t\t\t\tis_headref(ref_store, de->d_name)))\n    ++\t\tif (dtype == DT_REG && is_root_ref(de->d_name))\n      \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n      \n      \t\tstrbuf_setlen(&refname, dirnamelen);\n    @@ refs/reftable-backend.c: static int reftable_ref_iterator_advance(struct ref_ite\n      \t\t */\n      \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n      \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n    --\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n    +-\t\t     (is_root_ref(iter->ref.refname) ||\n     -\t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n    -+\t\t      is_root_ref(&iter->refs->base, iter->ref.refname))) {\n    ++\t\t      is_root_ref(iter->ref.refname))) {\n      \t\t\tcontinue;\n      \t\t}\n      \n 8:  19af8c754c !  8:  5709d7f780 refs: pseudorefs are no refs\n    @@ refs.c: int is_per_worktree_ref(const char *refname)\n      static int is_root_ref_syntax(const char *refname)\n      {\n      \tconst char *c;\n    -@@ refs.c: int is_root_ref(struct ref_store *refs, const char *refname)\n    - \tunsigned int flags;\n    +@@ refs.c: int is_root_ref(const char *refname)\n    + \t};\n      \tsize_t i;\n      \n     -\tif (!is_root_ref_syntax(refname))\n    @@ refs.c: int is_root_ref(struct ref_store *refs, const char *refname)\n     +\t    is_pseudo_ref(refname))\n      \t\treturn 0;\n      \n    - \t/*\n    + \tif (ends_with(refname, \"_HEAD\"))\n     @@ refs.c: static int refs_read_special_head(struct ref_store *ref_store,\n      \treturn result;\n      }\n 9:  86f7f2d2d8 !  9:  c7e90e3170 ref-filter: properly distinuish pseudo and root refs\n    @@ ref-filter.c: static int ref_kind_from_refname(const char *refname)\n      \t\t\treturn ref_kind[i].kind;\n      \t}\n      \n    --\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n    +-\tif (is_root_ref(refname))\n     +\tif (is_pseudo_ref(refname))\n      \t\treturn FILTER_REFS_PSEUDOREFS;\n    -+\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n    ++\tif (is_root_ref(refname))\n     +\t\treturn FILTER_REFS_ROOT_REFS;\n      \n      \treturn FILTER_REFS_OTHERS;\n    @@ refs.c: int is_per_worktree_ref(const char *refname)\n      ## refs.h ##\n     @@ refs.h: void update_ref_namespace(enum ref_namespace namespace, char *ref);\n       */\n    - int is_root_ref(struct ref_store *refs, const char *refname);\n    + int is_root_ref(const char *refname);\n      \n     +/*\n     + * Pseudorefs are refs that have different semantics compared to\n10:  640d3b169f = 10:  15595991dc refs: refuse to write pseudorefs\n\nbase-commit: 83f1add914c6b4682de1e944ec0d1ac043d53d78\n-- \n2.45.GIT\n\n"},{"id":"494790","messageId":"1f2445b95b32e70696e07a6e9445b63ce45a85c0.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 01/10] Documentation/glossary: redefine pseudorefs as special refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:50:28Z","receivedAt":"2024-05-15T06:50:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Nowadays, Git knows about three different kinds of refs. As defined in\ngitglossary(7):\n\n  - Regular refs that start with \"refs/\", like \"refs/heads/main\".\n\n  - Pseudorefs, which live in the root directory. These must have\n    all-caps names and must be a file that start with an object hash.\n    Consequently, symbolic refs are not pseudorefs because they do not\n    start with an object hash.\n\n  - Special refs, of which we only have \"FETCH_HEAD\" and \"MERGE_HEAD\".\n\nThis state is extremely confusing, and I would claim that most folks\ndon't fully understand what is what here. The current definitions also\nhave several problems:\n\n  - Where does \"HEAD\" fit in? It's not a pseudoref because it can be\n    a symbolic ref. It's not a regular ref because it does not start\n    with \"refs/\". And it's not a special ref, either.\n\n  - There is a strong overlap between pseudorefs and special refs. The\n    pseudoref section for example mentions \"MERGE_HEAD\", even though it\n    is a special ref. Is it thus both a pseudoref and a special ref?\n\n  - Why do we even need to distinguish refs that live in the root from\n    other refs when they behave just like a regular ref anyway?\n\nIn other words, the current state is quite a mess and leads to wild\ninconsistencies without much of a good reason.\n\nThe original reason why pseudorefs were introduced is that there are\nsome refs that sometimes behave like a ref, even though they aren't a\nref. And we really only have two of these nowadays, namely \"MERGE_HEAD\"\nand \"FETCH_HEAD\". Those files are never written via the ref backends,\nbut are instead written by git-fetch(1), git-pull(1) and git-merge(1).\nThey contain additional metadata that highlights where a ref has been\nfetched from or the list of commits that have been merged.\n\nThis original intent in fact matches the definition of special refs that\nwe have recently introduced in 8df4c5d205 (Documentation: add \"special\nrefs\" to the glossary, 2024-01-19). Due to the introduction of the new\nreftable backend we were forced to distinguish those refs more clearly\nsuch that we don't ever try to read or write them via the reftable\nbackend. In the same series, we also addressed all the other cases where\nwe used to write those special refs via the filesystem directly, thus\ncircumventing the ref backend, to instead write them via the backends.\nConsequently, there are no other refs left anymore which are special.\n\nLet's address this mess and return the pseudoref terminology back to its\noriginal intent: a ref that sometimes behave like a ref, but which isn't\nreally a ref because it gets written to the filesystem directly. Or in\nother words, let's redefine pseudorefs to match the current definition\nof special refs. As special refs and pseudorefs are now the same per\ndefinition, we can drop the \"special refs\" term again. It's not exposed\nto our users and thus they wouldn't ever encounter that term anyway.\n\nRefs that live in the root of the ref hierarchy but which are not\npseudorefs will be further defined in a subsequent commit.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 40 +++++++++---------------------\n 1 file changed, 12 insertions(+), 28 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex d71b199955..e686c83026 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -497,20 +497,18 @@ exclude;;\n \tunusual refs.\n \n [[def_pseudoref]]pseudoref::\n-\tPseudorefs are a class of files under `$GIT_DIR` which behave\n-\tlike refs for the purposes of rev-parse, but which are treated\n-\tspecially by git.  Pseudorefs both have names that are all-caps,\n-\tand always start with a line consisting of a\n-\t<<def_SHA1,SHA-1>> followed by whitespace.  So, HEAD is not a\n-\tpseudoref, because it is sometimes a symbolic ref.  They might\n-\toptionally contain some additional data.  `MERGE_HEAD` and\n-\t`CHERRY_PICK_HEAD` are examples.  Unlike\n-\t<<def_per_worktree_ref,per-worktree refs>>, these files cannot\n-\tbe symbolic refs, and never have reflogs.  They also cannot be\n-\tupdated through the normal ref update machinery.  Instead,\n-\tthey are updated by directly writing to the files.  However,\n-\tthey can be read as if they were refs, so `git rev-parse\n-\tMERGE_HEAD` will work.\n+\tA ref that has different semantics than normal refs. These refs can be\n+\taccessed via normal Git commands but may not behave the same as a\n+\tnormal ref in some cases.\n++\n+The following pseudorefs are known to Git:\n+\n+ - `FETCH_HEAD` is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n+   may refer to multiple object IDs. Each object ID is annotated with metadata\n+   indicating where it was fetched from and its fetch status.\n+\n+ - `MERGE_HEAD` is written by linkgit:git-merge[1] when resolving merge\n+   conflicts. It contains all commit IDs which are being merged.\n \n [[def_pull]]pull::\n \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n@@ -638,20 +636,6 @@ The most notable example is `HEAD`.\n \tAn <<def_object,object>> used to temporarily store the contents of a\n \t<<def_dirty,dirty>> working directory and the index for future reuse.\n \n-[[def_special_ref]]special ref::\n-\tA ref that has different semantics than normal refs. These refs can be\n-\taccessed via normal Git commands but may not behave the same as a\n-\tnormal ref in some cases.\n-+\n-The following special refs are known to Git:\n-\n- - \"`FETCH_HEAD`\" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It\n-   may refer to multiple object IDs. Each object ID is annotated with metadata\n-   indicating where it was fetched from and its fetch status.\n-\n- - \"`MERGE_HEAD`\" is written by linkgit:git-merge[1] when resolving merge\n-   conflicts. It contains all commit IDs which are being merged.\n-\n [[def_submodule]]submodule::\n \tA <<def_repository,repository>> that holds the history of a\n \tseparate project inside another repository (the latter of\n-- \n2.45.GIT\n\n"},{"id":"494791","messageId":"d328081c528e73f6fc179d386d829f130bf46a6c.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 02/10] Documentation/glossary: clarify limitations of pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:50:33Z","receivedAt":"2024-05-15T06:50:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Clarify limitations that pseudorefs have:\n\n  - They can be read via git-rev-parse(1) and similar tools.\n\n  - They are not surfaced when iterating through refs, like when using\n    git-for-each-ref(1). They are not refs, so iterating through refs\n    should not surface them.\n\n  - They cannot be written via git-update-ref(1) and related commands.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex e686c83026..d8c04b37be 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -498,8 +498,8 @@ exclude;;\n \n [[def_pseudoref]]pseudoref::\n \tA ref that has different semantics than normal refs. These refs can be\n-\taccessed via normal Git commands but may not behave the same as a\n-\tnormal ref in some cases.\n+\tread via normal Git commands, but cannot be written to by commands like\n+\tlinkgit:git-update-ref[1].\n +\n The following pseudorefs are known to Git:\n \n-- \n2.45.GIT\n\n"},{"id":"494792","messageId":"0d185e6479394025c31d2869c144a667df3f4301.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 03/10] Documentation/glossary: define root refs as refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:50:37Z","receivedAt":"2024-05-15T06:50:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Except for the pseudorefs MERGE_HEAD and FETCH_HEAD, all refs that live\nin the root of the ref hierarchy behave the exact same as normal refs.\nThey can be symbolic refs or direct refs and can be read, iterated over\nand written via normal tooling. All of these refs are stored in the ref\nbackends, which further demonstrates that they are just normal refs.\n\nExtend the definition of \"ref\" to also cover such root refs. The only\nadditional restriction for root refs is that they must conform to a\nspecific naming schema.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/glossary-content.txt | 32 +++++++++++++++++++++++-------\n 1 file changed, 25 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt\nindex d8c04b37be..c434387186 100644\n--- a/Documentation/glossary-content.txt\n+++ b/Documentation/glossary-content.txt\n@@ -550,20 +550,38 @@ The following pseudorefs are known to Git:\n \tto the result.\n \n [[def_ref]]ref::\n-\tA name that begins with `refs/` (e.g. `refs/heads/master`)\n-\tthat points to an <<def_object_name,object name>> or another\n-\tref (the latter is called a <<def_symref,symbolic ref>>).\n+\tA name that that points to an <<def_object_name,object name>> or\n+\tanother ref (the latter is called a <<def_symref,symbolic ref>>).\n \tFor convenience, a ref can sometimes be abbreviated when used\n \tas an argument to a Git command; see linkgit:gitrevisions[7]\n \tfor details.\n \tRefs are stored in the <<def_repository,repository>>.\n +\n The ref namespace is hierarchical.\n-Different subhierarchies are used for different purposes (e.g. the\n-`refs/heads/` hierarchy is used to represent local branches).\n+Ref names must either start with `refs/` or be located in the root of\n+the hierarchy. For the latter, their name must follow these rules:\n +\n-There are a few special-purpose refs that do not begin with `refs/`.\n-The most notable example is `HEAD`.\n+ - The name consists of only upper-case characters or underscores.\n+\n+ - The name ends with \"`_HEAD`\" or is equal to \"`HEAD`\".\n++\n+There are some irregular refs in the root of the hierarchy that do not\n+match these rules. The following list is exhaustive and shall not be\n+extended in the future:\n++\n+ - `AUTO_MERGE`\n+\n+ - `BISECT_EXPECTED_REV`\n+\n+ - `NOTES_MERGE_PARTIAL`\n+\n+ - `NOTES_MERGE_REF`\n+\n+ - `MERGE_AUTOSTASH`\n++\n+Different subhierarchies are used for different purposes. For example,\n+the `refs/heads/` hierarchy is used to represent local branches whereas\n+the `refs/tags/` hierarchy is used to represent local tags..\n \n [[def_reflog]]reflog::\n \tA reflog shows the local \"history\" of a ref.  In other words,\n-- \n2.45.GIT\n\n"},{"id":"494793","messageId":"33b221b248edbf18ee36a867e96a13d01e713846.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 04/10] refs: rename `is_pseudoref()` to `is_root_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:50:42Z","receivedAt":"2024-05-15T06:50:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename `is_pseudoref()` to `is_root_ref()` to adapt to the newly defined\nterminology in our gitglossary(7).\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c            |  2 +-\n refs.c                  | 18 +++++++-----------\n refs.h                  | 28 +++++++++++++++++++++++++++-\n refs/files-backend.c    |  2 +-\n refs/reftable-backend.c |  2 +-\n 5 files changed, 37 insertions(+), 15 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 59ad6f54dd..361beb6619 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2756,7 +2756,7 @@ static int ref_kind_from_refname(const char *refname)\n \t\t\treturn ref_kind[i].kind;\n \t}\n \n-\tif (is_pseudoref(get_main_ref_store(the_repository), refname))\n+\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n \t\treturn FILTER_REFS_PSEUDOREFS;\n \n \treturn FILTER_REFS_OTHERS;\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..434c4da7ce 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,7 +844,7 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n-static int is_pseudoref_syntax(const char *refname)\n+static int is_root_ref_syntax(const char *refname)\n {\n \tconst char *c;\n \n@@ -853,16 +853,12 @@ static int is_pseudoref_syntax(const char *refname)\n \t\t\treturn 0;\n \t}\n \n-\t/*\n-\t * HEAD is not a pseudoref, but it certainly uses the\n-\t * pseudoref syntax.\n-\t */\n \treturn 1;\n }\n \n-int is_pseudoref(struct ref_store *refs, const char *refname)\n+int is_root_ref(struct ref_store *refs, const char *refname)\n {\n-\tstatic const char *const irregular_pseudorefs[] = {\n+\tstatic const char *const irregular_root_refs[] = {\n \t\t\"AUTO_MERGE\",\n \t\t\"BISECT_EXPECTED_REV\",\n \t\t\"NOTES_MERGE_PARTIAL\",\n@@ -872,7 +868,7 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \tstruct object_id oid;\n \tsize_t i;\n \n-\tif (!is_pseudoref_syntax(refname))\n+\tif (!is_root_ref_syntax(refname))\n \t\treturn 0;\n \n \tif (ends_with(refname, \"_HEAD\")) {\n@@ -882,8 +878,8 @@ int is_pseudoref(struct ref_store *refs, const char *refname)\n \t\treturn !is_null_oid(&oid);\n \t}\n \n-\tfor (i = 0; i < ARRAY_SIZE(irregular_pseudorefs); i++)\n-\t\tif (!strcmp(refname, irregular_pseudorefs[i])) {\n+\tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n+\t\tif (!strcmp(refname, irregular_root_refs[i])) {\n \t\t\trefs_resolve_ref_unsafe(refs, refname,\n \t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n \t\t\t\t\t\t&oid, NULL);\n@@ -902,7 +898,7 @@ int is_headref(struct ref_store *refs, const char *refname)\n }\n \n static int is_current_worktree_ref(const char *ref) {\n-\treturn is_pseudoref_syntax(ref) || is_per_worktree_ref(ref);\n+\treturn is_root_ref_syntax(ref) || is_per_worktree_ref(ref);\n }\n \n enum ref_worktree_type parse_worktree_ref(const char *maybe_worktree_ref,\ndiff --git a/refs.h b/refs.h\nindex d278775e08..d0374c3275 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1051,7 +1051,33 @@ extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];\n  */\n void update_ref_namespace(enum ref_namespace namespace, char *ref);\n \n-int is_pseudoref(struct ref_store *refs, const char *refname);\n+/*\n+ * Check whether the reference is an existing root reference.\n+ *\n+ * A root ref is a reference that lives in the root of the reference hierarchy.\n+ * These references must conform to special syntax:\n+ *\n+ *   - Their name must be all-uppercase or underscores (\"_\").\n+ *\n+ *   - Their name must end with \"_HEAD\".\n+ *\n+ *   - Their name may not contain a slash.\n+ *\n+ * There is a special set of irregular root refs that exist due to historic\n+ * reasons, only. This list shall not be expanded in the future:\n+ *\n+ *   - AUTO_MERGE\n+ *\n+ *   - BISECT_EXPECTED_REV\n+ *\n+ *   - NOTES_MERGE_PARTIAL\n+ *\n+ *   - NOTES_MERGE_REF\n+ *\n+ *   - MERGE_AUTOSTASH\n+ */\n+int is_root_ref(struct ref_store *refs, const char *refname);\n+\n int is_headref(struct ref_store *refs, const char *refname);\n \n #endif /* REFS_H */\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..0fcb601444 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -351,7 +351,7 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n \t\tstrbuf_addstr(&refname, de->d_name);\n \n \t\tdtype = get_dtype(de, &path, 1);\n-\t\tif (dtype == DT_REG && (is_pseudoref(ref_store, de->d_name) ||\n+\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n \t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 010ef811b6..36ab3357a7 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -354,7 +354,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n-\t\t     (is_pseudoref(&iter->refs->base, iter->ref.refname) ||\n+\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n \t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n \t\t\tcontinue;\n \t\t}\n-- \n2.45.GIT\n\n"},{"id":"494794","messageId":"9087696d8206c9a099463c27c9274b7bab2b86dc.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 05/10] refs: rename `is_special_ref()` to `is_pseudo_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:50:47Z","receivedAt":"2024-05-15T06:50:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Rename `is_special_ref()` to `is_pseudo_ref()` to adapt to the newly\ndefined terminology in our gitglossary(7). Note that in the preceding\ncommit we have just renamed `is_pseudoref()` to `is_root_ref()`, where\nthere may be confusion for in-flight patch series that add new calls to\n`is_pseudoref()`. In order to intentionally break such patch series we\nhave thus picked `is_pseudo_ref()` instead of `is_pseudoref()` as the\nnew name.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 434c4da7ce..c1c406fc5f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1872,13 +1872,13 @@ static int refs_read_special_head(struct ref_store *ref_store,\n \treturn result;\n }\n \n-static int is_special_ref(const char *refname)\n+static int is_pseudo_ref(const char *refname)\n {\n \t/*\n-\t * Special references are refs that have different semantics compared\n-\t * to \"normal\" refs. These refs can thus not be stored in the ref\n-\t * backend, but must always be accessed via the filesystem. The\n-\t * following refs are special:\n+\t * Pseudorefs are refs that have different semantics compared to\n+\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+\t * but must always be accessed via the filesystem. The following refs\n+\t * are pseudorefs:\n \t *\n \t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n \t *   carries additional metadata like where it came from.\n@@ -1887,17 +1887,17 @@ static int is_special_ref(const char *refname)\n \t *   heads.\n \t *\n \t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (special refs) or through the reference\n+\t * through the filesystem (pseudorefs) or through the reference\n \t * backend (normal ones).\n \t */\n-\tstatic const char * const special_refs[] = {\n+\tstatic const char * const pseudo_refs[] = {\n \t\t\"FETCH_HEAD\",\n \t\t\"MERGE_HEAD\",\n \t};\n \tsize_t i;\n \n-\tfor (i = 0; i < ARRAY_SIZE(special_refs); i++)\n-\t\tif (!strcmp(refname, special_refs[i]))\n+\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n+\t\tif (!strcmp(refname, pseudo_refs[i]))\n \t\t\treturn 1;\n \n \treturn 0;\n@@ -1908,7 +1908,7 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,\n \t\t      unsigned int *type, int *failure_errno)\n {\n \tassert(failure_errno);\n-\tif (is_special_ref(refname))\n+\tif (is_pseudo_ref(refname))\n \t\treturn refs_read_special_head(ref_store, refname, oid, referent,\n \t\t\t\t\t      type, failure_errno);\n \n-- \n2.45.GIT\n\n"},{"id":"494795","messageId":"af22581c2212088ea6a380cc1a58923abfdc4fe1.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 06/10] refs: do not check ref existence in `is_root_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:50:51Z","receivedAt":"2024-05-15T06:50:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before this patch series, root refs except for \"HEAD\" and our special\nrefs were classified as pseudorefs. Furthermore, our terminology\nclarified that pseudorefs must not be symbolic refs. This restriction\nis enforced in `is_root_ref()`, which explicitly checks that a supposed\nroot ref resolves to an object ID without recursing.\n\nThis has been extremely confusing right from the start because (in old\nterminology) a ref name may sometimes be a pseudoref and sometimes not\ndepending on whether it is a symbolic or regular ref. This behaviour\ndoes not seem reasonable at all and I very much doubt that it results in\nanything sane.\n\nLast but not least, the current behaviour can actually lead to a\nsegfault when calling `is_root_ref()` with a reference that either does\nnot exist or that is a symbolic ref because we never initialized `oid`,\nbut then read it via `is_null_oid()`.\n\nWe have now changed terminology to clarify that pseudorefs are really\nonly \"MERGE_HEAD\" and \"FETCH_HEAD\", whereas all the other refs that live\nin the root of the ref hierarchy are just plain refs. Thus, we do not\nneed to check whether the ref is symbolic or not. In fact, we can now\navoid looking up the ref completely as the name is sufficient for us to\nfigure out whether something would be a root ref or not.\n\nThis change of course changes semantics for our callers. As there are\nonly three of them we can assess each of them individually:\n\n  - \"ref-filter.c:ref_kind_from_refname()\" uses it to classify refs.\n    It's clear that the intent is to classify based on the ref name,\n    only.\n\n  - \"refs/reftable_backend.c:reftable_ref_iterator_advance()\" uses it to\n    filter root refs. Again, using existence checks is pointless here as\n    the iterator has just surfaced the ref, so we know it does exist.\n\n  - \"refs/files_backend.c:add_pseudoref_and_head_entries()\" uses it to\n    determine whether it should add a ref to the root directory of its\n    iterator. This had the effect that we skipped over any files that\n    are either a symbolic ref, or which are not a ref at all.\n\n    The new behaviour is to include symbolic refs know, which aligns us\n    with the adapted terminology. Furthermore, files which look like\n    root refs but aren't are now mark those as \"broken\". As broken refs\n    are not surfaced by our tooling, this should not lead to a change in\n    user-visible behaviour, but may cause us to emit warnings. This\n    feels like the right thing to do as we would otherwise just silently\n    ignore corrupted root refs completely.\n\nSo in all cases the existence check was either superfluous, not in line\nwith the adapted terminology or masked potential issues. This commit\nthus changes the behaviour as proposed and drops the existence check\naltogether.\n\nAdd a test that verifies that this does not change user-visible\nbehaviour. Namely, we still don't want to show broken refs to the user\nby default in git-for-each-ref(1). What this does allow though is for\ninternal callers to surface dangling root refs when they pass in the\n`DO_FOR_EACH_INCLUDE_BROKEN` flag.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c                   |  2 +-\n refs.c                         | 19 +++++--------------\n refs.h                         |  5 +++--\n refs/files-backend.c           |  4 ++--\n refs/reftable-backend.c        |  2 +-\n t/t6302-for-each-ref-filter.sh | 17 +++++++++++++++++\n 6 files changed, 29 insertions(+), 20 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 361beb6619..23e81e3e04 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2756,7 +2756,7 @@ static int ref_kind_from_refname(const char *refname)\n \t\t\treturn ref_kind[i].kind;\n \t}\n \n-\tif (is_root_ref(get_main_ref_store(the_repository), refname))\n+\tif (is_root_ref(refname))\n \t\treturn FILTER_REFS_PSEUDOREFS;\n \n \treturn FILTER_REFS_OTHERS;\ndiff --git a/refs.c b/refs.c\nindex c1c406fc5f..4fec29e660 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -856,7 +856,7 @@ static int is_root_ref_syntax(const char *refname)\n \treturn 1;\n }\n \n-int is_root_ref(struct ref_store *refs, const char *refname)\n+int is_root_ref(const char *refname)\n {\n \tstatic const char *const irregular_root_refs[] = {\n \t\t\"AUTO_MERGE\",\n@@ -865,26 +865,17 @@ int is_root_ref(struct ref_store *refs, const char *refname)\n \t\t\"NOTES_MERGE_REF\",\n \t\t\"MERGE_AUTOSTASH\",\n \t};\n-\tstruct object_id oid;\n \tsize_t i;\n \n \tif (!is_root_ref_syntax(refname))\n \t\treturn 0;\n \n-\tif (ends_with(refname, \"_HEAD\")) {\n-\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t&oid, NULL);\n-\t\treturn !is_null_oid(&oid);\n-\t}\n+\tif (ends_with(refname, \"_HEAD\"))\n+\t\treturn 1;\n \n \tfor (i = 0; i < ARRAY_SIZE(irregular_root_refs); i++)\n-\t\tif (!strcmp(refname, irregular_root_refs[i])) {\n-\t\t\trefs_resolve_ref_unsafe(refs, refname,\n-\t\t\t\t\t\tRESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,\n-\t\t\t\t\t\t&oid, NULL);\n-\t\t\treturn !is_null_oid(&oid);\n-\t\t}\n+\t\tif (!strcmp(refname, irregular_root_refs[i]))\n+\t\t\treturn 1;\n \n \treturn 0;\n }\ndiff --git a/refs.h b/refs.h\nindex d0374c3275..8a574a22c7 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1052,7 +1052,8 @@ extern struct ref_namespace_info ref_namespace[NAMESPACE__COUNT];\n void update_ref_namespace(enum ref_namespace namespace, char *ref);\n \n /*\n- * Check whether the reference is an existing root reference.\n+ * Check whether the provided name names a root reference. This function only\n+ * performs a syntactic check.\n  *\n  * A root ref is a reference that lives in the root of the reference hierarchy.\n  * These references must conform to special syntax:\n@@ -1076,7 +1077,7 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  *\n  *   - MERGE_AUTOSTASH\n  */\n-int is_root_ref(struct ref_store *refs, const char *refname);\n+int is_root_ref(const char *refname);\n \n int is_headref(struct ref_store *refs, const char *refname);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0fcb601444..06240ce327 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -351,8 +351,8 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n \t\tstrbuf_addstr(&refname, de->d_name);\n \n \t\tdtype = get_dtype(de, &path, 1);\n-\t\tif (dtype == DT_REG && (is_root_ref(ref_store, de->d_name) ||\n-\t\t\t\t\t\t\t\tis_headref(ref_store, de->d_name)))\n+\t\tif (dtype == DT_REG && (is_root_ref(de->d_name) ||\n+\t\t\t\t\tis_headref(ref_store, de->d_name)))\n \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n \n \t\tstrbuf_setlen(&refname, dirnamelen);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 36ab3357a7..bc927ef17b 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -354,7 +354,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n-\t\t     (is_root_ref(&iter->refs->base, iter->ref.refname) ||\n+\t\t     (is_root_ref(iter->ref.refname) ||\n \t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n \t\t\tcontinue;\n \t\t}\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 948f1bb5f4..92ed8957c8 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -62,6 +62,23 @@ test_expect_success '--include-root-refs with other patterns' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--include-root-refs omits dangling symrefs' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit initial &&\n+\t\tgit symbolic-ref DANGLING_HEAD refs/heads/missing &&\n+\t\tcat >expect <<-EOF &&\n+\t\tHEAD\n+\t\t$(git symbolic-ref HEAD)\n+\t\trefs/tags/initial\n+\t\tEOF\n+\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success 'filtering with --points-at' '\n \tcat >expect <<-\\EOF &&\n \trefs/heads/main\n-- \n2.45.GIT\n\n"},{"id":"494796","messageId":"b719fb7110249e88b80e0cd9943a2d864b18d0e0.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 07/10] refs: classify HEAD as a root ref","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:50:56Z","receivedAt":"2024-05-15T06:51:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Root refs are those refs that live in the root of the ref hierarchy.\nOur old and venerable \"HEAD\" reference falls into this category, but we\ndon't yet classify it as such in `is_root_ref()`.\n\nAdapt the function to also treat \"HEAD\" as a root ref. This change is\nsafe to do for all current callers:\n\n  - `ref_kind_from_refname()` already handles \"HEAD\" explicitly before\n    calling `is_root_ref()`.\n\n  - The \"files\" and \"reftable\" backends explicitly call both\n    `is_root_ref()` and `is_headref()` together.\n\nThis also aligns behaviour or `is_root_ref()` and `is_headref()` such\nthat we stop checking for ref existence. This changes semantics for our\nbackends:\n\n  - In the reftable backend we already know that the ref must exist\n    because `is_headref()` is called as part of the ref iterator. The\n    existence check is thus redundant, and the change is safe to do.\n\n  - In the files backend we use it when populating root refs, where we\n    would skip adding the \"HEAD\" file if it was not possible to resolve\n    it. The new behaviour is to instead mark \"HEAD\" as broken, which\n    will cause us to emit warnings in various places.\n\nAs there are no callers of `is_headref()` left afer the refactoring, we\ncan absorb it completely into `is_root_ref()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                  | 9 +--------\n refs.h                  | 5 ++---\n refs/files-backend.c    | 3 +--\n refs/reftable-backend.c | 3 +--\n 4 files changed, 5 insertions(+), 15 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 4fec29e660..9fb1061d52 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -859,6 +859,7 @@ static int is_root_ref_syntax(const char *refname)\n int is_root_ref(const char *refname)\n {\n \tstatic const char *const irregular_root_refs[] = {\n+\t\t\"HEAD\",\n \t\t\"AUTO_MERGE\",\n \t\t\"BISECT_EXPECTED_REV\",\n \t\t\"NOTES_MERGE_PARTIAL\",\n@@ -880,14 +881,6 @@ int is_root_ref(const char *refname)\n \treturn 0;\n }\n \n-int is_headref(struct ref_store *refs, const char *refname)\n-{\n-\tif (!strcmp(refname, \"HEAD\"))\n-\t\treturn refs_ref_exists(refs, refname);\n-\n-\treturn 0;\n-}\n-\n static int is_current_worktree_ref(const char *ref) {\n \treturn is_root_ref_syntax(ref) || is_per_worktree_ref(ref);\n }\ndiff --git a/refs.h b/refs.h\nindex 8a574a22c7..8489b45265 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1060,7 +1060,8 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  *\n  *   - Their name must be all-uppercase or underscores (\"_\").\n  *\n- *   - Their name must end with \"_HEAD\".\n+ *   - Their name must end with \"_HEAD\". As a special rule, \"HEAD\" is a root\n+ *     ref, as well.\n  *\n  *   - Their name may not contain a slash.\n  *\n@@ -1079,6 +1080,4 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  */\n int is_root_ref(const char *refname);\n \n-int is_headref(struct ref_store *refs, const char *refname);\n-\n #endif /* REFS_H */\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 06240ce327..6f9a631592 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -351,8 +351,7 @@ static void add_pseudoref_and_head_entries(struct ref_store *ref_store,\n \t\tstrbuf_addstr(&refname, de->d_name);\n \n \t\tdtype = get_dtype(de, &path, 1);\n-\t\tif (dtype == DT_REG && (is_root_ref(de->d_name) ||\n-\t\t\t\t\tis_headref(ref_store, de->d_name)))\n+\t\tif (dtype == DT_REG && is_root_ref(de->d_name))\n \t\t\tloose_fill_ref_dir_regular_file(refs, refname.buf, dir);\n \n \t\tstrbuf_setlen(&refname, dirnamelen);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex bc927ef17b..821acd461a 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -354,8 +354,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t */\n \t\tif (!starts_with(iter->ref.refname, \"refs/\") &&\n \t\t    !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS &&\n-\t\t     (is_root_ref(iter->ref.refname) ||\n-\t\t      is_headref(&iter->refs->base, iter->ref.refname)))) {\n+\t\t      is_root_ref(iter->ref.refname))) {\n \t\t\tcontinue;\n \t\t}\n \n-- \n2.45.GIT\n\n"},{"id":"494797","messageId":"5709d7f780a990ef28bb1bd7de3d336ea994a8d5.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 08/10] refs: pseudorefs are no refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:51:01Z","receivedAt":"2024-05-15T06:51:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `is_root_ref()` function will happily clarify a pseudoref as a root\nref, even though pseudorefs are no refs. Next to being wrong, it also\nleads to inconsistent behaviour across ref backends: while the \"files\"\nbackend accidentally knows to parse those pseudorefs and thus yields\nthem to the caller, the \"reftable\" backend won't ever see the pseudoref\nat all because they are never stored in the \"reftable\" backend.\n\nFix this issue by filtering out pseudorefs in `is_root_ref()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                         | 65 +++++++++++++++++-----------------\n t/t6302-for-each-ref-filter.sh | 17 +++++++++\n 2 files changed, 50 insertions(+), 32 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 9fb1061d52..2074281a0e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,6 +844,37 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n+static int is_pseudo_ref(const char *refname)\n+{\n+\t/*\n+\t * Pseudorefs are refs that have different semantics compared to\n+\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+\t * but must always be accessed via the filesystem. The following refs\n+\t * are pseudorefs:\n+\t *\n+\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n+\t *   carries additional metadata like where it came from.\n+\t *\n+\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n+\t *   heads.\n+\t *\n+\t * Reading, writing or deleting references must consistently go either\n+\t * through the filesystem (pseudorefs) or through the reference\n+\t * backend (normal ones).\n+\t */\n+\tstatic const char * const pseudo_refs[] = {\n+\t\t\"FETCH_HEAD\",\n+\t\t\"MERGE_HEAD\",\n+\t};\n+\tsize_t i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n+\t\tif (!strcmp(refname, pseudo_refs[i]))\n+\t\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n static int is_root_ref_syntax(const char *refname)\n {\n \tconst char *c;\n@@ -868,7 +899,8 @@ int is_root_ref(const char *refname)\n \t};\n \tsize_t i;\n \n-\tif (!is_root_ref_syntax(refname))\n+\tif (!is_root_ref_syntax(refname) ||\n+\t    is_pseudo_ref(refname))\n \t\treturn 0;\n \n \tif (ends_with(refname, \"_HEAD\"))\n@@ -1856,37 +1888,6 @@ static int refs_read_special_head(struct ref_store *ref_store,\n \treturn result;\n }\n \n-static int is_pseudo_ref(const char *refname)\n-{\n-\t/*\n-\t * Pseudorefs are refs that have different semantics compared to\n-\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n-\t * but must always be accessed via the filesystem. The following refs\n-\t * are pseudorefs:\n-\t *\n-\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n-\t *   carries additional metadata like where it came from.\n-\t *\n-\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n-\t *   heads.\n-\t *\n-\t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (pseudorefs) or through the reference\n-\t * backend (normal ones).\n-\t */\n-\tstatic const char * const pseudo_refs[] = {\n-\t\t\"FETCH_HEAD\",\n-\t\t\"MERGE_HEAD\",\n-\t};\n-\tsize_t i;\n-\n-\tfor (i = 0; i < ARRAY_SIZE(pseudo_refs); i++)\n-\t\tif (!strcmp(refname, pseudo_refs[i]))\n-\t\t\treturn 1;\n-\n-\treturn 0;\n-}\n-\n int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,\n \t\t      struct object_id *oid, struct strbuf *referent,\n \t\t      unsigned int *type, int *failure_errno)\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 92ed8957c8..163c378cfd 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -52,6 +52,23 @@ test_expect_success '--include-root-refs pattern prints pseudorefs' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--include-root-refs pattern does not print special refs' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\ttest_commit initial &&\n+\t\tgit rev-parse HEAD >.git/MERGE_HEAD &&\n+\t\tgit for-each-ref --format=\"%(refname)\" --include-root-refs >actual &&\n+\t\tcat >expect <<-EOF &&\n+\t\tHEAD\n+\t\t$(git symbolic-ref HEAD)\n+\t\trefs/tags/initial\n+\t\tEOF\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success '--include-root-refs with other patterns' '\n \tcat >expect <<-\\EOF &&\n \tHEAD\n-- \n2.45.GIT\n\n"},{"id":"494798","messageId":"c7e90e31706b214cd9b61492487fafe3345a77ee.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 09/10] ref-filter: properly distinuish pseudo and root refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:51:05Z","receivedAt":"2024-05-15T06:51:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The ref-filter interfaces currently define root refs as either a\ndetached HEAD or a pseudo ref. Pseudo refs aren't root refs though, so\nlet's properly distinguish those ref types.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/for-each-ref.c |  2 +-\n ref-filter.c           | 16 +++++++++-------\n ref-filter.h           |  4 ++--\n refs.c                 | 18 +-----------------\n refs.h                 | 18 ++++++++++++++++++\n 5 files changed, 31 insertions(+), 27 deletions(-)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 919282e12a..5517a4a1c0 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -98,7 +98,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (include_root_refs)\n-\t\tflags |= FILTER_REFS_ROOT_REFS;\n+\t\tflags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD;\n \n \tfilter.match_as_path = 1;\n \tfilter_and_format_refs(&filter, flags, sorting, &format);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 23e81e3e04..41f639bc2f 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2628,7 +2628,7 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,\n \t\t\t\t       each_ref_fn cb,\n \t\t\t\t       void *cb_data)\n {\n-\tif (filter->kind == FILTER_REFS_KIND_MASK) {\n+\tif (filter->kind & FILTER_REFS_ROOT_REFS) {\n \t\t/* In this case, we want to print all refs including root refs. */\n \t\treturn refs_for_each_include_root_refs(get_main_ref_store(the_repository),\n \t\t\t\t\t\t       cb, cb_data);\n@@ -2756,8 +2756,10 @@ static int ref_kind_from_refname(const char *refname)\n \t\t\treturn ref_kind[i].kind;\n \t}\n \n-\tif (is_root_ref(refname))\n+\tif (is_pseudo_ref(refname))\n \t\treturn FILTER_REFS_PSEUDOREFS;\n+\tif (is_root_ref(refname))\n+\t\treturn FILTER_REFS_ROOT_REFS;\n \n \treturn FILTER_REFS_OTHERS;\n }\n@@ -2794,11 +2796,11 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const struct\n \t/*\n \t * Generally HEAD refs are printed with special description denoting a rebase,\n \t * detached state and so forth. This is useful when only printing the HEAD ref\n-\t * But when it is being printed along with other pseudorefs, it makes sense to\n-\t * keep the formatting consistent. So we mask the type to act like a pseudoref.\n+\t * But when it is being printed along with other root refs, it makes sense to\n+\t * keep the formatting consistent. So we mask the type to act like a root ref.\n \t */\n-\tif (filter->kind == FILTER_REFS_KIND_MASK && kind == FILTER_REFS_DETACHED_HEAD)\n-\t\tkind = FILTER_REFS_PSEUDOREFS;\n+\tif (filter->kind & FILTER_REFS_ROOT_REFS && kind == FILTER_REFS_DETACHED_HEAD)\n+\t\tkind = FILTER_REFS_ROOT_REFS;\n \telse if (!(kind & filter->kind))\n \t\treturn NULL;\n \n@@ -3072,7 +3074,7 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref\n \t\t * When printing all ref types, HEAD is already included,\n \t\t * so we don't want to print HEAD again.\n \t\t */\n-\t\tif (!ret && (filter->kind != FILTER_REFS_KIND_MASK) &&\n+\t\tif (!ret && !(filter->kind & FILTER_REFS_ROOT_REFS) &&\n \t\t    (filter->kind & FILTER_REFS_DETACHED_HEAD))\n \t\t\thead_ref(fn, cb_data);\n \t}\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 0ca28d2bba..27ae1aa0d1 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -23,9 +23,9 @@\n \t\t\t\t    FILTER_REFS_REMOTES | FILTER_REFS_OTHERS)\n #define FILTER_REFS_DETACHED_HEAD  0x0020\n #define FILTER_REFS_PSEUDOREFS     0x0040\n-#define FILTER_REFS_ROOT_REFS      (FILTER_REFS_DETACHED_HEAD | FILTER_REFS_PSEUDOREFS)\n+#define FILTER_REFS_ROOT_REFS      0x0080\n #define FILTER_REFS_KIND_MASK      (FILTER_REFS_REGULAR | FILTER_REFS_DETACHED_HEAD | \\\n-\t\t\t\t    FILTER_REFS_PSEUDOREFS)\n+\t\t\t\t    FILTER_REFS_PSEUDOREFS | FILTER_REFS_ROOT_REFS)\n \n struct atom_value;\n struct ref_sorting;\ndiff --git a/refs.c b/refs.c\nindex 2074281a0e..c13b8ff6d8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -844,24 +844,8 @@ int is_per_worktree_ref(const char *refname)\n \t       starts_with(refname, \"refs/rewritten/\");\n }\n \n-static int is_pseudo_ref(const char *refname)\n+int is_pseudo_ref(const char *refname)\n {\n-\t/*\n-\t * Pseudorefs are refs that have different semantics compared to\n-\t * \"normal\" refs. These refs can thus not be stored in the ref backend,\n-\t * but must always be accessed via the filesystem. The following refs\n-\t * are pseudorefs:\n-\t *\n-\t * - FETCH_HEAD may contain multiple object IDs, and each one of them\n-\t *   carries additional metadata like where it came from.\n-\t *\n-\t * - MERGE_HEAD may contain multiple object IDs when merging multiple\n-\t *   heads.\n-\t *\n-\t * Reading, writing or deleting references must consistently go either\n-\t * through the filesystem (pseudorefs) or through the reference\n-\t * backend (normal ones).\n-\t */\n \tstatic const char * const pseudo_refs[] = {\n \t\t\"FETCH_HEAD\",\n \t\t\"MERGE_HEAD\",\ndiff --git a/refs.h b/refs.h\nindex 8489b45265..dc4358727f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1080,4 +1080,22 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\n  */\n int is_root_ref(const char *refname);\n \n+/*\n+ * Pseudorefs are refs that have different semantics compared to\n+ * \"normal\" refs. These refs can thus not be stored in the ref backend,\n+ * but must always be accessed via the filesystem. The following refs\n+ * are pseudorefs:\n+ *\n+ * - FETCH_HEAD may contain multiple object IDs, and each one of them\n+ *   carries additional metadata like where it came from.\n+ *\n+ * - MERGE_HEAD may contain multiple object IDs when merging multiple\n+ *   heads.\n+ *\n+ * Reading, writing or deleting references must consistently go either\n+ * through the filesystem (pseudorefs) or through the reference\n+ * backend (normal ones).\n+ */\n+int is_pseudo_ref(const char *refname);\n+\n #endif /* REFS_H */\n-- \n2.45.GIT\n\n"},{"id":"494799","messageId":"15595991dce11b7666d8447691e5977635cef1ca.1715755591.git.ps@pks.im","threadId":"61387","inReplyTo":"cover.1715755591.git.ps@pks.im","subject":"[PATCH v5 10/10] refs: refuse to write pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:51:10Z","receivedAt":"2024-05-15T06:51:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Pseudorefs are not stored in the ref database as by definition, they\ncarry additional metadata that essentially makes them not a ref. As\nsuch, writing pseudorefs via the ref backend does not make any sense\nwhatsoever as the ref backend wouldn't know how exactly to store the\ndata.\n\nRestrict writing pseudorefs via the ref backend.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c           | 7 +++++++\n t/t5510-fetch.sh | 6 +++---\n 2 files changed, 10 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex c13b8ff6d8..9abd9e5b86 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1263,6 +1263,13 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\treturn -1;\n \t}\n \n+\tif (!(flags & REF_SKIP_REFNAME_VERIFICATION) &&\n+\t    is_pseudo_ref(refname)) {\n+\t\tstrbuf_addf(err, _(\"refusing to update pseudoref '%s'\"),\n+\t\t\t    refname);\n+\t\treturn -1;\n+\t}\n+\n \tif (flags & ~REF_TRANSACTION_UPDATE_ALLOWED_FLAGS)\n \t\tBUG(\"illegal flags 0x%x passed to ref_transaction_update()\", flags);\n \ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 9441793d06..2af277be9a 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -518,7 +518,7 @@ test_expect_success 'fetch with a non-applying branch.<name>.merge' '\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [1]' '\n \tone_head=$(cd one && git rev-parse HEAD) &&\n \tthis_head=$(git rev-parse HEAD) &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -530,7 +530,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n \tone_ref=$(cd one && git symbolic-ref HEAD) &&\n \tgit config branch.main.remote blub &&\n \tgit config branch.main.merge \"$one_ref\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n@@ -540,7 +540,7 @@ test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge\n # the merge spec does not match the branch the remote HEAD points to\n test_expect_success 'fetch from GIT URL with a non-applying branch.<name>.merge [3]' '\n \tgit config branch.main.merge \"${one_ref}_not\" &&\n-\tgit update-ref -d FETCH_HEAD &&\n+\trm .git/FETCH_HEAD &&\n \tgit fetch one &&\n \ttest $one_head = \"$(git rev-parse --verify FETCH_HEAD)\" &&\n \ttest $this_head = \"$(git rev-parse --verify HEAD)\"\n-- \n2.45.GIT\n\n"},{"id":"494800","messageId":"ZkRdXC9wv6e4KtAe@tanuki","threadId":"61387","inReplyTo":"20240515064912.GF110841@coredump.intra.peff.net","subject":"Re: [PATCH v3 07/10] refs: root refs can be symbolic refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-15T06:59:40Z","receivedAt":"2024-05-15T06:59:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 15, 2024 at 02:49:12AM -0400, Jeff King wrote:\n> On Wed, May 15, 2024 at 08:35:52AM +0200, Patrick Steinhardt wrote:\n> \n> > > Yeah, and I'd expect that the more-strict check_refname_format() that I\n> > > proposed elsewhere would be in the same boat. The only reason I used the\n> > > \"_syntax()\" variant is that it was obviously wrong to do existence\n> > > checks there. Once those are gone, then naturally it should be able to\n> > > rely on is_root_ref() itself.\n> > \n> > This series hasn't been queued/merged yet, right? Do you plan to reroll\n> > it? I think that the changes in there are a good complementary addition\n> > to the clarifications in my patch series.\n> \n> Correct, I don't think Junio picked it up. It needed a re-roll anyway,\n> so I'd plan to do it on top of your patches, assuming they are on track\n> to get merged (and it sounds like there are no real objections).\n\nThat feels sensible. The series needs another thorough review and an Ack\nby somebody before Junio wants to merge it, but until now I'm not aware\nof any objections, yeah. So it should hopefully land soonish.\n\nPatrick\n"},{"id":"494841","messageId":"vgzwb5xnlvz2gfiqamzrfcjs2xya3zhhoootyzopfpdrjapayq@wfsomyal4cf6","threadId":"61387","inReplyTo":"af22581c2212088ea6a380cc1a58923abfdc4fe1.1715755591.git.ps@pks.im","subject":"Re: [PATCH v5 06/10] refs: do not check ref existence in `is_root_ref()`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2024-05-15T20:38:47Z","receivedAt":"2024-05-15T20:40:16Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 24/05/15 08:50AM, Patrick Steinhardt wrote:\n> Before this patch series, root refs except for \"HEAD\" and our special\n> refs were classified as pseudorefs. Furthermore, our terminology\n> clarified that pseudorefs must not be symbolic refs. This restriction\n> is enforced in `is_root_ref()`, which explicitly checks that a supposed\n> root ref resolves to an object ID without recursing.\n> \n> This has been extremely confusing right from the start because (in old\n> terminology) a ref name may sometimes be a pseudoref and sometimes not\n> depending on whether it is a symbolic or regular ref. This behaviour\n> does not seem reasonable at all and I very much doubt that it results in\n> anything sane.\n> \n> Last but not least, the current behaviour can actually lead to a\n> segfault when calling `is_root_ref()` with a reference that either does\n> not exist or that is a symbolic ref because we never initialized `oid`,\n> but then read it via `is_null_oid()`.\n> \n> We have now changed terminology to clarify that pseudorefs are really\n> only \"MERGE_HEAD\" and \"FETCH_HEAD\", whereas all the other refs that live\n> in the root of the ref hierarchy are just plain refs. Thus, we do not\n> need to check whether the ref is symbolic or not. In fact, we can now\n> avoid looking up the ref completely as the name is sufficient for us to\n> figure out whether something would be a root ref or not.\n> \n> This change of course changes semantics for our callers. As there are\n> only three of them we can assess each of them individually:\n> \n>   - \"ref-filter.c:ref_kind_from_refname()\" uses it to classify refs.\n>     It's clear that the intent is to classify based on the ref name,\n>     only.\n> \n>   - \"refs/reftable_backend.c:reftable_ref_iterator_advance()\" uses it to\n>     filter root refs. Again, using existence checks is pointless here as\n>     the iterator has just surfaced the ref, so we know it does exist.\n> \n>   - \"refs/files_backend.c:add_pseudoref_and_head_entries()\" uses it to\n>     determine whether it should add a ref to the root directory of its\n>     iterator. This had the effect that we skipped over any files that\n>     are either a symbolic ref, or which are not a ref at all.\n> \n>     The new behaviour is to include symbolic refs know, which aligns us\n\ns/know/now/\n\n>     with the adapted terminology. Furthermore, files which look like\n>     root refs but aren't are now mark those as \"broken\". As broken refs\n>     are not surfaced by our tooling, this should not lead to a change in\n>     user-visible behaviour, but may cause us to emit warnings. This\n>     feels like the right thing to do as we would otherwise just silently\n>     ignore corrupted root refs completely.\n\nIs there an expected source of broken root refs? Or would it just be due\nto bugs?\n\n> So in all cases the existence check was either superfluous, not in line\n> with the adapted terminology or masked potential issues. This commit\n> thus changes the behaviour as proposed and drops the existence check\n> altogether.\n\nDropping the existence check makes sense to me. It also has the added\nbenefit of simplifying `is_root_ref()` which is nice.\n\n> \n> Add a test that verifies that this does not change user-visible\n> behaviour. Namely, we still don't want to show broken refs to the user\n> by default in git-for-each-ref(1). What this does allow though is for\n> internal callers to surface dangling root refs when they pass in the\n> `DO_FOR_EACH_INCLUDE_BROKEN` flag.\n"},{"id":"494842","messageId":"z5q2mk7c5d63gapkbeeuqa42sw2txbc65zmsgmel6u7llffrlz@yvkrj6d7xl3u","threadId":"61387","inReplyTo":"b719fb7110249e88b80e0cd9943a2d864b18d0e0.1715755591.git.ps@pks.im","subject":"Re: [PATCH v5 07/10] refs: classify HEAD as a root ref","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2024-05-15T20:44:29Z","receivedAt":"2024-05-15T20:45:59Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 24/05/15 08:50AM, Patrick Steinhardt wrote:\n> Root refs are those refs that live in the root of the ref hierarchy.\n> Our old and venerable \"HEAD\" reference falls into this category, but we\n> don't yet classify it as such in `is_root_ref()`.\n> \n> Adapt the function to also treat \"HEAD\" as a root ref. This change is\n> safe to do for all current callers:\n\nI like that this change gives HEAD a proper home now. :)\n"},{"id":"494849","messageId":"ZkWH-eUXtVF9OmfV@tanuki","threadId":"61387","inReplyTo":"vgzwb5xnlvz2gfiqamzrfcjs2xya3zhhoootyzopfpdrjapayq@wfsomyal4cf6","subject":"Re: [PATCH v5 06/10] refs: do not check ref existence in `is_root_ref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-16T04:13:45Z","receivedAt":"2024-05-16T04:13:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 15, 2024 at 03:38:47PM -0500, Justin Tobler wrote:\n> On 24/05/15 08:50AM, Patrick Steinhardt wrote:\n[snip]\n> >     The new behaviour is to include symbolic refs know, which aligns us\n> \n> s/know/now/\n\nFixed locally. I'll refrain from sending a new version just to fix this\ntypo though.\n\n> >     with the adapted terminology. Furthermore, files which look like\n> >     root refs but aren't are now mark those as \"broken\". As broken refs\n> >     are not surfaced by our tooling, this should not lead to a change in\n> >     user-visible behaviour, but may cause us to emit warnings. This\n> >     feels like the right thing to do as we would otherwise just silently\n> >     ignore corrupted root refs completely.\n> \n> Is there an expected source of broken root refs? Or would it just be due\n> to bugs?\n\nDangling symbolic refs are the only expected source. The fact that we\ndid not include those here feels like a bug to me.\n\nPatrick\n"}]}