builtin-describe.con commit Teach git-describe to verify annotated tag names before output (212945d)
   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"
   8
   9#define SEEN            (1u<<0)
  10#define MAX_TAGS        (FLAG_BITS - 1)
  11
  12static const char * const describe_usage[] = {
  13        "git-describe [options] <committish>*",
  14        NULL
  15};
  16
  17static 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;
  23
  24struct commit_name {
  25        struct tag *tag;
  26        int prio; /* annotated tag = 2, tag = 1, head = 0 */
  27        unsigned char sha1[20];
  28        char path[FLEX_ARRAY]; /* more */
  29};
  30static const char *prio_names[] = {
  31        "head", "lightweight", "annotated",
  32};
  33
  34static void add_to_known_names(const char *path,
  35                               struct commit *commit,
  36                               int prio,
  37                               const unsigned char *sha1)
  38{
  39        struct commit_name *e = commit->util;
  40        if (!e || e->prio < prio) {
  41                size_t len = strlen(path)+1;
  42                free(e);
  43                e = xmalloc(sizeof(struct commit_name) + len);
  44                e->tag = NULL;
  45                e->prio = prio;
  46                hashcpy(e->sha1, sha1);
  47                memcpy(e->path, path, len);
  48                commit->util = e;
  49        }
  50}
  51
  52static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
  53{
  54        int might_be_tag = !prefixcmp(path, "refs/tags/");
  55        struct commit *commit;
  56        struct object *object;
  57        unsigned char peeled[20];
  58        int is_tag, prio;
  59
  60        if (!all && !might_be_tag)
  61                return 0;
  62
  63        if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
  64                commit = lookup_commit_reference_gently(peeled, 1);
  65                if (!commit)
  66                        return 0;
  67                is_tag = !!hashcmp(sha1, commit->object.sha1);
  68        } else {
  69                commit = lookup_commit_reference_gently(sha1, 1);
  70                object = parse_object(sha1);
  71                if (!commit || !object)
  72                        return 0;
  73                is_tag = object->type == OBJ_TAG;
  74        }
  75
  76        /* If --all, then any refs are used.
  77         * If --tags, then any tags are used.
  78         * Otherwise only annotated tags are used.
  79         */
  80        if (might_be_tag) {
  81                if (is_tag) {
  82                        prio = 2;
  83                        if (pattern && fnmatch(pattern, path + 10, 0))
  84                                prio = 0;
  85                } else
  86                        prio = 1;
  87        }
  88        else
  89                prio = 0;
  90
  91        if (!all) {
  92                if (!prio)
  93                        return 0;
  94                if (!tags && prio < 2)
  95                        return 0;
  96        }
  97        add_to_known_names(all ? path + 5 : path + 10, commit, prio, sha1);
  98        return 0;
  99}
 100
 101struct possible_tag {
 102        struct commit_name *name;
 103        int depth;
 104        int found_order;
 105        unsigned flag_within;
 106};
 107
 108static int compare_pt(const void *a_, const void *b_)
 109{
 110        struct possible_tag *a = (struct possible_tag *)a_;
 111        struct possible_tag *b = (struct possible_tag *)b_;
 112        if (a->name->prio != b->name->prio)
 113                return b->name->prio - a->name->prio;
 114        if (a->depth != b->depth)
 115                return a->depth - b->depth;
 116        if (a->found_order != b->found_order)
 117                return a->found_order - b->found_order;
 118        return 0;
 119}
 120
 121static unsigned long finish_depth_computation(
 122        struct commit_list **list,
 123        struct possible_tag *best)
 124{
 125        unsigned long seen_commits = 0;
 126        while (*list) {
 127                struct commit *c = pop_commit(list);
 128                struct commit_list *parents = c->parents;
 129                seen_commits++;
 130                if (c->object.flags & best->flag_within) {
 131                        struct commit_list *a = *list;
 132                        while (a) {
 133                                struct commit *i = a->item;
 134                                if (!(i->object.flags & best->flag_within))
 135                                        break;
 136                                a = a->next;
 137                        }
 138                        if (!a)
 139                                break;
 140                } else
 141                        best->depth++;
 142                while (parents) {
 143                        struct commit *p = parents->item;
 144                        parse_commit(p);
 145                        if (!(p->object.flags & SEEN))
 146                                insert_by_date(p, list);
 147                        p->object.flags |= c->object.flags;
 148                        parents = parents->next;
 149                }
 150        }
 151        return seen_commits;
 152}
 153
 154static void display_name(struct commit_name *n)
 155{
 156        if (n->prio == 2 && !n->tag) {
 157                n->tag = lookup_tag(n->sha1);
 158                if (!n->tag || !n->tag->tag)
 159                        die("annotated tag %s not available", n->path);
 160                if (strcmp(n->tag->tag, n->path))
 161                        warning("tag '%s' is really '%s' here", n->tag->tag, n->path);
 162        }
 163
 164        if (n->tag)
 165                printf("%s", n->tag->tag);
 166        else
 167                printf("%s", n->path);
 168}
 169
 170static void describe(const char *arg, int last_one)
 171{
 172        unsigned char sha1[20];
 173        struct commit *cmit, *gave_up_on = NULL;
 174        struct commit_list *list;
 175        static int initialized = 0;
 176        struct commit_name *n;
 177        struct possible_tag all_matches[MAX_TAGS];
 178        unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
 179        unsigned long seen_commits = 0;
 180
 181        if (get_sha1(arg, sha1))
 182                die("Not a valid object name %s", arg);
 183        cmit = lookup_commit_reference(sha1);
 184        if (!cmit)
 185                die("%s is not a valid '%s' object", arg, commit_type);
 186
 187        if (!initialized) {
 188                initialized = 1;
 189                for_each_ref(get_name, NULL);
 190        }
 191
 192        n = cmit->util;
 193        if (n) {
 194                display_name(n);
 195                printf("\n");
 196                return;
 197        }
 198
 199        if (!max_candidates)
 200                die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1));
 201        if (debug)
 202                fprintf(stderr, "searching to describe %s\n", arg);
 203
 204        list = NULL;
 205        cmit->object.flags = SEEN;
 206        commit_list_insert(cmit, &list);
 207        while (list) {
 208                struct commit *c = pop_commit(&list);
 209                struct commit_list *parents = c->parents;
 210                seen_commits++;
 211                n = c->util;
 212                if (n) {
 213                        if (match_cnt < max_candidates) {
 214                                struct possible_tag *t = &all_matches[match_cnt++];
 215                                t->name = n;
 216                                t->depth = seen_commits - 1;
 217                                t->flag_within = 1u << match_cnt;
 218                                t->found_order = match_cnt;
 219                                c->object.flags |= t->flag_within;
 220                                if (n->prio == 2)
 221                                        annotated_cnt++;
 222                        }
 223                        else {
 224                                gave_up_on = c;
 225                                break;
 226                        }
 227                }
 228                for (cur_match = 0; cur_match < match_cnt; cur_match++) {
 229                        struct possible_tag *t = &all_matches[cur_match];
 230                        if (!(c->object.flags & t->flag_within))
 231                                t->depth++;
 232                }
 233                if (annotated_cnt && !list) {
 234                        if (debug)
 235                                fprintf(stderr, "finished search at %s\n",
 236                                        sha1_to_hex(c->object.sha1));
 237                        break;
 238                }
 239                while (parents) {
 240                        struct commit *p = parents->item;
 241                        parse_commit(p);
 242                        if (!(p->object.flags & SEEN))
 243                                insert_by_date(p, &list);
 244                        p->object.flags |= c->object.flags;
 245                        parents = parents->next;
 246                }
 247        }
 248
 249        if (!match_cnt)
 250                die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1));
 251
 252        qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
 253
 254        if (gave_up_on) {
 255                insert_by_date(gave_up_on, &list);
 256                seen_commits--;
 257        }
 258        seen_commits += finish_depth_computation(&list, &all_matches[0]);
 259        free_commit_list(list);
 260
 261        if (debug) {
 262                for (cur_match = 0; cur_match < match_cnt; cur_match++) {
 263                        struct possible_tag *t = &all_matches[cur_match];
 264                        fprintf(stderr, " %-11s %8d %s\n",
 265                                prio_names[t->name->prio],
 266                                t->depth, t->name->path);
 267                }
 268                fprintf(stderr, "traversed %lu commits\n", seen_commits);
 269                if (gave_up_on) {
 270                        fprintf(stderr,
 271                                "more than %i tags found; listed %i most recent\n"
 272                                "gave up search at %s\n",
 273                                max_candidates, max_candidates,
 274                                sha1_to_hex(gave_up_on->object.sha1));
 275                }
 276        }
 277
 278        display_name(all_matches[0].name);
 279        if (abbrev)
 280                printf("-%d-g%s", all_matches[0].depth,
 281                       find_unique_abbrev(cmit->object.sha1, abbrev));
 282        printf("\n");
 283
 284        if (!last_one)
 285                clear_commit_marks(cmit, -1);
 286}
 287
 288int cmd_describe(int argc, const char **argv, const char *prefix)
 289{
 290        int contains = 0;
 291        struct option options[] = {
 292                OPT_BOOLEAN(0, "contains",   &contains, "find the tag that comes after the commit"),
 293                OPT_BOOLEAN(0, "debug",      &debug, "debug search strategy on stderr"),
 294                OPT_BOOLEAN(0, "all",        &all, "use any ref in .git/refs"),
 295                OPT_BOOLEAN(0, "tags",       &tags, "use any tag in .git/refs/tags"),
 296                OPT__ABBREV(&abbrev),
 297                OPT_SET_INT(0, "exact-match", &max_candidates,
 298                            "only output exact matches", 0),
 299                OPT_INTEGER(0, "candidates", &max_candidates,
 300                            "consider <n> most recent tags (default: 10)"),
 301                OPT_STRING(0, "match",       &pattern, "pattern",
 302                           "only consider tags matching <pattern>"),
 303                OPT_END(),
 304        };
 305
 306        argc = parse_options(argc, argv, options, describe_usage, 0);
 307        if (max_candidates < 0)
 308                max_candidates = 0;
 309        else if (max_candidates > MAX_TAGS)
 310                max_candidates = MAX_TAGS;
 311
 312        save_commit_buffer = 0;
 313
 314        if (contains) {
 315                const char **args = xmalloc((6 + argc) * sizeof(char*));
 316                int i = 0;
 317                args[i++] = "name-rev";
 318                args[i++] = "--name-only";
 319                args[i++] = "--no-undefined";
 320                if (!all) {
 321                        args[i++] = "--tags";
 322                        if (pattern) {
 323                                char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1);
 324                                sprintf(s, "--refs=refs/tags/%s", pattern);
 325                                args[i++] = s;
 326                        }
 327                }
 328                memcpy(args + i, argv, argc * sizeof(char*));
 329                args[i + argc] = NULL;
 330                return cmd_name_rev(i + argc, args, prefix);
 331        }
 332
 333        if (argc == 0) {
 334                describe("HEAD", 1);
 335        } else {
 336                while (argc-- > 0) {
 337                        describe(*argv++, argc == 0);
 338                }
 339        }
 340        return 0;
 341}