summaryrefslogtreecommitdiff
path: root/describe.c
blob: e1b6588c9b8d0bd70702a56e182b2352dacc4386 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include "cache.h"
#include "commit.h"
#include "tag.h"
#include "refs.h"

#define SEEN (1u << 0)

static const char describe_usage[] = "git-describe [--all] <committish>*";

static int all = 0;	/* Default to annotated tags only */

static int names = 0, allocs = 0;
static struct commit_name {
	const struct commit *commit;
	char path[];
} **name_array = NULL;

static struct commit_name *match(struct commit *cmit)
{
	int i = names;
	struct commit_name **p = name_array;

	while (i-- > 0) {
		struct commit_name *n = *p++;
		if (n->commit == cmit)
			return n;
	}
	return NULL;
}

static void add_to_known_names(const char *path, const struct commit *commit)
{
	int idx;
	int len = strlen(path)+1;
	struct commit_name *name = xmalloc(sizeof(struct commit_name) + len);

	name->commit = commit;
	memcpy(name->path, path, len);
	idx = names;
	if (idx >= allocs) {
		allocs = (idx + 50) * 3 / 2;
		name_array = xrealloc(name_array, allocs*sizeof(*name_array));
	}
	name_array[idx] = name;
	names = ++idx;
}

static int get_name(const char *path, const unsigned char *sha1)
{
	struct commit *commit = lookup_commit_reference_gently(sha1, 1);
	if (!commit)
		return 0;
	if (!all) {
		struct object *object;
		if (strncmp(path, "refs/tags/", 10))
			return 0;
		object = parse_object(sha1);
		if (object->type != tag_type)
			return 0;
	}
	add_to_known_names(all ? path : path + 10, commit);
	return 0;
}

static int compare_names(const void *_a, const void *_b)
{
	struct commit_name *a = *(struct commit_name **)_a;
	struct commit_name *b = *(struct commit_name **)_b;
	unsigned long a_date = a->commit->date;
	unsigned long b_date = b->commit->date;
	return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
}

static void describe(struct commit *cmit)
{
	struct commit_list *list;
	static int initialized = 0;
	struct commit_name *n;

	if (!initialized) {
		initialized = 1;
		for_each_ref(get_name);
		qsort(name_array, names, sizeof(*name_array), compare_names);
	}

	n = match(cmit);
	if (n) {
		printf("%s\n", n->path);
		return;
	}

	list = NULL;
	commit_list_insert(cmit, &list);
	while (list) {
		struct commit *c = pop_most_recent_commit(&list, SEEN);
		n = match(c);
		if (n) {
			printf("%s-g%.8s\n", n->path, sha1_to_hex(cmit->object.sha1));
			return;
		}
	}
}

int main(int argc, char **argv)
{
	int i;

	for (i = 1; i < argc; i++) {
		const char *arg = argv[i];
		unsigned char sha1[20];
		struct commit *cmit;

		if (!strcmp(arg, "--all")) {
			all = 1;
			continue;
		}
		if (get_sha1(arg, sha1) < 0)
			usage(describe_usage);
		cmit = lookup_commit_reference(sha1);
		if (!cmit)
			usage(describe_usage);
		describe(cmit);
	}
	return 0;
}