Tighten refspec processing

This changes the pattern matching code to not store the required final
/ before the *, and then to require each side to be a valid ref (or
empty). In particular, any refspec that looks like it should be a
pattern but doesn't quite meet the requirements will be found to be
invalid as a fallback non-pattern.

This was cherry picked from commit ef00d15 (Tighten refspec processing,
2008-03-17), and two fix-up commits 46220ca (remote.c: Fix overtight
refspec validation, 2008-03-20) and 7d19da4 (refspec: allow colon-less
wildcard "refs/category/*", 2008-03-25) squashed in.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Daniel Barkalow 2008-03-17 22:05:23 -04:00 committed by Junio C Hamano
parent 71a5099b64
commit c091b3d415
5 changed files with 214 additions and 42 deletions

View File

@ -630,5 +630,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
signal(SIGINT, unlock_pack_on_signal);
atexit(unlock_pack);
return do_fetch(transport, parse_ref_spec(ref_nr, refs), ref_nr);
return do_fetch(transport,
parse_fetch_refspec(ref_nr, refs), ref_nr);
}

View File

@ -536,7 +536,7 @@ static void verify_remote_names(int nr_heads, const char **heads)
int i;
for (i = 0; i < nr_heads; i++) {
const char *remote = strchr(heads[i], ':');
const char *remote = strrchr(heads[i], ':');
remote = remote ? (remote + 1) : heads[i];
switch (check_ref_format(remote)) {

176
remote.c
View File

@ -305,42 +305,127 @@ static void read_config(void)
git_config(handle_config);
}
struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch)
{
int i;
int st;
struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec);
for (i = 0; i < nr_refspec; i++) {
const char *sp, *ep, *gp;
sp = refspec[i];
if (*sp == '+') {
size_t llen, rlen;
int is_glob;
const char *lhs, *rhs;
llen = rlen = is_glob = 0;
lhs = refspec[i];
if (*lhs == '+') {
rs[i].force = 1;
sp++;
lhs++;
}
gp = strchr(sp, '*');
ep = strchr(sp, ':');
if (gp && ep && gp > ep)
gp = NULL;
if (ep) {
if (ep[1]) {
const char *glob = strchr(ep + 1, '*');
if (!glob)
gp = NULL;
if (gp)
rs[i].dst = xstrndup(ep + 1,
glob - ep - 1);
else
rs[i].dst = xstrdup(ep + 1);
rhs = strrchr(lhs, ':');
if (rhs) {
rhs++;
rlen = strlen(rhs);
is_glob = (2 <= rlen && !strcmp(rhs + rlen - 2, "/*"));
if (is_glob)
rlen -= 2;
rs[i].dst = xstrndup(rhs, rlen);
}
llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
if (2 <= llen && !memcmp(lhs + llen - 2, "/*", 2)) {
if ((rhs && !is_glob) || (!rhs && fetch))
goto invalid;
is_glob = 1;
llen -= 2;
} else if (rhs && is_glob) {
goto invalid;
}
rs[i].pattern = is_glob;
rs[i].src = xstrndup(lhs, llen);
if (fetch) {
/*
* LHS
* - empty is allowed; it means HEAD.
* - otherwise it must be a valid looking ref.
*/
if (!*rs[i].src)
; /* empty is ok */
else {
st = check_ref_format(rs[i].src);
if (st && st != CHECK_REF_FORMAT_ONELEVEL)
goto invalid;
}
/*
* RHS
* - missing is ok, and is same as empty.
* - empty is ok; it means not to store.
* - otherwise it must be a valid looking ref.
*/
if (!rs[i].dst) {
; /* ok */
} else if (!*rs[i].dst) {
; /* ok */
} else {
st = check_ref_format(rs[i].dst);
if (st && st != CHECK_REF_FORMAT_ONELEVEL)
goto invalid;
}
} else {
ep = sp + strlen(sp);
/*
* LHS
* - empty is allowed; it means delete.
* - when wildcarded, it must be a valid looking ref.
* - otherwise, it must be an extended SHA-1, but
* there is no existing way to validate this.
*/
if (!*rs[i].src)
; /* empty is ok */
else if (is_glob) {
st = check_ref_format(rs[i].src);
if (st && st != CHECK_REF_FORMAT_ONELEVEL)
goto invalid;
}
else
; /* anything goes, for now */
/*
* RHS
* - missing is allowed, but LHS then must be a
* valid looking ref.
* - empty is not allowed.
* - otherwise it must be a valid looking ref.
*/
if (!rs[i].dst) {
st = check_ref_format(rs[i].src);
if (st && st != CHECK_REF_FORMAT_ONELEVEL)
goto invalid;
} else if (!*rs[i].dst) {
goto invalid;
} else {
st = check_ref_format(rs[i].dst);
if (st && st != CHECK_REF_FORMAT_ONELEVEL)
goto invalid;
}
}
if (gp) {
rs[i].pattern = 1;
ep = gp;
}
rs[i].src = xstrndup(sp, ep - sp);
}
return rs;
invalid:
die("Invalid refspec '%s'", refspec[i]);
}
struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec)
{
return parse_refspec_internal(nr_refspec, refspec, 1);
}
struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
{
return parse_refspec_internal(nr_refspec, refspec, 0);
}
static int valid_remote_nick(const char *name)
@ -371,8 +456,8 @@ struct remote *remote_get(const char *name)
add_url(ret, name);
if (!ret->url)
return NULL;
ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec);
ret->push = parse_ref_spec(ret->push_refspec_nr, ret->push_refspec);
ret->fetch = parse_fetch_refspec(ret->fetch_refspec_nr, ret->fetch_refspec);
ret->push = parse_push_refspec(ret->push_refspec_nr, ret->push_refspec);
return ret;
}
@ -385,11 +470,11 @@ int for_each_remote(each_remote_fn fn, void *priv)
if (!r)
continue;
if (!r->fetch)
r->fetch = parse_ref_spec(r->fetch_refspec_nr,
r->fetch_refspec);
r->fetch = parse_fetch_refspec(r->fetch_refspec_nr,
r->fetch_refspec);
if (!r->push)
r->push = parse_ref_spec(r->push_refspec_nr,
r->push_refspec);
r->push = parse_push_refspec(r->push_refspec_nr,
r->push_refspec);
result = fn(r, priv);
}
return result;
@ -455,7 +540,8 @@ int remote_find_tracking(struct remote *remote, struct refspec *refspec)
if (!fetch->dst)
continue;
if (fetch->pattern) {
if (!prefixcmp(needle, key)) {
if (!prefixcmp(needle, key) &&
needle[strlen(key)] == '/') {
*result = xmalloc(strlen(value) +
strlen(needle) -
strlen(key) + 1);
@ -695,7 +781,9 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
{
int i;
for (i = 0; i < rs_nr; i++) {
if (rs[i].pattern && !prefixcmp(src->name, rs[i].src))
if (rs[i].pattern &&
!prefixcmp(src->name, rs[i].src) &&
src->name[strlen(rs[i].src)] == '/')
return rs + i;
}
return NULL;
@ -710,7 +798,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
int nr_refspec, const char **refspec, int flags)
{
struct refspec *rs =
parse_ref_spec(nr_refspec, (const char **) refspec);
parse_push_refspec(nr_refspec, (const char **) refspec);
int send_all = flags & MATCH_REFS_ALL;
int send_mirror = flags & MATCH_REFS_MIRROR;
@ -894,7 +982,7 @@ int get_fetch_map(const struct ref *remote_refs,
struct ref ***tail,
int missing_ok)
{
struct ref *ref_map, *rm;
struct ref *ref_map, **rmp;
if (refspec->pattern) {
ref_map = get_expanded_map(remote_refs, refspec);
@ -911,10 +999,20 @@ int get_fetch_map(const struct ref *remote_refs,
}
}
for (rm = ref_map; rm; rm = rm->next) {
if (rm->peer_ref && check_ref_format(rm->peer_ref->name + 5))
die("* refusing to create funny ref '%s' locally",
rm->peer_ref->name);
for (rmp = &ref_map; *rmp; ) {
if ((*rmp)->peer_ref) {
int st = check_ref_format((*rmp)->peer_ref->name + 5);
if (st && st != CHECK_REF_FORMAT_ONELEVEL) {
struct ref *ignore = *rmp;
error("* Ignoring funny ref '%s' locally",
(*rmp)->peer_ref->name);
*rmp = (*rmp)->next;
free(ignore->peer_ref);
free(ignore);
continue;
}
}
rmp = &((*rmp)->next);
}
if (ref_map)

View File

@ -63,7 +63,8 @@ void free_refs(struct ref *ref);
*/
void ref_remove_duplicates(struct ref *ref_map);
struct refspec *parse_ref_spec(int nr_refspec, const char **refspec);
struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
struct refspec *parse_push_refspec(int nr_refspec, const char **refspec);
int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
int nr_refspec, const char **refspec, int all);

72
t/t5511-refspec.sh Executable file
View File

@ -0,0 +1,72 @@
#!/bin/sh
test_description='refspec parsing'
. ./test-lib.sh
test_refspec () {
kind=$1 refspec=$2 expect=$3
git config remote.frotz.url "." &&
git config --remove-section remote.frotz &&
git config remote.frotz.url "." &&
git config "remote.frotz.$kind" "$refspec" &&
if test "$expect" != invalid
then
title="$kind $refspec"
test='git ls-remote frotz'
else
title="$kind $refspec (invalid)"
test='test_must_fail git ls-remote frotz'
fi
test_expect_success "$title" "$test"
}
test_refspec push '' invalid
test_refspec push ':' invalid
test_refspec fetch ''
test_refspec fetch ':'
test_refspec push 'refs/heads/*:refs/remotes/frotz/*'
test_refspec push 'refs/heads/*:refs/remotes/frotz' invalid
test_refspec push 'refs/heads:refs/remotes/frotz/*' invalid
test_refspec push 'refs/heads/master:refs/remotes/frotz/xyzzy'
# These have invalid LHS, but we do not have a formal "valid sha-1
# expression syntax checker" so they are not checked with the current
# code. They will be caught downstream anyway, but we may want to
# have tighter check later...
: test_refspec push 'refs/heads/master::refs/remotes/frotz/xyzzy' invalid
: test_refspec push 'refs/heads/maste :refs/remotes/frotz/xyzzy' invalid
test_refspec fetch 'refs/heads/*:refs/remotes/frotz/*'
test_refspec fetch 'refs/heads/*:refs/remotes/frotz' invalid
test_refspec fetch 'refs/heads:refs/remotes/frotz/*' invalid
test_refspec fetch 'refs/heads/master:refs/remotes/frotz/xyzzy'
test_refspec fetch 'refs/heads/master::refs/remotes/frotz/xyzzy' invalid
test_refspec fetch 'refs/heads/maste :refs/remotes/frotz/xyzzy' invalid
test_refspec push 'master~1:refs/remotes/frotz/backup'
test_refspec fetch 'master~1:refs/remotes/frotz/backup' invalid
test_refspec push 'HEAD~4:refs/remotes/frotz/new'
test_refspec fetch 'HEAD~4:refs/remotes/frotz/new' invalid
test_refspec push 'HEAD'
test_refspec fetch 'HEAD'
test_refspec push 'refs/heads/ nitfol' invalid
test_refspec fetch 'refs/heads/ nitfol' invalid
test_refspec push 'HEAD:' invalid
test_refspec fetch 'HEAD:'
test_refspec push 'refs/heads/ nitfol:' invalid
test_refspec fetch 'refs/heads/ nitfol:' invalid
test_refspec push ':refs/remotes/frotz/deleteme'
test_refspec fetch ':refs/remotes/frotz/HEAD-to-me'
test_refspec push ':refs/remotes/frotz/delete me' invalid
test_refspec fetch ':refs/remotes/frotz/HEAD to me' invalid
test_done