Code

582ef023f7c8d00576c403ec80b7e541d3edf494
[git.git] / builtin-describe.c
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;
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)
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         }
207 int cmd_describe(int argc, const char **argv, const char *prefix)
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;