1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 
8 #include "common.h"
9 
10 #include "git2/describe.h"
11 #include "git2/strarray.h"
12 #include "git2/diff.h"
13 #include "git2/status.h"
14 
15 #include "commit.h"
16 #include "commit_list.h"
17 #include "oidmap.h"
18 #include "refs.h"
19 #include "repository.h"
20 #include "revwalk.h"
21 #include "tag.h"
22 #include "vector.h"
23 #include "wildmatch.h"
24 
25 /* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */
26 
27 struct commit_name {
28 	git_tag *tag;
29 	unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
30 	unsigned name_checked:1;
31 	git_oid sha1;
32 	char *path;
33 
34 	/* Khash workaround. They original key has to still be reachable */
35 	git_oid peeled;
36 };
37 
oidmap_value_bykey(git_oidmap * map,const git_oid * key)38 static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key)
39 {
40 	return git_oidmap_get(map, key);
41 }
42 
find_commit_name(git_oidmap * names,const git_oid * peeled)43 static struct commit_name *find_commit_name(
44 	git_oidmap *names,
45 	const git_oid *peeled)
46 {
47 	return (struct commit_name *)(oidmap_value_bykey(names, peeled));
48 }
49 
replace_name(git_tag ** tag,git_repository * repo,struct commit_name * e,unsigned int prio,const git_oid * sha1)50 static int replace_name(
51 	git_tag **tag,
52 	git_repository *repo,
53 	struct commit_name *e,
54 	unsigned int prio,
55 	const git_oid *sha1)
56 {
57 	git_time_t e_time = 0, t_time = 0;
58 
59 	if (!e || e->prio < prio)
60 		return 1;
61 
62 	if (e->prio == 2 && prio == 2) {
63 		/* Multiple annotated tags point to the same commit.
64 		 * Select one to keep based upon their tagger date.
65 		 */
66 		git_tag *t = NULL;
67 
68 		if (!e->tag) {
69 			if (git_tag_lookup(&t, repo, &e->sha1) < 0)
70 				return 1;
71 			e->tag = t;
72 		}
73 
74 		if (git_tag_lookup(&t, repo, sha1) < 0)
75 			return 0;
76 
77 		*tag = t;
78 
79 		if (e->tag->tagger)
80 			e_time = e->tag->tagger->when.time;
81 
82 		if (t->tagger)
83 			t_time = t->tagger->when.time;
84 
85 		if (e_time < t_time)
86 			return 1;
87 	}
88 
89 	return 0;
90 }
91 
add_to_known_names(git_repository * repo,git_oidmap * names,const char * path,const git_oid * peeled,unsigned int prio,const git_oid * sha1)92 static int add_to_known_names(
93 	git_repository *repo,
94 	git_oidmap *names,
95 	const char *path,
96 	const git_oid *peeled,
97 	unsigned int prio,
98 	const git_oid *sha1)
99 {
100 	struct commit_name *e = find_commit_name(names, peeled);
101 	bool found = (e != NULL);
102 
103 	git_tag *tag = NULL;
104 	if (replace_name(&tag, repo, e, prio, sha1)) {
105 		if (!found) {
106 			e = git__malloc(sizeof(struct commit_name));
107 			GIT_ERROR_CHECK_ALLOC(e);
108 
109 			e->path = NULL;
110 			e->tag = NULL;
111 		}
112 
113 		if (e->tag)
114 			git_tag_free(e->tag);
115 		e->tag = tag;
116 		e->prio = prio;
117 		e->name_checked = 0;
118 		git_oid_cpy(&e->sha1, sha1);
119 		git__free(e->path);
120 		e->path = git__strdup(path);
121 		git_oid_cpy(&e->peeled, peeled);
122 
123 		if (!found && git_oidmap_set(names, &e->peeled, e) < 0)
124 			return -1;
125 	}
126 	else
127 		git_tag_free(tag);
128 
129 	return 0;
130 }
131 
retrieve_peeled_tag_or_object_oid(git_oid * peeled_out,git_oid * ref_target_out,git_repository * repo,const char * refname)132 static int retrieve_peeled_tag_or_object_oid(
133 	git_oid *peeled_out,
134 	git_oid *ref_target_out,
135 	git_repository *repo,
136 	const char *refname)
137 {
138 	git_reference *ref;
139 	git_object *peeled = NULL;
140 	int error;
141 
142 	if ((error = git_reference_lookup_resolved(&ref, repo, refname, -1)) < 0)
143 		return error;
144 
145 	if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_ANY)) < 0)
146 		goto cleanup;
147 
148 	git_oid_cpy(ref_target_out, git_reference_target(ref));
149 	git_oid_cpy(peeled_out, git_object_id(peeled));
150 
151 	if (git_oid_cmp(ref_target_out, peeled_out) != 0)
152 		error = 1; /* The reference was pointing to a annotated tag */
153 	else
154 		error = 0; /* Any other object */
155 
156 cleanup:
157 	git_reference_free(ref);
158 	git_object_free(peeled);
159 	return error;
160 }
161 
162 struct git_describe_result {
163 	int dirty;
164 	int exact_match;
165 	int fallback_to_id;
166 	git_oid commit_id;
167 	git_repository *repo;
168 	struct commit_name *name;
169 	struct possible_tag *tag;
170 };
171 
172 struct get_name_data
173 {
174 	git_describe_options *opts;
175 	git_repository *repo;
176 	git_oidmap *names;
177 	git_describe_result *result;
178 };
179 
commit_name_dup(struct commit_name ** out,struct commit_name * in)180 static int commit_name_dup(struct commit_name **out, struct commit_name *in)
181 {
182 	struct commit_name *name;
183 
184 	name = git__malloc(sizeof(struct commit_name));
185 	GIT_ERROR_CHECK_ALLOC(name);
186 
187 	memcpy(name, in,  sizeof(struct commit_name));
188 	name->tag = NULL;
189 	name->path = NULL;
190 
191 	if (in->tag && git_tag_dup(&name->tag, in->tag) < 0)
192 		return -1;
193 
194 	name->path = git__strdup(in->path);
195 	GIT_ERROR_CHECK_ALLOC(name->path);
196 
197 	*out = name;
198 	return 0;
199 }
200 
get_name(const char * refname,void * payload)201 static int get_name(const char *refname, void *payload)
202 {
203 	struct get_name_data *data;
204 	bool is_tag, is_annotated, all;
205 	git_oid peeled, sha1;
206 	unsigned int prio;
207 	int error = 0;
208 
209 	data = (struct get_name_data *)payload;
210 	is_tag = !git__prefixcmp(refname, GIT_REFS_TAGS_DIR);
211 	all = data->opts->describe_strategy == GIT_DESCRIBE_ALL;
212 
213 	/* Reject anything outside refs/tags/ unless --all */
214 	if (!all && !is_tag)
215 		return 0;
216 
217 	/* Accept only tags that match the pattern, if given */
218 	if (data->opts->pattern && (!is_tag || wildmatch(data->opts->pattern,
219 		refname + strlen(GIT_REFS_TAGS_DIR), 0)))
220 				return 0;
221 
222 	/* Is it annotated? */
223 	if ((error = retrieve_peeled_tag_or_object_oid(
224 		&peeled, &sha1, data->repo, refname)) < 0)
225 		return error;
226 
227 	is_annotated = error;
228 
229 	/*
230 	 * By default, we only use annotated tags, but with --tags
231 	 * we fall back to lightweight ones (even without --tags,
232 	 * we still remember lightweight ones, only to give hints
233 	 * in an error message).  --all allows any refs to be used.
234 	 */
235 	if (is_annotated)
236 		prio = 2;
237 	else if (is_tag)
238 		prio = 1;
239 	else
240 		prio = 0;
241 
242 	add_to_known_names(data->repo, data->names,
243 		all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR),
244 		&peeled, prio, &sha1);
245 	return 0;
246 }
247 
248 struct possible_tag {
249 	struct commit_name *name;
250 	int depth;
251 	int found_order;
252 	unsigned flag_within;
253 };
254 
possible_tag_dup(struct possible_tag ** out,struct possible_tag * in)255 static int possible_tag_dup(struct possible_tag **out, struct possible_tag *in)
256 {
257 	struct possible_tag *tag;
258 	int error;
259 
260 	tag = git__malloc(sizeof(struct possible_tag));
261 	GIT_ERROR_CHECK_ALLOC(tag);
262 
263 	memcpy(tag, in, sizeof(struct possible_tag));
264 	tag->name = NULL;
265 
266 	if ((error = commit_name_dup(&tag->name, in->name)) < 0) {
267 		git__free(tag);
268 		*out = NULL;
269 		return error;
270 	}
271 
272 	*out = tag;
273 	return 0;
274 }
275 
compare_pt(const void * a_,const void * b_)276 static int compare_pt(const void *a_, const void *b_)
277 {
278 	struct possible_tag *a = (struct possible_tag *)a_;
279 	struct possible_tag *b = (struct possible_tag *)b_;
280 	if (a->depth != b->depth)
281 		return a->depth - b->depth;
282 	if (a->found_order != b->found_order)
283 		return a->found_order - b->found_order;
284 	return 0;
285 }
286 
287 #define SEEN (1u << 0)
288 
finish_depth_computation(git_pqueue * list,git_revwalk * walk,struct possible_tag * best)289 static unsigned long finish_depth_computation(
290 	git_pqueue *list,
291 	git_revwalk *walk,
292 	struct possible_tag *best)
293 {
294 	unsigned long seen_commits = 0;
295 	int error, i;
296 
297 	while (git_pqueue_size(list) > 0) {
298 		git_commit_list_node *c = git_pqueue_pop(list);
299 		seen_commits++;
300 		if (c->flags & best->flag_within) {
301 			size_t index = 0;
302 			while (git_pqueue_size(list) > index) {
303 				git_commit_list_node *i = git_pqueue_get(list, index);
304 				if (!(i->flags & best->flag_within))
305 					break;
306 				index++;
307 			}
308 			if (index > git_pqueue_size(list))
309 				break;
310 		} else
311 			best->depth++;
312 		for (i = 0; i < c->out_degree; i++) {
313 			git_commit_list_node *p = c->parents[i];
314 			if ((error = git_commit_list_parse(walk, p)) < 0)
315 				return error;
316 			if (!(p->flags & SEEN))
317 				if ((error = git_pqueue_insert(list, p)) < 0)
318 					return error;
319 			p->flags |= c->flags;
320 		}
321 	}
322 	return seen_commits;
323 }
324 
display_name(git_buf * buf,git_repository * repo,struct commit_name * n)325 static int display_name(git_buf *buf, git_repository *repo, struct commit_name *n)
326 {
327 	if (n->prio == 2 && !n->tag) {
328 		if (git_tag_lookup(&n->tag, repo, &n->sha1) < 0) {
329 			git_error_set(GIT_ERROR_TAG, "annotated tag '%s' not available", n->path);
330 			return -1;
331 		}
332 	}
333 
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);
337 			return -1;
338 		}
339 
340 		/* TODO: Cope with warnings
341 		if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
342 			warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
343 		*/
344 
345 		n->name_checked = 1;
346 	}
347 
348 	if (n->tag)
349 		git_buf_printf(buf, "%s", git_tag_name(n->tag));
350 	else
351 		git_buf_printf(buf, "%s", n->path);
352 
353 	return 0;
354 }
355 
find_unique_abbrev_size(int * out,git_repository * repo,const git_oid * oid_in,unsigned int abbreviated_size)356 static int find_unique_abbrev_size(
357 	int *out,
358 	git_repository *repo,
359 	const git_oid *oid_in,
360 	unsigned int abbreviated_size)
361 {
362 	size_t size = abbreviated_size;
363 	git_odb *odb;
364 	git_oid dummy;
365 	int error;
366 
367 	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
368 		return error;
369 
370 	while (size < GIT_OID_HEXSZ) {
371 		if ((error = git_odb_exists_prefix(&dummy, odb, oid_in, size)) == 0) {
372 			*out = (int) size;
373 			return 0;
374 		}
375 
376 		/* If the error wasn't that it's not unique, then it's a proper error */
377 		if (error != GIT_EAMBIGUOUS)
378 			return error;
379 
380 		/* Try again with a larger size */
381 		size++;
382 	}
383 
384 	/* If we didn't find any shorter prefix, we have to do the whole thing */
385 	*out = GIT_OID_HEXSZ;
386 
387 	return 0;
388 }
389 
show_suffix(git_buf * buf,int depth,git_repository * repo,const git_oid * id,unsigned int abbrev_size)390 static int show_suffix(
391 	git_buf *buf,
392 	int depth,
393 	git_repository *repo,
394 	const git_oid* id,
395 	unsigned int abbrev_size)
396 {
397 	int error, size = 0;
398 
399 	char hex_oid[GIT_OID_HEXSZ];
400 
401 	if ((error = find_unique_abbrev_size(&size, repo, id, abbrev_size)) < 0)
402 		return error;
403 
404 	git_oid_fmt(hex_oid, id);
405 
406 	git_buf_printf(buf, "-%d-g", depth);
407 
408 	git_buf_put(buf, hex_oid, size);
409 
410 	return git_buf_oom(buf) ? -1 : 0;
411 }
412 
413 #define MAX_CANDIDATES_TAGS FLAG_BITS - 1
414 
describe_not_found(const git_oid * oid,const char * message_format)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);
418 
419 	git_error_set(GIT_ERROR_DESCRIBE, message_format, oid_str);
420 	return GIT_ENOTFOUND;
421 }
422 
describe(struct get_name_data * data,git_commit * commit)423 static int describe(
424 	struct get_name_data *data,
425 	git_commit *commit)
426 {
427 	struct commit_name *n;
428 	struct possible_tag *best;
429 	bool all, tags;
430 	git_revwalk *walk = NULL;
431 	git_pqueue list;
432 	git_commit_list_node *cmit, *gave_up_on = NULL;
433 	git_vector all_matches = GIT_VECTOR_INIT;
434 	unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
435 	unsigned long seen_commits = 0;	/* TODO: Check long */
436 	unsigned int unannotated_cnt = 0;
437 	int error;
438 
439 	if (git_vector_init(&all_matches, MAX_CANDIDATES_TAGS, compare_pt) < 0)
440 		return -1;
441 
442 	if ((error = git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp)) < 0)
443 		goto cleanup;
444 
445 	all = data->opts->describe_strategy == GIT_DESCRIBE_ALL;
446 	tags = data->opts->describe_strategy == GIT_DESCRIBE_TAGS;
447 
448 	git_oid_cpy(&data->result->commit_id, git_commit_id(commit));
449 
450 	n = find_commit_name(data->names, git_commit_id(commit));
451 	if (n && (tags || all || n->prio == 2)) {
452 		/*
453 		 * Exact match to an existing ref.
454 		 */
455 		data->result->exact_match = 1;
456 		if ((error = commit_name_dup(&data->result->name, n)) < 0)
457 			goto cleanup;
458 
459 		goto cleanup;
460 	}
461 
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'");
466 
467 		goto cleanup;
468 	}
469 
470 	if ((error = git_revwalk_new(&walk, git_commit_owner(commit))) < 0)
471 		goto cleanup;
472 
473 	if ((cmit = git_revwalk__commit_lookup(walk, git_commit_id(commit))) == NULL)
474 		goto cleanup;
475 
476 	if ((error = git_commit_list_parse(walk, cmit)) < 0)
477 		goto cleanup;
478 
479 	cmit->flags = SEEN;
480 
481 	if ((error = git_pqueue_insert(&list, cmit)) < 0)
482 		goto cleanup;
483 
484 	while (git_pqueue_size(&list) > 0)
485 	{
486 		int i;
487 
488 		git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list);
489 		seen_commits++;
490 
491 		n = find_commit_name(data->names, &c->oid);
492 
493 		if (n) {
494 			if (!tags && !all && n->prio < 2) {
495 				unannotated_cnt++;
496 			} else if (match_cnt < data->opts->max_candidates_tags) {
497 				struct possible_tag *t = git__malloc(sizeof(struct commit_name));
498 				GIT_ERROR_CHECK_ALLOC(t);
499 				if ((error = git_vector_insert(&all_matches, t)) < 0)
500 					goto cleanup;
501 
502 				match_cnt++;
503 
504 				t->name = n;
505 				t->depth = seen_commits - 1;
506 				t->flag_within = 1u << match_cnt;
507 				t->found_order = match_cnt;
508 				c->flags |= t->flag_within;
509 				if (n->prio == 2)
510 					annotated_cnt++;
511 			}
512 			else {
513 				gave_up_on = c;
514 				break;
515 			}
516 		}
517 
518 		for (cur_match = 0; cur_match < match_cnt; cur_match++) {
519 			struct possible_tag *t = git_vector_get(&all_matches, cur_match);
520 			if (!(c->flags & t->flag_within))
521 				t->depth++;
522 		}
523 
524 		if (annotated_cnt && (git_pqueue_size(&list) == 0)) {
525 			/*
526 			if (debug) {
527 				char oid_str[GIT_OID_HEXSZ + 1];
528 				git_oid_tostr(oid_str, sizeof(oid_str), &c->oid);
529 
530 				fprintf(stderr, "finished search at %s\n", oid_str);
531 			}
532 			*/
533 			break;
534 		}
535 		for (i = 0; i < c->out_degree; i++) {
536 			git_commit_list_node *p = c->parents[i];
537 			if ((error = git_commit_list_parse(walk, p)) < 0)
538 				goto cleanup;
539 			if (!(p->flags & SEEN))
540 				if ((error = git_pqueue_insert(&list, p)) < 0)
541 					goto cleanup;
542 			p->flags |= c->flags;
543 
544 			if (data->opts->only_follow_first_parent)
545 				break;
546 		}
547 	}
548 
549 	if (!match_cnt) {
550 		if (data->opts->show_commit_oid_as_fallback) {
551 			data->result->fallback_to_id = 1;
552 			git_oid_cpy(&data->result->commit_id, &cmit->oid);
553 
554 			goto cleanup;
555 		}
556 		if (unannotated_cnt) {
557 			error = describe_not_found(git_commit_id(commit),
558 				"cannot describe - "
559 				"no annotated tags can describe '%s'; "
560 			    "however, there were unannotated tags.");
561 			goto cleanup;
562 		}
563 		else {
564 			error = describe_not_found(git_commit_id(commit),
565 				"cannot describe - "
566 				"no tags can describe '%s'.");
567 			goto cleanup;
568 		}
569 	}
570 
571 	git_vector_sort(&all_matches);
572 
573 	best = (struct possible_tag *)git_vector_get(&all_matches, 0);
574 
575 	if (gave_up_on) {
576 		if ((error = git_pqueue_insert(&list, gave_up_on)) < 0)
577 			goto cleanup;
578 		seen_commits--;
579 	}
580 	if ((error = finish_depth_computation(
581 		&list, walk, best)) < 0)
582 		goto cleanup;
583 
584 	seen_commits += error;
585 	if ((error = possible_tag_dup(&data->result->tag, best)) < 0)
586 		goto cleanup;
587 
588 	/*
589 	{
590 		static const char *prio_names[] = {
591 			"head", "lightweight", "annotated",
592 		};
593 
594 		char oid_str[GIT_OID_HEXSZ + 1];
595 
596 		if (debug) {
597 			for (cur_match = 0; cur_match < match_cnt; cur_match++) {
598 				struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match);
599 				fprintf(stderr, " %-11s %8d %s\n",
600 					prio_names[t->name->prio],
601 					t->depth, t->name->path);
602 			}
603 			fprintf(stderr, "traversed %lu commits\n", seen_commits);
604 			if (gave_up_on) {
605 				git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid);
606 				fprintf(stderr,
607 					"more than %i tags found; listed %i most recent\n"
608 					"gave up search at %s\n",
609 					data->opts->max_candidates_tags, data->opts->max_candidates_tags,
610 					oid_str);
611 			}
612 		}
613 	}
614 	*/
615 
616 	git_oid_cpy(&data->result->commit_id, &cmit->oid);
617 
618 cleanup:
619 	{
620 		size_t i;
621 		struct possible_tag *match;
622 		git_vector_foreach(&all_matches, i, match) {
623 			git__free(match);
624 		}
625 	}
626 	git_vector_free(&all_matches);
627 	git_pqueue_free(&list);
628 	git_revwalk_free(walk);
629 	return error;
630 }
631 
normalize_options(git_describe_options * dst,const git_describe_options * src)632 static int normalize_options(
633 	git_describe_options *dst,
634 	const git_describe_options *src)
635 {
636 	git_describe_options default_options = GIT_DESCRIBE_OPTIONS_INIT;
637 	if (!src) src = &default_options;
638 
639 	*dst = *src;
640 
641 	if (dst->max_candidates_tags > GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS)
642 		dst->max_candidates_tags = GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS;
643 
644 	return 0;
645 }
646 
git_describe_commit(git_describe_result ** result,git_object * committish,git_describe_options * opts)647 int git_describe_commit(
648 	git_describe_result **result,
649 	git_object *committish,
650 	git_describe_options *opts)
651 {
652 	struct get_name_data data;
653 	struct commit_name *name;
654 	git_commit *commit;
655 	int error = -1;
656 	git_describe_options normalized;
657 
658 	GIT_ASSERT_ARG(result);
659 	GIT_ASSERT_ARG(committish);
660 
661 	data.result = git__calloc(1, sizeof(git_describe_result));
662 	GIT_ERROR_CHECK_ALLOC(data.result);
663 	data.result->repo = git_object_owner(committish);
664 
665 	data.repo = git_object_owner(committish);
666 
667 	if ((error = normalize_options(&normalized, opts)) < 0)
668 		return error;
669 
670 	GIT_ERROR_CHECK_VERSION(
671 		&normalized,
672 		GIT_DESCRIBE_OPTIONS_VERSION,
673 		"git_describe_options");
674 	data.opts = &normalized;
675 
676 	if ((error = git_oidmap_new(&data.names)) < 0)
677 		return error;
678 
679 	/** TODO: contains to be implemented */
680 
681 	if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJECT_COMMIT)) < 0)
682 		goto cleanup;
683 
684 	if ((error = git_reference_foreach_name(
685 			git_object_owner(committish),
686 			get_name, &data)) < 0)
687 				goto cleanup;
688 
689 	if (git_oidmap_size(data.names) == 0 && !normalized.show_commit_oid_as_fallback) {
690 		git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - "
691 			"no reference found, cannot describe anything.");
692 		error = -1;
693 		goto cleanup;
694 	}
695 
696 	if ((error = describe(&data, commit)) < 0)
697 		goto cleanup;
698 
699 cleanup:
700 	git_commit_free(commit);
701 
702 	git_oidmap_foreach_value(data.names, name, {
703 		git_tag_free(name->tag);
704 		git__free(name->path);
705 		git__free(name);
706 	});
707 
708 	git_oidmap_free(data.names);
709 
710 	if (error < 0)
711 		git_describe_result_free(data.result);
712 	else
713 		*result = data.result;
714 
715 	return error;
716 }
717 
git_describe_workdir(git_describe_result ** out,git_repository * repo,git_describe_options * opts)718 int git_describe_workdir(
719 	git_describe_result **out,
720 	git_repository *repo,
721 	git_describe_options *opts)
722 {
723 	int error;
724 	git_oid current_id;
725 	git_status_list *status = NULL;
726 	git_status_options status_opts = GIT_STATUS_OPTIONS_INIT;
727 	git_describe_result *result = NULL;
728 	git_object *commit;
729 
730 	if ((error = git_reference_name_to_id(&current_id, repo, GIT_HEAD_FILE)) < 0)
731 		return error;
732 
733 	if ((error = git_object_lookup(&commit, repo, &current_id, GIT_OBJECT_COMMIT)) < 0)
734 		return error;
735 
736 	/* The first step is to perform a describe of HEAD, so we can leverage this */
737 	if ((error = git_describe_commit(&result, commit, opts)) < 0)
738 		goto out;
739 
740 	if ((error = git_status_list_new(&status, repo, &status_opts)) < 0)
741 		goto out;
742 
743 
744 	if (git_status_list_entrycount(status) > 0)
745 		result->dirty = 1;
746 
747 out:
748 	git_object_free(commit);
749 	git_status_list_free(status);
750 
751 	if (error < 0)
752 		git_describe_result_free(result);
753 	else
754 		*out = result;
755 
756 	return error;
757 }
758 
normalize_format_options(git_describe_format_options * dst,const git_describe_format_options * src)759 static int normalize_format_options(
760 	git_describe_format_options *dst,
761 	const git_describe_format_options *src)
762 {
763 	if (!src) {
764 		git_describe_format_options_init(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION);
765 		return 0;
766 	}
767 
768 	memcpy(dst, src, sizeof(git_describe_format_options));
769 	return 0;
770 }
771 
git_describe_format(git_buf * out,const git_describe_result * result,const git_describe_format_options * given)772 int git_describe_format(git_buf *out, const git_describe_result *result, const git_describe_format_options *given)
773 {
774 	int error;
775 	git_repository *repo;
776 	struct commit_name *name;
777 	git_describe_format_options opts;
778 
779 	GIT_ASSERT_ARG(out);
780 	GIT_ASSERT_ARG(result);
781 
782 	GIT_ERROR_CHECK_VERSION(given, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, "git_describe_format_options");
783 	normalize_format_options(&opts, given);
784 
785 	if ((error = git_buf_sanitize(out)) < 0)
786 		return error;
787 
788 
789 	if (opts.always_use_long_format && opts.abbreviated_size == 0) {
790 		git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - "
791 			"'always_use_long_format' is incompatible with a zero"
792 			"'abbreviated_size'");
793 		return -1;
794 	}
795 
796 
797 	repo = result->repo;
798 
799 	/* If we did find an exact match, then it's the easier method */
800 	if (result->exact_match) {
801 		name = result->name;
802 		if ((error = display_name(out, repo, name)) < 0)
803 			return error;
804 
805 		if (opts.always_use_long_format) {
806 			const git_oid *id = name->tag ? git_tag_target_id(name->tag) : &result->commit_id;
807 			if ((error = show_suffix(out, 0, repo, id, opts.abbreviated_size)) < 0)
808 				return error;
809 		}
810 
811 		if (result->dirty && opts.dirty_suffix)
812 			git_buf_puts(out, opts.dirty_suffix);
813 
814 		return git_buf_oom(out) ? -1 : 0;
815 	}
816 
817 	/* If we didn't find *any* tags, we fall back to the commit's id */
818 	if (result->fallback_to_id) {
819 		char hex_oid[GIT_OID_HEXSZ + 1] = {0};
820 		int size = 0;
821 
822 		if ((error = find_unique_abbrev_size(
823 			     &size, repo, &result->commit_id, opts.abbreviated_size)) < 0)
824 			return -1;
825 
826 		git_oid_fmt(hex_oid, &result->commit_id);
827 		git_buf_put(out, hex_oid, size);
828 
829 		if (result->dirty && opts.dirty_suffix)
830 			git_buf_puts(out, opts.dirty_suffix);
831 
832 		return git_buf_oom(out) ? -1 : 0;
833 	}
834 
835 	/* Lastly, if we found a matching tag, we show that */
836 	name = result->tag->name;
837 
838 	if ((error = display_name(out, repo, name)) < 0)
839 		return error;
840 
841 	if (opts.abbreviated_size) {
842 		if ((error = show_suffix(out, result->tag->depth, repo,
843 			&result->commit_id, opts.abbreviated_size)) < 0)
844 			return error;
845 	}
846 
847 	if (result->dirty && opts.dirty_suffix) {
848 		git_buf_puts(out, opts.dirty_suffix);
849 	}
850 
851 	return git_buf_oom(out) ? -1 : 0;
852 }
853 
git_describe_result_free(git_describe_result * result)854 void git_describe_result_free(git_describe_result *result)
855 {
856 	if (result == NULL)
857 		return;
858 
859 	if (result->name) {
860 		git_tag_free(result->name->tag);
861 		git__free(result->name->path);
862 		git__free(result->name);
863 	}
864 
865 	if (result->tag) {
866 		git_tag_free(result->tag->name->tag);
867 		git__free(result->tag->name->path);
868 		git__free(result->tag->name);
869 		git__free(result->tag);
870 	}
871 
872 	git__free(result);
873 }
874 
git_describe_options_init(git_describe_options * opts,unsigned int version)875 int git_describe_options_init(git_describe_options *opts, unsigned int version)
876 {
877 	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
878 		opts, version, git_describe_options, GIT_DESCRIBE_OPTIONS_INIT);
879 	return 0;
880 }
881 
882 #ifndef GIT_DEPRECATE_HARD
git_describe_init_options(git_describe_options * opts,unsigned int version)883 int git_describe_init_options(git_describe_options *opts, unsigned int version)
884 {
885 	return git_describe_options_init(opts, version);
886 }
887 #endif
888 
git_describe_format_options_init(git_describe_format_options * opts,unsigned int version)889 int git_describe_format_options_init(git_describe_format_options *opts, unsigned int version)
890 {
891 	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
892 		opts, version, git_describe_format_options, GIT_DESCRIBE_FORMAT_OPTIONS_INIT);
893 	return 0;
894 }
895 
896 #ifndef GIT_DEPRECATE_HARD
git_describe_init_format_options(git_describe_format_options * opts,unsigned int version)897 int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version)
898 {
899 	return git_describe_format_options_init(opts, version);
900 }
901 #endif
902