branch: suggest <remote>/<branch> on upstream slip

When setting the upstream of the current branch to the 'main' branch of the remote 'origin', i.e., $ 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 'main' does exist, it instead dies with: fatal: the requested upstream branch 'origin' does not exist leaving the user equally puzzled. When the operated-on branch is missing and '<remote>/<branch>' names a real remote-tracking ref, suggest the intended form: $ git branch --set-upstream-to=origin/main The suggestion is gated on '<remote>/<branch>' existing so it only appears when a slipped slash is the likely explanation. Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Harald Nordgren committed Jun 27, 2026 at 18:02 UTC 2faf0f0256cbf4cef447202276ca57d36e1d9380
2 files changed +70
builtin/branch.c
+32
@@ -706,6 +706,29 @@ static int edit_branch_description(const char *branch_name)
706 return 0;
707 }
708
709 +static void die_if_upstream_looks_like_remote(const char *new_upstream, const char *branch_name)
710 +{
711 + struct strbuf remote_ref = STRBUF_INIT;
712 + int code;
713 +
714 + if (strchr(new_upstream, '/') ||
715 + !remote_is_configured(remote_get(new_upstream), 0))
716 + return;
717 +
718 + strbuf_addf(&remote_ref, "refs/remotes/%s/%s", new_upstream, branch_name);
719 + if (!refs_ref_exists(get_main_ref_store(the_repository), remote_ref.buf)) {
720 + strbuf_release(&remote_ref);
721 + return;
722 + }
723 +
724 + code = die_message(_("--set-upstream-to takes a single <remote>/<branch> argument"));
725 + advise_if_enabled(ADVICE_SET_UPSTREAM_FAILURE,
726 + _("Did you mean to use: git branch --set-upstream-to=%s/%s?"),
727 + new_upstream, branch_name);
728 + strbuf_release(&remote_ref);
729 + exit(code);
730 +}
731 +
732 int cmd_branch(int argc,
733 const char **argv,
734 const char *prefix,
@@ -957,6 +980,15 @@ int cmd_branch(int argc,
980 if (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {
981 if (!argc || branch_checked_out(branch->refname))
982 die(_("no commit on branch '%s' yet"), branch->name);
983 + /*
984 + * Check the advice up front to avoid the ref
985 + * lookups when the hint is off. The helper still
986 + * calls advise_if_enabled() so the hint carries the
987 + * standard "disable this message" instructions.
988 + */
989 + if (argc == 1 &&
990 + advice_enabled(ADVICE_SET_UPSTREAM_FAILURE))
991 + die_if_upstream_looks_like_remote(new_upstream, argv[0]);
992 die(_("branch '%s' does not exist"), branch->name);
993 }
994
t/t3200-branch.sh
+38
@@ -1022,6 +1022,44 @@ test_expect_success '--set-upstream-to fails on a missing dst branch' '
1022 test_cmp expect err
1023 '
1024
1025 +test_expect_success '--set-upstream-to suggests <remote>/<branch> on slip' '
1026 + test_when_finished "git remote remove slip-remote" &&
1027 + git remote add slip-remote . &&
1028 + git update-ref refs/remotes/slip-remote/slip-feature HEAD &&
1029 + test_must_fail git branch --set-upstream-to slip-remote slip-feature 2>err &&
1030 + test_grep "takes a single <remote>/<branch> argument" err &&
1031 + test_grep "hint: Did you mean to use: git branch --set-upstream-to=slip-remote/slip-feature?" err &&
1032 + test_must_fail git -c advice.setUpstreamFailure=false \
1033 + branch --set-upstream-to slip-remote slip-feature 2>err &&
1034 + test_grep ! "Did you mean" err
1035 +'
1036 +
1037 +test_expect_success '--set-upstream-to does not suggest when no matching remote ref' '
1038 + test_when_finished "git remote remove slip-remote" &&
1039 + git remote add slip-remote . &&
1040 + test_must_fail git branch --set-upstream-to slip-remote no-such-branch 2>err &&
1041 + test_grep "branch ${SQ}no-such-branch${SQ} does not exist" err &&
1042 + test_grep ! "Did you mean" err
1043 +'
1044 +
1045 +test_expect_success '--set-upstream-to to a local branch is not mistaken for a slip' '
1046 + git branch slip-local-upstream &&
1047 + git branch slip-local-target &&
1048 + git branch --set-upstream-to=slip-local-upstream slip-local-target 2>err &&
1049 + test_grep ! "Did you mean" err &&
1050 + echo refs/heads/slip-local-upstream >expect &&
1051 + git config branch.slip-local-target.merge >actual &&
1052 + test_cmp expect actual
1053 +'
1054 +
1055 +test_expect_success '--set-upstream-to slip suggestion keeps a slashed branch name' '
1056 + test_when_finished "git remote remove slip-remote" &&
1057 + git remote add slip-remote . &&
1058 + git update-ref refs/remotes/slip-remote/slip/feature HEAD &&
1059 + test_must_fail git branch --set-upstream-to slip-remote slip/feature 2>err &&
1060 + test_grep "hint: Did you mean to use: git branch --set-upstream-to=slip-remote/slip/feature?" err
1061 +'
1062 +
1063 test_expect_success '--set-upstream-to fails on a missing src branch' '
1064 test_must_fail git branch --set-upstream-to does-not-exist main 2>err &&
1065 test_grep "the requested upstream branch '"'"'does-not-exist'"'"' does not exist" err