diff --git a/refspec.c b/refspec.c index 22188f0106..8bf4ebbd3d 100644 --- a/refspec.c +++ b/refspec.c @@ -14,110 +14,123 @@ static struct refspec_item s_tag_refspec = { /* See TAG_REFSPEC for the string version */ const struct refspec_item *tag_refspec = &s_tag_refspec; +/* + * Parses the provided refspec 'refspec' and populates the refspec_item 'item'. + * Returns 1 if successful and 0 if the refspec is invalid. + */ +static int parse_refspec(struct refspec_item *item, const char *refspec, int fetch) +{ + size_t llen; + int is_glob; + const char *lhs, *rhs; + int flags; + + is_glob = 0; + + lhs = refspec; + if (*lhs == '+') { + item->force = 1; + lhs++; + } + + rhs = strrchr(lhs, ':'); + + /* + * Before going on, special case ":" (or "+:") as a refspec + * for pushing matching refs. + */ + if (!fetch && rhs == lhs && rhs[1] == '\0') { + item->matching = 1; + return 1; + } + + if (rhs) { + size_t rlen = strlen(++rhs); + is_glob = (1 <= rlen && strchr(rhs, '*')); + item->dst = xstrndup(rhs, rlen); + } + + llen = (rhs ? (rhs - lhs - 1) : strlen(lhs)); + if (1 <= llen && memchr(lhs, '*', llen)) { + if ((rhs && !is_glob) || (!rhs && fetch)) + return 0; + is_glob = 1; + } else if (rhs && is_glob) { + return 0; + } + + item->pattern = is_glob; + item->src = xstrndup(lhs, llen); + flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0); + + if (fetch) { + struct object_id unused; + + /* LHS */ + if (!*item->src) + ; /* empty is ok; it means "HEAD" */ + else if (llen == GIT_SHA1_HEXSZ && !get_oid_hex(item->src, &unused)) + item->exact_sha1 = 1; /* ok */ + else if (!check_refname_format(item->src, flags)) + ; /* valid looking ref is ok */ + else + return 0; + /* RHS */ + if (!item->dst) + ; /* missing is ok; it is the same as empty */ + else if (!*item->dst) + ; /* empty is ok; it means "do not store" */ + else if (!check_refname_format(item->dst, flags)) + ; /* valid looking ref is ok */ + else + return 0; + } else { + /* + * 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 (!*item->src) + ; /* empty is ok */ + else if (is_glob) { + if (check_refname_format(item->src, flags)) + return 0; + } + 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 (!item->dst) { + if (check_refname_format(item->src, flags)) + return 0; + } else if (!*item->dst) { + return 0; + } else { + if (check_refname_format(item->dst, flags)) + return 0; + } + } + + return 1; +} + static struct refspec_item *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify) { int i; struct refspec_item *rs = xcalloc(nr_refspec, sizeof(*rs)); for (i = 0; i < nr_refspec; i++) { - size_t llen; - int is_glob; - const char *lhs, *rhs; - int flags; - - is_glob = 0; - - lhs = refspec[i]; - if (*lhs == '+') { - rs[i].force = 1; - lhs++; - } - - rhs = strrchr(lhs, ':'); - - /* - * Before going on, special case ":" (or "+:") as a refspec - * for pushing matching refs. - */ - if (!fetch && rhs == lhs && rhs[1] == '\0') { - rs[i].matching = 1; - continue; - } - - if (rhs) { - size_t rlen = strlen(++rhs); - is_glob = (1 <= rlen && strchr(rhs, '*')); - rs[i].dst = xstrndup(rhs, rlen); - } - - llen = (rhs ? (rhs - lhs - 1) : strlen(lhs)); - if (1 <= llen && memchr(lhs, '*', llen)) { - if ((rhs && !is_glob) || (!rhs && fetch)) - goto invalid; - is_glob = 1; - } else if (rhs && is_glob) { + if (!parse_refspec(&rs[i], refspec[i], fetch)) goto invalid; - } - - rs[i].pattern = is_glob; - rs[i].src = xstrndup(lhs, llen); - flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0); - - if (fetch) { - struct object_id unused; - - /* LHS */ - if (!*rs[i].src) - ; /* empty is ok; it means "HEAD" */ - else if (llen == GIT_SHA1_HEXSZ && !get_oid_hex(rs[i].src, &unused)) - rs[i].exact_sha1 = 1; /* ok */ - else if (!check_refname_format(rs[i].src, flags)) - ; /* valid looking ref is ok */ - else - goto invalid; - /* RHS */ - if (!rs[i].dst) - ; /* missing is ok; it is the same as empty */ - else if (!*rs[i].dst) - ; /* empty is ok; it means "do not store" */ - else if (!check_refname_format(rs[i].dst, flags)) - ; /* valid looking ref is ok */ - else - goto invalid; - } else { - /* - * 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) { - if (check_refname_format(rs[i].src, flags)) - 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) { - if (check_refname_format(rs[i].src, flags)) - goto invalid; - } else if (!*rs[i].dst) { - goto invalid; - } else { - if (check_refname_format(rs[i].dst, flags)) - goto invalid; - } - } } + return rs; invalid: