1#include "cache.h"2#include "commit.h"3#include "tag.h"4#include "refs.h"5#include "builtin.h"6#include "exec_cmd.h"7#include "parse-options.h"89#define SEEN (1u<<0)10#define MAX_TAGS (FLAG_BITS - 1)1112static const char * const describe_usage[] = {13"git-describe [options] <committish>*",14NULL15};1617static int debug; /* Display lots of verbose info */18static int all; /* Default to annotated tags only */19static int tags; /* But allow any tags if --tags is specified */20static int abbrev = DEFAULT_ABBREV;21static int max_candidates = 10;22const char *pattern = NULL;2324struct commit_name {25int prio; /* annotated tag = 2, tag = 1, head = 0 */26char path[FLEX_ARRAY]; /* more */27};28static const char *prio_names[] = {29"head", "lightweight", "annotated",30};3132static void add_to_known_names(const char *path,33struct commit *commit,34int prio)35{36struct commit_name *e = commit->util;37if (!e || e->prio < prio) {38size_t len = strlen(path)+1;39free(e);40e = xmalloc(sizeof(struct commit_name) + len);41e->prio = prio;42memcpy(e->path, path, len);43commit->util = e;44}45}4647static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)48{49struct commit *commit = lookup_commit_reference_gently(sha1, 1);50struct object *object;51int prio;5253if (!commit)54return 0;55object = parse_object(sha1);56/* If --all, then any refs are used.57* If --tags, then any tags are used.58* Otherwise only annotated tags are used.59*/60if (!prefixcmp(path, "refs/tags/")) {61if (object->type == OBJ_TAG) {62prio = 2;63if (pattern && fnmatch(pattern, path + 10, 0))64prio = 0;65} else66prio = 1;67}68else69prio = 0;7071if (!all) {72if (!prio)73return 0;74if (!tags && prio < 2)75return 0;76}77add_to_known_names(all ? path + 5 : path + 10, commit, prio);78return 0;79}8081struct possible_tag {82struct commit_name *name;83int depth;84int found_order;85unsigned flag_within;86};8788static int compare_pt(const void *a_, const void *b_)89{90struct possible_tag *a = (struct possible_tag *)a_;91struct possible_tag *b = (struct possible_tag *)b_;92if (a->name->prio != b->name->prio)93return b->name->prio - a->name->prio;94if (a->depth != b->depth)95return a->depth - b->depth;96if (a->found_order != b->found_order)97return a->found_order - b->found_order;98return 0;99}100101static unsigned long finish_depth_computation(102struct commit_list **list,103struct possible_tag *best)104{105unsigned long seen_commits = 0;106while (*list) {107struct commit *c = pop_commit(list);108struct commit_list *parents = c->parents;109seen_commits++;110if (c->object.flags & best->flag_within) {111struct commit_list *a = *list;112while (a) {113struct commit *i = a->item;114if (!(i->object.flags & best->flag_within))115break;116a = a->next;117}118if (!a)119break;120} else121best->depth++;122while (parents) {123struct commit *p = parents->item;124parse_commit(p);125if (!(p->object.flags & SEEN))126insert_by_date(p, list);127p->object.flags |= c->object.flags;128parents = parents->next;129}130}131return seen_commits;132}133134static void describe(const char *arg, int last_one)135{136unsigned char sha1[20];137struct commit *cmit, *gave_up_on = NULL;138struct commit_list *list;139static int initialized = 0;140struct commit_name *n;141struct possible_tag all_matches[MAX_TAGS];142unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;143unsigned long seen_commits = 0;144145if (get_sha1(arg, sha1))146die("Not a valid object name %s", arg);147cmit = lookup_commit_reference(sha1);148if (!cmit)149die("%s is not a valid '%s' object", arg, commit_type);150151if (!initialized) {152initialized = 1;153for_each_ref(get_name, NULL);154}155156n = cmit->util;157if (n) {158printf("%s\n", n->path);159return;160}161162if (debug)163fprintf(stderr, "searching to describe %s\n", arg);164165list = NULL;166cmit->object.flags = SEEN;167commit_list_insert(cmit, &list);168while (list) {169struct commit *c = pop_commit(&list);170struct commit_list *parents = c->parents;171seen_commits++;172n = c->util;173if (n) {174if (match_cnt < max_candidates) {175struct possible_tag *t = &all_matches[match_cnt++];176t->name = n;177t->depth = seen_commits - 1;178t->flag_within = 1u << match_cnt;179t->found_order = match_cnt;180c->object.flags |= t->flag_within;181if (n->prio == 2)182annotated_cnt++;183}184else {185gave_up_on = c;186break;187}188}189for (cur_match = 0; cur_match < match_cnt; cur_match++) {190struct possible_tag *t = &all_matches[cur_match];191if (!(c->object.flags & t->flag_within))192t->depth++;193}194if (annotated_cnt && !list) {195if (debug)196fprintf(stderr, "finished search at %s\n",197sha1_to_hex(c->object.sha1));198break;199}200while (parents) {201struct commit *p = parents->item;202parse_commit(p);203if (!(p->object.flags & SEEN))204insert_by_date(p, &list);205p->object.flags |= c->object.flags;206parents = parents->next;207}208}209210if (!match_cnt)211die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1));212213qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);214215if (gave_up_on) {216insert_by_date(gave_up_on, &list);217seen_commits--;218}219seen_commits += finish_depth_computation(&list, &all_matches[0]);220free_commit_list(list);221222if (debug) {223for (cur_match = 0; cur_match < match_cnt; cur_match++) {224struct possible_tag *t = &all_matches[cur_match];225fprintf(stderr, " %-11s %8d %s\n",226prio_names[t->name->prio],227t->depth, t->name->path);228}229fprintf(stderr, "traversed %lu commits\n", seen_commits);230if (gave_up_on) {231fprintf(stderr,232"more than %i tags found; listed %i most recent\n"233"gave up search at %s\n",234max_candidates, max_candidates,235sha1_to_hex(gave_up_on->object.sha1));236}237}238if (abbrev == 0)239printf("%s\n", all_matches[0].name->path );240else241printf("%s-%d-g%s\n", all_matches[0].name->path,242all_matches[0].depth,243find_unique_abbrev(cmit->object.sha1, abbrev));244245if (!last_one)246clear_commit_marks(cmit, -1);247}248249int cmd_describe(int argc, const char **argv, const char *prefix)250{251int contains = 0;252struct option options[] = {253OPT_BOOLEAN(0, "contains", &contains, "find the tag that comes after the commit"),254OPT_BOOLEAN(0, "debug", &debug, "debug search strategy on stderr"),255OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"),256OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"),257OPT__ABBREV(&abbrev),258OPT_INTEGER(0, "candidates", &max_candidates,259"consider <n> most recent tags (default: 10)"),260OPT_STRING(0, "match", &pattern, "pattern",261"only consider tags matching <pattern>"),262OPT_END(),263};264265argc = parse_options(argc, argv, options, describe_usage, 0);266if (max_candidates < 1)267max_candidates = 1;268else if (max_candidates > MAX_TAGS)269max_candidates = MAX_TAGS;270271save_commit_buffer = 0;272273if (contains) {274const char **args = xmalloc((6 + argc) * sizeof(char*));275int i = 0;276args[i++] = "name-rev";277args[i++] = "--name-only";278args[i++] = "--no-undefined";279if (!all) {280args[i++] = "--tags";281if (pattern) {282char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);283sprintf(s, "--refs=refs/tags/%s", pattern);284args[i++] = s;285}286}287memcpy(args + i, argv, argc * sizeof(char*));288args[i + argc] = NULL;289return cmd_name_rev(i + argc, args, prefix);290}291292if (argc == 0) {293describe("HEAD", 1);294} else {295while (argc-- > 0) {296describe(*argv++, argc == 0);297}298}299return 0;300}