From: Harald Nordgren via GitGitGadget Date: Wed, 24 Jun 2026 21:55:12 GMT Subject: [PATCH v2 0/2] branch/push: suggest intended form when remote/branch slip given Message-ID: In-Reply-To: When the repository or upstream argument is a slip like "origin/main" or "origin main", suggest the intended "git push origin main" or "git branch --set-upstream-to=origin/main" form instead of failing with an unrelated error. Changes in v2: * Rewrote both commit messages to lead with the intended command, the easy slip, and the resulting error, instead of the terse original. * Gated each suggestion on advice_enabled() up front, so a user who silenced the hint pays no remote/ref lookups and falls through to the original error. Extracted the detection logic into helpers (die_if_repo_looks_like_ref, die_if_upstream_looks_like_remote) so each call site reads as a single guarded line. Harald Nordgren (2): branch: suggest / on upstream slip push: suggest for a slash slip Documentation/config/advice.adoc | 5 +++++ advice.c | 1 + advice.h | 1 + builtin/branch.c | 26 ++++++++++++++++++++++ builtin/push.c | 31 +++++++++++++++++++++++++- t/t3200-branch.sh | 38 ++++++++++++++++++++++++++++++++ t/t5529-push-errors.sh | 31 ++++++++++++++++++++++++++ 7 files changed, 132 insertions(+), 1 deletion(-) base-commit: ab776a62a78576513ee121424adb19597fbb7613 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2331%2FHaraldNordgren%2Fsuggest-remote-branch-slips-v2 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2331/HaraldNordgren/suggest-remote-branch-slips-v2 Pull-Request: https://github.com/git/git/pull/2331 Range-diff vs v1: 1: 21684539de ! 1: 11bcecebf4 branch: suggest / on upstream slip @@ Metadata ## Commit message ## branch: suggest / on upstream slip - "git branch --set-upstream-to origin main" reads the trailing word as - the local branch to operate on and dies with "branch 'main' does not - exist", pointing at the wrong problem. + When setting the upstream of the current branch to the 'main' branch + of the remote 'origin', i.e., - When that branch is missing and "/" names a real - remote-tracking ref, suggest the intended - "git branch --set-upstream-to=/" form. + $ git branch --set-upstream-to origin/main + + it is easy to mistakenly write + + $ git branch --set-upstream-to origin main + + That is parsed as a request to set the upstream of the local branch + 'main' to 'origin'. When 'main' does not exist, the command dies + with: + + fatal: branch 'main' does not exist + + pointing at a branch the user never meant to name. + + When the operated-on branch is missing and '/' names + a real remote-tracking ref, suggest the intended form: + + $ git branch --set-upstream-to=origin/main + + The suggestion is gated on '/' existing so it only + appears when a slipped slash is the likely explanation. Signed-off-by: Harald Nordgren ## builtin/branch.c ## +@@ builtin/branch.c: static int edit_branch_description(const char *branch_name) + return 0; + } + ++static void die_if_upstream_looks_like_remote(const char *new_upstream, const char *branch_name) ++{ ++ struct strbuf remote_ref = STRBUF_INIT; ++ int code; ++ ++ if (strchr(new_upstream, '/') || ++ !remote_is_configured(remote_get(new_upstream), 0)) ++ return; ++ ++ strbuf_addf(&remote_ref, "refs/remotes/%s/%s", new_upstream, branch_name); ++ if (!refs_ref_exists(get_main_ref_store(the_repository), remote_ref.buf)) { ++ strbuf_release(&remote_ref); ++ return; ++ } ++ ++ code = die_message(_("--set-upstream-to takes a single / argument")); ++ advise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE, ++ _("Did you mean to use: git branch --set-upstream-to=%s/%s?"), ++ new_upstream, branch_name); ++ strbuf_release(&remote_ref); ++ exit(code); ++} ++ + int cmd_branch(int argc, + const char **argv, + const char *prefix, @@ builtin/branch.c: int cmd_branch(int argc, if (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) { if (!argc || branch_checked_out(branch->refname)) die(_("no commit on branch '%s' yet"), branch->name); -+ if (argc == 1 && !strchr(new_upstream, '/') && -+ remote_is_configured(remote_get(new_upstream), 0)) { -+ struct strbuf remote_ref = STRBUF_INIT; -+ -+ strbuf_addf(&remote_ref, "refs/remotes/%s/%s", -+ new_upstream, argv[0]); -+ if (refs_ref_exists(get_main_ref_store(the_repository), -+ remote_ref.buf)) { -+ int code = die_message(_("--set-upstream-to takes a single / argument")); -+ advise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE, -+ _("Did you mean to use: git branch --set-upstream-to=%s/%s?"), -+ new_upstream, argv[0]); -+ strbuf_release(&remote_ref); -+ exit(code); -+ } -+ strbuf_release(&remote_ref); -+ } ++ if (argc == 1 && ++ advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) ++ die_if_upstream_looks_like_remote(new_upstream, argv[0]); die(_("branch '%s' does not exist"), branch->name); } 2: ea1412b110 ! 2: 49de5a925d push: suggest for a slash slip @@ Metadata ## Commit message ## push: suggest for a slash slip - "git push origin/main" is treated as a repository and dies with - "'origin/main' does not appear to be a git repository", with no hint - that a space was meant instead of a slash. + When pushing the 'main' branch to the remote 'origin', i.e., - When the argument is not an existing path or configured remote but its - part before the first slash names one, suggest the intended - "git push " form. The suggestion is shown as advice so - it can be silenced with advice.pushRepoLooksLikeRef. + $ git push origin main + + it is easy to mistakenly write + + $ git push origin/main + + That is parsed as the repository to push to, and since 'origin/main' + is neither a configured remote nor a path it dies with: + + fatal: 'origin/main' does not appear to be a git repository + + Often 'origin/main' does not exist as a repository, so the command + fails without doing any harm, but it gives no hint that a space was + meant instead of a slash and can leave the user puzzled. + + When the argument is not an existing path or configured remote but + its part before the first slash names one, suggest the intended + ' ' form: + + $ git push origin main + + The suggestion is shown as advice so it can be silenced with + advice.pushRepoLooksLikeRef. Signed-off-by: Harald Nordgren @@ builtin/push.c #include "environment.h" #include "gettext.h" #include "hex.h" +@@ builtin/push.c: static int push_multiple(struct string_list *list, + return result; + } + ++static void die_if_repo_looks_like_ref(const char *repo) ++{ ++ const char *slash = strchr(repo, '/'); ++ struct strbuf name = STRBUF_INIT; ++ int code; ++ ++ if (!slash || !slash[1] || file_exists(repo)) ++ return; ++ ++ strbuf_add(&name, repo, slash - repo); ++ if (!remote_is_configured(remote_get(name.buf), 0)) { ++ strbuf_release(&name); ++ return; ++ } ++ ++ code = die_message(_("'%s' is not a valid push target"), repo); ++ advise_if_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF, ++ _("Did you mean to use: git push %s %s?"), ++ name.buf, slash + 1); ++ strbuf_release(&name); ++ exit(code); ++} ++ + int cmd_push(int argc, + const char **argv, + const char *prefix, @@ builtin/push.c: int cmd_push(int argc, if (repo) { if (!add_remote_or_group(repo, &remote_group)) { -+ const char *slash = strchr(repo, '/'); + struct remote *r; + -+ /* -+ * A "/" argument that does not name -+ * a path is likely a slip for the separate -+ * " " form, so suggest that instead. -+ */ -+ if (slash && slash[1] && !file_exists(repo)) { -+ struct strbuf name = STRBUF_INIT; -+ -+ strbuf_add(&name, repo, slash - repo); -+ if (remote_is_configured(remote_get(name.buf), 0)) { -+ int code = die_message(_("'%s' is not a valid push target"), repo); -+ advise_if_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF, -+ _("Did you mean to use: git push %s %s?"), -+ name.buf, slash + 1); -+ strbuf_release(&name); -+ exit(code); -+ } -+ strbuf_release(&name); -+ } ++ if (advice_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF)) ++ die_if_repo_looks_like_ref(repo); + /* * Not a configured remote name or group name. -- gitgitgadget