#include "builtin.h" #include "cache.h" #include "parse-options.h" #include "refs.h" #include "wildmatch.h" #include "commit.h" #include "remote.h" #include "color.h" #include "tag.h" #include "quote.h" #include "ref-filter.h" #include "revision.h" typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type; static struct { const char *name; cmp_type cmp_type; } valid_atom[] = { { "refname" }, { "objecttype" }, { "objectsize", FIELD_ULONG }, { "objectname" }, { "tree" }, { "parent" }, { "numparent", FIELD_ULONG }, { "object" }, { "type" }, { "tag" }, { "author" }, { "authorname" }, { "authoremail" }, { "authordate", FIELD_TIME }, { "committer" }, { "committername" }, { "committeremail" }, { "committerdate", FIELD_TIME }, { "tagger" }, { "taggername" }, { "taggeremail" }, { "taggerdate", FIELD_TIME }, { "creator" }, { "creatordate", FIELD_TIME }, { "subject" }, { "body" }, { "contents" }, { "contents:subject" }, { "contents:body" }, { "contents:signature" }, { "upstream" }, { "push" }, { "symref" }, { "flag" }, { "HEAD" }, { "color" }, }; #define REF_FORMATTING_STATE_INIT { 0, NULL } struct ref_formatting_stack { struct ref_formatting_stack *prev; struct strbuf output; }; struct ref_formatting_state { int quote_style; struct ref_formatting_stack *stack; }; struct atom_value { const char *s; unsigned long ul; /* used for sorting when not FIELD_STR */ }; /* * An atom is a valid field atom listed above, possibly prefixed with * a "*" to denote deref_tag(). * * We parse given format string and sort specifiers, and make a list * of properties that we need to extract out of objects. ref_array_item * structure will hold an array of values extracted that can be * indexed with the "atom number", which is an index into this * array. */ static const char **used_atom; static cmp_type *used_atom_type; static int used_atom_cnt, need_tagged, need_symref; static int need_color_reset_at_eol; /* * Used to parse format string and sort specifiers */ int parse_ref_filter_atom(const char *atom, const char *ep) { const char *sp; int i, at; sp = atom; if (*sp == '*' && sp < ep) sp++; /* deref */ if (ep <= sp) die("malformed field name: %.*s", (int)(ep-atom), atom); /* Do we have the atom already used elsewhere? */ for (i = 0; i < used_atom_cnt; i++) { int len = strlen(used_atom[i]); if (len == ep - atom && !memcmp(used_atom[i], atom, len)) return i; } /* Is the atom a valid one? */ for (i = 0; i < ARRAY_SIZE(valid_atom); i++) { int len = strlen(valid_atom[i].name); /* * If the atom name has a colon, strip it and everything after * it off - it specifies the format for this entry, and * shouldn't be used for checking against the valid_atom * table. */ const char *formatp = strchr(sp, ':'); if (!formatp || ep < formatp) formatp = ep; if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len)) break; } if (ARRAY_SIZE(valid_atom) <= i) die("unknown field name: %.*s", (int)(ep-atom), atom); /* Add it in, including the deref prefix */ at = used_atom_cnt; used_atom_cnt++; REALLOC_ARRAY(used_atom, used_atom_cnt); REALLOC_ARRAY(used_atom_type, used_atom_cnt); used_atom[at] = xmemdupz(atom, ep - atom); used_atom_type[at] = valid_atom[i].cmp_type; if (*atom == '*') need_tagged = 1; if (!strcmp(used_atom[at], "symref")) need_symref = 1; return at; } 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; } /* * In a format string, find the next occurrence of %(atom). */ static const char *find_next(const char *cp) { while (*cp) { if (*cp == '%') { /* * %( is the start of an atom; * %% is a quoted per-cent. */ if (cp[1] == '(') return cp; else if (cp[1] == '%') cp++; /* skip over two % */ /* otherwise this is a singleton, literal % */ } cp++; } return NULL; } /* * Make sure the format string is well formed, and parse out * the used atoms. */ int verify_ref_format(const char *format) { const char *cp, *sp; need_color_reset_at_eol = 0; for (cp = format; *cp && (sp = find_next(cp)); ) { const char *color, *ep = strchr(sp, ')'); int at; if (!ep) return error("malformed format string %s", sp); /* sp points at "%(" and ep points at the closing ")" */ at = parse_ref_filter_atom(sp + 2, ep); cp = ep + 1; if (skip_prefix(used_atom[at], "color:", &color)) need_color_reset_at_eol = !!strcmp(color, "reset"); } return 0; } /* * Given an object name, read the object data and size, and return a * "struct object". If the object data we are returning is also borrowed * 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) { enum object_type type; void *buf = read_sha1_file(sha1, &type, sz); if (buf) *obj = parse_object_buffer(sha1, type, *sz, buf, eaten); else *obj = NULL; return buf; } 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; return 1; } if (!strcmp(name, "objectname:short")) { v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); return 1; } return 0; } /* See grab_values */ static void grab_common_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { int i; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &val[i]; if (!!deref != (*name == '*')) continue; if (deref) name++; 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; } else if (deref) grab_objectname(name, obj->sha1, v); } } /* See grab_values */ static void grab_tag_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { int i; struct tag *tag = (struct tag *) obj; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &val[i]; if (!!deref != (*name == '*')) continue; if (deref) name++; if (!strcmp(name, "tag")) 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; } } } /* See grab_values */ static void grab_commit_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { int i; struct commit *commit = (struct commit *) obj; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &val[i]; if (!!deref != (*name == '*')) continue; if (deref) name++; if (!strcmp(name, "tree")) { char *s = xmalloc(41); strcpy(s, sha1_to_hex(commit->tree->object.sha1)); v->s = s; } if (!strcmp(name, "numparent")) { char *s = xmalloc(40); v->ul = commit_list_count(commit->parents); sprintf(s, "%lu", v->ul); v->s = s; } 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 commit *parent = parents->item; strcpy(s+i, sha1_to_hex(parent->object.sha1)); if (parents->next) s[i+40] = ' '; } if (!i) *s = '\0'; } } } static const char *find_wholine(const char *who, int wholen, const char *buf, unsigned long sz) { const char *eol; while (*buf) { if (!strncmp(buf, who, wholen) && buf[wholen] == ' ') return buf + wholen + 1; eol = strchr(buf, '\n'); if (!eol) return ""; eol++; if (*eol == '\n') return ""; /* end of header */ buf = eol; } return ""; } static const char *copy_line(const char *buf) { const char *eol = strchrnul(buf, '\n'); return xmemdupz(buf, eol - buf); } static const char *copy_name(const char *buf) { const char *cp; for (cp = buf; *cp && *cp != '\n'; cp++) { if (!strncmp(cp, " <", 2)) return xmemdupz(buf, cp - buf); } return ""; } static const char *copy_email(const char *buf) { const char *email = strchr(buf, '<'); const char *eoemail; if (!email) return ""; eoemail = strchr(email, '>'); if (!eoemail) return ""; return xmemdupz(email, eoemail + 1 - email); } static char *copy_subject(const char *buf, unsigned long len) { char *r = xmemdupz(buf, len); int i; for (i = 0; i < len; i++) if (r[i] == '\n') r[i] = ' '; return r; } static void grab_date(const char *buf, struct atom_value *v, const char *atomname) { const char *eoemail = strstr(buf, "> "); char *zone; unsigned long timestamp; long tz; struct date_mode date_mode = { DATE_NORMAL }; const char *formatp; /* * We got here because atomname ends in "date" or "date"; * it's not possible that is not ":" because * parse_ref_filter_atom() wouldn't have allowed it, so we can assume that no * ":" means no format is specified, and use the default. */ formatp = strchr(atomname, ':'); if (formatp != NULL) { formatp++; parse_date_format(formatp, &date_mode); } if (!eoemail) goto bad; timestamp = strtoul(eoemail + 2, &zone, 10); if (timestamp == ULONG_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; return; bad: v->s = ""; v->ul = 0; } /* See grab_values */ static void grab_person(const char *who, struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { int i; int wholen = strlen(who); const char *wholine = NULL; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &val[i]; if (!!deref != (*name == '*')) continue; if (deref) name++; if (strncmp(who, name, wholen)) continue; if (name[wholen] != 0 && strcmp(name + wholen, "name") && strcmp(name + wholen, "email") && !starts_with(name + wholen, "date")) continue; if (!wholine) wholine = find_wholine(who, wholen, buf, sz); if (!wholine) return; /* no point looking for it */ if (name[wholen] == 0) v->s = copy_line(wholine); else if (!strcmp(name + wholen, "name")) v->s = copy_name(wholine); else if (!strcmp(name + wholen, "email")) v->s = copy_email(wholine); else if (starts_with(name + wholen, "date")) grab_date(wholine, v, name); } /* * For a tag or a commit object, if "creator" or "creatordate" is * requested, do something special. */ if (strcmp(who, "tagger") && strcmp(who, "committer")) return; /* "author" for commit object is not wanted */ if (!wholine) wholine = find_wholine(who, wholen, buf, sz); if (!wholine) return; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &val[i]; if (!!deref != (*name == '*')) continue; if (deref) name++; if (starts_with(name, "creatordate")) grab_date(wholine, v, name); else if (!strcmp(name, "creator")) v->s = copy_line(wholine); } } static void find_subpos(const char *buf, unsigned long sz, const char **sub, unsigned long *sublen, const char **body, unsigned long *bodylen, unsigned long *nonsiglen, const char **sig, unsigned long *siglen) { const char *eol; /* skip past header until we hit empty line */ while (*buf && *buf != '\n') { eol = strchrnul(buf, '\n'); if (*eol) eol++; buf = eol; } /* skip any empty lines */ while (*buf == '\n') buf++; /* parse signature first; we might not even have a subject line */ *sig = buf + parse_signature(buf, strlen(buf)); *siglen = strlen(*sig); /* subject is first non-empty line */ *sub = buf; /* subject goes to first empty line */ while (buf < *sig && *buf && *buf != '\n') { eol = strchrnul(buf, '\n'); if (*eol) eol++; buf = eol; } *sublen = buf - *sub; /* drop trailing newline, if present */ if (*sublen && (*sub)[*sublen - 1] == '\n') *sublen -= 1; /* skip any empty lines */ while (*buf == '\n') buf++; *body = buf; *bodylen = strlen(buf); *nonsiglen = *sig - buf; } /* See grab_values */ static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { int i; const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &val[i]; if (!!deref != (*name == '*')) continue; if (deref) name++; if (strcmp(name, "subject") && strcmp(name, "body") && strcmp(name, "contents") && strcmp(name, "contents:subject") && strcmp(name, "contents:body") && strcmp(name, "contents:signature")) continue; if (!subpos) find_subpos(buf, sz, &subpos, &sublen, &bodypos, &bodylen, &nonsiglen, &sigpos, &siglen); if (!strcmp(name, "subject")) v->s = copy_subject(subpos, sublen); else if (!strcmp(name, "contents:subject")) v->s = copy_subject(subpos, sublen); else if (!strcmp(name, "body")) v->s = xmemdupz(bodypos, bodylen); else if (!strcmp(name, "contents:body")) v->s = xmemdupz(bodypos, nonsiglen); else if (!strcmp(name, "contents:signature")) v->s = xmemdupz(sigpos, siglen); else if (!strcmp(name, "contents")) v->s = xstrdup(subpos); } } /* * We want to have empty print-string for field requests * that do not apply (e.g. "authordate" for a tag object) */ static void fill_missing_values(struct atom_value *val) { int i; for (i = 0; i < used_atom_cnt; i++) { struct atom_value *v = &val[i]; if (v->s == NULL) v->s = ""; } } /* * val is a list of atom_value to hold returned values. Extract * the values for atoms in used_atom array out of (obj, buf, sz). * when deref is false, (obj, buf, sz) is the object that is * pointed at by the ref itself; otherwise it is the object the * ref (which is a tag) refers to. */ static void grab_values(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz) { grab_common_values(val, deref, obj, buf, sz); switch (obj->type) { case OBJ_TAG: grab_tag_values(val, deref, obj, buf, sz); grab_sub_body_contents(val, deref, obj, buf, sz); grab_person("tagger", val, deref, obj, buf, sz); break; case OBJ_COMMIT: grab_commit_values(val, deref, obj, buf, sz); grab_sub_body_contents(val, deref, obj, buf, sz); grab_person("author", val, deref, obj, buf, sz); grab_person("committer", val, deref, obj, buf, sz); break; case OBJ_TREE: /* grab_tree_values(val, deref, obj, buf, sz); */ break; case OBJ_BLOB: /* grab_blob_values(val, deref, obj, buf, sz); */ break; default: die("Eh? Object of type %d?", obj->type); } } static inline char *copy_advance(char *dst, const char *src) { while (*src) *dst++ = *src++; return dst; } /* * Parse the object referred by ref, and grab needed value. */ static void populate_value(struct ref_array_item *ref) { void *buf; struct object *obj; int eaten, i; unsigned long size; const unsigned char *tagged; ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value)); if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { unsigned char unused1[20]; ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING, unused1, NULL); if (!ref->symref) ref->symref = ""; } /* Fill in specials first */ for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &ref->value[i]; int deref = 0; const char *refname; const char *formatp; struct branch *branch = NULL; if (*name == '*') { deref = 1; name++; } if (starts_with(name, "refname")) refname = ref->refname; else if (starts_with(name, "symref")) refname = ref->symref ? ref->symref : ""; else if (starts_with(name, "upstream")) { const char *branch_name; /* only local branches may have an upstream */ if (!skip_prefix(ref->refname, "refs/heads/", &branch_name)) continue; branch = branch_get(branch_name); refname = branch_get_upstream(branch, NULL); if (!refname) continue; } else if (starts_with(name, "push")) { const char *branch_name; if (!skip_prefix(ref->refname, "refs/heads/", &branch_name)) continue; branch = branch_get(branch_name); refname = branch_get_push(branch, NULL); if (!refname) continue; } else if (starts_with(name, "color:")) { char color[COLOR_MAXLEN] = ""; if (color_parse(name + 6, color) < 0) die(_("unable to parse format")); v->s = xstrdup(color); continue; } else if (!strcmp(name, "flag")) { char buf[256], *cp = buf; if (ref->flag & REF_ISSYMREF) cp = copy_advance(cp, ",symref"); if (ref->flag & REF_ISPACKED) cp = copy_advance(cp, ",packed"); if (cp == buf) v->s = ""; else { *cp = '\0'; v->s = xstrdup(buf + 1); } continue; } else if (!deref && grab_objectname(name, ref->objectname, v)) { continue; } else if (!strcmp(name, "HEAD")) { const char *head; unsigned char sha1[20]; head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, sha1, NULL); if (!strcmp(ref->refname, head)) v->s = "*"; else v->s = " "; continue; } else continue; formatp = strchr(name, ':'); if (formatp) { int num_ours, num_theirs; formatp++; if (!strcmp(formatp, "short")) refname = shorten_unambiguous_ref(refname, warn_ambiguous_refs); 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)) continue; 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); } continue; } else if (!strcmp(formatp, "trackshort") && (starts_with(name, "upstream") || starts_with(name, "push"))) { assert(branch); if (stat_tracking_info(branch, &num_ours, &num_theirs, NULL)) continue; if (!num_ours && !num_theirs) v->s = "="; else if (!num_ours) v->s = "<"; else if (!num_theirs) v->s = ">"; else v->s = "<>"; continue; } else die("unknown %.*s format %s", (int)(formatp - name), name, formatp); } if (!deref) v->s = refname; else { int len = strlen(refname); char *s = xmalloc(len + 4); sprintf(s, "%s^{}", refname); v->s = s; } } for (i = 0; i < used_atom_cnt; i++) { struct atom_value *v = &ref->value[i]; if (v->s == NULL) goto need_obj; } return; need_obj: buf = get_obj(ref->objectname, &obj, &size, &eaten); if (!buf) die("missing object %s for %s", sha1_to_hex(ref->objectname), ref->refname); if (!obj) die("parse_object_buffer failed on %s for %s", sha1_to_hex(ref->objectname), ref->refname); grab_values(ref->value, 0, obj, buf, size); if (!eaten) free(buf); /* * If there is no atom that wants to know about tagged * object, we are done. */ if (!need_tagged || (obj->type != OBJ_TAG)) return; /* * 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; /* * 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. */ buf = get_obj(tagged, &obj, &size, &eaten); if (!buf) die("missing object %s for %s", sha1_to_hex(tagged), ref->refname); if (!obj) die("parse_object_buffer failed on %s for %s", sha1_to_hex(tagged), ref->refname); grab_values(ref->value, 1, obj, buf, size); if (!eaten) free(buf); } /* * Given a ref, return the value for the atom. This lazily gets value * out of the object by calling populate value. */ static void get_ref_atom_value(struct ref_array_item *ref, int atom, struct atom_value **v) { if (!ref->value) { populate_value(ref); fill_missing_values(ref->value); } *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 (!hashcmp(want->item->object.sha1, c->object.sha1)) 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 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). */ static int match_name_as_path(const char **pattern, const char *refname) { int namelen = strlen(refname); for (; *pattern; pattern++) { const char *p = *pattern; int plen = strlen(p); if ((plen <= namelen) && !strncmp(refname, p, plen) && (refname[plen] == '\0' || refname[plen] == '/' || p[plen-1] == '/')) return 1; if (!wildmatch(p, refname, WM_PATHNAME, NULL)) return 1; } return 0; } /* * 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->sha1; 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, int flag) { size_t len = strlen(refname); struct ref_array_item *ref = xcalloc(1, sizeof(struct ref_array_item) + len + 1); memcpy(ref->refname, refname, len); ref->refname[len] = '\0'; hashcpy(ref->objectname, objectname); ref->flag = flag; return ref; } /* * 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) { struct ref_filter_cbdata *ref_cbdata = cb_data; struct ref_filter *filter = ref_cbdata->filter; struct ref_array_item *ref; struct commit *commit = NULL; if (flag & REF_BAD_NAME) { warning("ignoring ref with broken name %s", refname); return 0; } if (flag & REF_ISBROKEN) { warning("ignoring broken ref %s", refname); return 0; } if (*filter->name_patterns && !match_name_as_path(filter->name_patterns, 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) { 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; return 0; } /* Free memory allocated for a ref_array_item */ static void free_array_item(struct ref_array_item *item) { free((char *)item->symref); free(item); } /* Free all memory allocated for ref_array */ void ref_array_clear(struct ref_array *array) { int i; for (i = 0; i < array->nr; i++) free_array_item(array->items[i]); free(array->items); array->items = NULL; 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 * 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 ret = 0; ref_cbdata.array = array; ref_cbdata.filter = filter; /* Simple per-ref filtering */ if (type & (FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN)) ret = for_each_rawref(ref_filter_handler, &ref_cbdata); else if (type & FILTER_REFS_ALL) ret = for_each_ref(ref_filter_handler, &ref_cbdata); else if (type) die("filter_refs: invalid type"); /* 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) { struct atom_value *va, *vb; int cmp; cmp_type cmp_type = used_atom_type[s->atom]; get_ref_atom_value(a, s->atom, &va); get_ref_atom_value(b, s->atom, &vb); switch (cmp_type) { case FIELD_STR: cmp = strcmp(va->s, vb->s); break; default: if (va->ul < vb->ul) cmp = -1; else if (va->ul == vb->ul) cmp = 0; else cmp = 1; break; } return (s->reverse) ? -cmp : cmp; } static struct ref_sorting *ref_sorting; static int compare_refs(const void *a_, const void *b_) { struct ref_array_item *a = *((struct ref_array_item **)a_); struct ref_array_item *b = *((struct ref_array_item **)b_); struct ref_sorting *s; for (s = ref_sorting; s; s = s->next) { int cmp = cmp_ref_sorting(s, a, b); if (cmp) return cmp; } return 0; } void ref_array_sort(struct ref_sorting *sorting, struct ref_array *array) { ref_sorting = sorting; qsort(array->items, array->nr, sizeof(struct ref_array_item *), compare_refs); } static void append_atom(struct atom_value *v, struct ref_formatting_state *state) { struct strbuf *s = &state->stack->output; switch (state->quote_style) { case QUOTE_NONE: strbuf_addstr(s, v->s); break; case QUOTE_SHELL: sq_quote_buf(s, v->s); break; case QUOTE_PERL: perl_quote_buf(s, v->s); break; case QUOTE_PYTHON: python_quote_buf(s, v->s); break; case QUOTE_TCL: tcl_quote_buf(s, v->s); break; } } static int hex1(char ch) { if ('0' <= ch && ch <= '9') return ch - '0'; else if ('a' <= ch && ch <= 'f') return ch - 'a' + 10; else if ('A' <= ch && ch <= 'F') return ch - 'A' + 10; return -1; } static int hex2(const char *cp) { if (cp[0] && cp[1]) return (hex1(cp[0]) << 4) | hex1(cp[1]); else return -1; } 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] == '%') cp++; else { int ch = hex2(cp + 1); if (0 <= ch) { strbuf_addch(s, ch); cp += 3; continue; } } } strbuf_addch(s, *cp); cp++; } } 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) append_literal(cp, sp, &state); get_ref_atom_value(info, parse_ref_filter_atom(sp + 2, ep), &atomv); append_atom(atomv, &state); } if (*cp) { sp = cp + strlen(cp); append_literal(cp, sp, &state); } if (need_color_reset_at_eol) { struct atom_value resetv; char color[COLOR_MAXLEN] = ""; if (color_parse("reset", color) < 0) die("BUG: couldn't parse 'reset' as a color"); resetv.s = color; append_atom(&resetv, &state); } final_buf = &state.stack->output; fwrite(final_buf->buf, 1, final_buf->len, stdout); pop_stack_element(&state.stack); putchar('\n'); } /* If no sorting option is given, use refname to sort as default */ 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_ref_filter_atom(cstr_name, cstr_name + strlen(cstr_name)); return sorting; } int parse_opt_ref_sorting(const struct option *opt, const char *arg, int unset) { struct ref_sorting **sorting_tail = opt->value; struct ref_sorting *s; int len; if (!arg) /* should --no-sort void the list ? */ return -1; s = xcalloc(1, sizeof(*s)); s->next = *sorting_tail; *sorting_tail = s; if (*arg == '-') { s->reverse = 1; arg++; } 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; }