{"thread":{"id":"65796","subject":"[PATCH 0/2] branch/push: suggest intended form when remote/branch slip given","startedAt":"2026-06-12T11:10:21Z","lastAt":"2026-06-28T07:22:01Z","messageCount":26,"participants":["Harald Nordgren via GitGitGadget","Harald Nordgren","Weijie Yuan","Junio C Hamano","Ben Knoble"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"545363","messageId":"pull.2331.git.git.1781262619.gitgitgadget@gmail.com","threadId":"65796","inReplyTo":null,"subject":"[PATCH 0/2] branch/push: suggest intended form when remote/branch slip given","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-06-12T11:10:17Z","receivedAt":"2026-06-12T11:10:21Z","isPatch":true,"body":"When the repository or upstream argument is a slip like \"origin/main\" or\n\"origin main\", suggest the intended \"git push origin main\" or \"git branch\n--set-upstream-to=origin/main\" form instead of failing with an unrelated\nerror.\n\nHarald Nordgren (2):\n  branch: suggest <remote>/<branch> on upstream slip\n  push: suggest <remote> <branch> for a slash slip\n\n Documentation/config/advice.adoc |  5 +++++\n advice.c                         |  1 +\n advice.h                         |  1 +\n builtin/branch.c                 | 17 ++++++++++++++\n builtin/push.c                   | 26 +++++++++++++++++++++-\n t/t3200-branch.sh                | 38 ++++++++++++++++++++++++++++++++\n t/t5529-push-errors.sh           | 31 ++++++++++++++++++++++++++\n 7 files changed, 118 insertions(+), 1 deletion(-)\n\n\nbase-commit: 3e65291872de10c3f0bf05ea8c24187e7a71ebf0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2331%2FHaraldNordgren%2Fsuggest-remote-branch-slips-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2331/HaraldNordgren/suggest-remote-branch-slips-v1\nPull-Request: https://github.com/git/git/pull/2331\n-- \ngitgitgadget\n"},{"id":"545364","messageId":"21684539debaf433b6b63404e1a7622a5cc33283.1781262619.git.gitgitgadget@gmail.com","threadId":"65796","inReplyTo":"pull.2331.git.git.1781262619.gitgitgadget@gmail.com","subject":"[PATCH 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-06-12T11:10:18Z","receivedAt":"2026-06-12T11:10:22Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\n\"git branch --set-upstream-to origin main\" reads the trailing word as\nthe local branch to operate on and dies with \"branch 'main' does not\nexist\", pointing at the wrong problem.\n\nWhen that branch is missing and \"<remote>/<branch>\" names a real\nremote-tracking ref, suggest the intended\n\"git branch --set-upstream-to=<remote>/<branch>\" form.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/branch.c  | 17 +++++++++++++++++\n t/t3200-branch.sh | 38 ++++++++++++++++++++++++++++++++++++++\n 2 files changed, 55 insertions(+)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 1572a4f9ef..7ad3efb908 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -957,6 +957,23 @@ int cmd_branch(int argc,\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {\n \t\t\tif (!argc || branch_checked_out(branch->refname))\n \t\t\t\tdie(_(\"no commit on branch '%s' yet\"), branch->name);\n+\t\t\tif (argc == 1 && !strchr(new_upstream, '/') &&\n+\t\t\t    remote_is_configured(remote_get(new_upstream), 0)) {\n+\t\t\t\tstruct strbuf remote_ref = STRBUF_INIT;\n+\n+\t\t\t\tstrbuf_addf(&remote_ref, \"refs/remotes/%s/%s\",\n+\t\t\t\t\t    new_upstream, argv[0]);\n+\t\t\t\tif (refs_ref_exists(get_main_ref_store(the_repository),\n+\t\t\t\t\t\t    remote_ref.buf)) {\n+\t\t\t\t\tint code = die_message(_(\"--set-upstream-to takes a single <remote>/<branch> argument\"));\n+\t\t\t\t\tadvise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE,\n+\t\t\t\t\t\t\t  _(\"Did you mean to use: git branch --set-upstream-to=%s/%s?\"),\n+\t\t\t\t\t\t\t  new_upstream, argv[0]);\n+\t\t\t\t\tstrbuf_release(&remote_ref);\n+\t\t\t\t\texit(code);\n+\t\t\t\t}\n+\t\t\t\tstrbuf_release(&remote_ref);\n+\t\t\t}\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \t\t}\n \ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex e7829c2c4b..e2682a83a0 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1022,6 +1022,44 @@ test_expect_success '--set-upstream-to fails on a missing dst branch' '\n \ttest_cmp expect err\n '\n \n+test_expect_success '--set-upstream-to suggests <remote>/<branch> on slip' '\n+\ttest_when_finished \"git remote remove slip-remote\" &&\n+\tgit remote add slip-remote . &&\n+\tgit update-ref refs/remotes/slip-remote/slip-feature HEAD &&\n+\ttest_must_fail git branch --set-upstream-to slip-remote slip-feature 2>err &&\n+\ttest_grep \"takes a single <remote>/<branch> argument\" err &&\n+\ttest_grep \"hint: Did you mean to use: git branch --set-upstream-to=slip-remote/slip-feature?\" err &&\n+\ttest_must_fail git -c advice.setUpstreamFailure=false \\\n+\t\tbranch --set-upstream-to slip-remote slip-feature 2>err &&\n+\ttest_grep ! \"Did you mean\" err\n+'\n+\n+test_expect_success '--set-upstream-to does not suggest when no matching remote ref' '\n+\ttest_when_finished \"git remote remove slip-remote\" &&\n+\tgit remote add slip-remote . &&\n+\ttest_must_fail git branch --set-upstream-to slip-remote no-such-branch 2>err &&\n+\ttest_grep \"branch ${SQ}no-such-branch${SQ} does not exist\" err &&\n+\ttest_grep ! \"Did you mean\" err\n+'\n+\n+test_expect_success '--set-upstream-to to a local branch is not mistaken for a slip' '\n+\tgit branch slip-local-upstream &&\n+\tgit branch slip-local-target &&\n+\tgit branch --set-upstream-to=slip-local-upstream slip-local-target 2>err &&\n+\ttest_grep ! \"Did you mean\" err &&\n+\techo refs/heads/slip-local-upstream >expect &&\n+\tgit config branch.slip-local-target.merge >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--set-upstream-to slip suggestion keeps a slashed branch name' '\n+\ttest_when_finished \"git remote remove slip-remote\" &&\n+\tgit remote add slip-remote . &&\n+\tgit update-ref refs/remotes/slip-remote/slip/feature HEAD &&\n+\ttest_must_fail git branch --set-upstream-to slip-remote slip/feature 2>err &&\n+\ttest_grep \"hint: Did you mean to use: git branch --set-upstream-to=slip-remote/slip/feature?\" err\n+'\n+\n test_expect_success '--set-upstream-to fails on a missing src branch' '\n \ttest_must_fail git branch --set-upstream-to does-not-exist main 2>err &&\n \ttest_grep \"the requested upstream branch '\"'\"'does-not-exist'\"'\"' does not exist\" err\n-- \ngitgitgadget\n\n"},{"id":"545365","messageId":"ea1412b1107f485cf52c953e387a513d95d82b53.1781262619.git.gitgitgadget@gmail.com","threadId":"65796","inReplyTo":"pull.2331.git.git.1781262619.gitgitgadget@gmail.com","subject":"[PATCH 2/2] push: suggest <remote> <branch> for a slash slip","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-06-12T11:10:19Z","receivedAt":"2026-06-12T11:10:23Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\n\"git push origin/main\" is treated as a repository and dies with\n\"'origin/main' does not appear to be a git repository\", with no hint\nthat a space was meant instead of a slash.\n\nWhen the argument is not an existing path or configured remote but its\npart before the first slash names one, suggest the intended\n\"git push <remote> <branch>\" form. The suggestion is shown as advice so\nit can be silenced with advice.pushRepoLooksLikeRef.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/config/advice.adoc |  5 +++++\n advice.c                         |  1 +\n advice.h                         |  1 +\n builtin/push.c                   | 26 +++++++++++++++++++++++++-\n t/t5529-push-errors.sh           | 31 +++++++++++++++++++++++++++++++\n 5 files changed, 63 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc\nindex 257db58918..fa77a5110e 100644\n--- a/Documentation/config/advice.adoc\n+++ b/Documentation/config/advice.adoc\n@@ -90,6 +90,11 @@ all advice messages.\n \t\tShown when linkgit:git-push[1] rejects a forced update of\n \t\ta branch when its remote-tracking ref has updates that we\n \t\tdo not have locally.\n+\tpushRepoLooksLikeRef::\n+\t\tShown when the repository given to linkgit:git-push[1] is not\n+\t\ta configured remote but looks like a `<remote>/<branch>` ref,\n+\t\tsuggesting that the remote and branch be given as separate\n+\t\targuments.\n \tpushUnqualifiedRefname::\n \t\tShown when linkgit:git-push[1] gives up trying to\n \t\tguess based on the source and destination refs what\ndiff --git a/advice.c b/advice.c\nindex 0018501b7b..63bf8b0c5f 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -69,6 +69,7 @@ static struct {\n \t[ADVICE_PUSH_NON_FF_CURRENT]\t\t\t= { \"pushNonFFCurrent\" },\n \t[ADVICE_PUSH_NON_FF_MATCHING]\t\t\t= { \"pushNonFFMatching\" },\n \t[ADVICE_PUSH_REF_NEEDS_UPDATE]\t\t\t= { \"pushRefNeedsUpdate\" },\n+\t[ADVICE_PUSH_REPO_LOOKS_LIKE_REF]\t\t= { \"pushRepoLooksLikeRef\" },\n \t[ADVICE_PUSH_UNQUALIFIED_REF_NAME]\t\t= { \"pushUnqualifiedRefName\" },\n \t[ADVICE_PUSH_UPDATE_REJECTED]\t\t\t= { \"pushUpdateRejected\" },\n \t[ADVICE_PUSH_UPDATE_REJECTED_ALIAS]\t\t= { \"pushNonFastForward\" }, /* backwards compatibility */\ndiff --git a/advice.h b/advice.h\nindex 8def280688..66f6cd6a77 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -36,6 +36,7 @@ enum advice_type {\n \tADVICE_PUSH_NON_FF_CURRENT,\n \tADVICE_PUSH_NON_FF_MATCHING,\n \tADVICE_PUSH_REF_NEEDS_UPDATE,\n+\tADVICE_PUSH_REPO_LOOKS_LIKE_REF,\n \tADVICE_PUSH_UNQUALIFIED_REF_NAME,\n \tADVICE_PUSH_UPDATE_REJECTED,\n \tADVICE_PUSH_UPDATE_REJECTED_ALIAS,\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 6021b71d66..c21febadbe 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -8,6 +8,7 @@\n #include \"advice.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"dir.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n@@ -744,6 +745,29 @@ int cmd_push(int argc,\n \n \tif (repo) {\n \t\tif (!add_remote_or_group(repo, &remote_group)) {\n+\t\t\tconst char *slash = strchr(repo, '/');\n+\t\t\tstruct remote *r;\n+\n+\t\t\t/*\n+\t\t\t * A \"<remote>/<branch>\" argument that does not name\n+\t\t\t * a path is likely a slip for the separate\n+\t\t\t * \"<remote> <branch>\" form, so suggest that instead.\n+\t\t\t */\n+\t\t\tif (slash && slash[1] && !file_exists(repo)) {\n+\t\t\t\tstruct strbuf name = STRBUF_INIT;\n+\n+\t\t\t\tstrbuf_add(&name, repo, slash - repo);\n+\t\t\t\tif (remote_is_configured(remote_get(name.buf), 0)) {\n+\t\t\t\t\tint code = die_message(_(\"'%s' is not a valid push target\"), repo);\n+\t\t\t\t\tadvise_if_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF,\n+\t\t\t\t\t\t\t  _(\"Did you mean to use: git push %s %s?\"),\n+\t\t\t\t\t\t\t  name.buf, slash + 1);\n+\t\t\t\t\tstrbuf_release(&name);\n+\t\t\t\t\texit(code);\n+\t\t\t\t}\n+\t\t\t\tstrbuf_release(&name);\n+\t\t\t}\n+\n \t\t\t/*\n \t\t\t * Not a configured remote name or group name.\n \t\t\t * Try treating it as a direct URL or path, e.g.\n@@ -753,7 +777,7 @@ int cmd_push(int argc,\n \t\t\t * from the URL so the loop below can handle it\n \t\t\t * identically to a named remote.\n \t\t\t */\n-\t\t\tstruct remote *r = pushremote_get(repo);\n+\t\t\tr = pushremote_get(repo);\n \t\t\tif (!r)\n \t\t\t\tdie(_(\"bad repository '%s'\"), repo);\n \t\t\tstring_list_append(&remote_group, r->name);\ndiff --git a/t/t5529-push-errors.sh b/t/t5529-push-errors.sh\nindex 80b06a0cd2..cfb294305d 100755\n--- a/t/t5529-push-errors.sh\n+++ b/t/t5529-push-errors.sh\n@@ -54,6 +54,37 @@ test_expect_success 'detect empty remote with targeted refspec' '\n \tgrep \"fatal: bad repository ${SQ}${SQ}\" stderr\n '\n \n+test_expect_success 'suggest <remote> <branch> for a <remote>/<branch> slip' '\n+\ttest_must_fail git push origin/main 2>stderr &&\n+\tgrep \"${SQ}origin/main${SQ} is not a valid push target\" stderr &&\n+\tgrep \"hint: Did you mean to use: git push origin main?\" stderr &&\n+\ttest_must_fail git -c advice.pushRepoLooksLikeRef=false push origin/main 2>stderr &&\n+\t! grep \"Did you mean\" stderr\n+'\n+\n+test_expect_success 'suggest <remote> <branch> when the branch has slashes' '\n+\ttest_must_fail git push origin/feature/x 2>stderr &&\n+\tgrep \"hint: Did you mean to use: git push origin feature/x?\" stderr\n+'\n+\n+test_expect_success 'no suggestion when prefix is not a configured remote' '\n+\ttest_must_fail git push not-a-remote/main 2>stderr &&\n+\t! grep \"Did you mean\" stderr\n+'\n+\n+test_expect_success 'no suggestion for a trailing slash with no branch' '\n+\ttest_must_fail git push origin/ 2>stderr &&\n+\t! grep \"Did you mean\" stderr\n+'\n+\n+test_expect_success 'no suggestion when the argument is an existing path' '\n+\ttest_when_finished \"rm -rf origin\" &&\n+\tgit init --bare origin/main &&\n+\tgit push origin/main HEAD:refs/heads/pushed 2>stderr &&\n+\t! grep \"Did you mean\" stderr &&\n+\tgit -C origin/main rev-parse --verify refs/heads/pushed\n+'\n+\n test_expect_success 'detect ambiguous refs early' '\n \tgit branch foo &&\n \tgit tag foo &&\n-- \ngitgitgadget\n"},{"id":"546140","messageId":"CAHwyqnUNb-P7F3EREbTtdTmpx=M7bSw3idrKdm-tK3wTKnePZA@mail.gmail.com","threadId":"65796","inReplyTo":"pull.2331.git.git.1781262619.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/2] branch/push: suggest intended form when remote/branch slip given","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-06-22T08:41:18Z","receivedAt":"2026-06-22T08:41:56Z","isPatch":true,"body":"Would be nice to get some feedback here.\n\nThis is maybe not the most pressing issue, but I remember being quite\ndumbfounded when I was a new Git user; not understanding why some\ncommands required one format \"origin/main\" and other required \"origin\nmain\". It would be better if we could actually solve it, but I suspect\nit's hard without breaking backward compatibility, so maybe this is a\ngood middle road?\n\n\nHarald\n"},{"id":"546149","messageId":"ajj5Y82644z-ytSA@wyuan.org","threadId":"65796","inReplyTo":"CAHwyqnUNb-P7F3EREbTtdTmpx=M7bSw3idrKdm-tK3wTKnePZA@mail.gmail.com","subject":"Re: [PATCH 0/2] branch/push: suggest intended form when remote/branch slip given","fromName":"Weijie Yuan","fromEmail":"wy@wyuan.org","sentAt":"2026-06-22T08:59:15Z","receivedAt":"2026-06-22T08:59:32Z","isPatch":true,"body":"On Mon, Jun 22, 2026 at 10:41:18AM +0200, Harald Nordgren wrote:\n> This is maybe not the most pressing issue, but I remember being quite\n> dumbfounded when I was a new Git user; not understanding why some\n> commands required one format \"origin/main\" and other required \"origin\n> main\".\n\nCan't agree more. It's quite confusing for new users like me.\n"},{"id":"546211","messageId":"xmqq1pdytkmj.fsf@gitster.g","threadId":"65796","inReplyTo":"21684539debaf433b6b63404e1a7622a5cc33283.1781262619.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-22T19:56:04Z","receivedAt":"2026-06-22T19:56:07Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> \"git branch --set-upstream-to origin main\" reads the trailing word as\n> the local branch to operate on and dies with \"branch 'main' does not\n> exist\", pointing at the wrong problem.\n\nWhen 'main' does not exist locally,\n\n    $ git branch --set-upstream-to \"$anything\" main\n\nwould fail before even looking at the \"$anything\" (which is supposed\nto specify the new_upstream for the named local branch 'main').  The\noperation is to set the upstream for 'main', and if 'main' does not\nexist, doesn't the user deserve the error that says 'main' does not\nexist, no matter what \"$anything\" is, whether it is a well-formed or\nill-formed remote tracking branch name?\n\nSo it is unclear, at least to me, why \"branch 'main' does not exist\"\nis an inappropriate message, mostly because these three lines does\nnot clearly tell me what the user _expected_ the command line to do.\n\nWhen 'main' does exist, but named upstream \"$anything\" does not, we\nget\n\n    $ git branch sample master ;# make sure the thing exists\n    $ git branch --set-upstream-to origin sample\n    fatal: the requested upstream branch 'origin' does not exist\n    hint:\n    hint: If you are planning on basing your work on an upstream\n    hint: branch that already exists at the remote, you may need to\n    hint: run \"git fetch\" to retrieve it.\n    hint:\n    hint: If you are planning to push out a new local branch that\n    hint: will track its remote counterpart, you may want to use\n    hint: \"git push -u\" to set the upstream config as you push.\n    hint: Disable this message with \"git config set advice.setUpstreamFailure false\"\n\nwhich does sound clear enough to me, even though it does not exactly\nsay \"Even though upstream branch 'origin' does not exist, 'origin'\nis a nickname for a remote, perhaps you meant to say\norigin/something?\"\n\nI do not doubt you are trying to address a real issue, but the above\nthree-line description does not tell me what that problem is.\n\nNow I do not regularly use --set-upstream-to, so I may be missing an\nobvious common mistake modes, but a couple of my attempts to make\nbad command invocations seem to give me reasonable responses:\n\n    $ git branch --set-upstream-to ko/master sample\n    branch 'sample' set up to track 'ko/master'.\n\nOK, both are well formed so no problem.\n\n    $ git branch --set-upstream-to ko/mastre sample\n    fatal: the requested upstream branch 'ko/mastre' does not exist\n    hint:\n    hint: If you are planning on basing your work on an upstream\n    hint: branch that already exists at the remote, you may need to\n    hint: run \"git fetch\" to retrieve it.\n    hint:\n    hint: If you are planning to push out a new local branch that\n    hint: will track its remote counterpart, you may want to use\n    hint: \"git push -u\" to set the upstream config as you push.\n    hint: Disable this message with \"git config set advice.setUpstreamFailure false\"\n\nMisspelt upstream branch name diagnosed correctly, just like the\ncase where I gave 'origin', which does not exist, either.\n"},{"id":"546215","messageId":"xmqqtsqus40t.fsf@gitster.g","threadId":"65796","inReplyTo":"ea1412b1107f485cf52c953e387a513d95d82b53.1781262619.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] push: suggest <remote> <branch> for a slash slip","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-22T20:40:02Z","receivedAt":"2026-06-22T20:40:08Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> \"git push origin/main\" is treated as a repository and dies with\n> \"'origin/main' does not appear to be a git repository\", with no hint\n> that a space was meant instead of a slash.\n\nThis is easier for me to guess than what the user may have wanted to\ndo in the decription of [1/2].  But it still will be easier on\nreaders to say\n\n    When pusing out up update the \"main\" branch to the remote\n    \"origin\", i.e.,\n\n        $ git push origin main\n\n    it is easy for some users to mistakenly say\n\n        $ git push origin/main\n\n    instead.  This however instructs to push to remote \"origin/main\"\n    with configured refspecs, which means a completely different\n    thing.  Lucikly, often origin/main does not exist as a remote\n    and the command fails without doing any harm, but still may\n    leave the user puzzled what happened.  Give hint to ...\n\nor something like that.\n\n> When the argument is not an existing path or configured remote but its\n> part before the first slash names one, suggest the intended\n> \"git push <remote> <branch>\" form. The suggestion is shown as advice so\n> it can be silenced with advice.pushRepoLooksLikeRef.\n\nSounds sensible.\n\n>  \tif (repo) {\n>  \t\tif (!add_remote_or_group(repo, &remote_group)) {\n> +\t\t\tconst char *slash = strchr(repo, '/');\n> +\t\t\tstruct remote *r;\n> +\n> +\t\t\t/*\n> +\t\t\t * A \"<remote>/<branch>\" argument that does not name\n> +\t\t\t * a path is likely a slip for the separate\n> +\t\t\t * \"<remote> <branch>\" form, so suggest that instead.\n> +\t\t\t */\n> +\t\t\tif (slash && slash[1] && !file_exists(repo)) {\n> +\t\t\t\tstruct strbuf name = STRBUF_INIT;\n> +\n> +\t\t\t\tstrbuf_add(&name, repo, slash - repo);\n> +\t\t\t\tif (remote_is_configured(remote_get(name.buf), 0)) {\n> +\t\t\t\t\tint code = die_message(_(\"'%s' is not a valid push target\"), repo);\n> +\t\t\t\t\tadvise_if_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF,\n> +\t\t\t\t\t\t\t  _(\"Did you mean to use: git push %s %s?\"),\n> +\t\t\t\t\t\t\t  name.buf, slash + 1);\n> +\t\t\t\t\tstrbuf_release(&name);\n> +\t\t\t\t\texit(code);\n> +\t\t\t\t}\n> +\t\t\t\tstrbuf_release(&name);\n> +\t\t\t}\n\nHmph, if this class of hint is not enabled, do we still have to\nspend cycles on these \"is this a remote?  is the first token a\nremote?\" computation?  I would have expected that a change here\nwould be a two-liner:\n\n    if (!add_remote_or_group(...)) {\n+\tif (advise_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF))\n+\t\tdie_if_plausible_typo(...);\n\t... do the \"try treating it as a direct URL or path\" thing ...\n    }\n\n\nwith the bulk of the \"if it has slash, it is not a file, then advise\nand die\" logic inside the new helper function.\n\nWhat I find especially troubling is that even when advise for this\nclass of hint is not enabled, the new code will hit the new exit(),\nwithout falling back to the \"try treating it as a direct URL or\npath\" thing.  Or am I missing something?\n\nThanks.\n\n"},{"id":"546218","messageId":"xmqqpl1is2bm.fsf@gitster.g","threadId":"65796","inReplyTo":"pull.2331.git.git.1781262619.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/2] branch/push: suggest intended form when remote/branch slip given","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-22T21:16:45Z","receivedAt":"2026-06-22T21:16:48Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> When the repository or upstream argument is a slip like \"origin/main\" or\n> \"origin main\", suggest the intended \"git push origin main\" or \"git branch\n> --set-upstream-to=origin/main\" form instead of failing with an unrelated\n> error.\n\nSorry for asking a question that may be stupid, but what does the\nword \"slip\" mean in the context of the above sentence?  I am having\na hard time coming up with a topic name while queuing these two\npatches (an obvious candidate is hn/branch-push-slip-advise but I do\nnot know how well the word sits there).\n\nThanks.\n"},{"id":"546219","messageId":"xmqqh5mus1ga.fsf@gitster.g","threadId":"65796","inReplyTo":"xmqq1pdytkmj.fsf@gitster.g","subject":"Re: [PATCH 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-22T21:35:33Z","receivedAt":"2026-06-22T21:35:35Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> From: Harald Nordgren <haraldnordgren@gmail.com>\n>>\n>> \"git branch --set-upstream-to origin main\" reads the trailing word as\n>> the local branch to operate on and dies with \"branch 'main' does not\n>> exist\", pointing at the wrong problem.\n>\n> When 'main' does not exist locally,\n>\n>     $ git branch --set-upstream-to \"$anything\" main\n>\n> would fail before even looking at the \"$anything\" (which is supposed\n> to specify the new_upstream for the named local branch 'main').  The\n> operation is to set the upstream for 'main', and if 'main' does not\n> exist, doesn't the user deserve the error that says 'main' does not\n> exist, no matter what \"$anything\" is, whether it is a well-formed or\n> ill-formed remote tracking branch name?\n>\n> So it is unclear, at least to me, why \"branch 'main' does not exist\"\n> is an inappropriate message, mostly because these three lines does\n> not clearly tell me what the user _expected_ the command line to do.\n\nAfter pondering on this a bit, I _think_ (but I am guessing, and\nyour job as an author of proposed commit log message is to make sure\nyour readers do not have to guess) what the user expected was to set\nthe upstream for the currrent branch.\n\n    When trying to set the upstream for the current branch to \"main\"\n    branch of the remote \"origin\", i.e.,\n\n        $ git branch --set-upstream-to origin/main\n\n    it is easy for some users to mistakenly say\n\n        $ git branch --set-upstream-to origin main\n\n    But it is a request to set the upstream for the local branch\n    \"main\" to \"origin\", which is not expected to work as the\n    upstream most likely would look like <remote>/<branch> (e.g.,\n    \"origin/main\").  The user would get either one of these errors:\n\n        fatal: branch 'main' does not exist\n        fatal: the requested upstream branch 'origin' does not exist\n\n    Give a hint that we _suspect_ the user may have meant to set the\n    upstream of the current branch to 'origin/main' (but do so only\n    when 'origin/main' does exist), and tell them the right way to\n    spell that request.\n\nor something perhaps?\n"},{"id":"546223","messageId":"CAHwyqnXdp=hRZimp-UXSxmCW5WJ7QrL5Dmk2yBF3RaH3KTLPwg@mail.gmail.com","threadId":"65796","inReplyTo":"xmqqpl1is2bm.fsf@gitster.g","subject":"Re: [PATCH 0/2] branch/push: suggest intended form when remote/branch slip given","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-06-23T07:35:48Z","receivedAt":"2026-06-23T07:36:26Z","isPatch":true,"body":"By slip it meant mistake, so you can call it 'hn/branch-push-mistake-advise'\n\n\nHarald\n"},{"id":"546317","messageId":"0F1878CC-F982-4CC3-A2A7-D670C36515D1@gmail.com","threadId":"65796","inReplyTo":"xmqq1pdytkmj.fsf@gitster.g","subject":"Re: [PATCH 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-06-24T12:35:21Z","receivedAt":"2026-06-24T12:35:34Z","isPatch":true,"body":"\n> Le 22 juin 2026 à 15:58, Junio C Hamano <gitster@pobox.com> a écrit :\n> \n> ﻿\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n>> From: Harald Nordgren <haraldnordgren@gmail.com>\n>> \n>> \"git branch --set-upstream-to origin main\" reads the trailing word as\n>> the local branch to operate on and dies with \"branch 'main' does not\n>> exist\", pointing at the wrong problem.\n> \n> When 'main' does not exist locally,\n> \n>    $ git branch --set-upstream-to \"$anything\" main\n> \n> would fail before even looking at the \"$anything\" (which is supposed\n> to specify the new_upstream for the named local branch 'main').  The\n> operation is to set the upstream for 'main', and if 'main' does not\n> exist, doesn't the user deserve the error that says 'main' does not\n> exist, no matter what \"$anything\" is, whether it is a well-formed or\n> ill-formed remote tracking branch name?\n> \n> So it is unclear, at least to me, why \"branch 'main' does not exist\"\n> is an inappropriate message, mostly because these three lines does\n> not clearly tell me what the user _expected_ the command line to do.\n> \n> When 'main' does exist, but named upstream \"$anything\" does not, we\n> get\n> \n>    $ git branch sample master ;# make sure the thing exists\n>    $ git branch --set-upstream-to origin sample\n>    fatal: the requested upstream branch 'origin' does not exist\n\nRelatedly, if memory serves: when origin/HEAD is available locally, this works to set sample’s upstream branch to whatever origin/HEAD refers to, right? So it may not even be a mistake. (I’m pretty sure I’ve used something like « git branch -u origin » when I didn’t give an upstream of « origin » to « git switch -c branch » for various reasons.)"},{"id":"546360","messageId":"pull.2331.v2.git.git.1782338114.gitgitgadget@gmail.com","threadId":"65796","inReplyTo":"pull.2331.git.git.1781262619.gitgitgadget@gmail.com","subject":"[PATCH v2 0/2] branch/push: suggest intended form when remote/branch slip given","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-06-24T21:55:12Z","receivedAt":"2026-06-24T21:55:17Z","isPatch":true,"body":"When the repository or upstream argument is a slip like \"origin/main\" or\n\"origin main\", suggest the intended \"git push origin main\" or \"git branch\n--set-upstream-to=origin/main\" form instead of failing with an unrelated\nerror.\n\nChanges in v2:\n\n * Rewrote both commit messages to lead with the intended command, the easy\n   slip, and the resulting error, instead of the terse original.\n * Gated each suggestion on advice_enabled() up front, so a user who\n   silenced the hint pays no remote/ref lookups and falls through to the\n   original error. Extracted the detection logic into helpers\n   (die_if_repo_looks_like_ref, die_if_upstream_looks_like_remote) so each\n   call site reads as a single guarded line.\n\nHarald Nordgren (2):\n  branch: suggest <remote>/<branch> on upstream slip\n  push: suggest <remote> <branch> for a slash slip\n\n Documentation/config/advice.adoc |  5 +++++\n advice.c                         |  1 +\n advice.h                         |  1 +\n builtin/branch.c                 | 26 ++++++++++++++++++++++\n builtin/push.c                   | 31 +++++++++++++++++++++++++-\n t/t3200-branch.sh                | 38 ++++++++++++++++++++++++++++++++\n t/t5529-push-errors.sh           | 31 ++++++++++++++++++++++++++\n 7 files changed, 132 insertions(+), 1 deletion(-)\n\n\nbase-commit: ab776a62a78576513ee121424adb19597fbb7613\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2331%2FHaraldNordgren%2Fsuggest-remote-branch-slips-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2331/HaraldNordgren/suggest-remote-branch-slips-v2\nPull-Request: https://github.com/git/git/pull/2331\n\nRange-diff vs v1:\n\n 1:  21684539de ! 1:  11bcecebf4 branch: suggest <remote>/<branch> on upstream slip\n     @@ Metadata\n       ## Commit message ##\n          branch: suggest <remote>/<branch> on upstream slip\n      \n     -    \"git branch --set-upstream-to origin main\" reads the trailing word as\n     -    the local branch to operate on and dies with \"branch 'main' does not\n     -    exist\", pointing at the wrong problem.\n     +    When setting the upstream of the current branch to the 'main' branch\n     +    of the remote 'origin', i.e.,\n      \n     -    When that branch is missing and \"<remote>/<branch>\" names a real\n     -    remote-tracking ref, suggest the intended\n     -    \"git branch --set-upstream-to=<remote>/<branch>\" form.\n     +        $ git branch --set-upstream-to origin/main\n     +\n     +    it is easy to mistakenly write\n     +\n     +        $ git branch --set-upstream-to origin main\n     +\n     +    That is parsed as a request to set the upstream of the local branch\n     +    'main' to 'origin'. When 'main' does not exist, the command dies\n     +    with:\n     +\n     +        fatal: branch 'main' does not exist\n     +\n     +    pointing at a branch the user never meant to name.\n     +\n     +    When the operated-on branch is missing and '<remote>/<branch>' names\n     +    a real remote-tracking ref, suggest the intended form:\n     +\n     +        $ git branch --set-upstream-to=origin/main\n     +\n     +    The suggestion is gated on '<remote>/<branch>' existing so it only\n     +    appears when a slipped slash is the likely explanation.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n       ## builtin/branch.c ##\n     +@@ builtin/branch.c: static int edit_branch_description(const char *branch_name)\n     + \treturn 0;\n     + }\n     + \n     ++static void die_if_upstream_looks_like_remote(const char *new_upstream, const char *branch_name)\n     ++{\n     ++\tstruct strbuf remote_ref = STRBUF_INIT;\n     ++\tint code;\n     ++\n     ++\tif (strchr(new_upstream, '/') ||\n     ++\t    !remote_is_configured(remote_get(new_upstream), 0))\n     ++\t\treturn;\n     ++\n     ++\tstrbuf_addf(&remote_ref, \"refs/remotes/%s/%s\", new_upstream, branch_name);\n     ++\tif (!refs_ref_exists(get_main_ref_store(the_repository), remote_ref.buf)) {\n     ++\t\tstrbuf_release(&remote_ref);\n     ++\t\treturn;\n     ++\t}\n     ++\n     ++\tcode = die_message(_(\"--set-upstream-to takes a single <remote>/<branch> argument\"));\n     ++\tadvise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE,\n     ++\t\t\t  _(\"Did you mean to use: git branch --set-upstream-to=%s/%s?\"),\n     ++\t\t\t  new_upstream, branch_name);\n     ++\tstrbuf_release(&remote_ref);\n     ++\texit(code);\n     ++}\n     ++\n     + int cmd_branch(int argc,\n     + \t       const char **argv,\n     + \t       const char *prefix,\n      @@ builtin/branch.c: int cmd_branch(int argc,\n       \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {\n       \t\t\tif (!argc || branch_checked_out(branch->refname))\n       \t\t\t\tdie(_(\"no commit on branch '%s' yet\"), branch->name);\n     -+\t\t\tif (argc == 1 && !strchr(new_upstream, '/') &&\n     -+\t\t\t    remote_is_configured(remote_get(new_upstream), 0)) {\n     -+\t\t\t\tstruct strbuf remote_ref = STRBUF_INIT;\n     -+\n     -+\t\t\t\tstrbuf_addf(&remote_ref, \"refs/remotes/%s/%s\",\n     -+\t\t\t\t\t    new_upstream, argv[0]);\n     -+\t\t\t\tif (refs_ref_exists(get_main_ref_store(the_repository),\n     -+\t\t\t\t\t\t    remote_ref.buf)) {\n     -+\t\t\t\t\tint code = die_message(_(\"--set-upstream-to takes a single <remote>/<branch> argument\"));\n     -+\t\t\t\t\tadvise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE,\n     -+\t\t\t\t\t\t\t  _(\"Did you mean to use: git branch --set-upstream-to=%s/%s?\"),\n     -+\t\t\t\t\t\t\t  new_upstream, argv[0]);\n     -+\t\t\t\t\tstrbuf_release(&remote_ref);\n     -+\t\t\t\t\texit(code);\n     -+\t\t\t\t}\n     -+\t\t\t\tstrbuf_release(&remote_ref);\n     -+\t\t\t}\n     ++\t\t\tif (argc == 1 &&\n     ++\t\t\t    advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n     ++\t\t\t\tdie_if_upstream_looks_like_remote(new_upstream, argv[0]);\n       \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n       \t\t}\n       \n 2:  ea1412b110 ! 2:  49de5a925d push: suggest <remote> <branch> for a slash slip\n     @@ Metadata\n       ## Commit message ##\n          push: suggest <remote> <branch> for a slash slip\n      \n     -    \"git push origin/main\" is treated as a repository and dies with\n     -    \"'origin/main' does not appear to be a git repository\", with no hint\n     -    that a space was meant instead of a slash.\n     +    When pushing the 'main' branch to the remote 'origin', i.e.,\n      \n     -    When the argument is not an existing path or configured remote but its\n     -    part before the first slash names one, suggest the intended\n     -    \"git push <remote> <branch>\" form. The suggestion is shown as advice so\n     -    it can be silenced with advice.pushRepoLooksLikeRef.\n     +        $ git push origin main\n     +\n     +    it is easy to mistakenly write\n     +\n     +        $ git push origin/main\n     +\n     +    That is parsed as the repository to push to, and since 'origin/main'\n     +    is neither a configured remote nor a path it dies with:\n     +\n     +        fatal: 'origin/main' does not appear to be a git repository\n     +\n     +    Often 'origin/main' does not exist as a repository, so the command\n     +    fails without doing any harm, but it gives no hint that a space was\n     +    meant instead of a slash and can leave the user puzzled.\n     +\n     +    When the argument is not an existing path or configured remote but\n     +    its part before the first slash names one, suggest the intended\n     +    '<remote> <branch>' form:\n     +\n     +        $ git push origin main\n     +\n     +    The suggestion is shown as advice so it can be silenced with\n     +    advice.pushRepoLooksLikeRef.\n      \n          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n      \n     @@ builtin/push.c\n       #include \"environment.h\"\n       #include \"gettext.h\"\n       #include \"hex.h\"\n     +@@ builtin/push.c: static int push_multiple(struct string_list *list,\n     + \treturn result;\n     + }\n     + \n     ++static void die_if_repo_looks_like_ref(const char *repo)\n     ++{\n     ++\tconst char *slash = strchr(repo, '/');\n     ++\tstruct strbuf name = STRBUF_INIT;\n     ++\tint code;\n     ++\n     ++\tif (!slash || !slash[1] || file_exists(repo))\n     ++\t\treturn;\n     ++\n     ++\tstrbuf_add(&name, repo, slash - repo);\n     ++\tif (!remote_is_configured(remote_get(name.buf), 0)) {\n     ++\t\tstrbuf_release(&name);\n     ++\t\treturn;\n     ++\t}\n     ++\n     ++\tcode = die_message(_(\"'%s' is not a valid push target\"), repo);\n     ++\tadvise_if_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF,\n     ++\t\t\t  _(\"Did you mean to use: git push %s %s?\"),\n     ++\t\t\t  name.buf, slash + 1);\n     ++\tstrbuf_release(&name);\n     ++\texit(code);\n     ++}\n     ++\n     + int cmd_push(int argc,\n     + \t     const char **argv,\n     + \t     const char *prefix,\n      @@ builtin/push.c: int cmd_push(int argc,\n       \n       \tif (repo) {\n       \t\tif (!add_remote_or_group(repo, &remote_group)) {\n     -+\t\t\tconst char *slash = strchr(repo, '/');\n      +\t\t\tstruct remote *r;\n      +\n     -+\t\t\t/*\n     -+\t\t\t * A \"<remote>/<branch>\" argument that does not name\n     -+\t\t\t * a path is likely a slip for the separate\n     -+\t\t\t * \"<remote> <branch>\" form, so suggest that instead.\n     -+\t\t\t */\n     -+\t\t\tif (slash && slash[1] && !file_exists(repo)) {\n     -+\t\t\t\tstruct strbuf name = STRBUF_INIT;\n     -+\n     -+\t\t\t\tstrbuf_add(&name, repo, slash - repo);\n     -+\t\t\t\tif (remote_is_configured(remote_get(name.buf), 0)) {\n     -+\t\t\t\t\tint code = die_message(_(\"'%s' is not a valid push target\"), repo);\n     -+\t\t\t\t\tadvise_if_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF,\n     -+\t\t\t\t\t\t\t  _(\"Did you mean to use: git push %s %s?\"),\n     -+\t\t\t\t\t\t\t  name.buf, slash + 1);\n     -+\t\t\t\t\tstrbuf_release(&name);\n     -+\t\t\t\t\texit(code);\n     -+\t\t\t\t}\n     -+\t\t\t\tstrbuf_release(&name);\n     -+\t\t\t}\n     ++\t\t\tif (advice_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF))\n     ++\t\t\t\tdie_if_repo_looks_like_ref(repo);\n      +\n       \t\t\t/*\n       \t\t\t * Not a configured remote name or group name.\n\n-- \ngitgitgadget\n"},{"id":"546361","messageId":"11bcecebf43797a889f08e79401370f43b2917a8.1782338114.git.gitgitgadget@gmail.com","threadId":"65796","inReplyTo":"pull.2331.v2.git.git.1782338114.gitgitgadget@gmail.com","subject":"[PATCH v2 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-06-24T21:55:13Z","receivedAt":"2026-06-24T21:55:18Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen setting the upstream of the current branch to the 'main' branch\nof the remote 'origin', i.e.,\n\n    $ git branch --set-upstream-to origin/main\n\nit is easy to mistakenly write\n\n    $ git branch --set-upstream-to origin main\n\nThat is parsed as a request to set the upstream of the local branch\n'main' to 'origin'. When 'main' does not exist, the command dies\nwith:\n\n    fatal: branch 'main' does not exist\n\npointing at a branch the user never meant to name.\n\nWhen the operated-on branch is missing and '<remote>/<branch>' names\na real remote-tracking ref, suggest the intended form:\n\n    $ git branch --set-upstream-to=origin/main\n\nThe suggestion is gated on '<remote>/<branch>' existing so it only\nappears when a slipped slash is the likely explanation.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/branch.c  | 26 ++++++++++++++++++++++++++\n t/t3200-branch.sh | 38 ++++++++++++++++++++++++++++++++++++++\n 2 files changed, 64 insertions(+)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 1572a4f9ef..cefc4519a7 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -706,6 +706,29 @@ static int edit_branch_description(const char *branch_name)\n \treturn 0;\n }\n \n+static void die_if_upstream_looks_like_remote(const char *new_upstream, const char *branch_name)\n+{\n+\tstruct strbuf remote_ref = STRBUF_INIT;\n+\tint code;\n+\n+\tif (strchr(new_upstream, '/') ||\n+\t    !remote_is_configured(remote_get(new_upstream), 0))\n+\t\treturn;\n+\n+\tstrbuf_addf(&remote_ref, \"refs/remotes/%s/%s\", new_upstream, branch_name);\n+\tif (!refs_ref_exists(get_main_ref_store(the_repository), remote_ref.buf)) {\n+\t\tstrbuf_release(&remote_ref);\n+\t\treturn;\n+\t}\n+\n+\tcode = die_message(_(\"--set-upstream-to takes a single <remote>/<branch> argument\"));\n+\tadvise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE,\n+\t\t\t  _(\"Did you mean to use: git branch --set-upstream-to=%s/%s?\"),\n+\t\t\t  new_upstream, branch_name);\n+\tstrbuf_release(&remote_ref);\n+\texit(code);\n+}\n+\n int cmd_branch(int argc,\n \t       const char **argv,\n \t       const char *prefix,\n@@ -957,6 +980,9 @@ int cmd_branch(int argc,\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {\n \t\t\tif (!argc || branch_checked_out(branch->refname))\n \t\t\t\tdie(_(\"no commit on branch '%s' yet\"), branch->name);\n+\t\t\tif (argc == 1 &&\n+\t\t\t    advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n+\t\t\t\tdie_if_upstream_looks_like_remote(new_upstream, argv[0]);\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \t\t}\n \ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex e7829c2c4b..e2682a83a0 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1022,6 +1022,44 @@ test_expect_success '--set-upstream-to fails on a missing dst branch' '\n \ttest_cmp expect err\n '\n \n+test_expect_success '--set-upstream-to suggests <remote>/<branch> on slip' '\n+\ttest_when_finished \"git remote remove slip-remote\" &&\n+\tgit remote add slip-remote . &&\n+\tgit update-ref refs/remotes/slip-remote/slip-feature HEAD &&\n+\ttest_must_fail git branch --set-upstream-to slip-remote slip-feature 2>err &&\n+\ttest_grep \"takes a single <remote>/<branch> argument\" err &&\n+\ttest_grep \"hint: Did you mean to use: git branch --set-upstream-to=slip-remote/slip-feature?\" err &&\n+\ttest_must_fail git -c advice.setUpstreamFailure=false \\\n+\t\tbranch --set-upstream-to slip-remote slip-feature 2>err &&\n+\ttest_grep ! \"Did you mean\" err\n+'\n+\n+test_expect_success '--set-upstream-to does not suggest when no matching remote ref' '\n+\ttest_when_finished \"git remote remove slip-remote\" &&\n+\tgit remote add slip-remote . &&\n+\ttest_must_fail git branch --set-upstream-to slip-remote no-such-branch 2>err &&\n+\ttest_grep \"branch ${SQ}no-such-branch${SQ} does not exist\" err &&\n+\ttest_grep ! \"Did you mean\" err\n+'\n+\n+test_expect_success '--set-upstream-to to a local branch is not mistaken for a slip' '\n+\tgit branch slip-local-upstream &&\n+\tgit branch slip-local-target &&\n+\tgit branch --set-upstream-to=slip-local-upstream slip-local-target 2>err &&\n+\ttest_grep ! \"Did you mean\" err &&\n+\techo refs/heads/slip-local-upstream >expect &&\n+\tgit config branch.slip-local-target.merge >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--set-upstream-to slip suggestion keeps a slashed branch name' '\n+\ttest_when_finished \"git remote remove slip-remote\" &&\n+\tgit remote add slip-remote . &&\n+\tgit update-ref refs/remotes/slip-remote/slip/feature HEAD &&\n+\ttest_must_fail git branch --set-upstream-to slip-remote slip/feature 2>err &&\n+\ttest_grep \"hint: Did you mean to use: git branch --set-upstream-to=slip-remote/slip/feature?\" err\n+'\n+\n test_expect_success '--set-upstream-to fails on a missing src branch' '\n \ttest_must_fail git branch --set-upstream-to does-not-exist main 2>err &&\n \ttest_grep \"the requested upstream branch '\"'\"'does-not-exist'\"'\"' does not exist\" err\n-- \ngitgitgadget\n\n"},{"id":"546363","messageId":"49de5a925de506ed9a141eb72927b2548b73af22.1782338114.git.gitgitgadget@gmail.com","threadId":"65796","inReplyTo":"pull.2331.v2.git.git.1782338114.gitgitgadget@gmail.com","subject":"[PATCH v2 2/2] push: suggest <remote> <branch> for a slash slip","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-06-24T21:55:14Z","receivedAt":"2026-06-24T21:55:20Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen pushing the 'main' branch to the remote 'origin', i.e.,\n\n    $ git push origin main\n\nit is easy to mistakenly write\n\n    $ git push origin/main\n\nThat is parsed as the repository to push to, and since 'origin/main'\nis neither a configured remote nor a path it dies with:\n\n    fatal: 'origin/main' does not appear to be a git repository\n\nOften 'origin/main' does not exist as a repository, so the command\nfails without doing any harm, but it gives no hint that a space was\nmeant instead of a slash and can leave the user puzzled.\n\nWhen the argument is not an existing path or configured remote but\nits part before the first slash names one, suggest the intended\n'<remote> <branch>' form:\n\n    $ git push origin main\n\nThe suggestion is shown as advice so it can be silenced with\nadvice.pushRepoLooksLikeRef.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/config/advice.adoc |  5 +++++\n advice.c                         |  1 +\n advice.h                         |  1 +\n builtin/push.c                   | 31 ++++++++++++++++++++++++++++++-\n t/t5529-push-errors.sh           | 31 +++++++++++++++++++++++++++++++\n 5 files changed, 68 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc\nindex 257db58918..fa77a5110e 100644\n--- a/Documentation/config/advice.adoc\n+++ b/Documentation/config/advice.adoc\n@@ -90,6 +90,11 @@ all advice messages.\n \t\tShown when linkgit:git-push[1] rejects a forced update of\n \t\ta branch when its remote-tracking ref has updates that we\n \t\tdo not have locally.\n+\tpushRepoLooksLikeRef::\n+\t\tShown when the repository given to linkgit:git-push[1] is not\n+\t\ta configured remote but looks like a `<remote>/<branch>` ref,\n+\t\tsuggesting that the remote and branch be given as separate\n+\t\targuments.\n \tpushUnqualifiedRefname::\n \t\tShown when linkgit:git-push[1] gives up trying to\n \t\tguess based on the source and destination refs what\ndiff --git a/advice.c b/advice.c\nindex 0018501b7b..63bf8b0c5f 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -69,6 +69,7 @@ static struct {\n \t[ADVICE_PUSH_NON_FF_CURRENT]\t\t\t= { \"pushNonFFCurrent\" },\n \t[ADVICE_PUSH_NON_FF_MATCHING]\t\t\t= { \"pushNonFFMatching\" },\n \t[ADVICE_PUSH_REF_NEEDS_UPDATE]\t\t\t= { \"pushRefNeedsUpdate\" },\n+\t[ADVICE_PUSH_REPO_LOOKS_LIKE_REF]\t\t= { \"pushRepoLooksLikeRef\" },\n \t[ADVICE_PUSH_UNQUALIFIED_REF_NAME]\t\t= { \"pushUnqualifiedRefName\" },\n \t[ADVICE_PUSH_UPDATE_REJECTED]\t\t\t= { \"pushUpdateRejected\" },\n \t[ADVICE_PUSH_UPDATE_REJECTED_ALIAS]\t\t= { \"pushNonFastForward\" }, /* backwards compatibility */\ndiff --git a/advice.h b/advice.h\nindex 8def280688..66f6cd6a77 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -36,6 +36,7 @@ enum advice_type {\n \tADVICE_PUSH_NON_FF_CURRENT,\n \tADVICE_PUSH_NON_FF_MATCHING,\n \tADVICE_PUSH_REF_NEEDS_UPDATE,\n+\tADVICE_PUSH_REPO_LOOKS_LIKE_REF,\n \tADVICE_PUSH_UNQUALIFIED_REF_NAME,\n \tADVICE_PUSH_UPDATE_REJECTED,\n \tADVICE_PUSH_UPDATE_REJECTED_ALIAS,\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 6021b71d66..255556b44d 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -8,6 +8,7 @@\n #include \"advice.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"dir.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n@@ -662,6 +663,29 @@ static int push_multiple(struct string_list *list,\n \treturn result;\n }\n \n+static void die_if_repo_looks_like_ref(const char *repo)\n+{\n+\tconst char *slash = strchr(repo, '/');\n+\tstruct strbuf name = STRBUF_INIT;\n+\tint code;\n+\n+\tif (!slash || !slash[1] || file_exists(repo))\n+\t\treturn;\n+\n+\tstrbuf_add(&name, repo, slash - repo);\n+\tif (!remote_is_configured(remote_get(name.buf), 0)) {\n+\t\tstrbuf_release(&name);\n+\t\treturn;\n+\t}\n+\n+\tcode = die_message(_(\"'%s' is not a valid push target\"), repo);\n+\tadvise_if_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF,\n+\t\t\t  _(\"Did you mean to use: git push %s %s?\"),\n+\t\t\t  name.buf, slash + 1);\n+\tstrbuf_release(&name);\n+\texit(code);\n+}\n+\n int cmd_push(int argc,\n \t     const char **argv,\n \t     const char *prefix,\n@@ -744,6 +768,11 @@ int cmd_push(int argc,\n \n \tif (repo) {\n \t\tif (!add_remote_or_group(repo, &remote_group)) {\n+\t\t\tstruct remote *r;\n+\n+\t\t\tif (advice_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF))\n+\t\t\t\tdie_if_repo_looks_like_ref(repo);\n+\n \t\t\t/*\n \t\t\t * Not a configured remote name or group name.\n \t\t\t * Try treating it as a direct URL or path, e.g.\n@@ -753,7 +782,7 @@ int cmd_push(int argc,\n \t\t\t * from the URL so the loop below can handle it\n \t\t\t * identically to a named remote.\n \t\t\t */\n-\t\t\tstruct remote *r = pushremote_get(repo);\n+\t\t\tr = pushremote_get(repo);\n \t\t\tif (!r)\n \t\t\t\tdie(_(\"bad repository '%s'\"), repo);\n \t\t\tstring_list_append(&remote_group, r->name);\ndiff --git a/t/t5529-push-errors.sh b/t/t5529-push-errors.sh\nindex 80b06a0cd2..cfb294305d 100755\n--- a/t/t5529-push-errors.sh\n+++ b/t/t5529-push-errors.sh\n@@ -54,6 +54,37 @@ test_expect_success 'detect empty remote with targeted refspec' '\n \tgrep \"fatal: bad repository ${SQ}${SQ}\" stderr\n '\n \n+test_expect_success 'suggest <remote> <branch> for a <remote>/<branch> slip' '\n+\ttest_must_fail git push origin/main 2>stderr &&\n+\tgrep \"${SQ}origin/main${SQ} is not a valid push target\" stderr &&\n+\tgrep \"hint: Did you mean to use: git push origin main?\" stderr &&\n+\ttest_must_fail git -c advice.pushRepoLooksLikeRef=false push origin/main 2>stderr &&\n+\t! grep \"Did you mean\" stderr\n+'\n+\n+test_expect_success 'suggest <remote> <branch> when the branch has slashes' '\n+\ttest_must_fail git push origin/feature/x 2>stderr &&\n+\tgrep \"hint: Did you mean to use: git push origin feature/x?\" stderr\n+'\n+\n+test_expect_success 'no suggestion when prefix is not a configured remote' '\n+\ttest_must_fail git push not-a-remote/main 2>stderr &&\n+\t! grep \"Did you mean\" stderr\n+'\n+\n+test_expect_success 'no suggestion for a trailing slash with no branch' '\n+\ttest_must_fail git push origin/ 2>stderr &&\n+\t! grep \"Did you mean\" stderr\n+'\n+\n+test_expect_success 'no suggestion when the argument is an existing path' '\n+\ttest_when_finished \"rm -rf origin\" &&\n+\tgit init --bare origin/main &&\n+\tgit push origin/main HEAD:refs/heads/pushed 2>stderr &&\n+\t! grep \"Did you mean\" stderr &&\n+\tgit -C origin/main rev-parse --verify refs/heads/pushed\n+'\n+\n test_expect_success 'detect ambiguous refs early' '\n \tgit branch foo &&\n \tgit tag foo &&\n-- \ngitgitgadget\n"},{"id":"546368","messageId":"xmqqechvh8m8.fsf@gitster.g","threadId":"65796","inReplyTo":"11bcecebf43797a889f08e79401370f43b2917a8.1782338114.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-24T22:33:03Z","receivedAt":"2026-06-24T22:33:05Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> When setting the upstream of the current branch to the 'main' branch\n> of the remote 'origin', i.e.,\n>\n>     $ git branch --set-upstream-to origin/main\n>\n> it is easy to mistakenly write\n>\n>     $ git branch --set-upstream-to origin main\n>\n> That is parsed as a request to set the upstream of the local branch\n> 'main' to 'origin'. When 'main' does not exist, the command dies\n> with:\n>\n>     fatal: branch 'main' does not exist\n>\n> pointing at a branch the user never meant to name.\n\nIt is more complete to add the other case here, something along the\nlines of ...\n\n    And then when 'main' does exist, the command would die with\n\n        fatal: the requested upstream branch 'origin' does not exist\n\n    leaving the user equally confused.\n\n... no?  In any case, this is much more nicely described than the\nprevious round.  I see no room for confusion.\n\n> When the operated-on branch is missing and '<remote>/<branch>' names\n> a real remote-tracking ref, suggest the intended form:\n>\n>     $ git branch --set-upstream-to=origin/main\n>\n> The suggestion is gated on '<remote>/<branch>' existing so it only\n> appears when a slipped slash is the likely explanation.\n\nMakes sense.\n\nDo we want to do anything on a case where the operated-on branch\ndoes exist but '<remote>' is not a name suitable for an upstream,\nbut '<remote>/<branch>' is?\n\n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index 1572a4f9ef..cefc4519a7 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -706,6 +706,29 @@ static int edit_branch_description(const char *branch_name)\n>  \treturn 0;\n>  }\n>  \n> +static void die_if_upstream_looks_like_remote(const char *new_upstream, const char *branch_name)\n> +{\n> +\tstruct strbuf remote_ref = STRBUF_INIT;\n> +\tint code;\n> +\n> +\tif (strchr(new_upstream, '/') ||\n> +\t    !remote_is_configured(remote_get(new_upstream), 0))\n> +\t\treturn;\n> +\n> +\tstrbuf_addf(&remote_ref, \"refs/remotes/%s/%s\", new_upstream, branch_name);\n> +\tif (!refs_ref_exists(get_main_ref_store(the_repository), remote_ref.buf)) {\n> +\t\tstrbuf_release(&remote_ref);\n> +\t\treturn;\n> +\t}\n> +\n> +\tcode = die_message(_(\"--set-upstream-to takes a single <remote>/<branch> argument\"));\n> +\tadvise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE,\n> +\t\t\t  _(\"Did you mean to use: git branch --set-upstream-to=%s/%s?\"),\n> +\t\t\t  new_upstream, branch_name);\n\nDo we still need the _if_enabled() thing here?  Isn't the caller\ngated with the same condition in this version?\n\n> +\tstrbuf_release(&remote_ref);\n> +\texit(code);\n> +}\n> +\n>  int cmd_branch(int argc,\n>  \t       const char **argv,\n>  \t       const char *prefix,\n> @@ -957,6 +980,9 @@ int cmd_branch(int argc,\n>  \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {\n>  \t\t\tif (!argc || branch_checked_out(branch->refname))\n>  \t\t\t\tdie(_(\"no commit on branch '%s' yet\"), branch->name);\n> +\t\t\tif (argc == 1 &&\n> +\t\t\t    advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n> +\t\t\t\tdie_if_upstream_looks_like_remote(new_upstream, argv[0]);\n>  \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n>  \t\t}\n\nThis is totally a tangent, but has anybody noticed that the web\ninterface to the lore archive seems to be constipated?  I am reading\nover nntp and subscribers are reading from their inbox, so no real\nharm done, but from time to time we get reminded how heavily our\ndevelopment process relies on the services like kernel.org and feel\ngrateful to have them.\n"},{"id":"546369","messageId":"xmqqa4sjh85o.fsf@gitster.g","threadId":"65796","inReplyTo":"49de5a925de506ed9a141eb72927b2548b73af22.1782338114.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/2] push: suggest <remote> <branch> for a slash slip","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-24T22:42:59Z","receivedAt":"2026-06-24T22:43:02Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/t/t5529-push-errors.sh b/t/t5529-push-errors.sh\n> index 80b06a0cd2..cfb294305d 100755\n> --- a/t/t5529-push-errors.sh\n> +++ b/t/t5529-push-errors.sh\n> @@ -54,6 +54,37 @@ test_expect_success 'detect empty remote with targeted refspec' '\n>  \tgrep \"fatal: bad repository ${SQ}${SQ}\" stderr\n>  '\n>  \n> +test_expect_success 'suggest <remote> <branch> for a <remote>/<branch> slip' '\n> +\ttest_must_fail git push origin/main 2>stderr &&\n> +\tgrep \"${SQ}origin/main${SQ} is not a valid push target\" stderr &&\n> +\tgrep \"hint: Did you mean to use: git push origin main?\" stderr &&\n> +\ttest_must_fail git -c advice.pushRepoLooksLikeRef=false push origin/main 2>stderr &&\n> +\t! grep \"Did you mean\" stderr\n> +'\n> +\n> +test_expect_success 'suggest <remote> <branch> when the branch has slashes' '\n> +\ttest_must_fail git push origin/feature/x 2>stderr &&\n> +\tgrep \"hint: Did you mean to use: git push origin feature/x?\" stderr\n> +'\n> +\n> +test_expect_success 'no suggestion when prefix is not a configured remote' '\n> +\ttest_must_fail git push not-a-remote/main 2>stderr &&\n> +\t! grep \"Did you mean\" stderr\n> +'\n> +\n> +test_expect_success 'no suggestion for a trailing slash with no branch' '\n> +\ttest_must_fail git push origin/ 2>stderr &&\n> +\t! grep \"Did you mean\" stderr\n> +'\n\nt5529-push-errors.sh:59: error: bare grep outside pipeline (use test_grep)\nt5529-push-errors.sh:60: error: bare grep outside pipeline (use test_grep)\nt5529-push-errors.sh:62: error: bare grep outside pipeline (use test_grep)\nt5529-push-errors.sh:67: error: bare grep outside pipeline (use test_grep)\nt5529-push-errors.sh:72: error: bare grep outside pipeline (use test_grep)\nt5529-push-errors.sh:77: error: bare grep outside pipeline (use test_grep)\nt5529-push-errors.sh:84: error: bare grep outside pipeline (use test_grep)\n\n"},{"id":"546372","messageId":"xmqq1pdvgukt.fsf@gitster.g","threadId":"65796","inReplyTo":"xmqqa4sjh85o.fsf@gitster.g","subject":"Re: [PATCH v2 2/2] push: suggest <remote> <branch> for a slash slip","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-25T03:36:18Z","receivedAt":"2026-06-25T03:36:21Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> diff --git a/t/t5529-push-errors.sh b/t/t5529-push-errors.sh\n>> index 80b06a0cd2..cfb294305d 100755\n>> --- a/t/t5529-push-errors.sh\n>> +++ b/t/t5529-push-errors.sh\n>> @@ -54,6 +54,37 @@ test_expect_success 'detect empty remote with targeted refspec' '\n>>  \tgrep \"fatal: bad repository ${SQ}${SQ}\" stderr\n>>  '\n> t5529-push-errors.sh:59: error: bare grep outside pipeline (use test_grep)\n> t5529-push-errors.sh:60: error: bare grep outside pipeline (use test_grep)\n> t5529-push-errors.sh:62: error: bare grep outside pipeline (use test_grep)\n> t5529-push-errors.sh:67: error: bare grep outside pipeline (use test_grep)\n> t5529-push-errors.sh:72: error: bare grep outside pipeline (use test_grep)\n> t5529-push-errors.sh:77: error: bare grep outside pipeline (use test_grep)\n> t5529-push-errors.sh:84: error: bare grep outside pipeline (use test_grep)\n\nI've queued this squashable? fix on top of the branch before merging\nthe result to 'seen' for tonight's push-out.\n\nThanks.\n\n--- >8 ---\nSubject: [PATCH] SQUASH??? use test_grep\n\n---\n t/t5529-push-errors.sh | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/t/t5529-push-errors.sh b/t/t5529-push-errors.sh\nindex cfb294305d..2294645902 100755\n--- a/t/t5529-push-errors.sh\n+++ b/t/t5529-push-errors.sh\n@@ -56,32 +56,32 @@ test_expect_success 'detect empty remote with targeted refspec' '\n \n test_expect_success 'suggest <remote> <branch> for a <remote>/<branch> slip' '\n \ttest_must_fail git push origin/main 2>stderr &&\n-\tgrep \"${SQ}origin/main${SQ} is not a valid push target\" stderr &&\n-\tgrep \"hint: Did you mean to use: git push origin main?\" stderr &&\n+\ttest_grep \"${SQ}origin/main${SQ} is not a valid push target\" stderr &&\n+\ttest_grep \"hint: Did you mean to use: git push origin main?\" stderr &&\n \ttest_must_fail git -c advice.pushRepoLooksLikeRef=false push origin/main 2>stderr &&\n-\t! grep \"Did you mean\" stderr\n+\ttest_grep ! \"Did you mean\" stderr\n '\n \n test_expect_success 'suggest <remote> <branch> when the branch has slashes' '\n \ttest_must_fail git push origin/feature/x 2>stderr &&\n-\tgrep \"hint: Did you mean to use: git push origin feature/x?\" stderr\n+\ttest_grep \"hint: Did you mean to use: git push origin feature/x?\" stderr\n '\n \n test_expect_success 'no suggestion when prefix is not a configured remote' '\n \ttest_must_fail git push not-a-remote/main 2>stderr &&\n-\t! grep \"Did you mean\" stderr\n+\ttest_grep ! \"Did you mean\" stderr\n '\n \n test_expect_success 'no suggestion for a trailing slash with no branch' '\n \ttest_must_fail git push origin/ 2>stderr &&\n-\t! grep \"Did you mean\" stderr\n+\ttest_grep ! \"Did you mean\" stderr\n '\n \n test_expect_success 'no suggestion when the argument is an existing path' '\n \ttest_when_finished \"rm -rf origin\" &&\n \tgit init --bare origin/main &&\n \tgit push origin/main HEAD:refs/heads/pushed 2>stderr &&\n-\t! grep \"Did you mean\" stderr &&\n+\ttest_grep ! \"Did you mean\" stderr &&\n \tgit -C origin/main rev-parse --verify refs/heads/pushed\n '\n \n-- \n2.55.0-rc2-165-g3249676ba5\n\n"},{"id":"546381","messageId":"CAHwyqnXZ_eGUPOhq1hXs==uYuYbRBWw120fXRQa=apWKekxVAQ@mail.gmail.com","threadId":"65796","inReplyTo":"xmqqechvh8m8.fsf@gitster.g","subject":"Re: [PATCH v2 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-06-25T07:44:12Z","receivedAt":"2026-06-25T07:44:51Z","isPatch":true,"body":"> Do we still need the _if_enabled() thing here?  Isn't the caller\n> gated with the same condition in this version?\n>\n> > +     strbuf_release(&remote_ref);\n> > +     exit(code);\n> > +}\n> > +\n> >  int cmd_branch(int argc,\n> >              const char **argv,\n> >              const char *prefix,\n> > @@ -957,6 +980,9 @@ int cmd_branch(int argc,\n> >               if (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {\n> >                       if (!argc || branch_checked_out(branch->refname))\n> >                               die(_(\"no commit on branch '%s' yet\"), branch->name);\n> > +                     if (argc == 1 &&\n> > +                         advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n> > +                             die_if_upstream_looks_like_remote(new_upstream, argv[0]);\n> >                       die(_(\"branch '%s' does not exist\"), branch->name);\n> >               }\n\nI think we do, so it will give the advice and tell the user that it\ncan be disabled in the standard format.\n\n\nHarald\n"},{"id":"546382","messageId":"CAHwyqnWaY9bHX1ZvuMTDMGGBG8f+_tbhcPycOYbWAcW23ZAoPg@mail.gmail.com","threadId":"65796","inReplyTo":"xmqq1pdvgukt.fsf@gitster.g","subject":"Re: [PATCH v2 2/2] push: suggest <remote> <branch> for a slash slip","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-06-25T07:53:22Z","receivedAt":"2026-06-25T07:54:01Z","isPatch":true,"body":"> Junio C Hamano <gitster@pobox.com> writes:\n>\n> > \"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >\n> >> diff --git a/t/t5529-push-errors.sh b/t/t5529-push-errors.sh\n> >> index 80b06a0cd2..cfb294305d 100755\n> >> --- a/t/t5529-push-errors.sh\n> >> +++ b/t/t5529-push-errors.sh\n> >> @@ -54,6 +54,37 @@ test_expect_success 'detect empty remote with targeted refspec' '\n> >>      grep \"fatal: bad repository ${SQ}${SQ}\" stderr\n> >>  '\n> > t5529-push-errors.sh:59: error: bare grep outside pipeline (use test_grep)\n> > t5529-push-errors.sh:60: error: bare grep outside pipeline (use test_grep)\n> > t5529-push-errors.sh:62: error: bare grep outside pipeline (use test_grep)\n> > t5529-push-errors.sh:67: error: bare grep outside pipeline (use test_grep)\n> > t5529-push-errors.sh:72: error: bare grep outside pipeline (use test_grep)\n> > t5529-push-errors.sh:77: error: bare grep outside pipeline (use test_grep)\n> > t5529-push-errors.sh:84: error: bare grep outside pipeline (use test_grep)\n>\n> I've queued this squashable? fix on top of the branch before merging\n> the result to 'seen' for tonight's push-out.\n>\n> Thanks.\n\nThank you!\n\nIs someone working on fixing the GitHub CI? I used to rely on it\nbefore, it would have caught this, and now I'm relying on \"if less\nthan five GitHub CI tests are failing (with the 4GB warning) and tests\nare passing locally, then I can submit\" which is admittedly not a good\nheuristic.\n\n\nHarald\n"},{"id":"546416","messageId":"xmqqwlvmg3vv.fsf@gitster.g","threadId":"65796","inReplyTo":"CAHwyqnWaY9bHX1ZvuMTDMGGBG8f+_tbhcPycOYbWAcW23ZAoPg@mail.gmail.com","subject":"Re: [PATCH v2 2/2] push: suggest <remote> <branch> for a slash slip","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-25T13:12:52Z","receivedAt":"2026-06-25T13:12:55Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n> than five GitHub CI tests are failing (with the 4GB warning) and tests\n> are passing locally, then I can submit\" which is admittedly not a good\n> heuristic.\n\nThis particular one you would have caught if you try-merged your\ntopic to 'seen' and ran test locally.  That is how I found it before\npushing the integration result out.\n\n\n"},{"id":"546436","messageId":"xmqqfr2ae2wp.fsf@gitster.g","threadId":"65796","inReplyTo":"CAHwyqnXZ_eGUPOhq1hXs==uYuYbRBWw120fXRQa=apWKekxVAQ@mail.gmail.com","subject":"Re: [PATCH v2 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-25T21:16:54Z","receivedAt":"2026-06-25T21:16:57Z","isPatch":true,"body":"Harald Nordgren <haraldnordgren@gmail.com> writes:\n\n>> Do we still need the _if_enabled() thing here?  Isn't the caller\n>> gated with the same condition in this version?\n>>\n>> > +     strbuf_release(&remote_ref);\n>> > +     exit(code);\n>> > +}\n>> > +\n>> >  int cmd_branch(int argc,\n>> >              const char **argv,\n>> >              const char *prefix,\n>> > @@ -957,6 +980,9 @@ int cmd_branch(int argc,\n>> >               if (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {\n>> >                       if (!argc || branch_checked_out(branch->refname))\n>> >                               die(_(\"no commit on branch '%s' yet\"), branch->name);\n>> > +                     if (argc == 1 &&\n>> > +                         advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n>> > +                             die_if_upstream_looks_like_remote(new_upstream, argv[0]);\n>> >                       die(_(\"branch '%s' does not exist\"), branch->name);\n>> >               }\n>\n> I think we do, so it will give the advice and tell the user that it\n> can be disabled in the standard format.\n\nI was hoping that unconditional advise() should be sufficient, but\nthe caller there needs to say if_enabled, even though it _knows_\nthat it is enabled, only to give the turn-off instructions.\n\nI wonder if future readers would be confused just like I was,\nwithout a comment on the callsite of _if_enabled() added by this\npatch?\n\nThanks.\n"},{"id":"546557","messageId":"pull.2331.v3.git.git.1782583345.gitgitgadget@gmail.com","threadId":"65796","inReplyTo":"pull.2331.v2.git.git.1782338114.gitgitgadget@gmail.com","subject":"[PATCH v3 0/2] branch/push: suggest intended form when remote/branch slip given","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-06-27T18:02:23Z","receivedAt":"2026-06-27T18:02:27Z","isPatch":true,"body":"When the repository or upstream argument is a slip like \"origin/main\" or\n\"origin main\", suggest the intended \"git push origin main\" or \"git branch\n--set-upstream-to=origin/main\" form instead of failing with an unrelated\nerror.\n\nChanges in v3:\n\n * Reworded the branch commit message to also show the second confusing\n   error (the requested upstream branch 'origin' does not exist) for when\n   the operated-on branch does exist.\n * Converted the new t5529 tests from bare grep/! grep to test_grep to fix\n   the test-lint failures.\n * Added a brief call-site comment explaining the up-front advice_enabled()\n   gate (skips the lookups when the hint is off, while advise_if_enabled()\n   still prints the disable-message footer).\n\nChanges in v2:\n\n * Rewrote both commit messages to lead with the intended command, the easy\n   slip, and the resulting error, instead of the terse original.\n * Gated each suggestion on advice_enabled() up front, so a user who\n   silenced the hint pays no remote/ref lookups and falls through to the\n   original error. Extracted the detection logic into helpers\n   (die_if_repo_looks_like_ref, die_if_upstream_looks_like_remote) so each\n   call site reads as a single guarded line.\n\nHarald Nordgren (2):\n  branch: suggest <remote>/<branch> on upstream slip\n  push: suggest <remote> <branch> for a slash slip\n\n Documentation/config/advice.adoc |  5 +++++\n advice.c                         |  1 +\n advice.h                         |  1 +\n builtin/branch.c                 | 32 +++++++++++++++++++++++++++\n builtin/push.c                   | 37 ++++++++++++++++++++++++++++++-\n t/t3200-branch.sh                | 38 ++++++++++++++++++++++++++++++++\n t/t5529-push-errors.sh           | 31 ++++++++++++++++++++++++++\n 7 files changed, 144 insertions(+), 1 deletion(-)\n\n\nbase-commit: ab776a62a78576513ee121424adb19597fbb7613\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2331%2FHaraldNordgren%2Fsuggest-remote-branch-slips-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2331/HaraldNordgren/suggest-remote-branch-slips-v3\nPull-Request: https://github.com/git/git/pull/2331\n\nRange-diff vs v2:\n\n 1:  11bcecebf4 ! 1:  9883c28482 branch: suggest <remote>/<branch> on upstream slip\n     @@ Commit message\n      \n              fatal: branch 'main' does not exist\n      \n     -    pointing at a branch the user never meant to name.\n     +    pointing at a branch the user never meant to name. When 'main' does\n     +    exist, it instead dies with:\n     +\n     +        fatal: the requested upstream branch 'origin' does not exist\n     +\n     +    leaving the user equally puzzled.\n      \n          When the operated-on branch is missing and '<remote>/<branch>' names\n          a real remote-tracking ref, suggest the intended form:\n     @@ builtin/branch.c: int cmd_branch(int argc,\n       \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {\n       \t\t\tif (!argc || branch_checked_out(branch->refname))\n       \t\t\t\tdie(_(\"no commit on branch '%s' yet\"), branch->name);\n     ++\t\t\t/*\n     ++\t\t\t * Check the advice up front to avoid the ref\n     ++\t\t\t * lookups when the hint is off. The helper still\n     ++\t\t\t * calls advise_if_enabled() so the hint carries the\n     ++\t\t\t * standard \"disable this message\" instructions.\n     ++\t\t\t */\n      +\t\t\tif (argc == 1 &&\n      +\t\t\t    advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n      +\t\t\t\tdie_if_upstream_looks_like_remote(new_upstream, argv[0]);\n 2:  49de5a925d ! 2:  dbe4dbc346 push: suggest <remote> <branch> for a slash slip\n     @@ builtin/push.c: int cmd_push(int argc,\n       \t\tif (!add_remote_or_group(repo, &remote_group)) {\n      +\t\t\tstruct remote *r;\n      +\n     ++\t\t\t/*\n     ++\t\t\t * Check the advice up front to avoid the remote\n     ++\t\t\t * lookup when the hint is off. The helper still\n     ++\t\t\t * calls advise_if_enabled() so the hint carries the\n     ++\t\t\t * standard \"disable this message\" instructions.\n     ++\t\t\t */\n      +\t\t\tif (advice_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF))\n      +\t\t\t\tdie_if_repo_looks_like_ref(repo);\n      +\n     @@ t/t5529-push-errors.sh: test_expect_success 'detect empty remote with targeted r\n       \n      +test_expect_success 'suggest <remote> <branch> for a <remote>/<branch> slip' '\n      +\ttest_must_fail git push origin/main 2>stderr &&\n     -+\tgrep \"${SQ}origin/main${SQ} is not a valid push target\" stderr &&\n     -+\tgrep \"hint: Did you mean to use: git push origin main?\" stderr &&\n     ++\ttest_grep \"${SQ}origin/main${SQ} is not a valid push target\" stderr &&\n     ++\ttest_grep \"hint: Did you mean to use: git push origin main?\" stderr &&\n      +\ttest_must_fail git -c advice.pushRepoLooksLikeRef=false push origin/main 2>stderr &&\n     -+\t! grep \"Did you mean\" stderr\n     ++\ttest_grep ! \"Did you mean\" stderr\n      +'\n      +\n      +test_expect_success 'suggest <remote> <branch> when the branch has slashes' '\n      +\ttest_must_fail git push origin/feature/x 2>stderr &&\n     -+\tgrep \"hint: Did you mean to use: git push origin feature/x?\" stderr\n     ++\ttest_grep \"hint: Did you mean to use: git push origin feature/x?\" stderr\n      +'\n      +\n      +test_expect_success 'no suggestion when prefix is not a configured remote' '\n      +\ttest_must_fail git push not-a-remote/main 2>stderr &&\n     -+\t! grep \"Did you mean\" stderr\n     ++\ttest_grep ! \"Did you mean\" stderr\n      +'\n      +\n      +test_expect_success 'no suggestion for a trailing slash with no branch' '\n      +\ttest_must_fail git push origin/ 2>stderr &&\n     -+\t! grep \"Did you mean\" stderr\n     ++\ttest_grep ! \"Did you mean\" stderr\n      +'\n      +\n      +test_expect_success 'no suggestion when the argument is an existing path' '\n      +\ttest_when_finished \"rm -rf origin\" &&\n      +\tgit init --bare origin/main &&\n      +\tgit push origin/main HEAD:refs/heads/pushed 2>stderr &&\n     -+\t! grep \"Did you mean\" stderr &&\n     ++\ttest_grep ! \"Did you mean\" stderr &&\n      +\tgit -C origin/main rev-parse --verify refs/heads/pushed\n      +'\n      +\n\n-- \ngitgitgadget\n"},{"id":"546558","messageId":"9883c28482be4ad43f0f999c2e6be9f9dd9fb13b.1782583345.git.gitgitgadget@gmail.com","threadId":"65796","inReplyTo":"pull.2331.v3.git.git.1782583345.gitgitgadget@gmail.com","subject":"[PATCH v3 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-06-27T18:02:24Z","receivedAt":"2026-06-27T18:02:28Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen setting the upstream of the current branch to the 'main' branch\nof the remote 'origin', i.e.,\n\n    $ git branch --set-upstream-to origin/main\n\nit is easy to mistakenly write\n\n    $ git branch --set-upstream-to origin main\n\nThat is parsed as a request to set the upstream of the local branch\n'main' to 'origin'. When 'main' does not exist, the command dies\nwith:\n\n    fatal: branch 'main' does not exist\n\npointing at a branch the user never meant to name. When 'main' does\nexist, it instead dies with:\n\n    fatal: the requested upstream branch 'origin' does not exist\n\nleaving the user equally puzzled.\n\nWhen the operated-on branch is missing and '<remote>/<branch>' names\na real remote-tracking ref, suggest the intended form:\n\n    $ git branch --set-upstream-to=origin/main\n\nThe suggestion is gated on '<remote>/<branch>' existing so it only\nappears when a slipped slash is the likely explanation.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n builtin/branch.c  | 32 ++++++++++++++++++++++++++++++++\n t/t3200-branch.sh | 38 ++++++++++++++++++++++++++++++++++++++\n 2 files changed, 70 insertions(+)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 1572a4f9ef..dede60d27b 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -706,6 +706,29 @@ static int edit_branch_description(const char *branch_name)\n \treturn 0;\n }\n \n+static void die_if_upstream_looks_like_remote(const char *new_upstream, const char *branch_name)\n+{\n+\tstruct strbuf remote_ref = STRBUF_INIT;\n+\tint code;\n+\n+\tif (strchr(new_upstream, '/') ||\n+\t    !remote_is_configured(remote_get(new_upstream), 0))\n+\t\treturn;\n+\n+\tstrbuf_addf(&remote_ref, \"refs/remotes/%s/%s\", new_upstream, branch_name);\n+\tif (!refs_ref_exists(get_main_ref_store(the_repository), remote_ref.buf)) {\n+\t\tstrbuf_release(&remote_ref);\n+\t\treturn;\n+\t}\n+\n+\tcode = die_message(_(\"--set-upstream-to takes a single <remote>/<branch> argument\"));\n+\tadvise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE,\n+\t\t\t  _(\"Did you mean to use: git branch --set-upstream-to=%s/%s?\"),\n+\t\t\t  new_upstream, branch_name);\n+\tstrbuf_release(&remote_ref);\n+\texit(code);\n+}\n+\n int cmd_branch(int argc,\n \t       const char **argv,\n \t       const char *prefix,\n@@ -957,6 +980,15 @@ int cmd_branch(int argc,\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {\n \t\t\tif (!argc || branch_checked_out(branch->refname))\n \t\t\t\tdie(_(\"no commit on branch '%s' yet\"), branch->name);\n+\t\t\t/*\n+\t\t\t * Check the advice up front to avoid the ref\n+\t\t\t * lookups when the hint is off. The helper still\n+\t\t\t * calls advise_if_enabled() so the hint carries the\n+\t\t\t * standard \"disable this message\" instructions.\n+\t\t\t */\n+\t\t\tif (argc == 1 &&\n+\t\t\t    advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n+\t\t\t\tdie_if_upstream_looks_like_remote(new_upstream, argv[0]);\n \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n \t\t}\n \ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex e7829c2c4b..e2682a83a0 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1022,6 +1022,44 @@ test_expect_success '--set-upstream-to fails on a missing dst branch' '\n \ttest_cmp expect err\n '\n \n+test_expect_success '--set-upstream-to suggests <remote>/<branch> on slip' '\n+\ttest_when_finished \"git remote remove slip-remote\" &&\n+\tgit remote add slip-remote . &&\n+\tgit update-ref refs/remotes/slip-remote/slip-feature HEAD &&\n+\ttest_must_fail git branch --set-upstream-to slip-remote slip-feature 2>err &&\n+\ttest_grep \"takes a single <remote>/<branch> argument\" err &&\n+\ttest_grep \"hint: Did you mean to use: git branch --set-upstream-to=slip-remote/slip-feature?\" err &&\n+\ttest_must_fail git -c advice.setUpstreamFailure=false \\\n+\t\tbranch --set-upstream-to slip-remote slip-feature 2>err &&\n+\ttest_grep ! \"Did you mean\" err\n+'\n+\n+test_expect_success '--set-upstream-to does not suggest when no matching remote ref' '\n+\ttest_when_finished \"git remote remove slip-remote\" &&\n+\tgit remote add slip-remote . &&\n+\ttest_must_fail git branch --set-upstream-to slip-remote no-such-branch 2>err &&\n+\ttest_grep \"branch ${SQ}no-such-branch${SQ} does not exist\" err &&\n+\ttest_grep ! \"Did you mean\" err\n+'\n+\n+test_expect_success '--set-upstream-to to a local branch is not mistaken for a slip' '\n+\tgit branch slip-local-upstream &&\n+\tgit branch slip-local-target &&\n+\tgit branch --set-upstream-to=slip-local-upstream slip-local-target 2>err &&\n+\ttest_grep ! \"Did you mean\" err &&\n+\techo refs/heads/slip-local-upstream >expect &&\n+\tgit config branch.slip-local-target.merge >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--set-upstream-to slip suggestion keeps a slashed branch name' '\n+\ttest_when_finished \"git remote remove slip-remote\" &&\n+\tgit remote add slip-remote . &&\n+\tgit update-ref refs/remotes/slip-remote/slip/feature HEAD &&\n+\ttest_must_fail git branch --set-upstream-to slip-remote slip/feature 2>err &&\n+\ttest_grep \"hint: Did you mean to use: git branch --set-upstream-to=slip-remote/slip/feature?\" err\n+'\n+\n test_expect_success '--set-upstream-to fails on a missing src branch' '\n \ttest_must_fail git branch --set-upstream-to does-not-exist main 2>err &&\n \ttest_grep \"the requested upstream branch '\"'\"'does-not-exist'\"'\"' does not exist\" err\n-- \ngitgitgadget\n\n"},{"id":"546559","messageId":"dbe4dbc346024eedaaa08472d4cf451b56ee90bf.1782583345.git.gitgitgadget@gmail.com","threadId":"65796","inReplyTo":"pull.2331.v3.git.git.1782583345.gitgitgadget@gmail.com","subject":"[PATCH v3 2/2] push: suggest <remote> <branch> for a slash slip","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-06-27T18:02:25Z","receivedAt":"2026-06-27T18:02:30Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nWhen pushing the 'main' branch to the remote 'origin', i.e.,\n\n    $ git push origin main\n\nit is easy to mistakenly write\n\n    $ git push origin/main\n\nThat is parsed as the repository to push to, and since 'origin/main'\nis neither a configured remote nor a path it dies with:\n\n    fatal: 'origin/main' does not appear to be a git repository\n\nOften 'origin/main' does not exist as a repository, so the command\nfails without doing any harm, but it gives no hint that a space was\nmeant instead of a slash and can leave the user puzzled.\n\nWhen the argument is not an existing path or configured remote but\nits part before the first slash names one, suggest the intended\n'<remote> <branch>' form:\n\n    $ git push origin main\n\nThe suggestion is shown as advice so it can be silenced with\nadvice.pushRepoLooksLikeRef.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n Documentation/config/advice.adoc |  5 +++++\n advice.c                         |  1 +\n advice.h                         |  1 +\n builtin/push.c                   | 37 +++++++++++++++++++++++++++++++-\n t/t5529-push-errors.sh           | 31 ++++++++++++++++++++++++++\n 5 files changed, 74 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc\nindex 257db58918..fa77a5110e 100644\n--- a/Documentation/config/advice.adoc\n+++ b/Documentation/config/advice.adoc\n@@ -90,6 +90,11 @@ all advice messages.\n \t\tShown when linkgit:git-push[1] rejects a forced update of\n \t\ta branch when its remote-tracking ref has updates that we\n \t\tdo not have locally.\n+\tpushRepoLooksLikeRef::\n+\t\tShown when the repository given to linkgit:git-push[1] is not\n+\t\ta configured remote but looks like a `<remote>/<branch>` ref,\n+\t\tsuggesting that the remote and branch be given as separate\n+\t\targuments.\n \tpushUnqualifiedRefname::\n \t\tShown when linkgit:git-push[1] gives up trying to\n \t\tguess based on the source and destination refs what\ndiff --git a/advice.c b/advice.c\nindex 0018501b7b..63bf8b0c5f 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -69,6 +69,7 @@ static struct {\n \t[ADVICE_PUSH_NON_FF_CURRENT]\t\t\t= { \"pushNonFFCurrent\" },\n \t[ADVICE_PUSH_NON_FF_MATCHING]\t\t\t= { \"pushNonFFMatching\" },\n \t[ADVICE_PUSH_REF_NEEDS_UPDATE]\t\t\t= { \"pushRefNeedsUpdate\" },\n+\t[ADVICE_PUSH_REPO_LOOKS_LIKE_REF]\t\t= { \"pushRepoLooksLikeRef\" },\n \t[ADVICE_PUSH_UNQUALIFIED_REF_NAME]\t\t= { \"pushUnqualifiedRefName\" },\n \t[ADVICE_PUSH_UPDATE_REJECTED]\t\t\t= { \"pushUpdateRejected\" },\n \t[ADVICE_PUSH_UPDATE_REJECTED_ALIAS]\t\t= { \"pushNonFastForward\" }, /* backwards compatibility */\ndiff --git a/advice.h b/advice.h\nindex 8def280688..66f6cd6a77 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -36,6 +36,7 @@ enum advice_type {\n \tADVICE_PUSH_NON_FF_CURRENT,\n \tADVICE_PUSH_NON_FF_MATCHING,\n \tADVICE_PUSH_REF_NEEDS_UPDATE,\n+\tADVICE_PUSH_REPO_LOOKS_LIKE_REF,\n \tADVICE_PUSH_UNQUALIFIED_REF_NAME,\n \tADVICE_PUSH_UPDATE_REJECTED,\n \tADVICE_PUSH_UPDATE_REJECTED_ALIAS,\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 6021b71d66..1b2ad3b8df 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -8,6 +8,7 @@\n #include \"advice.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"dir.h\"\n #include \"environment.h\"\n #include \"gettext.h\"\n #include \"hex.h\"\n@@ -662,6 +663,29 @@ static int push_multiple(struct string_list *list,\n \treturn result;\n }\n \n+static void die_if_repo_looks_like_ref(const char *repo)\n+{\n+\tconst char *slash = strchr(repo, '/');\n+\tstruct strbuf name = STRBUF_INIT;\n+\tint code;\n+\n+\tif (!slash || !slash[1] || file_exists(repo))\n+\t\treturn;\n+\n+\tstrbuf_add(&name, repo, slash - repo);\n+\tif (!remote_is_configured(remote_get(name.buf), 0)) {\n+\t\tstrbuf_release(&name);\n+\t\treturn;\n+\t}\n+\n+\tcode = die_message(_(\"'%s' is not a valid push target\"), repo);\n+\tadvise_if_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF,\n+\t\t\t  _(\"Did you mean to use: git push %s %s?\"),\n+\t\t\t  name.buf, slash + 1);\n+\tstrbuf_release(&name);\n+\texit(code);\n+}\n+\n int cmd_push(int argc,\n \t     const char **argv,\n \t     const char *prefix,\n@@ -744,6 +768,17 @@ int cmd_push(int argc,\n \n \tif (repo) {\n \t\tif (!add_remote_or_group(repo, &remote_group)) {\n+\t\t\tstruct remote *r;\n+\n+\t\t\t/*\n+\t\t\t * Check the advice up front to avoid the remote\n+\t\t\t * lookup when the hint is off. The helper still\n+\t\t\t * calls advise_if_enabled() so the hint carries the\n+\t\t\t * standard \"disable this message\" instructions.\n+\t\t\t */\n+\t\t\tif (advice_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF))\n+\t\t\t\tdie_if_repo_looks_like_ref(repo);\n+\n \t\t\t/*\n \t\t\t * Not a configured remote name or group name.\n \t\t\t * Try treating it as a direct URL or path, e.g.\n@@ -753,7 +788,7 @@ int cmd_push(int argc,\n \t\t\t * from the URL so the loop below can handle it\n \t\t\t * identically to a named remote.\n \t\t\t */\n-\t\t\tstruct remote *r = pushremote_get(repo);\n+\t\t\tr = pushremote_get(repo);\n \t\t\tif (!r)\n \t\t\t\tdie(_(\"bad repository '%s'\"), repo);\n \t\t\tstring_list_append(&remote_group, r->name);\ndiff --git a/t/t5529-push-errors.sh b/t/t5529-push-errors.sh\nindex 80b06a0cd2..2294645902 100755\n--- a/t/t5529-push-errors.sh\n+++ b/t/t5529-push-errors.sh\n@@ -54,6 +54,37 @@ test_expect_success 'detect empty remote with targeted refspec' '\n \tgrep \"fatal: bad repository ${SQ}${SQ}\" stderr\n '\n \n+test_expect_success 'suggest <remote> <branch> for a <remote>/<branch> slip' '\n+\ttest_must_fail git push origin/main 2>stderr &&\n+\ttest_grep \"${SQ}origin/main${SQ} is not a valid push target\" stderr &&\n+\ttest_grep \"hint: Did you mean to use: git push origin main?\" stderr &&\n+\ttest_must_fail git -c advice.pushRepoLooksLikeRef=false push origin/main 2>stderr &&\n+\ttest_grep ! \"Did you mean\" stderr\n+'\n+\n+test_expect_success 'suggest <remote> <branch> when the branch has slashes' '\n+\ttest_must_fail git push origin/feature/x 2>stderr &&\n+\ttest_grep \"hint: Did you mean to use: git push origin feature/x?\" stderr\n+'\n+\n+test_expect_success 'no suggestion when prefix is not a configured remote' '\n+\ttest_must_fail git push not-a-remote/main 2>stderr &&\n+\ttest_grep ! \"Did you mean\" stderr\n+'\n+\n+test_expect_success 'no suggestion for a trailing slash with no branch' '\n+\ttest_must_fail git push origin/ 2>stderr &&\n+\ttest_grep ! \"Did you mean\" stderr\n+'\n+\n+test_expect_success 'no suggestion when the argument is an existing path' '\n+\ttest_when_finished \"rm -rf origin\" &&\n+\tgit init --bare origin/main &&\n+\tgit push origin/main HEAD:refs/heads/pushed 2>stderr &&\n+\ttest_grep ! \"Did you mean\" stderr &&\n+\tgit -C origin/main rev-parse --verify refs/heads/pushed\n+'\n+\n test_expect_success 'detect ambiguous refs early' '\n \tgit branch foo &&\n \tgit tag foo &&\n-- \ngitgitgadget\n"},{"id":"546581","messageId":"xmqqfr272lq7.fsf@gitster.g","threadId":"65796","inReplyTo":"9883c28482be4ad43f0f999c2e6be9f9dd9fb13b.1782583345.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-28T07:00:16Z","receivedAt":"2026-06-28T07:00:19Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index 1572a4f9ef..dede60d27b 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -706,6 +706,29 @@ static int edit_branch_description(const char *branch_name)\n>  \treturn 0;\n>  }\n>  \n> +static void die_if_upstream_looks_like_remote(const char *new_upstream, const char *branch_name)\n> +{\n> +\tstruct strbuf remote_ref = STRBUF_INIT;\n> +\tint code;\n> +\n> +\tif (strchr(new_upstream, '/') ||\n> +\t    !remote_is_configured(remote_get(new_upstream), 0))\n> +\t\treturn;\n> +\n> +\tstrbuf_addf(&remote_ref, \"refs/remotes/%s/%s\", new_upstream, branch_name);\n> +\tif (!refs_ref_exists(get_main_ref_store(the_repository), remote_ref.buf)) {\n> +\t\tstrbuf_release(&remote_ref);\n> +\t\treturn;\n> +\t}\n> +\n> +\tcode = die_message(_(\"--set-upstream-to takes a single <remote>/<branch> argument\"));\n> +\tadvise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE,\n> +\t\t\t  _(\"Did you mean to use: git branch --set-upstream-to=%s/%s?\"),\n> +\t\t\t  new_upstream, branch_name);\n> +\tstrbuf_release(&remote_ref);\n> +\texit(code);\n> +}\n> +\n>  int cmd_branch(int argc,\n>  \t       const char **argv,\n>  \t       const char *prefix,\n> @@ -957,6 +980,15 @@ int cmd_branch(int argc,\n>  \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {\n>  \t\t\tif (!argc || branch_checked_out(branch->refname))\n>  \t\t\t\tdie(_(\"no commit on branch '%s' yet\"), branch->name);\n> +\t\t\t/*\n> +\t\t\t * Check the advice up front to avoid the ref\n> +\t\t\t * lookups when the hint is off. The helper still\n> +\t\t\t * calls advise_if_enabled() so the hint carries the\n> +\t\t\t * standard \"disable this message\" instructions.\n> +\t\t\t */\n> +\t\t\tif (argc == 1 &&\n> +\t\t\t    advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))\n> +\t\t\t\tdie_if_upstream_looks_like_remote(new_upstream, argv[0]);\n>  \t\t\tdie(_(\"branch '%s' does not exist\"), branch->name);\n>  \t\t}\n\nHmph, something like adding a single liner in the caller, like this. ...\n\n\tcode = die_message(_(\"--set-upstream-to takes a single <remote>/<branch> argument\"));\n+\t/* use _if_enabled here to show the hint on how to disable */\n\tadvise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE,\n\t\t\t  _(\"Did you mean to use: git branch --set-upstream-to=%s/%s?\"),\n\t\t\t  new_upstream, branch_name);\n\tstrbuf_release(&remote_ref);\n\texit(code);\n\n... was what I meant, because the most puzzling piece is that the\nfunction calls _if_enabled form there, when the caller is presumably\nalready checked _enabled() and leaves the reader wondering if there\nare other callers of this function that does not check before\ncalling it.\n\nBut this is so tiny a thing that once the code is written, it is\nprobably not worth the churn to redo it.  Let's declare victory and\nmark the topic ready for 'next'?\n"},{"id":"546582","messageId":"CAHwyqnX+YcHgpfcAZO=6=6HvuivzmrOn6yzx2Bta=kd2xSbe5A@mail.gmail.com","threadId":"65796","inReplyTo":"xmqqfr272lq7.fsf@gitster.g","subject":"Re: [PATCH v3 1/2] branch: suggest <remote>/<branch> on upstream slip","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-06-28T07:21:22Z","receivedAt":"2026-06-28T07:22:01Z","isPatch":true,"body":"Let's do it!\n\n\nHarald\n"}]}