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