{"thread":{"id":"64884","subject":"[PATCH] revisions: add @{default} shorthand for default branch","startedAt":"2026-01-29T15:25:54Z","lastAt":"2026-02-03T14:38:53Z","messageCount":32,"participants":["Harald Nordgren via GitGitGadget","Junio C Hamano","Harald Nordgren","Kristoffer Haugsbakk","Phillip Wood","D. Ben Knoble","Ben Knoble"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"534817","messageId":"pull.2183.git.git.1769700352081.gitgitgadget@gmail.com","threadId":"64884","inReplyTo":null,"subject":"[PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-01-29T15:25:52Z","receivedAt":"2026-01-29T15:25:54Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nGit already has shorthands like @{upstream} and @{push} to refer to\ntracking branches, but there is no convenient way to refer to the\ndefault branch of a repository (typically \"main\" or \"master\").\n\nUsers often want to switch to the default branch regardless of its\nname, especially when working across repositories with different\ndefault branch names. Currently they must either hardcode the branch\nname or query it via configuration, which is cumbersome.\n\nAdd a new @{default} shorthand that resolves to the default branch\nas determined by init.defaultBranch (or falls back to \"main\" or\n\"master\" depending on Git version). This allows users to write:\n\n  git checkout @{default}\n\ninstead of having to know or look up the default branch name.\n\nThe implementation follows the same pattern as @{upstream} and @{push},\nusing a new branch_get_default() function that queries the default\nbranch name and verifies it exists in the repository.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    revisions: add @{default} shorthand for default branch\n    \n    Git already has shorthands like @{upstream} and @{push} to refer to\n    tracking branches, but there is no convenient way to refer to the\n    default branch of a repository (typically \"main\" or \"master\").\n    \n    Users often want to switch to the default branch regardless of its name,\n    especially when working across repositories with different default\n    branch names. Currently they must either hardcode the branch name or\n    query it via configuration, which is cumbersome.\n    \n    Add a new @{default} shorthand that resolves to the default branch as\n    determined by init.defaultBranch (or falls back to \"main\" or \"master\"\n    depending on Git version). This allows users to write:\n    \n    git checkout @{default}\n    \n    instead of having to know or look up the default branch name.\n    \n    The implementation follows the same pattern as @{upstream} and @{push},\n    using a new branch_get_default() function that queries the default\n    branch name and verifies it exists in the repository.\n    \n    Signed-off-by: Harald Nordgren haraldnordgren@gmail.com\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2183%2FHaraldNordgren%2Fdefault_shorthand-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2183/HaraldNordgren/default_shorthand-v1\nPull-Request: https://github.com/git/git/pull/2183\n\n Documentation/revisions.adoc | 17 +++++++++++++++++\n object-name.c                | 21 ++++++++++++++++++++-\n remote.c                     | 12 ++++++++++++\n remote.h                     |  6 ++++++\n t/t1508-at-combinations.sh   |  1 +\n t/t2012-checkout-last.sh     |  6 ++++++\n 6 files changed, 62 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/revisions.adoc b/Documentation/revisions.adoc\nindex 6ea6c7cead..17bf42765f 100644\n--- a/Documentation/revisions.adoc\n+++ b/Documentation/revisions.adoc\n@@ -149,6 +149,23 @@ from one location and push to another. In a non-triangular workflow,\n This suffix is also accepted when spelled in uppercase, and means the same\n thing no matter the case.\n \n+'@\\{default\\}'::\n+  The suffix '@\\{default}' refers to the default branch of the repository,\n+  typically `main` or `master`. This is determined by the `init.defaultBranch`\n+  configuration option, or falls back to `main` (or `master` in older Git\n+  versions) if not configured. The default branch must exist in the repository\n+  for this syntax to work.\n++\n+Here's an example:\n++\n+------------------------------\n+$ git checkout @{default}\n+Switched to branch 'main'\n+\n+$ git rev-parse --symbolic-full-name @{default}\n+refs/heads/main\n+------------------------------\n+\n '<rev>{caret}[<n>]', e.g. 'HEAD{caret}, v1.5.1{caret}0'::\n   A suffix '{caret}' to a revision parameter means the first parent of\n   that commit object.  '{caret}<n>' means the <n>th parent (i.e.\ndiff --git a/object-name.c b/object-name.c\nindex 8b862c124e..34172f9f80 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -947,6 +947,12 @@ static inline int push_mark(const char *string, int len)\n \treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n }\n \n+static inline int default_mark(const char *string, int len)\n+{\n+\tconst char *suffix[] = { \"@{default}\" };\n+\treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n+}\n+\n static enum get_oid_result get_oid_1(struct repository *r, const char *name, int len, struct object_id *oid, unsigned lookup_flags);\n static int interpret_nth_prior_checkout(struct repository *r, const char *name, int namelen, struct strbuf *buf);\n \n@@ -998,7 +1004,8 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tif (!upstream_mark(str + at, len - at) &&\n-\t\t\t\t    !push_mark(str + at, len - at)) {\n+\t\t\t\t    !push_mark(str + at, len - at) &&\n+\t\t\t\t    !default_mark(str + at, len - at)) {\n \t\t\t\t\treflog_len = (len-1) - (at+2);\n \t\t\t\t\tlen = at;\n \t\t\t\t}\n@@ -1707,6 +1714,12 @@ static int branch_interpret_allowed(const char *refname, unsigned allowed)\n \treturn 0;\n }\n \n+static const char *branch_get_default_mark(struct branch *branch UNUSED,\n+\t\t\t\t\t   struct strbuf *err UNUSED)\n+{\n+\treturn branch_get_default_ref();\n+}\n+\n static int interpret_branch_mark(struct repository *r,\n \t\t\t\t const char *name, int namelen,\n \t\t\t\t int at, struct strbuf *buf,\n@@ -1798,6 +1811,12 @@ int repo_interpret_branch_name(struct repository *r,\n \t\t\t\t\t    options);\n \t\tif (len > 0)\n \t\t\treturn len;\n+\n+\t\tlen = interpret_branch_mark(r, name, namelen, at - name, buf,\n+\t\t\t\t\t    default_mark, branch_get_default_mark,\n+\t\t\t\t\t    options);\n+\t\tif (len > 0)\n+\t\t\treturn len;\n \t}\n \n \treturn -1;\ndiff --git a/remote.c b/remote.c\nindex b756ff6f15..2c829c8c34 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1961,6 +1961,18 @@ const char *branch_get_push(struct branch *branch, struct strbuf *err)\n \treturn branch->push_tracking_ref;\n }\n \n+const char *branch_get_default_ref(void)\n+{\n+\tstatic struct strbuf default_ref = STRBUF_INIT;\n+\tchar *default_branch_name;\n+\n+\tstrbuf_reset(&default_ref);\n+\tdefault_branch_name = repo_default_branch_name(the_repository, 1);\n+\tstrbuf_addf(&default_ref, \"refs/heads/%s\", default_branch_name);\n+\tfree(default_branch_name);\n+\treturn default_ref.buf;\n+}\n+\n static int ignore_symref_update(const char *refname, struct strbuf *scratch)\n {\n \treturn !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch);\ndiff --git a/remote.h b/remote.h\nindex 0ca399e183..5ebb27e173 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -366,6 +366,12 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err);\n  */\n const char *branch_get_push(struct branch *branch, struct strbuf *err);\n \n+/**\n+ * Return the fully-qualified refname of the default branch.\n+ * I.e., what \"@{default}\" would give you.\n+ */\n+const char *branch_get_default_ref(void);\n+\n /* Flags to match_refs. */\n enum match_refs_flags {\n \tMATCH_REFS_NONE\t\t= 0,\ndiff --git a/t/t1508-at-combinations.sh b/t/t1508-at-combinations.sh\nindex 87a4286414..09d888df53 100755\n--- a/t/t1508-at-combinations.sh\n+++ b/t/t1508-at-combinations.sh\n@@ -69,6 +69,7 @@ check \"@{-1}@{u}\" ref refs/heads/main\n check \"@{-1}@{u}@{1}\" commit main-one\n check \"@\" commit new-two\n check \"@@{u}\" ref refs/heads/upstream-branch\n+check \"@{default}\" ref refs/heads/main\n check \"@@/at-test\" ref refs/heads/@@/at-test\n test_have_prereq MINGW ||\n check \"@/at-test\" ref refs/heads/@/at-test\ndiff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh\nindex 1f6c4ed042..59999f0852 100755\n--- a/t/t2012-checkout-last.sh\n+++ b/t/t2012-checkout-last.sh\n@@ -27,6 +27,12 @@ test_cmp_symbolic_HEAD_ref () {\n \ttest_cmp expect actual\n }\n \n+test_expect_success '\"checkout @{default}\" switches to default branch' '\n+\tgit checkout @{default} &&\n+\ttest_cmp_symbolic_HEAD_ref main &&\n+\tgit checkout other\n+'\n+\n test_expect_success '\"checkout -\" switches back' '\n \tgit checkout - &&\n \ttest_cmp_symbolic_HEAD_ref main\n\nbase-commit: ea717645d199f6f1b66058886475db3e8c9330e9\n-- \ngitgitgadget\n"},{"id":"534835","messageId":"xmqq1pj8b22h.fsf@gitster.g","threadId":"64884","inReplyTo":"pull.2183.git.git.1769700352081.gitgitgadget@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-29T20:23:34Z","receivedAt":"2026-01-29T20:23:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> Git already has shorthands like @{upstream} and @{push} to refer to\n> tracking branches, but there is no convenient way to refer to the\n> default branch of a repository (typically \"main\" or \"master\").\n>\n> Users often want to switch to the default branch regardless of its\n> name, especially when working across repositories with different\n> default branch names. Currently they must either hardcode the branch\n> name or query it via configuration, which is cumbersome.\n>\n> Add a new @{default} shorthand that resolves to the default branch\n> as determined by init.defaultBranch (or falls back to \"main\" or\n> \"master\" depending on Git version). This allows users to write:\n>\n>   git checkout @{default}\n>\n> instead of having to know or look up the default branch name.\n>\n> The implementation follows the same pattern as @{upstream} and @{push},\n> using a new branch_get_default() function that queries the default\n> branch name and verifies it exists in the repository.\n\nBut @{upstream} and @{push} are inherently very different from what\nyou are adding, aren't they?  Asking for topic1@{upstream} and\ntopic2@{upstream} makes quite a lot of sense, because the meaning of\n@{upstream} depends on \"which branch's upstream are you talking\nabout???\".  But I suspect that asking for topic1@{default} and\nexpect it would be different from topic2@{default} is nonsense, as\n\"the default\" is not per branch but is an attribute of a repository.\nIn other words, <branch>@{default} may by itself be a nonsense\nquery.  Are you rejecting a non-empty <branch> that may appear\nbefore @{default} as an error?\n\nAfter cloning an upstream project, those who dislike the local\nbranch name 'master' often rename it to something else, like \n\n    $ git branch -m master main\n\nand be happy, without configuring \"init.defaultbranch\".  After all,\nthat configuration variable affects newly created repositories, so\nafter renaming 'master' to 'main', it is too late anyway.  In such a\nrepository, if you say @{default}, what should happen?  As 'master'\nbranch no longer exist, even though it is the @{default}, should it\nerror out?  Does your implementation error out?\n\nAlso I do not quite see how this would be useful in practice.  Given\nthat the names of local branches are under control of the local end\nuser and not upstream projects, I would imagine that the primary\nbranch used by a user is of per-user nature, not per repository.  In\nother words, instead of having to do \"git branch -m\" after cloning,\nyou may do \"git config --global init.defaultBranch\" just once and\nkeep using the same default name.  Under that condition, \"can I ask\nwhat default branch name this repository uses, so that I can work on\nthat branch\" is rarely needed, if you are writing a script to use in\nmany of your repositories, isn't it?\n\nSo, I am not sure.  I wouldn't mind too terribly if <name>@{default}\nis rejected, but I do not imagine many people using it.\n"},{"id":"534859","messageId":"20260130105954.59636-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"xmqq1pj8b22h.fsf@gitster.g","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-01-30T10:59:54Z","receivedAt":"2026-01-30T10:59:58Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"Thanks for your comments!\n\n> But @{upstream} and @{push} are inherently very different from what\n> you are adding, aren't they?  Asking for topic1@{upstream} and\n> topic2@{upstream} makes quite a lot of sense, because the meaning of\n> @{upstream} depends on \"which branch's upstream are you talking\n> about???\".  But I suspect that asking for topic1@{default} and\n> expect it would be different from topic2@{default} is nonsense, as\n> \"the default\" is not per branch but is an attribute of a repository.\n> In other words, <branch>@{default} may by itself be a nonsense\n> query.  Are you rejecting a non-empty <branch> that may appear\n> before @{default} as an error?\n\nI will update the code to treat 'new-branch@{default}' as nonsense, it's\nnot a case I thought about, and would never use 😅\n\n> After cloning an upstream project, those who dislike the local\n> branch name 'master' often rename it to something else, like \n> \n>     $ git branch -m master main\n\nI have never heard about anyone doing that. Isn't it more expected that\npeople keep whatever branch is on the remote? But regardless, I hope\nthere is a way to still make @{default} map to whatever your renamed your\ndefault branch to.\n\n> Given\n> that the names of local branches are under control of the local end\n> user and not upstream projects, I would imagine that the primary\n> branch used by a user is of per-user nature, not per repository.  In\n> other words, instead of having to do \"git branch -m\" after cloning,\n> you may do \"git config --global init.defaultBranch\" just once and\n> keep using the same default name.\n\nMy ratio on cloning other people's repo vs. create new repos is likely\n999/1, so I'm given the default names that maintainer chose. I have\ndefault branches called 'master', 'main' and 'develop'.\n\nYes it's possible to rename, but what this feature does is open up the\nconvenience of not having to bother with that.\n\nI have this script that I run many times a day. However it doesn't work\nwhen remote is not called 'origin', so I have another version for\n'upstream', etc:\n\n    git checkout $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')\n\nThat's uneccessary overhead, that could now be replaced with:\n\n    git checkout @{default}\n\n\nHarald\n"},{"id":"534860","messageId":"20260130111211.78639-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"20260130105954.59636-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-01-30T11:12:11Z","receivedAt":"2026-01-30T11:12:16Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"I realize now when looping over all repos on my machine that quite a few of\nthem error out with\n\n   error: pathspec '@{default}' did not match any file(s) known to git\n\nI would expect all of them to have a default branch set. Maybe this is a\nshowstopper 🤔\n\n\nHarald\n"},{"id":"534867","messageId":"pull.2183.v2.git.git.1769779599196.gitgitgadget@gmail.com","threadId":"64884","inReplyTo":"pull.2183.git.git.1769700352081.gitgitgadget@gmail.com","subject":"[PATCH v2] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-01-30T13:26:39Z","receivedAt":"2026-01-30T13:26:47Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nGit already has shorthands like @{upstream} and @{push} to refer to\ntracking branches, but there is no convenient way to refer to the\ndefault branch of a repository (typically \"main\" or \"master\").\n\nUsers often want to switch to the default branch regardless of its\nname, especially when working across repositories with different\ndefault branch names. Currently they must either hardcode the branch\nname or query it via configuration, which is cumbersome.\n\nAdd a new @{default} shorthand that resolves to the default branch\nas determined by init.defaultBranch (or falls back to \"main\" or\n\"master\" depending on Git version). This allows users to write:\n\n  git checkout @{default}\n\ninstead of having to know or look up the default branch name.\n\nThe implementation follows the same pattern as @{upstream} and @{push},\nusing a new branch_get_default() function that queries the default\nbranch name and verifies it exists in the repository.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    revisions: add @{default} shorthand for default branch\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2183%2FHaraldNordgren%2Fdefault_shorthand-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2183/HaraldNordgren/default_shorthand-v2\nPull-Request: https://github.com/git/git/pull/2183\n\nRange-diff vs v1:\n\n 1:  72b75e106d ! 1:  abe0f0c2b7 revisions: add @{default} shorthand for default branch\n     @@ object-name.c: int repo_interpret_branch_name(struct repository *r,\n       \t\tif (len > 0)\n       \t\t\treturn len;\n      +\n     ++\t\tif (default_mark(at, namelen - (at - name))) {\n     ++\t\t\tif (at - name > 0)\n     ++\t\t\t\treturn -1;\n     ++\t\t}\n     ++\n      +\t\tlen = interpret_branch_mark(r, name, namelen, at - name, buf,\n      +\t\t\t\t\t    default_mark, branch_get_default_mark,\n      +\t\t\t\t\t    options);\n     @@ t/t1508-at-combinations.sh: check \"@{-1}@{u}\" ref refs/heads/main\n       check \"@@/at-test\" ref refs/heads/@@/at-test\n       test_have_prereq MINGW ||\n       check \"@/at-test\" ref refs/heads/@/at-test\n     +@@ t/t1508-at-combinations.sh: nonsense \"@{0}@{0}\"\n     + nonsense \"@{1}@{u}\"\n     + nonsense \"HEAD@{-1}\"\n     + nonsense \"@{-1}@{-1}\"\n     ++nonsense \"new-branch@{default}\"\n     + \n     + # @{N} versus HEAD@{N}\n     + \n      \n       ## t/t2012-checkout-last.sh ##\n      @@ t/t2012-checkout-last.sh: test_cmp_symbolic_HEAD_ref () {\n\n\n Documentation/revisions.adoc | 17 +++++++++++++++++\n object-name.c                | 26 +++++++++++++++++++++++++-\n remote.c                     | 12 ++++++++++++\n remote.h                     |  6 ++++++\n t/t1508-at-combinations.sh   |  2 ++\n t/t2012-checkout-last.sh     |  6 ++++++\n 6 files changed, 68 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/revisions.adoc b/Documentation/revisions.adoc\nindex 6ea6c7cead..17bf42765f 100644\n--- a/Documentation/revisions.adoc\n+++ b/Documentation/revisions.adoc\n@@ -149,6 +149,23 @@ from one location and push to another. In a non-triangular workflow,\n This suffix is also accepted when spelled in uppercase, and means the same\n thing no matter the case.\n \n+'@\\{default\\}'::\n+  The suffix '@\\{default}' refers to the default branch of the repository,\n+  typically `main` or `master`. This is determined by the `init.defaultBranch`\n+  configuration option, or falls back to `main` (or `master` in older Git\n+  versions) if not configured. The default branch must exist in the repository\n+  for this syntax to work.\n++\n+Here's an example:\n++\n+------------------------------\n+$ git checkout @{default}\n+Switched to branch 'main'\n+\n+$ git rev-parse --symbolic-full-name @{default}\n+refs/heads/main\n+------------------------------\n+\n '<rev>{caret}[<n>]', e.g. 'HEAD{caret}, v1.5.1{caret}0'::\n   A suffix '{caret}' to a revision parameter means the first parent of\n   that commit object.  '{caret}<n>' means the <n>th parent (i.e.\ndiff --git a/object-name.c b/object-name.c\nindex 8b862c124e..254705d1b0 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -947,6 +947,12 @@ static inline int push_mark(const char *string, int len)\n \treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n }\n \n+static inline int default_mark(const char *string, int len)\n+{\n+\tconst char *suffix[] = { \"@{default}\" };\n+\treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n+}\n+\n static enum get_oid_result get_oid_1(struct repository *r, const char *name, int len, struct object_id *oid, unsigned lookup_flags);\n static int interpret_nth_prior_checkout(struct repository *r, const char *name, int namelen, struct strbuf *buf);\n \n@@ -998,7 +1004,8 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tif (!upstream_mark(str + at, len - at) &&\n-\t\t\t\t    !push_mark(str + at, len - at)) {\n+\t\t\t\t    !push_mark(str + at, len - at) &&\n+\t\t\t\t    !default_mark(str + at, len - at)) {\n \t\t\t\t\treflog_len = (len-1) - (at+2);\n \t\t\t\t\tlen = at;\n \t\t\t\t}\n@@ -1707,6 +1714,12 @@ static int branch_interpret_allowed(const char *refname, unsigned allowed)\n \treturn 0;\n }\n \n+static const char *branch_get_default_mark(struct branch *branch UNUSED,\n+\t\t\t\t\t   struct strbuf *err UNUSED)\n+{\n+\treturn branch_get_default_ref();\n+}\n+\n static int interpret_branch_mark(struct repository *r,\n \t\t\t\t const char *name, int namelen,\n \t\t\t\t int at, struct strbuf *buf,\n@@ -1798,6 +1811,17 @@ int repo_interpret_branch_name(struct repository *r,\n \t\t\t\t\t    options);\n \t\tif (len > 0)\n \t\t\treturn len;\n+\n+\t\tif (default_mark(at, namelen - (at - name))) {\n+\t\t\tif (at - name > 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\tlen = interpret_branch_mark(r, name, namelen, at - name, buf,\n+\t\t\t\t\t    default_mark, branch_get_default_mark,\n+\t\t\t\t\t    options);\n+\t\tif (len > 0)\n+\t\t\treturn len;\n \t}\n \n \treturn -1;\ndiff --git a/remote.c b/remote.c\nindex b756ff6f15..2c829c8c34 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1961,6 +1961,18 @@ const char *branch_get_push(struct branch *branch, struct strbuf *err)\n \treturn branch->push_tracking_ref;\n }\n \n+const char *branch_get_default_ref(void)\n+{\n+\tstatic struct strbuf default_ref = STRBUF_INIT;\n+\tchar *default_branch_name;\n+\n+\tstrbuf_reset(&default_ref);\n+\tdefault_branch_name = repo_default_branch_name(the_repository, 1);\n+\tstrbuf_addf(&default_ref, \"refs/heads/%s\", default_branch_name);\n+\tfree(default_branch_name);\n+\treturn default_ref.buf;\n+}\n+\n static int ignore_symref_update(const char *refname, struct strbuf *scratch)\n {\n \treturn !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch);\ndiff --git a/remote.h b/remote.h\nindex 0ca399e183..5ebb27e173 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -366,6 +366,12 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err);\n  */\n const char *branch_get_push(struct branch *branch, struct strbuf *err);\n \n+/**\n+ * Return the fully-qualified refname of the default branch.\n+ * I.e., what \"@{default}\" would give you.\n+ */\n+const char *branch_get_default_ref(void);\n+\n /* Flags to match_refs. */\n enum match_refs_flags {\n \tMATCH_REFS_NONE\t\t= 0,\ndiff --git a/t/t1508-at-combinations.sh b/t/t1508-at-combinations.sh\nindex 87a4286414..ff9f8e90cb 100755\n--- a/t/t1508-at-combinations.sh\n+++ b/t/t1508-at-combinations.sh\n@@ -69,6 +69,7 @@ check \"@{-1}@{u}\" ref refs/heads/main\n check \"@{-1}@{u}@{1}\" commit main-one\n check \"@\" commit new-two\n check \"@@{u}\" ref refs/heads/upstream-branch\n+check \"@{default}\" ref refs/heads/main\n check \"@@/at-test\" ref refs/heads/@@/at-test\n test_have_prereq MINGW ||\n check \"@/at-test\" ref refs/heads/@/at-test\n@@ -78,6 +79,7 @@ nonsense \"@{0}@{0}\"\n nonsense \"@{1}@{u}\"\n nonsense \"HEAD@{-1}\"\n nonsense \"@{-1}@{-1}\"\n+nonsense \"new-branch@{default}\"\n \n # @{N} versus HEAD@{N}\n \ndiff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh\nindex 1f6c4ed042..59999f0852 100755\n--- a/t/t2012-checkout-last.sh\n+++ b/t/t2012-checkout-last.sh\n@@ -27,6 +27,12 @@ test_cmp_symbolic_HEAD_ref () {\n \ttest_cmp expect actual\n }\n \n+test_expect_success '\"checkout @{default}\" switches to default branch' '\n+\tgit checkout @{default} &&\n+\ttest_cmp_symbolic_HEAD_ref main &&\n+\tgit checkout other\n+'\n+\n test_expect_success '\"checkout -\" switches back' '\n \tgit checkout - &&\n \ttest_cmp_symbolic_HEAD_ref main\n\nbase-commit: ea717645d199f6f1b66058886475db3e8c9330e9\n-- \ngitgitgadget\n"},{"id":"534891","messageId":"xmqq4io3831o.fsf@gitster.g","threadId":"64884","inReplyTo":"20260130105954.59636-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-30T16:42:59Z","receivedAt":"2026-01-30T16:43:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>> After cloning an upstream project, those who dislike the local\n>> branch name 'master' often rename it to something else, like \n>> \n>>     $ git branch -m master main\n>\n> I have never heard about anyone doing that. Isn't it more expected that\n> people keep whatever branch is on the remote? But regardless, I hope\n> there is a way to still make @{default} map to whatever your renamed your\n> default branch to.\n\nBut then that is what \"default\" is, isn't it?  The \"default\" branch\nis what \"git init\" would create unless it is told otherwise.  The\n'main' branch that the above example user renamed to to use because\nthey did not like the name 'master' is their primary branch that is\nnot the \"default\".\n\nIn a sense, I think what you are after _is_ \"what the user considers\nthe primary branch in this repository\".  How init.defaultBranch is\nconfigured in their global (i.e., per-user) configuration file may\nbe a good hint to help answering the question, but not necessarily.\n\nFor those who follow the naming the upstream decided to use in\ncloned repositories, init.defaultBranch is probably the last thing\nyou want to take as a hint, as these people decided to _ignore_ the\npreference of their own and instead to follow what upstream uses.\nFor that, refs/remotes/origin/HEAD would be a lot more stronger\nhint.  If they call their primary branch 'trunk', these people would\nwant to call theirs 'trunk'.  And for that, these people would not\ndo anything with init.defaultBranch.\n\n>     git checkout $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')\n>\n> That's uneccessary overhead, that could now be replaced with:\n>\n>     git checkout @{default}\n\nThat mirrors what I wrote in the previous paragraph.  Those who\nfollow the naming the upstream uses, refs/remotes/$remote/HEAD\n(here, \"origin\" may not be the default remote) would be a better\nhint than init.defaultBranch so calling it @{default} is misleading.\n\nI am not good at naming things, so instead of calling it @{primary}\nlet's call it @{dumbo}.  With the realization that what branch\nrefs/remotes/$remote/HEAD points at is a good source of hint, I\nactually think $branch@{dumbo} does make sense, and @{dumbo} should\nbe a short-hand for $branch@{dumbo} where the name of the current\nbranch is substituted for $branch (i.e. similar to @{push}, I\nsuppose).  As you may be interacting with two sets of branches that\ngo to two different remotes.\n\nIn any case, it is very different from what you implemented as the\n@{default} in your patch.\n\nThanks.\n\n"},{"id":"534895","messageId":"7b62316f-a30a-4895-808d-baa20be0f3af@app.fastmail.com","threadId":"64884","inReplyTo":"pull.2183.v2.git.git.1769779599196.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] revisions: add @{default} shorthand for default branch","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-01-30T16:54:19Z","receivedAt":"2026-01-30T16:54:40Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Fri, Jan 30, 2026, at 14:26, Harald Nordgren via GitGitGadget wrote:\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> Git already has shorthands like @{upstream} and @{push} to refer to\n> tracking branches, but there is no convenient way to refer to the\n> default branch of a repository (typically \"main\" or \"master\").\n\nI don’t use a lot of different repositories. But for the two I do use I\nuse `origin`. (Really `o` since I name the regular remote `o`.) Most of\nthe time I do not need to have the main *branch* as a branch. I am not\nworking on the main branch. Using the remote-tracking branch directly is\nmore convenient.\n\n> Users often want to switch to the default branch regardless of its\n> name, especially when working across repositories with different\n> default branch names. Currently they must either hardcode the branch\n> name or query it via configuration, which is cumbersome.\n\n*Query it* sounds like git-config(1). I have found `git var\nGIT_DEFAULT_BRANCH` useful for when I want to answer a question\nwithout hardcodig `main` or `master`.\n\n>\n> Add a new @{default} shorthand that resolves to the default branch\n> as determined by init.defaultBranch (or falls back to \"main\" or\n> \"master\" depending on Git version). This allows users to write:\n>\n>   git checkout @{default}\n>\n> instead of having to know or look up the default branch name.\n>\n> The implementation follows the same pattern as @{upstream} and @{push},\n> using a new branch_get_default() function that queries the default\n> branch name and verifies it exists in the repository.\n>\n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>[snip]\n"},{"id":"534905","messageId":"pull.2183.v3.git.git.1769805948018.gitgitgadget@gmail.com","threadId":"64884","inReplyTo":"pull.2183.v2.git.git.1769779599196.gitgitgadget@gmail.com","subject":"[PATCH v3] revisions: add @{primary} shorthand for primary branch","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-01-30T20:45:47Z","receivedAt":"2026-01-30T20:45:51Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nGit already has shorthands like @{upstream} and @{push} to refer to\ntracking branches, but there is no convenient way to refer to the\nprimary branch of a repository (typically \"main\" or \"master\").\n\nUsers often want to switch to the primary branch regardless of its\nname, especially when working across repositories with different\nprimary branch names. Currently they must either hardcode the branch\nname or query it via configuration, which is cumbersome.\n\nAdd a new @{primary} shorthand that resolves to the primary branch\nas determined by init.defaultBranch (or falls back to \"main\" or\n\"master\" depending on Git version). This allows users to write:\n\n  git checkout @{primary}\n\ninstead of having to know or look up the primary branch name.\n\nThe implementation follows the same pattern as @{upstream} and @{push},\nusing a new branch_get_primary_ref() function that queries the primary\nbranch name and verifies it exists in the repository.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    revisions: add @{default} shorthand for default branch\n    \n    cc: \"Kristoffer Haugsbakk\" kristofferhaugsbakk@fastmail.com\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2183%2FHaraldNordgren%2Fdefault_shorthand-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2183/HaraldNordgren/default_shorthand-v3\nPull-Request: https://github.com/git/git/pull/2183\n\nRange-diff vs v2:\n\n 1:  abe0f0c2b7 ! 1:  0fc9eb75ea revisions: add @{default} shorthand for default branch\n     @@ Metadata\n      Author: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## Commit message ##\n     -    revisions: add @{default} shorthand for default branch\n     +    revisions: add @{primary} shorthand for primary branch\n      \n          Git already has shorthands like @{upstream} and @{push} to refer to\n          tracking branches, but there is no convenient way to refer to the\n     -    default branch of a repository (typically \"main\" or \"master\").\n     +    primary branch of a repository (typically \"main\" or \"master\").\n      \n     -    Users often want to switch to the default branch regardless of its\n     +    Users often want to switch to the primary branch regardless of its\n          name, especially when working across repositories with different\n     -    default branch names. Currently they must either hardcode the branch\n     +    primary branch names. Currently they must either hardcode the branch\n          name or query it via configuration, which is cumbersome.\n      \n     -    Add a new @{default} shorthand that resolves to the default branch\n     +    Add a new @{primary} shorthand that resolves to the primary branch\n          as determined by init.defaultBranch (or falls back to \"main\" or\n          \"master\" depending on Git version). This allows users to write:\n      \n     -      git checkout @{default}\n     +      git checkout @{primary}\n      \n     -    instead of having to know or look up the default branch name.\n     +    instead of having to know or look up the primary branch name.\n      \n          The implementation follows the same pattern as @{upstream} and @{push},\n     -    using a new branch_get_default() function that queries the default\n     +    using a new branch_get_primary_ref() function that queries the primary\n          branch name and verifies it exists in the repository.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n     @@ Documentation/revisions.adoc: from one location and push to another. In a non-tr\n       This suffix is also accepted when spelled in uppercase, and means the same\n       thing no matter the case.\n       \n     -+'@\\{default\\}'::\n     -+  The suffix '@\\{default}' refers to the default branch of the repository,\n     ++'@\\{primary\\}'::\n     ++  The suffix '@\\{primary}' refers to the primary branch of the repository,\n      +  typically `main` or `master`. This is determined by the `init.defaultBranch`\n      +  configuration option, or falls back to `main` (or `master` in older Git\n     -+  versions) if not configured. The default branch must exist in the repository\n     ++  versions) if not configured. The primary branch must exist in the repository\n      +  for this syntax to work.\n      ++\n      +Here's an example:\n      ++\n      +------------------------------\n     -+$ git checkout @{default}\n     ++$ git checkout @{primary}\n      +Switched to branch 'main'\n      +\n     -+$ git rev-parse --symbolic-full-name @{default}\n     ++$ git rev-parse --symbolic-full-name @{primary}\n      +refs/heads/main\n      +------------------------------\n      +\n     @@ object-name.c: static inline int push_mark(const char *string, int len)\n       \treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n       }\n       \n     -+static inline int default_mark(const char *string, int len)\n     ++static inline int primary_mark(const char *string, int len)\n      +{\n     -+\tconst char *suffix[] = { \"@{default}\" };\n     ++\tconst char *suffix[] = { \"@{primary}\" };\n      +\treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n      +}\n      +\n     @@ object-name.c: static int get_oid_basic(struct repository *r, const char *str, i\n       \t\t\t\tif (!upstream_mark(str + at, len - at) &&\n      -\t\t\t\t    !push_mark(str + at, len - at)) {\n      +\t\t\t\t    !push_mark(str + at, len - at) &&\n     -+\t\t\t\t    !default_mark(str + at, len - at)) {\n     ++\t\t\t\t    !primary_mark(str + at, len - at)) {\n       \t\t\t\t\treflog_len = (len-1) - (at+2);\n       \t\t\t\t\tlen = at;\n       \t\t\t\t}\n     @@ object-name.c: static int branch_interpret_allowed(const char *refname, unsigned\n       \treturn 0;\n       }\n       \n     -+static const char *branch_get_default_mark(struct branch *branch UNUSED,\n     -+\t\t\t\t\t   struct strbuf *err UNUSED)\n     ++static const char *branch_get_primary_mark(struct branch *branch,\n     ++\t\t\t\t\t   struct strbuf *err)\n      +{\n     -+\treturn branch_get_default_ref();\n     ++\treturn branch_get_primary_ref(branch, err);\n      +}\n      +\n       static int interpret_branch_mark(struct repository *r,\n     @@ object-name.c: int repo_interpret_branch_name(struct repository *r,\n       \t\tif (len > 0)\n       \t\t\treturn len;\n      +\n     -+\t\tif (default_mark(at, namelen - (at - name))) {\n     -+\t\t\tif (at - name > 0)\n     -+\t\t\t\treturn -1;\n     -+\t\t}\n     -+\n      +\t\tlen = interpret_branch_mark(r, name, namelen, at - name, buf,\n     -+\t\t\t\t\t    default_mark, branch_get_default_mark,\n     ++\t\t\t\t\t    primary_mark, branch_get_primary_mark,\n      +\t\t\t\t\t    options);\n      +\t\tif (len > 0)\n      +\t\t\treturn len;\n     @@ object-name.c: int repo_interpret_branch_name(struct repository *r,\n       \treturn -1;\n      \n       ## remote.c ##\n     +@@ remote.c: static const char *error_buf(struct strbuf *err, const char *fmt, ...)\n     + \treturn NULL;\n     + }\n     + \n     +-const char *branch_get_upstream(struct branch *branch, struct strbuf *err)\n     ++const char *branch_get_upstream_options(struct branch *branch, struct strbuf *err,\n     ++\t\t\t\t\tint omit_remote)\n     + {\n     ++\tstatic struct strbuf upstream_branch_buf = STRBUF_INIT;\n     ++\tconst char *dst;\n     ++\n     + \tif (!branch)\n     + \t\treturn error_buf(err, _(\"HEAD does not point to a branch\"));\n     + \n     +@@ remote.c: const char *branch_get_upstream(struct branch *branch, struct strbuf *err)\n     + \t\t\t\t _(\"upstream branch '%s' not stored as a remote-tracking branch\"),\n     + \t\t\t\t branch->merge[0]->src);\n     + \n     +-\treturn branch->merge[0]->dst;\n     ++\tdst = branch->merge[0]->dst;\n     ++\tif (!omit_remote)\n     ++\t\treturn dst;\n     ++\n     ++\tstrbuf_reset(&upstream_branch_buf);\n     ++\tif (skip_prefix(dst, \"refs/remotes/\", &dst) && (dst = strchr(dst, '/')))\n     ++\t\tstrbuf_addf(&upstream_branch_buf, \"refs/heads/%s\", dst + 1);\n     ++\telse\n     ++\t\tstrbuf_addstr(&upstream_branch_buf, branch->merge[0]->dst);\n     ++\treturn upstream_branch_buf.buf;\n     ++}\n     ++\n     ++const char *branch_get_upstream(struct branch *branch, struct strbuf *err)\n     ++{\n     ++\treturn branch_get_upstream_options(branch, err, 0);\n     + }\n     + \n     + static const char *tracking_for_push_dest(struct remote *remote,\n      @@ remote.c: const char *branch_get_push(struct branch *branch, struct strbuf *err)\n       \treturn branch->push_tracking_ref;\n       }\n       \n     -+const char *branch_get_default_ref(void)\n     ++const char *branch_get_primary_ref(struct branch *branch, struct strbuf *err)\n      +{\n     -+\tstatic struct strbuf default_ref = STRBUF_INIT;\n     -+\tchar *default_branch_name;\n     -+\n     -+\tstrbuf_reset(&default_ref);\n     -+\tdefault_branch_name = repo_default_branch_name(the_repository, 1);\n     -+\tstrbuf_addf(&default_ref, \"refs/heads/%s\", default_branch_name);\n     -+\tfree(default_branch_name);\n     -+\treturn default_ref.buf;\n     ++\treturn branch_get_upstream_options(branch, err, 1);\n      +}\n      +\n       static int ignore_symref_update(const char *refname, struct strbuf *scratch)\n     @@ remote.c: const char *branch_get_push(struct branch *branch, struct strbuf *err)\n       \treturn !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch);\n      \n       ## remote.h ##\n     +@@ remote.h: int branch_has_merge_config(struct branch *branch);\n     + \n     + int branch_merge_matches(struct branch *, int n, const char *);\n     + \n     ++const char *branch_get_upstream_options(struct branch *branch, struct strbuf *err,\n     ++\t\t\t\t\tint omit_remote);\n     ++\n     + /**\n     +  * Return the fully-qualified refname of the tracking branch for `branch`.\n     +  * I.e., what \"branch@{upstream}\" would give you. Returns NULL if no\n      @@ remote.h: const char *branch_get_upstream(struct branch *branch, struct strbuf *err);\n        */\n       const char *branch_get_push(struct branch *branch, struct strbuf *err);\n       \n      +/**\n     -+ * Return the fully-qualified refname of the default branch.\n     -+ * I.e., what \"@{default}\" would give you.\n     ++ * Return the fully-qualified refname of the primary branch.\n     ++ * I.e., what \"@{primary}\" would give you.\n      + */\n     -+const char *branch_get_default_ref(void);\n     ++const char *branch_get_primary_ref(struct branch *branch, struct strbuf *err);\n      +\n       /* Flags to match_refs. */\n       enum match_refs_flags {\n       \tMATCH_REFS_NONE\t\t= 0,\n      \n     + ## t/t1507-rev-parse-upstream.sh ##\n     +@@ t/t1507-rev-parse-upstream.sh: test_expect_success 'log -g other@{u}@{now}' '\n     + \ttest_cmp expect actual\n     + '\n     + \n     ++test_expect_success '@{primary} resolves to correct full name' '\n     ++\techo refs/heads/main >expect &&\n     ++\tgit -C clone rev-parse --symbolic-full-name @{primary} >actual &&\n     ++\ttest_cmp expect actual\n     ++'\n     ++\n     + test_expect_success '@{reflog}-parsing does not look beyond colon' '\n     + \techo content >@{yesterday} &&\n     + \tgit add @{yesterday} &&\n     +\n       ## t/t1508-at-combinations.sh ##\n      @@ t/t1508-at-combinations.sh: check \"@{-1}@{u}\" ref refs/heads/main\n       check \"@{-1}@{u}@{1}\" commit main-one\n       check \"@\" commit new-two\n       check \"@@{u}\" ref refs/heads/upstream-branch\n     -+check \"@{default}\" ref refs/heads/main\n     ++check \"@{primary}\" ref refs/heads/upstream-branch\n       check \"@@/at-test\" ref refs/heads/@@/at-test\n       test_have_prereq MINGW ||\n       check \"@/at-test\" ref refs/heads/@/at-test\n     -@@ t/t1508-at-combinations.sh: nonsense \"@{0}@{0}\"\n     - nonsense \"@{1}@{u}\"\n     - nonsense \"HEAD@{-1}\"\n     - nonsense \"@{-1}@{-1}\"\n     -+nonsense \"new-branch@{default}\"\n     - \n     - # @{N} versus HEAD@{N}\n     - \n      \n     - ## t/t2012-checkout-last.sh ##\n     -@@ t/t2012-checkout-last.sh: test_cmp_symbolic_HEAD_ref () {\n     - \ttest_cmp expect actual\n     - }\n     + ## t/t6040-tracking-info.sh ##\n     +@@ t/t6040-tracking-info.sh: test_expect_success 'checkout (up-to-date with upstream)' '\n     + \ttest_grep \"Your branch is up to date with .origin/main\" actual\n     + '\n       \n     -+test_expect_success '\"checkout @{default}\" switches to default branch' '\n     -+\tgit checkout @{default} &&\n     -+\ttest_cmp_symbolic_HEAD_ref main &&\n     -+\tgit checkout other\n     ++test_expect_success 'checkout @{primary} (up-to-date with upstream)' '\n     ++\t(\n     ++\t\tcd test &&\n     ++\t\tgit checkout b6 &&\n     ++\t\tgit checkout @{primary} >../actual\n     ++\t) &&\n     ++\tcat >expect <<-EOF &&\n     ++\tYour branch is up to date with ${SQ}origin/main${SQ}.\n     ++\tEOF\n     ++\ttest_cmp expect actual\n     ++'\n     ++\n     ++test_expect_success 'status from @{primary} (up-to-date with upstream)' '\n     ++\t(\n     ++\t\tcd test &&\n     ++\t\tgit checkout @{primary} &&\n     ++\t\tgit status >../actual\n     ++\t) &&\n     ++\tcat >expect <<-EOF &&\n     ++\tOn branch main\n     ++\tYour branch is up to date with ${SQ}origin/main${SQ}.\n     ++\n     ++\tnothing to commit, working tree clean\n     ++\tEOF\n     ++\ttest_cmp expect actual\n      +'\n      +\n     - test_expect_success '\"checkout -\" switches back' '\n     - \tgit checkout - &&\n     - \ttest_cmp_symbolic_HEAD_ref main\n     + test_expect_success 'status (diverged from upstream)' '\n     + \t(\n     + \t\tcd test &&\n\n\n Documentation/revisions.adoc  | 17 +++++++++++++++++\n object-name.c                 | 21 ++++++++++++++++++++-\n remote.c                      | 27 +++++++++++++++++++++++++--\n remote.h                      |  9 +++++++++\n t/t1507-rev-parse-upstream.sh |  6 ++++++\n t/t1508-at-combinations.sh    |  1 +\n t/t6040-tracking-info.sh      | 27 +++++++++++++++++++++++++++\n 7 files changed, 105 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/revisions.adoc b/Documentation/revisions.adoc\nindex 6ea6c7cead..d5c98bfdb1 100644\n--- a/Documentation/revisions.adoc\n+++ b/Documentation/revisions.adoc\n@@ -149,6 +149,23 @@ from one location and push to another. In a non-triangular workflow,\n This suffix is also accepted when spelled in uppercase, and means the same\n thing no matter the case.\n \n+'@\\{primary\\}'::\n+  The suffix '@\\{primary}' refers to the primary branch of the repository,\n+  typically `main` or `master`. This is determined by the `init.defaultBranch`\n+  configuration option, or falls back to `main` (or `master` in older Git\n+  versions) if not configured. The primary branch must exist in the repository\n+  for this syntax to work.\n++\n+Here's an example:\n++\n+------------------------------\n+$ git checkout @{primary}\n+Switched to branch 'main'\n+\n+$ git rev-parse --symbolic-full-name @{primary}\n+refs/heads/main\n+------------------------------\n+\n '<rev>{caret}[<n>]', e.g. 'HEAD{caret}, v1.5.1{caret}0'::\n   A suffix '{caret}' to a revision parameter means the first parent of\n   that commit object.  '{caret}<n>' means the <n>th parent (i.e.\ndiff --git a/object-name.c b/object-name.c\nindex 8b862c124e..1cdc9a0339 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -947,6 +947,12 @@ static inline int push_mark(const char *string, int len)\n \treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n }\n \n+static inline int primary_mark(const char *string, int len)\n+{\n+\tconst char *suffix[] = { \"@{primary}\" };\n+\treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n+}\n+\n static enum get_oid_result get_oid_1(struct repository *r, const char *name, int len, struct object_id *oid, unsigned lookup_flags);\n static int interpret_nth_prior_checkout(struct repository *r, const char *name, int namelen, struct strbuf *buf);\n \n@@ -998,7 +1004,8 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tif (!upstream_mark(str + at, len - at) &&\n-\t\t\t\t    !push_mark(str + at, len - at)) {\n+\t\t\t\t    !push_mark(str + at, len - at) &&\n+\t\t\t\t    !primary_mark(str + at, len - at)) {\n \t\t\t\t\treflog_len = (len-1) - (at+2);\n \t\t\t\t\tlen = at;\n \t\t\t\t}\n@@ -1707,6 +1714,12 @@ static int branch_interpret_allowed(const char *refname, unsigned allowed)\n \treturn 0;\n }\n \n+static const char *branch_get_primary_mark(struct branch *branch,\n+\t\t\t\t\t   struct strbuf *err)\n+{\n+\treturn branch_get_primary_ref(branch, err);\n+}\n+\n static int interpret_branch_mark(struct repository *r,\n \t\t\t\t const char *name, int namelen,\n \t\t\t\t int at, struct strbuf *buf,\n@@ -1798,6 +1811,12 @@ int repo_interpret_branch_name(struct repository *r,\n \t\t\t\t\t    options);\n \t\tif (len > 0)\n \t\t\treturn len;\n+\n+\t\tlen = interpret_branch_mark(r, name, namelen, at - name, buf,\n+\t\t\t\t\t    primary_mark, branch_get_primary_mark,\n+\t\t\t\t\t    options);\n+\t\tif (len > 0)\n+\t\t\treturn len;\n \t}\n \n \treturn -1;\ndiff --git a/remote.c b/remote.c\nindex b756ff6f15..a4a0a69176 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1842,8 +1842,12 @@ static const char *error_buf(struct strbuf *err, const char *fmt, ...)\n \treturn NULL;\n }\n \n-const char *branch_get_upstream(struct branch *branch, struct strbuf *err)\n+const char *branch_get_upstream_options(struct branch *branch, struct strbuf *err,\n+\t\t\t\t\tint omit_remote)\n {\n+\tstatic struct strbuf upstream_branch_buf = STRBUF_INIT;\n+\tconst char *dst;\n+\n \tif (!branch)\n \t\treturn error_buf(err, _(\"HEAD does not point to a branch\"));\n \n@@ -1866,7 +1870,21 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)\n \t\t\t\t _(\"upstream branch '%s' not stored as a remote-tracking branch\"),\n \t\t\t\t branch->merge[0]->src);\n \n-\treturn branch->merge[0]->dst;\n+\tdst = branch->merge[0]->dst;\n+\tif (!omit_remote)\n+\t\treturn dst;\n+\n+\tstrbuf_reset(&upstream_branch_buf);\n+\tif (skip_prefix(dst, \"refs/remotes/\", &dst) && (dst = strchr(dst, '/')))\n+\t\tstrbuf_addf(&upstream_branch_buf, \"refs/heads/%s\", dst + 1);\n+\telse\n+\t\tstrbuf_addstr(&upstream_branch_buf, branch->merge[0]->dst);\n+\treturn upstream_branch_buf.buf;\n+}\n+\n+const char *branch_get_upstream(struct branch *branch, struct strbuf *err)\n+{\n+\treturn branch_get_upstream_options(branch, err, 0);\n }\n \n static const char *tracking_for_push_dest(struct remote *remote,\n@@ -1961,6 +1979,11 @@ const char *branch_get_push(struct branch *branch, struct strbuf *err)\n \treturn branch->push_tracking_ref;\n }\n \n+const char *branch_get_primary_ref(struct branch *branch, struct strbuf *err)\n+{\n+\treturn branch_get_upstream_options(branch, err, 1);\n+}\n+\n static int ignore_symref_update(const char *refname, struct strbuf *scratch)\n {\n \treturn !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch);\ndiff --git a/remote.h b/remote.h\nindex 0ca399e183..414187827b 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -347,6 +347,9 @@ int branch_has_merge_config(struct branch *branch);\n \n int branch_merge_matches(struct branch *, int n, const char *);\n \n+const char *branch_get_upstream_options(struct branch *branch, struct strbuf *err,\n+\t\t\t\t\tint omit_remote);\n+\n /**\n  * Return the fully-qualified refname of the tracking branch for `branch`.\n  * I.e., what \"branch@{upstream}\" would give you. Returns NULL if no\n@@ -366,6 +369,12 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err);\n  */\n const char *branch_get_push(struct branch *branch, struct strbuf *err);\n \n+/**\n+ * Return the fully-qualified refname of the primary branch.\n+ * I.e., what \"@{primary}\" would give you.\n+ */\n+const char *branch_get_primary_ref(struct branch *branch, struct strbuf *err);\n+\n /* Flags to match_refs. */\n enum match_refs_flags {\n \tMATCH_REFS_NONE\t\t= 0,\ndiff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh\nindex cb9ef7e329..27b45442c2 100755\n--- a/t/t1507-rev-parse-upstream.sh\n+++ b/t/t1507-rev-parse-upstream.sh\n@@ -259,6 +259,12 @@ test_expect_success 'log -g other@{u}@{now}' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '@{primary} resolves to correct full name' '\n+\techo refs/heads/main >expect &&\n+\tgit -C clone rev-parse --symbolic-full-name @{primary} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success '@{reflog}-parsing does not look beyond colon' '\n \techo content >@{yesterday} &&\n \tgit add @{yesterday} &&\ndiff --git a/t/t1508-at-combinations.sh b/t/t1508-at-combinations.sh\nindex 87a4286414..88bf625c74 100755\n--- a/t/t1508-at-combinations.sh\n+++ b/t/t1508-at-combinations.sh\n@@ -69,6 +69,7 @@ check \"@{-1}@{u}\" ref refs/heads/main\n check \"@{-1}@{u}@{1}\" commit main-one\n check \"@\" commit new-two\n check \"@@{u}\" ref refs/heads/upstream-branch\n+check \"@{primary}\" ref refs/heads/upstream-branch\n check \"@@/at-test\" ref refs/heads/@@/at-test\n test_have_prereq MINGW ||\n check \"@/at-test\" ref refs/heads/@/at-test\ndiff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh\nindex 0b719bbae6..039eb7108f 100755\n--- a/t/t6040-tracking-info.sh\n+++ b/t/t6040-tracking-info.sh\n@@ -106,6 +106,33 @@ test_expect_success 'checkout (up-to-date with upstream)' '\n \ttest_grep \"Your branch is up to date with .origin/main\" actual\n '\n \n+test_expect_success 'checkout @{primary} (up-to-date with upstream)' '\n+\t(\n+\t\tcd test &&\n+\t\tgit checkout b6 &&\n+\t\tgit checkout @{primary} >../actual\n+\t) &&\n+\tcat >expect <<-EOF &&\n+\tYour branch is up to date with ${SQ}origin/main${SQ}.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status from @{primary} (up-to-date with upstream)' '\n+\t(\n+\t\tcd test &&\n+\t\tgit checkout @{primary} &&\n+\t\tgit status >../actual\n+\t) &&\n+\tcat >expect <<-EOF &&\n+\tOn branch main\n+\tYour branch is up to date with ${SQ}origin/main${SQ}.\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'status (diverged from upstream)' '\n \t(\n \t\tcd test &&\n\nbase-commit: ea717645d199f6f1b66058886475db3e8c9330e9\n-- \ngitgitgadget\n"},{"id":"534906","messageId":"20260130205830.45806-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"xmqq4io3831o.fsf@gitster.g","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-01-30T20:58:30Z","receivedAt":"2026-01-30T20:58:33Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"I pushed a WIP with some of these ideas now, not intended as the final\nthing.\n\n\nHarald\n"},{"id":"534908","messageId":"xmqqpl6q69z8.fsf@gitster.g","threadId":"64884","inReplyTo":"20260130205830.45806-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-30T21:56:11Z","receivedAt":"2026-01-30T21:56:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n> I pushed a WIP with some of these ideas now, not intended as the final\n> thing.\n>\n>\n> Harald\n\nMeaning we should feel free to ignore v3 and possibly a few later\nversions, until we hear from you?\n\nI was writing the following as v3 review, but I guess these are\ncomments on a version not for public consumption, so ...\n\n--- >8 ---\n\nI'd rather not see you use \"primary\" for what init.defaultBranch\nspecifies, which already has a good name, \"default\".  If you are\nusing a different concept, like:\n\n * learn the remote @{upstream} for the current branch (for\n   \"@{primary}\") or the named branch (for \"$name@{primary}\"), and\n   then\n\n * look at refs/remotes/$remote/HEAD\n\nthen I would appreciate a good name to call that (which is a concept\nthat has no good name yet, as far as I can see) and \"primary\" might\nbe a good name for that new concept.\n\nAnd from what I read as _your_ use case in an earlier message,\ninit.defaultBranch aka @{default} is not what you want 999/1, yet I\nthink what the patch implements is still that one.  Puzzled...\n\n\n"},{"id":"534910","messageId":"pull.2183.v4.git.git.1769817987594.gitgitgadget@gmail.com","threadId":"64884","inReplyTo":"pull.2183.v3.git.git.1769805948018.gitgitgadget@gmail.com","subject":"[PATCH v4] revisions: add @{primary} shorthand for primary branch","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-01-31T00:06:27Z","receivedAt":"2026-01-31T00:06:29Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nGit already has shorthands like @{upstream} and @{push} to refer to\ntracking branches, but there is no convenient way to refer to the\nprimary branch of a repository (typically \"main\" or \"master\").\n\nUsers often want to switch to the primary branch regardless of its\nname, especially when working across repositories with different\nprimary branch names. Currently they must either hardcode the branch\nname or query it via configuration, which is cumbersome.\n\nAdd a new @{primary} shorthand that resolves to the primary branch\nas determined by init.defaultBranch (or falls back to \"main\" or\n\"master\" depending on Git version). This allows users to write:\n\n  git checkout @{primary}\n\ninstead of having to know or look up the primary branch name.\n\nThe implementation follows the same pattern as @{upstream} and @{push},\nusing a new branch_get_primary_ref() function that queries the primary\nbranch name and verifies it exists in the repository.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    revisions: add @{default} shorthand for default branch\n    \n    cc: \"Kristoffer Haugsbakk\" kristofferhaugsbakk@fastmail.com\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2183%2FHaraldNordgren%2Fdefault_shorthand-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2183/HaraldNordgren/default_shorthand-v4\nPull-Request: https://github.com/git/git/pull/2183\n\nRange-diff vs v3:\n\n 1:  0fc9eb75ea ! 1:  cfad3c3197 revisions: add @{primary} shorthand for primary branch\n     @@ object-name.c: static int branch_interpret_allowed(const char *refname, unsigned\n      +static const char *branch_get_primary_mark(struct branch *branch,\n      +\t\t\t\t\t   struct strbuf *err)\n      +{\n     -+\treturn branch_get_primary_ref(branch, err);\n     ++\treturn branch_get_upstream_options(branch, err, 1);\n      +}\n      +\n       static int interpret_branch_mark(struct repository *r,\n     @@ remote.c: const char *branch_get_upstream(struct branch *branch, struct strbuf *\n       }\n       \n       static const char *tracking_for_push_dest(struct remote *remote,\n     -@@ remote.c: const char *branch_get_push(struct branch *branch, struct strbuf *err)\n     - \treturn branch->push_tracking_ref;\n     - }\n     - \n     -+const char *branch_get_primary_ref(struct branch *branch, struct strbuf *err)\n     -+{\n     -+\treturn branch_get_upstream_options(branch, err, 1);\n     -+}\n     -+\n     - static int ignore_symref_update(const char *refname, struct strbuf *scratch)\n     - {\n     - \treturn !refs_read_symbolic_ref(get_main_ref_store(the_repository), refname, scratch);\n      \n       ## remote.h ##\n      @@ remote.h: int branch_has_merge_config(struct branch *branch);\n     @@ remote.h: int branch_has_merge_config(struct branch *branch);\n       /**\n        * Return the fully-qualified refname of the tracking branch for `branch`.\n        * I.e., what \"branch@{upstream}\" would give you. Returns NULL if no\n     -@@ remote.h: const char *branch_get_upstream(struct branch *branch, struct strbuf *err);\n     -  */\n     - const char *branch_get_push(struct branch *branch, struct strbuf *err);\n     - \n     -+/**\n     -+ * Return the fully-qualified refname of the primary branch.\n     -+ * I.e., what \"@{primary}\" would give you.\n     -+ */\n     -+const char *branch_get_primary_ref(struct branch *branch, struct strbuf *err);\n     -+\n     - /* Flags to match_refs. */\n     - enum match_refs_flags {\n     - \tMATCH_REFS_NONE\t\t= 0,\n      \n       ## t/t1507-rev-parse-upstream.sh ##\n      @@ t/t1507-rev-parse-upstream.sh: test_expect_success 'log -g other@{u}@{now}' '\n     @@ t/t6040-tracking-info.sh: test_expect_success 'checkout (up-to-date with upstrea\n       \ttest_grep \"Your branch is up to date with .origin/main\" actual\n       '\n       \n     -+test_expect_success 'checkout @{primary} (up-to-date with upstream)' '\n     ++test_expect_success 'checkout @{primary} same as checkout main' '\n      +\t(\n      +\t\tcd test &&\n      +\t\tgit checkout b6 &&\n     @@ t/t6040-tracking-info.sh: test_expect_success 'checkout (up-to-date with upstrea\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success 'status from @{primary} (up-to-date with upstream)' '\n     ++test_expect_success 'status from @{primary} same as status from main' '\n      +\t(\n      +\t\tcd test &&\n      +\t\tgit checkout @{primary} &&\n\n\n Documentation/revisions.adoc  | 17 +++++++++++++++++\n object-name.c                 | 21 ++++++++++++++++++++-\n remote.c                      | 22 ++++++++++++++++++++--\n remote.h                      |  3 +++\n t/t1507-rev-parse-upstream.sh |  6 ++++++\n t/t1508-at-combinations.sh    |  1 +\n t/t6040-tracking-info.sh      | 27 +++++++++++++++++++++++++++\n 7 files changed, 94 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/revisions.adoc b/Documentation/revisions.adoc\nindex 6ea6c7cead..d5c98bfdb1 100644\n--- a/Documentation/revisions.adoc\n+++ b/Documentation/revisions.adoc\n@@ -149,6 +149,23 @@ from one location and push to another. In a non-triangular workflow,\n This suffix is also accepted when spelled in uppercase, and means the same\n thing no matter the case.\n \n+'@\\{primary\\}'::\n+  The suffix '@\\{primary}' refers to the primary branch of the repository,\n+  typically `main` or `master`. This is determined by the `init.defaultBranch`\n+  configuration option, or falls back to `main` (or `master` in older Git\n+  versions) if not configured. The primary branch must exist in the repository\n+  for this syntax to work.\n++\n+Here's an example:\n++\n+------------------------------\n+$ git checkout @{primary}\n+Switched to branch 'main'\n+\n+$ git rev-parse --symbolic-full-name @{primary}\n+refs/heads/main\n+------------------------------\n+\n '<rev>{caret}[<n>]', e.g. 'HEAD{caret}, v1.5.1{caret}0'::\n   A suffix '{caret}' to a revision parameter means the first parent of\n   that commit object.  '{caret}<n>' means the <n>th parent (i.e.\ndiff --git a/object-name.c b/object-name.c\nindex 8b862c124e..6d38df2f08 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -947,6 +947,12 @@ static inline int push_mark(const char *string, int len)\n \treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n }\n \n+static inline int primary_mark(const char *string, int len)\n+{\n+\tconst char *suffix[] = { \"@{primary}\" };\n+\treturn at_mark(string, len, suffix, ARRAY_SIZE(suffix));\n+}\n+\n static enum get_oid_result get_oid_1(struct repository *r, const char *name, int len, struct object_id *oid, unsigned lookup_flags);\n static int interpret_nth_prior_checkout(struct repository *r, const char *name, int namelen, struct strbuf *buf);\n \n@@ -998,7 +1004,8 @@ static int get_oid_basic(struct repository *r, const char *str, int len,\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \t\t\t\tif (!upstream_mark(str + at, len - at) &&\n-\t\t\t\t    !push_mark(str + at, len - at)) {\n+\t\t\t\t    !push_mark(str + at, len - at) &&\n+\t\t\t\t    !primary_mark(str + at, len - at)) {\n \t\t\t\t\treflog_len = (len-1) - (at+2);\n \t\t\t\t\tlen = at;\n \t\t\t\t}\n@@ -1707,6 +1714,12 @@ static int branch_interpret_allowed(const char *refname, unsigned allowed)\n \treturn 0;\n }\n \n+static const char *branch_get_primary_mark(struct branch *branch,\n+\t\t\t\t\t   struct strbuf *err)\n+{\n+\treturn branch_get_upstream_options(branch, err, 1);\n+}\n+\n static int interpret_branch_mark(struct repository *r,\n \t\t\t\t const char *name, int namelen,\n \t\t\t\t int at, struct strbuf *buf,\n@@ -1798,6 +1811,12 @@ int repo_interpret_branch_name(struct repository *r,\n \t\t\t\t\t    options);\n \t\tif (len > 0)\n \t\t\treturn len;\n+\n+\t\tlen = interpret_branch_mark(r, name, namelen, at - name, buf,\n+\t\t\t\t\t    primary_mark, branch_get_primary_mark,\n+\t\t\t\t\t    options);\n+\t\tif (len > 0)\n+\t\t\treturn len;\n \t}\n \n \treturn -1;\ndiff --git a/remote.c b/remote.c\nindex b756ff6f15..2316dfea07 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1842,8 +1842,12 @@ static const char *error_buf(struct strbuf *err, const char *fmt, ...)\n \treturn NULL;\n }\n \n-const char *branch_get_upstream(struct branch *branch, struct strbuf *err)\n+const char *branch_get_upstream_options(struct branch *branch, struct strbuf *err,\n+\t\t\t\t\tint omit_remote)\n {\n+\tstatic struct strbuf upstream_branch_buf = STRBUF_INIT;\n+\tconst char *dst;\n+\n \tif (!branch)\n \t\treturn error_buf(err, _(\"HEAD does not point to a branch\"));\n \n@@ -1866,7 +1870,21 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)\n \t\t\t\t _(\"upstream branch '%s' not stored as a remote-tracking branch\"),\n \t\t\t\t branch->merge[0]->src);\n \n-\treturn branch->merge[0]->dst;\n+\tdst = branch->merge[0]->dst;\n+\tif (!omit_remote)\n+\t\treturn dst;\n+\n+\tstrbuf_reset(&upstream_branch_buf);\n+\tif (skip_prefix(dst, \"refs/remotes/\", &dst) && (dst = strchr(dst, '/')))\n+\t\tstrbuf_addf(&upstream_branch_buf, \"refs/heads/%s\", dst + 1);\n+\telse\n+\t\tstrbuf_addstr(&upstream_branch_buf, branch->merge[0]->dst);\n+\treturn upstream_branch_buf.buf;\n+}\n+\n+const char *branch_get_upstream(struct branch *branch, struct strbuf *err)\n+{\n+\treturn branch_get_upstream_options(branch, err, 0);\n }\n \n static const char *tracking_for_push_dest(struct remote *remote,\ndiff --git a/remote.h b/remote.h\nindex 0ca399e183..879be2162c 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -347,6 +347,9 @@ int branch_has_merge_config(struct branch *branch);\n \n int branch_merge_matches(struct branch *, int n, const char *);\n \n+const char *branch_get_upstream_options(struct branch *branch, struct strbuf *err,\n+\t\t\t\t\tint omit_remote);\n+\n /**\n  * Return the fully-qualified refname of the tracking branch for `branch`.\n  * I.e., what \"branch@{upstream}\" would give you. Returns NULL if no\ndiff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh\nindex cb9ef7e329..27b45442c2 100755\n--- a/t/t1507-rev-parse-upstream.sh\n+++ b/t/t1507-rev-parse-upstream.sh\n@@ -259,6 +259,12 @@ test_expect_success 'log -g other@{u}@{now}' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '@{primary} resolves to correct full name' '\n+\techo refs/heads/main >expect &&\n+\tgit -C clone rev-parse --symbolic-full-name @{primary} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success '@{reflog}-parsing does not look beyond colon' '\n \techo content >@{yesterday} &&\n \tgit add @{yesterday} &&\ndiff --git a/t/t1508-at-combinations.sh b/t/t1508-at-combinations.sh\nindex 87a4286414..88bf625c74 100755\n--- a/t/t1508-at-combinations.sh\n+++ b/t/t1508-at-combinations.sh\n@@ -69,6 +69,7 @@ check \"@{-1}@{u}\" ref refs/heads/main\n check \"@{-1}@{u}@{1}\" commit main-one\n check \"@\" commit new-two\n check \"@@{u}\" ref refs/heads/upstream-branch\n+check \"@{primary}\" ref refs/heads/upstream-branch\n check \"@@/at-test\" ref refs/heads/@@/at-test\n test_have_prereq MINGW ||\n check \"@/at-test\" ref refs/heads/@/at-test\ndiff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh\nindex 0b719bbae6..b42d56d438 100755\n--- a/t/t6040-tracking-info.sh\n+++ b/t/t6040-tracking-info.sh\n@@ -106,6 +106,33 @@ test_expect_success 'checkout (up-to-date with upstream)' '\n \ttest_grep \"Your branch is up to date with .origin/main\" actual\n '\n \n+test_expect_success 'checkout @{primary} same as checkout main' '\n+\t(\n+\t\tcd test &&\n+\t\tgit checkout b6 &&\n+\t\tgit checkout @{primary} >../actual\n+\t) &&\n+\tcat >expect <<-EOF &&\n+\tYour branch is up to date with ${SQ}origin/main${SQ}.\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'status from @{primary} same as status from main' '\n+\t(\n+\t\tcd test &&\n+\t\tgit checkout @{primary} &&\n+\t\tgit status >../actual\n+\t) &&\n+\tcat >expect <<-EOF &&\n+\tOn branch main\n+\tYour branch is up to date with ${SQ}origin/main${SQ}.\n+\n+\tnothing to commit, working tree clean\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'status (diverged from upstream)' '\n \t(\n \t\tcd test &&\n\nbase-commit: ea717645d199f6f1b66058886475db3e8c9330e9\n-- \ngitgitgadget\n"},{"id":"534912","messageId":"20260131000923.70152-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"xmqqpl6q69z8.fsf@gitster.g","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-01-31T00:09:23Z","receivedAt":"2026-01-31T00:09:27Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> Meaning we should feel free to ignore v3 and possibly a few later\n> versions, until we hear from you?\n\nI pushed v4 now. Please feel free to review it.\n\n> And from what I read as _your_ use case in an earlier message,\n> init.defaultBranch aka @{default} is not what you want 999/1, yet I\n>think what the patch implements is still that one.  Puzzled...\n\nI'm agnostic with the regards to the implementation as long as it solves\nmy problem.\n\nI changed my mind after realizing the other approach would have required\nme to set 'init.defaultBranch' on many repos -- or worse, badger the\nmaintainers to set it. That does not scale.\n\nWith the new approach, things run smoothly for all the repos on my machine,\nso my goal of convenience is achieved there. I ran this to check that:\n\n    for x in */*; do (\n      cd \"$x\" && \\\n      echo && \\\n      echo \"$x\" && \\\n      /Users/Harald/git-repos/github.com/git/git/git checkout @{primary}\n    ); done\n\n\nHarald\n"},{"id":"534922","messageId":"xmqqv7gh4mpw.fsf@gitster.g","threadId":"64884","inReplyTo":"xmqq4io3831o.fsf@gitster.g","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-01-31T19:16:11Z","receivedAt":"2026-01-31T19:16:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> For those who follow the naming the upstream decided to use in\n> cloned repositories, init.defaultBranch is probably the last thing\n> you want to take as a hint, as these people decided to _ignore_ the\n> preference of their own and instead to follow what upstream uses.\n> For that, refs/remotes/origin/HEAD would be a lot more stronger\n> hint.  If they call their primary branch 'trunk', these people would\n> want to call theirs 'trunk'.  And for that, these people would not\n> do anything with init.defaultBranch.\n>\n>>     git checkout $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')\n>>\n>> That's uneccessary overhead, that could now be replaced with:\n>>\n>>     git checkout @{default}\n>\n> That mirrors what I wrote in the previous paragraph.  Those who\n> follow the naming the upstream uses, refs/remotes/$remote/HEAD\n> (here, \"origin\" may not be the default remote) would be a better\n> hint than init.defaultBranch so calling it @{default} is misleading.\n\nThinking about this more, while I can see how using the\nremote-tracking branch that is pointed by refs/remotes/origin/HEAD\nmay make sense, I see no sensible reason why mapping that to a local\nbranch name by simply stripping refs/remotes/origin/ makes sense.\n\nStepping back a bit, the workflow that may be helped by being able\nto learn the value of \"refs/remotes/origin/HEAD\", in addition to\n@{upstream} and @{push} would be laid out like the following:\n\n * The project uses 'main' as its primary integration branch.  After\n   you clone it, refs/remotes/origin/HEAD points at their 'main'\n   (i.e., you have refs/remotes/origin/main keeping track of it),\n   \"git fetch\" updates refs/remotes/origin/HEAD when they change\n   their naming if you are using a recent enough version of Git.\n\n * The project uses topic based workflow.  The idea is each topic\n   gets its own topic branch, e.g., 'feature', and participants join\n   forces to bring it to perfection, after which 'main' merges the\n   completed 'feature'.\n\n * You, as a participant of this project, fork your own 'feature'\n   local branch from the remote-tracking branch 'origin/feature'.\n   You publish the result into a separate repository of your own,\n   different from where you cloned from.  If you fetch from there, a\n   remote-tracking branch 'refs/remotes/publish/feature' may be\n   created in your local clone as well (assuming your publishing\n   repository is called 'publish').\n\nNow, you have @{upstream} that is their 'feature' (that is kept\ntrack of with refs/remotes/origin/feature remote-tracking branch\nin your local clone), @{push} that is refs/remotes/publish/feature.\nComparing your local progress against these two are useful to see\nwhere you are, how far you came, and how much others you see in\n@{upstream} may have diverged.\n\nIn addition, the overall \"progress\" of the project is how far\n@{upstream} has come relative to refs/remotes/origin/main, which is\nthe ultimate target that you and your fellow project participants\nwant to see @{upstream} gets merged, is a useful thing to keep track\nof.\n\nSo in that sense, I do understand why somebody may find it useful if\nthere is a handy short-hand for refs/remotes/origin/main (or\nwhichever branch is pointed at by refs/remotes/origin/HEAD) in the\nabove picture.  And refs/remotes/origin/HEAD already does have a\nhandy short-hand, which is 'origin' ;-).\n\nBut step back and notice that there is no mention of local 'main' in\nthe above layout?\n\nAs Kristoffer said in another message [*1*], I would too expect that\npeople would not work on their 'main' (or have their 'main' track\nthe upstream's 'main').  So the utility of the piping to sed we saw\nabove is dubious, unless we are talking about quite different\nworkflow, but I do not think of what that other workflow would look\nlike that makes a neutral synonym for 'main' useful.\n\nSo, enough about refs/remotes/origin/HEAD.  Back to your original\nidea of using init.defaultBranch.\n\nIn one of your other messages [*2*], you reported that you were\nhaving trouble with repositories without init.defaultBranch\nconfiguration variable cofigured.\n\nDoesn't repo_default_branch_name() do the right thing without being\nnoisy at all even in a repository without that configured, as the\nfunction will fall back to the built-in default?  While I do not\nthink of a workflow in which a handy access to the value the\nfunction gives would be so useful that it deserves a short-hand, it\nwould be a reasonable candidate of what to be called \"@{default}\",\nif it proves useful, I would think.\n\n> I am not good at naming things, so instead of calling it @{primary}\n> let's call it @{dumbo}.  With the realization that what branch\n> refs/remotes/$remote/HEAD points at is a good source of hint, I\n> actually think $branch@{dumbo} does make sense, and @{dumbo} should\n> be a short-hand for $branch@{dumbo} where the name of the current\n> branch is substituted for $branch (i.e. similar to @{push}, I\n> suppose).  As you may be interacting with two sets of branches that\n> go to two different remotes.\n>\n> In any case, it is very different from what you implemented as the\n> @{default} in your patch.\n\n\n[References]\n\n*1* https://lore.kernel.org/git/7b62316f-a30a-4895-808d-baa20be0f3af@app.fastmail.com/\n\n*2* https://lore.kernel.org/git/20260131000923.70152-1-haraldnordgren@gmail.com/\n"},{"id":"534926","messageId":"20260131202232.9213-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"xmqqv7gh4mpw.fsf@gitster.g","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-01-31T20:22:32Z","receivedAt":"2026-01-31T20:22:35Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> So in that sense, I do understand why somebody may find it useful if\n> there is a handy short-hand for refs/remotes/origin/main (or\n> whichever branch is pointed at by refs/remotes/origin/HEAD) in the\n> above picture.  And refs/remotes/origin/HEAD already does have a\n> handy short-hand, which is 'origin' ;-).\n\n'git checkout origin' doesn't work without resulting in a detached head.\n\n> As Kristoffer said in another message [*1*], I would too expect that\n> people would not work on their 'main' (or have their 'main' track\n> the upstream's 'main').  So the utility of the piping to sed we saw\n> above is dubious, unless we are talking about quite different\n> workflow, but I do not think of what that other workflow would look\n> like that makes a neutral synonym for 'main' useful.\n\nI don't work directly on the main branch.\n\nHowever it serves and the only starting point for creating any new feature\nbranches. This is the command I use, and would be nice if it could be\nsimplified:\n\n    git fetch --all\n    git checkout $(git remote | rg '^(origin|upstream)$' | tail -n1)/HEAD -b new_branch\n\nThe main branch is used in my work frontend project for the app release\ncommand, so there I do\n\n    git checkout $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')\n    yarn release\n\nI think me as a non-hardcore Git maintainer spend more time in different\nrepos than you two do, so maybe the pain of switching between systems is\nmore pronounced. That's my motivation for unifying stuff.\n\nJust for reference, iterating all forked open-source repos on my machine\nthese are the different upstream names I work with:\n\n    99designs/gqlgen\n    refs/remotes/upstream/master\n\n    amplitude/experiment-react-native-client\n    refs/remotes/upstream/main\n\n    Antonboom/testifylint\n    refs/remotes/upstream/master\n\n    cli/cli\n    refs/remotes/upstream/trunk\n\n    datastax/python-driver\n    refs/remotes/origin/master\n\n    dependabot/dependabot-core\n    refs/remotes/origin/main\n\n    derailed/k9s\n    refs/remotes/origin/master\n\n    elastic/go-elasticsearch\n    refs/remotes/upstream/main\n\n    git/git\n    refs/remotes/upstream/master\n\n    gitgitgadget/gitgitgadget\n    refs/remotes/upstream/main\n\n    github-linguist/linguist\n    refs/remotes/origin/main\n\n    go-redis/redis_rate\n    refs/remotes/origin/v10\n\n    golang-migrate/migrate\n    refs/remotes/upstream/master\n\n    golang/go\n    refs/remotes/origin/master\n\n    golangci/golangci-lint-action\n    refs/remotes/upstream/main\n\n    gradle/gradle\n    refs/remotes/origin/master\n\n    Homebrew/brew\n    refs/remotes/origin/main\n\n    jwalton/gh-docker-logs\n    refs/remotes/upstream/master\n\n    Khan/genqlient\n    refs/remotes/upstream/main\n\n    kubernetes-sigs/controller-tools\n    refs/remotes/origin/main\n\n    kubernetes/kompose\n    refs/remotes/origin/main\n\n    kubernetes/kubernetes\n    refs/remotes/origin/master\n\n    ldez/usetesting\n    refs/remotes/origin/main\n\n    liushuangls/go-anthropic\n    refs/remotes/upstream/main\n\n    matryer/moq\n    refs/remotes/upstream/main\n\n    mhemmings/revenuecat\n    refs/remotes/origin/master\n\n    ohmyzsh/ohmyzsh\n    refs/remotes/upstream/master\n\n    prettier/prettier\n    refs/remotes/origin/main\n\n    RevenueCat/docs\n    refs/remotes/upstream/main\n\n    RevenueCat/purchases-ios\n    refs/remotes/origin/main\n\n    RevenueCat/react-native-purchases\n    refs/remotes/origin/main\n\n    sashabaranov/go-openai\n    refs/remotes/upstream/master\n\n    stretchr/testify\n    refs/remotes/origin/master\n\n    vektah/gqlparser\n    refs/remotes/upstream/master\n\n\n> Doesn't repo_default_branch_name() do the right thing without being\n> noisy at all even in a repository without that configured, as the\n> function will fall back to the built-in default?  While I do not\n> think of a workflow in which a handy access to the value the\n> function gives would be so useful that it deserves a short-hand, it\n> would be a reasonable candidate of what to be called \"@{default}\",\n> if it proves useful, I would think.\n\nI'll play around with this a bit and see how it works. Thanks for the tip!\n\n\nHarald\n"},{"id":"534928","messageId":"20260131205521.11876-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"20260131202232.9213-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-01-31T20:55:21Z","receivedAt":"2026-01-31T20:55:24Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> Doesn't repo_default_branch_name() do the right thing without being\n> noisy at all even in a repository without that configured, as the\n> function will fall back to the built-in default?  While I do not\n> think of a workflow in which a handy access to the value the\n> function gives would be so useful that it deserves a short-hand, it\n> would be a reasonable candidate of what to be called \"@{default}\",\n> if it proves useful, I would think.\n\nAfter looking a this, this is hard-coded. Not showing what is relevant for\neach repo that exists:\n\n```\nchar *repo_default_branch_name(struct repository *r, int quiet)\n{\n  const char *config_key = \"init.defaultbranch\";\n  const char *config_display_key = \"init.defaultBranch\";\n  char *ret = NULL, *full_ref;\n  const char *env = getenv(\"GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\");\n\n  if (env && *env)\n    ret = xstrdup(env);\n  if (!ret && repo_config_get_string(r, config_key, &ret) < 0)\n    die(_(\"could not retrieve `%s`\"), config_display_key);\n\n  if (!ret) {\n#ifdef WITH_BREAKING_CHANGES\n    ret = xstrdup(\"main\");\n#else\n    ret = xstrdup(\"master\");\n```\n\n\nHarald\n"},{"id":"534960","messageId":"d92bd08f-abee-49a6-89ad-3be5e0c06ad6@gmail.com","threadId":"64884","inReplyTo":"20260131202232.9213-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-02-02T09:37:10Z","receivedAt":"2026-02-02T09:37:15Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 31/01/2026 20:22, Harald Nordgren wrote:\n>> So in that sense, I do understand why somebody may find it useful if\n>> there is a handy short-hand for refs/remotes/origin/main (or\n>> whichever branch is pointed at by refs/remotes/origin/HEAD) in the\n>> above picture.  And refs/remotes/origin/HEAD already does have a\n>> handy short-hand, which is 'origin' ;-).\n> \n> 'git checkout origin' doesn't work without resulting in a detached head.\n\nThat's expected because it refers to a remote tracking branch. Please \ncorrect me if I'm wrong but I think maybe what you're asking for is a \nshorthand for the branch \"$b\" where\n\n\tgit push origin $b\n\nwould update the remote tracking branch pointed to by \"origin/HEAD\". \nI've not really thought this through but if that is what you want maybe \nwe could add \"@{local}\" to give that branch. Then, with the default \nrefspecs and with \"origin/HEAD\" pointing to \"origin/master\", \n\"origin@{local}\" would be \"refs/heads/master\". If you created a feature \nbranch with\n\n\tgit checkout -b feature origin\n\nand you wanted to merge it into the local branch corresponding to the \ndefault branch on its upstream remote you could do\n\n\tgit checkout feature@{upstream}@{local}\n\tgit merge feature\n\nI don't really understand what you're trying to achieve and I'm not sure \nif the suggestion above is a good idea but it might help understand what \nit is you're trying to do. Below you say you don't work directly on the \nmain branch but then later on you're then creating a release from it. Is \n\"main\" just a mirror of \"origin/main\" or are you merging local work into \nit as well?\n\nThanks\n\nPhillip\n\n>> As Kristoffer said in another message [*1*], I would too expect that\n>> people would not work on their 'main' (or have their 'main' track\n>> the upstream's 'main').  So the utility of the piping to sed we saw\n>> above is dubious, unless we are talking about quite different\n>> workflow, but I do not think of what that other workflow would look\n>> like that makes a neutral synonym for 'main' useful.\n> \n> I don't work directly on the main branch.\n> \n> However it serves and the only starting point for creating any new feature\n> branches. This is the command I use, and would be nice if it could be\n> simplified:\n> \n>      git fetch --all\n>      git checkout $(git remote | rg '^(origin|upstream)$' | tail -n1)/HEAD -b new_branch\n> \n> The main branch is used in my work frontend project for the app release\n> command, so there I do\n> \n>      git checkout $(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')\n>      yarn release\n> \n> I think me as a non-hardcore Git maintainer spend more time in different\n> repos than you two do, so maybe the pain of switching between systems is\n> more pronounced. That's my motivation for unifying stuff.\n> \n> Just for reference, iterating all forked open-source repos on my machine\n> these are the different upstream names I work with:\n> \n>      99designs/gqlgen\n>      refs/remotes/upstream/master\n> \n>      amplitude/experiment-react-native-client\n>      refs/remotes/upstream/main\n> \n>      Antonboom/testifylint\n>      refs/remotes/upstream/master\n> \n>      cli/cli\n>      refs/remotes/upstream/trunk\n> \n>      datastax/python-driver\n>      refs/remotes/origin/master\n> \n>      dependabot/dependabot-core\n>      refs/remotes/origin/main\n> \n>      derailed/k9s\n>      refs/remotes/origin/master\n> \n>      elastic/go-elasticsearch\n>      refs/remotes/upstream/main\n> \n>      git/git\n>      refs/remotes/upstream/master\n> \n>      gitgitgadget/gitgitgadget\n>      refs/remotes/upstream/main\n> \n>      github-linguist/linguist\n>      refs/remotes/origin/main\n> \n>      go-redis/redis_rate\n>      refs/remotes/origin/v10\n> \n>      golang-migrate/migrate\n>      refs/remotes/upstream/master\n> \n>      golang/go\n>      refs/remotes/origin/master\n> \n>      golangci/golangci-lint-action\n>      refs/remotes/upstream/main\n> \n>      gradle/gradle\n>      refs/remotes/origin/master\n> \n>      Homebrew/brew\n>      refs/remotes/origin/main\n> \n>      jwalton/gh-docker-logs\n>      refs/remotes/upstream/master\n> \n>      Khan/genqlient\n>      refs/remotes/upstream/main\n> \n>      kubernetes-sigs/controller-tools\n>      refs/remotes/origin/main\n> \n>      kubernetes/kompose\n>      refs/remotes/origin/main\n> \n>      kubernetes/kubernetes\n>      refs/remotes/origin/master\n> \n>      ldez/usetesting\n>      refs/remotes/origin/main\n> \n>      liushuangls/go-anthropic\n>      refs/remotes/upstream/main\n> \n>      matryer/moq\n>      refs/remotes/upstream/main\n> \n>      mhemmings/revenuecat\n>      refs/remotes/origin/master\n> \n>      ohmyzsh/ohmyzsh\n>      refs/remotes/upstream/master\n> \n>      prettier/prettier\n>      refs/remotes/origin/main\n> \n>      RevenueCat/docs\n>      refs/remotes/upstream/main\n> \n>      RevenueCat/purchases-ios\n>      refs/remotes/origin/main\n> \n>      RevenueCat/react-native-purchases\n>      refs/remotes/origin/main\n> \n>      sashabaranov/go-openai\n>      refs/remotes/upstream/master\n> \n>      stretchr/testify\n>      refs/remotes/origin/master\n> \n>      vektah/gqlparser\n>      refs/remotes/upstream/master\n> \n> \n>> Doesn't repo_default_branch_name() do the right thing without being\n>> noisy at all even in a repository without that configured, as the\n>> function will fall back to the built-in default?  While I do not\n>> think of a workflow in which a handy access to the value the\n>> function gives would be so useful that it deserves a short-hand, it\n>> would be a reasonable candidate of what to be called \"@{default}\",\n>> if it proves useful, I would think.\n> \n> I'll play around with this a bit and see how it works. Thanks for the tip!\n> \n> \n> Harald\n> \n\n"},{"id":"534963","messageId":"20260202101445.13790-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"d92bd08f-abee-49a6-89ad-3be5e0c06ad6@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-02-02T10:14:45Z","receivedAt":"2026-02-02T10:14:48Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> I don't really understand what you're trying to achieve and I'm not sure \n> if the suggestion above is a good idea but it might help understand what \n> it is you're trying to do.\nI didn't realize I was so bad at explaining 😅\n\nWhat I want is a shorthand for switching to the local version of the\ndefault branch of the repo. This but with less voodoo:\n\n    git switch $(git rev-parse --abbrev-ref $(git remote | rg '^(origin|upstream)$' | tail -n1) | sed 's@.*/@@')\n\n\n> you say you don't work directly on the main branch but then later on\n> you're then creating a release from it. Is  \"main\" just a mirror of\n> \"origin/main\" or are you merging local work into it as well?\n\nMy main is a mirror of upstream/main. I never commit to it, just do\n'git pull' to create releases.\n\nAlso, I switch to it when I discover a bug on my branch, to try to\nunderstand if the bug is already on main or not. It's the baseline all work\nis compared against.\n\n>>      99designs/gqlgen\n>>      refs/remotes/upstream/master\n>> \n>>      amplitude/experiment-react-native-client\n>>      refs/remotes/upstream/main\n>> \n>>      Antonboom/testifylint\n>>      refs/remotes/upstream/master\n>> \n>>      cli/cli\n>>      refs/remotes/upstream/trunk\n\nI want a shorthand so that when in any of these repos, I'm switching to the\ndefault branch, I simply have to run\n\n    git switch @{primary}\n\nand I would end up with\n\n      99designs/gqlgen\n      Switched to branch 'master'\n \n      amplitude/experiment-react-native-client\n      Switched to branch 'main'\n \n      Antonboom/testifylint\n      Switched to branch 'main'\n \n      cli/cli\n      Switched to branch 'trunk'\n\n\nHarald\n"},{"id":"534970","messageId":"xmqqjywv2une.fsf@gitster.g","threadId":"64884","inReplyTo":"20260131205521.11876-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-02T12:32:21Z","receivedAt":"2026-02-02T12:32:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>> Doesn't repo_default_branch_name() do the right thing without being\n>> noisy at all even in a repository without that configured, as the\n>> function will fall back to the built-in default?  While I do not\n>> think of a workflow in which a handy access to the value the\n>> function gives would be so useful that it deserves a short-hand, it\n>> would be a reasonable candidate of what to be called \"@{default}\",\n>> if it proves useful, I would think.\n>\n> After looking a this, this is hard-coded. Not showing what is relevant for\n> each repo that exists:\n\nYes.\n\nOf course.  It was a suggestion to avoid getting failures in\nrepositories that do not override it with their own configuration\nfiles.\n\nSo the @{default} we originally discussed was not something that is\n\"relevant for each repo\", and where refs/remotes/origin/HEAD points\nat has a better chance of closer to the relevant name?\n\n"},{"id":"534978","messageId":"20260202153041.2939-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"xmqqjywv2une.fsf@gitster.g","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-02-02T15:30:41Z","receivedAt":"2026-02-02T15:30:46Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> So the @{default} we originally discussed was not something that is\n> \"relevant for each repo\", and where refs/remotes/origin/HEAD points\n> at has a better chance of closer to the relevant name?\n\nThis is a communication error on my side again, my goal was always to have\nsometing that is relevant for every repo.\n\n\nHarald\n"},{"id":"535007","messageId":"CALnO6CDrdfSFuY8xjvno3+2MVB2JHUYdNsYTYqqRsfcDiWa1uQ@mail.gmail.com","threadId":"64884","inReplyTo":"20260202101445.13790-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-02-02T19:40:38Z","receivedAt":"2026-02-02T19:40:49Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Mon, Feb 2, 2026 at 5:19 AM Harald Nordgren <haraldnordgren@gmail.com> wrote:\n>\n> > I don't really understand what you're trying to achieve and I'm not sure\n> > if the suggestion above is a good idea but it might help understand what\n> > it is you're trying to do.\n> I didn't realize I was so bad at explaining 😅\n>\n> What I want is a shorthand for switching to the local version of the\n> default branch of the repo. This but with less voodoo:\n>\n>     git switch $(git rev-parse --abbrev-ref $(git remote | rg '^(origin|upstream)$' | tail -n1) | sed 's@.*/@@')\n>\n>\n> > you say you don't work directly on the main branch but then later on\n> > you're then creating a release from it. Is  \"main\" just a mirror of\n> > \"origin/main\" or are you merging local work into it as well?\n>\n> My main is a mirror of upstream/main. I never commit to it, just do\n> 'git pull' to create releases.\n>\n> Also, I switch to it when I discover a bug on my branch, to try to\n> understand if the bug is already on main or not. It's the baseline all work\n> is compared against.\n>\n> >>      99designs/gqlgen\n> >>      refs/remotes/upstream/master\n> >>\n> >>      amplitude/experiment-react-native-client\n> >>      refs/remotes/upstream/main\n> >>\n> >>      Antonboom/testifylint\n> >>      refs/remotes/upstream/master\n> >>\n> >>      cli/cli\n> >>      refs/remotes/upstream/trunk\n>\n> I want a shorthand so that when in any of these repos, I'm switching to the\n> default branch, I simply have to run\n>\n>     git switch @{primary}\n>\n> and I would end up with\n>\n>       99designs/gqlgen\n>       Switched to branch 'master'\n>\n>       amplitude/experiment-react-native-client\n>       Switched to branch 'main'\n>\n>       Antonboom/testifylint\n>       Switched to branch 'main'\n>\n>       cli/cli\n>       Switched to branch 'trunk'\n>\n>\n> Harald\n\nIf you don't need to be on a branch, then \"git switch -d origin\" (or\nupstream, or whatever your remote is) should work just fine.\n\nThat just makes discovering the name of the remote the \"interesting\" part…\n\n-- \nD. Ben Knoble\n"},{"id":"535015","messageId":"20260202211919.4968-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"CALnO6CDrdfSFuY8xjvno3+2MVB2JHUYdNsYTYqqRsfcDiWa1uQ@mail.gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-02-02T21:19:19Z","receivedAt":"2026-02-02T21:19:22Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> If you don't need to be on a branch, then \"git switch -d origin\" (or\n> upstream, or whatever your remote is) should work just fine.\n\nThanks, but it needs to be a branch, do you use detached heads for\nanything? 🤗 For me, the only ever happen by accident.\n\n\nHarald\n"},{"id":"535018","messageId":"xmqqa4xqygns.fsf@gitster.g","threadId":"64884","inReplyTo":"CALnO6CDrdfSFuY8xjvno3+2MVB2JHUYdNsYTYqqRsfcDiWa1uQ@mail.gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-02T21:33:27Z","receivedAt":"2026-02-02T21:33:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n\n> If you don't need to be on a branch, then \"git switch -d origin\" (or\n> upstream, or whatever your remote is) should work just fine.\n>\n> That just makes discovering the name of the remote the \"interesting\" part…\n\nThe only thing that is different is if you need to _name_ a branch,\nor the commit pointed at is sufficient.  In order to run something\nlike \"git shortlog origin..\", \"git shortlog @{default}..\" is not\nneeded.\n\nOf course, checking out and to be on the branch requires you to name\na branch (otherwise when two branches point at the same commit, you\ncannot tell which one you want to check out).\n\n\n"},{"id":"535019","messageId":"xmqqms1qx1kk.fsf@gitster.g","threadId":"64884","inReplyTo":"20260202101445.13790-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-02T21:44:43Z","receivedAt":"2026-02-02T21:44:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n> My main is a mirror of upstream/main. I never commit to it, just do\n> 'git pull' to create releases.\n>\n> Also, I switch to it when I discover a bug on my branch, to try to\n> understand if the bug is already on main or not. It's the baseline all work\n> is compared against.\n\nOK.\n\nBut for that kind of \"I go there to see, but I never modify anything\nthere let alone committing to it\" usage, detached HEAD is exactly\nthe tool invented for.  So while I can understand the allure of\nalways having my local 'main' be at the 'main' at the remote, I no\nlonger see this as a \"must have, somebody would die unless we add\nit\" kind of thing anymore, even though I think it may be a nice\nthing to have for some people.\n\nThanks.\n"},{"id":"535020","messageId":"6e2e8e79-7fe9-4b3c-9257-bedba0be3308@app.fastmail.com","threadId":"64884","inReplyTo":"20260202211919.4968-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-02-02T21:53:08Z","receivedAt":"2026-02-02T21:53:32Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Mon, Feb 2, 2026, at 22:19, Harald Nordgren wrote:\n>> If you don't need to be on a branch, then \"git switch -d origin\" (or\n>> upstream, or whatever your remote is) should work just fine.\n>\n> Thanks, but it needs to be a branch, do you use detached heads for\n> anything? 🤗 For me, the only ever happen by accident.\n\nThe remote-tracking branch itself is enough for me to do things like:\n\n> > Also, I switch to it when I discover a bug on my branch, to try to\n> > understand if the bug is already on main or not. It's the baseline all work\n> > is compared against.\n\nAnd to compare against with git-diff(1), use in a range to git-log(1),\nand use in `git branch --set-upstream-to=origin`.\n"},{"id":"535021","messageId":"4EAD764C-5FCD-4934-BC39-5BF66C723C32@gmail.com","threadId":"64884","inReplyTo":"xmqqa4xqygns.fsf@gitster.g","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-02-02T22:16:26Z","receivedAt":"2026-02-02T22:16:38Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"\n> Le 2 févr. 2026 à 16:33, Junio C Hamano <gitster@pobox.com> a écrit :\n> \n> ﻿\"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n> \n>> If you don't need to be on a branch, then \"git switch -d origin\" (or\n>> upstream, or whatever your remote is) should work just fine.\n>> \n>> That just makes discovering the name of the remote the \"interesting\" part…\n> \n> The only thing that is different is if you need to _name_ a branch,\n> or the commit pointed at is sufficient.  In order to run something\n> like \"git shortlog origin..\", \"git shortlog @{default}..\" is not\n> needed.\n> \n> Of course, checking out and to be on the branch requires you to name\n> a branch (otherwise when two branches point at the same commit, you\n> cannot tell which one you want to check out)\n\nI oversimplified; thanks."},{"id":"535022","messageId":"A8942826-6235-4C46-9217-708671ED4678@gmail.com","threadId":"64884","inReplyTo":"20260202211919.4968-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-02-02T22:17:31Z","receivedAt":"2026-02-02T22:17:43Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"\n> Le 2 févr. 2026 à 16:19, Harald Nordgren <haraldnordgren@gmail.com> a écrit :\n> \n> ﻿\n>> \n>> If you don't need to be on a branch, then \"git switch -d origin\" (or\n>> upstream, or whatever your remote is) should work just fine.\n> \n> Thanks, but it needs to be a branch, do you use detached heads for\n> anything? 🤗 For me, the only ever happen by accident.\n\nYes, frequently :)\n\nI run « git switch -d origin » a lot to avoid having to keep a local main branch up to date (if I don’t use it for anything, which is often the case). "},{"id":"535023","messageId":"xmqqcy2mwzjy.fsf@gitster.g","threadId":"64884","inReplyTo":"d92bd08f-abee-49a6-89ad-3be5e0c06ad6@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-02T22:28:17Z","receivedAt":"2026-02-02T22:28:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> ... Please \n> correct me if I'm wrong but I think maybe what you're asking for is a \n> shorthand for the branch \"$b\" where\n>\n> \tgit push origin $b\n>\n> would update the remote tracking branch pointed to by \"origin/HEAD\". \n> I've not really thought this through but if that is what you want maybe \n> we could add \"@{local}\" to give that branch. Then, with the default \n> refspecs and with \"origin/HEAD\" pointing to \"origin/master\", \n> \"origin@{local}\" would be \"refs/heads/master\". If you created a feature \n> branch with\n>\n> \tgit checkout -b feature origin\n>\n> and you wanted to merge it into the local branch corresponding to the \n> default branch on its upstream remote you could do\n>\n> \tgit checkout feature@{upstream}@{local}\n> \tgit merge feature\n\nI do not know if that is what Harald is looking for, but I did\nwonder if we have use cases like that where we can string together\nmultiple @{modifier} after a branch name.  The @{local} thing that\ntakes a remote-tracking branch and gives the local branch that would\npush to would be a \"reverse\" of @{push}; I wonder if three is need\nfor a similar concept for a reverse of @{upstream} and if so, it\nwould also be @{local-something-else}, and we may want to name this\none not just @{local} but @{local-something}.\n\nThat \"feature@{upstream}@{local}\" notation is a great food for\nthought.\n\nThanks.\n\n"},{"id":"535025","messageId":"20260202225436.12409-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"A8942826-6235-4C46-9217-708671ED4678@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-02-02T22:54:36Z","receivedAt":"2026-02-02T22:54:40Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> Yes, frequently :)\n>\n> I run « git switch -d origin » a lot to avoid having to keep a local main\n> branch up to date (if I don’t use it for anything, which is often the\n> case).\n\nVery interesting! I'm gonna try this! I will also do this\n\n    git config --global advice.detachedHead false\n\nbecause that advice always looked to me like I was doing something very\nwrong.\n\n\n\nHarald\n"},{"id":"535026","messageId":"20260202225630.12507-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"xmqqms1qx1kk.fsf@gitster.g","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-02-02T22:56:30Z","receivedAt":"2026-02-02T22:56:33Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> But for that kind of \"I go there to see, but I never modify anything\n> there let alone committing to it\" usage, detached HEAD is exactly\n> the tool invented for.  So while I can understand the allure of\n> always having my local 'main' be at the 'main' at the remote, I no\n> longer see this as a \"must have, somebody would die unless we add\n> it\" kind of thing anymore, even though I think it may be a nice\n> thing to have for some people.\n\nThis makes a lot of sense 👍\n\n\nHarald\n"},{"id":"535027","messageId":"20260202230302.13009-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"CALnO6CDrdfSFuY8xjvno3+2MVB2JHUYdNsYTYqqRsfcDiWa1uQ@mail.gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-02-02T23:03:02Z","receivedAt":"2026-02-02T23:03:07Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"> That just makes discovering the name of the remote the \"interesting\" part…\n\nYeah, this is true! Discovering which is the upstream remote is still\nnon-trivial. It's aggravated because the GitHub 'gh' tool will rename\n'origin' to 'upstream' when forking + creating PR from the CLI.\n\nWhich I why I do this becomes necessary\n\n    git remote | rg '^(origin|upstream)$' | tail -n1\n\n\nHarald\n"},{"id":"535059","messageId":"20260203111829.31088-1-haraldnordgren@gmail.com","threadId":"64884","inReplyTo":"20260202225630.12507-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-02-03T11:18:29Z","receivedAt":"2026-02-03T11:18:33Z","isPatch":true,"sender":{"key":"haraldnordgren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9569897?v=4"},"body":"I just ran into this issue when working from a detached HEAD. Maybe I can\nget around it by adding \"HEAD\" as acceptable branches.\n\nBut it's an example that a lot of code out there requires real branches to\ndo things.\n\n\t$ release-it --ci\n\tERROR Must be on branch release/*,hotfix/*,develop\n\tDocumentation: https://git.io/release-it-git\n\n\terror Command failed with exit code 1.\n\tinfo Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.\n\n\n\nHarald\n"},{"id":"535062","messageId":"9120dbf3-f10f-4333-aa70-f24835d7ef02@gmail.com","threadId":"64884","inReplyTo":"20260202101445.13790-1-haraldnordgren@gmail.com","subject":"Re: [PATCH] revisions: add @{default} shorthand for default branch","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-02-03T14:38:48Z","receivedAt":"2026-02-03T14:38:53Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 02/02/2026 10:14, Harald Nordgren wrote:\n>> I don't really understand what you're trying to achieve and I'm not sure\n>> if the suggestion above is a good idea but it might help understand what\n>> it is you're trying to do.\n >\n> I want a shorthand so that when in any of these repos, I'm switching to the\n> default branch, I simply have to run\n> \n>      git switch @{primary}\n> \n> and I would end up with\n> \n>        99designs/gqlgen\n>        Switched to branch 'master'\n>   \n>        amplitude/experiment-react-native-client\n>        Switched to branch 'main'\n>   \n>        Antonboom/testifylint\n>        Switched to branch 'main'\n>   \n>        cli/cli\n>        Switched to branch 'trunk'\n\nI think I understand now. That sounds tricky to do in the general case \nbecause we don't know what the remote is called. \"origin\" and \"upstream\" \nare popular choices but the user can choose any name they want when they \nrun \"git clone\" (or rename the remote after they clone). If there is \nonly one remote then its simple because there is only one choice. It's \nalso simple if there are multiple remotes and they all use the same \ndefault branch name and refspecs. If  remote.pushDefault is set we can \nprobably rule that remote out. If there's a branch checked out with an \nupstream set we could use that remote but there's no guarantee that's \nthe remote the user wants. I don't think there's a robust way to \ndetermine the remote the user wants in the general case.\n\nWith the \"@{local}\" thing I suggested yesterday the user would have to \nname the remote which makes everything well defined but I think you want \nto avoid having to do that.\n\nThanks\n\nPhillip\n"}]}