Skip to content

Commit f8aae12

Browse files
peffgitster
authored andcommitted
push: allow unqualified dest refspecs to DWIM
Previously, a push like: git push remote src:dst would go through the following steps: 1. check for an unambiguous 'dst' on the remote; if it exists, then push to that ref 2. otherwise, check if 'dst' begins with 'refs/'; if it does, create a new ref 3. otherwise, complain because we don't know where in the refs hierarchy to put 'dst' However, in some cases, we can guess about the ref type of 'dst' based on the ref type of 'src'. Specifically, before complaining we now check: 2.5. if 'src' resolves to a ref starting with refs/heads or refs/tags, then prepend that to 'dst' So now this creates a new branch on the remote, whereas it previously failed with an error message: git push master:newbranch Note that, by design, we limit this DWIM behavior only to source refs which resolve exactly (including symrefs which resolve to existing refs). We still complain on a partial destination refspec if the source is a raw sha1, or a ref expression such as 'master~10'. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 31c6390 commit f8aae12

File tree

2 files changed

+69
-3
lines changed

2 files changed

+69
-3
lines changed

remote.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,26 @@ static struct ref *make_linked_ref(const char *name, struct ref ***tail)
812812
return ret;
813813
}
814814

815+
static char *guess_ref(const char *name, struct ref *peer)
816+
{
817+
struct strbuf buf = STRBUF_INIT;
818+
unsigned char sha1[20];
819+
820+
const char *r = resolve_ref(peer->name, sha1, 1, NULL);
821+
if (!r)
822+
return NULL;
823+
824+
if (!prefixcmp(r, "refs/heads/"))
825+
strbuf_addstr(&buf, "refs/heads/");
826+
else if (!prefixcmp(r, "refs/tags/"))
827+
strbuf_addstr(&buf, "refs/tags/");
828+
else
829+
return NULL;
830+
831+
strbuf_addstr(&buf, name);
832+
return strbuf_detach(&buf, NULL);
833+
}
834+
815835
static int match_explicit(struct ref *src, struct ref *dst,
816836
struct ref ***dst_tail,
817837
struct refspec *rs,
@@ -820,6 +840,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
820840
struct ref *matched_src, *matched_dst;
821841

822842
const char *dst_value = rs->dst;
843+
char *dst_guess;
823844

824845
if (rs->pattern)
825846
return errs;
@@ -866,10 +887,15 @@ static int match_explicit(struct ref *src, struct ref *dst,
866887
case 0:
867888
if (!memcmp(dst_value, "refs/", 5))
868889
matched_dst = make_linked_ref(dst_value, dst_tail);
890+
else if((dst_guess = guess_ref(dst_value, matched_src)))
891+
matched_dst = make_linked_ref(dst_guess, dst_tail);
869892
else
870-
error("dst refspec %s does not match any "
871-
"existing ref on the remote and does "
872-
"not start with refs/.", dst_value);
893+
error("unable to push to unqualified destination: %s\n"
894+
"The destination refspec neither matches an "
895+
"existing ref on the remote nor\n"
896+
"begins with refs/, and we are unable to "
897+
"guess a prefix based on the source ref.",
898+
dst_value);
873899
break;
874900
default:
875901
matched_dst = NULL;

t/t5516-fetch-push.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,37 @@ test_expect_success 'push with colon-less refspec (4)' '
273273
274274
'
275275

276+
test_expect_success 'push head with non-existant, incomplete dest' '
277+
278+
mk_test &&
279+
git push testrepo master:branch &&
280+
check_push_result $the_commit heads/branch
281+
282+
'
283+
284+
test_expect_success 'push tag with non-existant, incomplete dest' '
285+
286+
mk_test &&
287+
git tag -f v1.0 &&
288+
git push testrepo v1.0:tag &&
289+
check_push_result $the_commit tags/tag
290+
291+
'
292+
293+
test_expect_success 'push sha1 with non-existant, incomplete dest' '
294+
295+
mk_test &&
296+
test_must_fail git push testrepo `git rev-parse master`:foo
297+
298+
'
299+
300+
test_expect_success 'push ref expression with non-existant, incomplete dest' '
301+
302+
mk_test &&
303+
test_must_fail git push testrepo master^:branch
304+
305+
'
306+
276307
test_expect_success 'push with HEAD' '
277308
278309
mk_test heads/master &&
@@ -311,6 +342,15 @@ test_expect_success 'push with +HEAD' '
311342
312343
'
313344

345+
test_expect_success 'push HEAD with non-existant, incomplete dest' '
346+
347+
mk_test &&
348+
git checkout master &&
349+
git push testrepo HEAD:branch &&
350+
check_push_result $the_commit heads/branch
351+
352+
'
353+
314354
test_expect_success 'push with config remote.*.push = HEAD' '
315355
316356
mk_test heads/local &&

0 commit comments

Comments
 (0)