{"thread":{"id":"66636","subject":"[PATCH 0/5] root refs vs pseudorefs","startedAt":"2026-10-22T21:28:48Z","lastAt":"2026-11-03T18:30:25Z","messageCount":34,"participants":["Andy Koppe","Junio C Hamano","Patrick Steinhardt","Julia Evans"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"555920","messageId":"20261022212903.177261-1-andy.koppe@gmail.com","threadId":"66636","inReplyTo":null,"subject":"[PATCH 0/5] root refs vs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-10-22T21:28:48Z","receivedAt":"2026-10-22T21:28:48Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"Since 993d57eded (refs: pseudorefs are no refs, 2024-05-15), the term\n\"pseudoref\" has been restricted to FETCH_HEAD and MERGE_HEAD, which are\nnot stored in the ref backends, while other refs outside of \"refs/\",\nsuch as HEAD and ORIG_HEAD, are called root refs. However, a fair number\nof docs, comments, messages and tests still use \"pseudoref\" in its old\nsense. And while the glossary describes the rules for refs in the root\nof the hierarchy, it does not explicitly define the term, even though\n\"git for-each-ref --include-root-refs\" uses it.\n\nThis series adds a \"root ref\" entry to the glossary and clarifies the\n\"pseudoref\" entry, and then replaces the outdated uses of \"pseudoref\" in\ndocs, code and tests. While at it, it also updates the long outdated\nglossary entry for per-worktree refs, and fixes a few inaccuracies\nfound along the way, for example about which refs get a reflog in\ngit-update-ref(1) and core.logAllRefUpdates.\n\nI've left __git_pseudoref_exists() in the completion script alone apart\nfrom its comment, even though it's used for both pseudorefs and root\nrefs. It could be renamed, keeping the old name as a deprecated wrapper\nlike elsewhere in the script, but it's not obvious what to, because\nthere is no term covering both.\n\nAndy Koppe (5):\n  doc: glossary: add root ref and clarify pseudorefs\n  doc: glossary: update per-worktree refs\n  doc: stop calling root refs pseudorefs\n  refs: stop calling root refs pseudorefs\n  t: stop calling root refs pseudorefs\n\n Documentation/config/core.adoc                |  2 +-\n Documentation/for-each-ref-options.adoc       |  2 +-\n Documentation/git-ls-remote.adoc              |  4 +-\n Documentation/git-update-ref.adoc             | 13 ++-\n Documentation/git-worktree.adoc               |  9 +-\n Documentation/glossary-content.adoc           | 86 ++++++++++++-------\n Documentation/rev-list-options.adoc           |  4 +-\n builtin/for-each-ref.c                        |  2 +-\n contrib/completion/git-completion.bash        |  7 +-\n refs.h                                        | 13 ++-\n revision.c                                    |  2 +-\n t/show-ref-exists-tests.sh                    |  4 +-\n t/t0600-reffiles-backend.sh                   |  2 +-\n t/t0610-reftable-basics.sh                    |  2 +-\n t/t1400-update-ref.sh                         | 66 +++++++-------\n t/t1403-show-ref.sh                           |  2 +-\n t/t1460-refs-migrate.sh                       |  4 +-\n t/t4013-diff-various.sh                       |  6 +-\n ...iff.diff-tree_--cc_--summary_REVERSE_HEAD} |  2 +-\n t/t6302-for-each-ref-filter.sh                |  4 +-\n t/t9902-completion.sh                         |  4 +-\n 21 files changed, 129 insertions(+), 111 deletions(-)\n rename t/t4013/{diff.diff-tree_--cc_--summary_REVERSE => diff.diff-tree_--cc_--summary_REVERSE_HEAD} (73%)\n\n\nbase-commit: 7e34d8174dcda3435d1404c9a30bef46afe6c13e\n-- \n2.56.0\n\n\n"},{"id":"555921","messageId":"20261022212903.177261-2-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH 1/5] doc: glossary: add root ref and clarify pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-10-22T21:28:49Z","receivedAt":"2026-10-22T21:28:49Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"The glossary describes refs in the root of the ref hierarchy, but does\nnot actually name them \"root refs\", even though the term is used\nelsewhere, for example by \"git for-each-ref --include-root-refs\". It\nalso defines pseudorefs as refs, yet is_root_ref() deliberately excludes\nthem, as they are not stored or handled by the ref backends.\n\nAdd a \"root ref\" entry, moving the naming rules and the list of\nirregular root refs there from the \"ref\" entry. State that pseudorefs\nare not root refs, and refer to gitrevisions(7) for descriptions of\ncommonly used root refs and pseudorefs.\n\nReword the \"pseudoref\" entry so that it no longer calls them refs, add\ngit-for-each-ref(1) as another example of how they are not proper refs,\nand explain that using a pseudoref containing multiple object IDs yields\nthe first one.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n Documentation/glossary-content.adoc | 68 +++++++++++++++++------------\n 1 file changed, 41 insertions(+), 27 deletions(-)\n\ndiff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\nindex 52301a56dd..b2c582254b 100644\n--- a/Documentation/glossary-content.adoc\n+++ b/Documentation/glossary-content.adoc\n@@ -500,9 +500,10 @@ exclude;;\n \tunusual refs.\n \n [[def_pseudoref]]pseudoref::\n-\tA ref that has different semantics than normal refs. These refs can be\n-\tread via normal Git commands, but cannot be written to by commands like\n-\tlinkgit:git-update-ref[1].\n+\tA name that can be used like a <<def_ref,ref>> to refer to\n+\t<<def_object,objects>>, but that is not stored in the same way as refs.\n+\tFor example, pseudorefs cannot be written by linkgit:git-update-ref[1]\n+\tor listed by linkgit:git-for-each-ref[1].\n +\n The following pseudorefs are known to Git:\n \n@@ -512,6 +513,9 @@ The following pseudorefs are known to Git:\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+When a pseudoref contains more than one object ID, using it to refer to an\n+object yields the first one.\n \n [[def_pull]]pull::\n \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n@@ -562,33 +566,12 @@ The following pseudorefs are known to Git:\n +\n The ref namespace is hierarchical.\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---\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-+\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---\n- - `AUTO_MERGE`\n-\n- - `BISECT_EXPECTED_REV`\n-\n- - `NOTES_MERGE_PARTIAL`\n-\n- - `NOTES_MERGE_REF`\n-\n- - `MERGE_AUTOSTASH`\n---\n+the hierarchy, in which case they must follow the naming rules for\n+<<def_root_ref,root refs>>.\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+the `refs/tags/` hierarchy is used to represent local tags.\n +\n See also \"REFERENCES\" in linkgit:gitdatamodel[7].\n \n@@ -640,6 +623,37 @@ format. See linkgit:git-reflog[1] for details.\n \tTo throw away part of the development, i.e. to assign the\n \t<<def_head,head>> to an earlier <<def_revision,revision>>.\n \n+[[def_root_ref]]root ref::\n+\tA <<def_ref,ref>> located in the root of the ref hierarchy, outside\n+\t`refs/`. Root ref names must follow these rules:\n++\n+--\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++\n+The following irregular root refs are exceptions to these rules. This\n+list is exhaustive and shall not be extended in future:\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++\n+<<def_pseudoref,Pseudorefs>> are not considered root refs, even though\n+they follow the naming rules.\n++\n+See linkgit:gitrevisions[7] for descriptions of commonly used root refs\n+and pseudorefs.\n+\n [[def_SCM]]SCM::\n \tSource code management (tool).\n \n-- \n2.56.0\n\n\n"},{"id":"555922","messageId":"20261022212903.177261-3-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH 2/5] doc: glossary: update per-worktree refs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-10-22T21:28:50Z","receivedAt":"2026-10-22T21:28:50Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"The \"per-worktree ref\" entry says that these are only HEAD and refs\nunder \"refs/bisect/\", but that has been out of date since a9be29c981\n(sequencer: make refs generated by the `label` command worktree-local,\n2018-04-25). All root refs are per-worktree, as are refs under\n\"refs/worktree/\" and \"refs/rewritten/\". Pseudorefs are specific to each\nworktree too.\n\nBring the list up to date, and refer to git-worktree(1) for how to\naccess per-worktree refs of other worktrees. In the \"worktree\" entry,\ndescribe HEAD as a per-worktree ref and MERGE_HEAD as a pseudoref,\nrather than listing them separately from per-worktree refs.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n Documentation/glossary-content.adoc | 18 ++++++++++++------\n 1 file changed, 12 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\nindex b2c582254b..eff6d23215 100644\n--- a/Documentation/glossary-content.adoc\n+++ b/Documentation/glossary-content.adoc\n@@ -494,10 +494,16 @@ exclude;;\n \tinterface than the <<def_plumbing,plumbing>>.\n \n [[def_per_worktree_ref]]per-worktree ref::\n-\tRefs that are per-<<def_worktree,worktree>>, rather than\n-\tglobal.  This is presently only <<def_HEAD,HEAD>> and any refs\n-\tthat start with `refs/bisect/`, but might later include other\n-\tunusual refs.\n+\tA <<def_ref,ref>> that is specific to a <<def_worktree,worktree>>,\n+\trather than shared between all worktrees. All\n+\t<<def_root_ref,root refs>>, such as <<def_HEAD,HEAD>>, are\n+\tper-worktree, as are refs that start with `refs/bisect/`,\n+\t`refs/worktree/` or `refs/rewritten/`.\n++\n+<<def_pseudoref,Pseudorefs>> are also specific to each worktree.\n++\n+See \"REFS\" in linkgit:git-worktree[1] for how to access the\n+per-worktree refs of other worktrees.\n \n [[def_pseudoref]]pseudoref::\n \tA name that can be used like a <<def_ref,ref>> to refer to\n@@ -788,5 +794,5 @@ and pseudorefs.\n \t\"working tree\" and repository metadata, most of which are\n \tshared among other worktrees of a single repository, and\n \tsome of which are maintained separately per worktree\n-\t(e.g. the index, HEAD and pseudorefs like MERGE_HEAD,\n-\tper-worktree refs and per-worktree configuration file).\n+\t(e.g. the index, per-worktree refs such as HEAD, pseudorefs\n+\tsuch as MERGE_HEAD, and the per-worktree configuration file).\n-- \n2.56.0\n\n\n"},{"id":"555923","messageId":"20261022212903.177261-4-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH 3/5] doc: stop calling root refs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-10-22T21:28:51Z","receivedAt":"2026-10-22T21:28:51Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"Several manual pages still use \"pseudoref\" in its old sense of any ref\noutside of \"refs/\", which the glossary now calls a root ref. Pseudorefs\nhave since been restricted to FETCH_HEAD and MERGE_HEAD, which are not\nconsidered root refs.\n\nReplace \"pseudoref\" with \"root ref\" in those pages, giving HEAD and\nORIG_HEAD as examples. In git-worktree(1), say that both root refs and\npseudorefs are per-worktree, and drop the description of them as being\ndirectly under $GIT_DIR, because that does not apply to the reftable\nbackend.\n\nIn git-ls-remote(1), describe --refs as omitting refs outside \"refs/\",\nsince that is what it actually checks. In the example, refer to HEAD,\nwhich is the only root ref that git-upload-pack(1) advertises. While\nthere, correct \"symbolics\" to \"symbolic refs\".\n\nIn git-update-ref(1), drop the incorrect mention of ORIG_HEAD. HEAD is\nthe only root ref that gets a reflog by default. Also restructure the\nsentence to make it easier to parse.\n\nIn the description of core.logAllRefUpdates, correct the claim that\n\"always\" only applies to refs under \"refs/\". It applies to root refs\ntoo.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n Documentation/config/core.adoc          |  2 +-\n Documentation/for-each-ref-options.adoc |  2 +-\n Documentation/git-ls-remote.adoc        |  4 ++--\n Documentation/git-update-ref.adoc       | 13 ++++++-------\n Documentation/git-worktree.adoc         |  9 ++++-----\n Documentation/rev-list-options.adoc     |  4 ++--\n 6 files changed, 16 insertions(+), 18 deletions(-)\n\ndiff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc\nindex bdc74d291c..8f8fe36a58 100644\n--- a/Documentation/config/core.adoc\n+++ b/Documentation/config/core.adoc\n@@ -376,7 +376,7 @@ core.logAllRefUpdates::\n \t`refs/heads/`), remote refs (i.e. under `refs/remotes/`),\n \tnote refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`.\n \tIf it is set to `always`, then a missing reflog is automatically\n-\tcreated for any ref under `refs/`.\n+\tcreated for any ref, including root refs such as `ORIG_HEAD`.\n +\n This information can be used to determine what commit\n was the tip of a branch \"2 days ago\".\ndiff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc\nindex 54e2fa95c2..2b6d4ede0b 100644\n--- a/Documentation/for-each-ref-options.adoc\n+++ b/Documentation/for-each-ref-options.adoc\n@@ -73,7 +73,7 @@ TAB %(refname)`.\n \tusing the same rules as _<pattern>_ above.\n \n `--include-root-refs`::\n-\tList root refs (`HEAD` and pseudorefs) apart from regular refs.\n+\tAlso list root refs, such as `HEAD` or `ORIG_HEAD`.\n \n `--start-after=<marker>`::\n     Allows paginating the output by skipping references up to and including the\ndiff --git a/Documentation/git-ls-remote.adoc b/Documentation/git-ls-remote.adoc\nindex d71c4ab3e2..7ce7df3149 100644\n--- a/Documentation/git-ls-remote.adoc\n+++ b/Documentation/git-ls-remote.adoc\n@@ -35,7 +35,7 @@ OPTIONS\n \twith other git subcommands.\n \n --refs::\n-\tDo not show peeled tags or pseudorefs like `HEAD` in the output.\n+\tDo not show peeled tags or refs outside `refs/`.\n \n -q::\n --quiet::\n@@ -118,7 +118,7 @@ line shows the name of the object the tag points at.\n EXAMPLES\n --------\n \n-* List all references (including symbolics and pseudorefs), peeling\n+* List all references (including `HEAD` and symbolic refs), peeling\n   tags:\n +\n ----\ndiff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\nindex 37a5019a8b..c775334230 100644\n--- a/Documentation/git-update-ref.adoc\n+++ b/Documentation/git-update-ref.adoc\n@@ -163,13 +163,12 @@ still see a subset of the modifications.\n \n LOGGING UPDATES\n ---------------\n-If config parameter \"core.logAllRefUpdates\" is true and the ref is one\n-under \"refs/heads/\", \"refs/remotes/\", \"refs/notes/\", or a pseudoref\n-like HEAD or ORIG_HEAD; or the file \"$GIT_DIR/logs/<ref>\" exists then\n-`git update-ref` will append a line to the log file\n-\"$GIT_DIR/logs/<ref>\" (dereferencing all symbolic refs before creating\n-the log name) describing the change in ref value.  Log lines are\n-formatted as:\n+If config parameter \"core.logAllRefUpdates\" is true and the ref is\n+HEAD or one under \"refs/heads/\", \"refs/remotes/\" or \"refs/notes/\", or\n+if the file \"$GIT_DIR/logs/<ref>\" exists, then `git update-ref` will\n+append a line to the log file \"$GIT_DIR/logs/<ref>\" (dereferencing all\n+symbolic refs before creating the log name) describing the change in\n+ref value.  Log lines are formatted as:\n \n     oldsha1 SP newsha1 SP committer LF\n \ndiff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc\nindex e6e77252ab..f0eb6d4137 100644\n--- a/Documentation/git-worktree.adoc\n+++ b/Documentation/git-worktree.adoc\n@@ -303,11 +303,10 @@ but others are specific to an individual worktree. One example is `HEAD`,\n which is different for each worktree. This section is about the sharing\n rules and how to access refs of one worktree from another.\n \n-In general, all pseudo refs are per-worktree and all refs starting with\n-`refs/` are shared. Pseudo refs are ones like `HEAD` which are directly\n-under `$GIT_DIR` instead of inside `$GIT_DIR/refs`. There are exceptions,\n-however: refs inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are\n-not shared.\n+In general, all root refs and pseudorefs are per-worktree and all refs\n+starting with `refs/` are shared. There are exceptions, however: refs\n+inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are not\n+shared.\n \n Refs that are per-worktree can still be accessed from another worktree via\n two special paths, `main-worktree` and `worktrees`. The former gives\ndiff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc\nindex bd9f345690..822604278a 100644\n--- a/Documentation/rev-list-options.adoc\n+++ b/Documentation/rev-list-options.adoc\n@@ -350,8 +350,8 @@ Under `--pretty=reference`, this information will not be shown at all.\n \n `--merge`::\n \tShow commits touching conflicted paths in the range `HEAD...<other>`,\n-\twhere `<other>` is the first existing pseudoref in `MERGE_HEAD`,\n-\t`CHERRY_PICK_HEAD`, `REVERT_HEAD` or `REBASE_HEAD`. Only works\n+\twhere `<other>` is the first of `MERGE_HEAD`, `CHERRY_PICK_HEAD`,\n+\t`REVERT_HEAD` or `REBASE_HEAD` that exists. Only works\n \twhen the index has unmerged entries. This option can be used to show\n \trelevant commits when resolving conflicts from a 3-way merge.\n \n-- \n2.56.0\n\n\n"},{"id":"555924","messageId":"20261022212903.177261-5-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH 4/5] refs: stop calling root refs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-10-22T21:28:52Z","receivedAt":"2026-10-22T21:28:52Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"Some messages and comments in the code still use \"pseudoref\" in its old\nsense of any ref outside of \"refs/\", whereas the glossary now calls\nthose root refs and restricts pseudorefs to FETCH_HEAD and MERGE_HEAD.\n\nReplace \"pseudoref\" with \"root ref\" in the help text for\n\"git for-each-ref --include-root-refs\", in comments in refs.h, and in\ncomments in the bash completion script, including the one for\n__git_pseudoref_exists(), which is also used for root refs. The function\nitself is not renamed. There is no obvious term covering both root refs\nand pseudorefs, and the old name might need to be kept for third-party\ncompletion scripts.\n\nUpdate the description of pseudorefs in refs.h to match the glossary.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n builtin/for-each-ref.c                 |  2 +-\n contrib/completion/git-completion.bash |  7 ++++---\n refs.h                                 | 13 ++++++-------\n revision.c                             |  2 +-\n 4 files changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 4a2fc421db..6ff32e919c 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -48,7 +48,7 @@ int for_each_ref_core(int argc, const char **argv, const char *prefix, struct re\n \t\tOPT_NO_CONTAINS(&filter.no_commit, N_(\"print only refs which don't contain the commit\")),\n \t\tOPT_BOOL(0, \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n \t\tOPT_BOOL(0, \"stdin\", &from_stdin, N_(\"read reference patterns from stdin\")),\n-\t\tOPT_BOOL(0, \"include-root-refs\", &include_root_refs, N_(\"also include HEAD ref and pseudorefs\")),\n+\t\tOPT_BOOL(0, \"include-root-refs\", &include_root_refs, N_(\"also include root refs\")),\n \t\tOPT_END(),\n \t};\n \ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex c3a49e2e22..e12ac0359c 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -147,8 +147,9 @@ __git_eread ()\n \ttest -r \"$1\" && IFS=$'\\r\\n' read -r \"$2\" <\"$1\"\n }\n \n-# Runs git in $__git_repo_path to determine whether a pseudoref exists.\n-# 1: The pseudo-ref to search\n+# Runs git in $__git_repo_path to determine whether a root ref or\n+# pseudoref exists.\n+# 1: The ref to check\n __git_pseudoref_exists ()\n {\n \tlocal ref=$1\n@@ -954,7 +955,7 @@ __git_refs ()\n \tesac\n }\n \n-# Completes refs, short and long, local and remote, symbolic and pseudo.\n+# Completes refs, short and long, local and remote, symbolic, root and pseudo.\n #\n # Usage: __git_complete_refs [<option>]...\n # --remote=<remote>: The remote to list refs from, can be the name of a\ndiff --git a/refs.h b/refs.h\nindex ee3b8a62ef..3be7ef3b0e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -85,7 +85,7 @@ enum ref_transaction_error {\n  * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/\n  * directory and do not consist of all caps and underscores cannot be\n  * resolved. The function returns NULL for such ref names.\n- * Caps and underscores refers to the pseudorefs, such as HEAD,\n+ * Caps and underscores refers to root refs and pseudorefs, such as HEAD,\n  * FETCH_HEAD and friends, that all live outside of the refs/ directory.\n  */\n #define RESOLVE_REF_READING 0x01\n@@ -448,8 +448,8 @@ enum refs_for_each_flag {\n \tREFS_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),\n \n \t/*\n-\t * Include root refs i.e. HEAD and pseudorefs along with the regular\n-\t * refs.\n+\t * Include root refs, such as HEAD and ORIG_HEAD, along with the\n+\t * regular refs.\n \t */\n \tREFS_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),\n };\n@@ -1306,10 +1306,9 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\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+ * Pseudorefs are names that can be used like refs, but have different\n+ * semantics. They cannot be stored in the ref backend, but must always\n+ * be accessed via the filesystem. The following 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.\ndiff --git a/revision.c b/revision.c\nindex a83e499047..d6dd56faf1 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2000,7 +2000,7 @@ static const char *lookup_other_head(struct object_id *oid)\n \t\t\treturn other_head[i];\n \t\t}\n \n-\tdie(_(\"--merge requires one of the pseudorefs MERGE_HEAD, CHERRY_PICK_HEAD, REVERT_HEAD or REBASE_HEAD\"));\n+\tdie(_(\"--merge requires one of MERGE_HEAD, CHERRY_PICK_HEAD, REVERT_HEAD or REBASE_HEAD\"));\n }\n \n static void prepare_show_merge(struct rev_info *revs)\n-- \n2.56.0\n\n\n"},{"id":"555925","messageId":"20261022212903.177261-6-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH 5/5] t: stop calling root refs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-10-22T21:28:53Z","receivedAt":"2026-10-22T21:28:53Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"Several test titles and comments still use \"pseudoref\" in its old sense\nof any ref outside of \"refs/\", which the glossary now calls root refs.\n\nReplace \"pseudoref\" with \"root ref\" there. In t1400 and t4013, rename\nthe root refs that the tests create from PSEUDOREF to ROOT_REF_HEAD and\nfrom REVERSE to REVERSE_HEAD, so that they follow the naming rules for\nroot refs. This includes renaming the expected output file in t4013.\n\nSome tests also still refer to FETCH_HEAD and MERGE_HEAD as \"special\nrefs\", which was the previous term for \"pseudorefs\". Correct them.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n t/show-ref-exists-tests.sh                    |  4 +-\n t/t0600-reffiles-backend.sh                   |  2 +-\n t/t0610-reftable-basics.sh                    |  2 +-\n t/t1400-update-ref.sh                         | 66 +++++++++----------\n t/t1403-show-ref.sh                           |  2 +-\n t/t1460-refs-migrate.sh                       |  4 +-\n t/t4013-diff-various.sh                       |  6 +-\n ...iff.diff-tree_--cc_--summary_REVERSE_HEAD} |  2 +-\n t/t6302-for-each-ref-filter.sh                |  4 +-\n t/t9902-completion.sh                         |  4 +-\n 10 files changed, 48 insertions(+), 48 deletions(-)\n rename t/t4013/{diff.diff-tree_--cc_--summary_REVERSE => diff.diff-tree_--cc_--summary_REVERSE_HEAD} (73%)\n\ndiff --git a/t/show-ref-exists-tests.sh b/t/show-ref-exists-tests.sh\nindex 1351f690eb..d4a021a0c6 100644\n--- a/t/show-ref-exists-tests.sh\n+++ b/t/show-ref-exists-tests.sh\n@@ -64,11 +64,11 @@ test_expect_success '--exists with directory fails with generic error' '\n \ttest_cmp expect err\n '\n \n-test_expect_success '--exists with non-existent special ref' '\n+test_expect_success '--exists with non-existent pseudoref' '\n \ttest_expect_code 2 ${git_show_ref_exists} FETCH_HEAD\n '\n \n-test_expect_success '--exists with existing special ref' '\n+test_expect_success '--exists with existing pseudoref' '\n \ttest_when_finished \"rm .git/FETCH_HEAD\" &&\n \tgit rev-parse HEAD >.git/FETCH_HEAD &&\n \t${git_show_ref_exists} FETCH_HEAD\ndiff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh\nindex 62b0851852..26385aab7d 100755\n--- a/t/t0600-reffiles-backend.sh\n+++ b/t/t0600-reffiles-backend.sh\n@@ -267,7 +267,7 @@ test_expect_success 'setup worktree' '\n \t)\n '\n \n-# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should\n+# Some refs (refs/bisect/*, root refs) are kept per worktree, so they should\n # only appear in the for-each-reflog output if it is called from the correct\n # worktree, which is exercised in this test. This test is poorly written for\n # multiple reasons: 1) it creates invalidly formatted log entries. 2) it uses\ndiff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\nindex a702745839..01a0310762 100755\n--- a/t/t0610-reftable-basics.sh\n+++ b/t/t0610-reftable-basics.sh\n@@ -1156,7 +1156,7 @@ test_expect_success 'worktree: adds worktree with detached HEAD' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'fetch: accessing FETCH_HEAD special ref works' '\n+test_expect_success 'fetch: accessing FETCH_HEAD pseudoref works' '\n \ttest_when_finished \"rm -rf repo sub\" &&\n \n \tgit init sub &&\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 1a164c96f9..95a617254b 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -503,61 +503,61 @@ test_expect_success 'git cat-file blob main@{2005-05-26 23:42}:F (expect OTHER)'\n \ttest OTHER = $(git cat-file blob \"main@{2005-05-26 23:42}:F\")\n '\n \n-# Test adding and deleting pseudorefs\n+# Test adding and deleting root refs\n \n-test_expect_success 'given old value for missing pseudoref, do not create' '\n-\ttest_must_fail git update-ref PSEUDOREF $A $B 2>err &&\n-\ttest_must_fail git rev-parse PSEUDOREF &&\n+test_expect_success 'given old value for missing root ref, do not create' '\n+\ttest_must_fail git update-ref ROOT_REF_HEAD $A $B 2>err &&\n+\ttest_must_fail git rev-parse ROOT_REF_HEAD &&\n \ttest_grep \"unable to resolve reference\" err\n '\n \n-test_expect_success 'create pseudoref' '\n-\tgit update-ref PSEUDOREF $A &&\n-\ttest $A = $(git show-ref -s --verify PSEUDOREF)\n+test_expect_success 'create root ref' '\n+\tgit update-ref ROOT_REF_HEAD $A &&\n+\ttest $A = $(git show-ref -s --verify ROOT_REF_HEAD)\n '\n \n-test_expect_success 'overwrite pseudoref with no old value given' '\n-\tgit update-ref PSEUDOREF $B &&\n-\ttest $B = $(git show-ref -s --verify PSEUDOREF)\n+test_expect_success 'overwrite root ref with no old value given' '\n+\tgit update-ref ROOT_REF_HEAD $B &&\n+\ttest $B = $(git show-ref -s --verify ROOT_REF_HEAD)\n '\n \n-test_expect_success 'overwrite pseudoref with correct old value' '\n-\tgit update-ref PSEUDOREF $C $B &&\n-\ttest $C = $(git show-ref -s --verify PSEUDOREF)\n+test_expect_success 'overwrite root ref with correct old value' '\n+\tgit update-ref ROOT_REF_HEAD $C $B &&\n+\ttest $C = $(git show-ref -s --verify ROOT_REF_HEAD)\n '\n \n-test_expect_success 'do not overwrite pseudoref with wrong old value' '\n-\ttest_must_fail git update-ref PSEUDOREF $D $E 2>err &&\n-\ttest $C = $(git show-ref -s --verify PSEUDOREF) &&\n+test_expect_success 'do not overwrite root ref with wrong old value' '\n+\ttest_must_fail git update-ref ROOT_REF_HEAD $D $E 2>err &&\n+\ttest $C = $(git show-ref -s --verify ROOT_REF_HEAD) &&\n \ttest_grep \"cannot lock ref.*expected\" err\n '\n \n-test_expect_success 'delete pseudoref' '\n-\tgit update-ref -d PSEUDOREF &&\n-\ttest_must_fail git show-ref -s --verify PSEUDOREF\n+test_expect_success 'delete root ref' '\n+\tgit update-ref -d ROOT_REF_HEAD &&\n+\ttest_must_fail git show-ref -s --verify ROOT_REF_HEAD\n '\n \n-test_expect_success 'do not delete pseudoref with wrong old value' '\n-\tgit update-ref PSEUDOREF $A &&\n-\ttest_must_fail git update-ref -d PSEUDOREF $B 2>err &&\n-\ttest $A = $(git show-ref -s --verify PSEUDOREF) &&\n+test_expect_success 'do not delete root ref with wrong old value' '\n+\tgit update-ref ROOT_REF_HEAD $A &&\n+\ttest_must_fail git update-ref -d ROOT_REF_HEAD $B 2>err &&\n+\ttest $A = $(git show-ref -s --verify ROOT_REF_HEAD) &&\n \ttest_grep \"cannot lock ref.*expected\" err\n '\n \n-test_expect_success 'delete pseudoref with correct old value' '\n-\tgit update-ref -d PSEUDOREF $A &&\n-\ttest_must_fail git show-ref -s --verify PSEUDOREF\n+test_expect_success 'delete root ref with correct old value' '\n+\tgit update-ref -d ROOT_REF_HEAD $A &&\n+\ttest_must_fail git show-ref -s --verify ROOT_REF_HEAD\n '\n \n-test_expect_success 'create pseudoref with old OID zero' '\n-\tgit update-ref PSEUDOREF $A $Z &&\n-\ttest $A = $(git show-ref -s --verify PSEUDOREF)\n+test_expect_success 'create root ref with old OID zero' '\n+\tgit update-ref ROOT_REF_HEAD $A $Z &&\n+\ttest $A = $(git show-ref -s --verify ROOT_REF_HEAD)\n '\n \n-test_expect_success 'do not overwrite pseudoref with old OID zero' '\n-\ttest_when_finished git update-ref -d PSEUDOREF &&\n-\ttest_must_fail git update-ref PSEUDOREF $B $Z 2>err &&\n-\ttest $A = $(git show-ref -s --verify PSEUDOREF) &&\n+test_expect_success 'do not overwrite root ref with old OID zero' '\n+\ttest_when_finished git update-ref -d ROOT_REF_HEAD &&\n+\ttest_must_fail git update-ref ROOT_REF_HEAD $B $Z 2>err &&\n+\ttest $A = $(git show-ref -s --verify ROOT_REF_HEAD) &&\n \ttest_grep \"already exists\" err\n '\n \ndiff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh\nindex 226b3ec0e8..38648fd560 100755\n--- a/t/t1403-show-ref.sh\n+++ b/t/t1403-show-ref.sh\n@@ -181,7 +181,7 @@ test_expect_success 'show-ref --verify HEAD' '\n \ttest_must_be_empty actual\n '\n \n-test_expect_success 'show-ref --verify pseudorefs' '\n+test_expect_success 'show-ref --verify root refs' '\n \tgit update-ref CHERRY_PICK_HEAD HEAD $ZERO_OID &&\n \ttest_when_finished \"git update-ref -d CHERRY_PICK_HEAD\" &&\n \tgit show-ref -s --verify HEAD >actual &&\ndiff --git a/t/t1460-refs-migrate.sh b/t/t1460-refs-migrate.sh\nindex ecf6411288..1f59d820a0 100755\n--- a/t/t1460-refs-migrate.sh\n+++ b/t/t1460-refs-migrate.sh\n@@ -170,7 +170,7 @@ do\n \t\t\ttest_cmp expect actual\n \t\t'\n \n-\t\ttest_expect_success \"$from_format -> $to_format: pseudo-refs\" '\n+\t\ttest_expect_success \"$from_format -> $to_format: root refs\" '\n \t\t\ttest_when_finished \"rm -rf repo\" &&\n \t\t\tgit init --ref-storage-format=$from_format repo &&\n \t\t\ttest_commit -C repo initial &&\n@@ -178,7 +178,7 @@ do\n \t\t\ttest_migration repo \"$to_format\"\n \t\t'\n \n-\t\ttest_expect_success \"$from_format -> $to_format: special refs are left alone\" '\n+\t\ttest_expect_success \"$from_format -> $to_format: pseudorefs are left alone\" '\n \t\t\ttest_when_finished \"rm -rf repo\" &&\n \t\t\tgit init --ref-storage-format=$from_format repo &&\n \t\t\ttest_commit -C repo initial &&\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 21c9ad7c5d..43a9bf6d7c 100755\n--- a/t/t4013-diff-various.sh\n+++ b/t/t4013-diff-various.sh\n@@ -115,10 +115,10 @@ test_expect_success setup '\n \tgit checkout -f main &&\n \n \t# Same merge as main, but with parents reversed. Hide it in a\n-\t# pseudo-ref to avoid impacting tests with --all.\n+\t# root ref to avoid impacting tests with --all.\n \tcommit=$(echo reverse |\n \t\t git commit-tree -p main^2 -p main^1 main^{tree}) &&\n-\tgit update-ref REVERSE $commit &&\n+\tgit update-ref REVERSE_HEAD $commit &&\n \n \tgit config diff.renames false &&\n \n@@ -328,7 +328,7 @@ diff-tree --cc --stat --summary main\n diff-tree -c --stat --summary side\n diff-tree --cc --stat --summary side\n diff-tree --cc --shortstat main\n-diff-tree --cc --summary REVERSE\n+diff-tree --cc --summary REVERSE_HEAD\n # improved by Timo's patch\n diff-tree --cc --patch-with-stat main\n # improved by Timo's patch\ndiff --git a/t/t4013/diff.diff-tree_--cc_--summary_REVERSE b/t/t4013/diff.diff-tree_--cc_--summary_REVERSE_HEAD\nsimilarity index 73%\nrename from t/t4013/diff.diff-tree_--cc_--summary_REVERSE\nrename to t/t4013/diff.diff-tree_--cc_--summary_REVERSE_HEAD\nindex e208dd5682..0f86ae60db 100644\n--- a/t/t4013/diff.diff-tree_--cc_--summary_REVERSE\n+++ b/t/t4013/diff.diff-tree_--cc_--summary_REVERSE_HEAD\n@@ -1,4 +1,4 @@\n-$ git diff-tree --cc --summary REVERSE\n+$ git diff-tree --cc --summary REVERSE_HEAD\n 2562325a7ee916efb2481da93073b82cec801cbc\n  create mode 100644 file1\n  delete mode 100644 file2\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex 7f060d97bf..14f6d7e597 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -31,7 +31,7 @@ test_expect_success 'setup some history and refs' '\n \tgit update-ref refs/odd/spot main\n '\n \n-test_expect_success '--include-root-refs pattern prints pseudorefs' '\n+test_expect_success '--include-root-refs pattern prints root refs' '\n \tcat >expect <<-\\EOF &&\n \tHEAD\n \tORIG_HEAD\n@@ -52,7 +52,7 @@ 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+test_expect_success '--include-root-refs pattern does not print pseudorefs' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n \t(\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex f6de74054a..1cb55f9301 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2693,13 +2693,13 @@ test_expect_success 'checkout matches case insensitively with GIT_COMPLETION_IGN\n \t)\n '\n \n-test_expect_success 'checkout completes pseudo refs' '\n+test_expect_success 'checkout completes root refs' '\n \ttest_completion \"git checkout H\" <<-\\EOF\n \tHEAD Z\n \tEOF\n '\n \n-test_expect_success 'checkout completes pseudo refs case insensitively with GIT_COMPLETION_IGNORE_CASE' '\n+test_expect_success 'checkout completes root refs case insensitively with GIT_COMPLETION_IGNORE_CASE' '\n \t(\n \t\tGIT_COMPLETION_IGNORE_CASE=1 &&\n \t\ttest_completion \"git checkout h\" <<-\\EOF\n-- \n2.56.0\n\n\n"},{"id":"555927","messageId":"xmqqv76tcrtb.fsf@gitster.g","threadId":"66636","inReplyTo":"20261022212903.177261-2-andy.koppe@gmail.com","subject":"Re: [PATCH 1/5] doc: glossary: add root ref and clarify pseudorefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-22T22:07:44Z","receivedAt":"2026-10-22T22:07:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andy Koppe <andy.koppe@gmail.com> writes:\n\n> The glossary describes refs in the root of the ref hierarchy, but does\n> not actually name them \"root refs\", even though the term is used\n> elsewhere, for example by \"git for-each-ref --include-root-refs\". It\n> also defines pseudorefs as refs, yet is_root_ref() deliberately excludes\n> them, as they are not stored or handled by the ref backends.\n>\n> Add a \"root ref\" entry, moving the naming rules and the list of\n> irregular root refs there from the \"ref\" entry. State that pseudorefs\n> are not root refs, and refer to gitrevisions(7) for descriptions of\n> commonly used root refs and pseudorefs.\n>\n> Reword the \"pseudoref\" entry so that it no longer calls them refs, add\n> git-for-each-ref(1) as another example of how they are not proper refs,\n> and explain that using a pseudoref containing multiple object IDs yields\n> the first one.\n>\n> Signed-off-by: Andy Koppe <andy.koppe@gmail.com>\n> ---\n>  Documentation/glossary-content.adoc | 68 +++++++++++++++++------------\n>  1 file changed, 41 insertions(+), 27 deletions(-)\n>\n> diff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\n> index 52301a56dd..b2c582254b 100644\n> --- a/Documentation/glossary-content.adoc\n> +++ b/Documentation/glossary-content.adoc\n> @@ -500,9 +500,10 @@ exclude;;\n>  \tunusual refs.\n>  \n>  [[def_pseudoref]]pseudoref::\n> -\tA ref that has different semantics than normal refs. These refs can be\n> -\tread via normal Git commands, but cannot be written to by commands like\n> -\tlinkgit:git-update-ref[1].\n> +\tA name that can be used like a <<def_ref,ref>> to refer to\n> +\t<<def_object,objects>>, but that is not stored in the same way as refs.\n> +\tFor example, pseudorefs cannot be written by linkgit:git-update-ref[1]\n> +\tor listed by linkgit:git-for-each-ref[1].\n\nI would love to get input from Patrick who wrote 993d57eded (refs:\npseudorefs are no refs, 2024-05-15), as I consider that the loss of\n\"different semantics\" from this description is a rather serious one.\nFETCH_HEAD and MERGE_HEAD cannot be written with update-ref simply\nbecause it is normal for them to record multiple object names, so it\nis not just \"how\" they are stored, but also \"what\" they can store,\nthat makes them \"strange\", \"pseudo\", \"different\", and \"special\".\n\n>  The following pseudorefs are known to Git:\n>  \n> @@ -512,6 +513,9 @@ The following pseudorefs are known to Git:\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> +When a pseudoref contains more than one object ID, using it to refer to an\n> +object yields the first one.\n\nThis is an important addition.\n\n> @@ -562,33 +566,12 @@ The following pseudorefs are known to Git:\n>  +\n>  The ref namespace is hierarchical.\n>  Ref names must either start with `refs/` or be located in the root of\n> +the hierarchy, in which case they must follow the naming rules for\n> +<<def_root_ref,root refs>>.\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> +the `refs/tags/` hierarchy is used to represent local tags.\n>  +\n>  See also \"REFERENCES\" in linkgit:gitdatamodel[7].\n>  \n> @@ -640,6 +623,37 @@ format. See linkgit:git-reflog[1] for details.\n>  \tTo throw away part of the development, i.e. to assign the\n>  \t<<def_head,head>> to an earlier <<def_revision,revision>>.\n>  \n> +[[def_root_ref]]root ref::\n> +\tA <<def_ref,ref>> located in the root of the ref hierarchy, outside\n> +\t`refs/`. Root ref names must follow these rules:\n> ++\n> +--\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> ++\n> +The following irregular root refs are exceptions to these rules. This\n> +list is exhaustive and shall not be extended in future:\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> ++\n> +<<def_pseudoref,Pseudorefs>> are not considered root refs, even though\n> +they follow the naming rules.\n> ++\n> +See linkgit:gitrevisions[7] for descriptions of commonly used root refs\n> +and pseudorefs.\n\nI think this is a great reorganization to split out the \"these are\nexceptions\" into its own section.\n\n"},{"id":"555928","messageId":"xmqqqzhhcrb1.fsf@gitster.g","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"Re: [PATCH 0/5] root refs vs pseudorefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-10-22T22:18:42Z","receivedAt":"2026-10-22T22:18:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andy Koppe <andy.koppe@gmail.com> writes:\n\n> Since 993d57eded (refs: pseudorefs are no refs, 2024-05-15), the term\n> \"pseudoref\" has been restricted to FETCH_HEAD and MERGE_HEAD, which are\n> not stored in the ref backends, while other refs outside of \"refs/\",\n> such as HEAD and ORIG_HEAD, are called root refs. However, a fair number\n> of docs, comments, messages and tests still use \"pseudoref\" in its old\n> sense. And while the glossary describes the rules for refs in the root\n> of the hierarchy, it does not explicitly define the term, even though\n> \"git for-each-ref --include-root-refs\" uses it.\n\nThanks.  Overall this was a pleasant read.\n\n"},{"id":"555973","messageId":"atsPsFnb5mrneAMb@pks.im","threadId":"66636","inReplyTo":"20261022212903.177261-2-andy.koppe@gmail.com","subject":"Re: [PATCH 1/5] doc: glossary: add root ref and clarify pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-23T07:41:43Z","receivedAt":"2026-10-23T07:41:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 22, 2026 at 10:28:49PM +0100, Andy Koppe wrote:\n> diff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\n> index 52301a56dd..b2c582254b 100644\n> --- a/Documentation/glossary-content.adoc\n> +++ b/Documentation/glossary-content.adoc\n> @@ -500,9 +500,10 @@ exclude;;\n>  \tunusual refs.\n>  \n>  [[def_pseudoref]]pseudoref::\n> -\tA ref that has different semantics than normal refs. These refs can be\n> -\tread via normal Git commands, but cannot be written to by commands like\n> -\tlinkgit:git-update-ref[1].\n> +\tA name that can be used like a <<def_ref,ref>> to refer to\n> +\t<<def_object,objects>>, but that is not stored in the same way as refs.\n> +\tFor example, pseudorefs cannot be written by linkgit:git-update-ref[1]\n> +\tor listed by linkgit:git-for-each-ref[1].\n\nAs Junio already mentioned, I think we should retain the information\nabout different semantics.\n\nI also think that the previous guidance around how pseudorefs can be\nused is weakened, because now you only list examples of how they cannot\nbe used. Previously though we explicitly specified that the only\nsupported operation is reading the refs directly. So instead of giving\nexamples of how they cannot be used, I think we should continue to list\nthe few operations that _are_ possible.\n\n> @@ -512,6 +513,9 @@ The following pseudorefs are known to Git:\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> +When a pseudoref contains more than one object ID, using it to refer to an\n> +object yields the first one.\n\nThis is indeed a good addition.\n\n> @@ -640,6 +623,37 @@ format. See linkgit:git-reflog[1] for details.\n>  \tTo throw away part of the development, i.e. to assign the\n>  \t<<def_head,head>> to an earlier <<def_revision,revision>>.\n>  \n> +[[def_root_ref]]root ref::\n\nAnd splitting this out makes sense, as well.\n\n> +\tA <<def_ref,ref>> located in the root of the ref hierarchy, outside\n> +\t`refs/`. Root ref names must follow these rules:\n> ++\n> +--\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> ++\n> +The following irregular root refs are exceptions to these rules. This\n> +list is exhaustive and shall not be extended in future:\n\nOkay, this paragraph has been shortened a bit, which I think is a good\nchange.\n\nThanks!\n\nPatrick\n\n"},{"id":"555974","messageId":"atsPvF6wVdugtwrH@pks.im","threadId":"66636","inReplyTo":"20261022212903.177261-3-andy.koppe@gmail.com","subject":"Re: [PATCH 2/5] doc: glossary: update per-worktree refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-23T07:41:48Z","receivedAt":"2026-10-23T07:41:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 22, 2026 at 10:28:50PM +0100, Andy Koppe wrote:\n> diff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\n> index b2c582254b..eff6d23215 100644\n> --- a/Documentation/glossary-content.adoc\n> +++ b/Documentation/glossary-content.adoc\n> @@ -494,10 +494,16 @@ exclude;;\n>  \tinterface than the <<def_plumbing,plumbing>>.\n>  \n>  [[def_per_worktree_ref]]per-worktree ref::\n> -\tRefs that are per-<<def_worktree,worktree>>, rather than\n> -\tglobal.  This is presently only <<def_HEAD,HEAD>> and any refs\n> -\tthat start with `refs/bisect/`, but might later include other\n> -\tunusual refs.\n> +\tA <<def_ref,ref>> that is specific to a <<def_worktree,worktree>>,\n> +\trather than shared between all worktrees. All\n> +\t<<def_root_ref,root refs>>, such as <<def_HEAD,HEAD>>, are\n> +\tper-worktree, as are refs that start with `refs/bisect/`,\n> +\t`refs/worktree/` or `refs/rewritten/`.\n> ++\n> +<<def_pseudoref,Pseudorefs>> are also specific to each worktree.\n\nShould we maybe also turn this into an itemized list? Makes it easier to\nsee at a glance which rrefs are per-worktree, and it becomes easier to\nextend going forward.\n\n> +See \"REFS\" in linkgit:git-worktree[1] for how to access the\n> +per-worktree refs of other worktrees.\n>  \n>  [[def_pseudoref]]pseudoref::\n>  \tA name that can be used like a <<def_ref,ref>> to refer to\n> @@ -788,5 +794,5 @@ and pseudorefs.\n>  \t\"working tree\" and repository metadata, most of which are\n>  \tshared among other worktrees of a single repository, and\n>  \tsome of which are maintained separately per worktree\n> -\t(e.g. the index, HEAD and pseudorefs like MERGE_HEAD,\n> -\tper-worktree refs and per-worktree configuration file).\n> +\t(e.g. the index, per-worktree refs such as HEAD, pseudorefs\n> +\tsuch as MERGE_HEAD, and the per-worktree configuration file).\n\nThis is a bit curious as you distinguish between per-worktree refs and\npseudorefs, even though you mention both of them as part of the\nper-worktree ref section. I wonder whether we should simply refer to\nthat section without naming any examples.\n\nPatrick\n\n"},{"id":"555975","messageId":"atsPwpbD3YqmBds_@pks.im","threadId":"66636","inReplyTo":"20261022212903.177261-4-andy.koppe@gmail.com","subject":"Re: [PATCH 3/5] doc: stop calling root refs pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-23T07:41:54Z","receivedAt":"2026-10-23T07:41:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 22, 2026 at 10:28:51PM +0100, Andy Koppe wrote:\n> diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\n> index 37a5019a8b..c775334230 100644\n> --- a/Documentation/git-update-ref.adoc\n> +++ b/Documentation/git-update-ref.adoc\n> @@ -163,13 +163,12 @@ still see a subset of the modifications.\n>  \n>  LOGGING UPDATES\n>  ---------------\n> -If config parameter \"core.logAllRefUpdates\" is true and the ref is one\n> -under \"refs/heads/\", \"refs/remotes/\", \"refs/notes/\", or a pseudoref\n> -like HEAD or ORIG_HEAD; or the file \"$GIT_DIR/logs/<ref>\" exists then\n> -`git update-ref` will append a line to the log file\n> -\"$GIT_DIR/logs/<ref>\" (dereferencing all symbolic refs before creating\n> -the log name) describing the change in ref value.  Log lines are\n> -formatted as:\n> +If config parameter \"core.logAllRefUpdates\" is true and the ref is\n> +HEAD or one under \"refs/heads/\", \"refs/remotes/\" or \"refs/notes/\", or\n> +if the file \"$GIT_DIR/logs/<ref>\" exists, then `git update-ref` will\n\nIt's kind of hard to understand how the ANDs and ORs play together.\nLike, does the config parameter extend to all subsequent ORs? That's not\nreally a new issue though.\n\n> +append a line to the log file \"$GIT_DIR/logs/<ref>\" (dereferencing all\n\nAlso, this infomation about \"$GIT_DIR/logs/<ref>\" is out-of-date, as the\nreftable backend doesn't store its reflogs in there.\n\nI wonder whether we should maybe just stop explaining how\n\"core.logAllRefUpdates\" works and instead refer to git-config(1).\n\n> diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc\n> index e6e77252ab..f0eb6d4137 100644\n> --- a/Documentation/git-worktree.adoc\n> +++ b/Documentation/git-worktree.adoc\n> @@ -303,11 +303,10 @@ but others are specific to an individual worktree. One example is `HEAD`,\n>  which is different for each worktree. This section is about the sharing\n>  rules and how to access refs of one worktree from another.\n>  \n> -In general, all pseudo refs are per-worktree and all refs starting with\n> -`refs/` are shared. Pseudo refs are ones like `HEAD` which are directly\n> -under `$GIT_DIR` instead of inside `$GIT_DIR/refs`. There are exceptions,\n> -however: refs inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are\n> -not shared.\n> +In general, all root refs and pseudorefs are per-worktree and all refs\n> +starting with `refs/` are shared. There are exceptions, however: refs\n> +inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are not\n> +shared.\n\nIt might make sense to have a bulleted list here, same as I proposed in\na preceding patch.\n\nPatrick\n\n"},{"id":"555976","messageId":"atsPx0Wn9tyMzpCl@pks.im","threadId":"66636","inReplyTo":"20261022212903.177261-5-andy.koppe@gmail.com","subject":"Re: [PATCH 4/5] refs: stop calling root refs pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-10-23T07:41:59Z","receivedAt":"2026-10-23T07:41:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 22, 2026 at 10:28:52PM +0100, Andy Koppe wrote:\n> Some messages and comments in the code still use \"pseudoref\" in its old\n> sense of any ref outside of \"refs/\", whereas the glossary now calls\n> those root refs and restricts pseudorefs to FETCH_HEAD and MERGE_HEAD.\n> \n> Replace \"pseudoref\" with \"root ref\" in the help text for\n> \"git for-each-ref --include-root-refs\", in comments in refs.h, and in\n> comments in the bash completion script, including the one for\n> __git_pseudoref_exists(), which is also used for root refs.\n\nI feel like this sentence is somewhat pointless, as you basically\njust explain the diff.\n\n> The function\n> itself is not renamed. There is no obvious term covering both root refs\n> and pseudorefs, and the old name might need to be kept for third-party\n> completion scripts.\n\nThis is useful though.\n\n> Update the description of pseudorefs in refs.h to match the glossary.\n\nAnd this is again info that can simply be dropped.\n\nPatrick \n\n"},{"id":"556033","messageId":"93D20D7C-E19B-4A41-AC10-4AFC8330E844@jvns.ca","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"Re: [PATCH 0/5] root refs vs pseudorefs","fromName":"Julia Evans","fromEmail":"julia@jvns.ca","sentAt":"2026-10-23T13:21:11Z","receivedAt":"2026-10-23T13:21:11Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"\n\n> On Oct 22, 2026, at 5:28 PM, Andy Koppe <andy.koppe@gmail.com> wrote:\n> \n> Since 993d57eded (refs: pseudorefs are no refs, 2024-05-15), the term\n> \"pseudoref\" has been restricted to FETCH_HEAD and MERGE_HEAD, which are\n> not stored in the ref backends, while other refs outside of \"refs/\",\n> such as HEAD and ORIG_HEAD, are called root refs. However, a fair number\n> of docs, comments, messages and tests still use \"pseudoref\" in its old\n> sense. And while the glossary describes the rules for refs in the root\n> of the hierarchy, it does not explicitly define the term, even though\n> \"git for-each-ref --include-root-refs\" uses it.\n> \n> This series adds a \"root ref\" entry to the glossary and clarifies the\n> \"pseudoref\" entry, and then replaces the outdated uses of \"pseudoref\" in\n> docs, code and tests. While at it, it also updates the long outdated\n> glossary entry for per-worktree refs, and fixes a few inaccuracies\n> found along the way, for example about which refs get a reflog in\n> git-update-ref(1) and core.logAllRefUpdates.\n> \n> I've left __git_pseudoref_exists() in the completion script alone apart\n> from its comment, even though it's used for both pseudorefs and root\n> refs. It could be renamed, keeping the old name as a deprecated wrapper\n> like elsewhere in the script, but it's not obvious what to, because\n> there is no term covering both.\n\n> Andy Koppe (5):\n>  doc: glossary: add root ref and clarify pseudorefs\n>  doc: glossary: update per-worktree refs\n>  doc: stop calling root refs pseudorefs\n>  refs: stop calling root refs pseudorefs\n>  t: stop calling root refs pseudorefs\n> \n> Documentation/config/core.adoc                |  2 +-\n> Documentation/for-each-ref-options.adoc       |  2 +-\n> Documentation/git-ls-remote.adoc              |  4 +-\n> Documentation/git-update-ref.adoc             | 13 ++-\n> Documentation/git-worktree.adoc               |  9 +-\n> Documentation/glossary-content.adoc           | 86 ++++++++++++-------\n> Documentation/rev-list-options.adoc           |  4 +-\n\nI wonder if gitdatamodel.adoc should also cover pseudorefs and root refs\nin the “References” section. Right now it only says something extremely\nshort on the topic:\n\n  Git may also create references other than HEAD at the base of\n  the hierarchy, like ORIG_HEAD.\n\n\n> builtin/for-each-ref.c                        |  2 +-\n> contrib/completion/git-completion.bash        |  7 +-\n> refs.h                                        | 13 ++-\n> revision.c                                    |  2 +-\n> t/show-ref-exists-tests.sh                    |  4 +-\n> t/t0600-reffiles-backend.sh                   |  2 +-\n> t/t0610-reftable-basics.sh                    |  2 +-\n> t/t1400-update-ref.sh                         | 66 +++++++-------\n> t/t1403-show-ref.sh                           |  2 +-\n> t/t1460-refs-migrate.sh                       |  4 +-\n> t/t4013-diff-various.sh                       |  6 +-\n> ...iff.diff-tree_--cc_--summary_REVERSE_HEAD} |  2 +-\n> t/t6302-for-each-ref-filter.sh                |  4 +-\n> t/t9902-completion.sh                         |  4 +-\n> 21 files changed, 129 insertions(+), 111 deletions(-)\n> rename t/t4013/{diff.diff-tree_--cc_--summary_REVERSE => diff.diff-tree_--cc_--summary_REVERSE_HEAD} (73%)\n> \n> \n> base-commit: 7e34d8174dcda3435d1404c9a30bef46afe6c13e\n> -- \n> 2.56.0\n> \n> \n\n\n"},{"id":"557095","messageId":"20261101230326.3675597-1-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH v2 0/5] root refs vs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:03:18Z","receivedAt":"2026-11-01T23:03:18Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"Changes in v2:\n- Tweak pseudoref definition.\n- Add prefix lists for per-worktree refs.\n- Wrap lists in '--'.\n- Add root refs and pseudorefs to ref types in gitdatamodel(7).\n- Trim messages of commits 3 and 4.\n\nAndy Koppe (5):\n  doc: glossary: add root ref and clarify pseudorefs\n  doc: glossary: update per-worktree refs\n  doc: stop calling root refs pseudorefs\n  refs: stop calling root refs pseudorefs\n  t: stop calling root refs pseudorefs\n\n Documentation/config/core.adoc                |  2 +-\n Documentation/for-each-ref-options.adoc       |  2 +-\n Documentation/git-ls-remote.adoc              |  4 +-\n Documentation/git-update-ref.adoc             | 13 ++-\n Documentation/git-worktree.adoc               | 13 ++-\n Documentation/gitdatamodel.adoc               | 18 +++-\n Documentation/glossary-content.adoc           | 98 ++++++++++++-------\n Documentation/rev-list-options.adoc           |  4 +-\n builtin/for-each-ref.c                        |  2 +-\n contrib/completion/git-completion.bash        |  7 +-\n refs.h                                        | 13 ++-\n revision.c                                    |  2 +-\n t/show-ref-exists-tests.sh                    |  4 +-\n t/t0600-reffiles-backend.sh                   |  2 +-\n t/t0610-reftable-basics.sh                    |  2 +-\n t/t1400-update-ref.sh                         | 66 ++++++-------\n t/t1403-show-ref.sh                           |  2 +-\n t/t1460-refs-migrate.sh                       |  4 +-\n t/t4013-diff-various.sh                       |  6 +-\n ...iff.diff-tree_--cc_--summary_REVERSE_HEAD} |  2 +-\n t/t6302-for-each-ref-filter.sh                |  4 +-\n t/t9902-completion.sh                         |  4 +-\n 22 files changed, 158 insertions(+), 116 deletions(-)\n rename t/t4013/{diff.diff-tree_--cc_--summary_REVERSE => diff.diff-tree_--cc_--summary_REVERSE_HEAD} (73%)\n\nRange-diff against v1:\n1:  9979ad1589 ! 1:  4734fa2f2e doc: glossary: add root ref and clarify pseudorefs\n    @@ Documentation/glossary-content.adoc: exclude;;\n     -\tA ref that has different semantics than normal refs. These refs can be\n     -\tread via normal Git commands, but cannot be written to by commands like\n     -\tlinkgit:git-update-ref[1].\n    -+\tA name that can be used like a <<def_ref,ref>> to refer to\n    -+\t<<def_object,objects>>, but that is not stored in the same way as refs.\n    -+\tFor example, pseudorefs cannot be written by linkgit:git-update-ref[1]\n    -+\tor listed by linkgit:git-for-each-ref[1].\n    ++\tA name that can be used like a <<def_ref,ref>> to refer to an\n    ++\t<<def_object,object>>, but that is not stored in the same way as refs.\n    ++\tFor example, pseudorefs can be passed to linkgit:git-log[1] or\n    ++\tlinkgit:git-diff[1], but they cannot be written by\n    ++\tlinkgit:git-update-ref[1] or listed by linkgit:git-for-each-ref[1].\n      +\n      The following pseudorefs are known to Git:\n    - \n    -@@ Documentation/glossary-content.adoc: The following pseudorefs are known to Git:\n    +-\n    +++\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     ++\n     +When a pseudoref contains more than one object ID, using it to refer to an\n     +object yields the first one.\n2:  8932691331 ! 2:  62f4ae009c doc: glossary: update per-worktree refs\n    @@ Documentation/glossary-content.adoc: exclude;;\n     -\tthat start with `refs/bisect/`, but might later include other\n     -\tunusual refs.\n     +\tA <<def_ref,ref>> that is specific to a <<def_worktree,worktree>>,\n    -+\trather than shared between all worktrees. All\n    -+\t<<def_root_ref,root refs>>, such as <<def_HEAD,HEAD>>, are\n    -+\tper-worktree, as are refs that start with `refs/bisect/`,\n    -+\t`refs/worktree/` or `refs/rewritten/`.\n    ++\trather than shared between all worktrees. All <<def_root_ref,root refs>>,\n    ++\tfor example <<def_HEAD,HEAD>> and `ORIG_HEAD`, are per-worktree, as are\n    ++\trefs with the following prefixes:\n    +++\n    ++--\n    ++ - `refs/bisect/`\n    ++\n    ++ - `refs/rewritten/`\n    ++\n    ++ - `refs/worktree/`\n    ++--\n     ++\n     +<<def_pseudoref,Pseudorefs>> are also specific to each worktree.\n     ++\n    @@ Documentation/glossary-content.adoc: exclude;;\n     +per-worktree refs of other worktrees.\n      \n      [[def_pseudoref]]pseudoref::\n    - \tA name that can be used like a <<def_ref,ref>> to refer to\n    + \tA name that can be used like a <<def_ref,ref>> to refer to an\n     @@ Documentation/glossary-content.adoc: and pseudorefs.\n      \t\"working tree\" and repository metadata, most of which are\n      \tshared among other worktrees of a single repository, and\n      \tsome of which are maintained separately per worktree\n     -\t(e.g. the index, HEAD and pseudorefs like MERGE_HEAD,\n     -\tper-worktree refs and per-worktree configuration file).\n    -+\t(e.g. the index, per-worktree refs such as HEAD, pseudorefs\n    -+\tsuch as MERGE_HEAD, and the per-worktree configuration file).\n    ++\t(e.g. the index, <<def_per_worktree_ref,per-worktree refs>>, and the\n    ++\tper-worktree configuration file).\n3:  8cdd299c28 ! 3:  73dcb3d715 doc: stop calling root refs pseudorefs\n    @@ Commit message\n         there, correct \"symbolics\" to \"symbolic refs\".\n     \n         In git-update-ref(1), drop the incorrect mention of ORIG_HEAD. HEAD is\n    -    the only root ref that gets a reflog by default. Also restructure the\n    -    sentence to make it easier to parse.\n    +    the only root ref that gets a reflog by default. In the description of\n    +    core.logAllRefUpdates, correct the claim that \"always\" only applies to\n    +    refs under \"refs/\". It applies to root refs too.\n     \n    -    In the description of core.logAllRefUpdates, correct the claim that\n    -    \"always\" only applies to refs under \"refs/\". It applies to root refs\n    -    too.\n    +    Distinguish root refs and pseudorefs in gitdatamodel(7). Lowercase the\n    +    \"other references\" list entry there to match the others.\n     \n         Signed-off-by: Andy Koppe <andy.koppe@gmail.com>\n     \n    @@ Documentation/git-worktree.adoc: but others are specific to an individual worktr\n     -however: refs inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are\n     -not shared.\n     +In general, all root refs and pseudorefs are per-worktree and all refs\n    -+starting with `refs/` are shared. There are exceptions, however: refs\n    -+inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are not\n    -+shared.\n    ++starting with `refs/` are shared, except for refs with the following prefixes:\n    ++\n    ++ - `refs/bisect/`\n    ++\n    ++ - `refs/rewritten/`\n    ++\n    ++ - `refs/worktree/`\n      \n      Refs that are per-worktree can still be accessed from another worktree via\n      two special paths, `main-worktree` and `worktrees`. The former gives\n     \n    + ## Documentation/gitdatamodel.adoc ##\n    +@@ Documentation/gitdatamodel.adoc: remote-tracking branches: `refs/remotes/<remote>/<branch>`::\n    + default branch. This is the branch that `git clone` checks out by default.\n    + \n    + [[other-refs]]\n    +-Other references::\n    ++other references::\n    +     Git tools may create references anywhere under `refs/`.\n    +     For example, linkgit:git-stash[1], linkgit:git-bisect[1],\n    +     and linkgit:git-notes[1] all create their own references\n    +     in `refs/stash`, `refs/bisect`, etc.\n    +     Third-party Git tools may also create their own references.\n    +-+\n    +-Git may also create references other than `HEAD` at the base of the\n    +-hierarchy, like `ORIG_HEAD`.\n    ++\n    ++[[root-refs]]\n    ++root refs::\n    ++    Git may also create references other than `HEAD` at the root of the\n    ++    hierarchy, for example `ORIG_HEAD` or `REBASE_HEAD`.\n    ++\n    ++[[pseudorefs]]\n    ++pseudorefs::\n    ++    linkgit:git-fetch[1] and linkgit:git-merge[1] create the so-called\n    ++    pseudorefs `FETCH_HEAD` and `MERGE_HEAD`. They are not references as\n    ++    such, as they are not stored in the same way, but they can nevertheless\n    ++    be used to refer to objects in commands such as linkgit:git-log[1] and\n    ++    linkgit:git-diff[1].\n    + \n    + NOTE: Git may delete objects that aren't \"reachable\" from any reference\n    + or <<reflogs,reflog>>.\n    +\n      ## Documentation/rev-list-options.adoc ##\n     @@ Documentation/rev-list-options.adoc: Under `--pretty=reference`, this information will not be shown at all.\n      \n4:  8bb6d5435c ! 4:  eb3d64fa96 refs: stop calling root refs pseudorefs\n    @@ Commit message\n     \n         Some messages and comments in the code still use \"pseudoref\" in its old\n         sense of any ref outside of \"refs/\", whereas the glossary now calls\n    -    those root refs and restricts pseudorefs to FETCH_HEAD and MERGE_HEAD.\n    +    those root refs and restricts pseudorefs to FETCH_HEAD and MERGE_HEAD,\n    +    so update them.\n     \n    -    Replace \"pseudoref\" with \"root ref\" in the help text for\n    -    \"git for-each-ref --include-root-refs\", in comments in refs.h, and in\n    -    comments in the bash completion script, including the one for\n    -    __git_pseudoref_exists(), which is also used for root refs. The function\n    -    itself is not renamed. There is no obvious term covering both root refs\n    -    and pseudorefs, and the old name might need to be kept for third-party\n    -    completion scripts.\n    -\n    -    Update the description of pseudorefs in refs.h to match the glossary.\n    +    Bash completion function __git_pseudoref_exists() is not renamed, as\n    +    there is no obvious term covering both root refs and pseudorefs, and\n    +    third-party completion scripts might depend on the name.\n     \n         Signed-off-by: Andy Koppe <andy.koppe@gmail.com>\n     \n5:  c681a032ba = 5:  31022553a6 t: stop calling root refs pseudorefs\n-- \n2.56.0\n\n\n"},{"id":"557096","messageId":"20261101230326.3675597-2-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH v2 1/5] doc: glossary: add root ref and clarify pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:03:19Z","receivedAt":"2026-11-01T23:03:19Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"The glossary describes refs in the root of the ref hierarchy, but does\nnot actually name them \"root refs\", even though the term is used\nelsewhere, for example by \"git for-each-ref --include-root-refs\". It\nalso defines pseudorefs as refs, yet is_root_ref() deliberately excludes\nthem, as they are not stored or handled by the ref backends.\n\nAdd a \"root ref\" entry, moving the naming rules and the list of\nirregular root refs there from the \"ref\" entry. State that pseudorefs\nare not root refs, and refer to gitrevisions(7) for descriptions of\ncommonly used root refs and pseudorefs.\n\nReword the \"pseudoref\" entry so that it no longer calls them refs, add\ngit-for-each-ref(1) as another example of how they are not proper refs,\nand explain that using a pseudoref containing multiple object IDs yields\nthe first one.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n Documentation/glossary-content.adoc | 73 ++++++++++++++++++-----------\n 1 file changed, 45 insertions(+), 28 deletions(-)\n\ndiff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\nindex 52301a56dd..f905b4467b 100644\n--- a/Documentation/glossary-content.adoc\n+++ b/Documentation/glossary-content.adoc\n@@ -500,18 +500,25 @@ exclude;;\n \tunusual refs.\n \n [[def_pseudoref]]pseudoref::\n-\tA ref that has different semantics than normal refs. These refs can be\n-\tread via normal Git commands, but cannot be written to by commands like\n-\tlinkgit:git-update-ref[1].\n+\tA name that can be used like a <<def_ref,ref>> to refer to an\n+\t<<def_object,object>>, but that is not stored in the same way as refs.\n+\tFor example, pseudorefs can be passed to linkgit:git-log[1] or\n+\tlinkgit:git-diff[1], but they cannot be written by\n+\tlinkgit:git-update-ref[1] or listed by linkgit:git-for-each-ref[1].\n +\n The following pseudorefs are known to Git:\n-\n++\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++\n+When a pseudoref contains more than one object ID, using it to refer to an\n+object yields the first one.\n \n [[def_pull]]pull::\n \tPulling a <<def_branch,branch>> means to <<def_fetch,fetch>> it and\n@@ -562,33 +569,12 @@ The following pseudorefs are known to Git:\n +\n The ref namespace is hierarchical.\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---\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-+\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---\n- - `AUTO_MERGE`\n-\n- - `BISECT_EXPECTED_REV`\n-\n- - `NOTES_MERGE_PARTIAL`\n-\n- - `NOTES_MERGE_REF`\n-\n- - `MERGE_AUTOSTASH`\n---\n+the hierarchy, in which case they must follow the naming rules for\n+<<def_root_ref,root refs>>.\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+the `refs/tags/` hierarchy is used to represent local tags.\n +\n See also \"REFERENCES\" in linkgit:gitdatamodel[7].\n \n@@ -640,6 +626,37 @@ format. See linkgit:git-reflog[1] for details.\n \tTo throw away part of the development, i.e. to assign the\n \t<<def_head,head>> to an earlier <<def_revision,revision>>.\n \n+[[def_root_ref]]root ref::\n+\tA <<def_ref,ref>> located in the root of the ref hierarchy, outside\n+\t`refs/`. Root ref names must follow these rules:\n++\n+--\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++\n+The following irregular root refs are exceptions to these rules. This\n+list is exhaustive and shall not be extended in future:\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++\n+<<def_pseudoref,Pseudorefs>> are not considered root refs, even though\n+they follow the naming rules.\n++\n+See linkgit:gitrevisions[7] for descriptions of commonly used root refs\n+and pseudorefs.\n+\n [[def_SCM]]SCM::\n \tSource code management (tool).\n \n-- \n2.56.0\n\n\n"},{"id":"557097","messageId":"20261101230326.3675597-3-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH v2 2/5] doc: glossary: update per-worktree refs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:03:20Z","receivedAt":"2026-11-01T23:03:20Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"The \"per-worktree ref\" entry says that these are only HEAD and refs\nunder \"refs/bisect/\", but that has been out of date since a9be29c981\n(sequencer: make refs generated by the `label` command worktree-local,\n2018-04-25). All root refs are per-worktree, as are refs under\n\"refs/worktree/\" and \"refs/rewritten/\". Pseudorefs are specific to each\nworktree too.\n\nBring the list up to date, and refer to git-worktree(1) for how to\naccess per-worktree refs of other worktrees. In the \"worktree\" entry,\ndescribe HEAD as a per-worktree ref and MERGE_HEAD as a pseudoref,\nrather than listing them separately from per-worktree refs.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n Documentation/glossary-content.adoc | 25 +++++++++++++++++++------\n 1 file changed, 19 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\nindex f905b4467b..67fe49b26d 100644\n--- a/Documentation/glossary-content.adoc\n+++ b/Documentation/glossary-content.adoc\n@@ -494,10 +494,23 @@ exclude;;\n \tinterface than the <<def_plumbing,plumbing>>.\n \n [[def_per_worktree_ref]]per-worktree ref::\n-\tRefs that are per-<<def_worktree,worktree>>, rather than\n-\tglobal.  This is presently only <<def_HEAD,HEAD>> and any refs\n-\tthat start with `refs/bisect/`, but might later include other\n-\tunusual refs.\n+\tA <<def_ref,ref>> that is specific to a <<def_worktree,worktree>>,\n+\trather than shared between all worktrees. All <<def_root_ref,root refs>>,\n+\tfor example <<def_HEAD,HEAD>> and `ORIG_HEAD`, are per-worktree, as are\n+\trefs with the following prefixes:\n++\n+--\n+ - `refs/bisect/`\n+\n+ - `refs/rewritten/`\n+\n+ - `refs/worktree/`\n+--\n++\n+<<def_pseudoref,Pseudorefs>> are also specific to each worktree.\n++\n+See \"REFS\" in linkgit:git-worktree[1] for how to access the\n+per-worktree refs of other worktrees.\n \n [[def_pseudoref]]pseudoref::\n \tA name that can be used like a <<def_ref,ref>> to refer to an\n@@ -791,5 +804,5 @@ and pseudorefs.\n \t\"working tree\" and repository metadata, most of which are\n \tshared among other worktrees of a single repository, and\n \tsome of which are maintained separately per worktree\n-\t(e.g. the index, HEAD and pseudorefs like MERGE_HEAD,\n-\tper-worktree refs and per-worktree configuration file).\n+\t(e.g. the index, <<def_per_worktree_ref,per-worktree refs>>, and the\n+\tper-worktree configuration file).\n-- \n2.56.0\n\n\n"},{"id":"557098","messageId":"20261101230326.3675597-4-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH v2 3/5] doc: stop calling root refs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:03:21Z","receivedAt":"2026-11-01T23:03:21Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"Several manual pages still use \"pseudoref\" in its old sense of any ref\noutside of \"refs/\", which the glossary now calls a root ref. Pseudorefs\nhave since been restricted to FETCH_HEAD and MERGE_HEAD, which are not\nconsidered root refs.\n\nReplace \"pseudoref\" with \"root ref\" in those pages, giving HEAD and\nORIG_HEAD as examples. In git-worktree(1), say that both root refs and\npseudorefs are per-worktree, and drop the description of them as being\ndirectly under $GIT_DIR, because that does not apply to the reftable\nbackend.\n\nIn git-ls-remote(1), describe --refs as omitting refs outside \"refs/\",\nsince that is what it actually checks. In the example, refer to HEAD,\nwhich is the only root ref that git-upload-pack(1) advertises. While\nthere, correct \"symbolics\" to \"symbolic refs\".\n\nIn git-update-ref(1), drop the incorrect mention of ORIG_HEAD. HEAD is\nthe only root ref that gets a reflog by default. In the description of\ncore.logAllRefUpdates, correct the claim that \"always\" only applies to\nrefs under \"refs/\". It applies to root refs too.\n\nDistinguish root refs and pseudorefs in gitdatamodel(7). Lowercase the\n\"other references\" list entry there to match the others.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n Documentation/config/core.adoc          |  2 +-\n Documentation/for-each-ref-options.adoc |  2 +-\n Documentation/git-ls-remote.adoc        |  4 ++--\n Documentation/git-update-ref.adoc       | 13 ++++++-------\n Documentation/git-worktree.adoc         | 13 ++++++++-----\n Documentation/gitdatamodel.adoc         | 18 ++++++++++++++----\n Documentation/rev-list-options.adoc     |  4 ++--\n 7 files changed, 34 insertions(+), 22 deletions(-)\n\ndiff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc\nindex bdc74d291c..8f8fe36a58 100644\n--- a/Documentation/config/core.adoc\n+++ b/Documentation/config/core.adoc\n@@ -376,7 +376,7 @@ core.logAllRefUpdates::\n \t`refs/heads/`), remote refs (i.e. under `refs/remotes/`),\n \tnote refs (i.e. under `refs/notes/`), and the symbolic ref `HEAD`.\n \tIf it is set to `always`, then a missing reflog is automatically\n-\tcreated for any ref under `refs/`.\n+\tcreated for any ref, including root refs such as `ORIG_HEAD`.\n +\n This information can be used to determine what commit\n was the tip of a branch \"2 days ago\".\ndiff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc\nindex 54e2fa95c2..2b6d4ede0b 100644\n--- a/Documentation/for-each-ref-options.adoc\n+++ b/Documentation/for-each-ref-options.adoc\n@@ -73,7 +73,7 @@ TAB %(refname)`.\n \tusing the same rules as _<pattern>_ above.\n \n `--include-root-refs`::\n-\tList root refs (`HEAD` and pseudorefs) apart from regular refs.\n+\tAlso list root refs, such as `HEAD` or `ORIG_HEAD`.\n \n `--start-after=<marker>`::\n     Allows paginating the output by skipping references up to and including the\ndiff --git a/Documentation/git-ls-remote.adoc b/Documentation/git-ls-remote.adoc\nindex d71c4ab3e2..7ce7df3149 100644\n--- a/Documentation/git-ls-remote.adoc\n+++ b/Documentation/git-ls-remote.adoc\n@@ -35,7 +35,7 @@ OPTIONS\n \twith other git subcommands.\n \n --refs::\n-\tDo not show peeled tags or pseudorefs like `HEAD` in the output.\n+\tDo not show peeled tags or refs outside `refs/`.\n \n -q::\n --quiet::\n@@ -118,7 +118,7 @@ line shows the name of the object the tag points at.\n EXAMPLES\n --------\n \n-* List all references (including symbolics and pseudorefs), peeling\n+* List all references (including `HEAD` and symbolic refs), peeling\n   tags:\n +\n ----\ndiff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\nindex 37a5019a8b..c775334230 100644\n--- a/Documentation/git-update-ref.adoc\n+++ b/Documentation/git-update-ref.adoc\n@@ -163,13 +163,12 @@ still see a subset of the modifications.\n \n LOGGING UPDATES\n ---------------\n-If config parameter \"core.logAllRefUpdates\" is true and the ref is one\n-under \"refs/heads/\", \"refs/remotes/\", \"refs/notes/\", or a pseudoref\n-like HEAD or ORIG_HEAD; or the file \"$GIT_DIR/logs/<ref>\" exists then\n-`git update-ref` will append a line to the log file\n-\"$GIT_DIR/logs/<ref>\" (dereferencing all symbolic refs before creating\n-the log name) describing the change in ref value.  Log lines are\n-formatted as:\n+If config parameter \"core.logAllRefUpdates\" is true and the ref is\n+HEAD or one under \"refs/heads/\", \"refs/remotes/\" or \"refs/notes/\", or\n+if the file \"$GIT_DIR/logs/<ref>\" exists, then `git update-ref` will\n+append a line to the log file \"$GIT_DIR/logs/<ref>\" (dereferencing all\n+symbolic refs before creating the log name) describing the change in\n+ref value.  Log lines are formatted as:\n \n     oldsha1 SP newsha1 SP committer LF\n \ndiff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc\nindex e6e77252ab..ba620d8ace 100644\n--- a/Documentation/git-worktree.adoc\n+++ b/Documentation/git-worktree.adoc\n@@ -303,11 +303,14 @@ but others are specific to an individual worktree. One example is `HEAD`,\n which is different for each worktree. This section is about the sharing\n rules and how to access refs of one worktree from another.\n \n-In general, all pseudo refs are per-worktree and all refs starting with\n-`refs/` are shared. Pseudo refs are ones like `HEAD` which are directly\n-under `$GIT_DIR` instead of inside `$GIT_DIR/refs`. There are exceptions,\n-however: refs inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are\n-not shared.\n+In general, all root refs and pseudorefs are per-worktree and all refs\n+starting with `refs/` are shared, except for refs with the following prefixes:\n+\n+ - `refs/bisect/`\n+\n+ - `refs/rewritten/`\n+\n+ - `refs/worktree/`\n \n Refs that are per-worktree can still be accessed from another worktree via\n two special paths, `main-worktree` and `worktrees`. The former gives\ndiff --git a/Documentation/gitdatamodel.adoc b/Documentation/gitdatamodel.adoc\nindex 8d9be02036..6958ac033c 100644\n--- a/Documentation/gitdatamodel.adoc\n+++ b/Documentation/gitdatamodel.adoc\n@@ -211,15 +211,25 @@ remote-tracking branches: `refs/remotes/<remote>/<branch>`::\n default branch. This is the branch that `git clone` checks out by default.\n \n [[other-refs]]\n-Other references::\n+other references::\n     Git tools may create references anywhere under `refs/`.\n     For example, linkgit:git-stash[1], linkgit:git-bisect[1],\n     and linkgit:git-notes[1] all create their own references\n     in `refs/stash`, `refs/bisect`, etc.\n     Third-party Git tools may also create their own references.\n-+\n-Git may also create references other than `HEAD` at the base of the\n-hierarchy, like `ORIG_HEAD`.\n+\n+[[root-refs]]\n+root refs::\n+    Git may also create references other than `HEAD` at the root of the\n+    hierarchy, for example `ORIG_HEAD` or `REBASE_HEAD`.\n+\n+[[pseudorefs]]\n+pseudorefs::\n+    linkgit:git-fetch[1] and linkgit:git-merge[1] create the so-called\n+    pseudorefs `FETCH_HEAD` and `MERGE_HEAD`. They are not references as\n+    such, as they are not stored in the same way, but they can nevertheless\n+    be used to refer to objects in commands such as linkgit:git-log[1] and\n+    linkgit:git-diff[1].\n \n NOTE: Git may delete objects that aren't \"reachable\" from any reference\n or <<reflogs,reflog>>.\ndiff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc\nindex bd9f345690..822604278a 100644\n--- a/Documentation/rev-list-options.adoc\n+++ b/Documentation/rev-list-options.adoc\n@@ -350,8 +350,8 @@ Under `--pretty=reference`, this information will not be shown at all.\n \n `--merge`::\n \tShow commits touching conflicted paths in the range `HEAD...<other>`,\n-\twhere `<other>` is the first existing pseudoref in `MERGE_HEAD`,\n-\t`CHERRY_PICK_HEAD`, `REVERT_HEAD` or `REBASE_HEAD`. Only works\n+\twhere `<other>` is the first of `MERGE_HEAD`, `CHERRY_PICK_HEAD`,\n+\t`REVERT_HEAD` or `REBASE_HEAD` that exists. Only works\n \twhen the index has unmerged entries. This option can be used to show\n \trelevant commits when resolving conflicts from a 3-way merge.\n \n-- \n2.56.0\n\n\n"},{"id":"557099","messageId":"20261101230326.3675597-5-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH v2 4/5] refs: stop calling root refs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:03:22Z","receivedAt":"2026-11-01T23:03:22Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"Some messages and comments in the code still use \"pseudoref\" in its old\nsense of any ref outside of \"refs/\", whereas the glossary now calls\nthose root refs and restricts pseudorefs to FETCH_HEAD and MERGE_HEAD,\nso update them.\n\nBash completion function __git_pseudoref_exists() is not renamed, as\nthere is no obvious term covering both root refs and pseudorefs, and\nthird-party completion scripts might depend on the name.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n builtin/for-each-ref.c                 |  2 +-\n contrib/completion/git-completion.bash |  7 ++++---\n refs.h                                 | 13 ++++++-------\n revision.c                             |  2 +-\n 4 files changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c\nindex 4a2fc421db..6ff32e919c 100644\n--- a/builtin/for-each-ref.c\n+++ b/builtin/for-each-ref.c\n@@ -48,7 +48,7 @@ int for_each_ref_core(int argc, const char **argv, const char *prefix, struct re\n \t\tOPT_NO_CONTAINS(&filter.no_commit, N_(\"print only refs which don't contain the commit\")),\n \t\tOPT_BOOL(0, \"ignore-case\", &icase, N_(\"sorting and filtering are case insensitive\")),\n \t\tOPT_BOOL(0, \"stdin\", &from_stdin, N_(\"read reference patterns from stdin\")),\n-\t\tOPT_BOOL(0, \"include-root-refs\", &include_root_refs, N_(\"also include HEAD ref and pseudorefs\")),\n+\t\tOPT_BOOL(0, \"include-root-refs\", &include_root_refs, N_(\"also include root refs\")),\n \t\tOPT_END(),\n \t};\n \ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex c3a49e2e22..e12ac0359c 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -147,8 +147,9 @@ __git_eread ()\n \ttest -r \"$1\" && IFS=$'\\r\\n' read -r \"$2\" <\"$1\"\n }\n \n-# Runs git in $__git_repo_path to determine whether a pseudoref exists.\n-# 1: The pseudo-ref to search\n+# Runs git in $__git_repo_path to determine whether a root ref or\n+# pseudoref exists.\n+# 1: The ref to check\n __git_pseudoref_exists ()\n {\n \tlocal ref=$1\n@@ -954,7 +955,7 @@ __git_refs ()\n \tesac\n }\n \n-# Completes refs, short and long, local and remote, symbolic and pseudo.\n+# Completes refs, short and long, local and remote, symbolic, root and pseudo.\n #\n # Usage: __git_complete_refs [<option>]...\n # --remote=<remote>: The remote to list refs from, can be the name of a\ndiff --git a/refs.h b/refs.h\nindex ee3b8a62ef..3be7ef3b0e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -85,7 +85,7 @@ enum ref_transaction_error {\n  * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/\n  * directory and do not consist of all caps and underscores cannot be\n  * resolved. The function returns NULL for such ref names.\n- * Caps and underscores refers to the pseudorefs, such as HEAD,\n+ * Caps and underscores refers to root refs and pseudorefs, such as HEAD,\n  * FETCH_HEAD and friends, that all live outside of the refs/ directory.\n  */\n #define RESOLVE_REF_READING 0x01\n@@ -448,8 +448,8 @@ enum refs_for_each_flag {\n \tREFS_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2),\n \n \t/*\n-\t * Include root refs i.e. HEAD and pseudorefs along with the regular\n-\t * refs.\n+\t * Include root refs, such as HEAD and ORIG_HEAD, along with the\n+\t * regular refs.\n \t */\n \tREFS_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3),\n };\n@@ -1306,10 +1306,9 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);\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+ * Pseudorefs are names that can be used like refs, but have different\n+ * semantics. They cannot be stored in the ref backend, but must always\n+ * be accessed via the filesystem. The following 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.\ndiff --git a/revision.c b/revision.c\nindex a83e499047..d6dd56faf1 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2000,7 +2000,7 @@ static const char *lookup_other_head(struct object_id *oid)\n \t\t\treturn other_head[i];\n \t\t}\n \n-\tdie(_(\"--merge requires one of the pseudorefs MERGE_HEAD, CHERRY_PICK_HEAD, REVERT_HEAD or REBASE_HEAD\"));\n+\tdie(_(\"--merge requires one of MERGE_HEAD, CHERRY_PICK_HEAD, REVERT_HEAD or REBASE_HEAD\"));\n }\n \n static void prepare_show_merge(struct rev_info *revs)\n-- \n2.56.0\n\n\n"},{"id":"557100","messageId":"20261101230326.3675597-6-andy.koppe@gmail.com","threadId":"66636","inReplyTo":"20261022212903.177261-1-andy.koppe@gmail.com","subject":"[PATCH v2 5/5] t: stop calling root refs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:03:23Z","receivedAt":"2026-11-01T23:03:23Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"Several test titles and comments still use \"pseudoref\" in its old sense\nof any ref outside of \"refs/\", which the glossary now calls root refs.\n\nReplace \"pseudoref\" with \"root ref\" there. In t1400 and t4013, rename\nthe root refs that the tests create from PSEUDOREF to ROOT_REF_HEAD and\nfrom REVERSE to REVERSE_HEAD, so that they follow the naming rules for\nroot refs. This includes renaming the expected output file in t4013.\n\nSome tests also still refer to FETCH_HEAD and MERGE_HEAD as \"special\nrefs\", which was the previous term for \"pseudorefs\". Correct them.\n\nSigned-off-by: Andy Koppe <andy.koppe@gmail.com>\n---\n t/show-ref-exists-tests.sh                    |  4 +-\n t/t0600-reffiles-backend.sh                   |  2 +-\n t/t0610-reftable-basics.sh                    |  2 +-\n t/t1400-update-ref.sh                         | 66 +++++++++----------\n t/t1403-show-ref.sh                           |  2 +-\n t/t1460-refs-migrate.sh                       |  4 +-\n t/t4013-diff-various.sh                       |  6 +-\n ...iff.diff-tree_--cc_--summary_REVERSE_HEAD} |  2 +-\n t/t6302-for-each-ref-filter.sh                |  4 +-\n t/t9902-completion.sh                         |  4 +-\n 10 files changed, 48 insertions(+), 48 deletions(-)\n rename t/t4013/{diff.diff-tree_--cc_--summary_REVERSE => diff.diff-tree_--cc_--summary_REVERSE_HEAD} (73%)\n\ndiff --git a/t/show-ref-exists-tests.sh b/t/show-ref-exists-tests.sh\nindex 1351f690eb..d4a021a0c6 100644\n--- a/t/show-ref-exists-tests.sh\n+++ b/t/show-ref-exists-tests.sh\n@@ -64,11 +64,11 @@ test_expect_success '--exists with directory fails with generic error' '\n \ttest_cmp expect err\n '\n \n-test_expect_success '--exists with non-existent special ref' '\n+test_expect_success '--exists with non-existent pseudoref' '\n \ttest_expect_code 2 ${git_show_ref_exists} FETCH_HEAD\n '\n \n-test_expect_success '--exists with existing special ref' '\n+test_expect_success '--exists with existing pseudoref' '\n \ttest_when_finished \"rm .git/FETCH_HEAD\" &&\n \tgit rev-parse HEAD >.git/FETCH_HEAD &&\n \t${git_show_ref_exists} FETCH_HEAD\ndiff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh\nindex 62b0851852..26385aab7d 100755\n--- a/t/t0600-reffiles-backend.sh\n+++ b/t/t0600-reffiles-backend.sh\n@@ -267,7 +267,7 @@ test_expect_success 'setup worktree' '\n \t)\n '\n \n-# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should\n+# Some refs (refs/bisect/*, root refs) are kept per worktree, so they should\n # only appear in the for-each-reflog output if it is called from the correct\n # worktree, which is exercised in this test. This test is poorly written for\n # multiple reasons: 1) it creates invalidly formatted log entries. 2) it uses\ndiff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\nindex a702745839..01a0310762 100755\n--- a/t/t0610-reftable-basics.sh\n+++ b/t/t0610-reftable-basics.sh\n@@ -1156,7 +1156,7 @@ test_expect_success 'worktree: adds worktree with detached HEAD' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'fetch: accessing FETCH_HEAD special ref works' '\n+test_expect_success 'fetch: accessing FETCH_HEAD pseudoref works' '\n \ttest_when_finished \"rm -rf repo sub\" &&\n \n \tgit init sub &&\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 1a164c96f9..95a617254b 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -503,61 +503,61 @@ test_expect_success 'git cat-file blob main@{2005-05-26 23:42}:F (expect OTHER)'\n \ttest OTHER = $(git cat-file blob \"main@{2005-05-26 23:42}:F\")\n '\n \n-# Test adding and deleting pseudorefs\n+# Test adding and deleting root refs\n \n-test_expect_success 'given old value for missing pseudoref, do not create' '\n-\ttest_must_fail git update-ref PSEUDOREF $A $B 2>err &&\n-\ttest_must_fail git rev-parse PSEUDOREF &&\n+test_expect_success 'given old value for missing root ref, do not create' '\n+\ttest_must_fail git update-ref ROOT_REF_HEAD $A $B 2>err &&\n+\ttest_must_fail git rev-parse ROOT_REF_HEAD &&\n \ttest_grep \"unable to resolve reference\" err\n '\n \n-test_expect_success 'create pseudoref' '\n-\tgit update-ref PSEUDOREF $A &&\n-\ttest $A = $(git show-ref -s --verify PSEUDOREF)\n+test_expect_success 'create root ref' '\n+\tgit update-ref ROOT_REF_HEAD $A &&\n+\ttest $A = $(git show-ref -s --verify ROOT_REF_HEAD)\n '\n \n-test_expect_success 'overwrite pseudoref with no old value given' '\n-\tgit update-ref PSEUDOREF $B &&\n-\ttest $B = $(git show-ref -s --verify PSEUDOREF)\n+test_expect_success 'overwrite root ref with no old value given' '\n+\tgit update-ref ROOT_REF_HEAD $B &&\n+\ttest $B = $(git show-ref -s --verify ROOT_REF_HEAD)\n '\n \n-test_expect_success 'overwrite pseudoref with correct old value' '\n-\tgit update-ref PSEUDOREF $C $B &&\n-\ttest $C = $(git show-ref -s --verify PSEUDOREF)\n+test_expect_success 'overwrite root ref with correct old value' '\n+\tgit update-ref ROOT_REF_HEAD $C $B &&\n+\ttest $C = $(git show-ref -s --verify ROOT_REF_HEAD)\n '\n \n-test_expect_success 'do not overwrite pseudoref with wrong old value' '\n-\ttest_must_fail git update-ref PSEUDOREF $D $E 2>err &&\n-\ttest $C = $(git show-ref -s --verify PSEUDOREF) &&\n+test_expect_success 'do not overwrite root ref with wrong old value' '\n+\ttest_must_fail git update-ref ROOT_REF_HEAD $D $E 2>err &&\n+\ttest $C = $(git show-ref -s --verify ROOT_REF_HEAD) &&\n \ttest_grep \"cannot lock ref.*expected\" err\n '\n \n-test_expect_success 'delete pseudoref' '\n-\tgit update-ref -d PSEUDOREF &&\n-\ttest_must_fail git show-ref -s --verify PSEUDOREF\n+test_expect_success 'delete root ref' '\n+\tgit update-ref -d ROOT_REF_HEAD &&\n+\ttest_must_fail git show-ref -s --verify ROOT_REF_HEAD\n '\n \n-test_expect_success 'do not delete pseudoref with wrong old value' '\n-\tgit update-ref PSEUDOREF $A &&\n-\ttest_must_fail git update-ref -d PSEUDOREF $B 2>err &&\n-\ttest $A = $(git show-ref -s --verify PSEUDOREF) &&\n+test_expect_success 'do not delete root ref with wrong old value' '\n+\tgit update-ref ROOT_REF_HEAD $A &&\n+\ttest_must_fail git update-ref -d ROOT_REF_HEAD $B 2>err &&\n+\ttest $A = $(git show-ref -s --verify ROOT_REF_HEAD) &&\n \ttest_grep \"cannot lock ref.*expected\" err\n '\n \n-test_expect_success 'delete pseudoref with correct old value' '\n-\tgit update-ref -d PSEUDOREF $A &&\n-\ttest_must_fail git show-ref -s --verify PSEUDOREF\n+test_expect_success 'delete root ref with correct old value' '\n+\tgit update-ref -d ROOT_REF_HEAD $A &&\n+\ttest_must_fail git show-ref -s --verify ROOT_REF_HEAD\n '\n \n-test_expect_success 'create pseudoref with old OID zero' '\n-\tgit update-ref PSEUDOREF $A $Z &&\n-\ttest $A = $(git show-ref -s --verify PSEUDOREF)\n+test_expect_success 'create root ref with old OID zero' '\n+\tgit update-ref ROOT_REF_HEAD $A $Z &&\n+\ttest $A = $(git show-ref -s --verify ROOT_REF_HEAD)\n '\n \n-test_expect_success 'do not overwrite pseudoref with old OID zero' '\n-\ttest_when_finished git update-ref -d PSEUDOREF &&\n-\ttest_must_fail git update-ref PSEUDOREF $B $Z 2>err &&\n-\ttest $A = $(git show-ref -s --verify PSEUDOREF) &&\n+test_expect_success 'do not overwrite root ref with old OID zero' '\n+\ttest_when_finished git update-ref -d ROOT_REF_HEAD &&\n+\ttest_must_fail git update-ref ROOT_REF_HEAD $B $Z 2>err &&\n+\ttest $A = $(git show-ref -s --verify ROOT_REF_HEAD) &&\n \ttest_grep \"already exists\" err\n '\n \ndiff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh\nindex 226b3ec0e8..38648fd560 100755\n--- a/t/t1403-show-ref.sh\n+++ b/t/t1403-show-ref.sh\n@@ -181,7 +181,7 @@ test_expect_success 'show-ref --verify HEAD' '\n \ttest_must_be_empty actual\n '\n \n-test_expect_success 'show-ref --verify pseudorefs' '\n+test_expect_success 'show-ref --verify root refs' '\n \tgit update-ref CHERRY_PICK_HEAD HEAD $ZERO_OID &&\n \ttest_when_finished \"git update-ref -d CHERRY_PICK_HEAD\" &&\n \tgit show-ref -s --verify HEAD >actual &&\ndiff --git a/t/t1460-refs-migrate.sh b/t/t1460-refs-migrate.sh\nindex ecf6411288..1f59d820a0 100755\n--- a/t/t1460-refs-migrate.sh\n+++ b/t/t1460-refs-migrate.sh\n@@ -170,7 +170,7 @@ do\n \t\t\ttest_cmp expect actual\n \t\t'\n \n-\t\ttest_expect_success \"$from_format -> $to_format: pseudo-refs\" '\n+\t\ttest_expect_success \"$from_format -> $to_format: root refs\" '\n \t\t\ttest_when_finished \"rm -rf repo\" &&\n \t\t\tgit init --ref-storage-format=$from_format repo &&\n \t\t\ttest_commit -C repo initial &&\n@@ -178,7 +178,7 @@ do\n \t\t\ttest_migration repo \"$to_format\"\n \t\t'\n \n-\t\ttest_expect_success \"$from_format -> $to_format: special refs are left alone\" '\n+\t\ttest_expect_success \"$from_format -> $to_format: pseudorefs are left alone\" '\n \t\t\ttest_when_finished \"rm -rf repo\" &&\n \t\t\tgit init --ref-storage-format=$from_format repo &&\n \t\t\ttest_commit -C repo initial &&\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 21c9ad7c5d..43a9bf6d7c 100755\n--- a/t/t4013-diff-various.sh\n+++ b/t/t4013-diff-various.sh\n@@ -115,10 +115,10 @@ test_expect_success setup '\n \tgit checkout -f main &&\n \n \t# Same merge as main, but with parents reversed. Hide it in a\n-\t# pseudo-ref to avoid impacting tests with --all.\n+\t# root ref to avoid impacting tests with --all.\n \tcommit=$(echo reverse |\n \t\t git commit-tree -p main^2 -p main^1 main^{tree}) &&\n-\tgit update-ref REVERSE $commit &&\n+\tgit update-ref REVERSE_HEAD $commit &&\n \n \tgit config diff.renames false &&\n \n@@ -328,7 +328,7 @@ diff-tree --cc --stat --summary main\n diff-tree -c --stat --summary side\n diff-tree --cc --stat --summary side\n diff-tree --cc --shortstat main\n-diff-tree --cc --summary REVERSE\n+diff-tree --cc --summary REVERSE_HEAD\n # improved by Timo's patch\n diff-tree --cc --patch-with-stat main\n # improved by Timo's patch\ndiff --git a/t/t4013/diff.diff-tree_--cc_--summary_REVERSE b/t/t4013/diff.diff-tree_--cc_--summary_REVERSE_HEAD\nsimilarity index 73%\nrename from t/t4013/diff.diff-tree_--cc_--summary_REVERSE\nrename to t/t4013/diff.diff-tree_--cc_--summary_REVERSE_HEAD\nindex e208dd5682..0f86ae60db 100644\n--- a/t/t4013/diff.diff-tree_--cc_--summary_REVERSE\n+++ b/t/t4013/diff.diff-tree_--cc_--summary_REVERSE_HEAD\n@@ -1,4 +1,4 @@\n-$ git diff-tree --cc --summary REVERSE\n+$ git diff-tree --cc --summary REVERSE_HEAD\n 2562325a7ee916efb2481da93073b82cec801cbc\n  create mode 100644 file1\n  delete mode 100644 file2\ndiff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh\nindex a2fc257f98..5a6d6fef55 100755\n--- a/t/t6302-for-each-ref-filter.sh\n+++ b/t/t6302-for-each-ref-filter.sh\n@@ -31,7 +31,7 @@ test_expect_success 'setup some history and refs' '\n \tgit update-ref refs/odd/spot main\n '\n \n-test_expect_success '--include-root-refs pattern prints pseudorefs' '\n+test_expect_success '--include-root-refs pattern prints root refs' '\n \tcat >expect <<-\\EOF &&\n \tHEAD\n \tORIG_HEAD\n@@ -52,7 +52,7 @@ 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+test_expect_success '--include-root-refs pattern does not print pseudorefs' '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n \t(\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex f6de74054a..1cb55f9301 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -2693,13 +2693,13 @@ test_expect_success 'checkout matches case insensitively with GIT_COMPLETION_IGN\n \t)\n '\n \n-test_expect_success 'checkout completes pseudo refs' '\n+test_expect_success 'checkout completes root refs' '\n \ttest_completion \"git checkout H\" <<-\\EOF\n \tHEAD Z\n \tEOF\n '\n \n-test_expect_success 'checkout completes pseudo refs case insensitively with GIT_COMPLETION_IGNORE_CASE' '\n+test_expect_success 'checkout completes root refs case insensitively with GIT_COMPLETION_IGNORE_CASE' '\n \t(\n \t\tGIT_COMPLETION_IGNORE_CASE=1 &&\n \t\ttest_completion \"git checkout h\" <<-\\EOF\n-- \n2.56.0\n\n\n"},{"id":"557101","messageId":"CAHWeT-aG1Uubq6eEXYk=TurnkhbF+tYh0dU0Xxhj4m4iQg5siw@mail.gmail.com","threadId":"66636","inReplyTo":"93D20D7C-E19B-4A41-AC10-4AFC8330E844@jvns.ca","subject":"Re: [PATCH 0/5] root refs vs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:07:32Z","receivedAt":"2026-11-01T23:07:32Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"On Fri, 23 Oct 2026 at 14:21, Julia Evans <julia@jvns.ca> wrote:\n> I wonder if gitdatamodel.adoc should also cover pseudorefs and root refs\n> in the “References” section. Right now it only says something extremely\n> short on the topic:\n>\n>   Git may also create references other than HEAD at the base of\n>   the hierarchy, like ORIG_HEAD.\n\nI've given it a go as part of commit 3 in v2.\n\nAndy\n\n"},{"id":"557102","messageId":"CAHWeT-aC9D0Oa0pR00rKeHNpMqfWhY5-vicASHwPRZn+J7vWaQ@mail.gmail.com","threadId":"66636","inReplyTo":"atsPsFnb5mrneAMb@pks.im","subject":"Re: [PATCH 1/5] doc: glossary: add root ref and clarify pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:18:45Z","receivedAt":"2026-11-01T23:18:45Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"On Fri, 23 Oct 2026 at 08:41, Patrick Steinhardt <ps@pks.im> wrote:\n> On Thu, Oct 22, 2026 at 10:28:49PM +0100, Andy Koppe wrote:\n> > diff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\n> > index 52301a56dd..b2c582254b 100644\n> > --- a/Documentation/glossary-content.adoc\n> > +++ b/Documentation/glossary-content.adoc\n> > @@ -500,9 +500,10 @@ exclude;;\n> >       unusual refs.\n> >\n> >  [[def_pseudoref]]pseudoref::\n> > -     A ref that has different semantics than normal refs. These refs can be\n> > -     read via normal Git commands, but cannot be written to by commands like\n> > -     linkgit:git-update-ref[1].\n> > +     A name that can be used like a <<def_ref,ref>> to refer to\n> > +     <<def_object,objects>>, but that is not stored in the same way as refs.\n> > +     For example, pseudorefs cannot be written by linkgit:git-update-ref[1]\n> > +     or listed by linkgit:git-for-each-ref[1].\n>\n> As Junio already mentioned, I think we should retain the information\n> about different semantics.\n>\n> I also think that the previous guidance around how pseudorefs can be\n> used is weakened, because now you only list examples of how they cannot\n> be used. Previously though we explicitly specified that the only\n> supported operation is reading the refs directly. So instead of giving\n> examples of how they cannot be used, I think we should continue to list\n> the few operations that _are_ possible.\n\nInstead of saying that they are different, I tried to describe how\nthey are different . But, yep, I struggled a bit with that.\n\nIn v2, I have added git-log and git-diff as examples of where\npseudorefs can be used like references.\n\nThanks,\nAndy\n\n"},{"id":"557103","messageId":"CAHWeT-be9+8vpjmj=NVYK3rjG5MrBqmSwuX+jJvvUhkKwvKF2w@mail.gmail.com","threadId":"66636","inReplyTo":"atsPvF6wVdugtwrH@pks.im","subject":"Re: [PATCH 2/5] doc: glossary: update per-worktree refs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:26:04Z","receivedAt":"2026-11-01T23:26:04Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"On Fri, 23 Oct 2026 at 08:41, Patrick Steinhardt <ps@pks.im> wrote:\n> On Thu, Oct 22, 2026 at 10:28:50PM +0100, Andy Koppe wrote:\n> > diff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\n> > index b2c582254b..eff6d23215 100644\n> > --- a/Documentation/glossary-content.adoc\n> > +++ b/Documentation/glossary-content.adoc\n> > @@ -494,10 +494,16 @@ exclude;;\n> >       interface than the <<def_plumbing,plumbing>>.\n> >\n> >  [[def_per_worktree_ref]]per-worktree ref::\n> > -     Refs that are per-<<def_worktree,worktree>>, rather than\n> > -     global.  This is presently only <<def_HEAD,HEAD>> and any refs\n> > -     that start with `refs/bisect/`, but might later include other\n> > -     unusual refs.\n> > +     A <<def_ref,ref>> that is specific to a <<def_worktree,worktree>>,\n> > +     rather than shared between all worktrees. All\n> > +     <<def_root_ref,root refs>>, such as <<def_HEAD,HEAD>>, are\n> > +     per-worktree, as are refs that start with `refs/bisect/`,\n> > +     `refs/worktree/` or `refs/rewritten/`.\n> > ++\n> > +<<def_pseudoref,Pseudorefs>> are also specific to each worktree.\n>\n> Should we maybe also turn this into an itemized list? Makes it easier to\n> see at a glance which rrefs are per-worktree, and it becomes easier to\n> extend going forward.\n\nDone in v2.\n\n> > +See \"REFS\" in linkgit:git-worktree[1] for how to access the\n> > +per-worktree refs of other worktrees.\n> >\n> >  [[def_pseudoref]]pseudoref::\n> >       A name that can be used like a <<def_ref,ref>> to refer to\n> > @@ -788,5 +794,5 @@ and pseudorefs.\n> >       \"working tree\" and repository metadata, most of which are\n> >       shared among other worktrees of a single repository, and\n> >       some of which are maintained separately per worktree\n> > -     (e.g. the index, HEAD and pseudorefs like MERGE_HEAD,\n> > -     per-worktree refs and per-worktree configuration file).\n> > +     (e.g. the index, per-worktree refs such as HEAD, pseudorefs\n> > +     such as MERGE_HEAD, and the per-worktree configuration file).\n>\n> This is a bit curious as you distinguish between per-worktree refs and\n> pseudorefs, even though you mention both of them as part of the\n> per-worktree ref section.\n\nIt's difficult, because pseudorefs are per-worktree, but they aren't\nrefs. I thought they should still be mentioned under \"per-worktree\nrefs\", without calling them refs.\n\n> I wonder whether we should simply refer to\n> that section without naming any examples.\n\nGood idea. Done in v2.\n\nAndy\n\n"},{"id":"557108","messageId":"CAHWeT-aa=6WByd4U9BB-6mMMUAxPQCC5GhyM1WMSSVVatvN0Tw@mail.gmail.com","threadId":"66636","inReplyTo":"atsPwpbD3YqmBds_@pks.im","subject":"Re: [PATCH 3/5] doc: stop calling root refs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:44:53Z","receivedAt":"2026-11-01T23:44:53Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"On Fri, 23 Oct 2026 at 08:42, Patrick Steinhardt <ps@pks.im> wrote:\n> On Thu, Oct 22, 2026 at 10:28:51PM +0100, Andy Koppe wrote:\n> > diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\n> > index 37a5019a8b..c775334230 100644\n> > --- a/Documentation/git-update-ref.adoc\n> > +++ b/Documentation/git-update-ref.adoc\n> > @@ -163,13 +163,12 @@ still see a subset of the modifications.\n> >\n> >  LOGGING UPDATES\n> >  ---------------\n> > -If config parameter \"core.logAllRefUpdates\" is true and the ref is one\n> > -under \"refs/heads/\", \"refs/remotes/\", \"refs/notes/\", or a pseudoref\n> > -like HEAD or ORIG_HEAD; or the file \"$GIT_DIR/logs/<ref>\" exists then\n> > -`git update-ref` will append a line to the log file\n> > -\"$GIT_DIR/logs/<ref>\" (dereferencing all symbolic refs before creating\n> > -the log name) describing the change in ref value.  Log lines are\n> > -formatted as:\n> > +If config parameter \"core.logAllRefUpdates\" is true and the ref is\n> > +HEAD or one under \"refs/heads/\", \"refs/remotes/\" or \"refs/notes/\", or\n> > +if the file \"$GIT_DIR/logs/<ref>\" exists, then `git update-ref` will\n>\n> It's kind of hard to understand how the ANDs and ORs play together.\n> Like, does the config parameter extend to all subsequent ORs? That's not\n> really a new issue though.\n>\n> > +append a line to the log file \"$GIT_DIR/logs/<ref>\" (dereferencing all\n>\n> Also, this infomation about \"$GIT_DIR/logs/<ref>\" is out-of-date, as the\n> reftable backend doesn't store its reflogs in there.\n>\n> I wonder whether we should maybe just stop explaining how\n> \"core.logAllRefUpdates\" works and instead refer to git-config(1).\n\nThe whole LOGGING UPDATES section in git-update-ref(1) seems outdated\nas it assumes ref files and SHA-256, but I think that's out of scope\nof this patch series.\n\n> > diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc\n> > index e6e77252ab..f0eb6d4137 100644\n> > --- a/Documentation/git-worktree.adoc\n> > +++ b/Documentation/git-worktree.adoc\n> > @@ -303,11 +303,10 @@ but others are specific to an individual worktree. One example is `HEAD`,\n> >  which is different for each worktree. This section is about the sharing\n> >  rules and how to access refs of one worktree from another.\n> >\n> > -In general, all pseudo refs are per-worktree and all refs starting with\n> > -`refs/` are shared. Pseudo refs are ones like `HEAD` which are directly\n> > -under `$GIT_DIR` instead of inside `$GIT_DIR/refs`. There are exceptions,\n> > -however: refs inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are\n> > -not shared.\n> > +In general, all root refs and pseudorefs are per-worktree and all refs\n> > +starting with `refs/` are shared. There are exceptions, however: refs\n> > +inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are not\n> > +shared.\n>\n> It might make sense to have a bulleted list here, same as I proposed in\n> a preceding patch.\n\nYep. Done in v2.\n\nAndy\n\n"},{"id":"557109","messageId":"CAHWeT-bNKp_1draVXjgqVHKLCaa5yS5Fr3agEhdaurXfy4owXg@mail.gmail.com","threadId":"66636","inReplyTo":"atsPx0Wn9tyMzpCl@pks.im","subject":"Re: [PATCH 4/5] refs: stop calling root refs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-01T23:46:20Z","receivedAt":"2026-11-01T23:46:20Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"On Fri, 23 Oct 2026 at 08:42, Patrick Steinhardt wrote:\n> On Thu, Oct 22, 2026 at 10:28:52PM +0100, Andy Koppe wrote:\n> > Replace \"pseudoref\" with \"root ref\" in the help text for\n> > \"git for-each-ref --include-root-refs\", in comments in refs.h, and in\n> > comments in the bash completion script, including the one for\n> > __git_pseudoref_exists(), which is also used for root refs.\n>\n> I feel like this sentence is somewhat pointless, as you basically\n> just explain the diff.\n>\n> > Update the description of pseudorefs in refs.h to match the glossary.\n>\n> And this is again info that can simply be dropped.\n\nFair. Dropped in v2.\n\nAndy\n\n"},{"id":"557214","messageId":"840bc117-75e9-4d63-882e-3831156871a3@app.fastmail.com","threadId":"66636","inReplyTo":"20261101230326.3675597-1-andy.koppe@gmail.com","subject":"Re: [PATCH v2 0/5] root refs vs pseudorefs","fromName":"Julia Evans","fromEmail":"julia@jvns.ca","sentAt":"2026-11-02T18:19:57Z","receivedAt":"2026-11-02T18:19:57Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"\n\nOn Sun, Nov 1, 2026, at 6:03 PM, Andy Koppe wrote:\n> Changes in v2:\n> - Tweak pseudoref definition.\n> - Add prefix lists for per-worktree refs.\n> - Wrap lists in '--'.\n> - Add root refs and pseudorefs to ref types in gitdatamodel(7).\n> - Trim messages of commits 3 and 4.\n>\n> Andy Koppe (5):\n>   doc: glossary: add root ref and clarify pseudorefs\n>   doc: glossary: update per-worktree refs\n>   doc: stop calling root refs pseudorefs\n>   refs: stop calling root refs pseudorefs\n>   t: stop calling root refs pseudorefs\n>\n>  Documentation/config/core.adoc                |  2 +-\n>  Documentation/for-each-ref-options.adoc       |  2 +-\n>  Documentation/git-ls-remote.adoc              |  4 +-\n>  Documentation/git-update-ref.adoc             | 13 ++-\n>  Documentation/git-worktree.adoc               | 13 ++-\n>  Documentation/gitdatamodel.adoc               | 18 +++-\n>  Documentation/glossary-content.adoc           | 98 ++++++++++++-------\n>  Documentation/rev-list-options.adoc           |  4 +-\n>  builtin/for-each-ref.c                        |  2 +-\n>  contrib/completion/git-completion.bash        |  7 +-\n>  refs.h                                        | 13 ++-\n>  revision.c                                    |  2 +-\n>  t/show-ref-exists-tests.sh                    |  4 +-\n>  t/t0600-reffiles-backend.sh                   |  2 +-\n>  t/t0610-reftable-basics.sh                    |  2 +-\n>  t/t1400-update-ref.sh                         | 66 ++++++-------\n>  t/t1403-show-ref.sh                           |  2 +-\n>  t/t1460-refs-migrate.sh                       |  4 +-\n>  t/t4013-diff-various.sh                       |  6 +-\n>  ...iff.diff-tree_--cc_--summary_REVERSE_HEAD} |  2 +-\n>  t/t6302-for-each-ref-filter.sh                |  4 +-\n>  t/t9902-completion.sh                         |  4 +-\n>  22 files changed, 158 insertions(+), 116 deletions(-)\n>  rename t/t4013/{diff.diff-tree_--cc_--summary_REVERSE => \n> diff.diff-tree_--cc_--summary_REVERSE_HEAD} (73%)\n>\n> Range-diff against v1:\n> 1:  9979ad1589 ! 1:  4734fa2f2e doc: glossary: add root ref and clarify \n> pseudorefs\n>     @@ Documentation/glossary-content.adoc: exclude;;\n>      -\tA ref that has different semantics than normal refs. These refs \n> can be\n>      -\tread via normal Git commands, but cannot be written to by \n> commands like\n>      -\tlinkgit:git-update-ref[1].\n>     -+\tA name that can be used like a <<def_ref,ref>> to refer to\n>     -+\t<<def_object,objects>>, but that is not stored in the same way \n> as refs.\n>     -+\tFor example, pseudorefs cannot be written by \n> linkgit:git-update-ref[1]\n>     -+\tor listed by linkgit:git-for-each-ref[1].\n>     ++\tA name that can be used like a <<def_ref,ref>> to refer to an\n>     ++\t<<def_object,object>>, but that is not stored in the same way as \n> refs.\n>     ++\tFor example, pseudorefs can be passed to linkgit:git-log[1] or\n>     ++\tlinkgit:git-diff[1], but they cannot be written by\n>     ++\tlinkgit:git-update-ref[1] or listed by \n> linkgit:git-for-each-ref[1].\n>       +\n>       The following pseudorefs are known to Git:\n>     - \n>     -@@ Documentation/glossary-content.adoc: The following pseudorefs \n> are known to Git:\n>     +-\n>     +++\n>     ++--\n>     +  - `FETCH_HEAD` is written by linkgit:git-fetch[1] or \n> linkgit:git-pull[1]. It\n>     +    may refer to multiple object IDs. Each object ID is annotated \n> 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>      ++\n>      +When a pseudoref contains more than one object ID, using it to refer to an\n>      +object yields the first one.\n> 2:  8932691331 ! 2:  62f4ae009c doc: glossary: update per-worktree refs\n>     @@ Documentation/glossary-content.adoc: exclude;;\n>      -\tthat start with `refs/bisect/`, but might later include other\n>      -\tunusual refs.\n>      +\tA <<def_ref,ref>> that is specific to a <<def_worktree,worktree>>,\n>     -+\trather than shared between all worktrees. All\n>     -+\t<<def_root_ref,root refs>>, such as <<def_HEAD,HEAD>>, are\n>     -+\tper-worktree, as are refs that start with `refs/bisect/`,\n>     -+\t`refs/worktree/` or `refs/rewritten/`.\n>     ++\trather than shared between all worktrees. All <<def_root_ref,root refs>>,\n>     ++\tfor example <<def_HEAD,HEAD>> and `ORIG_HEAD`, are per-worktree, as are\n>     ++\trefs with the following prefixes:\n>     +++\n>     ++--\n>     ++ - `refs/bisect/`\n>     ++\n>     ++ - `refs/rewritten/`\n>     ++\n>     ++ - `refs/worktree/`\n>     ++--\n>      ++\n>      +<<def_pseudoref,Pseudorefs>> are also specific to each worktree.\n>      ++\n>     @@ Documentation/glossary-content.adoc: exclude;;\n>      +per-worktree refs of other worktrees.\n>      \n>       [[def_pseudoref]]pseudoref::\n>     - \tA name that can be used like a <<def_ref,ref>> to refer to\n>     + \tA name that can be used like a <<def_ref,ref>> to refer to an\n>      @@ Documentation/glossary-content.adoc: and pseudorefs.\n>       \t\"working tree\" and repository metadata, most of which are\n>       \tshared among other worktrees of a single repository, and\n>       \tsome of which are maintained separately per worktree\n>      -\t(e.g. the index, HEAD and pseudorefs like MERGE_HEAD,\n>      -\tper-worktree refs and per-worktree configuration file).\n>     -+\t(e.g. the index, per-worktree refs such as HEAD, pseudorefs\n>     -+\tsuch as MERGE_HEAD, and the per-worktree configuration file).\n>     ++\t(e.g. the index, <<def_per_worktree_ref,per-worktree refs>>, and the\n>     ++\tper-worktree configuration file).\n> 3:  8cdd299c28 ! 3:  73dcb3d715 doc: stop calling root refs pseudorefs\n>     @@ Commit message\n>          there, correct \"symbolics\" to \"symbolic refs\".\n>     \n>          In git-update-ref(1), drop the incorrect mention of ORIG_HEAD. HEAD is\n>     -    the only root ref that gets a reflog by default. Also restructure the\n>     -    sentence to make it easier to parse.\n>     +    the only root ref that gets a reflog by default. In the description of\n>     +    core.logAllRefUpdates, correct the claim that \"always\" only applies to\n>     +    refs under \"refs/\". It applies to root refs too.\n>     \n>     -    In the description of core.logAllRefUpdates, correct the claim that\n>     -    \"always\" only applies to refs under \"refs/\". It applies to root refs\n>     -    too.\n>     +    Distinguish root refs and pseudorefs in gitdatamodel(7). Lowercase the\n>     +    \"other references\" list entry there to match the others.\n>     \n>          Signed-off-by: Andy Koppe <andy.koppe@gmail.com>\n>     \n>     @@ Documentation/git-worktree.adoc: but others are specific to an \n> individual worktr\n>      -however: refs inside `refs/bisect`, `refs/worktree` and \n> `refs/rewritten` are\n>      -not shared.\n>      +In general, all root refs and pseudorefs are per-worktree and all \n> refs\n>     -+starting with `refs/` are shared. There are exceptions, however: \n> refs\n>     -+inside `refs/bisect`, `refs/worktree` and `refs/rewritten` are not\n>     -+shared.\n>     ++starting with `refs/` are shared, except for refs with the \n> following prefixes:\n>     ++\n>     ++ - `refs/bisect/`\n>     ++\n>     ++ - `refs/rewritten/`\n>     ++\n>     ++ - `refs/worktree/`\n>      \n>       Refs that are per-worktree can still be accessed from another worktree via\n>       two special paths, `main-worktree` and `worktrees`. The former gives\n>     \n>     + ## Documentation/gitdatamodel.adoc ##\n>     +@@ Documentation/gitdatamodel.adoc: remote-tracking branches: \n> `refs/remotes/<remote>/<branch>`::\n>     + default branch. This is the branch that `git clone` checks out by \n> default.\n>     + \n>     + [[other-refs]]\n>     +-Other references::\n>     ++other references::\n>     +     Git tools may create references anywhere under `refs/`.\n>     +     For example, linkgit:git-stash[1], linkgit:git-bisect[1],\n>     +     and linkgit:git-notes[1] all create their own references\n>     +     in `refs/stash`, `refs/bisect`, etc.\n>     +     Third-party Git tools may also create their own references.\n>     +-+\n>     +-Git may also create references other than `HEAD` at the base of \n> the\n>     +-hierarchy, like `ORIG_HEAD`.\n>     ++\n>     ++[[root-refs]]\n>     ++root refs::\n>     ++    Git may also create references other than `HEAD` at the root \n> of the\n>     ++    hierarchy, for example `ORIG_HEAD` or `REBASE_HEAD`.\n>     ++\n>     ++[[pseudorefs]]\n>     ++pseudorefs::\n>     ++    linkgit:git-fetch[1] and linkgit:git-merge[1] create the \n> so-called\n>     ++    pseudorefs `FETCH_HEAD` and `MERGE_HEAD`. They are not \n> references as\n>     ++    such, as they are not stored in the same way, but they can \n> nevertheless\n\nWhen I look at my .git directory during a merge conflict, I see these\nfiles:\n\n$ ls .git | grep HEAD\nFETCH_HEAD\nHEAD\nMERGE_HEAD\nORIG_HEAD\n\nFrom the above, it sounds like ORIG_HEAD is a \"root ref\" and\nMERGE_HEAD is a \"pseudoref\", but naively it does look like\nthey're stored in the same way. What's the difference?\n\n"},{"id":"557229","messageId":"CAHWeT-ZdZ0o=_rPWFPcXqCyc-WV=fzmKVRVx2LetfQzgYkKO0w@mail.gmail.com","threadId":"66636","inReplyTo":"840bc117-75e9-4d63-882e-3831156871a3@app.fastmail.com","subject":"Re: [PATCH v2 0/5] root refs vs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-02T19:47:48Z","receivedAt":"2026-11-02T19:47:48Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"On Mon, 2 Nov 2026 at 18:20, Julia Evans wrote:\n> On Sun, Nov 1, 2026, at 6:03 PM, Andy Koppe wrote:\n\n> >     ++[[root-refs]]\n> >     ++root refs::\n> >     ++    Git may also create references other than `HEAD` at the root\n> > of the\n> >     ++    hierarchy, for example `ORIG_HEAD` or `REBASE_HEAD`.\n> >     ++\n> >     ++[[pseudorefs]]\n> >     ++pseudorefs::\n> >     ++    linkgit:git-fetch[1] and linkgit:git-merge[1] create the\n> > so-called\n> >     ++    pseudorefs `FETCH_HEAD` and `MERGE_HEAD`. They are not\n> > references as\n> >     ++    such, as they are not stored in the same way, but they can\n> > nevertheless\n>\n> When I look at my .git directory during a merge conflict, I see these\n> files:\n>\n> $ ls .git | grep HEAD\n> FETCH_HEAD\n> HEAD\n> MERGE_HEAD\n> ORIG_HEAD\n>\n> From the above, it sounds like ORIG_HEAD is a \"root ref\" and\n> MERGE_HEAD is a \"pseudoref\", but naively it does look like\n> they're stored in the same way. What's the difference?\n\nGood question. The differences certainly aren't obvious, and they\nbarely matter to users.\n\nRoot refs:\n- Can be stored in reftables instead of files in .git.\n- Contain either a single hash or a single symbolic ref such as 'ref:\nrefs/heads/main'.\n- Can be modified with 'git update-ref' (generally a bad idea for\nbuilt-in root refs) and listed with 'git for-each-ref' and similar\ncommands.\n\nPseudorefs:\n- Are always stored as files in .git.\n- May contain multiple hashes, and in the case of FETCH_HEAD, carry\nadditional information about where each hash came from.\n- Are only modified by the commands they serve (i.e. fetch, merge and\npull) and can't be listed.\n\nThe big thing they have in common, apart from the *_HEAD names, is\nthat you can pass both types to commands such as 'git log', 'git diff'\nor 'git checkout' to refer to a commit or tree, just like any ref\nunder refs/.\n\n"},{"id":"557234","messageId":"f194e2af-cffc-4867-9069-7070c86785da@app.fastmail.com","threadId":"66636","inReplyTo":"CAHWeT-ZdZ0o=_rPWFPcXqCyc-WV=fzmKVRVx2LetfQzgYkKO0w@mail.gmail.com","subject":"Re: [PATCH v2 0/5] root refs vs pseudorefs","fromName":"Julia Evans","fromEmail":"julia@jvns.ca","sentAt":"2026-11-02T21:00:48Z","receivedAt":"2026-11-02T21:00:48Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"\n\nOn Mon, Nov 2, 2026, at 2:47 PM, Andy Koppe wrote:\n> On Mon, 2 Nov 2026 at 18:20, Julia Evans wrote:\n>> On Sun, Nov 1, 2026, at 6:03 PM, Andy Koppe wrote:\n>\n>> >     ++[[root-refs]]\n>> >     ++root refs::\n>> >     ++    Git may also create references other than `HEAD` at the root\n>> > of the\n>> >     ++    hierarchy, for example `ORIG_HEAD` or `REBASE_HEAD`.\n>> >     ++\n>> >     ++[[pseudorefs]]\n>> >     ++pseudorefs::\n>> >     ++    linkgit:git-fetch[1] and linkgit:git-merge[1] create the\n>> > so-called\n>> >     ++    pseudorefs `FETCH_HEAD` and `MERGE_HEAD`. They are not\n>> > references as\n>> >     ++    such, as they are not stored in the same way, but they can\n>> > nevertheless\n>>\n>> When I look at my .git directory during a merge conflict, I see these\n>> files:\n>>\n>> $ ls .git | grep HEAD\n>> FETCH_HEAD\n>> HEAD\n>> MERGE_HEAD\n>> ORIG_HEAD\n>>\n>> From the above, it sounds like ORIG_HEAD is a \"root ref\" and\n>> MERGE_HEAD is a \"pseudoref\", but naively it does look like\n>> they're stored in the same way. What's the difference?\n>\n> Good question. The differences certainly aren't obvious, and they\n> barely matter to users.\n>\n> Root refs:\n> - Can be stored in reftables instead of files in .git.\n> - Contain either a single hash or a single symbolic ref such as 'ref:\n> refs/heads/main'.\n> - Can be modified with 'git update-ref' (generally a bad idea for\n> built-in root refs) and listed with 'git for-each-ref' and similar\n> commands.\n>\n> Pseudorefs:\n> - Are always stored as files in .git.\n> - May contain multiple hashes, and in the case of FETCH_HEAD, carry\n> additional information about where each hash came from.\n> - Are only modified by the commands they serve (i.e. fetch, merge and\n> pull) and can't be listed.\n>\n> The big thing they have in common, apart from the *_HEAD names, is\n> that you can pass both types to commands such as 'git log', 'git diff'\n> or 'git checkout' to refer to a commit or tree, just like any ref\n> under refs/.\n\nThat’s very helpful. I think it would make sense to include\n“may contain multiple hashes” at least, that on its own\nmakes it very clear what the difference is.\n\nMy impression is that FETCH_HEAD specifically is a\nrelatively popular pseudoref and folks would be interested to\nknow that it can contain multiple hashes.\n\nIf a pseudoref contains multiple hashes and you pass it to \na command, will the command use the first hash?\n\nAlso are FETCH_HEAD and MERGE_HEAD the only\npseudorefs?\n\n"},{"id":"557240","messageId":"xmqqcxsmhpkj.fsf@gitster.g","threadId":"66636","inReplyTo":"840bc117-75e9-4d63-882e-3831156871a3@app.fastmail.com","subject":"Re: [PATCH v2 0/5] root refs vs pseudorefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-11-02T21:49:00Z","receivedAt":"2026-11-02T21:49:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Julia Evans\" <julia@jvns.ca> writes:\n\n... 200 lines of quoted material omitted ...\n\n>>     ++[[pseudorefs]]\n>>     ++pseudorefs::\n>>     ++    linkgit:git-fetch[1] and linkgit:git-merge[1] create the \n>> so-called\n>>     ++    pseudorefs `FETCH_HEAD` and `MERGE_HEAD`. They are not \n>> references as\n>>     ++    such, as they are not stored in the same way, but they can \n>> nevertheless\n>\n> When I look at my .git directory during a merge conflict, I see these\n> files:\n>\n> $ ls .git | grep HEAD\n> FETCH_HEAD\n> HEAD\n> MERGE_HEAD\n> ORIG_HEAD\n>\n> From the above, it sounds like ORIG_HEAD is a \"root ref\" and\n> MERGE_HEAD is a \"pseudoref\", but naively it does look like\n> they're stored in the same way. What's the difference?\n\nA pseudoref cannot be treated as a true ref because it can describe\nmultiple objects.  If you force it to be interpreted as an object,\nyou will still get one of the objects, but that is not the primary\nway it is designed to be used.  FETCH_HEAD even describes the nature\nof each object, e.g.,\n\na018953...4\t\tbranch 'maint' of gitolite.kernel.org:/pub/scm/git/git\n8d1b215...8\tnot-for-merge\tbranch 'master' of gitolite.kernel.org:/pub/scm/git/git\n882cbad...5\tnot-for-merge\tbranch 'next' of gitolite.kernel.org:/pub/scm/git/git\n4231a2f...4\tnot-for-merge\tbranch 'jch' of gitolite.kernel.org:/pub/scm/git/git\n2fa90fe...8\tnot-for-merge\tbranch 'seen' of gitolite.kernel.org:/pub/scm/git/git\n\nThe other kind, just like a normal ref, describes one and only one\nobject.  The only special trait it has is that it is outside the\nrefs/ hierarchy, so it may be a slight misnomer to call them \"root\"\nrefs.  We do accept $GIT_DIR/ORIG_HEAD as if it were a real ref, but\nif we did:\n\n        $ mkdir -p .git/foo/bar\n        $ git rev-parse HEAD~24 >.git/foo/bar/baz\n        $ git show -s foo/bar/baz\n\nwe would end up treating \"foo/bar/baz\" as if it were a real ref,\ntoo, with exactly the same mechanism used to treat ORIG_HEAD.\n\nThe only difference between .git/ORIG_HEAD and .git/foo/bar/baz in\nthe above example is that we _do_ create \"root\" refs ourselves,\nwhile we do not create such funky things like foo/bar/baz (hence\nthey do not deserve a name, and such \"use\" may suddenly break\nwithout notice).\n\nBy the way, *please* trim your quote.  Having to scroll past 200\nlines of material that are totally irrelevant to understanding your\ncomment is a real pain.\n\nThanks.\n\n"},{"id":"557243","messageId":"CAHWeT-YSK3xFA=Hztr3Uj5UW=L9JcC_0iOa5pUG-WGCpwng9vw@mail.gmail.com","threadId":"66636","inReplyTo":"f194e2af-cffc-4867-9069-7070c86785da@app.fastmail.com","subject":"Re: [PATCH v2 0/5] root refs vs pseudorefs","fromName":"Andy Koppe","fromEmail":"andy.koppe@gmail.com","sentAt":"2026-11-02T22:19:53Z","receivedAt":"2026-11-02T22:19:53Z","isPatch":true,"sender":{"key":"andy.koppe@gmail.com","avatar":"https://avatars.githubusercontent.com/u/223411?v=4"},"body":"On Mon, 2 Nov 2026 at 21:01, Julia Evans wrote:\n> On Mon, Nov 2, 2026, at 2:47 PM, Andy Koppe wrote:\n> > Root refs:\n> > - Can be stored in reftables instead of files in .git.\n> > - Contain either a single hash or a single symbolic ref such as 'ref:\n> > refs/heads/main'.\n> > - Can be modified with 'git update-ref' (generally a bad idea for\n> > built-in root refs) and listed with 'git for-each-ref' and similar\n> > commands.\n> >\n> > Pseudorefs:\n> > - Are always stored as files in .git.\n> > - May contain multiple hashes, and in the case of FETCH_HEAD, carry\n> > additional information about where each hash came from.\n> > - Are only modified by the commands they serve (i.e. fetch, merge and\n> > pull) and can't be listed.\n> >\n> > The big thing they have in common, apart from the *_HEAD names, is\n> > that you can pass both types to commands such as 'git log', 'git diff'\n> > or 'git checkout' to refer to a commit or tree, just like any ref\n> > under refs/.\n>\n> That’s very helpful. I think it would make sense to include\n> “may contain multiple hashes” at least, that on its own\n> makes it very clear what the difference is.\n>\n> My impression is that FETCH_HEAD specifically is a\n> relatively popular pseudoref and folks would be interested to\n> know that it can contain multiple hashes.\n>\n> If a pseudoref contains multiple hashes and you pass it to\n> a command, will the command use the first hash?\n>\n> Also are FETCH_HEAD and MERGE_HEAD the only\n> pseudorefs?\n\nI think the pseudoref glossary entry covers these questions. As it\nappears in 'man' after v2:\n\npseudoref\n    A name that can be used like a ref to refer to an object, but that\n    is not stored in the same way as refs. For example, pseudorefs can\n    be passed to git-log(1) or git-diff(1), but they cannot be written\n    by git-update-ref(1) or listed by git-for-each-ref(1).\n\n    The following pseudorefs are known to Git:\n\n    •   FETCH_HEAD is written by git-fetch(1) or git-pull(1). It may\n        refer to multiple object IDs. Each object ID is annotated with\n        metadata indicating where it was fetched from and its fetch\n        status.\n\n    •   MERGE_HEAD is written by git-merge(1) when resolving merge\n        conflicts. It contains all commit IDs which are being merged.\n\n    When a pseudoref contains more than one object ID, using it to\n    refer to an object yields the first one.\n\n"},{"id":"557298","messageId":"aunFqykTV0QHCmV3@pks.im","threadId":"66636","inReplyTo":"20261101230326.3675597-2-andy.koppe@gmail.com","subject":"Re: [PATCH v2 1/5] doc: glossary: add root ref and clarify pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-11-03T11:29:47Z","receivedAt":"2026-11-03T11:29:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Nov 01, 2026 at 11:03:19PM +0000, Andy Koppe wrote:\n> diff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\n> index 52301a56dd..f905b4467b 100644\n> --- a/Documentation/glossary-content.adoc\n> +++ b/Documentation/glossary-content.adoc\n> @@ -500,18 +500,25 @@ exclude;;\n>  \tunusual refs.\n>  \n>  [[def_pseudoref]]pseudoref::\n> -\tA ref that has different semantics than normal refs. These refs can be\n> -\tread via normal Git commands, but cannot be written to by commands like\n> -\tlinkgit:git-update-ref[1].\n> +\tA name that can be used like a <<def_ref,ref>> to refer to an\n> +\t<<def_object,object>>, but that is not stored in the same way as refs.\n> +\tFor example, pseudorefs can be passed to linkgit:git-log[1] or\n> +\tlinkgit:git-diff[1], but they cannot be written by\n> +\tlinkgit:git-update-ref[1] or listed by linkgit:git-for-each-ref[1].\n\nI'm still not entirely convinced by this change, as it only focusses on\nthe difference in storage. How about this instead:\n\n    [[def_pseudoref]]pseudoref::\n        A name that can be used like a <<def_ref,ref>> to refer to an\n        <<def_object,object>>, but that has different semantics: it can\n        record more than one object ID and may carry additional metadata.\n        In contrast to root refs, pseudorefs are therefore not stored or\n        handled by the ref backend. They can be passed to commands like\n        linkgit:git-log[1] or linkgit:git-diff[1], but cannot be written\n        by linkgit:git-update-ref[1] or listed by\n        linkgit:git-for-each-ref[1].\n\nPatrick\n\n"},{"id":"557299","messageId":"aunFsp6B2rNJCXTi@pks.im","threadId":"66636","inReplyTo":"20261101230326.3675597-3-andy.koppe@gmail.com","subject":"Re: [PATCH v2 2/5] doc: glossary: update per-worktree refs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-11-03T11:29:54Z","receivedAt":"2026-11-03T11:29:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Nov 01, 2026 at 11:03:20PM +0000, Andy Koppe wrote:\n[snip]\n> Bring the list up to date, and refer to git-worktree(1) for how to\n> access per-worktree refs of other worktrees. In the \"worktree\" entry,\n> describe HEAD as a per-worktree ref and MERGE_HEAD as a pseudoref,\n> rather than listing them separately from per-worktree refs.\n\nThis doesn't match what you're doing anymore, as you don't mention\nMERGE_HEAD at all anymore.\n\n> diff --git a/Documentation/glossary-content.adoc b/Documentation/glossary-content.adoc\n> index f905b4467b..67fe49b26d 100644\n> --- a/Documentation/glossary-content.adoc\n> +++ b/Documentation/glossary-content.adoc\n> @@ -791,5 +804,5 @@ and pseudorefs.\n>  \t\"working tree\" and repository metadata, most of which are\n>  \tshared among other worktrees of a single repository, and\n>  \tsome of which are maintained separately per worktree\n> -\t(e.g. the index, HEAD and pseudorefs like MERGE_HEAD,\n> -\tper-worktree refs and per-worktree configuration file).\n> +\t(e.g. the index, <<def_per_worktree_ref,per-worktree refs>>, and the\n> +\tper-worktree configuration file).\n\nPatrick\n\n"},{"id":"557300","messageId":"aunFublgJruOr1V5@pks.im","threadId":"66636","inReplyTo":"20261101230326.3675597-4-andy.koppe@gmail.com","subject":"Re: [PATCH v2 3/5] doc: stop calling root refs pseudorefs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-11-03T11:30:01Z","receivedAt":"2026-11-03T11:30:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Nov 01, 2026 at 11:03:21PM +0000, Andy Koppe wrote:\n> diff --git a/Documentation/gitdatamodel.adoc b/Documentation/gitdatamodel.adoc\n> index 8d9be02036..6958ac033c 100644\n> --- a/Documentation/gitdatamodel.adoc\n> +++ b/Documentation/gitdatamodel.adoc\n> @@ -211,15 +211,25 @@ remote-tracking branches: `refs/remotes/<remote>/<branch>`::\n>  default branch. This is the branch that `git clone` checks out by default.\n>  \n>  [[other-refs]]\n> -Other references::\n> +other references::\n>      Git tools may create references anywhere under `refs/`.\n>      For example, linkgit:git-stash[1], linkgit:git-bisect[1],\n>      and linkgit:git-notes[1] all create their own references\n>      in `refs/stash`, `refs/bisect`, etc.\n>      Third-party Git tools may also create their own references.\n> -+\n> -Git may also create references other than `HEAD` at the base of the\n> -hierarchy, like `ORIG_HEAD`.\n> +\n> +[[root-refs]]\n> +root refs::\n> +    Git may also create references other than `HEAD` at the root of the\n\nNit: I'd drop the \"also\" here. It made sense before, but it doesn't\nanymore now that this is its own list entry.\n\n> +    hierarchy, for example `ORIG_HEAD` or `REBASE_HEAD`.\n> +\n> +[[pseudorefs]]\n> +pseudorefs::\n> +    linkgit:git-fetch[1] and linkgit:git-merge[1] create the so-called\n> +    pseudorefs `FETCH_HEAD` and `MERGE_HEAD`. They are not references as\n> +    such, as they are not stored in the same way, but they can nevertheless\n> +    be used to refer to objects in commands such as linkgit:git-log[1] and\n> +    linkgit:git-diff[1].\n\nDo we maybe want to provide pointers to the glossary for those two\nentries?\n\nPatrick\n\n"},{"id":"557320","messageId":"d3878168-95b9-4b4b-bfd8-85d459122e5f@app.fastmail.com","threadId":"66636","inReplyTo":"CAHWeT-YSK3xFA=Hztr3Uj5UW=L9JcC_0iOa5pUG-WGCpwng9vw@mail.gmail.com","subject":"Re: [PATCH v2 0/5] root refs vs pseudorefs","fromName":"Julia Evans","fromEmail":"julia@jvns.ca","sentAt":"2026-11-03T14:36:17Z","receivedAt":"2026-11-03T14:36:17Z","isPatch":true,"sender":{"key":"julia@jvns.ca","avatar":"https://avatars.githubusercontent.com/u/817739?v=4"},"body":"\n> pseudoref\n>     A name that can be used like a ref to refer to an object, but that\n>     is not stored in the same way as refs. For example, pseudorefs can\n>     be passed to git-log(1) or git-diff(1), but they cannot be written\n>     by git-update-ref(1) or listed by git-for-each-ref(1).\n>\n>     The following pseudorefs are known to Git:\n>\n>     •   FETCH_HEAD is written by git-fetch(1) or git-pull(1). It may\n>         refer to multiple object IDs. Each object ID is annotated with\n>         metadata indicating where it was fetched from and its fetch\n>         status.\n\nI found \"Each object ID is annotated with metadata  indicating where it was\nfetched from and its fetch status.\" to be very hard to parse, and I had to\ngo look at .git/FETCH_HEAD myself to understand what it was talking about.\n\nIt might help to instead describe `FETCH_HEAD` with an example,\nespecially since we already aren't giving a lot of details. \nMaybe something like this:\n\n  Git has 2 special pseudorefs: `MERGE_HEAD` and `FETCH_HEAD`.\n  Unlike references (which can only contain an object ID or the name of\n  another reference), these have their own format:\n  \n  - `FETCH_HEAD` has a list of entries like this:\n    `d4c5f27105ee2e44366d718dbc5db95bbe99c714 not-for-merge branch 'example' of https://example.com/x/y`\n    with details about what was fetched by linkgit:git-fetch[1].\n  - `MERGE_HEAD` has a list of commit IDs\n  \n  These are stored as files in the `.git` directory. You can pass a pseudoref\n  to `git show`, `git log`, etc, and it will use the first ID in the list. \n  Pseudorefs can't be written by git-update-ref(1) or listed by\n  git-for-each-ref(1).\n\n\n"},{"id":"557348","messageId":"xmqqecd1939a.fsf@gitster.g","threadId":"66636","inReplyTo":"aunFqykTV0QHCmV3@pks.im","subject":"Re: [PATCH v2 1/5] doc: glossary: add root ref and clarify pseudorefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-11-03T18:30:25Z","receivedAt":"2026-11-03T18:30:25Z","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> I'm still not entirely convinced by this change, as it only focusses on\n> the difference in storage. How about this instead:\n>\n>     [[def_pseudoref]]pseudoref::\n>         A name that can be used like a <<def_ref,ref>> to refer to an\n>         <<def_object,object>>, but that has different semantics: it can\n>         record more than one object ID and may carry additional metadata.\n>         In contrast to root refs, pseudorefs are therefore not stored or\n>         handled by the ref backend. They can be passed to commands like\n>         linkgit:git-log[1] or linkgit:git-diff[1], but cannot be written\n>         by linkgit:git-update-ref[1] or listed by\n>         linkgit:git-for-each-ref[1].\n\nBetween the two I would prefer yours.  I would replace \"different\"\nwith \"additional\", because a pseudoref can be used as the name of\nthe first object it contains in a fully compatible way as any other\nrefs, I want to avoid \"different\" that implies \"there are additional\nthings it can do but there are things it cannot do\" Venn diagram.\n\nThen replace \"carry additional\" with \"carry extra\", just to avoid\nrepetitions of \"additional\" due to the earlier change.\n\n\n"}]}