2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
10 #include "git2/describe.h"
11 #include "git2/strarray.h"
12 #include "git2/diff.h"
13 #include "git2/status.h"
16 #include "commit_list.h"
19 #include "repository.h"
23 #include "wildmatch.h"
25 /* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */
29 unsigned prio
:2; /* annotated tag = 2, tag = 1, head = 0 */
30 unsigned name_checked
:1;
34 /* Khash workaround. They original key has to still be reachable */
38 static void *oidmap_value_bykey(git_oidmap
*map
, const git_oid
*key
)
40 return git_oidmap_get(map
, key
);
43 static struct commit_name
*find_commit_name(
45 const git_oid
*peeled
)
47 return (struct commit_name
*)(oidmap_value_bykey(names
, peeled
));
50 static int replace_name(
53 struct commit_name
*e
,
57 git_time_t e_time
= 0, t_time
= 0;
59 if (!e
|| e
->prio
< prio
)
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.
69 if (git_tag_lookup(&t
, repo
, &e
->sha1
) < 0)
74 if (git_tag_lookup(&t
, repo
, sha1
) < 0)
80 e_time
= e
->tag
->tagger
->when
.time
;
83 t_time
= t
->tagger
->when
.time
;
92 static int add_to_known_names(
96 const git_oid
*peeled
,
100 struct commit_name
*e
= find_commit_name(names
, peeled
);
101 bool found
= (e
!= NULL
);
104 if (replace_name(&tag
, repo
, e
, prio
, sha1
)) {
106 e
= git__malloc(sizeof(struct commit_name
));
107 GIT_ERROR_CHECK_ALLOC(e
);
114 git_tag_free(e
->tag
);
118 git_oid_cpy(&e
->sha1
, sha1
);
120 e
->path
= git__strdup(path
);
121 git_oid_cpy(&e
->peeled
, peeled
);
123 if (!found
&& git_oidmap_set(names
, &e
->peeled
, e
) < 0)
132 static int retrieve_peeled_tag_or_object_oid(
134 git_oid
*ref_target_out
,
135 git_repository
*repo
,
139 git_object
*peeled
= NULL
;
142 if ((error
= git_reference_lookup_resolved(&ref
, repo
, refname
, -1)) < 0)
145 if ((error
= git_reference_peel(&peeled
, ref
, GIT_OBJECT_ANY
)) < 0)
148 git_oid_cpy(ref_target_out
, git_reference_target(ref
));
149 git_oid_cpy(peeled_out
, git_object_id(peeled
));
151 if (git_oid_cmp(ref_target_out
, peeled_out
) != 0)
152 error
= 1; /* The reference was pointing to a annotated tag */
154 error
= 0; /* Any other object */
157 git_reference_free(ref
);
158 git_object_free(peeled
);
162 struct git_describe_result
{
167 git_repository
*repo
;
168 struct commit_name
*name
;
169 struct possible_tag
*tag
;
174 git_describe_options
*opts
;
175 git_repository
*repo
;
177 git_describe_result
*result
;
180 static int commit_name_dup(struct commit_name
**out
, struct commit_name
*in
)
182 struct commit_name
*name
;
184 name
= git__malloc(sizeof(struct commit_name
));
185 GIT_ERROR_CHECK_ALLOC(name
);
187 memcpy(name
, in
, sizeof(struct commit_name
));
191 if (in
->tag
&& git_tag_dup(&name
->tag
, in
->tag
) < 0)
194 name
->path
= git__strdup(in
->path
);
195 GIT_ERROR_CHECK_ALLOC(name
->path
);
201 static int get_name(const char *refname
, void *payload
)
203 struct get_name_data
*data
;
204 bool is_tag
, is_annotated
, all
;
205 git_oid peeled
, sha1
;
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
;
213 /* Reject anything outside refs/tags/ unless --all */
217 /* Accept only tags that match the pattern, if given */
218 if (data
->opts
->pattern
&& (!is_tag
|| wildmatch(data
->opts
->pattern
,
219 refname
+ strlen(GIT_REFS_TAGS_DIR
), 0)))
222 /* Is it annotated? */
223 if ((error
= retrieve_peeled_tag_or_object_oid(
224 &peeled
, &sha1
, data
->repo
, refname
)) < 0)
227 is_annotated
= error
;
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.
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
);
248 struct possible_tag
{
249 struct commit_name
*name
;
252 unsigned flag_within
;
255 static int possible_tag_dup(struct possible_tag
**out
, struct possible_tag
*in
)
257 struct possible_tag
*tag
;
260 tag
= git__malloc(sizeof(struct possible_tag
));
261 GIT_ERROR_CHECK_ALLOC(tag
);
263 memcpy(tag
, in
, sizeof(struct possible_tag
));
266 if ((error
= commit_name_dup(&tag
->name
, in
->name
)) < 0) {
276 static int compare_pt(const void *a_
, const void *b_
)
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
;
287 #define SEEN (1u << 0)
289 static unsigned long finish_depth_computation(
292 struct possible_tag
*best
)
294 unsigned long seen_commits
= 0;
297 while (git_pqueue_size(list
) > 0) {
298 git_commit_list_node
*c
= git_pqueue_pop(list
);
300 if (c
->flags
& best
->flag_within
) {
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
))
308 if (index
> git_pqueue_size(list
))
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)
316 if (!(p
->flags
& SEEN
))
317 if ((error
= git_pqueue_insert(list
, p
)) < 0)
319 p
->flags
|= c
->flags
;
325 static int display_name(git_buf
*buf
, git_repository
*repo
, struct commit_name
*n
)
327 if (n
->prio
== 2 && !n
->tag
) {
328 if (git_tag_lookup(&n
->tag
, repo
, &n
->sha1
) < 0) {
329 git_error_set(GIT_ERROR_TAG
, "annotated tag '%s' not available", n
->path
);
334 if (n
->tag
&& !n
->name_checked
) {
335 if (!git_tag_name(n
->tag
)) {
336 git_error_set(GIT_ERROR_TAG
, "annotated tag '%s' has no embedded name", n
->path
);
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);
349 git_buf_printf(buf
, "%s", git_tag_name(n
->tag
));
351 git_buf_printf(buf
, "%s", n
->path
);
356 static int find_unique_abbrev_size(
358 git_repository
*repo
,
359 const git_oid
*oid_in
,
360 unsigned int abbreviated_size
)
362 size_t size
= abbreviated_size
;
367 if ((error
= git_repository_odb__weakptr(&odb
, repo
)) < 0)
370 while (size
< GIT_OID_HEXSZ
) {
371 if ((error
= git_odb_exists_prefix(&dummy
, odb
, oid_in
, size
)) == 0) {
376 /* If the error wasn't that it's not unique, then it's a proper error */
377 if (error
!= GIT_EAMBIGUOUS
)
380 /* Try again with a larger size */
384 /* If we didn't find any shorter prefix, we have to do the whole thing */
385 *out
= GIT_OID_HEXSZ
;
390 static int show_suffix(
393 git_repository
*repo
,
395 unsigned int abbrev_size
)
399 char hex_oid
[GIT_OID_HEXSZ
];
401 if ((error
= find_unique_abbrev_size(&size
, repo
, id
, abbrev_size
)) < 0)
404 git_oid_fmt(hex_oid
, id
);
406 git_buf_printf(buf
, "-%d-g", depth
);
408 git_buf_put(buf
, hex_oid
, size
);
410 return git_buf_oom(buf
) ? -1 : 0;
413 #define MAX_CANDIDATES_TAGS FLAG_BITS - 1
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
);
419 git_error_set(GIT_ERROR_DESCRIBE
, message_format
, oid_str
);
420 return GIT_ENOTFOUND
;
424 struct get_name_data
*data
,
427 struct commit_name
*n
;
428 struct possible_tag
*best
;
430 git_revwalk
*walk
= NULL
;
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;
439 if (git_vector_init(&all_matches
, MAX_CANDIDATES_TAGS
, compare_pt
) < 0)
442 if ((error
= git_pqueue_init(&list
, 0, 2, git_commit_list_time_cmp
)) < 0)
445 all
= data
->opts
->describe_strategy
== GIT_DESCRIBE_ALL
;
446 tags
= data
->opts
->describe_strategy
== GIT_DESCRIBE_TAGS
;
448 git_oid_cpy(&data
->result
->commit_id
, git_commit_id(commit
));
450 n
= find_commit_name(data
->names
, git_commit_id(commit
));
451 if (n
&& (tags
|| all
|| n
->prio
== 2)) {
453 * Exact match to an existing ref.
455 data
->result
->exact_match
= 1;
456 if ((error
= commit_name_dup(&data
->result
->name
, n
)) < 0)
462 if (!data
->opts
->max_candidates_tags
) {
463 error
= describe_not_found(
464 git_commit_id(commit
),
465 "cannot describe - no tag exactly matches '%s'");
470 if ((error
= git_revwalk_new(&walk
, git_commit_owner(commit
))) < 0)
473 if ((cmit
= git_revwalk__commit_lookup(walk
, git_commit_id(commit
))) == NULL
)
476 if ((error
= git_commit_list_parse(walk
, cmit
)) < 0)
481 if ((error
= git_pqueue_insert(&list
, cmit
)) < 0)
484 while (git_pqueue_size(&list
) > 0)
488 git_commit_list_node
*c
= (git_commit_list_node
*)git_pqueue_pop(&list
);
491 n
= find_commit_name(data
->names
, &c
->oid
);
494 if (!tags
&& !all
&& n
->prio
< 2) {
496 } else if (match_cnt
< data
->opts
->max_candidates_tags
) {
497 struct possible_tag
*t
= git__malloc(sizeof(struct commit_name
));
498 GIT_ERROR_CHECK_ALLOC(t
);
499 if ((error
= git_vector_insert(&all_matches
, t
)) < 0)
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
;
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
))
524 if (annotated_cnt
&& (git_pqueue_size(&list
) == 0)) {
527 char oid_str[GIT_OID_HEXSZ + 1];
528 git_oid_tostr(oid_str, sizeof(oid_str), &c->oid);
530 fprintf(stderr, "finished search at %s\n", oid_str);
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)
539 if (!(p
->flags
& SEEN
))
540 if ((error
= git_pqueue_insert(&list
, p
)) < 0)
542 p
->flags
|= c
->flags
;
544 if (data
->opts
->only_follow_first_parent
)
550 if (data
->opts
->show_commit_oid_as_fallback
) {
551 data
->result
->fallback_to_id
= 1;
552 git_oid_cpy(&data
->result
->commit_id
, &cmit
->oid
);
556 if (unannotated_cnt
) {
557 error
= describe_not_found(git_commit_id(commit
),
559 "no annotated tags can describe '%s'; "
560 "however, there were unannotated tags.");
564 error
= describe_not_found(git_commit_id(commit
),
566 "no tags can describe '%s'.");
571 git_vector_sort(&all_matches
);
573 best
= (struct possible_tag
*)git_vector_get(&all_matches
, 0);
576 if ((error
= git_pqueue_insert(&list
, gave_up_on
)) < 0)
580 if ((error
= finish_depth_computation(
581 &list
, walk
, best
)) < 0)
584 seen_commits
+= error
;
585 if ((error
= possible_tag_dup(&data
->result
->tag
, best
)) < 0)
590 static const char *prio_names[] = {
591 "head", "lightweight", "annotated",
594 char oid_str[GIT_OID_HEXSZ + 1];
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);
603 fprintf(stderr, "traversed %lu commits\n", seen_commits);
605 git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid);
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,
616 git_oid_cpy(&data
->result
->commit_id
, &cmit
->oid
);
621 struct possible_tag
*match
;
622 git_vector_foreach(&all_matches
, i
, match
) {
626 git_vector_free(&all_matches
);
627 git_pqueue_free(&list
);
628 git_revwalk_free(walk
);
632 static int normalize_options(
633 git_describe_options
*dst
,
634 const git_describe_options
*src
)
636 git_describe_options default_options
= GIT_DESCRIBE_OPTIONS_INIT
;
637 if (!src
) src
= &default_options
;
641 if (dst
->max_candidates_tags
> GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS
)
642 dst
->max_candidates_tags
= GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS
;
647 int git_describe_commit(
648 git_describe_result
**result
,
649 git_object
*committish
,
650 git_describe_options
*opts
)
652 struct get_name_data data
;
653 struct commit_name
*name
;
656 git_describe_options normalized
;
660 data
.result
= git__calloc(1, sizeof(git_describe_result
));
661 GIT_ERROR_CHECK_ALLOC(data
.result
);
662 data
.result
->repo
= git_object_owner(committish
);
664 data
.repo
= git_object_owner(committish
);
666 if ((error
= normalize_options(&normalized
, opts
)) < 0)
669 GIT_ERROR_CHECK_VERSION(
671 GIT_DESCRIBE_OPTIONS_VERSION
,
672 "git_describe_options");
673 data
.opts
= &normalized
;
675 if ((error
= git_oidmap_new(&data
.names
)) < 0)
678 /** TODO: contains to be implemented */
680 if ((error
= git_object_peel((git_object
**)(&commit
), committish
, GIT_OBJECT_COMMIT
)) < 0)
683 if ((error
= git_reference_foreach_name(
684 git_object_owner(committish
),
685 get_name
, &data
)) < 0)
688 if (git_oidmap_size(data
.names
) == 0 && !normalized
.show_commit_oid_as_fallback
) {
689 git_error_set(GIT_ERROR_DESCRIBE
, "cannot describe - "
690 "no reference found, cannot describe anything.");
695 if ((error
= describe(&data
, commit
)) < 0)
699 git_commit_free(commit
);
701 git_oidmap_foreach_value(data
.names
, name
, {
702 git_tag_free(name
->tag
);
703 git__free(name
->path
);
707 git_oidmap_free(data
.names
);
710 git_describe_result_free(data
.result
);
712 *result
= data
.result
;
717 int git_describe_workdir(
718 git_describe_result
**out
,
719 git_repository
*repo
,
720 git_describe_options
*opts
)
724 git_status_list
*status
= NULL
;
725 git_status_options status_opts
= GIT_STATUS_OPTIONS_INIT
;
726 git_describe_result
*result
= NULL
;
729 if ((error
= git_reference_name_to_id(¤t_id
, repo
, GIT_HEAD_FILE
)) < 0)
732 if ((error
= git_object_lookup(&commit
, repo
, ¤t_id
, GIT_OBJECT_COMMIT
)) < 0)
735 /* The first step is to perform a describe of HEAD, so we can leverage this */
736 if ((error
= git_describe_commit(&result
, commit
, opts
)) < 0)
739 if ((error
= git_status_list_new(&status
, repo
, &status_opts
)) < 0)
743 if (git_status_list_entrycount(status
) > 0)
747 git_object_free(commit
);
748 git_status_list_free(status
);
751 git_describe_result_free(result
);
758 static int normalize_format_options(
759 git_describe_format_options
*dst
,
760 const git_describe_format_options
*src
)
763 git_describe_format_options_init(dst
, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION
);
767 memcpy(dst
, src
, sizeof(git_describe_format_options
));
771 int git_describe_format(git_buf
*out
, const git_describe_result
*result
, const git_describe_format_options
*given
)
774 git_repository
*repo
;
775 struct commit_name
*name
;
776 git_describe_format_options opts
;
778 assert(out
&& result
);
780 GIT_ERROR_CHECK_VERSION(given
, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION
, "git_describe_format_options");
781 normalize_format_options(&opts
, given
);
783 git_buf_sanitize(out
);
786 if (opts
.always_use_long_format
&& opts
.abbreviated_size
== 0) {
787 git_error_set(GIT_ERROR_DESCRIBE
, "cannot describe - "
788 "'always_use_long_format' is incompatible with a zero"
789 "'abbreviated_size'");
796 /* If we did find an exact match, then it's the easier method */
797 if (result
->exact_match
) {
799 if ((error
= display_name(out
, repo
, name
)) < 0)
802 if (opts
.always_use_long_format
) {
803 const git_oid
*id
= name
->tag
? git_tag_target_id(name
->tag
) : &result
->commit_id
;
804 if ((error
= show_suffix(out
, 0, repo
, id
, opts
.abbreviated_size
)) < 0)
808 if (result
->dirty
&& opts
.dirty_suffix
)
809 git_buf_puts(out
, opts
.dirty_suffix
);
811 return git_buf_oom(out
) ? -1 : 0;
814 /* If we didn't find *any* tags, we fall back to the commit's id */
815 if (result
->fallback_to_id
) {
816 char hex_oid
[GIT_OID_HEXSZ
+ 1] = {0};
819 if ((error
= find_unique_abbrev_size(
820 &size
, repo
, &result
->commit_id
, opts
.abbreviated_size
)) < 0)
823 git_oid_fmt(hex_oid
, &result
->commit_id
);
824 git_buf_put(out
, hex_oid
, size
);
826 if (result
->dirty
&& opts
.dirty_suffix
)
827 git_buf_puts(out
, opts
.dirty_suffix
);
829 return git_buf_oom(out
) ? -1 : 0;
832 /* Lastly, if we found a matching tag, we show that */
833 name
= result
->tag
->name
;
835 if ((error
= display_name(out
, repo
, name
)) < 0)
838 if (opts
.abbreviated_size
) {
839 if ((error
= show_suffix(out
, result
->tag
->depth
, repo
,
840 &result
->commit_id
, opts
.abbreviated_size
)) < 0)
844 if (result
->dirty
&& opts
.dirty_suffix
) {
845 git_buf_puts(out
, opts
.dirty_suffix
);
848 return git_buf_oom(out
) ? -1 : 0;
851 void git_describe_result_free(git_describe_result
*result
)
857 git_tag_free(result
->name
->tag
);
858 git__free(result
->name
->path
);
859 git__free(result
->name
);
863 git_tag_free(result
->tag
->name
->tag
);
864 git__free(result
->tag
->name
->path
);
865 git__free(result
->tag
->name
);
866 git__free(result
->tag
);
872 int git_describe_options_init(git_describe_options
*opts
, unsigned int version
)
874 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
875 opts
, version
, git_describe_options
, GIT_DESCRIBE_OPTIONS_INIT
);
879 #ifndef GIT_DEPRECATE_HARD
880 int git_describe_init_options(git_describe_options
*opts
, unsigned int version
)
882 return git_describe_options_init(opts
, version
);
886 int git_describe_format_options_init(git_describe_format_options
*opts
, unsigned int version
)
888 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
889 opts
, version
, git_describe_format_options
, GIT_DESCRIBE_FORMAT_OPTIONS_INIT
);
893 #ifndef GIT_DEPRECATE_HARD
894 int git_describe_init_format_options(git_describe_format_options
*opts
, unsigned int version
)
896 return git_describe_format_options_init(opts
, version
);