diff options
Diffstat (limited to 'builtin/name-rev.c')
-rw-r--r-- | builtin/name-rev.c | 238 |
1 files changed, 172 insertions, 66 deletions
diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 1b374583c2..57be35faf5 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -4,11 +4,13 @@ #include "tag.h" #include "refs.h" #include "parse-options.h" +#include "sha1-lookup.h" #define CUTOFF_DATE_SLOP 86400 /* one day */ typedef struct rev_name { const char *tip_name; + unsigned long taggerdate; int generation; int distance; } rev_name; @@ -19,24 +21,21 @@ static long cutoff = LONG_MAX; #define MERGE_TRAVERSAL_WEIGHT 65535 static void name_rev(struct commit *commit, - const char *tip_name, int generation, int distance, + const char *tip_name, unsigned long taggerdate, + int generation, int distance, int deref) { struct rev_name *name = (struct rev_name *)commit->util; struct commit_list *parents; int parent_number = 1; - if (!commit->object.parsed) - parse_commit(commit); + parse_commit(commit); if (commit->date < cutoff) return; if (deref) { - char *new_name = xmalloc(strlen(tip_name)+3); - strcpy(new_name, tip_name); - strcat(new_name, "^0"); - tip_name = new_name; + tip_name = xstrfmt("%s^0", tip_name); if (generation) die("generation: %d, but deref?", generation); @@ -46,9 +45,12 @@ static void name_rev(struct commit *commit, name = xmalloc(sizeof(rev_name)); commit->util = name; goto copy_data; - } else if (name->distance > distance) { + } else if (name->taggerdate > taggerdate || + (name->taggerdate == taggerdate && + name->distance > distance)) { copy_data: name->tip_name = tip_name; + name->taggerdate = taggerdate; name->generation = generation; name->distance = distance; } else @@ -58,72 +60,153 @@ copy_data: parents; parents = parents->next, parent_number++) { if (parent_number > 1) { - int len = strlen(tip_name); - char *new_name = xmalloc(len + - 1 + decimal_length(generation) + /* ~<n> */ - 1 + 2 + /* ^NN */ - 1); - - if (len > 2 && !strcmp(tip_name + len - 2, "^0")) - len -= 2; + size_t len; + char *new_name; + + strip_suffix(tip_name, "^0", &len); if (generation > 0) - sprintf(new_name, "%.*s~%d^%d", len, tip_name, - generation, parent_number); + new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name, + generation, parent_number); else - sprintf(new_name, "%.*s^%d", len, tip_name, - parent_number); + new_name = xstrfmt("%.*s^%d", (int)len, tip_name, + parent_number); - name_rev(parents->item, new_name, 0, + name_rev(parents->item, new_name, taggerdate, 0, distance + MERGE_TRAVERSAL_WEIGHT, 0); } else { - name_rev(parents->item, tip_name, generation + 1, - distance + 1, 0); + name_rev(parents->item, tip_name, taggerdate, + generation + 1, distance + 1, 0); } } } +static int subpath_matches(const char *path, const char *filter) +{ + const char *subpath = path; + + while (subpath) { + if (!wildmatch(filter, subpath, 0, NULL)) + return subpath - path; + subpath = strchr(subpath, '/'); + if (subpath) + subpath++; + } + return -1; +} + +static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) +{ + if (shorten_unambiguous) + refname = shorten_unambiguous_ref(refname, 0); + else if (starts_with(refname, "refs/heads/")) + refname = refname + 11; + else if (starts_with(refname, "refs/")) + refname = refname + 5; + return refname; +} + struct name_ref_data { int tags_only; int name_only; const char *ref_filter; }; -static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) +static struct tip_table { + struct tip_table_entry { + unsigned char sha1[20]; + const char *refname; + } *table; + int nr; + int alloc; + int sorted; +} tip_table; + +static void add_to_tip_table(const unsigned char *sha1, const char *refname, + int shorten_unambiguous) { - struct object *o = parse_object(sha1); + refname = name_ref_abbrev(refname, shorten_unambiguous); + + ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); + hashcpy(tip_table.table[tip_table.nr].sha1, sha1); + tip_table.table[tip_table.nr].refname = xstrdup(refname); + tip_table.nr++; + tip_table.sorted = 0; +} + +static int tipcmp(const void *a_, const void *b_) +{ + const struct tip_table_entry *a = a_, *b = b_; + return hashcmp(a->sha1, b->sha1); +} + +static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data) +{ + struct object *o = parse_object(oid->hash); struct name_ref_data *data = cb_data; + int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; + unsigned long taggerdate = ULONG_MAX; - if (data->tags_only && prefixcmp(path, "refs/tags/")) + if (data->tags_only && !starts_with(path, "refs/tags/")) return 0; - if (data->ref_filter && fnmatch(data->ref_filter, path, 0)) - return 0; + if (data->ref_filter) { + switch (subpath_matches(path, data->ref_filter)) { + case -1: /* did not match */ + return 0; + case 0: /* matched fully */ + break; + default: /* matched subpath */ + can_abbreviate_output = 1; + break; + } + } + + add_to_tip_table(oid->hash, path, can_abbreviate_output); while (o && o->type == OBJ_TAG) { struct tag *t = (struct tag *) o; if (!t->tagged) break; /* broken repository */ - o = parse_object(t->tagged->sha1); + o = parse_object(t->tagged->oid.hash); deref = 1; + taggerdate = t->date; } if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; - if (!prefixcmp(path, "refs/heads/")) - path = path + 11; - else if (data->tags_only - && data->name_only - && !prefixcmp(path, "refs/tags/")) - path = path + 10; - else if (!prefixcmp(path, "refs/")) - path = path + 5; - - name_rev(commit, xstrdup(path), 0, 0, deref); + path = name_ref_abbrev(path, can_abbreviate_output); + name_rev(commit, xstrdup(path), taggerdate, 0, 0, deref); } return 0; } +static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) +{ + struct tip_table_entry *table = table_; + return table[ix].sha1; +} + +static const char *get_exact_ref_match(const struct object *o) +{ + int found; + + if (!tip_table.table || !tip_table.nr) + return NULL; + + if (!tip_table.sorted) { + qsort(tip_table.table, tip_table.nr, sizeof(*tip_table.table), + tipcmp); + tip_table.sorted = 1; + } + + found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr, + nth_tip_table_ent); + if (0 <= found) + return tip_table.table[found].refname; + return NULL; +} + /* returns a static buffer */ static const char *get_rev_name(const struct object *o) { @@ -132,7 +215,7 @@ static const char *get_rev_name(const struct object *o) struct commit *c; if (o->type != OBJ_COMMIT) - return NULL; + return get_exact_ref_match(o); c = (struct commit *) o; n = c->util; if (!n) @@ -156,25 +239,25 @@ static void show_name(const struct object *obj, int always, int allow_undefined, int name_only) { const char *name; - const unsigned char *sha1 = obj->sha1; + const struct object_id *oid = &obj->oid; if (!name_only) - printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1)); + printf("%s ", caller_name ? caller_name : oid_to_hex(oid)); name = get_rev_name(obj); if (name) printf("%s\n", name); else if (allow_undefined) printf("undefined\n"); else if (always) - printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV)); + printf("%s\n", find_unique_abbrev(oid->hash, DEFAULT_ABBREV)); else - die("cannot describe '%s'", sha1_to_hex(sha1)); + die("cannot describe '%s'", oid_to_hex(oid)); } static char const * const name_rev_usage[] = { - "git name-rev [options] <commit>...", - "git name-rev [options] --all", - "git name-rev [options] --stdin", + N_("git name-rev [<options>] <commit>..."), + N_("git name-rev [<options>] --all"), + N_("git name-rev [<options>] --stdin"), NULL }; @@ -223,25 +306,31 @@ static void name_rev_line(char *p, struct name_ref_data *data) int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = OBJECT_ARRAY_INIT; - int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0; + int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, NULL }; struct option opts[] = { - OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"), - OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"), - OPT_STRING(0, "refs", &data.ref_filter, "pattern", - "only use refs matching <pattern>"), + OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")), + OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")), + OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"), + N_("only use refs matching <pattern>")), OPT_GROUP(""), - OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"), - OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"), - OPT_BOOLEAN(0, "undefined", &allow_undefined, "allow to print `undefined` names"), - OPT_BOOLEAN(0, "always", &always, - "show abbreviated commit object as fallback"), + OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), + OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")), + OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), + OPT_BOOL(0, "always", &always, + N_("show abbreviated commit object as fallback")), + { + /* A Hidden OPT_BOOL */ + OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL, + N_("dereference tags in the input (internal use)"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1, + }, OPT_END(), }; git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); - if (!!all + !!transform_stdin + !!argc > 1) { + if (all + transform_stdin + !!argc > 1) { error("Specify either a list, or --all, not both!"); usage_with_options(name_rev_usage, opts); } @@ -250,7 +339,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) for (; argc; argc--, argv++) { unsigned char sha1[20]; - struct object *o; + struct object *object; struct commit *commit; if (get_sha1(*argv, sha1)) { @@ -259,17 +348,34 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) continue; } - o = deref_tag(parse_object(sha1), *argv, 0); - if (!o || o->type != OBJ_COMMIT) { - fprintf(stderr, "Could not get commit for %s. Skipping.\n", + commit = NULL; + object = parse_object(sha1); + if (object) { + struct object *peeled = deref_tag(object, *argv, 0); + if (peeled && peeled->type == OBJ_COMMIT) + commit = (struct commit *)peeled; + } + + if (!object) { + fprintf(stderr, "Could not get object for %s. Skipping.\n", *argv); continue; } - commit = (struct commit *)o; - if (cutoff > commit->date) - cutoff = commit->date; - add_object_array((struct object *)commit, *argv, &revs); + if (commit) { + if (cutoff > commit->date) + cutoff = commit->date; + } + + if (peel_tag) { + if (!commit) { + fprintf(stderr, "Could not get commit for %s. Skipping.\n", + *argv); + continue; + } + object = (struct object *)commit; + } + add_object_array(object, *argv, &revs); } if (cutoff) |