builtin / name-rev.con commit name-rev: avoid leaking memory in the `deref` case (5308224)
   1#include "builtin.h"
   2#include "cache.h"
   3#include "commit.h"
   4#include "tag.h"
   5#include "refs.h"
   6#include "parse-options.h"
   7#include "sha1-lookup.h"
   8
   9#define CUTOFF_DATE_SLOP 86400 /* one day */
  10
  11typedef struct rev_name {
  12        const char *tip_name;
  13        unsigned long taggerdate;
  14        int generation;
  15        int distance;
  16} rev_name;
  17
  18static long cutoff = LONG_MAX;
  19
  20/* How many generations are maximally preferred over _one_ merge traversal? */
  21#define MERGE_TRAVERSAL_WEIGHT 65535
  22
  23static void name_rev(struct commit *commit,
  24                const char *tip_name, unsigned long taggerdate,
  25                int generation, int distance,
  26                int deref)
  27{
  28        struct rev_name *name = (struct rev_name *)commit->util;
  29        struct commit_list *parents;
  30        int parent_number = 1;
  31        char *to_free = NULL;
  32
  33        parse_commit(commit);
  34
  35        if (commit->date < cutoff)
  36                return;
  37
  38        if (deref) {
  39                tip_name = to_free = xstrfmt("%s^0", tip_name);
  40
  41                if (generation)
  42                        die("generation: %d, but deref?", generation);
  43        }
  44
  45        if (name == NULL) {
  46                name = xmalloc(sizeof(rev_name));
  47                commit->util = name;
  48                goto copy_data;
  49        } else if (name->taggerdate > taggerdate ||
  50                        (name->taggerdate == taggerdate &&
  51                         name->distance > distance)) {
  52copy_data:
  53                name->tip_name = tip_name;
  54                name->taggerdate = taggerdate;
  55                name->generation = generation;
  56                name->distance = distance;
  57        } else {
  58                free(to_free);
  59                return;
  60        }
  61
  62        for (parents = commit->parents;
  63                        parents;
  64                        parents = parents->next, parent_number++) {
  65                if (parent_number > 1) {
  66                        size_t len;
  67                        char *new_name;
  68
  69                        strip_suffix(tip_name, "^0", &len);
  70                        if (generation > 0)
  71                                new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name,
  72                                                   generation, parent_number);
  73                        else
  74                                new_name = xstrfmt("%.*s^%d", (int)len, tip_name,
  75                                                   parent_number);
  76
  77                        name_rev(parents->item, new_name, taggerdate, 0,
  78                                distance + MERGE_TRAVERSAL_WEIGHT, 0);
  79                } else {
  80                        name_rev(parents->item, tip_name, taggerdate,
  81                                generation + 1, distance + 1, 0);
  82                }
  83        }
  84}
  85
  86static int subpath_matches(const char *path, const char *filter)
  87{
  88        const char *subpath = path;
  89
  90        while (subpath) {
  91                if (!wildmatch(filter, subpath, 0, NULL))
  92                        return subpath - path;
  93                subpath = strchr(subpath, '/');
  94                if (subpath)
  95                        subpath++;
  96        }
  97        return -1;
  98}
  99
 100static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
 101{
 102        if (shorten_unambiguous)
 103                refname = shorten_unambiguous_ref(refname, 0);
 104        else if (starts_with(refname, "refs/heads/"))
 105                refname = refname + 11;
 106        else if (starts_with(refname, "refs/"))
 107                refname = refname + 5;
 108        return refname;
 109}
 110
 111struct name_ref_data {
 112        int tags_only;
 113        int name_only;
 114        struct string_list ref_filters;
 115        struct string_list exclude_filters;
 116};
 117
 118static struct tip_table {
 119        struct tip_table_entry {
 120                unsigned char sha1[20];
 121                const char *refname;
 122        } *table;
 123        int nr;
 124        int alloc;
 125        int sorted;
 126} tip_table;
 127
 128static void add_to_tip_table(const unsigned char *sha1, const char *refname,
 129                             int shorten_unambiguous)
 130{
 131        refname = name_ref_abbrev(refname, shorten_unambiguous);
 132
 133        ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc);
 134        hashcpy(tip_table.table[tip_table.nr].sha1, sha1);
 135        tip_table.table[tip_table.nr].refname = xstrdup(refname);
 136        tip_table.nr++;
 137        tip_table.sorted = 0;
 138}
 139
 140static int tipcmp(const void *a_, const void *b_)
 141{
 142        const struct tip_table_entry *a = a_, *b = b_;
 143        return hashcmp(a->sha1, b->sha1);
 144}
 145
 146static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data)
 147{
 148        struct object *o = parse_object(oid->hash);
 149        struct name_ref_data *data = cb_data;
 150        int can_abbreviate_output = data->tags_only && data->name_only;
 151        int deref = 0;
 152        unsigned long taggerdate = ULONG_MAX;
 153
 154        if (data->tags_only && !starts_with(path, "refs/tags/"))
 155                return 0;
 156
 157        if (data->exclude_filters.nr) {
 158                struct string_list_item *item;
 159
 160                for_each_string_list_item(item, &data->exclude_filters) {
 161                        if (subpath_matches(path, item->string) >= 0)
 162                                return 0;
 163                }
 164        }
 165
 166        if (data->ref_filters.nr) {
 167                struct string_list_item *item;
 168                int matched = 0;
 169
 170                /* See if any of the patterns match. */
 171                for_each_string_list_item(item, &data->ref_filters) {
 172                        /*
 173                         * Check all patterns even after finding a match, so
 174                         * that we can see if a match with a subpath exists.
 175                         * When a user asked for 'refs/tags/v*' and 'v1.*',
 176                         * both of which match, the user is showing her
 177                         * willingness to accept a shortened output by having
 178                         * the 'v1.*' in the acceptable refnames, so we
 179                         * shouldn't stop when seeing 'refs/tags/v1.4' matches
 180                         * 'refs/tags/v*'.  We should show it as 'v1.4'.
 181                         */
 182                        switch (subpath_matches(path, item->string)) {
 183                        case -1: /* did not match */
 184                                break;
 185                        case 0: /* matched fully */
 186                                matched = 1;
 187                                break;
 188                        default: /* matched subpath */
 189                                matched = 1;
 190                                can_abbreviate_output = 1;
 191                                break;
 192                        }
 193                }
 194
 195                /* If none of the patterns matched, stop now */
 196                if (!matched)
 197                        return 0;
 198        }
 199
 200        add_to_tip_table(oid->hash, path, can_abbreviate_output);
 201
 202        while (o && o->type == OBJ_TAG) {
 203                struct tag *t = (struct tag *) o;
 204                if (!t->tagged)
 205                        break; /* broken repository */
 206                o = parse_object(t->tagged->oid.hash);
 207                deref = 1;
 208                taggerdate = t->date;
 209        }
 210        if (o && o->type == OBJ_COMMIT) {
 211                struct commit *commit = (struct commit *)o;
 212
 213                path = name_ref_abbrev(path, can_abbreviate_output);
 214                name_rev(commit, xstrdup(path), taggerdate, 0, 0, deref);
 215        }
 216        return 0;
 217}
 218
 219static const unsigned char *nth_tip_table_ent(size_t ix, void *table_)
 220{
 221        struct tip_table_entry *table = table_;
 222        return table[ix].sha1;
 223}
 224
 225static const char *get_exact_ref_match(const struct object *o)
 226{
 227        int found;
 228
 229        if (!tip_table.table || !tip_table.nr)
 230                return NULL;
 231
 232        if (!tip_table.sorted) {
 233                QSORT(tip_table.table, tip_table.nr, tipcmp);
 234                tip_table.sorted = 1;
 235        }
 236
 237        found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr,
 238                         nth_tip_table_ent);
 239        if (0 <= found)
 240                return tip_table.table[found].refname;
 241        return NULL;
 242}
 243
 244/* may return a constant string or use "buf" as scratch space */
 245static const char *get_rev_name(const struct object *o, struct strbuf *buf)
 246{
 247        struct rev_name *n;
 248        struct commit *c;
 249
 250        if (o->type != OBJ_COMMIT)
 251                return get_exact_ref_match(o);
 252        c = (struct commit *) o;
 253        n = c->util;
 254        if (!n)
 255                return NULL;
 256
 257        if (!n->generation)
 258                return n->tip_name;
 259        else {
 260                int len = strlen(n->tip_name);
 261                if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
 262                        len -= 2;
 263                strbuf_reset(buf);
 264                strbuf_addf(buf, "%.*s~%d", len, n->tip_name, n->generation);
 265                return buf->buf;
 266        }
 267}
 268
 269static void show_name(const struct object *obj,
 270                      const char *caller_name,
 271                      int always, int allow_undefined, int name_only)
 272{
 273        const char *name;
 274        const struct object_id *oid = &obj->oid;
 275        struct strbuf buf = STRBUF_INIT;
 276
 277        if (!name_only)
 278                printf("%s ", caller_name ? caller_name : oid_to_hex(oid));
 279        name = get_rev_name(obj, &buf);
 280        if (name)
 281                printf("%s\n", name);
 282        else if (allow_undefined)
 283                printf("undefined\n");
 284        else if (always)
 285                printf("%s\n", find_unique_abbrev(oid->hash, DEFAULT_ABBREV));
 286        else
 287                die("cannot describe '%s'", oid_to_hex(oid));
 288        strbuf_release(&buf);
 289}
 290
 291static char const * const name_rev_usage[] = {
 292        N_("git name-rev [<options>] <commit>..."),
 293        N_("git name-rev [<options>] --all"),
 294        N_("git name-rev [<options>] --stdin"),
 295        NULL
 296};
 297
 298static void name_rev_line(char *p, struct name_ref_data *data)
 299{
 300        struct strbuf buf = STRBUF_INIT;
 301        int forty = 0;
 302        char *p_start;
 303        for (p_start = p; *p; p++) {
 304#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
 305                if (!ishex(*p))
 306                        forty = 0;
 307                else if (++forty == 40 &&
 308                         !ishex(*(p+1))) {
 309                        unsigned char sha1[40];
 310                        const char *name = NULL;
 311                        char c = *(p+1);
 312                        int p_len = p - p_start + 1;
 313
 314                        forty = 0;
 315
 316                        *(p+1) = 0;
 317                        if (!get_sha1(p - 39, sha1)) {
 318                                struct object *o =
 319                                        lookup_object(sha1);
 320                                if (o)
 321                                        name = get_rev_name(o, &buf);
 322                        }
 323                        *(p+1) = c;
 324
 325                        if (!name)
 326                                continue;
 327
 328                        if (data->name_only)
 329                                printf("%.*s%s", p_len - 40, p_start, name);
 330                        else
 331                                printf("%.*s (%s)", p_len, p_start, name);
 332                        p_start = p + 1;
 333                }
 334        }
 335
 336        /* flush */
 337        if (p_start != p)
 338                fwrite(p_start, p - p_start, 1, stdout);
 339
 340        strbuf_release(&buf);
 341}
 342
 343int cmd_name_rev(int argc, const char **argv, const char *prefix)
 344{
 345        struct object_array revs = OBJECT_ARRAY_INIT;
 346        int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
 347        struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP };
 348        struct option opts[] = {
 349                OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
 350                OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
 351                OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"),
 352                                   N_("only use refs matching <pattern>")),
 353                OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"),
 354                                   N_("ignore refs matching <pattern>")),
 355                OPT_GROUP(""),
 356                OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
 357                OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
 358                OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")),
 359                OPT_BOOL(0, "always",     &always,
 360                           N_("show abbreviated commit object as fallback")),
 361                {
 362                        /* A Hidden OPT_BOOL */
 363                        OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL,
 364                        N_("dereference tags in the input (internal use)"),
 365                        PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1,
 366                },
 367                OPT_END(),
 368        };
 369
 370        git_config(git_default_config, NULL);
 371        argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
 372        if (all + transform_stdin + !!argc > 1) {
 373                error("Specify either a list, or --all, not both!");
 374                usage_with_options(name_rev_usage, opts);
 375        }
 376        if (all || transform_stdin)
 377                cutoff = 0;
 378
 379        for (; argc; argc--, argv++) {
 380                unsigned char sha1[20];
 381                struct object *object;
 382                struct commit *commit;
 383
 384                if (get_sha1(*argv, sha1)) {
 385                        fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
 386                                        *argv);
 387                        continue;
 388                }
 389
 390                commit = NULL;
 391                object = parse_object(sha1);
 392                if (object) {
 393                        struct object *peeled = deref_tag(object, *argv, 0);
 394                        if (peeled && peeled->type == OBJ_COMMIT)
 395                                commit = (struct commit *)peeled;
 396                }
 397
 398                if (!object) {
 399                        fprintf(stderr, "Could not get object for %s. Skipping.\n",
 400                                        *argv);
 401                        continue;
 402                }
 403
 404                if (commit) {
 405                        if (cutoff > commit->date)
 406                                cutoff = commit->date;
 407                }
 408
 409                if (peel_tag) {
 410                        if (!commit) {
 411                                fprintf(stderr, "Could not get commit for %s. Skipping.\n",
 412                                        *argv);
 413                                continue;
 414                        }
 415                        object = (struct object *)commit;
 416                }
 417                add_object_array(object, *argv, &revs);
 418        }
 419
 420        if (cutoff)
 421                cutoff = cutoff - CUTOFF_DATE_SLOP;
 422        for_each_ref(name_ref, &data);
 423
 424        if (transform_stdin) {
 425                char buffer[2048];
 426
 427                while (!feof(stdin)) {
 428                        char *p = fgets(buffer, sizeof(buffer), stdin);
 429                        if (!p)
 430                                break;
 431                        name_rev_line(p, &data);
 432                }
 433        } else if (all) {
 434                int i, max;
 435
 436                max = get_max_object_index();
 437                for (i = 0; i < max; i++) {
 438                        struct object *obj = get_indexed_object(i);
 439                        if (!obj || obj->type != OBJ_COMMIT)
 440                                continue;
 441                        show_name(obj, NULL,
 442                                  always, allow_undefined, data.name_only);
 443                }
 444        } else {
 445                int i;
 446                for (i = 0; i < revs.nr; i++)
 447                        show_name(revs.objects[i].item, revs.objects[i].name,
 448                                  always, allow_undefined, data.name_only);
 449        }
 450
 451        return 0;
 452}