diff options
Diffstat (limited to 'ref-filter.c')
| -rw-r--r-- | ref-filter.c | 432 |
1 files changed, 315 insertions, 117 deletions
diff --git a/ref-filter.c b/ref-filter.c index 9dbc4f71bd..fdaabb5bb4 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -13,6 +13,8 @@ #include "oid-array.h" #include "repository.h" #include "commit.h" +#include "mailmap.h" +#include "ident.h" #include "remote.h" #include "color.h" #include "tag.h" @@ -215,8 +217,16 @@ static struct used_atom { struct { enum { O_SIZE, O_SIZE_DISK } option; } objectsize; - struct email_option { - enum { EO_RAW, EO_TRIM, EO_LOCALPART } option; + struct { + enum { N_RAW, N_MAILMAP } option; + } name_option; + struct { + enum { + EO_RAW = 0, + EO_TRIM = 1<<0, + EO_LOCALPART = 1<<1, + EO_MAILMAP = 1<<2, + } option; } email_option; struct { enum { S_BARE, S_GRADE, S_SIGNER, S_KEY, @@ -549,7 +559,8 @@ static int signature_atom_parser(struct ref_format *format UNUSED, return 0; } -static int trailers_atom_parser(struct ref_format *format, struct used_atom *atom, +static int trailers_atom_parser(struct ref_format *format UNUSED, + struct used_atom *atom, const char *arg, struct strbuf *err) { atom->u.contents.trailer_opts.no_divider = 1; @@ -719,21 +730,55 @@ static int oid_atom_parser(struct ref_format *format UNUSED, return 0; } -static int person_email_atom_parser(struct ref_format *format UNUSED, - struct used_atom *atom, - const char *arg, struct strbuf *err) +static int person_name_atom_parser(struct ref_format *format UNUSED, + struct used_atom *atom, + const char *arg, struct strbuf *err) { if (!arg) - atom->u.email_option.option = EO_RAW; - else if (!strcmp(arg, "trim")) - atom->u.email_option.option = EO_TRIM; - else if (!strcmp(arg, "localpart")) - atom->u.email_option.option = EO_LOCALPART; + atom->u.name_option.option = N_RAW; + else if (!strcmp(arg, "mailmap")) + atom->u.name_option.option = N_MAILMAP; else return err_bad_arg(err, atom->name, arg); return 0; } +static int email_atom_option_parser(struct used_atom *atom, + const char **arg, struct strbuf *err) +{ + if (!*arg) + return EO_RAW; + if (skip_prefix(*arg, "trim", arg)) + return EO_TRIM; + if (skip_prefix(*arg, "localpart", arg)) + return EO_LOCALPART; + if (skip_prefix(*arg, "mailmap", arg)) + return EO_MAILMAP; + return -1; +} + +static int person_email_atom_parser(struct ref_format *format UNUSED, + struct used_atom *atom, + const char *arg, struct strbuf *err) +{ + for (;;) { + int opt = email_atom_option_parser(atom, &arg, err); + const char *bad_arg = arg; + + if (opt < 0) + return err_bad_arg(err, atom->name, bad_arg); + atom->u.email_option.option |= opt; + + if (!arg || !*arg) + break; + if (*arg == ',') + arg++; + else + return err_bad_arg(err, atom->name, bad_arg); + } + return 0; +} + static int refname_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom, const char *arg, struct strbuf *err) @@ -821,7 +866,7 @@ static int if_atom_parser(struct ref_format *format UNUSED, return 0; } -static int rest_atom_parser(struct ref_format *format, +static int rest_atom_parser(struct ref_format *format UNUSED, struct used_atom *atom UNUSED, const char *arg, struct strbuf *err) { @@ -830,7 +875,8 @@ static int rest_atom_parser(struct ref_format *format, return 0; } -static int ahead_behind_atom_parser(struct ref_format *format, struct used_atom *atom, +static int ahead_behind_atom_parser(struct ref_format *format, + struct used_atom *atom UNUSED, const char *arg, struct strbuf *err) { struct string_list_item *item; @@ -875,15 +921,15 @@ static struct { [ATOM_TYPE] = { "type", SOURCE_OBJ }, [ATOM_TAG] = { "tag", SOURCE_OBJ }, [ATOM_AUTHOR] = { "author", SOURCE_OBJ }, - [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ }, + [ATOM_AUTHORNAME] = { "authorname", SOURCE_OBJ, FIELD_STR, person_name_atom_parser }, [ATOM_AUTHOREMAIL] = { "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser }, [ATOM_AUTHORDATE] = { "authordate", SOURCE_OBJ, FIELD_TIME }, [ATOM_COMMITTER] = { "committer", SOURCE_OBJ }, - [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ }, + [ATOM_COMMITTERNAME] = { "committername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser }, [ATOM_COMMITTEREMAIL] = { "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser }, [ATOM_COMMITTERDATE] = { "committerdate", SOURCE_OBJ, FIELD_TIME }, [ATOM_TAGGER] = { "tagger", SOURCE_OBJ }, - [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ }, + [ATOM_TAGGERNAME] = { "taggername", SOURCE_OBJ, FIELD_STR, person_name_atom_parser }, [ATOM_TAGGEREMAIL] = { "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser }, [ATOM_TAGGERDATE] = { "taggerdate", SOURCE_OBJ, FIELD_TIME }, [ATOM_CREATOR] = { "creator", SOURCE_OBJ }, @@ -1484,32 +1530,49 @@ static const char *copy_name(const char *buf) return xstrdup(""); } +static const char *find_end_of_email(const char *email, int opt) +{ + const char *eoemail; + + if (opt & EO_LOCALPART) { + eoemail = strchr(email, '@'); + if (eoemail) + return eoemail; + return strchr(email, '>'); + } + + if (opt & EO_TRIM) + return strchr(email, '>'); + + /* + * The option here is either the raw email option or the raw + * mailmap option (that is EO_RAW or EO_MAILMAP). In such cases, + * we directly grab the whole email including the closing + * angle brackets. + * + * If EO_MAILMAP was set with any other option (that is either + * EO_TRIM or EO_LOCALPART), we already grab the end of email + * above. + */ + eoemail = strchr(email, '>'); + if (eoemail) + eoemail++; + return eoemail; +} + static const char *copy_email(const char *buf, struct used_atom *atom) { const char *email = strchr(buf, '<'); const char *eoemail; + int opt = atom->u.email_option.option; + if (!email) return xstrdup(""); - switch (atom->u.email_option.option) { - case EO_RAW: - eoemail = strchr(email, '>'); - if (eoemail) - eoemail++; - break; - case EO_TRIM: - email++; - eoemail = strchr(email, '>'); - break; - case EO_LOCALPART: + + if (opt & (EO_LOCALPART | EO_TRIM)) email++; - eoemail = strchr(email, '@'); - if (!eoemail) - eoemail = strchr(email, '>'); - break; - default: - BUG("unknown email option"); - } + eoemail = find_end_of_email(email, opt); if (!eoemail) return xstrdup(""); return xmemdupz(email, eoemail - email); @@ -1570,16 +1633,23 @@ static void grab_date(const char *buf, struct atom_value *v, const char *atomnam v->value = 0; } +static struct string_list mailmap = STRING_LIST_INIT_NODUP; + /* See grab_values */ static void grab_person(const char *who, struct atom_value *val, int deref, void *buf) { int i; int wholen = strlen(who); const char *wholine = NULL; + const char *headers[] = { "author ", "committer ", + "tagger ", NULL }; for (i = 0; i < used_atom_cnt; i++) { - const char *name = used_atom[i].name; + struct used_atom *atom = &used_atom[i]; + const char *name = atom->name; struct atom_value *v = &val[i]; + struct strbuf mailmap_buf = STRBUF_INIT; + if (!!deref != (*name == '*')) continue; if (deref) @@ -1587,22 +1657,36 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void if (strncmp(who, name, wholen)) continue; if (name[wholen] != 0 && - strcmp(name + wholen, "name") && + !starts_with(name + wholen, "name") && !starts_with(name + wholen, "email") && !starts_with(name + wholen, "date")) continue; - if (!wholine) + + if ((starts_with(name + wholen, "name") && + (atom->u.name_option.option == N_MAILMAP)) || + (starts_with(name + wholen, "email") && + (atom->u.email_option.option & EO_MAILMAP))) { + if (!mailmap.items) + read_mailmap(&mailmap); + strbuf_addstr(&mailmap_buf, buf); + apply_mailmap_to_header(&mailmap_buf, headers, &mailmap); + wholine = find_wholine(who, wholen, mailmap_buf.buf); + } else { wholine = find_wholine(who, wholen, buf); + } + if (!wholine) return; /* no point looking for it */ if (name[wholen] == 0) v->s = copy_line(wholine); - else if (!strcmp(name + wholen, "name")) + else if (starts_with(name + wholen, "name")) v->s = copy_name(wholine); else if (starts_with(name + wholen, "email")) v->s = copy_email(wholine, &used_atom[i]); else if (starts_with(name + wholen, "date")) grab_date(wholine, v, name); + + strbuf_release(&mailmap_buf); } /* @@ -2424,17 +2508,12 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err) return 0; /* - * 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. + * If it is a tag object, see if we use the peeled value. If we do, + * grab the peeled OID. */ - oi_deref.oid = *get_tagged_oid((struct tag *)obj); + if (need_tagged && peel_iterated_oid(&obj->oid, &oi_deref.oid)) + die("bad tag"); - /* - * NEEDSWORK: This derefs tag only once, which - * is good to deal with chains of trust, but - * is not consistent with what deref_tag() does - * which peels the onion to the core. - */ return get_object(ref, 1, &obj, &oi_deref, err); } @@ -2632,15 +2711,18 @@ static struct ref_array_item *new_ref_array_item(const char *refname, return ref; } +static void ref_array_append(struct ref_array *array, struct ref_array_item *ref) +{ + ALLOC_GROW(array->items, array->nr + 1, array->alloc); + array->items[array->nr++] = ref; +} + struct ref_array_item *ref_array_push(struct ref_array *array, const char *refname, const struct object_id *oid) { struct ref_array_item *ref = new_ref_array_item(refname, oid); - - ALLOC_GROW(array->items, array->nr + 1, array->alloc); - array->items[array->nr++] = ref; - + ref_array_append(array, ref); return ref; } @@ -2677,48 +2759,36 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname) return ref_kind_from_refname(refname); } -struct ref_filter_cbdata { - struct ref_array *array; - struct ref_filter *filter; - struct contains_cache contains_cache; - struct contains_cache no_contains_cache; -}; - -/* - * A call-back given to for_each_ref(). Filter refs and keep them for - * later object processing. - */ -static int ref_filter_handler(const char *refname, const struct object_id *oid, int flag, void *cb_data) +static struct ref_array_item *apply_ref_filter(const char *refname, const struct object_id *oid, + int flag, struct ref_filter *filter) { - struct ref_filter_cbdata *ref_cbdata = cb_data; - struct ref_filter *filter = ref_cbdata->filter; struct ref_array_item *ref; struct commit *commit = NULL; unsigned int kind; if (flag & REF_BAD_NAME) { warning(_("ignoring ref with broken name %s"), refname); - return 0; + return NULL; } if (flag & REF_ISBROKEN) { warning(_("ignoring broken ref %s"), refname); - return 0; + return NULL; } /* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */ kind = filter_ref_kind(filter, refname); if (!(kind & filter->kind)) - return 0; + return NULL; if (!filter_pattern_match(filter, refname)) - return 0; + return NULL; if (filter_exclude_match(filter, refname)) - return 0; + return NULL; if (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname)) - return 0; + return NULL; /* * A merge filter is applied on refs pointing to commits. Hence @@ -2729,15 +2799,15 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, filter->with_commit || filter->no_commit || filter->verbose) { commit = lookup_commit_reference_gently(the_repository, oid, 1); if (!commit) - return 0; + return NULL; /* We perform the filtering for the '--contains' option... */ if (filter->with_commit && - !commit_contains(filter, commit, filter->with_commit, &ref_cbdata->contains_cache)) - return 0; + !commit_contains(filter, commit, filter->with_commit, &filter->internal.contains_cache)) + return NULL; /* ...or for the `--no-contains' option */ if (filter->no_commit && - commit_contains(filter, commit, filter->no_commit, &ref_cbdata->no_contains_cache)) - return 0; + commit_contains(filter, commit, filter->no_commit, &filter->internal.no_contains_cache)) + return NULL; } /* @@ -2745,11 +2815,32 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, * to do its job and the resulting list may yet to be pruned * by maxcount logic. */ - ref = ref_array_push(ref_cbdata->array, refname, oid); + ref = new_ref_array_item(refname, oid); ref->commit = commit; ref->flag = flag; ref->kind = kind; + return ref; +} + +struct ref_filter_cbdata { + struct ref_array *array; + struct ref_filter *filter; +}; + +/* + * A call-back given to for_each_ref(). Filter refs and keep them for + * later object processing. + */ +static int filter_one(const char *refname, const struct object_id *oid, int flag, void *cb_data) +{ + struct ref_filter_cbdata *ref_cbdata = cb_data; + struct ref_array_item *ref; + + ref = apply_ref_filter(refname, oid, flag, ref_cbdata->filter); + if (ref) + ref_array_append(ref_cbdata->array, ref); + return 0; } @@ -2767,6 +2858,49 @@ static void free_array_item(struct ref_array_item *item) free(item); } +struct ref_filter_and_format_cbdata { + struct ref_filter *filter; + struct ref_format *format; + + struct ref_filter_and_format_internal { + int count; + } internal; +}; + +static int filter_and_format_one(const char *refname, const struct object_id *oid, int flag, void *cb_data) +{ + struct ref_filter_and_format_cbdata *ref_cbdata = cb_data; + struct ref_array_item *ref; + struct strbuf output = STRBUF_INIT, err = STRBUF_INIT; + + ref = apply_ref_filter(refname, oid, flag, ref_cbdata->filter); + if (!ref) + return 0; + + if (format_ref_array_item(ref, ref_cbdata->format, &output, &err)) + die("%s", err.buf); + + if (output.len || !ref_cbdata->format->array_opts.omit_empty) { + fwrite(output.buf, 1, output.len, stdout); + putchar('\n'); + } + + strbuf_release(&output); + strbuf_release(&err); + free_array_item(ref); + + /* + * Increment the running count of refs that match the filter. If + * max_count is set and we've reached the max, stop the ref + * iteration by returning a nonzero value. + */ + if (ref_cbdata->format->array_opts.max_count && + ++ref_cbdata->internal.count >= ref_cbdata->format->array_opts.max_count) + return 1; + + return 0; +} + /* Free all memory allocated for ref_array */ void ref_array_clear(struct ref_array *array) { @@ -2885,28 +3019,14 @@ void filter_ahead_behind(struct repository *r, free(commits); } -/* - * API for filtering a set of refs. Based on the type of refs the user - * has requested, we iterate through those refs and apply filters - * as per the given ref_filter structure and finally store the - * filtered refs in the ref_array structure. - */ -int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type) +static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data) { - struct ref_filter_cbdata ref_cbdata; - int save_commit_buffer_orig; int ret = 0; - ref_cbdata.array = array; - ref_cbdata.filter = filter; - filter->kind = type & FILTER_REFS_KIND_MASK; - save_commit_buffer_orig = save_commit_buffer; - save_commit_buffer = 0; - - init_contains_cache(&ref_cbdata.contains_cache); - init_contains_cache(&ref_cbdata.no_contains_cache); + init_contains_cache(&filter->internal.contains_cache); + init_contains_cache(&filter->internal.no_contains_cache); /* Simple per-ref filtering */ if (!filter->kind) @@ -2919,19 +3039,42 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int * of filter_ref_kind(). */ if (filter->kind == FILTER_REFS_BRANCHES) - ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata); + ret = for_each_fullref_in("refs/heads/", fn, cb_data); else if (filter->kind == FILTER_REFS_REMOTES) - ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata); + ret = for_each_fullref_in("refs/remotes/", fn, cb_data); else if (filter->kind == FILTER_REFS_TAGS) - ret = for_each_fullref_in("refs/tags/", ref_filter_handler, &ref_cbdata); + ret = for_each_fullref_in("refs/tags/", fn, cb_data); else if (filter->kind & FILTER_REFS_ALL) - ret = for_each_fullref_in_pattern(filter, ref_filter_handler, &ref_cbdata); + ret = for_each_fullref_in_pattern(filter, fn, cb_data); if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD)) - head_ref(ref_filter_handler, &ref_cbdata); + head_ref(fn, cb_data); } - clear_contains_cache(&ref_cbdata.contains_cache); - clear_contains_cache(&ref_cbdata.no_contains_cache); + clear_contains_cache(&filter->internal.contains_cache); + clear_contains_cache(&filter->internal.no_contains_cache); + + return ret; +} + +/* + * API for filtering a set of refs. Based on the type of refs the user + * has requested, we iterate through those refs and apply filters + * as per the given ref_filter structure and finally store the + * filtered refs in the ref_array structure. + */ +int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type) +{ + struct ref_filter_cbdata ref_cbdata; + int save_commit_buffer_orig; + int ret = 0; + + ref_cbdata.array = array; + ref_cbdata.filter = filter; + + save_commit_buffer_orig = save_commit_buffer; + save_commit_buffer = 0; + + ret = do_filter_refs(filter, type, filter_one, &ref_cbdata); /* Filters that need revision walking */ reach_filter(array, &filter->reachable_from, INCLUDE_REACHED); @@ -2941,6 +3084,51 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int return ret; } +static inline int can_do_iterative_format(struct ref_filter *filter, + struct ref_sorting *sorting, + struct ref_format *format) +{ + /* + * Filtering & formatting results within a single ref iteration + * callback is not compatible with options that require + * post-processing a filtered ref_array. These include: + * - filtering on reachability + * - sorting the filtered results + * - including ahead-behind information in the formatted output + */ + return !(filter->reachable_from || + filter->unreachable_from || + sorting || + format->bases.nr); +} + +void filter_and_format_refs(struct ref_filter *filter, unsigned int type, + struct ref_sorting *sorting, + struct ref_format *format) +{ + if (can_do_iterative_format(filter, sorting, format)) { + int save_commit_buffer_orig; + struct ref_filter_and_format_cbdata ref_cbdata = { + .filter = filter, + .format = format, + }; + + save_commit_buffer_orig = save_commit_buffer; + save_commit_buffer = 0; + + do_filter_refs(filter, type, filter_and_format_one, &ref_cbdata); + + save_commit_buffer = save_commit_buffer_orig; + } else { + struct ref_array array = { 0 }; + filter_refs(&array, filter, type); + filter_ahead_behind(the_repository, format, &array); + ref_array_sort(sorting, &array); + print_formatted_ref_array(&array, format); + ref_array_clear(&array); + } +} + static int compare_detached_head(struct ref_array_item *a, struct ref_array_item *b) { if (!(a->kind ^ b->kind)) @@ -3058,7 +3246,8 @@ void ref_sorting_set_sort_flags_all(struct ref_sorting *sorting, void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array) { - QSORT_S(array->items, array->nr, compare_refs, sorting); + if (sorting) + QSORT_S(array->items, array->nr, compare_refs, sorting); } static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state) @@ -3129,6 +3318,29 @@ int format_ref_array_item(struct ref_array_item *info, return 0; } +void print_formatted_ref_array(struct ref_array *array, struct ref_format *format) +{ + int total; + struct strbuf output = STRBUF_INIT, err = STRBUF_INIT; + + total = format->array_opts.max_count; + if (!total || array->nr < total) + total = array->nr; + for (int i = 0; i < total; i++) { + strbuf_reset(&err); + strbuf_reset(&output); + if (format_ref_array_item(array->items[i], format, &output, &err)) + die("%s", err.buf); + if (output.len || !format->array_opts.omit_empty) { + fwrite(output.buf, 1, output.len, stdout); + putchar('\n'); + } + } + + strbuf_release(&err); + strbuf_release(&output); +} + void pretty_print_ref(const char *name, const struct object_id *oid, struct ref_format *format) { @@ -3164,18 +3376,6 @@ static int parse_sorting_atom(const char *atom) return res; } -/* If no sorting option is given, use refname to sort as default */ -static struct ref_sorting *ref_default_sorting(void) -{ - static const char cstr_name[] = "refname"; - - struct ref_sorting *sorting = xcalloc(1, sizeof(*sorting)); - - sorting->next = NULL; - sorting->atom = parse_sorting_atom(cstr_name); - return sorting; -} - static void parse_ref_sorting(struct ref_sorting **sorting_tail, const char *arg) { struct ref_sorting *s; @@ -3199,9 +3399,7 @@ struct ref_sorting *ref_sorting_options(struct string_list *options) struct string_list_item *item; struct ref_sorting *sorting = NULL, **tail = &sorting; - if (!options->nr) { - sorting = ref_default_sorting(); - } else { + if (options->nr) { for_each_string_list_item(item, options) parse_ref_sorting(tail, item->string); } |
