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