]>
Commit | Line | Data |
---|---|---|
3a728fb5 | 1 | /* |
2 | * Copyright (C) the libgit2 contributors. All rights reserved. | |
3 | * | |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
eae0bfdc PP |
7 | |
8 | #include "common.h" | |
9 | ||
3a728fb5 | 10 | #include "git2/describe.h" |
fd8126e4 CMN |
11 | #include "git2/strarray.h" |
12 | #include "git2/diff.h" | |
13 | #include "git2/status.h" | |
3a728fb5 | 14 | |
3a728fb5 | 15 | #include "commit.h" |
16 | #include "commit_list.h" | |
17 | #include "oidmap.h" | |
18 | #include "refs.h" | |
22a2d3d5 | 19 | #include "repository.h" |
3a728fb5 | 20 | #include "revwalk.h" |
21 | #include "tag.h" | |
22 | #include "vector.h" | |
22a2d3d5 | 23 | #include "wildmatch.h" |
3a728fb5 | 24 | |
25 | /* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */ | |
26 | ||
27 | struct commit_name { | |
28 | git_tag *tag; | |
29 | unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ | |
30 | unsigned name_checked:1; | |
31 | git_oid sha1; | |
32 | char *path; | |
33 | ||
34 | /* Khash workaround. They original key has to still be reachable */ | |
85fe63bc | 35 | git_oid peeled; |
3a728fb5 | 36 | }; |
37 | ||
38 | static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key) | |
39 | { | |
22a2d3d5 | 40 | return git_oidmap_get(map, key); |
3a728fb5 | 41 | } |
42 | ||
43 | static struct commit_name *find_commit_name( | |
44 | git_oidmap *names, | |
45 | const git_oid *peeled) | |
46 | { | |
47 | return (struct commit_name *)(oidmap_value_bykey(names, peeled)); | |
48 | } | |
49 | ||
50 | static int replace_name( | |
51 | git_tag **tag, | |
52 | git_repository *repo, | |
53 | struct commit_name *e, | |
54 | unsigned int prio, | |
55 | const git_oid *sha1) | |
56 | { | |
57 | git_time_t e_time = 0, t_time = 0; | |
58 | ||
59 | if (!e || e->prio < prio) | |
60 | return 1; | |
61 | ||
62 | if (e->prio == 2 && prio == 2) { | |
63 | /* Multiple annotated tags point to the same commit. | |
64 | * Select one to keep based upon their tagger date. | |
65 | */ | |
66 | git_tag *t = NULL; | |
67 | ||
68 | if (!e->tag) { | |
69 | if (git_tag_lookup(&t, repo, &e->sha1) < 0) | |
70 | return 1; | |
71 | e->tag = t; | |
72 | } | |
73 | ||
74 | if (git_tag_lookup(&t, repo, sha1) < 0) | |
75 | return 0; | |
76 | ||
77 | *tag = t; | |
78 | ||
79 | if (e->tag->tagger) | |
80 | e_time = e->tag->tagger->when.time; | |
81 | ||
82 | if (t->tagger) | |
83 | t_time = t->tagger->when.time; | |
84 | ||
85 | if (e_time < t_time) | |
86 | return 1; | |
87 | } | |
88 | ||
89 | return 0; | |
90 | } | |
91 | ||
92 | static int add_to_known_names( | |
93 | git_repository *repo, | |
94 | git_oidmap *names, | |
95 | const char *path, | |
96 | const git_oid *peeled, | |
97 | unsigned int prio, | |
98 | const git_oid *sha1) | |
99 | { | |
100 | struct commit_name *e = find_commit_name(names, peeled); | |
101 | bool found = (e != NULL); | |
102 | ||
103 | git_tag *tag = NULL; | |
104 | if (replace_name(&tag, repo, e, prio, sha1)) { | |
105 | if (!found) { | |
106 | e = git__malloc(sizeof(struct commit_name)); | |
ac3d33df | 107 | GIT_ERROR_CHECK_ALLOC(e); |
3a728fb5 | 108 | |
109 | e->path = NULL; | |
110 | e->tag = NULL; | |
111 | } | |
112 | ||
113 | if (e->tag) | |
114 | git_tag_free(e->tag); | |
115 | e->tag = tag; | |
116 | e->prio = prio; | |
117 | e->name_checked = 0; | |
118 | git_oid_cpy(&e->sha1, sha1); | |
119 | git__free(e->path); | |
120 | e->path = git__strdup(path); | |
121 | git_oid_cpy(&e->peeled, peeled); | |
122 | ||
22a2d3d5 UG |
123 | if (!found && git_oidmap_set(names, &e->peeled, e) < 0) |
124 | return -1; | |
3a728fb5 | 125 | } |
126 | else | |
127 | git_tag_free(tag); | |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
132 | static int retrieve_peeled_tag_or_object_oid( | |
133 | git_oid *peeled_out, | |
134 | git_oid *ref_target_out, | |
135 | git_repository *repo, | |
136 | const char *refname) | |
137 | { | |
138 | git_reference *ref; | |
139 | git_object *peeled = NULL; | |
140 | int error; | |
141 | ||
142 | if ((error = git_reference_lookup_resolved(&ref, repo, refname, -1)) < 0) | |
143 | return error; | |
144 | ||
ac3d33df | 145 | if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_ANY)) < 0) |
3a728fb5 | 146 | goto cleanup; |
147 | ||
148 | git_oid_cpy(ref_target_out, git_reference_target(ref)); | |
149 | git_oid_cpy(peeled_out, git_object_id(peeled)); | |
150 | ||
151 | if (git_oid_cmp(ref_target_out, peeled_out) != 0) | |
152 | error = 1; /* The reference was pointing to a annotated tag */ | |
153 | else | |
154 | error = 0; /* Any other object */ | |
155 | ||
156 | cleanup: | |
157 | git_reference_free(ref); | |
158 | git_object_free(peeled); | |
159 | return error; | |
160 | } | |
161 | ||
886710b7 | 162 | struct git_describe_result { |
3b6534b8 CMN |
163 | int dirty; |
164 | int exact_match; | |
165 | int fallback_to_id; | |
166 | git_oid commit_id; | |
167 | git_repository *repo; | |
168 | struct commit_name *name; | |
169 | struct possible_tag *tag; | |
886710b7 | 170 | }; |
3b6534b8 | 171 | |
3a728fb5 | 172 | struct get_name_data |
173 | { | |
25345c0c | 174 | git_describe_options *opts; |
3a728fb5 | 175 | git_repository *repo; |
176 | git_oidmap *names; | |
3b6534b8 | 177 | git_describe_result *result; |
3a728fb5 | 178 | }; |
179 | ||
3b6534b8 CMN |
180 | static int commit_name_dup(struct commit_name **out, struct commit_name *in) |
181 | { | |
182 | struct commit_name *name; | |
183 | ||
184 | name = git__malloc(sizeof(struct commit_name)); | |
ac3d33df | 185 | GIT_ERROR_CHECK_ALLOC(name); |
3b6534b8 CMN |
186 | |
187 | memcpy(name, in, sizeof(struct commit_name)); | |
188 | name->tag = NULL; | |
189 | name->path = NULL; | |
190 | ||
f0224772 | 191 | if (in->tag && git_tag_dup(&name->tag, in->tag) < 0) |
3b6534b8 CMN |
192 | return -1; |
193 | ||
194 | name->path = git__strdup(in->path); | |
ac3d33df | 195 | GIT_ERROR_CHECK_ALLOC(name->path); |
3b6534b8 CMN |
196 | |
197 | *out = name; | |
198 | return 0; | |
199 | } | |
200 | ||
3a728fb5 | 201 | static int get_name(const char *refname, void *payload) |
202 | { | |
203 | struct get_name_data *data; | |
204 | bool is_tag, is_annotated, all; | |
205 | git_oid peeled, sha1; | |
206 | unsigned int prio; | |
207 | int error = 0; | |
208 | ||
209 | data = (struct get_name_data *)payload; | |
210 | is_tag = !git__prefixcmp(refname, GIT_REFS_TAGS_DIR); | |
211 | all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; | |
212 | ||
213 | /* Reject anything outside refs/tags/ unless --all */ | |
214 | if (!all && !is_tag) | |
215 | return 0; | |
216 | ||
217 | /* Accept only tags that match the pattern, if given */ | |
22a2d3d5 | 218 | if (data->opts->pattern && (!is_tag || wildmatch(data->opts->pattern, |
3a728fb5 | 219 | refname + strlen(GIT_REFS_TAGS_DIR), 0))) |
220 | return 0; | |
221 | ||
222 | /* Is it annotated? */ | |
223 | if ((error = retrieve_peeled_tag_or_object_oid( | |
224 | &peeled, &sha1, data->repo, refname)) < 0) | |
225 | return error; | |
226 | ||
227 | is_annotated = error; | |
228 | ||
229 | /* | |
230 | * By default, we only use annotated tags, but with --tags | |
231 | * we fall back to lightweight ones (even without --tags, | |
232 | * we still remember lightweight ones, only to give hints | |
233 | * in an error message). --all allows any refs to be used. | |
234 | */ | |
235 | if (is_annotated) | |
236 | prio = 2; | |
237 | else if (is_tag) | |
238 | prio = 1; | |
239 | else | |
240 | prio = 0; | |
241 | ||
242 | add_to_known_names(data->repo, data->names, | |
243 | all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR), | |
244 | &peeled, prio, &sha1); | |
245 | return 0; | |
246 | } | |
247 | ||
248 | struct possible_tag { | |
249 | struct commit_name *name; | |
250 | int depth; | |
251 | int found_order; | |
252 | unsigned flag_within; | |
253 | }; | |
254 | ||
3b6534b8 CMN |
255 | static int possible_tag_dup(struct possible_tag **out, struct possible_tag *in) |
256 | { | |
257 | struct possible_tag *tag; | |
6f73e026 | 258 | int error; |
3b6534b8 CMN |
259 | |
260 | tag = git__malloc(sizeof(struct possible_tag)); | |
ac3d33df | 261 | GIT_ERROR_CHECK_ALLOC(tag); |
3b6534b8 CMN |
262 | |
263 | memcpy(tag, in, sizeof(struct possible_tag)); | |
264 | tag->name = NULL; | |
265 | ||
6f73e026 JG |
266 | if ((error = commit_name_dup(&tag->name, in->name)) < 0) { |
267 | git__free(tag); | |
268 | *out = NULL; | |
269 | return error; | |
270 | } | |
3b6534b8 CMN |
271 | |
272 | *out = tag; | |
273 | return 0; | |
274 | } | |
275 | ||
3a728fb5 | 276 | static int compare_pt(const void *a_, const void *b_) |
277 | { | |
278 | struct possible_tag *a = (struct possible_tag *)a_; | |
279 | struct possible_tag *b = (struct possible_tag *)b_; | |
280 | if (a->depth != b->depth) | |
281 | return a->depth - b->depth; | |
282 | if (a->found_order != b->found_order) | |
283 | return a->found_order - b->found_order; | |
284 | return 0; | |
285 | } | |
286 | ||
287 | #define SEEN (1u << 0) | |
288 | ||
289 | static unsigned long finish_depth_computation( | |
290 | git_pqueue *list, | |
291 | git_revwalk *walk, | |
292 | struct possible_tag *best) | |
293 | { | |
294 | unsigned long seen_commits = 0; | |
295 | int error, i; | |
296 | ||
297 | while (git_pqueue_size(list) > 0) { | |
298 | git_commit_list_node *c = git_pqueue_pop(list); | |
299 | seen_commits++; | |
300 | if (c->flags & best->flag_within) { | |
301 | size_t index = 0; | |
302 | while (git_pqueue_size(list) > index) { | |
303 | git_commit_list_node *i = git_pqueue_get(list, index); | |
304 | if (!(i->flags & best->flag_within)) | |
305 | break; | |
306 | index++; | |
307 | } | |
308 | if (index > git_pqueue_size(list)) | |
309 | break; | |
310 | } else | |
311 | best->depth++; | |
312 | for (i = 0; i < c->out_degree; i++) { | |
313 | git_commit_list_node *p = c->parents[i]; | |
314 | if ((error = git_commit_list_parse(walk, p)) < 0) | |
315 | return error; | |
316 | if (!(p->flags & SEEN)) | |
317 | if ((error = git_pqueue_insert(list, p)) < 0) | |
318 | return error; | |
319 | p->flags |= c->flags; | |
320 | } | |
321 | } | |
322 | return seen_commits; | |
323 | } | |
324 | ||
325 | static int display_name(git_buf *buf, git_repository *repo, struct commit_name *n) | |
326 | { | |
327 | if (n->prio == 2 && !n->tag) { | |
328 | if (git_tag_lookup(&n->tag, repo, &n->sha1) < 0) { | |
ac3d33df | 329 | git_error_set(GIT_ERROR_TAG, "annotated tag '%s' not available", n->path); |
3a728fb5 | 330 | return -1; |
331 | } | |
332 | } | |
333 | ||
334 | if (n->tag && !n->name_checked) { | |
335 | if (!git_tag_name(n->tag)) { | |
ac3d33df | 336 | git_error_set(GIT_ERROR_TAG, "annotated tag '%s' has no embedded name", n->path); |
3a728fb5 | 337 | return -1; |
338 | } | |
339 | ||
340 | /* TODO: Cope with warnings | |
341 | if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) | |
342 | warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); | |
343 | */ | |
344 | ||
345 | n->name_checked = 1; | |
346 | } | |
347 | ||
348 | if (n->tag) | |
349 | git_buf_printf(buf, "%s", git_tag_name(n->tag)); | |
350 | else | |
351 | git_buf_printf(buf, "%s", n->path); | |
352 | ||
353 | return 0; | |
354 | } | |
355 | ||
356 | static int find_unique_abbrev_size( | |
357 | int *out, | |
55f1b6b6 | 358 | git_repository *repo, |
3a728fb5 | 359 | const git_oid *oid_in, |
ac3d33df | 360 | unsigned int abbreviated_size) |
3a728fb5 | 361 | { |
55f1b6b6 CMN |
362 | size_t size = abbreviated_size; |
363 | git_odb *odb; | |
364 | git_oid dummy; | |
365 | int error; | |
366 | ||
367 | if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) | |
368 | return error; | |
3a728fb5 | 369 | |
55f1b6b6 CMN |
370 | while (size < GIT_OID_HEXSZ) { |
371 | if ((error = git_odb_exists_prefix(&dummy, odb, oid_in, size)) == 0) { | |
372 | *out = (int) size; | |
373 | return 0; | |
374 | } | |
3a728fb5 | 375 | |
55f1b6b6 CMN |
376 | /* If the error wasn't that it's not unique, then it's a proper error */ |
377 | if (error != GIT_EAMBIGUOUS) | |
378 | return error; | |
379 | ||
380 | /* Try again with a larger size */ | |
381 | size++; | |
382 | } | |
383 | ||
384 | /* If we didn't find any shorter prefix, we have to do the whole thing */ | |
3a728fb5 | 385 | *out = GIT_OID_HEXSZ; |
ac3d33df | 386 | |
3a728fb5 | 387 | return 0; |
388 | } | |
389 | ||
390 | static int show_suffix( | |
391 | git_buf *buf, | |
392 | int depth, | |
55f1b6b6 | 393 | git_repository *repo, |
c25aa7cd | 394 | const git_oid *id, |
ac3d33df | 395 | unsigned int abbrev_size) |
3a728fb5 | 396 | { |
369b0217 | 397 | int error, size = 0; |
3a728fb5 | 398 | |
399 | char hex_oid[GIT_OID_HEXSZ]; | |
400 | ||
55f1b6b6 | 401 | if ((error = find_unique_abbrev_size(&size, repo, id, abbrev_size)) < 0) |
3a728fb5 | 402 | return error; |
403 | ||
404 | git_oid_fmt(hex_oid, id); | |
405 | ||
406 | git_buf_printf(buf, "-%d-g", depth); | |
407 | ||
408 | git_buf_put(buf, hex_oid, size); | |
3a728fb5 | 409 | |
410 | return git_buf_oom(buf) ? -1 : 0; | |
411 | } | |
412 | ||
413 | #define MAX_CANDIDATES_TAGS FLAG_BITS - 1 | |
414 | ||
415 | static int describe_not_found(const git_oid *oid, const char *message_format) { | |
416 | char oid_str[GIT_OID_HEXSZ + 1]; | |
417 | git_oid_tostr(oid_str, sizeof(oid_str), oid); | |
418 | ||
ac3d33df | 419 | git_error_set(GIT_ERROR_DESCRIBE, message_format, oid_str); |
3a728fb5 | 420 | return GIT_ENOTFOUND; |
421 | } | |
422 | ||
423 | static int describe( | |
3a728fb5 | 424 | struct get_name_data *data, |
3b6534b8 | 425 | git_commit *commit) |
3a728fb5 | 426 | { |
427 | struct commit_name *n; | |
428 | struct possible_tag *best; | |
429 | bool all, tags; | |
3a728fb5 | 430 | git_revwalk *walk = NULL; |
431 | git_pqueue list; | |
432 | git_commit_list_node *cmit, *gave_up_on = NULL; | |
433 | git_vector all_matches = GIT_VECTOR_INIT; | |
434 | unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; | |
435 | unsigned long seen_commits = 0; /* TODO: Check long */ | |
436 | unsigned int unannotated_cnt = 0; | |
437 | int error; | |
438 | ||
439 | if (git_vector_init(&all_matches, MAX_CANDIDATES_TAGS, compare_pt) < 0) | |
440 | return -1; | |
441 | ||
442 | if ((error = git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp)) < 0) | |
443 | goto cleanup; | |
444 | ||
445 | all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; | |
446 | tags = data->opts->describe_strategy == GIT_DESCRIBE_TAGS; | |
447 | ||
3b6534b8 CMN |
448 | git_oid_cpy(&data->result->commit_id, git_commit_id(commit)); |
449 | ||
3a728fb5 | 450 | n = find_commit_name(data->names, git_commit_id(commit)); |
451 | if (n && (tags || all || n->prio == 2)) { | |
452 | /* | |
453 | * Exact match to an existing ref. | |
454 | */ | |
3b6534b8 CMN |
455 | data->result->exact_match = 1; |
456 | if ((error = commit_name_dup(&data->result->name, n)) < 0) | |
3a728fb5 | 457 | goto cleanup; |
458 | ||
3b6534b8 | 459 | goto cleanup; |
3a728fb5 | 460 | } |
461 | ||
462 | if (!data->opts->max_candidates_tags) { | |
463 | error = describe_not_found( | |
464 | git_commit_id(commit), | |
909d5494 | 465 | "cannot describe - no tag exactly matches '%s'"); |
3a728fb5 | 466 | |
467 | goto cleanup; | |
468 | } | |
469 | ||
470 | if ((error = git_revwalk_new(&walk, git_commit_owner(commit))) < 0) | |
471 | goto cleanup; | |
472 | ||
473 | if ((cmit = git_revwalk__commit_lookup(walk, git_commit_id(commit))) == NULL) | |
474 | goto cleanup; | |
475 | ||
476 | if ((error = git_commit_list_parse(walk, cmit)) < 0) | |
477 | goto cleanup; | |
478 | ||
479 | cmit->flags = SEEN; | |
480 | ||
481 | if ((error = git_pqueue_insert(&list, cmit)) < 0) | |
482 | goto cleanup; | |
483 | ||
484 | while (git_pqueue_size(&list) > 0) | |
485 | { | |
486 | int i; | |
487 | ||
488 | git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list); | |
489 | seen_commits++; | |
490 | ||
491 | n = find_commit_name(data->names, &c->oid); | |
492 | ||
493 | if (n) { | |
494 | if (!tags && !all && n->prio < 2) { | |
495 | unannotated_cnt++; | |
496 | } else if (match_cnt < data->opts->max_candidates_tags) { | |
497 | struct possible_tag *t = git__malloc(sizeof(struct commit_name)); | |
ac3d33df | 498 | GIT_ERROR_CHECK_ALLOC(t); |
3a728fb5 | 499 | if ((error = git_vector_insert(&all_matches, t)) < 0) |
500 | goto cleanup; | |
501 | ||
502 | match_cnt++; | |
503 | ||
504 | t->name = n; | |
505 | t->depth = seen_commits - 1; | |
506 | t->flag_within = 1u << match_cnt; | |
507 | t->found_order = match_cnt; | |
508 | c->flags |= t->flag_within; | |
509 | if (n->prio == 2) | |
510 | annotated_cnt++; | |
511 | } | |
512 | else { | |
513 | gave_up_on = c; | |
514 | break; | |
515 | } | |
516 | } | |
517 | ||
518 | for (cur_match = 0; cur_match < match_cnt; cur_match++) { | |
519 | struct possible_tag *t = git_vector_get(&all_matches, cur_match); | |
520 | if (!(c->flags & t->flag_within)) | |
521 | t->depth++; | |
522 | } | |
523 | ||
524 | if (annotated_cnt && (git_pqueue_size(&list) == 0)) { | |
525 | /* | |
526 | if (debug) { | |
527 | char oid_str[GIT_OID_HEXSZ + 1]; | |
528 | git_oid_tostr(oid_str, sizeof(oid_str), &c->oid); | |
529 | ||
530 | fprintf(stderr, "finished search at %s\n", oid_str); | |
531 | } | |
532 | */ | |
533 | break; | |
534 | } | |
535 | for (i = 0; i < c->out_degree; i++) { | |
536 | git_commit_list_node *p = c->parents[i]; | |
537 | if ((error = git_commit_list_parse(walk, p)) < 0) | |
538 | goto cleanup; | |
539 | if (!(p->flags & SEEN)) | |
540 | if ((error = git_pqueue_insert(&list, p)) < 0) | |
541 | goto cleanup; | |
542 | p->flags |= c->flags; | |
543 | ||
544 | if (data->opts->only_follow_first_parent) | |
545 | break; | |
546 | } | |
547 | } | |
548 | ||
549 | if (!match_cnt) { | |
550 | if (data->opts->show_commit_oid_as_fallback) { | |
3b6534b8 CMN |
551 | data->result->fallback_to_id = 1; |
552 | git_oid_cpy(&data->result->commit_id, &cmit->oid); | |
3a728fb5 | 553 | |
3b6534b8 | 554 | goto cleanup; |
3a728fb5 | 555 | } |
556 | if (unannotated_cnt) { | |
ac3d33df | 557 | error = describe_not_found(git_commit_id(commit), |
909d5494 ET |
558 | "cannot describe - " |
559 | "no annotated tags can describe '%s'; " | |
560 | "however, there were unannotated tags."); | |
3a728fb5 | 561 | goto cleanup; |
562 | } | |
563 | else { | |
ac3d33df | 564 | error = describe_not_found(git_commit_id(commit), |
909d5494 ET |
565 | "cannot describe - " |
566 | "no tags can describe '%s'."); | |
3a728fb5 | 567 | goto cleanup; |
568 | } | |
569 | } | |
570 | ||
3a728fb5 | 571 | git_vector_sort(&all_matches); |
572 | ||
573 | best = (struct possible_tag *)git_vector_get(&all_matches, 0); | |
574 | ||
575 | if (gave_up_on) { | |
345758ad PS |
576 | if ((error = git_pqueue_insert(&list, gave_up_on)) < 0) |
577 | goto cleanup; | |
3a728fb5 | 578 | seen_commits--; |
579 | } | |
580 | if ((error = finish_depth_computation( | |
581 | &list, walk, best)) < 0) | |
582 | goto cleanup; | |
3b6534b8 | 583 | |
3a728fb5 | 584 | seen_commits += error; |
3b6534b8 CMN |
585 | if ((error = possible_tag_dup(&data->result->tag, best)) < 0) |
586 | goto cleanup; | |
3a728fb5 | 587 | |
588 | /* | |
589 | { | |
590 | static const char *prio_names[] = { | |
591 | "head", "lightweight", "annotated", | |
592 | }; | |
593 | ||
594 | char oid_str[GIT_OID_HEXSZ + 1]; | |
595 | ||
596 | if (debug) { | |
597 | for (cur_match = 0; cur_match < match_cnt; cur_match++) { | |
598 | struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match); | |
599 | fprintf(stderr, " %-11s %8d %s\n", | |
600 | prio_names[t->name->prio], | |
601 | t->depth, t->name->path); | |
602 | } | |
603 | fprintf(stderr, "traversed %lu commits\n", seen_commits); | |
604 | if (gave_up_on) { | |
605 | git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid); | |
606 | fprintf(stderr, | |
607 | "more than %i tags found; listed %i most recent\n" | |
608 | "gave up search at %s\n", | |
609 | data->opts->max_candidates_tags, data->opts->max_candidates_tags, | |
610 | oid_str); | |
611 | } | |
612 | } | |
613 | } | |
614 | */ | |
615 | ||
3b6534b8 | 616 | git_oid_cpy(&data->result->commit_id, &cmit->oid); |
3a728fb5 | 617 | |
618 | cleanup: | |
619 | { | |
620 | size_t i; | |
621 | struct possible_tag *match; | |
622 | git_vector_foreach(&all_matches, i, match) { | |
623 | git__free(match); | |
624 | } | |
625 | } | |
626 | git_vector_free(&all_matches); | |
627 | git_pqueue_free(&list); | |
628 | git_revwalk_free(walk); | |
629 | return error; | |
630 | } | |
631 | ||
632 | static int normalize_options( | |
25345c0c CMN |
633 | git_describe_options *dst, |
634 | const git_describe_options *src) | |
3a728fb5 | 635 | { |
25345c0c | 636 | git_describe_options default_options = GIT_DESCRIBE_OPTIONS_INIT; |
3a728fb5 | 637 | if (!src) src = &default_options; |
638 | ||
639 | *dst = *src; | |
640 | ||
641 | if (dst->max_candidates_tags > GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS) | |
642 | dst->max_candidates_tags = GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS; | |
643 | ||
3a728fb5 | 644 | return 0; |
645 | } | |
646 | ||
1f501a08 | 647 | int git_describe_commit( |
3b6534b8 | 648 | git_describe_result **result, |
3a728fb5 | 649 | git_object *committish, |
25345c0c | 650 | git_describe_options *opts) |
3a728fb5 | 651 | { |
652 | struct get_name_data data; | |
653 | struct commit_name *name; | |
654 | git_commit *commit; | |
655 | int error = -1; | |
25345c0c | 656 | git_describe_options normalized; |
3a728fb5 | 657 | |
c25aa7cd PP |
658 | GIT_ASSERT_ARG(result); |
659 | GIT_ASSERT_ARG(committish); | |
3b6534b8 CMN |
660 | |
661 | data.result = git__calloc(1, sizeof(git_describe_result)); | |
ac3d33df | 662 | GIT_ERROR_CHECK_ALLOC(data.result); |
3b6534b8 | 663 | data.result->repo = git_object_owner(committish); |
3a728fb5 | 664 | |
3a728fb5 | 665 | data.repo = git_object_owner(committish); |
666 | ||
25345c0c | 667 | if ((error = normalize_options(&normalized, opts)) < 0) |
3a728fb5 | 668 | return error; |
669 | ||
ac3d33df | 670 | GIT_ERROR_CHECK_VERSION( |
25345c0c | 671 | &normalized, |
3a728fb5 | 672 | GIT_DESCRIBE_OPTIONS_VERSION, |
25345c0c | 673 | "git_describe_options"); |
0494a7c9 | 674 | data.opts = &normalized; |
3a728fb5 | 675 | |
22a2d3d5 UG |
676 | if ((error = git_oidmap_new(&data.names)) < 0) |
677 | return error; | |
3a728fb5 | 678 | |
679 | /** TODO: contains to be implemented */ | |
680 | ||
ac3d33df | 681 | if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJECT_COMMIT)) < 0) |
3a728fb5 | 682 | goto cleanup; |
683 | ||
32e2b758 | 684 | if ((error = git_reference_foreach_name( |
3a728fb5 | 685 | git_object_owner(committish), |
32e2b758 | 686 | get_name, &data)) < 0) |
3a728fb5 | 687 | goto cleanup; |
688 | ||
22a2d3d5 | 689 | if (git_oidmap_size(data.names) == 0 && !normalized.show_commit_oid_as_fallback) { |
ac3d33df | 690 | git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " |
909d5494 | 691 | "no reference found, cannot describe anything."); |
3a728fb5 | 692 | error = -1; |
693 | goto cleanup; | |
694 | } | |
695 | ||
3b6534b8 | 696 | if ((error = describe(&data, commit)) < 0) |
3a728fb5 | 697 | goto cleanup; |
698 | ||
699 | cleanup: | |
3b6534b8 CMN |
700 | git_commit_free(commit); |
701 | ||
3a728fb5 | 702 | git_oidmap_foreach_value(data.names, name, { |
703 | git_tag_free(name->tag); | |
704 | git__free(name->path); | |
705 | git__free(name); | |
706 | }); | |
707 | ||
708 | git_oidmap_free(data.names); | |
3b6534b8 CMN |
709 | |
710 | if (error < 0) | |
711 | git_describe_result_free(data.result); | |
712 | else | |
713 | *result = data.result; | |
3a728fb5 | 714 | |
715 | return error; | |
716 | } | |
3b6534b8 | 717 | |
fd8126e4 CMN |
718 | int git_describe_workdir( |
719 | git_describe_result **out, | |
720 | git_repository *repo, | |
25345c0c | 721 | git_describe_options *opts) |
fd8126e4 CMN |
722 | { |
723 | int error; | |
724 | git_oid current_id; | |
725 | git_status_list *status = NULL; | |
726 | git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; | |
64bcf567 | 727 | git_describe_result *result = NULL; |
fd8126e4 CMN |
728 | git_object *commit; |
729 | ||
730 | if ((error = git_reference_name_to_id(¤t_id, repo, GIT_HEAD_FILE)) < 0) | |
731 | return error; | |
732 | ||
ac3d33df | 733 | if ((error = git_object_lookup(&commit, repo, ¤t_id, GIT_OBJECT_COMMIT)) < 0) |
fd8126e4 CMN |
734 | return error; |
735 | ||
736 | /* The first step is to perform a describe of HEAD, so we can leverage this */ | |
737 | if ((error = git_describe_commit(&result, commit, opts)) < 0) | |
738 | goto out; | |
739 | ||
740 | if ((error = git_status_list_new(&status, repo, &status_opts)) < 0) | |
741 | goto out; | |
742 | ||
743 | ||
744 | if (git_status_list_entrycount(status) > 0) | |
745 | result->dirty = 1; | |
746 | ||
747 | out: | |
748 | git_object_free(commit); | |
749 | git_status_list_free(status); | |
750 | ||
751 | if (error < 0) | |
752 | git_describe_result_free(result); | |
753 | else | |
754 | *out = result; | |
755 | ||
756 | return error; | |
757 | } | |
758 | ||
59186d9b L |
759 | static int normalize_format_options( |
760 | git_describe_format_options *dst, | |
761 | const git_describe_format_options *src) | |
762 | { | |
763 | if (!src) { | |
22a2d3d5 | 764 | git_describe_format_options_init(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); |
59186d9b L |
765 | return 0; |
766 | } | |
767 | ||
768 | memcpy(dst, src, sizeof(git_describe_format_options)); | |
769 | return 0; | |
770 | } | |
771 | ||
772 | int git_describe_format(git_buf *out, const git_describe_result *result, const git_describe_format_options *given) | |
3b6534b8 CMN |
773 | { |
774 | int error; | |
775 | git_repository *repo; | |
776 | struct commit_name *name; | |
59186d9b | 777 | git_describe_format_options opts; |
3b6534b8 | 778 | |
c25aa7cd PP |
779 | GIT_ASSERT_ARG(out); |
780 | GIT_ASSERT_ARG(result); | |
3b6534b8 | 781 | |
ac3d33df | 782 | GIT_ERROR_CHECK_VERSION(given, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, "git_describe_format_options"); |
59186d9b L |
783 | normalize_format_options(&opts, given); |
784 | ||
c25aa7cd PP |
785 | if ((error = git_buf_sanitize(out)) < 0) |
786 | return error; | |
3b6534b8 CMN |
787 | |
788 | ||
59186d9b | 789 | if (opts.always_use_long_format && opts.abbreviated_size == 0) { |
ac3d33df | 790 | git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - " |
3b6534b8 CMN |
791 | "'always_use_long_format' is incompatible with a zero" |
792 | "'abbreviated_size'"); | |
793 | return -1; | |
794 | } | |
795 | ||
796 | ||
797 | repo = result->repo; | |
798 | ||
799 | /* If we did find an exact match, then it's the easier method */ | |
800 | if (result->exact_match) { | |
801 | name = result->name; | |
802 | if ((error = display_name(out, repo, name)) < 0) | |
803 | return error; | |
804 | ||
59186d9b | 805 | if (opts.always_use_long_format) { |
3b6534b8 | 806 | const git_oid *id = name->tag ? git_tag_target_id(name->tag) : &result->commit_id; |
59186d9b | 807 | if ((error = show_suffix(out, 0, repo, id, opts.abbreviated_size)) < 0) |
3b6534b8 CMN |
808 | return error; |
809 | } | |
810 | ||
59186d9b L |
811 | if (result->dirty && opts.dirty_suffix) |
812 | git_buf_puts(out, opts.dirty_suffix); | |
3b6534b8 CMN |
813 | |
814 | return git_buf_oom(out) ? -1 : 0; | |
815 | } | |
816 | ||
817 | /* If we didn't find *any* tags, we fall back to the commit's id */ | |
818 | if (result->fallback_to_id) { | |
819 | char hex_oid[GIT_OID_HEXSZ + 1] = {0}; | |
369b0217 ET |
820 | int size = 0; |
821 | ||
3b6534b8 | 822 | if ((error = find_unique_abbrev_size( |
59186d9b | 823 | &size, repo, &result->commit_id, opts.abbreviated_size)) < 0) |
3b6534b8 CMN |
824 | return -1; |
825 | ||
826 | git_oid_fmt(hex_oid, &result->commit_id); | |
827 | git_buf_put(out, hex_oid, size); | |
828 | ||
59186d9b L |
829 | if (result->dirty && opts.dirty_suffix) |
830 | git_buf_puts(out, opts.dirty_suffix); | |
3b6534b8 CMN |
831 | |
832 | return git_buf_oom(out) ? -1 : 0; | |
833 | } | |
834 | ||
835 | /* Lastly, if we found a matching tag, we show that */ | |
836 | name = result->tag->name; | |
837 | ||
838 | if ((error = display_name(out, repo, name)) < 0) | |
839 | return error; | |
840 | ||
59186d9b | 841 | if (opts.abbreviated_size) { |
55f1b6b6 | 842 | if ((error = show_suffix(out, result->tag->depth, repo, |
59186d9b | 843 | &result->commit_id, opts.abbreviated_size)) < 0) |
fd8126e4 | 844 | return error; |
3b6534b8 CMN |
845 | } |
846 | ||
59186d9b L |
847 | if (result->dirty && opts.dirty_suffix) { |
848 | git_buf_puts(out, opts.dirty_suffix); | |
fd8126e4 | 849 | } |
3b6534b8 CMN |
850 | |
851 | return git_buf_oom(out) ? -1 : 0; | |
852 | } | |
853 | ||
854 | void git_describe_result_free(git_describe_result *result) | |
855 | { | |
856 | if (result == NULL) | |
857 | return; | |
858 | ||
859 | if (result->name) { | |
860 | git_tag_free(result->name->tag); | |
861 | git__free(result->name->path); | |
862 | git__free(result->name); | |
863 | } | |
864 | ||
865 | if (result->tag) { | |
866 | git_tag_free(result->tag->name->tag); | |
867 | git__free(result->tag->name->path); | |
868 | git__free(result->tag->name); | |
869 | git__free(result->tag); | |
870 | } | |
871 | ||
872 | git__free(result); | |
873 | } | |
25345c0c | 874 | |
22a2d3d5 | 875 | int git_describe_options_init(git_describe_options *opts, unsigned int version) |
25345c0c CMN |
876 | { |
877 | GIT_INIT_STRUCTURE_FROM_TEMPLATE( | |
878 | opts, version, git_describe_options, GIT_DESCRIBE_OPTIONS_INIT); | |
879 | return 0; | |
880 | } | |
881 | ||
22a2d3d5 UG |
882 | #ifndef GIT_DEPRECATE_HARD |
883 | int git_describe_init_options(git_describe_options *opts, unsigned int version) | |
884 | { | |
885 | return git_describe_options_init(opts, version); | |
886 | } | |
887 | #endif | |
888 | ||
889 | int git_describe_format_options_init(git_describe_format_options *opts, unsigned int version) | |
25345c0c CMN |
890 | { |
891 | GIT_INIT_STRUCTURE_FROM_TEMPLATE( | |
892 | opts, version, git_describe_format_options, GIT_DESCRIBE_FORMAT_OPTIONS_INIT); | |
893 | return 0; | |
894 | } | |
22a2d3d5 UG |
895 | |
896 | #ifndef GIT_DEPRECATE_HARD | |
897 | int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version) | |
898 | { | |
899 | return git_describe_format_options_init(opts, version); | |
900 | } | |
901 | #endif |