582ef023f7c8d00576c403ec80b7e541d3edf494
   1#include "cache.h"
   2#include "commit.h"
   3#include "tag.h"
   4#include "refs.h"
   5#include "diff.h"
   6#include "diffcore.h"
   7#include "revision.h"
   8#include "builtin.h"
   9
  10static const char describe_usage[] =
  11"git-describe [--all] [--tags] [--abbrev=<n>] <committish>*";
  12
  13static int all; /* Default to annotated tags only */
  14static int tags;        /* But allow any tags if --tags is specified */
  15static int abbrev = DEFAULT_ABBREV;
  16
  17static unsigned int names[256], allocs[256];
  18static struct commit_name {
  19        struct commit *commit;
  20        int prio; /* annotated tag = 2, tag = 1, head = 0 */
  21        char path[FLEX_ARRAY]; /* more */
  22} **name_array[256];
  23
  24static struct commit_name *match(struct commit *cmit)
  25{
  26        unsigned char m = cmit->object.sha1[0];
  27        unsigned int i = names[m];
  28        struct commit_name **p = name_array[m];
  29
  30        while (i-- > 0) {
  31                struct commit_name *n = *p++;
  32                if (n->commit == cmit)
  33                        return n;
  34        }
  35        return NULL;
  36}
  37
  38static void add_to_known_names(const char *path,
  39                               struct commit *commit,
  40                               int prio)
  41{
  42        int idx;
  43        int len = strlen(path)+1;
  44        struct commit_name *name = xmalloc(sizeof(struct commit_name) + len);
  45        unsigned char m = commit->object.sha1[0];
  46
  47        name->commit = commit;
  48        name->prio = prio;
  49        memcpy(name->path, path, len);
  50        idx = names[m];
  51        if (idx >= allocs[m]) {
  52                allocs[m] = (idx + 50) * 3 / 2;
  53                name_array[m] = xrealloc(name_array[m],
  54                        allocs[m] * sizeof(*name_array));
  55        }
  56        name_array[m][idx] = name;
  57        names[m] = ++idx;
  58}
  59
  60static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
  61{
  62        struct commit *commit = lookup_commit_reference_gently(sha1, 1);
  63        struct object *object;
  64        int prio;
  65
  66        if (!commit)
  67                return 0;
  68        object = parse_object(sha1);
  69        /* If --all, then any refs are used.
  70         * If --tags, then any tags are used.
  71         * Otherwise only annotated tags are used.
  72         */
  73        if (!strncmp(path, "refs/tags/", 10)) {
  74                if (object->type == OBJ_TAG)
  75                        prio = 2;
  76                else
  77                        prio = 1;
  78        }
  79        else
  80                prio = 0;
  81
  82        if (!all) {
  83                if (!prio)
  84                        return 0;
  85                if (!tags && prio < 2)
  86                        return 0;
  87        }
  88        add_to_known_names(all ? path + 5 : path + 10, commit, prio);
  89        return 0;
  90}
  91
  92static int compare_names(const void *_a, const void *_b)
  93{
  94        struct commit_name *a = *(struct commit_name **)_a;
  95        struct commit_name *b = *(struct commit_name **)_b;
  96        unsigned long a_date = a->commit->date;
  97        unsigned long b_date = b->commit->date;
  98
  99        if (a->prio != b->prio)
 100                return b->prio - a->prio;
 101        return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
 102}
 103
 104struct possible_tag {
 105        struct possible_tag *next;
 106        struct commit_name *name;
 107        unsigned long depth;
 108};
 109
 110static void describe(const char *arg, int last_one)
 111{
 112        unsigned char sha1[20];
 113        struct commit *cmit;
 114        struct commit_list *list;
 115        static int initialized = 0;
 116        struct commit_name *n;
 117        struct possible_tag *all_matches, *min_match, *cur_match;
 118
 119        if (get_sha1(arg, sha1))
 120                die("Not a valid object name %s", arg);
 121        cmit = lookup_commit_reference(sha1);
 122        if (!cmit)
 123                die("%s is not a valid '%s' object", arg, commit_type);
 124
 125        if (!initialized) {
 126                unsigned int m;
 127                initialized = 1;
 128                for_each_ref(get_name, NULL);
 129                for (m = 0; m < ARRAY_SIZE(name_array); m++)
 130                        qsort(name_array[m], names[m],
 131                                sizeof(*name_array[m]), compare_names);
 132        }
 133
 134        n = match(cmit);
 135        if (n) {
 136                printf("%s\n", n->path);
 137                return;
 138        }
 139
 140        list = NULL;
 141        all_matches = NULL;
 142        cur_match = NULL;
 143        commit_list_insert(cmit, &list);
 144        while (list) {
 145                struct commit *c = pop_commit(&list);
 146                struct commit_list *parents = c->parents;
 147                n = match(c);
 148                if (n) {
 149                        struct possible_tag *p = xmalloc(sizeof(*p));
 150                        p->name = n;
 151                        p->next = NULL;
 152                        if (cur_match)
 153                                cur_match->next = p;
 154                        else
 155                                all_matches = p;
 156                        cur_match = p;
 157                        if (n->prio == 2)
 158                                continue;
 159                }
 160                while (parents) {
 161                        struct commit *p = parents->item;
 162                        parse_commit(p);
 163                        if (!(p->object.flags & SEEN)) {
 164                                p->object.flags |= SEEN;
 165                                insert_by_date(p, &list);
 166                        }
 167                        parents = parents->next;
 168                }
 169        }
 170
 171        if (!all_matches)
 172                die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1));
 173
 174        min_match = NULL;
 175        for (cur_match = all_matches; cur_match; cur_match = cur_match->next) {
 176                struct rev_info revs;
 177                struct commit *tagged = cur_match->name->commit;
 178
 179                clear_commit_marks(cmit, -1);
 180                init_revisions(&revs, NULL);
 181                tagged->object.flags |= UNINTERESTING;
 182                add_pending_object(&revs, &tagged->object, NULL);
 183                add_pending_object(&revs, &cmit->object, NULL);
 184
 185                prepare_revision_walk(&revs);
 186                cur_match->depth = 0;
 187                while ((!min_match || cur_match->depth < min_match->depth)
 188                        && get_revision(&revs))
 189                        cur_match->depth++;
 190                if (!min_match || (cur_match->depth < min_match->depth
 191                        && cur_match->name->prio >= min_match->name->prio))
 192                        min_match = cur_match;
 193                free_commit_list(revs.commits);
 194        }
 195        printf("%s-g%s\n", min_match->name->path,
 196                   find_unique_abbrev(cmit->object.sha1, abbrev));
 197
 198        if (!last_one) {
 199                for (cur_match = all_matches; cur_match; cur_match = min_match) {
 200                        min_match = cur_match->next;
 201                        free(cur_match);
 202                }
 203                clear_commit_marks(cmit, SEEN);
 204        }
 205}
 206
 207int cmd_describe(int argc, const char **argv, const char *prefix)
 208{
 209        int i;
 210
 211        for (i = 1; i < argc; i++) {
 212                const char *arg = argv[i];
 213
 214                if (*arg != '-')
 215                        break;
 216                else if (!strcmp(arg, "--all"))
 217                        all = 1;
 218                else if (!strcmp(arg, "--tags"))
 219                        tags = 1;
 220                else if (!strncmp(arg, "--abbrev=", 9)) {
 221                        abbrev = strtoul(arg + 9, NULL, 10);
 222                        if (abbrev < MINIMUM_ABBREV || 40 < abbrev)
 223                                abbrev = DEFAULT_ABBREV;
 224                }
 225                else
 226                        usage(describe_usage);
 227        }
 228
 229        save_commit_buffer = 0;
 230
 231        if (argc <= i)
 232                describe("HEAD", 1);
 233        else
 234                while (i < argc) {
 235                        describe(argv[i], (i == argc - 1));
 236                        i++;
 237                }
 238
 239        return 0;
 240}