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