diff --git a/remote.c b/remote.c index 06ad15627a6488f3a5e6c699828f6f721d8647ba..2d9af4023eba6f8b2fe528ccbf03569fcaa265ee 100644 --- a/remote.c +++ b/remote.c @@ -812,6 +812,26 @@ static struct ref *make_linked_ref(const char *name, struct ref ***tail) return ret; } +static char *guess_ref(const char *name, struct ref *peer) +{ + struct strbuf buf = STRBUF_INIT; + unsigned char sha1[20]; + + const char *r = resolve_ref(peer->name, sha1, 1, NULL); + if (!r) + return NULL; + + if (!prefixcmp(r, "refs/heads/")) + strbuf_addstr(&buf, "refs/heads/"); + else if (!prefixcmp(r, "refs/tags/")) + strbuf_addstr(&buf, "refs/tags/"); + else + return NULL; + + strbuf_addstr(&buf, name); + return strbuf_detach(&buf, NULL); +} + static int match_explicit(struct ref *src, struct ref *dst, struct ref ***dst_tail, struct refspec *rs, @@ -820,6 +840,7 @@ static int match_explicit(struct ref *src, struct ref *dst, struct ref *matched_src, *matched_dst; const char *dst_value = rs->dst; + char *dst_guess; if (rs->pattern) return errs; @@ -866,10 +887,15 @@ static int match_explicit(struct ref *src, struct ref *dst, case 0: if (!memcmp(dst_value, "refs/", 5)) matched_dst = make_linked_ref(dst_value, dst_tail); + else if((dst_guess = guess_ref(dst_value, matched_src))) + matched_dst = make_linked_ref(dst_guess, dst_tail); else - error("dst refspec %s does not match any " - "existing ref on the remote and does " - "not start with refs/.", dst_value); + error("unable to push to unqualified destination: %s\n" + "The destination refspec neither matches an " + "existing ref on the remote nor\n" + "begins with refs/, and we are unable to " + "guess a prefix based on the source ref.", + dst_value); break; default: matched_dst = NULL; diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index f93a100f8732dcde2a8e52fc24ab5d54d4890b23..0a757d5b9fe660245ced7749e445eabd0369bcf0 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -273,6 +273,37 @@ test_expect_success 'push with colon-less refspec (4)' ' ' +test_expect_success 'push head with non-existant, incomplete dest' ' + + mk_test && + git push testrepo master:branch && + check_push_result $the_commit heads/branch + +' + +test_expect_success 'push tag with non-existant, incomplete dest' ' + + mk_test && + git tag -f v1.0 && + git push testrepo v1.0:tag && + check_push_result $the_commit tags/tag + +' + +test_expect_success 'push sha1 with non-existant, incomplete dest' ' + + mk_test && + test_must_fail git push testrepo `git rev-parse master`:foo + +' + +test_expect_success 'push ref expression with non-existant, incomplete dest' ' + + mk_test && + test_must_fail git push testrepo master^:branch + +' + test_expect_success 'push with HEAD' ' mk_test heads/master && @@ -311,6 +342,15 @@ test_expect_success 'push with +HEAD' ' ' +test_expect_success 'push HEAD with non-existant, incomplete dest' ' + + mk_test && + git checkout master && + git push testrepo HEAD:branch && + check_push_result $the_commit heads/branch + +' + test_expect_success 'push with config remote.*.push = HEAD' ' mk_test heads/local &&