Add a "git-describe" command
[git.git] / describe.c
1 #include "cache.h"
2 #include "commit.h"
3 #include "refs.h"
4
5 #define SEEN (1u << 0)
6
7 static const char describe_usage[] = "git-describe [--all] <committish>*";
8
9 static int all = 0;     /* Default to tags only */
10
11 static int names = 0, allocs = 0;
12 static struct commit_name {
13         const struct commit *commit;
14         char path[];
15 } **name_array = NULL;
16
17 static struct commit_name *match(struct commit *cmit)
18 {
19         int i = names;
20         struct commit_name **p = name_array;
21
22         while (i-- > 0) {
23                 struct commit_name *n = *p++;
24                 if (n->commit == cmit)
25                         return n;
26         }
27         return NULL;
28 }
29
30 static void add_to_known_names(const char *path, const struct commit *commit)
31 {
32         int idx;
33         int len = strlen(path)+1;
34         struct commit_name *name = xmalloc(sizeof(struct commit_name) + len);
35
36         name->commit = commit;
37         memcpy(name->path, path, len);
38         idx = names;
39         if (idx >= allocs) {
40                 allocs = (idx + 50) * 3 / 2;
41                 name_array = xrealloc(name_array, allocs*sizeof(*name_array));
42         }
43         name_array[idx] = name;
44         names = ++idx;
45 }
46
47 static int get_name(const char *path, const unsigned char *sha1)
48 {
49         struct commit *commit = lookup_commit_reference_gently(sha1, 1);
50         if (!commit)
51                 return 0;
52         if (!all && strncmp(path, "refs/tags/", 10))
53                 return 0;
54         add_to_known_names(path, commit);
55         return 0;
56 }
57
58 static int compare_names(const void *_a, const void *_b)
59 {
60         struct commit_name *a = *(struct commit_name **)_a;
61         struct commit_name *b = *(struct commit_name **)_b;
62         unsigned long a_date = a->commit->date;
63         unsigned long b_date = b->commit->date;
64         return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
65 }
66
67 static void describe(struct commit *cmit)
68 {
69         struct commit_list *list;
70         static int initialized = 0;
71         struct commit_name *n;
72
73         if (!initialized) {
74                 initialized = 1;
75                 for_each_ref(get_name);
76                 qsort(name_array, names, sizeof(*name_array), compare_names);
77         }
78
79         n = match(cmit);
80         if (n) {
81                 printf("%s\n", n->path);
82                 return;
83         }
84
85         list = NULL;
86         commit_list_insert(cmit, &list);
87         while (list) {
88                 struct commit *c = pop_most_recent_commit(&list, SEEN);
89                 n = match(c);
90                 if (n) {
91                         printf("%s-g%.8s\n", n->path, sha1_to_hex(cmit->object.sha1));
92                         return;
93                 }
94         }
95 }
96
97 int main(int argc, char **argv)
98 {
99         int i;
100
101         for (i = 1; i < argc; i++) {
102                 const char *arg = argv[i];
103                 unsigned char sha1[20];
104                 struct commit *cmit;
105
106                 if (!strcmp(arg, "--all")) {
107                         all = 1;
108                         continue;
109                 }
110                 if (get_sha1(arg, sha1) < 0)
111                         usage(describe_usage);
112                 cmit = lookup_commit_reference(sha1);
113                 if (!cmit)
114                         usage(describe_usage);
115                 describe(cmit);
116         }
117         return 0;
118 }