Merge branch 'jk/detect-push-typo-early'
Catch "git push $there no-such-branch" early. * jk/detect-push-typo-early: push: detect local refspec errors early match_explicit_lhs: allow a "verify only" mode match_explicit: hoist refspec lhs checks into their own function
This commit is contained in:
commit
9befb340dd
104
remote.c
104
remote.c
@ -1036,11 +1036,13 @@ int count_refspec_match(const char *pattern,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!matched) {
|
if (!matched) {
|
||||||
*matched_ref = matched_weak;
|
if (matched_ref)
|
||||||
|
*matched_ref = matched_weak;
|
||||||
return weak_match;
|
return weak_match;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*matched_ref = matched;
|
if (matched_ref)
|
||||||
|
*matched_ref = matched;
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1060,18 +1062,25 @@ static struct ref *alloc_delete_ref(void)
|
|||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct ref *try_explicit_object_name(const char *name)
|
static int try_explicit_object_name(const char *name,
|
||||||
|
struct ref **match)
|
||||||
{
|
{
|
||||||
unsigned char sha1[20];
|
unsigned char sha1[20];
|
||||||
struct ref *ref;
|
|
||||||
|
|
||||||
if (!*name)
|
if (!*name) {
|
||||||
return alloc_delete_ref();
|
if (match)
|
||||||
|
*match = alloc_delete_ref();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (get_sha1(name, sha1))
|
if (get_sha1(name, sha1))
|
||||||
return NULL;
|
return -1;
|
||||||
ref = alloc_ref(name);
|
|
||||||
hashcpy(ref->new_sha1, sha1);
|
if (match) {
|
||||||
return ref;
|
*match = alloc_ref(name);
|
||||||
|
hashcpy((*match)->new_sha1, sha1);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct ref *make_linked_ref(const char *name, struct ref ***tail)
|
static struct ref *make_linked_ref(const char *name, struct ref ***tail)
|
||||||
@ -1101,12 +1110,37 @@ static char *guess_ref(const char *name, struct ref *peer)
|
|||||||
return strbuf_detach(&buf, NULL);
|
return strbuf_detach(&buf, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int match_explicit_lhs(struct ref *src,
|
||||||
|
struct refspec *rs,
|
||||||
|
struct ref **match,
|
||||||
|
int *allocated_match)
|
||||||
|
{
|
||||||
|
switch (count_refspec_match(rs->src, src, match)) {
|
||||||
|
case 1:
|
||||||
|
if (allocated_match)
|
||||||
|
*allocated_match = 0;
|
||||||
|
return 0;
|
||||||
|
case 0:
|
||||||
|
/* The source could be in the get_sha1() format
|
||||||
|
* not a reference name. :refs/other is a
|
||||||
|
* way to delete 'other' ref at the remote end.
|
||||||
|
*/
|
||||||
|
if (try_explicit_object_name(rs->src, match) < 0)
|
||||||
|
return error("src refspec %s does not match any.", rs->src);
|
||||||
|
if (allocated_match)
|
||||||
|
*allocated_match = 1;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return error("src refspec %s matches more than one.", rs->src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int match_explicit(struct ref *src, struct ref *dst,
|
static int match_explicit(struct ref *src, struct ref *dst,
|
||||||
struct ref ***dst_tail,
|
struct ref ***dst_tail,
|
||||||
struct refspec *rs)
|
struct refspec *rs)
|
||||||
{
|
{
|
||||||
struct ref *matched_src, *matched_dst;
|
struct ref *matched_src, *matched_dst;
|
||||||
int copy_src;
|
int allocated_src;
|
||||||
|
|
||||||
const char *dst_value = rs->dst;
|
const char *dst_value = rs->dst;
|
||||||
char *dst_guess;
|
char *dst_guess;
|
||||||
@ -1115,23 +1149,8 @@ static int match_explicit(struct ref *src, struct ref *dst,
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
matched_src = matched_dst = NULL;
|
matched_src = matched_dst = NULL;
|
||||||
switch (count_refspec_match(rs->src, src, &matched_src)) {
|
if (match_explicit_lhs(src, rs, &matched_src, &allocated_src) < 0)
|
||||||
case 1:
|
return -1;
|
||||||
copy_src = 1;
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
/* The source could be in the get_sha1() format
|
|
||||||
* not a reference name. :refs/other is a
|
|
||||||
* way to delete 'other' ref at the remote end.
|
|
||||||
*/
|
|
||||||
matched_src = try_explicit_object_name(rs->src);
|
|
||||||
if (!matched_src)
|
|
||||||
return error("src refspec %s does not match any.", rs->src);
|
|
||||||
copy_src = 0;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return error("src refspec %s matches more than one.", rs->src);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dst_value) {
|
if (!dst_value) {
|
||||||
unsigned char sha1[20];
|
unsigned char sha1[20];
|
||||||
@ -1176,7 +1195,9 @@ static int match_explicit(struct ref *src, struct ref *dst,
|
|||||||
return error("dst ref %s receives from more than one src.",
|
return error("dst ref %s receives from more than one src.",
|
||||||
matched_dst->name);
|
matched_dst->name);
|
||||||
else {
|
else {
|
||||||
matched_dst->peer_ref = copy_src ? copy_ref(matched_src) : matched_src;
|
matched_dst->peer_ref = allocated_src ?
|
||||||
|
matched_src :
|
||||||
|
copy_ref(matched_src);
|
||||||
matched_dst->force = rs->force;
|
matched_dst->force = rs->force;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -1357,6 +1378,31 @@ static void prepare_ref_index(struct string_list *ref_index, struct ref *ref)
|
|||||||
sort_string_list(ref_index);
|
sort_string_list(ref_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given only the set of local refs, sanity-check the set of push
|
||||||
|
* refspecs. We can't catch all errors that match_push_refs would,
|
||||||
|
* but we can catch some errors early before even talking to the
|
||||||
|
* remote side.
|
||||||
|
*/
|
||||||
|
int check_push_refs(struct ref *src, int nr_refspec, const char **refspec_names)
|
||||||
|
{
|
||||||
|
struct refspec *refspec = parse_push_refspec(nr_refspec, refspec_names);
|
||||||
|
int ret = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < nr_refspec; i++) {
|
||||||
|
struct refspec *rs = refspec + i;
|
||||||
|
|
||||||
|
if (rs->pattern || rs->matching)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ret |= match_explicit_lhs(src, rs, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
free_refspec(nr_refspec, refspec);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Given the set of refs the local repository has, the set of refs the
|
* Given the set of refs the local repository has, the set of refs the
|
||||||
* remote repository has, and the refspec used for push, determine
|
* remote repository has, and the refspec used for push, determine
|
||||||
|
1
remote.h
1
remote.h
@ -166,6 +166,7 @@ extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
|
|||||||
char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
|
char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
|
||||||
const char *name);
|
const char *name);
|
||||||
|
|
||||||
|
int check_push_refs(struct ref *src, int nr_refspec, const char **refspec);
|
||||||
int match_push_refs(struct ref *src, struct ref **dst,
|
int match_push_refs(struct ref *src, struct ref **dst,
|
||||||
int nr_refspec, const char **refspec, int all);
|
int nr_refspec, const char **refspec, int all);
|
||||||
void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
|
void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
|
||||||
|
48
t/t5529-push-errors.sh
Executable file
48
t/t5529-push-errors.sh
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='detect some push errors early (before contacting remote)'
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_expect_success 'setup commits' '
|
||||||
|
test_commit one
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'setup remote' '
|
||||||
|
git init --bare remote.git &&
|
||||||
|
git remote add origin remote.git
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'setup fake receive-pack' '
|
||||||
|
FAKE_RP_ROOT=$(pwd) &&
|
||||||
|
export FAKE_RP_ROOT &&
|
||||||
|
write_script fake-rp <<-\EOF &&
|
||||||
|
echo yes >"$FAKE_RP_ROOT"/rp-ran
|
||||||
|
exit 1
|
||||||
|
EOF
|
||||||
|
git config remote.origin.receivepack "\"\$FAKE_RP_ROOT/fake-rp\""
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'detect missing branches early' '
|
||||||
|
echo no >rp-ran &&
|
||||||
|
echo no >expect &&
|
||||||
|
test_must_fail git push origin missing &&
|
||||||
|
test_cmp expect rp-ran
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'detect missing sha1 expressions early' '
|
||||||
|
echo no >rp-ran &&
|
||||||
|
echo no >expect &&
|
||||||
|
test_must_fail git push origin master~2:master &&
|
||||||
|
test_cmp expect rp-ran
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'detect ambiguous refs early' '
|
||||||
|
git branch foo &&
|
||||||
|
git tag foo &&
|
||||||
|
echo no >rp-ran &&
|
||||||
|
echo no >expect &&
|
||||||
|
test_must_fail git push origin foo &&
|
||||||
|
test_cmp expect rp-ran
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
@ -1132,8 +1132,7 @@ int transport_push(struct transport *transport,
|
|||||||
|
|
||||||
return transport->push(transport, refspec_nr, refspec, flags);
|
return transport->push(transport, refspec_nr, refspec, flags);
|
||||||
} else if (transport->push_refs) {
|
} else if (transport->push_refs) {
|
||||||
struct ref *remote_refs =
|
struct ref *remote_refs;
|
||||||
transport->get_refs_list(transport, 1);
|
|
||||||
struct ref *local_refs = get_local_heads();
|
struct ref *local_refs = get_local_heads();
|
||||||
int match_flags = MATCH_REFS_NONE;
|
int match_flags = MATCH_REFS_NONE;
|
||||||
int verbose = (transport->verbose > 0);
|
int verbose = (transport->verbose > 0);
|
||||||
@ -1142,6 +1141,11 @@ int transport_push(struct transport *transport,
|
|||||||
int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
|
int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
|
||||||
int push_ret, ret, err;
|
int push_ret, ret, err;
|
||||||
|
|
||||||
|
if (check_push_refs(local_refs, refspec_nr, refspec) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
remote_refs = transport->get_refs_list(transport, 1);
|
||||||
|
|
||||||
if (flags & TRANSPORT_PUSH_ALL)
|
if (flags & TRANSPORT_PUSH_ALL)
|
||||||
match_flags |= MATCH_REFS_ALL;
|
match_flags |= MATCH_REFS_ALL;
|
||||||
if (flags & TRANSPORT_PUSH_MIRROR)
|
if (flags & TRANSPORT_PUSH_MIRROR)
|
||||||
|
Loading…
Reference in New Issue
Block a user