Git fork
at reftables-rust 698 lines 18 kB view raw
1#define USE_THE_REPOSITORY_VARIABLE 2#define DISABLE_SIGN_COMPARE_WARNINGS 3 4#include "builtin.h" 5#include "environment.h" 6#include "gettext.h" 7#include "hex.h" 8#include "config.h" 9#include "commit.h" 10#include "tag.h" 11#include "refs.h" 12#include "object-name.h" 13#include "pager.h" 14#include "parse-options.h" 15#include "prio-queue.h" 16#include "hash-lookup.h" 17#include "commit-slab.h" 18#include "commit-graph.h" 19#include "wildmatch.h" 20#include "mem-pool.h" 21 22/* 23 * One day. See the 'name a rev shortly after epoch' test in t6120 when 24 * changing this value 25 */ 26#define CUTOFF_DATE_SLOP 86400 27 28struct rev_name { 29 const char *tip_name; 30 timestamp_t taggerdate; 31 int generation; 32 int distance; 33 int from_tag; 34}; 35 36define_commit_slab(commit_rev_name, struct rev_name); 37 38static timestamp_t generation_cutoff = GENERATION_NUMBER_INFINITY; 39static timestamp_t cutoff = TIME_MAX; 40static struct commit_rev_name rev_names; 41 42/* Disable the cutoff checks entirely */ 43static void disable_cutoff(void) 44{ 45 generation_cutoff = 0; 46 cutoff = 0; 47} 48 49/* Cutoff searching any commits older than this one */ 50static void set_commit_cutoff(struct commit *commit) 51{ 52 53 if (cutoff > commit->date) 54 cutoff = commit->date; 55 56 if (generation_cutoff) { 57 timestamp_t generation = commit_graph_generation(commit); 58 59 if (generation_cutoff > generation) 60 generation_cutoff = generation; 61 } 62} 63 64/* adjust the commit date cutoff with a slop to allow for slightly incorrect 65 * commit timestamps in case of clock skew. 66 */ 67static void adjust_cutoff_timestamp_for_slop(void) 68{ 69 if (cutoff) { 70 /* check for underflow */ 71 if (cutoff > TIME_MIN + CUTOFF_DATE_SLOP) 72 cutoff = cutoff - CUTOFF_DATE_SLOP; 73 else 74 cutoff = TIME_MIN; 75 } 76} 77 78/* Check if a commit is before the cutoff. Prioritize generation numbers 79 * first, but use the commit timestamp if we lack generation data. 80 */ 81static int commit_is_before_cutoff(struct commit *commit) 82{ 83 if (generation_cutoff < GENERATION_NUMBER_INFINITY) 84 return generation_cutoff && 85 commit_graph_generation(commit) < generation_cutoff; 86 87 return commit->date < cutoff; 88} 89 90/* How many generations are maximally preferred over _one_ merge traversal? */ 91#define MERGE_TRAVERSAL_WEIGHT 65535 92 93static int is_valid_rev_name(const struct rev_name *name) 94{ 95 return name && name->tip_name; 96} 97 98static struct rev_name *get_commit_rev_name(const struct commit *commit) 99{ 100 struct rev_name *name = commit_rev_name_peek(&rev_names, commit); 101 102 return is_valid_rev_name(name) ? name : NULL; 103} 104 105static int effective_distance(int distance, int generation) 106{ 107 return distance + (generation > 0 ? MERGE_TRAVERSAL_WEIGHT : 0); 108} 109 110static int is_better_name(struct rev_name *name, 111 timestamp_t taggerdate, 112 int generation, 113 int distance, 114 int from_tag) 115{ 116 int name_distance = effective_distance(name->distance, name->generation); 117 int new_distance = effective_distance(distance, generation); 118 119 /* If both are tags, we prefer the nearer one. */ 120 if (from_tag && name->from_tag) 121 return name_distance > new_distance; 122 123 /* Favor a tag over a non-tag. */ 124 if (name->from_tag != from_tag) 125 return from_tag; 126 127 /* 128 * We are now looking at two non-tags. Tiebreak to favor 129 * shorter hops. 130 */ 131 if (name_distance != new_distance) 132 return name_distance > new_distance; 133 134 /* ... or tiebreak to favor older date */ 135 if (name->taggerdate != taggerdate) 136 return name->taggerdate > taggerdate; 137 138 /* keep the current one if we cannot decide */ 139 return 0; 140} 141 142static struct rev_name *create_or_update_name(struct commit *commit, 143 timestamp_t taggerdate, 144 int generation, int distance, 145 int from_tag) 146{ 147 struct rev_name *name = commit_rev_name_at(&rev_names, commit); 148 149 if (is_valid_rev_name(name) && 150 !is_better_name(name, taggerdate, generation, distance, from_tag)) 151 return NULL; 152 153 name->taggerdate = taggerdate; 154 name->generation = generation; 155 name->distance = distance; 156 name->from_tag = from_tag; 157 158 return name; 159} 160 161static char *get_parent_name(const struct rev_name *name, int parent_number, 162 struct mem_pool *string_pool) 163{ 164 size_t len; 165 166 strip_suffix(name->tip_name, "^0", &len); 167 if (name->generation > 0) { 168 return mem_pool_strfmt(string_pool, "%.*s~%d^%d", 169 (int)len, name->tip_name, 170 name->generation, parent_number); 171 } else { 172 return mem_pool_strfmt(string_pool, "%.*s^%d", 173 (int)len, name->tip_name, parent_number); 174 } 175} 176 177static void name_rev(struct commit *start_commit, 178 const char *tip_name, timestamp_t taggerdate, 179 int from_tag, int deref, struct mem_pool *string_pool) 180{ 181 struct prio_queue queue; 182 struct commit *commit; 183 struct commit **parents_to_queue = NULL; 184 size_t parents_to_queue_nr, parents_to_queue_alloc = 0; 185 struct rev_name *start_name; 186 187 repo_parse_commit(the_repository, start_commit); 188 if (commit_is_before_cutoff(start_commit)) 189 return; 190 191 start_name = create_or_update_name(start_commit, taggerdate, 0, 0, 192 from_tag); 193 if (!start_name) 194 return; 195 if (deref) 196 start_name->tip_name = mem_pool_strfmt(string_pool, "%s^0", 197 tip_name); 198 else 199 start_name->tip_name = mem_pool_strdup(string_pool, tip_name); 200 201 memset(&queue, 0, sizeof(queue)); /* Use the prio_queue as LIFO */ 202 prio_queue_put(&queue, start_commit); 203 204 while ((commit = prio_queue_get(&queue))) { 205 struct rev_name *name = get_commit_rev_name(commit); 206 struct commit_list *parents; 207 int parent_number = 1; 208 209 parents_to_queue_nr = 0; 210 211 for (parents = commit->parents; 212 parents; 213 parents = parents->next, parent_number++) { 214 struct commit *parent = parents->item; 215 struct rev_name *parent_name; 216 int generation, distance; 217 218 repo_parse_commit(the_repository, parent); 219 if (commit_is_before_cutoff(parent)) 220 continue; 221 222 if (parent_number > 1) { 223 generation = 0; 224 distance = name->distance + MERGE_TRAVERSAL_WEIGHT; 225 } else { 226 generation = name->generation + 1; 227 distance = name->distance + 1; 228 } 229 230 parent_name = create_or_update_name(parent, taggerdate, 231 generation, 232 distance, from_tag); 233 if (parent_name) { 234 if (parent_number > 1) 235 parent_name->tip_name = 236 get_parent_name(name, 237 parent_number, 238 string_pool); 239 else 240 parent_name->tip_name = name->tip_name; 241 ALLOC_GROW(parents_to_queue, 242 parents_to_queue_nr + 1, 243 parents_to_queue_alloc); 244 parents_to_queue[parents_to_queue_nr] = parent; 245 parents_to_queue_nr++; 246 } 247 } 248 249 /* The first parent must come out first from the prio_queue */ 250 while (parents_to_queue_nr) 251 prio_queue_put(&queue, 252 parents_to_queue[--parents_to_queue_nr]); 253 } 254 255 clear_prio_queue(&queue); 256 free(parents_to_queue); 257} 258 259static int subpath_matches(const char *path, const char *filter) 260{ 261 const char *subpath = path; 262 263 while (subpath) { 264 if (!wildmatch(filter, subpath, 0)) 265 return subpath - path; 266 subpath = strchr(subpath, '/'); 267 if (subpath) 268 subpath++; 269 } 270 return -1; 271} 272 273struct name_ref_data { 274 int tags_only; 275 int name_only; 276 struct string_list ref_filters; 277 struct string_list exclude_filters; 278}; 279 280static struct tip_table { 281 struct tip_table_entry { 282 struct object_id oid; 283 const char *refname; 284 struct commit *commit; 285 timestamp_t taggerdate; 286 unsigned int from_tag:1; 287 unsigned int deref:1; 288 } *table; 289 int nr; 290 int alloc; 291 int sorted; 292} tip_table; 293 294static void add_to_tip_table(const struct object_id *oid, const char *refname, 295 int shorten_unambiguous, struct commit *commit, 296 timestamp_t taggerdate, int from_tag, int deref) 297{ 298 char *short_refname = NULL; 299 300 if (shorten_unambiguous) 301 short_refname = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), 302 refname, 0); 303 else if (skip_prefix(refname, "refs/heads/", &refname)) 304 ; /* refname already advanced */ 305 else 306 skip_prefix(refname, "refs/", &refname); 307 308 ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); 309 oidcpy(&tip_table.table[tip_table.nr].oid, oid); 310 tip_table.table[tip_table.nr].refname = short_refname ? 311 short_refname : xstrdup(refname); 312 tip_table.table[tip_table.nr].commit = commit; 313 tip_table.table[tip_table.nr].taggerdate = taggerdate; 314 tip_table.table[tip_table.nr].from_tag = from_tag; 315 tip_table.table[tip_table.nr].deref = deref; 316 tip_table.nr++; 317 tip_table.sorted = 0; 318} 319 320static int tipcmp(const void *a_, const void *b_) 321{ 322 const struct tip_table_entry *a = a_, *b = b_; 323 return oidcmp(&a->oid, &b->oid); 324} 325 326static int cmp_by_tag_and_age(const void *a_, const void *b_) 327{ 328 const struct tip_table_entry *a = a_, *b = b_; 329 int cmp; 330 331 /* Prefer tags. */ 332 cmp = b->from_tag - a->from_tag; 333 if (cmp) 334 return cmp; 335 336 /* Older is better. */ 337 if (a->taggerdate < b->taggerdate) 338 return -1; 339 return a->taggerdate != b->taggerdate; 340} 341 342static int name_ref(const char *path, const char *referent UNUSED, const struct object_id *oid, 343 int flags UNUSED, void *cb_data) 344{ 345 struct object *o = parse_object(the_repository, oid); 346 struct name_ref_data *data = cb_data; 347 int can_abbreviate_output = data->tags_only && data->name_only; 348 int deref = 0; 349 int from_tag = 0; 350 struct commit *commit = NULL; 351 timestamp_t taggerdate = TIME_MAX; 352 353 if (data->tags_only && !starts_with(path, "refs/tags/")) 354 return 0; 355 356 if (data->exclude_filters.nr) { 357 struct string_list_item *item; 358 359 for_each_string_list_item(item, &data->exclude_filters) { 360 if (subpath_matches(path, item->string) >= 0) 361 return 0; 362 } 363 } 364 365 if (data->ref_filters.nr) { 366 struct string_list_item *item; 367 int matched = 0; 368 369 /* See if any of the patterns match. */ 370 for_each_string_list_item(item, &data->ref_filters) { 371 /* 372 * Check all patterns even after finding a match, so 373 * that we can see if a match with a subpath exists. 374 * When a user asked for 'refs/tags/v*' and 'v1.*', 375 * both of which match, the user is showing her 376 * willingness to accept a shortened output by having 377 * the 'v1.*' in the acceptable refnames, so we 378 * shouldn't stop when seeing 'refs/tags/v1.4' matches 379 * 'refs/tags/v*'. We should show it as 'v1.4'. 380 */ 381 switch (subpath_matches(path, item->string)) { 382 case -1: /* did not match */ 383 break; 384 case 0: /* matched fully */ 385 matched = 1; 386 break; 387 default: /* matched subpath */ 388 matched = 1; 389 can_abbreviate_output = 1; 390 break; 391 } 392 } 393 394 /* If none of the patterns matched, stop now */ 395 if (!matched) 396 return 0; 397 } 398 399 while (o && o->type == OBJ_TAG) { 400 struct tag *t = (struct tag *) o; 401 if (!t->tagged) 402 break; /* broken repository */ 403 o = parse_object(the_repository, &t->tagged->oid); 404 deref = 1; 405 taggerdate = t->date; 406 } 407 if (o && o->type == OBJ_COMMIT) { 408 commit = (struct commit *)o; 409 from_tag = starts_with(path, "refs/tags/"); 410 if (taggerdate == TIME_MAX) 411 taggerdate = commit->date; 412 } 413 414 add_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate, 415 from_tag, deref); 416 return 0; 417} 418 419static void name_tips(struct mem_pool *string_pool) 420{ 421 int i; 422 423 /* 424 * Try to set better names first, so that worse ones spread 425 * less. 426 */ 427 QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age); 428 for (i = 0; i < tip_table.nr; i++) { 429 struct tip_table_entry *e = &tip_table.table[i]; 430 if (e->commit) { 431 name_rev(e->commit, e->refname, e->taggerdate, 432 e->from_tag, e->deref, string_pool); 433 } 434 } 435} 436 437static const struct object_id *nth_tip_table_ent(size_t ix, const void *table_) 438{ 439 const struct tip_table_entry *table = table_; 440 return &table[ix].oid; 441} 442 443static const char *get_exact_ref_match(const struct object *o) 444{ 445 int found; 446 447 if (!tip_table.table || !tip_table.nr) 448 return NULL; 449 450 if (!tip_table.sorted) { 451 QSORT(tip_table.table, tip_table.nr, tipcmp); 452 tip_table.sorted = 1; 453 } 454 455 found = oid_pos(&o->oid, tip_table.table, tip_table.nr, 456 nth_tip_table_ent); 457 if (0 <= found) 458 return tip_table.table[found].refname; 459 return NULL; 460} 461 462/* may return a constant string or use "buf" as scratch space */ 463static const char *get_rev_name(const struct object *o, struct strbuf *buf) 464{ 465 struct rev_name *n; 466 const struct commit *c; 467 468 if (o->type != OBJ_COMMIT) 469 return get_exact_ref_match(o); 470 c = (const struct commit *) o; 471 n = get_commit_rev_name(c); 472 if (!n) 473 return NULL; 474 475 if (!n->generation) 476 return n->tip_name; 477 else { 478 strbuf_reset(buf); 479 strbuf_addstr(buf, n->tip_name); 480 strbuf_strip_suffix(buf, "^0"); 481 strbuf_addf(buf, "~%d", n->generation); 482 return buf->buf; 483 } 484} 485 486static void show_name(const struct object *obj, 487 const char *caller_name, 488 int always, int allow_undefined, int name_only) 489{ 490 const char *name; 491 const struct object_id *oid = &obj->oid; 492 struct strbuf buf = STRBUF_INIT; 493 494 if (!name_only) 495 printf("%s ", caller_name ? caller_name : oid_to_hex(oid)); 496 name = get_rev_name(obj, &buf); 497 if (name) 498 printf("%s\n", name); 499 else if (allow_undefined) 500 printf("undefined\n"); 501 else if (always) 502 printf("%s\n", 503 repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV)); 504 else 505 die("cannot describe '%s'", oid_to_hex(oid)); 506 strbuf_release(&buf); 507} 508 509static char const * const name_rev_usage[] = { 510 N_("git name-rev [<options>] <commit>..."), 511 N_("git name-rev [<options>] --all"), 512 N_("git name-rev [<options>] --annotate-stdin"), 513 NULL 514}; 515 516static void name_rev_line(char *p, struct name_ref_data *data) 517{ 518 struct strbuf buf = STRBUF_INIT; 519 int counter = 0; 520 char *p_start; 521 const unsigned hexsz = the_hash_algo->hexsz; 522 523 for (p_start = p; *p; p++) { 524#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f')) 525 if (!ishex(*p)) 526 counter = 0; 527 else if (++counter == hexsz && 528 !ishex(*(p+1))) { 529 struct object_id oid; 530 const char *name = NULL; 531 char c = *(p+1); 532 int p_len = p - p_start + 1; 533 534 counter = 0; 535 536 *(p+1) = 0; 537 if (!repo_get_oid(the_repository, p - (hexsz - 1), &oid)) { 538 struct object *o = 539 lookup_object(the_repository, &oid); 540 if (o) 541 name = get_rev_name(o, &buf); 542 } 543 *(p+1) = c; 544 545 if (!name) 546 continue; 547 548 if (data->name_only) 549 printf("%.*s%s", p_len - hexsz, p_start, name); 550 else 551 printf("%.*s (%s)", p_len, p_start, name); 552 p_start = p + 1; 553 } 554 } 555 556 /* flush */ 557 if (p_start != p) 558 fwrite(p_start, p - p_start, 1, stdout); 559 560 strbuf_release(&buf); 561} 562 563int cmd_name_rev(int argc, 564 const char **argv, 565 const char *prefix, 566 struct repository *repo UNUSED) 567{ 568 struct mem_pool string_pool; 569 struct object_array revs = OBJECT_ARRAY_INIT; 570 571#ifndef WITH_BREAKING_CHANGES 572 int transform_stdin = 0; 573#endif 574 int all = 0, annotate_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; 575 struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP }; 576 struct option opts[] = { 577 OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")), 578 OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")), 579 OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"), 580 N_("only use refs matching <pattern>")), 581 OPT_STRING_LIST(0, "exclude", &data.exclude_filters, N_("pattern"), 582 N_("ignore refs matching <pattern>")), 583 OPT_GROUP(""), 584 OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), 585#ifndef WITH_BREAKING_CHANGES 586 OPT_BOOL_F(0, 587 "stdin", 588 &transform_stdin, 589 N_("deprecated: use --annotate-stdin instead"), 590 PARSE_OPT_HIDDEN), 591#endif /* WITH_BREAKING_CHANGES */ 592 OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")), 593 OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), 594 OPT_BOOL(0, "always", &always, 595 N_("show abbreviated commit object as fallback")), 596 OPT_HIDDEN_BOOL(0, "peel-tag", &peel_tag, 597 N_("dereference tags in the input (internal use)")), 598 OPT_END(), 599 }; 600 601 mem_pool_init(&string_pool, 0); 602 init_commit_rev_name(&rev_names); 603 repo_config(the_repository, git_default_config, NULL); 604 argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); 605 606#ifndef WITH_BREAKING_CHANGES 607 if (transform_stdin) { 608 warning("--stdin is deprecated. Please use --annotate-stdin instead, " 609 "which is functionally equivalent.\n" 610 "This option will be removed in a future release."); 611 annotate_stdin = 1; 612 } 613#endif 614 615 if (all + annotate_stdin + !!argc > 1) { 616 error("Specify either a list, or --all, not both!"); 617 usage_with_options(name_rev_usage, opts); 618 } 619 if (all || annotate_stdin) 620 disable_cutoff(); 621 622 for (; argc; argc--, argv++) { 623 struct object_id oid; 624 struct object *object; 625 struct commit *commit; 626 627 if (repo_get_oid(the_repository, *argv, &oid)) { 628 fprintf(stderr, "Could not get sha1 for %s. Skipping.\n", 629 *argv); 630 continue; 631 } 632 633 commit = NULL; 634 object = parse_object(the_repository, &oid); 635 if (object) { 636 struct object *peeled = deref_tag(the_repository, 637 object, *argv, 0); 638 if (peeled && peeled->type == OBJ_COMMIT) 639 commit = (struct commit *)peeled; 640 } 641 642 if (!object) { 643 fprintf(stderr, "Could not get object for %s. Skipping.\n", 644 *argv); 645 continue; 646 } 647 648 if (commit) 649 set_commit_cutoff(commit); 650 651 if (peel_tag) { 652 if (!commit) { 653 fprintf(stderr, "Could not get commit for %s. Skipping.\n", 654 *argv); 655 continue; 656 } 657 object = (struct object *)commit; 658 } 659 add_object_array(object, *argv, &revs); 660 } 661 662 adjust_cutoff_timestamp_for_slop(); 663 664 refs_for_each_ref(get_main_ref_store(the_repository), name_ref, &data); 665 name_tips(&string_pool); 666 667 if (annotate_stdin) { 668 struct strbuf sb = STRBUF_INIT; 669 670 while (strbuf_getline(&sb, stdin) != EOF) { 671 strbuf_addch(&sb, '\n'); 672 name_rev_line(sb.buf, &data); 673 } 674 strbuf_release(&sb); 675 } else if (all) { 676 int i, max; 677 678 max = get_max_object_index(the_repository); 679 for (i = 0; i < max; i++) { 680 struct object *obj = get_indexed_object(the_repository, i); 681 if (!obj || obj->type != OBJ_COMMIT) 682 continue; 683 show_name(obj, NULL, 684 always, allow_undefined, data.name_only); 685 } 686 } else { 687 int i; 688 for (i = 0; i < revs.nr; i++) 689 show_name(revs.objects[i].item, revs.objects[i].name, 690 always, allow_undefined, data.name_only); 691 } 692 693 string_list_clear(&data.ref_filters, 0); 694 string_list_clear(&data.exclude_filters, 0); 695 mem_pool_discard(&string_pool, 0); 696 object_array_clear(&revs); 697 return 0; 698}