mirror of
				https://git.proxmox.com/git/libgit2
				synced 2025-10-31 19:49:16 +00:00 
			
		
		
		
	object: introduce git_describe_object()
This commit is contained in:
		
							parent
							
								
									4cc71bb7fb
								
							
						
					
					
						commit
						3a728fb508
					
				| @ -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" | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
							
								
								
									
										66
									
								
								include/git2/describe.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								include/git2/describe.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| @ -87,6 +87,7 @@ typedef enum { | ||||
| 	GITERR_REVERT, | ||||
| 	GITERR_CALLBACK, | ||||
| 	GITERR_CHERRYPICK, | ||||
| 	GITERR_DESCRIBE, | ||||
| } git_error_t; | ||||
| 
 | ||||
| /**
 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
							
								
								
									
										690
									
								
								src/describe.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										690
									
								
								src/describe.c
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
| } | ||||
| @ -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 | ||||
|  | ||||
							
								
								
									
										47
									
								
								tests/describe/describe.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								tests/describe/describe.c
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||
| } | ||||
							
								
								
									
										24
									
								
								tests/describe/describe_helpers.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								tests/describe/describe_helpers.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
| } | ||||
							
								
								
									
										9
									
								
								tests/describe/describe_helpers.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/describe/describe_helpers.h
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
							
								
								
									
										135
									
								
								tests/describe/t6120.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								tests/describe/t6120.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 nulltoken
						nulltoken