1#include "cache.h"
2#include "commit.h"
3#include "tag.h"
4#include "refs.h"
5#include "diff.h"
6#include "diffcore.h"
7#include "revision.h"
8#include "builtin.h"
9
10static const char describe_usage[] =
11"git-describe [--all] [--tags] [--abbrev=<n>] <committish>*";
12
13static int all; /* Default to annotated tags only */
14static int tags; /* But allow any tags if --tags is specified */
15
16static int abbrev = DEFAULT_ABBREV;
17
18static int names, allocs;
19static struct commit_name {
20 struct commit *commit;
21 int prio; /* annotated tag = 2, tag = 1, head = 0 */
22 char path[FLEX_ARRAY]; /* more */
23} **name_array = NULL;
24
25static struct commit_name *match(struct commit *cmit)
26{
27 int i = names;
28 struct commit_name **p = name_array;
29
30 while (i-- > 0) {
31 struct commit_name *n = *p++;
32 if (n->commit == cmit)
33 return n;
34 }
35 return NULL;
36}
37
38static void add_to_known_names(const char *path,
39 struct commit *commit,
40 int prio)
41{
42 int idx;
43 int len = strlen(path)+1;
44 struct commit_name *name = xmalloc(sizeof(struct commit_name) + len);
45
46 name->commit = commit;
47 name->prio = prio;
48 memcpy(name->path, path, len);
49 idx = names;
50 if (idx >= allocs) {
51 allocs = (idx + 50) * 3 / 2;
52 name_array = xrealloc(name_array, allocs*sizeof(*name_array));
53 }
54 name_array[idx] = name;
55 names = ++idx;
56}
57
58static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
59{
60 struct commit *commit = lookup_commit_reference_gently(sha1, 1);
61 struct object *object;
62 int prio;
63
64 if (!commit)
65 return 0;
66 object = parse_object(sha1);
67 /* If --all, then any refs are used.
68 * If --tags, then any tags are used.
69 * Otherwise only annotated tags are used.
70 */
71 if (!strncmp(path, "refs/tags/", 10)) {
72 if (object->type == OBJ_TAG)
73 prio = 2;
74 else
75 prio = 1;
76 }
77 else
78 prio = 0;
79
80 if (!all) {
81 if (!prio)
82 return 0;
83 if (!tags && prio < 2)
84 return 0;
85 }
86 add_to_known_names(all ? path + 5 : path + 10, commit, prio);
87 return 0;
88}
89
90static int compare_names(const void *_a, const void *_b)
91{
92 struct commit_name *a = *(struct commit_name **)_a;
93 struct commit_name *b = *(struct commit_name **)_b;
94 unsigned long a_date = a->commit->date;
95 unsigned long b_date = b->commit->date;
96
97 if (a->prio != b->prio)
98 return b->prio - a->prio;
99 return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
100}
101
102struct possible_tag {
103 struct possible_tag *next;
104 struct commit_name *name;
105 unsigned long depth;
106};
107
108static void describe(const char *arg, int last_one)
109{
110 unsigned char sha1[20];
111 struct commit *cmit;
112 struct commit_list *list;
113 static int initialized = 0;
114 struct commit_name *n;
115 struct possible_tag *all_matches, *min_match, *cur_match;
116
117 if (get_sha1(arg, sha1))
118 die("Not a valid object name %s", arg);
119 cmit = lookup_commit_reference(sha1);
120 if (!cmit)
121 die("%s is not a valid '%s' object", arg, commit_type);
122
123 if (!initialized) {
124 initialized = 1;
125 for_each_ref(get_name, NULL);
126 qsort(name_array, names, sizeof(*name_array), compare_names);
127 }
128
129 n = match(cmit);
130 if (n) {
131 printf("%s\n", n->path);
132 return;
133 }
134
135 list = NULL;
136 all_matches = NULL;
137 cur_match = NULL;
138 commit_list_insert(cmit, &list);
139 while (list) {
140 struct commit *c = pop_commit(&list);
141 struct commit_list *parents = c->parents;
142 n = match(c);
143 if (n) {
144 struct possible_tag *p = xmalloc(sizeof(*p));
145 p->name = n;
146 p->next = NULL;
147 if (cur_match)
148 cur_match->next = p;
149 else
150 all_matches = p;
151 cur_match = p;
152 if (n->prio == 2)
153 continue;
154 }
155 while (parents) {
156 struct commit *p = parents->item;
157 parse_commit(p);
158 if (!(p->object.flags & SEEN)) {
159 p->object.flags |= SEEN;
160 insert_by_date(p, &list);
161 }
162 parents = parents->next;
163 }
164 }
165
166 if (!all_matches)
167 die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1));
168
169 min_match = NULL;
170 for (cur_match = all_matches; cur_match; cur_match = cur_match->next) {
171 struct rev_info revs;
172 struct commit *tagged = cur_match->name->commit;
173
174 clear_commit_marks(cmit, -1);
175 init_revisions(&revs, NULL);
176 tagged->object.flags |= UNINTERESTING;
177 add_pending_object(&revs, &tagged->object, NULL);
178 add_pending_object(&revs, &cmit->object, NULL);
179
180 prepare_revision_walk(&revs);
181 cur_match->depth = 0;
182 while ((!min_match || cur_match->depth < min_match->depth)
183 && get_revision(&revs))
184 cur_match->depth++;
185 if (!min_match || (cur_match->depth < min_match->depth
186 && cur_match->name->prio >= min_match->name->prio))
187 min_match = cur_match;
188 free_commit_list(revs.commits);
189 }
190 printf("%s-g%s\n", min_match->name->path,
191 find_unique_abbrev(cmit->object.sha1, abbrev));
192
193 if (!last_one) {
194 for (cur_match = all_matches; cur_match; cur_match = min_match) {
195 min_match = cur_match->next;
196 free(cur_match);
197 }
198 clear_commit_marks(cmit, SEEN);
199 }
200}
201
202int cmd_describe(int argc, const char **argv, const char *prefix)
203{
204 int i;
205
206 for (i = 1; i < argc; i++) {
207 const char *arg = argv[i];
208
209 if (*arg != '-')
210 break;
211 else if (!strcmp(arg, "--all"))
212 all = 1;
213 else if (!strcmp(arg, "--tags"))
214 tags = 1;
215 else if (!strncmp(arg, "--abbrev=", 9)) {
216 abbrev = strtoul(arg + 9, NULL, 10);
217 if (abbrev < MINIMUM_ABBREV || 40 < abbrev)
218 abbrev = DEFAULT_ABBREV;
219 }
220 else
221 usage(describe_usage);
222 }
223
224 save_commit_buffer = 0;
225
226 if (argc <= i)
227 describe("HEAD", 1);
228 else
229 while (i < argc) {
230 describe(argv[i], (i == argc - 1));
231 i++;
232 }
233
234 return 0;
235}