Merge branch 'master' of git://git.kernel.org/pub/scm/git/git
[git/spearce.git] / builtin-describe.c
bloba8c98cea163a28cafb9b23861bd976b6354c2def
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 */
16 static int abbrev = DEFAULT_ABBREV;
18 static int names, allocs;
19 static 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;
25 static struct commit_name *match(struct commit *cmit)
27 int i = names;
28 struct commit_name **p = name_array;
30 while (i-- > 0) {
31 struct commit_name *n = *p++;
32 if (n->commit == cmit)
33 return n;
35 return NULL;
38 static void add_to_known_names(const char *path,
39 struct commit *commit,
40 int prio)
42 int idx;
43 int len = strlen(path)+1;
44 struct commit_name *name = xmalloc(sizeof(struct commit_name) + len);
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));
54 name_array[idx] = name;
55 names = ++idx;
58 static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
60 struct commit *commit = lookup_commit_reference_gently(sha1, 1);
61 struct object *object;
62 int prio;
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.
71 if (!strncmp(path, "refs/tags/", 10)) {
72 if (object->type == OBJ_TAG)
73 prio = 2;
74 else
75 prio = 1;
77 else
78 prio = 0;
80 if (!all) {
81 if (!prio)
82 return 0;
83 if (!tags && prio < 2)
84 return 0;
86 add_to_known_names(all ? path + 5 : path + 10, commit, prio);
87 return 0;
90 static int compare_names(const void *_a, const void *_b)
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;
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;
102 struct possible_tag {
103 struct possible_tag *next;
104 struct commit_name *name;
105 unsigned long depth;
108 static void describe(const char *arg, int last_one)
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;
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);
123 if (!initialized) {
124 initialized = 1;
125 for_each_ref(get_name, NULL);
126 qsort(name_array, names, sizeof(*name_array), compare_names);
129 n = match(cmit);
130 if (n) {
131 printf("%s\n", n->path);
132 return;
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);
160 parents = parents->next;
165 if (!all_matches)
166 die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1));
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;
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);
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);
188 printf("%s-g%s\n", min_match->name->path,
189 find_unique_abbrev(cmit->object.sha1, abbrev));
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);
196 clear_commit_marks(cmit, SEEN);
200 int cmd_describe(int argc, const char **argv, const char *prefix)
202 int i;
204 for (i = 1; i < argc; i++) {
205 const char *arg = argv[i];
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;
218 else
219 usage(describe_usage);
222 save_commit_buffer = 0;
224 if (argc <= i)
225 describe("HEAD", 1);
226 else
227 while (i < argc) {
228 describe(argv[i], (i == argc - 1));
229 i++;
232 return 0;