diff options
author | nulltoken <emeric.fermas@gmail.com> | 2012-11-13 16:35:24 +0100 |
---|---|---|
committer | Carlos MartÃn Nieto <cmn@dwim.me> | 2014-04-30 09:46:25 +0200 |
commit | 3a728fb508ea3eea8033a9e338c61a6421ad21b2 (patch) | |
tree | 6f32280311850282e684840cbcc6c52b5a7c77bd | |
parent | 4cc71bb7fb7d10347bfa8ba57cb4cf74dcc79d4b (diff) | |
download | libgit2-3a728fb508ea3eea8033a9e338c61a6421ad21b2.tar.gz |
object: introduce git_describe_object()
-rw-r--r-- | include/git2.h | 1 | ||||
-rw-r--r-- | include/git2/common.h | 2 | ||||
-rw-r--r-- | include/git2/describe.h | 66 | ||||
-rw-r--r-- | include/git2/errors.h | 1 | ||||
-rw-r--r-- | src/commit_list.h | 2 | ||||
-rw-r--r-- | src/describe.c | 690 | ||||
-rw-r--r-- | src/oidmap.h | 3 | ||||
-rw-r--r-- | tests/describe/describe.c | 47 | ||||
-rw-r--r-- | tests/describe/describe_helpers.c | 24 | ||||
-rw-r--r-- | tests/describe/describe_helpers.h | 9 | ||||
-rw-r--r-- | tests/describe/t6120.c | 135 |
11 files changed, 979 insertions, 1 deletions
diff --git a/include/git2.h b/include/git2.h index f74976061..6713b4961 100644 --- a/include/git2.h +++ b/include/git2.h @@ -19,6 +19,7 @@ #include "git2/commit.h" #include "git2/common.h" #include "git2/config.h" +#include "git2/describe.h" #include "git2/diff.h" #include "git2/errors.h" #include "git2/filter.h" diff --git a/include/git2/common.h b/include/git2/common.h index 32237efed..ceb27205a 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -83,6 +83,8 @@ GIT_BEGIN_DECL */ #define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000" +#define FLAG_BITS 27 + /** * Return the version of the libgit2 library * being currently used. diff --git a/include/git2/describe.h b/include/git2/describe.h new file mode 100644 index 000000000..8a00d258a --- /dev/null +++ b/include/git2/describe.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_describe_h__ +#define INCLUDE_git_describe_h__ + +#include "common.h" +#include "types.h" +#include "buffer.h" + +/** + * @file git2/describe.h + * @brief Git describing routines + * @defgroup git_describe Git describing routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +typedef enum { + GIT_DESCRIBE_DEFAULT, + GIT_DESCRIBE_TAGS, + GIT_DESCRIBE_ALL, +} git_describe_strategy_t; + +/** + * Describe options structure + * + * Zero out for defaults. Initialize with `GIT_DESCRIBE_OPTIONS_INIT` macro to + * correctly set the `version` field. E.g. + * + * git_describe_opts opts = GIT_DESCRIBE_OPTIONS_INIT; + */ +typedef struct git_describe_opts { + unsigned int version; + + unsigned int max_candidates_tags; /** default: 10 */ + unsigned int abbreviated_size; + unsigned int describe_strategy; /** default: GIT_DESCRIBE_DEFAULT */ + const char *pattern; + int always_use_long_format; + int only_follow_first_parent; + int show_commit_oid_as_fallback; +} git_describe_opts; + +#define GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS 10 +#define GIT_DESCRIBE_DEFAULT_ABBREVIATED_SIZE 7 + +#define GIT_DESCRIBE_OPTIONS_VERSION 1 +#define GIT_DESCRIBE_OPTIONS_INIT { \ + GIT_DESCRIBE_OPTIONS_VERSION, \ + GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS, \ + GIT_DESCRIBE_DEFAULT_ABBREVIATED_SIZE} + +GIT_EXTERN(int) git_describe_object( + git_buf *out, + git_object *committish, + git_describe_opts *opts); + +/** @} */ +GIT_END_DECL + +#endif diff --git a/include/git2/errors.h b/include/git2/errors.h index e22f0d86d..287498423 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -87,6 +87,7 @@ typedef enum { GITERR_REVERT, GITERR_CALLBACK, GITERR_CHERRYPICK, + GITERR_DESCRIBE, } git_error_t; /** diff --git a/src/commit_list.h b/src/commit_list.h index 490d841be..7cd3945ae 100644 --- a/src/commit_list.h +++ b/src/commit_list.h @@ -25,7 +25,7 @@ typedef struct git_commit_list_node { uninteresting:1, topo_delay:1, parsed:1, - flags : 4; + flags : FLAG_BITS; unsigned short in_degree; unsigned short out_degree; diff --git a/src/describe.c b/src/describe.c new file mode 100644 index 000000000..b503db761 --- /dev/null +++ b/src/describe.c @@ -0,0 +1,690 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "git2/describe.h" + +#include "common.h" +#include "commit.h" +#include "commit_list.h" +#include "oidmap.h" +#include "refs.h" +#include "revwalk.h" +#include "tag.h" +#include "vector.h" + +/* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */ + +struct commit_name { + git_tag *tag; + unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned name_checked:1; + git_oid sha1; + char *path; + + /* Khash workaround. They original key has to still be reachable */ + git_oid peeled; +}; + +static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key) +{ + khint_t pos = git_oidmap_lookup_index(map, key); + + if (!git_oidmap_valid_index(map, pos)) + return NULL; + + return git_oidmap_value_at(map, pos); +} + +static struct commit_name *find_commit_name( + git_oidmap *names, + const git_oid *peeled) +{ + return (struct commit_name *)(oidmap_value_bykey(names, peeled)); +} + +static int replace_name( + git_tag **tag, + git_repository *repo, + struct commit_name *e, + unsigned int prio, + const git_oid *sha1) +{ + git_time_t e_time = 0, t_time = 0; + + if (!e || e->prio < prio) + return 1; + + if (e->prio == 2 && prio == 2) { + /* Multiple annotated tags point to the same commit. + * Select one to keep based upon their tagger date. + */ + git_tag *t = NULL; + + if (!e->tag) { + if (git_tag_lookup(&t, repo, &e->sha1) < 0) + return 1; + e->tag = t; + } + + if (git_tag_lookup(&t, repo, sha1) < 0) + return 0; + + *tag = t; + + if (e->tag->tagger) + e_time = e->tag->tagger->when.time; + + if (t->tagger) + t_time = t->tagger->when.time; + + if (e_time < t_time) + return 1; + } + + return 0; +} + +static int add_to_known_names( + git_repository *repo, + git_oidmap *names, + const char *path, + const git_oid *peeled, + unsigned int prio, + const git_oid *sha1) +{ + struct commit_name *e = find_commit_name(names, peeled); + bool found = (e != NULL); + + git_tag *tag = NULL; + if (replace_name(&tag, repo, e, prio, sha1)) { + if (!found) { + e = git__malloc(sizeof(struct commit_name)); + GITERR_CHECK_ALLOC(e); + + e->path = NULL; + e->tag = NULL; + } + + if (e->tag) + git_tag_free(e->tag); + e->tag = tag; + e->prio = prio; + e->name_checked = 0; + git_oid_cpy(&e->sha1, sha1); + git__free(e->path); + e->path = git__strdup(path); + git_oid_cpy(&e->peeled, peeled); + + if (!found) { + int ret; + + git_oidmap_insert(names, &e->peeled, e, ret); + if (ret < 0) + return -1; + } + } + else + git_tag_free(tag); + + return 0; +} + +static int retrieve_peeled_tag_or_object_oid( + git_oid *peeled_out, + git_oid *ref_target_out, + git_repository *repo, + const char *refname) +{ + git_reference *ref; + git_object *peeled = NULL; + int error; + + if ((error = git_reference_lookup_resolved(&ref, repo, refname, -1)) < 0) + return error; + + if ((error = git_reference_peel(&peeled, ref, GIT_OBJ_ANY)) < 0) + goto cleanup; + + git_oid_cpy(ref_target_out, git_reference_target(ref)); + git_oid_cpy(peeled_out, git_object_id(peeled)); + + if (git_oid_cmp(ref_target_out, peeled_out) != 0) + error = 1; /* The reference was pointing to a annotated tag */ + else + error = 0; /* Any other object */ + +cleanup: + git_reference_free(ref); + git_object_free(peeled); + return error; +} + +struct get_name_data +{ + git_describe_opts *opts; + git_repository *repo; + git_oidmap *names; +}; + +static int get_name(const char *refname, void *payload) +{ + struct get_name_data *data; + bool is_tag, is_annotated, all; + git_oid peeled, sha1; + unsigned int prio; + int error = 0; + + data = (struct get_name_data *)payload; + is_tag = !git__prefixcmp(refname, GIT_REFS_TAGS_DIR); + all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; + + /* Reject anything outside refs/tags/ unless --all */ + if (!all && !is_tag) + return 0; + + /* Accept only tags that match the pattern, if given */ + if (data->opts->pattern && (!is_tag || p_fnmatch(data->opts->pattern, + refname + strlen(GIT_REFS_TAGS_DIR), 0))) + return 0; + + /* Is it annotated? */ + if ((error = retrieve_peeled_tag_or_object_oid( + &peeled, &sha1, data->repo, refname)) < 0) + return error; + + is_annotated = error; + + /* + * By default, we only use annotated tags, but with --tags + * we fall back to lightweight ones (even without --tags, + * we still remember lightweight ones, only to give hints + * in an error message). --all allows any refs to be used. + */ + if (is_annotated) + prio = 2; + else if (is_tag) + prio = 1; + else + prio = 0; + + add_to_known_names(data->repo, data->names, + all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR), + &peeled, prio, &sha1); + return 0; +} + +struct possible_tag { + struct commit_name *name; + int depth; + int found_order; + unsigned flag_within; +}; + +static int compare_pt(const void *a_, const void *b_) +{ + struct possible_tag *a = (struct possible_tag *)a_; + struct possible_tag *b = (struct possible_tag *)b_; + if (a->depth != b->depth) + return a->depth - b->depth; + if (a->found_order != b->found_order) + return a->found_order - b->found_order; + return 0; +} + +#define SEEN (1u << 0) + +static unsigned long finish_depth_computation( + git_pqueue *list, + git_revwalk *walk, + struct possible_tag *best) +{ + unsigned long seen_commits = 0; + int error, i; + + while (git_pqueue_size(list) > 0) { + git_commit_list_node *c = git_pqueue_pop(list); + seen_commits++; + if (c->flags & best->flag_within) { + size_t index = 0; + while (git_pqueue_size(list) > index) { + git_commit_list_node *i = git_pqueue_get(list, index); + if (!(i->flags & best->flag_within)) + break; + index++; + } + if (index > git_pqueue_size(list)) + break; + } else + best->depth++; + for (i = 0; i < c->out_degree; i++) { + git_commit_list_node *p = c->parents[i]; + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + if (!(p->flags & SEEN)) + if ((error = git_pqueue_insert(list, p)) < 0) + return error; + p->flags |= c->flags; + } + } + return seen_commits; +} + +static int display_name(git_buf *buf, git_repository *repo, struct commit_name *n) +{ + if (n->prio == 2 && !n->tag) { + if (git_tag_lookup(&n->tag, repo, &n->sha1) < 0) { + giterr_set(GITERR_TAG, "Annotated tag '%s' not available", n->path); + return -1; + } + } + + if (n->tag && !n->name_checked) { + if (!git_tag_name(n->tag)) { + giterr_set(GITERR_TAG, "Annotated tag '%s' has no embedded name", n->path); + return -1; + } + + /* TODO: Cope with warnings + if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) + warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); + */ + + n->name_checked = 1; + } + + if (n->tag) + git_buf_printf(buf, "%s", git_tag_name(n->tag)); + else + git_buf_printf(buf, "%s", n->path); + + return 0; +} + +static int find_unique_abbrev_size( + int *out, + const git_oid *oid_in, + int abbreviated_size) +{ + GIT_UNUSED(abbreviated_size); + + /* TODO: Actually find the abbreviated oid size */ + GIT_UNUSED(oid_in); + + *out = GIT_OID_HEXSZ; + + return 0; +} + +static int show_suffix( + git_buf *buf, + int depth, + const git_oid* id, + size_t abbrev_size) +{ + int error, size; + + char hex_oid[GIT_OID_HEXSZ]; + + if ((error = find_unique_abbrev_size(&size, id, abbrev_size)) < 0) + return error; + + git_oid_fmt(hex_oid, id); + + git_buf_printf(buf, "-%d-g", depth); + + git_buf_put(buf, hex_oid, size); + git_buf_putc(buf, '\0'); + + return git_buf_oom(buf) ? -1 : 0; +} + +#define MAX_CANDIDATES_TAGS FLAG_BITS - 1 + +static int describe_not_found(const git_oid *oid, const char *message_format) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, sizeof(oid_str), oid); + + giterr_set(GITERR_DESCRIBE, message_format, oid_str); + return GIT_ENOTFOUND; +} + +static int describe( + git_buf *out, + struct get_name_data *data, + git_commit *commit, + const char *dirty_suffix) +{ + struct commit_name *n; + struct possible_tag *best; + bool all, tags; + git_buf buf = GIT_BUF_INIT; + git_revwalk *walk = NULL; + git_pqueue list; + git_commit_list_node *cmit, *gave_up_on = NULL; + git_vector all_matches = GIT_VECTOR_INIT; + unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; + unsigned long seen_commits = 0; /* TODO: Check long */ + unsigned int unannotated_cnt = 0; + int error; + + if (git_vector_init(&all_matches, MAX_CANDIDATES_TAGS, compare_pt) < 0) + return -1; + + if ((error = git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp)) < 0) + goto cleanup; + + all = data->opts->describe_strategy == GIT_DESCRIBE_ALL; + tags = data->opts->describe_strategy == GIT_DESCRIBE_TAGS; + + n = find_commit_name(data->names, git_commit_id(commit)); + if (n && (tags || all || n->prio == 2)) { + /* + * Exact match to an existing ref. + */ + if ((error = display_name(&buf, data->repo, n)) < 0) + goto cleanup; + + if (data->opts->always_use_long_format) { + if ((error = show_suffix(&buf, 0, + n->tag ? git_tag_target_id(n->tag) : git_commit_id(commit), + data->opts->abbreviated_size)) < 0) + goto cleanup; + } + + if (dirty_suffix) + git_buf_printf(&buf, "%s", dirty_suffix); + + if (git_buf_oom(&buf)) + return -1; + + goto found; + } + + if (!data->opts->max_candidates_tags) { + error = describe_not_found( + git_commit_id(commit), + "Cannot describe - no tag exactly matches '%s'"); + + goto cleanup; + } + + if ((error = git_revwalk_new(&walk, git_commit_owner(commit))) < 0) + goto cleanup; + + if ((cmit = git_revwalk__commit_lookup(walk, git_commit_id(commit))) == NULL) + goto cleanup; + + if ((error = git_commit_list_parse(walk, cmit)) < 0) + goto cleanup; + + cmit->flags = SEEN; + + if ((error = git_pqueue_insert(&list, cmit)) < 0) + goto cleanup; + + while (git_pqueue_size(&list) > 0) + { + int i; + + git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list); + seen_commits++; + + n = find_commit_name(data->names, &c->oid); + + if (n) { + if (!tags && !all && n->prio < 2) { + unannotated_cnt++; + } else if (match_cnt < data->opts->max_candidates_tags) { + struct possible_tag *t = git__malloc(sizeof(struct commit_name)); + GITERR_CHECK_ALLOC(t); + if ((error = git_vector_insert(&all_matches, t)) < 0) + goto cleanup; + + match_cnt++; + + t->name = n; + t->depth = seen_commits - 1; + t->flag_within = 1u << match_cnt; + t->found_order = match_cnt; + c->flags |= t->flag_within; + if (n->prio == 2) + annotated_cnt++; + } + else { + gave_up_on = c; + break; + } + } + + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = git_vector_get(&all_matches, cur_match); + if (!(c->flags & t->flag_within)) + t->depth++; + } + + if (annotated_cnt && (git_pqueue_size(&list) == 0)) { + /* + if (debug) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, sizeof(oid_str), &c->oid); + + fprintf(stderr, "finished search at %s\n", oid_str); + } + */ + break; + } + for (i = 0; i < c->out_degree; i++) { + git_commit_list_node *p = c->parents[i]; + if ((error = git_commit_list_parse(walk, p)) < 0) + goto cleanup; + if (!(p->flags & SEEN)) + if ((error = git_pqueue_insert(&list, p)) < 0) + goto cleanup; + p->flags |= c->flags; + + if (data->opts->only_follow_first_parent) + break; + } + } + + if (!match_cnt) { + if (data->opts->show_commit_oid_as_fallback) { + char hex_oid[GIT_OID_HEXSZ]; + int size; + + if ((error = find_unique_abbrev_size( + &size, &cmit->oid, data->opts->abbreviated_size)) < 0) + goto cleanup; + + git_oid_fmt(hex_oid, &cmit->oid); + git_buf_put(&buf, hex_oid, size); + + if (dirty_suffix) + git_buf_printf(&buf, "%s", dirty_suffix); + + if (git_buf_oom(&buf)) + return -1; + + goto found; + } + if (unannotated_cnt) { + error = describe_not_found(git_commit_id(commit), + "Cannot describe - " + "No annotated tags can describe '%s'." + "However, there were unannotated tags."); + goto cleanup; + } + else { + error = describe_not_found(git_commit_id(commit), + "Cannot describe - " + "No tags can describe '%s'."); + goto cleanup; + } + } + + best = (struct possible_tag *)git_vector_get(&all_matches, 0); + + git_vector_sort(&all_matches); + + best = (struct possible_tag *)git_vector_get(&all_matches, 0); + + if (gave_up_on) { + git_pqueue_insert(&list, gave_up_on); + seen_commits--; + } + if ((error = finish_depth_computation( + &list, walk, best)) < 0) + goto cleanup; + seen_commits += error; + + /* + { + static const char *prio_names[] = { + "head", "lightweight", "annotated", + }; + + char oid_str[GIT_OID_HEXSZ + 1]; + + if (debug) { + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match); + fprintf(stderr, " %-11s %8d %s\n", + prio_names[t->name->prio], + t->depth, t->name->path); + } + fprintf(stderr, "traversed %lu commits\n", seen_commits); + if (gave_up_on) { + git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid); + fprintf(stderr, + "more than %i tags found; listed %i most recent\n" + "gave up search at %s\n", + data->opts->max_candidates_tags, data->opts->max_candidates_tags, + oid_str); + } + } + } + */ + + if ((error = display_name(&buf, data->repo, best->name)) < 0) + goto cleanup; + + if (data->opts->abbreviated_size) { + if ((error = show_suffix(&buf, best->depth, + &cmit->oid, data->opts->abbreviated_size)) < 0) + goto cleanup; + } + + if (dirty_suffix) + git_buf_printf(&buf, "%s", dirty_suffix); + + if (git_buf_oom(&buf)) + return -1; + +found: + out->ptr = buf.ptr; + out->asize = buf.asize; + out->size = buf.size; + +cleanup: + { + size_t i; + struct possible_tag *match; + git_vector_foreach(&all_matches, i, match) { + git__free(match); + } + } + git_vector_free(&all_matches); + git_pqueue_free(&list); + git_revwalk_free(walk); + return error; +} + +static int normalize_options( + git_describe_opts *dst, + const git_describe_opts *src) +{ + git_describe_opts default_options = GIT_DESCRIBE_OPTIONS_INIT; + if (!src) src = &default_options; + + *dst = *src; + + if (dst->max_candidates_tags > GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS) + dst->max_candidates_tags = GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS; + + if (dst->always_use_long_format && dst->abbreviated_size == 0) { + giterr_set(GITERR_DESCRIBE, "Cannot describe - " + "'always_use_long_format' is incompatible with a zero" + "'abbreviated_size'"); + return -1; + } + + return 0; +} + +/** TODO: Add git_object_describe_workdir(git_buf *, const char *dirty_suffix, git_describe_opts *); */ + +int git_describe_object( + git_buf *out, + git_object *committish, + git_describe_opts *opts) +{ + struct get_name_data data; + struct commit_name *name; + git_commit *commit; + int error = -1; + const char *dirty_suffix = NULL; + git_describe_opts normOptions; + + assert(out && committish); + + data.opts = opts; + data.repo = git_object_owner(committish); + + if ((error = normalize_options(&normOptions, opts)) < 0) + return error; + + GITERR_CHECK_VERSION( + &normOptions, + GIT_DESCRIBE_OPTIONS_VERSION, + "git_describe_opts"); + + data.names = git_oidmap_alloc(); + GITERR_CHECK_ALLOC(data.names); + + /** TODO: contains to be implemented */ + + /** TODO: deal with max_abbrev_size (either document or fix) */ + + if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJ_COMMIT)) < 0) + goto cleanup; + + if (git_reference_foreach_name( + git_object_owner(committish), + get_name, &data) < 0) + goto cleanup; + + if (git_oidmap_size(data.names) == 0) { + giterr_set(GITERR_DESCRIBE, "Cannot describe - " + "No reference found, cannot describe anything."); + error = -1; + goto cleanup; + } + + if ((error = describe(out, &data, commit, dirty_suffix)) < 0) + goto cleanup; + +cleanup: + git_oidmap_foreach_value(data.names, name, { + git_tag_free(name->tag); + git__free(name->path); + git__free(name); + }); + + git_oidmap_free(data.names); + git_commit_free(commit); + + return error; +} diff --git a/src/oidmap.h b/src/oidmap.h index 50da54b1c..b871a7926 100644 --- a/src/oidmap.h +++ b/src/oidmap.h @@ -45,4 +45,7 @@ GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid) } } while (0) #define git_oidmap_foreach_value kh_foreach_value + +#define git_oidmap_size(h) kh_size(h) + #endif diff --git a/tests/describe/describe.c b/tests/describe/describe.c new file mode 100644 index 000000000..fcd4486bc --- /dev/null +++ b/tests/describe/describe.c @@ -0,0 +1,47 @@ +#include "clar_libgit2.h" +#include "describe_helpers.h" + +void test_describe_describe__can_describe_against_a_bare_repo(void) +{ + git_repository *repo; + git_describe_opts opts = GIT_DESCRIBE_OPTIONS_INIT; + + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + assert_describe("hard_tag", "HEAD", repo, &opts, false); + + opts.show_commit_oid_as_fallback = 1; + + assert_describe("be3563a", "HEAD^", repo, &opts, true); + + git_repository_free(repo); +} + +static int delete_cb(git_reference *ref, void *payload) +{ + GIT_UNUSED(payload); + + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + return 0; +} + +void test_describe_describe__cannot_describe_against_a_repo_with_no_ref(void) +{ + git_repository *repo; + git_describe_opts opts = GIT_DESCRIBE_OPTIONS_INIT; + git_buf buf = GIT_BUF_INIT; + git_object *object; + + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_revparse_single(&object, repo, "HEAD")); + + cl_git_pass(git_reference_foreach(repo, delete_cb, NULL)); + + cl_git_fail(git_describe_object(&buf, object, &opts)); + + git_object_free(object); + git_buf_free(&buf); + cl_git_sandbox_cleanup(); +} diff --git a/tests/describe/describe_helpers.c b/tests/describe/describe_helpers.c new file mode 100644 index 000000000..aa2a54f9e --- /dev/null +++ b/tests/describe/describe_helpers.c @@ -0,0 +1,24 @@ +#include "describe_helpers.h" + +void assert_describe( + const char *expected_output, + const char *revparse_spec, + git_repository *repo, + git_describe_opts *opts, + bool is_prefix_match) +{ + git_object *object; + git_buf label; + + cl_git_pass(git_revparse_single(&object, repo, revparse_spec)); + + cl_git_pass(git_describe_object(&label, object, opts)); + + if (is_prefix_match) + cl_assert_equal_i(0, git__prefixcmp(git_buf_cstr(&label), expected_output)); + else + cl_assert_equal_s(expected_output, label); + + git_object_free(object); + git_buf_free(&label); +} diff --git a/tests/describe/describe_helpers.h b/tests/describe/describe_helpers.h new file mode 100644 index 000000000..0f107f5a7 --- /dev/null +++ b/tests/describe/describe_helpers.h @@ -0,0 +1,9 @@ +#include "clar_libgit2.h" +#include "buffer.h" + +extern void assert_describe( + const char *expected_output, + const char *revparse_spec, + git_repository *repo, + git_describe_opts *opts, + bool is_prefix_match); diff --git a/tests/describe/t6120.c b/tests/describe/t6120.c new file mode 100644 index 000000000..d589e82a6 --- /dev/null +++ b/tests/describe/t6120.c @@ -0,0 +1,135 @@ +#include "clar_libgit2.h" +#include "describe_helpers.h" +#include "repository.h" + +// Ported from https://github.com/git/git/blob/adfc1857bdb090786fd9d22c1acec39371c76048/t/t6120-describe.sh + +static git_repository *repo; + +void test_describe_t6120__initialize(void) +{ + repo = cl_git_sandbox_init("describe"); +} + +void test_describe_t6120__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_describe_t6120__default(void) +{ + git_describe_opts opts = GIT_DESCRIBE_OPTIONS_INIT; + + assert_describe("A-", "HEAD", repo, &opts, true); + assert_describe("A-", "HEAD^", repo, &opts, true); + assert_describe("R-", "HEAD^^", repo, &opts, true); + assert_describe("A-", "HEAD^^2", repo, &opts, true); + assert_describe("B", "HEAD^^2^", repo, &opts, false); + assert_describe("R-", "HEAD^^^", repo, &opts, true); +} + +void test_describe_t6120__tags(void) +{ + git_describe_opts opts = GIT_DESCRIBE_OPTIONS_INIT; + opts.describe_strategy = GIT_DESCRIBE_TAGS; + + assert_describe("c-", "HEAD", repo, &opts, true); + assert_describe("c-", "HEAD^", repo, &opts, true); + assert_describe("e-", "HEAD^^", repo, &opts, true); + assert_describe("c-", "HEAD^^2", repo, &opts, true); + assert_describe("B", "HEAD^^2^", repo, &opts, false); + assert_describe("e", "HEAD^^^", repo, &opts, false); +} + +void test_describe_t6120__all(void) +{ + git_describe_opts opts = GIT_DESCRIBE_OPTIONS_INIT; + opts.describe_strategy = GIT_DESCRIBE_ALL; + + assert_describe("heads/master", "HEAD", repo, &opts, false); + assert_describe("tags/c-", "HEAD^", repo, &opts, true); + assert_describe("tags/e", "HEAD^^^", repo, &opts, false); +} + +void test_describe_t6120__longformat(void) +{ + git_describe_opts opts = GIT_DESCRIBE_OPTIONS_INIT; + opts.always_use_long_format = 1; + + assert_describe("B-0-", "HEAD^^2^", repo, &opts, true); + assert_describe("A-3-", "HEAD^^2", repo, &opts, true); +} + +void test_describe_t6120__firstparent(void) +{ + git_describe_opts opts = GIT_DESCRIBE_OPTIONS_INIT; + opts.describe_strategy = GIT_DESCRIBE_TAGS; + + assert_describe("c-7-", "HEAD", repo, &opts, true); + + opts.only_follow_first_parent = 1; + assert_describe("e-3-", "HEAD", repo, &opts, true); +} + +static void commit_and_tag( + git_time_t *time, + const char *commit_msg, + const char *tag_name) +{ + git_index *index; + git_oid commit_id; + git_reference *ref; + + cl_git_pass(git_repository_index__weakptr(&index, repo)); + + cl_git_append2file("describe/file", "\n"); + + git_index_add_bypath(index, "describe/file"); + git_index_write(index); + + *time += 10; + cl_repo_commit_from_index(&commit_id, repo, NULL, *time, commit_msg); + + if (tag_name == NULL) + return; + + cl_git_pass(git_reference_create(&ref, repo, tag_name, &commit_id, 0, NULL, NULL)); + git_reference_free(ref); +} + +void test_describe_t6120__pattern(void) +{ + git_describe_opts opts = GIT_DESCRIBE_OPTIONS_INIT; + git_oid tag_id; + git_object *head; + git_signature *tagger; + git_time_t time; + + /* set-up matching pattern tests */ + cl_git_pass(git_revparse_single(&head, repo, "HEAD")); + + time = 1380553019; + cl_git_pass(git_signature_new(&tagger, "tagger", "tagger@libgit2.org", time, 0)); + cl_git_pass(git_tag_create(&tag_id, repo, "test-annotated", head, tagger, "test-annotated", 0)); + git_signature_free(tagger); + git_object_free(head); + + commit_and_tag(&time, "one more", "refs/tags/test1-lightweight"); + commit_and_tag(&time, "yet another", "refs/tags/test2-lightweight"); + commit_and_tag(&time, "even more", NULL); + + + /* Exercize */ + opts.pattern = "test-*"; + assert_describe("test-annotated-", "HEAD", repo, &opts, true); + + opts.describe_strategy = GIT_DESCRIBE_TAGS; + opts.pattern = "test1-*"; + assert_describe("test1-lightweight-", "HEAD", repo, &opts, true); + + opts.pattern = "test2-*"; + assert_describe("test2-lightweight-", "HEAD", repo, &opts, true); + + opts.always_use_long_format = 1; + assert_describe("test2-lightweight-", "HEAD^", repo, &opts, true); +} |