diff options
Diffstat (limited to 'ref-filter.c')
-rw-r--r-- | ref-filter.c | 724 |
1 files changed, 620 insertions, 104 deletions
diff --git a/ref-filter.c b/ref-filter.c index f38dee4f6..7bef7f8da 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -9,6 +9,10 @@ #include "tag.h" #include "quote.h" #include "ref-filter.h" +#include "revision.h" +#include "utf8.h" +#include "git-compat-util.h" +#include "version.h" typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; @@ -43,15 +47,48 @@ static struct { { "subject" }, { "body" }, { "contents" }, - { "contents:subject" }, - { "contents:body" }, - { "contents:signature" }, { "upstream" }, { "push" }, { "symref" }, { "flag" }, { "HEAD" }, { "color" }, + { "align" }, + { "end" }, +}; + +#define REF_FORMATTING_STATE_INIT { 0, NULL } + +struct align { + align_type position; + unsigned int width; +}; + +struct contents { + unsigned int lines; + struct object_id oid; +}; + +struct ref_formatting_stack { + struct ref_formatting_stack *prev; + struct strbuf output; + void (*at_end)(struct ref_formatting_stack *stack); + void *at_end_data; +}; + +struct ref_formatting_state { + int quote_style; + struct ref_formatting_stack *stack; +}; + +struct atom_value { + const char *s; + union { + struct align align; + struct contents contents; + } u; + void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state); + unsigned long ul; /* used for sorting when not FIELD_STR */ }; /* @@ -123,6 +160,120 @@ int parse_ref_filter_atom(const char *atom, const char *ep) return at; } +static void quote_formatting(struct strbuf *s, const char *str, int quote_style) +{ + switch (quote_style) { + case QUOTE_NONE: + strbuf_addstr(s, str); + break; + case QUOTE_SHELL: + sq_quote_buf(s, str); + break; + case QUOTE_PERL: + perl_quote_buf(s, str); + break; + case QUOTE_PYTHON: + python_quote_buf(s, str); + break; + case QUOTE_TCL: + tcl_quote_buf(s, str); + break; + } +} + +static void append_atom(struct atom_value *v, struct ref_formatting_state *state) +{ + /* + * Quote formatting is only done when the stack has a single + * element. Otherwise quote formatting is done on the + * element's entire output strbuf when the %(end) atom is + * encountered. + */ + if (!state->stack->prev) + quote_formatting(&state->stack->output, v->s, state->quote_style); + else + strbuf_addstr(&state->stack->output, v->s); +} + +static void push_stack_element(struct ref_formatting_stack **stack) +{ + struct ref_formatting_stack *s = xcalloc(1, sizeof(struct ref_formatting_stack)); + + strbuf_init(&s->output, 0); + s->prev = *stack; + *stack = s; +} + +static void pop_stack_element(struct ref_formatting_stack **stack) +{ + struct ref_formatting_stack *current = *stack; + struct ref_formatting_stack *prev = current->prev; + + if (prev) + strbuf_addbuf(&prev->output, ¤t->output); + strbuf_release(¤t->output); + free(current); + *stack = prev; +} + +static void end_align_handler(struct ref_formatting_stack *stack) +{ + struct align *align = (struct align *)stack->at_end_data; + struct strbuf s = STRBUF_INIT; + + strbuf_utf8_align(&s, align->position, align->width, stack->output.buf); + strbuf_swap(&stack->output, &s); + strbuf_release(&s); +} + +static void align_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *new; + + push_stack_element(&state->stack); + new = state->stack; + new->at_end = end_align_handler; + new->at_end_data = &atomv->u.align; +} + +static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state) +{ + struct ref_formatting_stack *current = state->stack; + struct strbuf s = STRBUF_INIT; + + if (!current->at_end) + die(_("format: %%(end) atom used without corresponding atom")); + current->at_end(current); + + /* + * Perform quote formatting when the stack element is that of + * a supporting atom. If nested then perform quote formatting + * only on the topmost supporting atom. + */ + if (!state->stack->prev->prev) { + quote_formatting(&s, current->output.buf, state->quote_style); + strbuf_swap(¤t->output, &s); + } + strbuf_release(&s); + pop_stack_element(&state->stack); +} + +static int match_atom_name(const char *name, const char *atom_name, const char **val) +{ + const char *body; + + if (!skip_prefix(name, atom_name, &body)) + return 0; /* doesn't even begin with "atom_name" */ + if (!body[0]) { + *val = NULL; /* %(atom_name) and no customization */ + return 1; + } + if (body[0] != ':') + return 0; /* "atom_namefoo" is not "atom_name" or "atom_name:..." */ + *val = body + 1; /* "atom_name:val" */ + return 1; +} + /* * In a format string, find the next occurrence of %(atom). */ @@ -192,9 +343,7 @@ static int grab_objectname(const char *name, const unsigned char *sha1, struct atom_value *v) { if (!strcmp(name, "objectname")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(sha1)); - v->s = s; + v->s = xstrdup(sha1_to_hex(sha1)); return 1; } if (!strcmp(name, "objectname:short")) { @@ -219,13 +368,11 @@ 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")) { - char *s = xmalloc(40); - sprintf(s, "%lu", sz); v->ul = sz; - v->s = s; + v->s = xstrfmt("%lu", sz); } else if (deref) - grab_objectname(name, obj->sha1, v); + grab_objectname(name, obj->oid.hash, v); } } @@ -246,11 +393,8 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob v->s = tag->tag; else if (!strcmp(name, "type") && tag->tagged) v->s = typename(tag->tagged->type); - else if (!strcmp(name, "object") && tag->tagged) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(tag->tagged->sha1)); - v->s = s; - } + else if (!strcmp(name, "object") && tag->tagged) + v->s = xstrdup(oid_to_hex(&tag->tagged->oid)); } } @@ -268,32 +412,22 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object if (deref) name++; if (!strcmp(name, "tree")) { - char *s = xmalloc(41); - strcpy(s, sha1_to_hex(commit->tree->object.sha1)); - v->s = s; + v->s = xstrdup(oid_to_hex(&commit->tree->object.oid)); } - if (!strcmp(name, "numparent")) { - char *s = xmalloc(40); + else if (!strcmp(name, "numparent")) { v->ul = commit_list_count(commit->parents); - sprintf(s, "%lu", v->ul); - v->s = s; + v->s = xstrfmt("%lu", v->ul); } else if (!strcmp(name, "parent")) { - int num = commit_list_count(commit->parents); - int i; struct commit_list *parents; - char *s = xmalloc(41 * num + 1); - v->s = s; - for (i = 0, parents = commit->parents; - parents; - parents = parents->next, i = i + 41) { + struct strbuf s = STRBUF_INIT; + for (parents = commit->parents; parents; parents = parents->next) { struct commit *parent = parents->item; - strcpy(s+i, sha1_to_hex(parent->object.sha1)); - if (parents->next) - s[i+40] = ' '; + if (parents != commit->parents) + strbuf_addch(&s, ' '); + strbuf_addstr(&s, oid_to_hex(&parent->object.oid)); } - if (!i) - *s = '\0'; + v->s = strbuf_detach(&s, NULL); } } } @@ -497,6 +631,30 @@ static void find_subpos(const char *buf, unsigned long sz, *nonsiglen = *sig - buf; } +/* + * If 'lines' is greater than 0, append that many lines from the given + * 'buf' of length 'size' to the given strbuf. + */ +static void append_lines(struct strbuf *out, const char *buf, unsigned long size, int lines) +{ + int i; + const char *sp, *eol; + size_t len; + + sp = buf; + + for (i = 0; i < lines && sp < buf + size; i++) { + if (i) + strbuf_addstr(out, "\n "); + eol = memchr(sp, '\n', size - (sp - buf)); + len = eol ? eol - sp : size - (sp - buf); + strbuf_add(out, sp, len); + if (!eol) + break; + sp = eol + 1; + } +} + /* See grab_values */ static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { @@ -507,6 +665,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &val[i]; + const char *valp = NULL; if (!!deref != (*name == '*')) continue; if (deref) @@ -516,7 +675,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj strcmp(name, "contents") && strcmp(name, "contents:subject") && strcmp(name, "contents:body") && - strcmp(name, "contents:signature")) + strcmp(name, "contents:signature") && + !starts_with(name, "contents:lines=")) continue; if (!subpos) find_subpos(buf, sz, @@ -536,6 +696,16 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj v->s = xmemdupz(sigpos, siglen); else if (!strcmp(name, "contents")) v->s = xstrdup(subpos); + else if (skip_prefix(name, "contents:lines=", &valp)) { + struct strbuf s = STRBUF_INIT; + const char *contents_end = bodylen + bodypos - siglen; + + if (strtoul_ui(valp, 10, &v->u.contents.lines)) + die(_("positive value expected contents:lines=%s"), valp); + /* Size is the length of the message after removing the signature */ + append_lines(&s, subpos, contents_end - subpos, v->u.contents.lines); + v->s = strbuf_detach(&s, NULL); + } } } @@ -621,8 +791,11 @@ static void populate_value(struct ref_array_item *ref) int deref = 0; const char *refname; const char *formatp; + const char *valp; struct branch *branch = NULL; + v->handler = append_atom; + if (*name == '*') { deref = 1; name++; @@ -653,10 +826,12 @@ static void populate_value(struct ref_array_item *ref) refname = branch_get_push(branch, NULL); if (!refname) continue; - } else if (starts_with(name, "color:")) { + } else if (match_atom_name(name, "color", &valp)) { char color[COLOR_MAXLEN] = ""; - if (color_parse(name + 6, color) < 0) + if (!valp) + die(_("expected format: %%(color:<color>)")); + if (color_parse(valp, color) < 0) die(_("unable to parse format")); v->s = xstrdup(color); continue; @@ -686,6 +861,48 @@ static void populate_value(struct ref_array_item *ref) else v->s = " "; continue; + } else if (match_atom_name(name, "align", &valp)) { + struct align *align = &v->u.align; + struct strbuf **s, **to_free; + int width = -1; + + if (!valp) + die(_("expected format: %%(align:<width>,<position>)")); + + /* + * TODO: Implement a function similar to strbuf_split_str() + * which would omit the separator from the end of each value. + */ + s = to_free = strbuf_split_str(valp, ',', 0); + + align->position = ALIGN_LEFT; + + while (*s) { + /* Strip trailing comma */ + if (s[1]) + strbuf_setlen(s[0], s[0]->len - 1); + if (!strtoul_ui(s[0]->buf, 10, (unsigned int *)&width)) + ; + else if (!strcmp(s[0]->buf, "left")) + align->position = ALIGN_LEFT; + else if (!strcmp(s[0]->buf, "right")) + align->position = ALIGN_RIGHT; + else if (!strcmp(s[0]->buf, "middle")) + align->position = ALIGN_MIDDLE; + else + die(_("improper format entered align:%s"), s[0]->buf); + s++; + } + + if (width < 0) + die(_("positive width expected with the %%(align) atom")); + align->width = width; + strbuf_list_free(to_free); + v->handler = align_atom_handler; + continue; + } else if (!strcmp(name, "end")) { + v->handler = end_atom_handler; + continue; } else continue; @@ -700,7 +917,6 @@ static void populate_value(struct ref_array_item *ref) else if (!strcmp(formatp, "track") && (starts_with(name, "upstream") || starts_with(name, "push"))) { - char buf[40]; if (stat_tracking_info(branch, &num_ours, &num_theirs, NULL)) @@ -708,17 +924,13 @@ static void populate_value(struct ref_array_item *ref) if (!num_ours && !num_theirs) v->s = ""; - else if (!num_ours) { - sprintf(buf, "[behind %d]", num_theirs); - v->s = xstrdup(buf); - } else if (!num_theirs) { - sprintf(buf, "[ahead %d]", num_ours); - v->s = xstrdup(buf); - } else { - sprintf(buf, "[ahead %d, behind %d]", - num_ours, num_theirs); - v->s = xstrdup(buf); - } + else if (!num_ours) + v->s = xstrfmt("[behind %d]", num_theirs); + else if (!num_theirs) + v->s = xstrfmt("[ahead %d]", num_ours); + else + v->s = xstrfmt("[ahead %d, behind %d]", + num_ours, num_theirs); continue; } else if (!strcmp(formatp, "trackshort") && (starts_with(name, "upstream") || @@ -745,12 +957,8 @@ static void populate_value(struct ref_array_item *ref) if (!deref) v->s = refname; - else { - int len = strlen(refname); - char *s = xmalloc(len + 4); - sprintf(s, "%s^{}", refname); - v->s = s; - } + else + v->s = xstrfmt("%s^{}", refname); } for (i = 0; i < used_atom_cnt; i++) { @@ -784,7 +992,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->sha1; + tagged = ((struct tag *)obj)->tagged->oid.hash; /* * NEEDSWORK: This derefs tag only once, which @@ -817,11 +1025,143 @@ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom *v = &ref->value[atom]; } +enum contains_result { + CONTAINS_UNKNOWN = -1, + CONTAINS_NO = 0, + CONTAINS_YES = 1 +}; + +/* + * Mimicking the real stack, this stack lives on the heap, avoiding stack + * overflows. + * + * At each recursion step, the stack items points to the commits whose + * ancestors are to be inspected. + */ +struct contains_stack { + int nr, alloc; + struct contains_stack_entry { + struct commit *commit; + struct commit_list *parents; + } *contains_stack; +}; + +static int in_commit_list(const struct commit_list *want, struct commit *c) +{ + for (; want; want = want->next) + if (!oidcmp(&want->item->object.oid, &c->object.oid)) + return 1; + return 0; +} + +/* + * Test whether the candidate or one of its parents is contained in the list. + * 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) +{ + /* 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; + /* or are we it? */ + if (in_commit_list(want, candidate)) { + candidate->object.flags |= TMP_MARK; + return 1; + } + + if (parse_commit(candidate) < 0) + return 0; + + return -1; +} + +static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack) +{ + ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc); + contains_stack->contains_stack[contains_stack->nr].commit = candidate; + contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents; +} + +static enum contains_result contains_tag_algo(struct commit *candidate, + const struct commit_list *want) +{ + struct contains_stack contains_stack = { 0, 0, NULL }; + int result = contains_test(candidate, want); + + if (result != CONTAINS_UNKNOWN) + return result; + + push_to_contains_stack(candidate, &contains_stack); + while (contains_stack.nr) { + struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1]; + struct commit *commit = entry->commit; + struct commit_list *parents = entry->parents; + + if (!parents) { + commit->object.flags |= UNINTERESTING; + contains_stack.nr--; + } + /* + * If we just popped the stack, parents->item has been marked, + * therefore contains_test will return a meaningful 0 or 1. + */ + else switch (contains_test(parents->item, want)) { + case CONTAINS_YES: + commit->object.flags |= TMP_MARK; + contains_stack.nr--; + break; + case CONTAINS_NO: + entry->parents = parents->next; + break; + case CONTAINS_UNKNOWN: + push_to_contains_stack(parents->item, &contains_stack); + break; + } + } + free(contains_stack.contains_stack); + return contains_test(candidate, want); +} + +static int commit_contains(struct ref_filter *filter, struct commit *commit) +{ + if (filter->with_commit_tag_algo) + return contains_tag_algo(commit, filter->with_commit); + return is_descendant_of(commit, filter->with_commit); +} + +/* + * Return 1 if the refname matches one of the patterns, otherwise 0. + * A pattern can be a literal prefix (e.g. a refname "refs/heads/master" + * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref + * matches "refs/heads/mas*", too). + */ +static int match_pattern(const char **patterns, const char *refname) +{ + /* + * When no '--format' option is given we need to skip the prefix + * for matching refs of tags and branches. + */ + (void)(skip_prefix(refname, "refs/tags/", &refname) || + skip_prefix(refname, "refs/heads/", &refname) || + skip_prefix(refname, "refs/remotes/", &refname) || + skip_prefix(refname, "refs/", &refname)); + + for (; *patterns; patterns++) { + if (!wildmatch(*patterns, refname, 0, NULL)) + return 1; + } + return 0; +} + /* * Return 1 if the refname matches one of the patterns, otherwise 0. * A pattern can be path prefix (e.g. a refname "refs/heads/master" - * matches a pattern "refs/heads/") or a wildcard (e.g. the same ref - * matches "refs/heads/m*",too). + * matches a pattern "refs/heads/" but not "refs/heads/m") or a + * wildcard (e.g. the same ref matches "refs/heads/m*", too). */ static int match_name_as_path(const char **pattern, const char *refname) { @@ -842,6 +1182,48 @@ static int match_name_as_path(const char **pattern, const char *refname) return 0; } +/* Return 1 if the refname matches one of the patterns, otherwise 0. */ +static int filter_pattern_match(struct ref_filter *filter, const char *refname) +{ + if (!*filter->name_patterns) + return 1; /* No pattern always matches */ + if (filter->match_as_path) + return match_name_as_path(filter->name_patterns, refname); + return match_pattern(filter->name_patterns, refname); +} + +/* + * 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. + * the given sha1_array. + * NEEDSWORK: + * 1. Only a single level of inderection is obtained, we might want to + * change this to account for multiple levels (e.g. annotated tags + * pointing to annotated tags pointing to a commit.) + * 2. As the refs are cached we might know what refname peels to without + * 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) +{ + const unsigned char *tagged_sha1 = NULL; + struct object *obj; + + if (sha1_array_lookup(points_at, sha1) >= 0) + return sha1; + obj = parse_object(sha1); + 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; + return NULL; +} + /* Allocate space for a new ref_array_item and copy the objectname and flag to it */ static struct ref_array_item *new_ref_array_item(const char *refname, const unsigned char *objectname, @@ -857,6 +1239,34 @@ static struct ref_array_item *new_ref_array_item(const char *refname, return ref; } +static int filter_ref_kind(struct ref_filter *filter, const char *refname) +{ + unsigned int i; + + static struct { + const char *prefix; + unsigned int kind; + } ref_kind[] = { + { "refs/heads/" , FILTER_REFS_BRANCHES }, + { "refs/remotes/" , FILTER_REFS_REMOTES }, + { "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")) + return FILTER_REFS_DETACHED_HEAD; + + for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { + if (starts_with(refname, ref_kind[i].prefix)) + return ref_kind[i].kind; + } + + return FILTER_REFS_OTHERS; +} + /* * A call-back given to for_each_ref(). Filter refs and keep them for * later object processing. @@ -866,6 +1276,8 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, 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); @@ -877,18 +1289,43 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, return 0; } - if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, refname)) + /* 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; + + if (!filter_pattern_match(filter, refname)) + return 0; + + if (filter->points_at.nr && !match_points_at(&filter->points_at, oid->hash, refname)) return 0; /* + * A merge filter is applied on refs pointing to commits. Hence + * 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 (!commit) + return 0; + /* We perform the filtering for the '--contains' option */ + if (filter->with_commit && + !commit_contains(filter, commit)) + return 0; + } + + /* * We do not open the object yet; sort may only need refname * to do its job and the resulting list may yet to be pruned * by maxcount logic. */ ref = new_ref_array_item(refname, oid->hash, flag); + ref->commit = commit; REALLOC_ARRAY(ref_cbdata->array->items, ref_cbdata->array->nr + 1); ref_cbdata->array->items[ref_cbdata->array->nr++] = ref; + ref->kind = kind; return 0; } @@ -911,6 +1348,50 @@ void ref_array_clear(struct ref_array *array) array->nr = array->alloc = 0; } +static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata) +{ + struct rev_info revs; + int i, old_nr; + struct ref_filter *filter = ref_cbdata->filter; + struct ref_array *array = ref_cbdata->array; + struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr); + + init_revisions(&revs, NULL); + + for (i = 0; i < array->nr; i++) { + struct ref_array_item *item = array->items[i]; + add_pending_object(&revs, &item->commit->object, item->refname); + to_clear[i] = item->commit; + } + + filter->merge_commit->object.flags |= UNINTERESTING; + add_pending_object(&revs, &filter->merge_commit->object, ""); + + revs.limited = 1; + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); + + old_nr = array->nr; + array->nr = 0; + + for (i = 0; i < old_nr; i++) { + struct ref_array_item *item = array->items[i]; + struct commit *commit = item->commit; + + int is_merged = !!(commit->object.flags & UNINTERESTING); + + if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE)) + array->items[array->nr++] = array->items[i]; + else + free_array_item(item); + } + + for (i = 0; i < old_nr; i++) + clear_commit_marks(to_clear[i], ALL_REV_FLAGS); + clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS); + free(to_clear); +} + /* * 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 @@ -920,17 +1401,44 @@ void ref_array_clear(struct ref_array *array) int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type) { struct ref_filter_cbdata ref_cbdata; + int ret = 0; + unsigned int broken = 0; ref_cbdata.array = array; ref_cbdata.filter = filter; - if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN)) - return for_each_rawref(ref_filter_handler, &ref_cbdata); - else if (type & FILTER_REFS_ALL) - return for_each_ref(ref_filter_handler, &ref_cbdata); - else + if (type & FILTER_REFS_INCLUDE_BROKEN) + broken = 1; + filter->kind = type & FILTER_REFS_KIND_MASK; + + /* Simple per-ref filtering */ + if (!filter->kind) die("filter_refs: invalid type"); - return 0; + else { + /* + * For common cases where we need only branches or remotes or tags, + * we only iterate through those refs. If a mix of refs is needed, + * we iterate over all refs and filter out required refs with the help + * of filter_ref_kind(). + */ + if (filter->kind == FILTER_REFS_BRANCHES) + ret = for_each_fullref_in("refs/heads/", ref_filter_handler, &ref_cbdata, broken); + else if (filter->kind == FILTER_REFS_REMOTES) + ret = for_each_fullref_in("refs/remotes/", ref_filter_handler, &ref_cbdata, broken); + 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); + if (!ret && (filter->kind & FILTER_REFS_DETACHED_HEAD)) + head_ref(ref_filter_handler, &ref_cbdata); + } + + + /* Filters that need revision walking */ + if (filter->merge_commit) + do_merge_filter(&ref_cbdata); + + return ret; } static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, struct ref_array_item *b) @@ -941,19 +1449,19 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru get_ref_atom_value(a, s->atom, &va); get_ref_atom_value(b, s->atom, &vb); - switch (cmp_type) { - case FIELD_STR: + if (s->version) + cmp = versioncmp(va->s, vb->s); + else if (cmp_type == FIELD_STR) cmp = strcmp(va->s, vb->s); - break; - default: + else { if (va->ul < vb->ul) cmp = -1; else if (va->ul == vb->ul) - cmp = 0; + cmp = strcmp(a->refname, b->refname); else cmp = 1; - break; } + return (s->reverse) ? -cmp : cmp; } @@ -978,32 +1486,6 @@ void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array) qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs); } -static void print_value(struct atom_value *v, int quote_style) -{ - struct strbuf sb = STRBUF_INIT; - switch (quote_style) { - case QUOTE_NONE: - fputs(v->s, stdout); - break; - case QUOTE_SHELL: - sq_quote_buf(&sb, v->s); - break; - case QUOTE_PERL: - perl_quote_buf(&sb, v->s); - break; - case QUOTE_PYTHON: - python_quote_buf(&sb, v->s); - break; - case QUOTE_TCL: - tcl_quote_buf(&sb, v->s); - break; - } - if (quote_style != QUOTE_NONE) { - fputs(sb.buf, stdout); - strbuf_release(&sb); - } -} - static int hex1(char ch) { if ('0' <= ch && ch <= '9') @@ -1022,8 +1504,10 @@ static int hex2(const char *cp) return -1; } -static void emit(const char *cp, const char *ep) +static void append_literal(const char *cp, const char *ep, struct ref_formatting_state *state) { + struct strbuf *s = &state->stack->output; + while (*cp && (!ep || cp < ep)) { if (*cp == '%') { if (cp[1] == '%') @@ -1031,13 +1515,13 @@ static void emit(const char *cp, const char *ep) else { int ch = hex2(cp + 1); if (0 <= ch) { - putchar(ch); + strbuf_addch(s, ch); cp += 3; continue; } } } - putchar(*cp); + strbuf_addch(s, *cp); cp++; } } @@ -1045,19 +1529,24 @@ static void emit(const char *cp, const char *ep) void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style) { const char *cp, *sp, *ep; + struct strbuf *final_buf; + struct ref_formatting_state state = REF_FORMATTING_STATE_INIT; + + state.quote_style = quote_style; + push_stack_element(&state.stack); for (cp = format; *cp && (sp = find_next(cp)); cp = ep + 1) { struct atom_value *atomv; ep = strchr(sp, ')'); if (cp < sp) - emit(cp, sp); + append_literal(cp, sp, &state); get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv); - print_value(atomv, quote_style); + atomv->handler(atomv, &state); } if (*cp) { sp = cp + strlen(cp); - emit(cp, sp); + append_literal(cp, sp, &state); } if (need_color_reset_at_eol) { struct atom_value resetv; @@ -1066,8 +1555,13 @@ void show_ref_array_item(struct ref_array_item *info, const char *format, int qu if (color_parse("reset", color) < 0) die("BUG: couldn't parse 'reset' as a color"); resetv.s = color; - print_value(&resetv, quote_style); + append_atom(&resetv, &state); } + if (state.stack->prev) + die(_("format: %%(end) atom missing")); + final_buf = &state.stack->output; + fwrite(final_buf->buf, 1, final_buf->len, stdout); + pop_stack_element(&state.stack); putchar('\n'); } @@ -1100,7 +1594,29 @@ int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) s->reverse = 1; arg++; } + if (skip_prefix(arg, "version:", &arg) || + skip_prefix(arg, "v:", &arg)) + s->version = 1; len = strlen(arg); s->atom = parse_ref_filter_atom(arg, arg+len); return 0; } + +int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset) +{ + struct ref_filter *rf = opt->value; + unsigned char sha1[20]; + + rf->merge = starts_with(opt->long_name, "no") + ? REF_FILTER_MERGED_OMIT + : REF_FILTER_MERGED_INCLUDE; + + if (get_sha1(arg, sha1)) + die(_("malformed object name %s"), arg); + + rf->merge_commit = lookup_commit_reference_gently(sha1, 0); + if (!rf->merge_commit) + return opterror(opt, "must point to a commit", 0); + + return 0; +} |