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