#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" #include "gettext.h" #include "hash.h" #include "hex.h" #include "string-list.h" #include "strvec.h" #include "refs.h" #include "refspec.h" #include "remote.h" #include "strbuf.h" /* * 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++; } else if (*lhs == '^') { item->negative = 1; lhs++; } rhs = strrchr(lhs, ':'); /* negative refspecs only have one side */ if (item->negative && rhs) return 0; /* * 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); } else { item->dst = NULL; } llen = (rhs ? (rhs - lhs - 1) : strlen(lhs)); if (1 <= llen && memchr(lhs, '*', llen)) { if ((rhs && !is_glob) || (!rhs && !item->negative && fetch)) return 0; is_glob = 1; } else if (rhs && is_glob) { return 0; } item->pattern = is_glob; if (llen == 1 && *lhs == '@') item->src = xstrdup("HEAD"); else item->src = xstrndup(lhs, llen); flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0); if (item->negative) { struct object_id unused; /* * Negative refspecs only have a LHS, which indicates a ref * (or pattern of refs) to exclude from other matches. This * can either be a simple ref, or a glob pattern. Exact sha1 * match is not currently supported. */ if (!*item->src) return 0; /* negative refspecs must not be empty */ else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused)) return 0; /* negative refpsecs cannot be exact sha1 */ else if (!check_refname_format(item->src, flags)) ; /* valid looking ref is ok */ else return 0; /* the other rules below do not apply to negative refspecs */ return 1; } if (fetch) { struct object_id unused; /* LHS */ if (!*item->src) ; /* empty is ok; it means "HEAD" */ else if (llen == the_hash_algo->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; } int refspec_item_init(struct refspec_item *item, const char *refspec, int fetch) { memset(item, 0, sizeof(*item)); item->raw = xstrdup(refspec); return parse_refspec(item, refspec, fetch); } void refspec_item_init_or_die(struct refspec_item *item, const char *refspec, int fetch) { if (!refspec_item_init(item, refspec, fetch)) die(_("invalid refspec '%s'"), refspec); } void refspec_item_clear(struct refspec_item *item) { FREE_AND_NULL(item->src); FREE_AND_NULL(item->dst); FREE_AND_NULL(item->raw); item->force = 0; item->pattern = 0; item->matching = 0; item->exact_sha1 = 0; } void refspec_init(struct refspec *rs, int fetch) { memset(rs, 0, sizeof(*rs)); rs->fetch = fetch; } void refspec_append(struct refspec *rs, const char *refspec) { struct refspec_item item; refspec_item_init_or_die(&item, refspec, rs->fetch); ALLOC_GROW(rs->items, rs->nr + 1, rs->alloc); rs->items[rs->nr] = item; rs->nr++; } void refspec_appendf(struct refspec *rs, const char *fmt, ...) { va_list ap; char *buf; va_start(ap, fmt); buf = xstrvfmt(fmt, ap); va_end(ap); refspec_append(rs, buf); free(buf); } void refspec_appendn(struct refspec *rs, const char **refspecs, int nr) { int i; for (i = 0; i < nr; i++) refspec_append(rs, refspecs[i]); } void refspec_clear(struct refspec *rs) { int i; for (i = 0; i < rs->nr; i++) refspec_item_clear(&rs->items[i]); FREE_AND_NULL(rs->items); rs->alloc = 0; rs->nr = 0; rs->fetch = 0; } int valid_fetch_refspec(const char *fetch_refspec_str) { struct refspec_item refspec; int ret = refspec_item_init(&refspec, fetch_refspec_str, REFSPEC_FETCH); refspec_item_clear(&refspec); return ret; } void refspec_ref_prefixes(const struct refspec *rs, struct strvec *ref_prefixes) { int i; for (i = 0; i < rs->nr; i++) { const struct refspec_item *item = &rs->items[i]; const char *prefix = NULL; if (item->exact_sha1 || item->negative) continue; if (rs->fetch == REFSPEC_FETCH) prefix = item->src; else if (item->dst) prefix = item->dst; else if (item->src && !item->exact_sha1) prefix = item->src; if (!prefix) continue; if (item->pattern) { const char *glob = strchr(prefix, '*'); strvec_pushf(ref_prefixes, "%.*s", (int)(glob - prefix), prefix); } else { expand_ref_prefix(ref_prefixes, prefix); } } } int match_name_with_pattern(const char *key, const char *name, const char *value, char **result) { const char *kstar = strchr(key, '*'); size_t klen; size_t ksuffixlen; size_t namelen; int ret; if (!kstar) die(_("key '%s' of pattern had no '*'"), key); klen = kstar - key; ksuffixlen = strlen(kstar + 1); namelen = strlen(name); ret = !strncmp(name, key, klen) && namelen >= klen + ksuffixlen && !memcmp(name + namelen - ksuffixlen, kstar + 1, ksuffixlen); if (ret && value) { struct strbuf sb = STRBUF_INIT; const char *vstar = strchr(value, '*'); if (!vstar) die(_("value '%s' of pattern has no '*'"), value); strbuf_add(&sb, value, vstar - value); strbuf_add(&sb, name + klen, namelen - klen - ksuffixlen); strbuf_addstr(&sb, vstar + 1); *result = strbuf_detach(&sb, NULL); } return ret; } static int refspec_match(const struct refspec_item *refspec, const char *name) { if (refspec->pattern) return match_name_with_pattern(refspec->src, name, NULL, NULL); return !strcmp(refspec->src, name); } int refname_matches_negative_refspec_item(const char *refname, struct refspec *rs) { int i; for (i = 0; i < rs->nr; i++) { if (rs->items[i].negative && refspec_match(&rs->items[i], refname)) return 1; } return 0; } static int refspec_find_negative_match(struct refspec *rs, struct refspec_item *query) { int i, matched_negative = 0; int find_src = !query->src; struct string_list reversed = STRING_LIST_INIT_DUP; const char *needle = find_src ? query->dst : query->src; /* * Check whether the queried ref matches any negative refpsec. If so, * then we should ultimately treat this as not matching the query at * all. * * Note that negative refspecs always match the source, but the query * item uses the destination. To handle this, we apply pattern * refspecs in reverse to figure out if the query source matches any * of the negative refspecs. * * The first loop finds and expands all positive refspecs * matched by the queried ref. * * The second loop checks if any of the results of the first loop * match any negative refspec. */ for (i = 0; i < rs->nr; i++) { struct refspec_item *refspec = &rs->items[i]; char *expn_name; if (refspec->negative) continue; /* Note the reversal of src and dst */ if (refspec->pattern) { const char *key = refspec->dst ? refspec->dst : refspec->src; const char *value = refspec->src; if (match_name_with_pattern(key, needle, value, &expn_name)) string_list_append_nodup(&reversed, expn_name); } else if (refspec->matching) { /* For the special matching refspec, any query should match */ string_list_append(&reversed, needle); } else if (!refspec->src) { BUG("refspec->src should not be null here"); } else if (!strcmp(needle, refspec->src)) { string_list_append(&reversed, refspec->src); } } for (i = 0; !matched_negative && i < reversed.nr; i++) { if (refname_matches_negative_refspec_item(reversed.items[i].string, rs)) matched_negative = 1; } string_list_clear(&reversed, 0); return matched_negative; } void refspec_find_all_matches(struct refspec *rs, struct refspec_item *query, struct string_list *results) { int i; int find_src = !query->src; if (find_src && !query->dst) BUG("refspec_find_all_matches: need either src or dst"); if (refspec_find_negative_match(rs, query)) return; for (i = 0; i < rs->nr; i++) { struct refspec_item *refspec = &rs->items[i]; const char *key = find_src ? refspec->dst : refspec->src; const char *value = find_src ? refspec->src : refspec->dst; const char *needle = find_src ? query->dst : query->src; char **result = find_src ? &query->src : &query->dst; if (!refspec->dst || refspec->negative) continue; if (refspec->pattern) { if (match_name_with_pattern(key, needle, value, result)) string_list_append_nodup(results, *result); } else if (!strcmp(needle, key)) { string_list_append(results, value); } } } int refspec_find_match(struct refspec *rs, struct refspec_item *query) { int i; int find_src = !query->src; const char *needle = find_src ? query->dst : query->src; char **result = find_src ? &query->src : &query->dst; if (find_src && !query->dst) BUG("refspec_find_match: need either src or dst"); if (refspec_find_negative_match(rs, query)) return -1; for (i = 0; i < rs->nr; i++) { struct refspec_item *refspec = &rs->items[i]; const char *key = find_src ? refspec->dst : refspec->src; const char *value = find_src ? refspec->src : refspec->dst; if (!refspec->dst || refspec->negative) continue; if (refspec->pattern) { if (match_name_with_pattern(key, needle, value, result)) { query->force = refspec->force; return 0; } } else if (!strcmp(needle, key)) { *result = xstrdup(value); query->force = refspec->force; return 0; } } return -1; } struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs) { struct ref **tail; for (tail = &ref_map; *tail; ) { struct ref *ref = *tail; if (refname_matches_negative_refspec_item(ref->name, rs)) { *tail = ref->next; free(ref->peer_ref); free(ref); } else tail = &ref->next; } return ref_map; } char *apply_refspecs(struct refspec *rs, const char *name) { struct refspec_item query; memset(&query, 0, sizeof(struct refspec_item)); query.src = (char *)name; if (refspec_find_match(rs, &query)) return NULL; return query.dst; }