]> git.proxmox.com Git - libgit2.git/blame - src/describe.c
Upload to experimental
[libgit2.git] / src / describe.c
CommitLineData
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
27struct 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
38static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key)
39{
22a2d3d5 40 return git_oidmap_get(map, key);
3a728fb5 41}
42
43static 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
50static 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
92static 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
132static 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
156cleanup:
157 git_reference_free(ref);
158 git_object_free(peeled);
159 return error;
160}
161
886710b7 162struct 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 172struct 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
180static 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 201static 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
248struct possible_tag {
249 struct commit_name *name;
250 int depth;
251 int found_order;
252 unsigned flag_within;
253};
254
3b6534b8
CMN
255static 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 276static 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
289static 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
325static 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
356static 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
390static 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
415static 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
423static 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
618cleanup:
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
632static 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 647int 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
699cleanup:
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
718int 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(&current_id, repo, GIT_HEAD_FILE)) < 0)
731 return error;
732
ac3d33df 733 if ((error = git_object_lookup(&commit, repo, &current_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
747out:
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
759static 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
772int 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
854void 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 875int 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
883int git_describe_init_options(git_describe_options *opts, unsigned int version)
884{
885 return git_describe_options_init(opts, version);
886}
887#endif
888
889int 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
897int 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