diff options
Diffstat (limited to 'ref-filter.c')
-rw-r--r-- | ref-filter.c | 292 |
1 files changed, 203 insertions, 89 deletions
diff --git a/ref-filter.c b/ref-filter.c index 238888e4d..ae6ecbd1c 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -15,6 +15,7 @@ #include "version.h" #include "trailer.h" #include "wt-status.h" +#include "commit-slab.h" static struct ref_msg { const char *gone; @@ -289,9 +290,9 @@ static void if_atom_parser(struct used_atom *atom, const char *arg) static void head_atom_parser(struct used_atom *atom, const char *arg) { - unsigned char unused[GIT_SHA1_RAWSZ]; + struct object_id unused; - atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, unused, NULL); + atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, unused.hash, NULL); } static struct { @@ -357,7 +358,7 @@ struct ref_formatting_state { struct atom_value { const char *s; void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state); - unsigned long ul; /* used for sorting when not FIELD_STR */ + uintmax_t value; /* used for sorting when not FIELD_STR */ struct used_atom *atom; }; @@ -683,13 +684,13 @@ int verify_ref_format(const char *format) * by the "struct object" representation, set *eaten as well---it is a * signal from parse_object_buffer to us not to free the buffer. */ -static void *get_obj(const unsigned char *sha1, struct object **obj, unsigned long *sz, int *eaten) +static void *get_obj(const struct object_id *oid, struct object **obj, unsigned long *sz, int *eaten) { enum object_type type; - void *buf = read_sha1_file(sha1, &type, sz); + void *buf = read_sha1_file(oid->hash, &type, sz); if (buf) - *obj = parse_object_buffer(sha1, type, *sz, buf, eaten); + *obj = parse_object_buffer(oid, type, *sz, buf, eaten); else *obj = NULL; return buf; @@ -729,7 +730,7 @@ static void grab_common_values(struct atom_value *val, int deref, struct object if (!strcmp(name, "objecttype")) v->s = typename(obj->type); else if (!strcmp(name, "objectsize")) { - v->ul = sz; + v->value = sz; v->s = xstrfmt("%lu", sz); } else if (deref) @@ -776,8 +777,8 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object v->s = xstrdup(oid_to_hex(&commit->tree->object.oid)); } else if (!strcmp(name, "numparent")) { - v->ul = commit_list_count(commit->parents); - v->s = xstrfmt("%lu", v->ul); + v->value = commit_list_count(commit->parents); + v->s = xstrfmt("%lu", (unsigned long)v->value); } else if (!strcmp(name, "parent")) { struct commit_list *parents; @@ -855,7 +856,7 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam { const char *eoemail = strstr(buf, "> "); char *zone; - unsigned long timestamp; + timestamp_t timestamp; long tz; struct date_mode date_mode = { DATE_NORMAL }; const char *formatp; @@ -874,18 +875,18 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam if (!eoemail) goto bad; - timestamp = strtoul(eoemail + 2, &zone, 10); - if (timestamp == ULONG_MAX) + timestamp = parse_timestamp(eoemail + 2, &zone, 10); + if (timestamp == TIME_MAX) goto bad; tz = strtol(zone, NULL, 10); if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE) goto bad; v->s = xstrdup(show_date(timestamp, tz, &date_mode)); - v->ul = timestamp; + v->value = timestamp; return; bad: v->s = ""; - v->ul = 0; + v->value = 0; } /* See grab_values */ @@ -1256,12 +1257,18 @@ char *get_head_description(void) strbuf_addf(&desc, _("(no branch, bisect started on %s)"), state.branch); else if (state.detached_from) { - /* TRANSLATORS: make sure these match _("HEAD detached at ") - and _("HEAD detached from ") in wt-status.c */ if (state.detached_at) + /* + * TRANSLATORS: make sure this matches "HEAD + * detached at " in wt-status.c + */ strbuf_addf(&desc, _("(HEAD detached at %s)"), state.detached_from); else + /* + * TRANSLATORS: make sure this matches "HEAD + * detached from " in wt-status.c + */ strbuf_addf(&desc, _("(HEAD detached from %s)"), state.detached_from); } @@ -1297,14 +1304,14 @@ static void populate_value(struct ref_array_item *ref) struct object *obj; int eaten, i; unsigned long size; - const unsigned char *tagged; + const struct object_id *tagged; ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value)); if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { - unsigned char unused1[20]; + struct object_id unused1; ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING, - unused1, NULL); + unused1.hash, NULL); if (!ref->symref) ref->symref = ""; } @@ -1370,7 +1377,7 @@ static void populate_value(struct ref_array_item *ref) v->s = xstrdup(buf + 1); } continue; - } else if (!deref && grab_objectname(name, ref->objectname, v, atom)) { + } else if (!deref && grab_objectname(name, ref->objectname.hash, v, atom)) { continue; } else if (!strcmp(name, "HEAD")) { if (atom->u.head && !strcmp(ref->refname, atom->u.head)) @@ -1414,13 +1421,13 @@ static void populate_value(struct ref_array_item *ref) return; need_obj: - buf = get_obj(ref->objectname, &obj, &size, &eaten); + buf = get_obj(&ref->objectname, &obj, &size, &eaten); if (!buf) die(_("missing object %s for %s"), - sha1_to_hex(ref->objectname), ref->refname); + oid_to_hex(&ref->objectname), ref->refname); if (!obj) die(_("parse_object_buffer failed on %s for %s"), - sha1_to_hex(ref->objectname), ref->refname); + oid_to_hex(&ref->objectname), ref->refname); grab_values(ref->value, 0, obj, buf, size); if (!eaten) @@ -1437,7 +1444,7 @@ static void populate_value(struct ref_array_item *ref) * If it is a tag object, see if we use a value that derefs * the object, and if we do grab the object it refers to. */ - tagged = ((struct tag *)obj)->tagged->oid.hash; + tagged = &((struct tag *)obj)->tagged->oid; /* * NEEDSWORK: This derefs tag only once, which @@ -1448,10 +1455,10 @@ static void populate_value(struct ref_array_item *ref) buf = get_obj(tagged, &obj, &size, &eaten); if (!buf) die(_("missing object %s for %s"), - sha1_to_hex(tagged), ref->refname); + oid_to_hex(tagged), ref->refname); if (!obj) die(_("parse_object_buffer failed on %s for %s"), - sha1_to_hex(tagged), ref->refname); + oid_to_hex(tagged), ref->refname); grab_values(ref->value, 1, obj, buf, size); if (!eaten) free(buf); @@ -1470,10 +1477,23 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom *v = &ref->value[atom]; } +/* + * Unknown has to be "0" here, because that's the default value for + * contains_cache slab entries that have not yet been assigned. + */ enum contains_result { - CONTAINS_UNKNOWN = -1, - CONTAINS_NO = 0, - CONTAINS_YES = 1 + CONTAINS_UNKNOWN = 0, + CONTAINS_NO, + CONTAINS_YES +}; + +define_commit_slab(contains_cache, enum contains_result); + +struct ref_filter_cbdata { + struct ref_array *array; + struct ref_filter *filter; + struct contains_cache contains_cache; + struct contains_cache no_contains_cache; }; /* @@ -1504,24 +1524,24 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) * Do not recurse to find out, though, but return -1 if inconclusive. */ static enum contains_result contains_test(struct commit *candidate, - const struct commit_list *want) + const struct commit_list *want, + struct contains_cache *cache) { - /* was it previously marked as containing a want commit? */ - if (candidate->object.flags & TMP_MARK) - return 1; - /* or marked as not possibly containing a want commit? */ - if (candidate->object.flags & UNINTERESTING) - return 0; + enum contains_result *cached = contains_cache_at(cache, candidate); + + /* If we already have the answer cached, return that. */ + if (*cached) + return *cached; + /* or are we it? */ if (in_commit_list(want, candidate)) { - candidate->object.flags |= TMP_MARK; - return 1; + *cached = CONTAINS_YES; + return CONTAINS_YES; } - if (parse_commit(candidate) < 0) - return 0; - - return -1; + /* Otherwise, we don't know; prepare to recurse */ + parse_commit_or_die(candidate); + return CONTAINS_UNKNOWN; } static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack) @@ -1532,10 +1552,11 @@ static void push_to_contains_stack(struct commit *candidate, struct contains_sta } static enum contains_result contains_tag_algo(struct commit *candidate, - const struct commit_list *want) + const struct commit_list *want, + struct contains_cache *cache) { struct contains_stack contains_stack = { 0, 0, NULL }; - int result = contains_test(candidate, want); + enum contains_result result = contains_test(candidate, want, cache); if (result != CONTAINS_UNKNOWN) return result; @@ -1547,16 +1568,16 @@ static enum contains_result contains_tag_algo(struct commit *candidate, struct commit_list *parents = entry->parents; if (!parents) { - commit->object.flags |= UNINTERESTING; + *contains_cache_at(cache, commit) = CONTAINS_NO; contains_stack.nr--; } /* * If we just popped the stack, parents->item has been marked, - * therefore contains_test will return a meaningful 0 or 1. + * therefore contains_test will return a meaningful yes/no. */ - else switch (contains_test(parents->item, want)) { + else switch (contains_test(parents->item, want, cache)) { case CONTAINS_YES: - commit->object.flags |= TMP_MARK; + *contains_cache_at(cache, commit) = CONTAINS_YES; contains_stack.nr--; break; case CONTAINS_NO: @@ -1568,14 +1589,15 @@ static enum contains_result contains_tag_algo(struct commit *candidate, } } free(contains_stack.contains_stack); - return contains_test(candidate, want); + return contains_test(candidate, want, cache); } -static int commit_contains(struct ref_filter *filter, struct commit *commit) +static int commit_contains(struct ref_filter *filter, struct commit *commit, + struct commit_list *list, struct contains_cache *cache) { if (filter->with_commit_tag_algo) - return contains_tag_algo(commit, filter->with_commit); - return is_descendant_of(commit, filter->with_commit); + return contains_tag_algo(commit, list, cache) == CONTAINS_YES; + return is_descendant_of(commit, list); } /* @@ -1602,7 +1624,7 @@ static int match_pattern(const struct ref_filter *filter, const char *refname) skip_prefix(refname, "refs/", &refname)); for (; *patterns; patterns++) { - if (!wildmatch(*patterns, refname, flags, NULL)) + if (!wildmatch(*patterns, refname, flags)) return 1; } return 0; @@ -1633,7 +1655,7 @@ static int match_name_as_path(const struct ref_filter *filter, const char *refna refname[plen] == '/' || p[plen-1] == '/')) return 1; - if (!wildmatch(p, refname, WM_PATHNAME, NULL)) + if (!wildmatch(p, refname, WM_PATHNAME)) return 1; } return 0; @@ -1650,6 +1672,68 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname) } /* + * Find the longest prefix of pattern we can pass to + * `for_each_fullref_in()`, namely the part of pattern preceding the + * first glob character. (Note that `for_each_fullref_in()` is + * perfectly happy working with a prefix that doesn't end at a + * pathname component boundary.) + */ +static void find_longest_prefix(struct strbuf *out, const char *pattern) +{ + const char *p; + + for (p = pattern; *p && !is_glob_special(*p); p++) + ; + + strbuf_add(out, pattern, p - pattern); +} + +/* + * This is the same as for_each_fullref_in(), but it tries to iterate + * only over the patterns we'll care about. Note that it _doesn't_ do a full + * pattern match, so the callback still has to match each ref individually. + */ +static int for_each_fullref_in_pattern(struct ref_filter *filter, + each_ref_fn cb, + void *cb_data, + int broken) +{ + struct strbuf prefix = STRBUF_INIT; + int ret; + + if (!filter->match_as_path) { + /* + * in this case, the patterns are applied after + * prefixes like "refs/heads/" etc. are stripped off, + * so we have to look at everything: + */ + return for_each_fullref_in("", cb, cb_data, broken); + } + + if (!filter->name_patterns[0]) { + /* no patterns; we have to look at everything */ + return for_each_fullref_in("", cb, cb_data, broken); + } + + if (filter->name_patterns[1]) { + /* + * multiple patterns; in theory this could still work as long + * as the patterns are disjoint. We'd just make multiple calls + * to for_each_ref(). But if they're not disjoint, we'd end up + * reporting the same ref multiple times. So let's punt on that + * for now. + */ + return for_each_fullref_in("", cb, cb_data, broken); + } + + find_longest_prefix(&prefix, filter->name_patterns[0]); + + ret = for_each_fullref_in(prefix.buf, cb, cb_data, broken); + strbuf_release(&prefix); + return ret; +} + +/* * Given a ref (sha1, refname), check if the ref belongs to the array * of sha1s. If the given ref is a tag, check if the given tag points * at one of the sha1s in the given sha1 array. @@ -1662,22 +1746,22 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname) * the need to parse the object via parse_object(). peel_ref() might be a * more efficient alternative to obtain the pointee. */ -static const unsigned char *match_points_at(struct sha1_array *points_at, - const unsigned char *sha1, - const char *refname) +static const struct object_id *match_points_at(struct oid_array *points_at, + const struct object_id *oid, + const char *refname) { - const unsigned char *tagged_sha1 = NULL; + const struct object_id *tagged_oid = NULL; struct object *obj; - if (sha1_array_lookup(points_at, sha1) >= 0) - return sha1; - obj = parse_object(sha1); + if (oid_array_lookup(points_at, oid) >= 0) + return oid; + obj = parse_object(oid); if (!obj) die(_("malformed object at '%s'"), refname); if (obj->type == OBJ_TAG) - tagged_sha1 = ((struct tag *)obj)->tagged->oid.hash; - if (tagged_sha1 && sha1_array_lookup(points_at, tagged_sha1) >= 0) - return tagged_sha1; + tagged_oid = &((struct tag *)obj)->tagged->oid; + if (tagged_oid && oid_array_lookup(points_at, tagged_oid) >= 0) + return tagged_oid; return NULL; } @@ -1688,13 +1772,13 @@ static struct ref_array_item *new_ref_array_item(const char *refname, { struct ref_array_item *ref; FLEX_ALLOC_STR(ref, refname, refname); - hashcpy(ref->objectname, objectname); + hashcpy(ref->objectname.hash, objectname); ref->flag = flag; return ref; } -static int filter_ref_kind(struct ref_filter *filter, const char *refname) +static int ref_kind_from_refname(const char *refname) { unsigned int i; @@ -1707,11 +1791,7 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname) { "refs/tags/", FILTER_REFS_TAGS} }; - if (filter->kind == FILTER_REFS_BRANCHES || - filter->kind == FILTER_REFS_REMOTES || - filter->kind == FILTER_REFS_TAGS) - return filter->kind; - else if (!strcmp(refname, "HEAD")) + if (!strcmp(refname, "HEAD")) return FILTER_REFS_DETACHED_HEAD; for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { @@ -1722,6 +1802,15 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname) return FILTER_REFS_OTHERS; } +static int filter_ref_kind(struct ref_filter *filter, const char *refname) +{ + if (filter->kind == FILTER_REFS_BRANCHES || + filter->kind == FILTER_REFS_REMOTES || + filter->kind == FILTER_REFS_TAGS) + return filter->kind; + return ref_kind_from_refname(refname); +} + /* * A call-back given to for_each_ref(). Filter refs and keep them for * later object processing. @@ -1752,7 +1841,7 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, if (!filter_pattern_match(filter, refname)) return 0; - if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname)) + if (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname)) return 0; /* @@ -1760,13 +1849,17 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, * obtain the commit using the 'oid' available and discard all * non-commits early. The actual filtering is done later. */ - if (filter->merge_commit || filter->with_commit || filter->verbose) { - commit = lookup_commit_reference_gently(oid->hash, 1); + if (filter->merge_commit || filter->with_commit || filter->no_commit || filter->verbose) { + commit = lookup_commit_reference_gently(oid, 1); if (!commit) return 0; - /* We perform the filtering for the '--contains' option */ + /* We perform the filtering for the '--contains' option... */ if (filter->with_commit && - !commit_contains(filter, commit)) + !commit_contains(filter, commit, filter->with_commit, &ref_cbdata->contains_cache)) + return 0; + /* ...or for the `--no-contains' option */ + if (filter->no_commit && + commit_contains(filter, commit, filter->no_commit, &ref_cbdata->no_contains_cache)) return 0; } @@ -1798,8 +1891,7 @@ void ref_array_clear(struct ref_array *array) for (i = 0; i < array->nr; i++) free_array_item(array->items[i]); - free(array->items); - array->items = NULL; + FREE_AND_NULL(array->items); array->nr = array->alloc = 0; } @@ -1866,6 +1958,9 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int broken = 1; filter->kind = type & FILTER_REFS_KIND_MASK; + init_contains_cache(&ref_cbdata.contains_cache); + init_contains_cache(&ref_cbdata.no_contains_cache); + /* Simple per-ref filtering */ if (!filter->kind) die("filter_refs: invalid type"); @@ -1883,11 +1978,13 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int else if (filter->kind == FILTER_REFS_TAGS) ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata, broken); else if (filter->kind & FILTER_REFS_ALL) - ret = for_each_fullref_in("", ref_filter_handler, &ref_cbdata, broken); + ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata, broken); if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD)) head_ref(ref_filter_handler, &ref_cbdata); } + clear_contains_cache(&ref_cbdata.contains_cache); + clear_contains_cache(&ref_cbdata.no_contains_cache); /* Filters that need revision walking */ if (filter->merge_commit) @@ -1911,9 +2008,9 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru else if (cmp_type == FIELD_STR) cmp = cmp_fn(va->s, vb->s); else { - if (va->ul < vb->ul) + if (va->value < vb->value) cmp = -1; - else if (va->ul == vb->ul) + else if (va->value == vb->value) cmp = cmp_fn(a->refname, b->refname); else cmp = 1; @@ -1922,8 +2019,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru return (s->reverse) ? -cmp : cmp; } -static struct ref_sorting *ref_sorting; -static int compare_refs(const void *a_, const void *b_) +static int compare_refs(const void *a_, const void *b_, void *ref_sorting) { struct ref_array_item *a = *((struct ref_array_item **)a_); struct ref_array_item *b = *((struct ref_array_item **)b_); @@ -1939,8 +2035,7 @@ static int compare_refs(const void *a_, const void *b_) void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array) { - ref_sorting = sorting; - QSORT(array->items, array->nr, compare_refs); + QSORT_S(array->items, array->nr, compare_refs, sorting); } static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state) @@ -2012,6 +2107,16 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu putchar('\n'); } +void pretty_print_ref(const char *name, const unsigned char *sha1, + const char *format) +{ + struct ref_array_item *ref_item; + ref_item = new_ref_array_item(name, sha1, 0); + ref_item->kind = ref_kind_from_refname(name); + show_ref_array_item(ref_item, format, 0); + free_array_item(ref_item); +} + /* If no sorting option is given, use refname to sort as default */ struct ref_sorting *ref_default_sorting(void) { @@ -2052,16 +2157,25 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset) { struct ref_filter *rf = opt->value; - unsigned char sha1[20]; + struct object_id oid; + int no_merged = starts_with(opt->long_name, "no"); + + if (rf->merge) { + if (no_merged) { + return opterror(opt, "is incompatible with --merged", 0); + } else { + return opterror(opt, "is incompatible with --no-merged", 0); + } + } - rf->merge = starts_with(opt->long_name, "no") + rf->merge = no_merged ? REF_FILTER_MERGED_OMIT : REF_FILTER_MERGED_INCLUDE; - if (get_sha1(arg, sha1)) + if (get_oid(arg, &oid)) die(_("malformed object name %s"), arg); - rf->merge_commit = lookup_commit_reference_gently(sha1, 0); + rf->merge_commit = lookup_commit_reference_gently(&oid, 0); if (!rf->merge_commit) return opterror(opt, "must point to a commit", 0); |