#include "cache.h" #include "commit.h" #include "tag.h" #include "refs.h" #define SEEN (1u << 0) static const char describe_usage[] = "git-describe [--all] [--tags] [--abbrev=<n>] <committish>*"; static int all = 0; /* Default to annotated tags only */ static int tags = 0; /* But allow any tags if --tags is specified */ static int abbrev = DEFAULT_ABBREV; static int names = 0, allocs = 0; static struct commit_name { const struct commit *commit; int prio; /* annotated tag = 2, tag = 1, head = 0 */ char path[FLEX_ARRAY]; /* more */ } **name_array = NULL; static struct commit_name *match(struct commit *cmit) { int i = names; struct commit_name **p = name_array; while (i-- > 0) { struct commit_name *n = *p++; if (n->commit == cmit) return n; } return NULL; } static void add_to_known_names(const char *path, const struct commit *commit, int prio) { int idx; int len = strlen(path)+1; struct commit_name *name = xmalloc(sizeof(struct commit_name) + len); name->commit = commit; name->prio = prio; memcpy(name->path, path, len); idx = names; if (idx >= allocs) { allocs = (idx + 50) * 3 / 2; name_array = xrealloc(name_array, allocs*sizeof(*name_array)); } name_array[idx] = name; names = ++idx; } static int get_name(const char *path, const unsigned char *sha1) { struct commit *commit = lookup_commit_reference_gently(sha1, 1); struct object *object; int prio; if (!commit) return 0; object = parse_object(sha1); /* If --all, then any refs are used. * If --tags, then any tags are used. * Otherwise only annotated tags are used. */ if (!strncmp(path, "refs/tags/", 10)) { if (object->type == tag_type) prio = 2; else prio = 1; } else prio = 0; if (!all) { if (!prio) return 0; if (!tags && prio < 2) return 0; } add_to_known_names(all ? path + 5 : path + 10, commit, prio); return 0; } static int compare_names(const void *_a, const void *_b) { struct commit_name *a = *(struct commit_name **)_a; struct commit_name *b = *(struct commit_name **)_b; unsigned long a_date = a->commit->date; unsigned long b_date = b->commit->date; if (a->prio != b->prio) return b->prio - a->prio; return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1; } static void describe(char *arg, int last_one) { unsigned char sha1[20]; struct commit *cmit; struct commit_list *list; static int initialized = 0; struct commit_name *n; if (get_sha1(arg, sha1)) die("Not a valid object name %s", arg); cmit = lookup_commit_reference(sha1); if (!cmit) die("%s is not a valid '%s' object", arg, commit_type); if (!initialized) { initialized = 1; for_each_ref(get_name); qsort(name_array, names, sizeof(*name_array), compare_names); } n = match(cmit); if (n) { printf("%s\n", n->path); return; } list = NULL; commit_list_insert(cmit, &list); while (list) { struct commit *c = pop_most_recent_commit(&list, SEEN); n = match(c); if (n) { printf("%s-g%s\n", n->path, find_unique_abbrev(cmit->object.sha1, abbrev)); if (!last_one) clear_commit_marks(cmit, SEEN); return; } } die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1)); } int main(int argc, char **argv) { int i; for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (*arg != '-') break; else if (!strcmp(arg, "--all")) all = 1; else if (!strcmp(arg, "--tags")) tags = 1; else if (!strncmp(arg, "--abbrev=", 9)) { abbrev = strtoul(arg + 9, NULL, 10); if (abbrev < MINIMUM_ABBREV || 40 <= abbrev) abbrev = DEFAULT_ABBREV; } else usage(describe_usage); } if (i == argc) describe("HEAD", 1); else while (i < argc) { describe(argv[i], (i == argc - 1)); i++; } return 0; }