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 n = match(c);
142 if (n) {
143 struct possible_tag *p = xmalloc(sizeof(*p));
144 p->name = n;
145 p->next = NULL;
146 if (cur_match)
147 cur_match->next = p;
148 else
149 all_matches = p;
150 cur_match = p;
151 } else {
152 struct commit_list *parents = c->parents;
153 while (parents) {
154 struct commit *p = parents->item;
155 parse_commit(p);
156 if (!(p->object.flags & SEEN)) {
157 p->object.flags |= SEEN;
158 insert_by_date(p, &list);
159 }
160 parents = parents->next;
161 }
162 }
163 }
164
165 if (!all_matches)
166 die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1));
167
168 min_match = NULL;
169 for (cur_match = all_matches; cur_match; cur_match = cur_match->next) {
170 struct rev_info revs;
171 struct commit *tagged = cur_match->name->commit;
172
173 clear_commit_marks(cmit, -1);
174 init_revisions(&revs, NULL);
175 tagged->object.flags |= UNINTERESTING;
176 add_pending_object(&revs, &tagged->object, NULL);
177 add_pending_object(&revs, &cmit->object, NULL);
178
179 prepare_revision_walk(&revs);
180 cur_match->depth = 0;
181 while ((!min_match || cur_match->depth < min_match->depth)
182 && get_revision(&revs))
183 cur_match->depth++;
184 if (!min_match || cur_match->depth < min_match->depth)
185 min_match = cur_match;
186 free_commit_list(revs.commits);
187 }
188 printf("%s-g%s\n", min_match->name->path,
189 find_unique_abbrev(cmit->object.sha1, abbrev));
190
191 if (!last_one) {
192 for (cur_match = all_matches; cur_match; cur_match = min_match) {
193 min_match = cur_match->next;
194 free(cur_match);
195 }
196 clear_commit_marks(cmit, SEEN);
197 }
198}
199
200int cmd_describe(int argc, const char **argv, const char *prefix)
201{
202 int i;
203
204 for (i = 1; i < argc; i++) {
205 const char *arg = argv[i];
206
207 if (*arg != '-')
208 break;
209 else if (!strcmp(arg, "--all"))
210 all = 1;
211 else if (!strcmp(arg, "--tags"))
212 tags = 1;
213 else if (!strncmp(arg, "--abbrev=", 9)) {
214 abbrev = strtoul(arg + 9, NULL, 10);
215 if (abbrev < MINIMUM_ABBREV || 40 < abbrev)
216 abbrev = DEFAULT_ABBREV;
217 }
218 else
219 usage(describe_usage);
220 }
221
222 save_commit_buffer = 0;
223
224 if (argc <= i)
225 describe("HEAD", 1);
226 else
227 while (i < argc) {
228 describe(argv[i], (i == argc - 1));
229 i++;
230 }
231
232 return 0;
233}