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.
7 #include "git2/describe.h"
8 #include "git2/strarray.h"
10 #include "git2/status.h"
14 #include "commit_list.h"
20 #include "repository.h"
22 /* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */
26 unsigned prio
:2; /* annotated tag = 2, tag = 1, head = 0 */
27 unsigned name_checked
:1;
31 /* Khash workaround. They original key has to still be reachable */
35 static void *oidmap_value_bykey(git_oidmap
*map
, const git_oid
*key
)
37 khint_t pos
= git_oidmap_lookup_index(map
, key
);
39 if (!git_oidmap_valid_index(map
, pos
))
42 return git_oidmap_value_at(map
, pos
);
45 static struct commit_name
*find_commit_name(
47 const git_oid
*peeled
)
49 return (struct commit_name
*)(oidmap_value_bykey(names
, peeled
));
52 static int replace_name(
55 struct commit_name
*e
,
59 git_time_t e_time
= 0, t_time
= 0;
61 if (!e
|| e
->prio
< prio
)
64 if (e
->prio
== 2 && prio
== 2) {
65 /* Multiple annotated tags point to the same commit.
66 * Select one to keep based upon their tagger date.
71 if (git_tag_lookup(&t
, repo
, &e
->sha1
) < 0)
76 if (git_tag_lookup(&t
, repo
, sha1
) < 0)
82 e_time
= e
->tag
->tagger
->when
.time
;
85 t_time
= t
->tagger
->when
.time
;
94 static int add_to_known_names(
98 const git_oid
*peeled
,
102 struct commit_name
*e
= find_commit_name(names
, peeled
);
103 bool found
= (e
!= NULL
);
106 if (replace_name(&tag
, repo
, e
, prio
, sha1
)) {
108 e
= git__malloc(sizeof(struct commit_name
));
109 GITERR_CHECK_ALLOC(e
);
116 git_tag_free(e
->tag
);
120 git_oid_cpy(&e
->sha1
, sha1
);
122 e
->path
= git__strdup(path
);
123 git_oid_cpy(&e
->peeled
, peeled
);
128 git_oidmap_insert(names
, &e
->peeled
, e
, ret
);
139 static int retrieve_peeled_tag_or_object_oid(
141 git_oid
*ref_target_out
,
142 git_repository
*repo
,
146 git_object
*peeled
= NULL
;
149 if ((error
= git_reference_lookup_resolved(&ref
, repo
, refname
, -1)) < 0)
152 if ((error
= git_reference_peel(&peeled
, ref
, GIT_OBJ_ANY
)) < 0)
155 git_oid_cpy(ref_target_out
, git_reference_target(ref
));
156 git_oid_cpy(peeled_out
, git_object_id(peeled
));
158 if (git_oid_cmp(ref_target_out
, peeled_out
) != 0)
159 error
= 1; /* The reference was pointing to a annotated tag */
161 error
= 0; /* Any other object */
164 git_reference_free(ref
);
165 git_object_free(peeled
);
169 struct git_describe_result
{
174 git_repository
*repo
;
175 struct commit_name
*name
;
176 struct possible_tag
*tag
;
181 git_describe_options
*opts
;
182 git_repository
*repo
;
184 git_describe_result
*result
;
187 static int commit_name_dup(struct commit_name
**out
, struct commit_name
*in
)
189 struct commit_name
*name
;
191 name
= git__malloc(sizeof(struct commit_name
));
192 GITERR_CHECK_ALLOC(name
);
194 memcpy(name
, in
, sizeof(struct commit_name
));
198 if (in
->tag
&& git_object_dup((git_object
**) &name
->tag
, (git_object
*) in
->tag
) < 0)
201 name
->path
= git__strdup(in
->path
);
202 GITERR_CHECK_ALLOC(name
->path
);
208 static int get_name(const char *refname
, void *payload
)
210 struct get_name_data
*data
;
211 bool is_tag
, is_annotated
, all
;
212 git_oid peeled
, sha1
;
216 data
= (struct get_name_data
*)payload
;
217 is_tag
= !git__prefixcmp(refname
, GIT_REFS_TAGS_DIR
);
218 all
= data
->opts
->describe_strategy
== GIT_DESCRIBE_ALL
;
220 /* Reject anything outside refs/tags/ unless --all */
224 /* Accept only tags that match the pattern, if given */
225 if (data
->opts
->pattern
&& (!is_tag
|| p_fnmatch(data
->opts
->pattern
,
226 refname
+ strlen(GIT_REFS_TAGS_DIR
), 0)))
229 /* Is it annotated? */
230 if ((error
= retrieve_peeled_tag_or_object_oid(
231 &peeled
, &sha1
, data
->repo
, refname
)) < 0)
234 is_annotated
= error
;
237 * By default, we only use annotated tags, but with --tags
238 * we fall back to lightweight ones (even without --tags,
239 * we still remember lightweight ones, only to give hints
240 * in an error message). --all allows any refs to be used.
249 add_to_known_names(data
->repo
, data
->names
,
250 all
? refname
+ strlen(GIT_REFS_DIR
) : refname
+ strlen(GIT_REFS_TAGS_DIR
),
251 &peeled
, prio
, &sha1
);
255 struct possible_tag
{
256 struct commit_name
*name
;
259 unsigned flag_within
;
262 static int possible_tag_dup(struct possible_tag
**out
, struct possible_tag
*in
)
264 struct possible_tag
*tag
;
267 tag
= git__malloc(sizeof(struct possible_tag
));
268 GITERR_CHECK_ALLOC(tag
);
270 memcpy(tag
, in
, sizeof(struct possible_tag
));
273 if ((error
= commit_name_dup(&tag
->name
, in
->name
)) < 0) {
283 static int compare_pt(const void *a_
, const void *b_
)
285 struct possible_tag
*a
= (struct possible_tag
*)a_
;
286 struct possible_tag
*b
= (struct possible_tag
*)b_
;
287 if (a
->depth
!= b
->depth
)
288 return a
->depth
- b
->depth
;
289 if (a
->found_order
!= b
->found_order
)
290 return a
->found_order
- b
->found_order
;
294 #define SEEN (1u << 0)
296 static unsigned long finish_depth_computation(
299 struct possible_tag
*best
)
301 unsigned long seen_commits
= 0;
304 while (git_pqueue_size(list
) > 0) {
305 git_commit_list_node
*c
= git_pqueue_pop(list
);
307 if (c
->flags
& best
->flag_within
) {
309 while (git_pqueue_size(list
) > index
) {
310 git_commit_list_node
*i
= git_pqueue_get(list
, index
);
311 if (!(i
->flags
& best
->flag_within
))
315 if (index
> git_pqueue_size(list
))
319 for (i
= 0; i
< c
->out_degree
; i
++) {
320 git_commit_list_node
*p
= c
->parents
[i
];
321 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
323 if (!(p
->flags
& SEEN
))
324 if ((error
= git_pqueue_insert(list
, p
)) < 0)
326 p
->flags
|= c
->flags
;
332 static int display_name(git_buf
*buf
, git_repository
*repo
, struct commit_name
*n
)
334 if (n
->prio
== 2 && !n
->tag
) {
335 if (git_tag_lookup(&n
->tag
, repo
, &n
->sha1
) < 0) {
336 giterr_set(GITERR_TAG
, "Annotated tag '%s' not available", n
->path
);
341 if (n
->tag
&& !n
->name_checked
) {
342 if (!git_tag_name(n
->tag
)) {
343 giterr_set(GITERR_TAG
, "Annotated tag '%s' has no embedded name", n
->path
);
347 /* TODO: Cope with warnings
348 if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
349 warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
356 git_buf_printf(buf
, "%s", git_tag_name(n
->tag
));
358 git_buf_printf(buf
, "%s", n
->path
);
363 static int find_unique_abbrev_size(
365 git_repository
*repo
,
366 const git_oid
*oid_in
,
367 int abbreviated_size
)
369 size_t size
= abbreviated_size
;
374 if ((error
= git_repository_odb__weakptr(&odb
, repo
)) < 0)
377 while (size
< GIT_OID_HEXSZ
) {
378 if ((error
= git_odb_exists_prefix(&dummy
, odb
, oid_in
, size
)) == 0) {
383 /* If the error wasn't that it's not unique, then it's a proper error */
384 if (error
!= GIT_EAMBIGUOUS
)
387 /* Try again with a larger size */
391 /* If we didn't find any shorter prefix, we have to do the whole thing */
392 *out
= GIT_OID_HEXSZ
;
397 static int show_suffix(
400 git_repository
*repo
,
406 char hex_oid
[GIT_OID_HEXSZ
];
408 if ((error
= find_unique_abbrev_size(&size
, repo
, id
, abbrev_size
)) < 0)
411 git_oid_fmt(hex_oid
, id
);
413 git_buf_printf(buf
, "-%d-g", depth
);
415 git_buf_put(buf
, hex_oid
, size
);
417 return git_buf_oom(buf
) ? -1 : 0;
420 #define MAX_CANDIDATES_TAGS FLAG_BITS - 1
422 static int describe_not_found(const git_oid
*oid
, const char *message_format
) {
423 char oid_str
[GIT_OID_HEXSZ
+ 1];
424 git_oid_tostr(oid_str
, sizeof(oid_str
), oid
);
426 giterr_set(GITERR_DESCRIBE
, message_format
, oid_str
);
427 return GIT_ENOTFOUND
;
431 struct get_name_data
*data
,
434 struct commit_name
*n
;
435 struct possible_tag
*best
;
437 git_revwalk
*walk
= NULL
;
439 git_commit_list_node
*cmit
, *gave_up_on
= NULL
;
440 git_vector all_matches
= GIT_VECTOR_INIT
;
441 unsigned int match_cnt
= 0, annotated_cnt
= 0, cur_match
;
442 unsigned long seen_commits
= 0; /* TODO: Check long */
443 unsigned int unannotated_cnt
= 0;
446 if (git_vector_init(&all_matches
, MAX_CANDIDATES_TAGS
, compare_pt
) < 0)
449 if ((error
= git_pqueue_init(&list
, 0, 2, git_commit_list_time_cmp
)) < 0)
452 all
= data
->opts
->describe_strategy
== GIT_DESCRIBE_ALL
;
453 tags
= data
->opts
->describe_strategy
== GIT_DESCRIBE_TAGS
;
455 git_oid_cpy(&data
->result
->commit_id
, git_commit_id(commit
));
457 n
= find_commit_name(data
->names
, git_commit_id(commit
));
458 if (n
&& (tags
|| all
|| n
->prio
== 2)) {
460 * Exact match to an existing ref.
462 data
->result
->exact_match
= 1;
463 if ((error
= commit_name_dup(&data
->result
->name
, n
)) < 0)
469 if (!data
->opts
->max_candidates_tags
) {
470 error
= describe_not_found(
471 git_commit_id(commit
),
472 "Cannot describe - no tag exactly matches '%s'");
477 if ((error
= git_revwalk_new(&walk
, git_commit_owner(commit
))) < 0)
480 if ((cmit
= git_revwalk__commit_lookup(walk
, git_commit_id(commit
))) == NULL
)
483 if ((error
= git_commit_list_parse(walk
, cmit
)) < 0)
488 if ((error
= git_pqueue_insert(&list
, cmit
)) < 0)
491 while (git_pqueue_size(&list
) > 0)
495 git_commit_list_node
*c
= (git_commit_list_node
*)git_pqueue_pop(&list
);
498 n
= find_commit_name(data
->names
, &c
->oid
);
501 if (!tags
&& !all
&& n
->prio
< 2) {
503 } else if (match_cnt
< data
->opts
->max_candidates_tags
) {
504 struct possible_tag
*t
= git__malloc(sizeof(struct commit_name
));
505 GITERR_CHECK_ALLOC(t
);
506 if ((error
= git_vector_insert(&all_matches
, t
)) < 0)
512 t
->depth
= seen_commits
- 1;
513 t
->flag_within
= 1u << match_cnt
;
514 t
->found_order
= match_cnt
;
515 c
->flags
|= t
->flag_within
;
525 for (cur_match
= 0; cur_match
< match_cnt
; cur_match
++) {
526 struct possible_tag
*t
= git_vector_get(&all_matches
, cur_match
);
527 if (!(c
->flags
& t
->flag_within
))
531 if (annotated_cnt
&& (git_pqueue_size(&list
) == 0)) {
534 char oid_str[GIT_OID_HEXSZ + 1];
535 git_oid_tostr(oid_str, sizeof(oid_str), &c->oid);
537 fprintf(stderr, "finished search at %s\n", oid_str);
542 for (i
= 0; i
< c
->out_degree
; i
++) {
543 git_commit_list_node
*p
= c
->parents
[i
];
544 if ((error
= git_commit_list_parse(walk
, p
)) < 0)
546 if (!(p
->flags
& SEEN
))
547 if ((error
= git_pqueue_insert(&list
, p
)) < 0)
549 p
->flags
|= c
->flags
;
551 if (data
->opts
->only_follow_first_parent
)
557 if (data
->opts
->show_commit_oid_as_fallback
) {
558 data
->result
->fallback_to_id
= 1;
559 git_oid_cpy(&data
->result
->commit_id
, &cmit
->oid
);
563 if (unannotated_cnt
) {
564 error
= describe_not_found(git_commit_id(commit
),
566 "No annotated tags can describe '%s'."
567 "However, there were unannotated tags.");
571 error
= describe_not_found(git_commit_id(commit
),
573 "No tags can describe '%s'.");
578 git_vector_sort(&all_matches
);
580 best
= (struct possible_tag
*)git_vector_get(&all_matches
, 0);
583 git_pqueue_insert(&list
, gave_up_on
);
586 if ((error
= finish_depth_computation(
587 &list
, walk
, best
)) < 0)
590 seen_commits
+= error
;
591 if ((error
= possible_tag_dup(&data
->result
->tag
, best
)) < 0)
596 static const char *prio_names[] = {
597 "head", "lightweight", "annotated",
600 char oid_str[GIT_OID_HEXSZ + 1];
603 for (cur_match = 0; cur_match < match_cnt; cur_match++) {
604 struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match);
605 fprintf(stderr, " %-11s %8d %s\n",
606 prio_names[t->name->prio],
607 t->depth, t->name->path);
609 fprintf(stderr, "traversed %lu commits\n", seen_commits);
611 git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid);
613 "more than %i tags found; listed %i most recent\n"
614 "gave up search at %s\n",
615 data->opts->max_candidates_tags, data->opts->max_candidates_tags,
622 git_oid_cpy(&data
->result
->commit_id
, &cmit
->oid
);
627 struct possible_tag
*match
;
628 git_vector_foreach(&all_matches
, i
, match
) {
632 git_vector_free(&all_matches
);
633 git_pqueue_free(&list
);
634 git_revwalk_free(walk
);
638 static int normalize_options(
639 git_describe_options
*dst
,
640 const git_describe_options
*src
)
642 git_describe_options default_options
= GIT_DESCRIBE_OPTIONS_INIT
;
643 if (!src
) src
= &default_options
;
647 if (dst
->max_candidates_tags
> GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS
)
648 dst
->max_candidates_tags
= GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS
;
653 int git_describe_commit(
654 git_describe_result
**result
,
655 git_object
*committish
,
656 git_describe_options
*opts
)
658 struct get_name_data data
;
659 struct commit_name
*name
;
662 git_describe_options normalized
;
666 data
.result
= git__calloc(1, sizeof(git_describe_result
));
667 GITERR_CHECK_ALLOC(data
.result
);
668 data
.result
->repo
= git_object_owner(committish
);
670 data
.repo
= git_object_owner(committish
);
672 if ((error
= normalize_options(&normalized
, opts
)) < 0)
675 GITERR_CHECK_VERSION(
677 GIT_DESCRIBE_OPTIONS_VERSION
,
678 "git_describe_options");
679 data
.opts
= &normalized
;
681 data
.names
= git_oidmap_alloc();
682 GITERR_CHECK_ALLOC(data
.names
);
684 /** TODO: contains to be implemented */
686 if ((error
= git_object_peel((git_object
**)(&commit
), committish
, GIT_OBJ_COMMIT
)) < 0)
689 if ((error
= git_reference_foreach_name(
690 git_object_owner(committish
),
691 get_name
, &data
)) < 0)
694 if (git_oidmap_size(data
.names
) == 0) {
695 giterr_set(GITERR_DESCRIBE
, "Cannot describe - "
696 "No reference found, cannot describe anything.");
701 if ((error
= describe(&data
, commit
)) < 0)
705 git_commit_free(commit
);
707 git_oidmap_foreach_value(data
.names
, name
, {
708 git_tag_free(name
->tag
);
709 git__free(name
->path
);
713 git_oidmap_free(data
.names
);
716 git_describe_result_free(data
.result
);
718 *result
= data
.result
;
723 int git_describe_workdir(
724 git_describe_result
**out
,
725 git_repository
*repo
,
726 git_describe_options
*opts
)
730 git_status_list
*status
= NULL
;
731 git_status_options status_opts
= GIT_STATUS_OPTIONS_INIT
;
732 git_describe_result
*result
= NULL
;
735 if ((error
= git_reference_name_to_id(¤t_id
, repo
, GIT_HEAD_FILE
)) < 0)
738 if ((error
= git_object_lookup(&commit
, repo
, ¤t_id
, GIT_OBJ_COMMIT
)) < 0)
741 /* The first step is to perform a describe of HEAD, so we can leverage this */
742 if ((error
= git_describe_commit(&result
, commit
, opts
)) < 0)
745 if ((error
= git_status_list_new(&status
, repo
, &status_opts
)) < 0)
749 if (git_status_list_entrycount(status
) > 0)
753 git_object_free(commit
);
754 git_status_list_free(status
);
757 git_describe_result_free(result
);
764 static int normalize_format_options(
765 git_describe_format_options
*dst
,
766 const git_describe_format_options
*src
)
769 git_describe_init_format_options(dst
, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION
);
773 memcpy(dst
, src
, sizeof(git_describe_format_options
));
777 int git_describe_format(git_buf
*out
, const git_describe_result
*result
, const git_describe_format_options
*given
)
780 git_repository
*repo
;
781 struct commit_name
*name
;
782 git_describe_format_options opts
;
784 assert(out
&& result
);
786 GITERR_CHECK_VERSION(given
, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION
, "git_describe_format_options");
787 normalize_format_options(&opts
, given
);
789 git_buf_sanitize(out
);
792 if (opts
.always_use_long_format
&& opts
.abbreviated_size
== 0) {
793 giterr_set(GITERR_DESCRIBE
, "Cannot describe - "
794 "'always_use_long_format' is incompatible with a zero"
795 "'abbreviated_size'");
802 /* If we did find an exact match, then it's the easier method */
803 if (result
->exact_match
) {
805 if ((error
= display_name(out
, repo
, name
)) < 0)
808 if (opts
.always_use_long_format
) {
809 const git_oid
*id
= name
->tag
? git_tag_target_id(name
->tag
) : &result
->commit_id
;
810 if ((error
= show_suffix(out
, 0, repo
, id
, opts
.abbreviated_size
)) < 0)
814 if (result
->dirty
&& opts
.dirty_suffix
)
815 git_buf_puts(out
, opts
.dirty_suffix
);
817 return git_buf_oom(out
) ? -1 : 0;
820 /* If we didn't find *any* tags, we fall back to the commit's id */
821 if (result
->fallback_to_id
) {
822 char hex_oid
[GIT_OID_HEXSZ
+ 1] = {0};
825 if ((error
= find_unique_abbrev_size(
826 &size
, repo
, &result
->commit_id
, opts
.abbreviated_size
)) < 0)
829 git_oid_fmt(hex_oid
, &result
->commit_id
);
830 git_buf_put(out
, hex_oid
, size
);
832 if (result
->dirty
&& opts
.dirty_suffix
)
833 git_buf_puts(out
, opts
.dirty_suffix
);
835 return git_buf_oom(out
) ? -1 : 0;
838 /* Lastly, if we found a matching tag, we show that */
839 name
= result
->tag
->name
;
841 if ((error
= display_name(out
, repo
, name
)) < 0)
844 if (opts
.abbreviated_size
) {
845 if ((error
= show_suffix(out
, result
->tag
->depth
, repo
,
846 &result
->commit_id
, opts
.abbreviated_size
)) < 0)
850 if (result
->dirty
&& opts
.dirty_suffix
) {
851 git_buf_puts(out
, opts
.dirty_suffix
);
854 return git_buf_oom(out
) ? -1 : 0;
857 void git_describe_result_free(git_describe_result
*result
)
863 git_tag_free(result
->name
->tag
);
864 git__free(result
->name
->path
);
865 git__free(result
->name
);
869 git_tag_free(result
->tag
->name
->tag
);
870 git__free(result
->tag
->name
->path
);
871 git__free(result
->tag
->name
);
872 git__free(result
->tag
);
878 int git_describe_init_options(git_describe_options
*opts
, unsigned int version
)
880 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
881 opts
, version
, git_describe_options
, GIT_DESCRIBE_OPTIONS_INIT
);
885 int git_describe_init_format_options(git_describe_format_options
*opts
, unsigned int version
)
887 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
888 opts
, version
, git_describe_format_options
, GIT_DESCRIBE_FORMAT_OPTIONS_INIT
);