Permalink
Browse files

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>
  • Loading branch information...
1 parent 31c6390 commit f8aae120345f511e59bb008e8de2a8f6e65cf377 @peff peff committed with gitster Apr 23, 2008
Showing with 69 additions and 3 deletions.
  1. +29 −3 remote.c
  2. +40 −0 t/t5516-fetch-push.sh
View
@@ -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;
View
@@ -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 &&

0 comments on commit f8aae12

Please sign in to comment.