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