When pushing the 'main' branch to the remote 'origin', i.e.,
$ 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
'<remote> <branch>' form:
$ git push origin main
The suggestion is shown as advice so it can be silenced with
advice.pushRepoLooksLikeRef.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Harald Nordgren committedJun 27, 2026 at 18:02 UTCa85a43c4802c124a504c42c63129a97d7f8346c1
5 files changed+74-1
Documentation/config/advice.adoc
+5
index 257db58918..fa77a5110e 100644--- a/Documentation/config/advice.adoc+++ b/Documentation/config/advice.adoc@@ -90,6 +90,11 @@ all advice messages. Shown when linkgit:git-push[1] rejects a forced update of a branch when its remote-tracking ref has updates that we do not have locally.+ pushRepoLooksLikeRef::+ Shown when the repository given to linkgit:git-push[1] is not+ a configured remote but looks like a `<remote>/<branch>` ref,+ suggesting that the remote and branch be given as separate+ arguments. pushUnqualifiedRefname:: Shown when linkgit:git-push[1] gives up trying to guess based on the source and destination refs what
index 6021b71d66..1b2ad3b8df 100644--- a/builtin/push.c+++ b/builtin/push.c@@ -8,6 +8,7 @@ #include "advice.h" #include "branch.h" #include "config.h"+#include "dir.h" #include "environment.h" #include "gettext.h" #include "hex.h"@@ -662,6 +663,29 @@ 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,@@ -744,6 +768,17 @@ int cmd_push(int argc, if (repo) { if (!add_remote_or_group(repo, &remote_group)) {+ struct remote *r;++ /*+ * Check the advice up front to avoid the remote+ * lookup when the hint is off. The helper still+ * calls advise_if_enabled() so the hint carries the+ * standard "disable this message" instructions.+ */+ if (advice_enabled(ADVICE_PUSH_REPO_LOOKS_LIKE_REF))+ die_if_repo_looks_like_ref(repo);+ /* * Not a configured remote name or group name. * Try treating it as a direct URL or path, e.g.@@ -753,7 +788,7 @@ int cmd_push(int argc, * from the URL so the loop below can handle it * identically to a named remote. */- struct remote *r = pushremote_get(repo);+ r = pushremote_get(repo); if (!r) die(_("bad repository '%s'"), repo); string_list_append(&remote_group, r->name);
t/t5529-push-errors.sh
+31
index 80b06a0cd2..2294645902 100755--- a/t/t5529-push-errors.sh+++ b/t/t5529-push-errors.sh@@ -54,6 +54,37 @@ test_expect_success 'detect empty remote with targeted refspec' ' grep "fatal: bad repository ${SQ}${SQ}" stderr '+test_expect_success 'suggest <remote> <branch> for a <remote>/<branch> slip' '+ test_must_fail git push origin/main 2>stderr &&+ test_grep "${SQ}origin/main${SQ} is not a valid push target" stderr &&+ test_grep "hint: Did you mean to use: git push origin main?" stderr &&+ test_must_fail git -c advice.pushRepoLooksLikeRef=false push origin/main 2>stderr &&+ test_grep ! "Did you mean" stderr+'++test_expect_success 'suggest <remote> <branch> when the branch has slashes' '+ test_must_fail git push origin/feature/x 2>stderr &&+ test_grep "hint: Did you mean to use: git push origin feature/x?" stderr+'++test_expect_success 'no suggestion when prefix is not a configured remote' '+ test_must_fail git push not-a-remote/main 2>stderr &&+ test_grep ! "Did you mean" stderr+'++test_expect_success 'no suggestion for a trailing slash with no branch' '+ test_must_fail git push origin/ 2>stderr &&+ test_grep ! "Did you mean" stderr+'++test_expect_success 'no suggestion when the argument is an existing path' '+ test_when_finished "rm -rf origin" &&+ git init --bare origin/main &&+ git push origin/main HEAD:refs/heads/pushed 2>stderr &&+ test_grep ! "Did you mean" stderr &&+ git -C origin/main rev-parse --verify refs/heads/pushed+'+ test_expect_success 'detect ambiguous refs early' ' git branch foo && git tag foo &&