Skip to content

Commit 9befb34

Browse files
committed
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
2 parents 249d54b + ba928c1 commit 9befb34

File tree

4 files changed

+130
-31
lines changed

4 files changed

+130
-31
lines changed

remote.c

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,11 +1036,13 @@ int count_refspec_match(const char *pattern,
10361036
}
10371037
}
10381038
if (!matched) {
1039-
*matched_ref = matched_weak;
1039+
if (matched_ref)
1040+
*matched_ref = matched_weak;
10401041
return weak_match;
10411042
}
10421043
else {
1043-
*matched_ref = matched;
1044+
if (matched_ref)
1045+
*matched_ref = matched;
10441046
return match;
10451047
}
10461048
}
@@ -1060,18 +1062,25 @@ static struct ref *alloc_delete_ref(void)
10601062
return ref;
10611063
}
10621064

1063-
static struct ref *try_explicit_object_name(const char *name)
1065+
static int try_explicit_object_name(const char *name,
1066+
struct ref **match)
10641067
{
10651068
unsigned char sha1[20];
1066-
struct ref *ref;
10671069

1068-
if (!*name)
1069-
return alloc_delete_ref();
1070+
if (!*name) {
1071+
if (match)
1072+
*match = alloc_delete_ref();
1073+
return 0;
1074+
}
1075+
10701076
if (get_sha1(name, sha1))
1071-
return NULL;
1072-
ref = alloc_ref(name);
1073-
hashcpy(ref->new_sha1, sha1);
1074-
return ref;
1077+
return -1;
1078+
1079+
if (match) {
1080+
*match = alloc_ref(name);
1081+
hashcpy((*match)->new_sha1, sha1);
1082+
}
1083+
return 0;
10751084
}
10761085

10771086
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)
11011110
return strbuf_detach(&buf, NULL);
11021111
}
11031112

1113+
static int match_explicit_lhs(struct ref *src,
1114+
struct refspec *rs,
1115+
struct ref **match,
1116+
int *allocated_match)
1117+
{
1118+
switch (count_refspec_match(rs->src, src, match)) {
1119+
case 1:
1120+
if (allocated_match)
1121+
*allocated_match = 0;
1122+
return 0;
1123+
case 0:
1124+
/* The source could be in the get_sha1() format
1125+
* not a reference name. :refs/other is a
1126+
* way to delete 'other' ref at the remote end.
1127+
*/
1128+
if (try_explicit_object_name(rs->src, match) < 0)
1129+
return error("src refspec %s does not match any.", rs->src);
1130+
if (allocated_match)
1131+
*allocated_match = 1;
1132+
return 0;
1133+
default:
1134+
return error("src refspec %s matches more than one.", rs->src);
1135+
}
1136+
}
1137+
11041138
static int match_explicit(struct ref *src, struct ref *dst,
11051139
struct ref ***dst_tail,
11061140
struct refspec *rs)
11071141
{
11081142
struct ref *matched_src, *matched_dst;
1109-
int copy_src;
1143+
int allocated_src;
11101144

11111145
const char *dst_value = rs->dst;
11121146
char *dst_guess;
@@ -1115,23 +1149,8 @@ static int match_explicit(struct ref *src, struct ref *dst,
11151149
return 0;
11161150

11171151
matched_src = matched_dst = NULL;
1118-
switch (count_refspec_match(rs->src, src, &matched_src)) {
1119-
case 1:
1120-
copy_src = 1;
1121-
break;
1122-
case 0:
1123-
/* The source could be in the get_sha1() format
1124-
* not a reference name. :refs/other is a
1125-
* way to delete 'other' ref at the remote end.
1126-
*/
1127-
matched_src = try_explicit_object_name(rs->src);
1128-
if (!matched_src)
1129-
return error("src refspec %s does not match any.", rs->src);
1130-
copy_src = 0;
1131-
break;
1132-
default:
1133-
return error("src refspec %s matches more than one.", rs->src);
1134-
}
1152+
if (match_explicit_lhs(src, rs, &matched_src, &allocated_src) < 0)
1153+
return -1;
11351154

11361155
if (!dst_value) {
11371156
unsigned char sha1[20];
@@ -1176,7 +1195,9 @@ static int match_explicit(struct ref *src, struct ref *dst,
11761195
return error("dst ref %s receives from more than one src.",
11771196
matched_dst->name);
11781197
else {
1179-
matched_dst->peer_ref = copy_src ? copy_ref(matched_src) : matched_src;
1198+
matched_dst->peer_ref = allocated_src ?
1199+
matched_src :
1200+
copy_ref(matched_src);
11801201
matched_dst->force = rs->force;
11811202
}
11821203
return 0;
@@ -1357,6 +1378,31 @@ static void prepare_ref_index(struct string_list *ref_index, struct ref *ref)
13571378
sort_string_list(ref_index);
13581379
}
13591380

1381+
/*
1382+
* Given only the set of local refs, sanity-check the set of push
1383+
* refspecs. We can't catch all errors that match_push_refs would,
1384+
* but we can catch some errors early before even talking to the
1385+
* remote side.
1386+
*/
1387+
int check_push_refs(struct ref *src, int nr_refspec, const char **refspec_names)
1388+
{
1389+
struct refspec *refspec = parse_push_refspec(nr_refspec, refspec_names);
1390+
int ret = 0;
1391+
int i;
1392+
1393+
for (i = 0; i < nr_refspec; i++) {
1394+
struct refspec *rs = refspec + i;
1395+
1396+
if (rs->pattern || rs->matching)
1397+
continue;
1398+
1399+
ret |= match_explicit_lhs(src, rs, NULL, NULL);
1400+
}
1401+
1402+
free_refspec(nr_refspec, refspec);
1403+
return ret;
1404+
}
1405+
13601406
/*
13611407
* Given the set of refs the local repository has, the set of refs the
13621408
* remote repository has, and the refspec used for push, determine

remote.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
166166
char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
167167
const char *name);
168168

169+
int check_push_refs(struct ref *src, int nr_refspec, const char **refspec);
169170
int match_push_refs(struct ref *src, struct ref **dst,
170171
int nr_refspec, const char **refspec, int all);
171172
void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,

t/t5529-push-errors.sh

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/bin/sh
2+
3+
test_description='detect some push errors early (before contacting remote)'
4+
. ./test-lib.sh
5+
6+
test_expect_success 'setup commits' '
7+
test_commit one
8+
'
9+
10+
test_expect_success 'setup remote' '
11+
git init --bare remote.git &&
12+
git remote add origin remote.git
13+
'
14+
15+
test_expect_success 'setup fake receive-pack' '
16+
FAKE_RP_ROOT=$(pwd) &&
17+
export FAKE_RP_ROOT &&
18+
write_script fake-rp <<-\EOF &&
19+
echo yes >"$FAKE_RP_ROOT"/rp-ran
20+
exit 1
21+
EOF
22+
git config remote.origin.receivepack "\"\$FAKE_RP_ROOT/fake-rp\""
23+
'
24+
25+
test_expect_success 'detect missing branches early' '
26+
echo no >rp-ran &&
27+
echo no >expect &&
28+
test_must_fail git push origin missing &&
29+
test_cmp expect rp-ran
30+
'
31+
32+
test_expect_success 'detect missing sha1 expressions early' '
33+
echo no >rp-ran &&
34+
echo no >expect &&
35+
test_must_fail git push origin master~2:master &&
36+
test_cmp expect rp-ran
37+
'
38+
39+
test_expect_success 'detect ambiguous refs early' '
40+
git branch foo &&
41+
git tag foo &&
42+
echo no >rp-ran &&
43+
echo no >expect &&
44+
test_must_fail git push origin foo &&
45+
test_cmp expect rp-ran
46+
'
47+
48+
test_done

transport.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,8 +1132,7 @@ int transport_push(struct transport *transport,
11321132

11331133
return transport->push(transport, refspec_nr, refspec, flags);
11341134
} else if (transport->push_refs) {
1135-
struct ref *remote_refs =
1136-
transport->get_refs_list(transport, 1);
1135+
struct ref *remote_refs;
11371136
struct ref *local_refs = get_local_heads();
11381137
int match_flags = MATCH_REFS_NONE;
11391138
int verbose = (transport->verbose > 0);
@@ -1142,6 +1141,11 @@ int transport_push(struct transport *transport,
11421141
int pretend = flags & TRANSPORT_PUSH_DRY_RUN;
11431142
int push_ret, ret, err;
11441143

1144+
if (check_push_refs(local_refs, refspec_nr, refspec) < 0)
1145+
return -1;
1146+
1147+
remote_refs = transport->get_refs_list(transport, 1);
1148+
11451149
if (flags & TRANSPORT_PUSH_ALL)
11461150
match_flags |= MATCH_REFS_ALL;
11471151
if (flags & TRANSPORT_PUSH_MIRROR)

0 commit comments

Comments
 (0)