Make git-rev-parse support cogito-style "short hex names"
[git.git] / rev-parse.c
1 /*
2  * rev-parse.c
3  *
4  * Copyright (C) Linus Torvalds, 2005
5  */
6 #include "cache.h"
7 #include "commit.h"
8 #include "refs.h"
9
10 static char *def = NULL;
11 static int no_revs = 0;
12 static int single_rev = 0;
13 static int revs_only = 0;
14 static int do_rev_argument = 1;
15 static int output_revs = 0;
16
17 #define NORMAL 0
18 #define REVERSED 1
19 static int show_type = NORMAL;
20
21 static int get_extended_sha1(char *name, unsigned char *sha1);
22
23 /*
24  * Some arguments are relevant "revision" arguments,
25  * others are about output format or other details.
26  * This sorts it all out.
27  */
28 static int is_rev_argument(const char *arg)
29 {
30         static const char *rev_args[] = {
31                 "--max-count=",
32                 "--max-age=",
33                 "--min-age=",
34                 "--merge-order",
35                 NULL
36         };
37         const char **p = rev_args;
38
39         for (;;) {
40                 const char *str = *p++;
41                 int len;
42                 if (!str)
43                         return 0;
44                 len = strlen(str);
45                 if (!strncmp(arg, str, len))
46                         return 1;
47         }
48 }
49
50 static void show_rev(int type, const unsigned char *sha1)
51 {
52         if (no_revs)
53                 return;
54         output_revs++;
55         printf("%s%s\n", type == show_type ? "" : "^", sha1_to_hex(sha1));
56 }
57
58 static void show_rev_arg(char *rev)
59 {
60         if (no_revs)
61                 return;
62         puts(rev);
63 }
64
65 static void show_norev(char *norev)
66 {
67         if (revs_only)
68                 return;
69         puts(norev);
70 }
71
72 static void show_arg(char *arg)
73 {
74         if (do_rev_argument && is_rev_argument(arg))
75                 show_rev_arg(arg);
76         else
77                 show_norev(arg);
78 }
79
80 static int get_parent(char *name, unsigned char *result, int idx)
81 {
82         unsigned char sha1[20];
83         int ret = get_extended_sha1(name, sha1);
84         struct commit *commit;
85         struct commit_list *p;
86
87         if (ret)
88                 return ret;
89         commit = lookup_commit_reference(sha1);
90         if (!commit)
91                 return -1;
92         if (parse_commit(commit))
93                 return -1;
94         p = commit->parents;
95         while (p) {
96                 if (!--idx) {
97                         memcpy(result, p->item->object.sha1, 20);
98                         return 0;
99                 }
100                 p = p->next;
101         }
102         return -1;
103 }
104
105 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
106 {
107         static char dirname[PATH_MAX];
108         char hex[40];
109         DIR *dir;
110         int found;
111
112         snprintf(dirname, sizeof(dirname), "%s/%.2s", get_object_directory(), name);
113         dir = opendir(dirname);
114         sprintf(hex, "%.2s", name);
115         found = 0;
116         if (dir) {
117                 struct dirent *de;
118                 while ((de = readdir(dir)) != NULL) {
119                         if (strlen(de->d_name) != 38)
120                                 continue;
121                         if (memcmp(de->d_name, name + 2, len-2))
122                                 continue;
123                         memcpy(hex + 2, de->d_name, 38);
124                         if (++found > 1)
125                                 break;
126                 }
127                 closedir(dir);
128         }
129         if (found == 1)
130                 return get_sha1_hex(hex, sha1) == 0;
131         return 0;
132 }
133
134 static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
135 {
136         return 0;
137 }
138
139 static int get_short_sha1(char *name, unsigned char *sha1)
140 {
141         int i;
142         char canonical[40];
143         unsigned char res[20];
144
145         memset(res, 0, 20);
146         memset(canonical, 'x', 40);
147         for (i = 0;;i++) {
148                 unsigned char c = name[i];
149                 unsigned char val;
150                 if (!c || i > 40)
151                         break;
152                 if (c >= '0' && c <= '9')
153                         val = c - '0';
154                 else if (c >= 'a' && c <= 'f')
155                         val = c - 'a' + 10;
156                 else if (c >= 'A' && c <='F') {
157                         val = c - 'A' + 10;
158                         c -= 'A' - 'a';
159                 }
160                 else
161                         return -1;
162                 canonical[i] = c;
163                 if (!(i & 1))
164                         val <<= 4;
165                 res[i >> 1] |= val;
166         }
167         if (i < 4)
168                 return -1;
169         if (find_short_object_filename(i, canonical, sha1))
170                 return 0;
171         if (find_short_packed_object(i, res, sha1))
172                 return 0;
173         return -1;
174 }
175
176 /*
177  * This is like "get_sha1()", except it allows "sha1 expressions",
178  * notably "xyz^" for "parent of xyz"
179  */
180 static int get_extended_sha1(char *name, unsigned char *sha1)
181 {
182         int parent, ret;
183         int len = strlen(name);
184
185         parent = 1;
186         if (len > 2 && name[len-1] >= '1' && name[len-1] <= '9') {
187                 parent = name[len-1] - '0';
188                 len--;
189         }
190         if (len > 1 && name[len-1] == '^') {
191                 name[len-1] = 0;
192                 ret = get_parent(name, sha1, parent);
193                 name[len-1] = '^';
194                 if (!ret)
195                         return 0;
196         }
197         ret = get_sha1(name, sha1);
198         if (!ret)
199                 return 0;
200         return get_short_sha1(name, sha1);
201 }
202
203 static void show_default(void)
204 {
205         char *s = def;
206
207         if (s) {
208                 unsigned char sha1[20];
209
210                 def = NULL;
211                 if (!get_extended_sha1(s, sha1)) {
212                         show_rev(NORMAL, sha1);
213                         return;
214                 }
215                 show_arg(s);
216         }
217 }
218
219 static int show_reference(const char *refname, const unsigned char *sha1)
220 {
221         show_rev(NORMAL, sha1);
222         return 0;
223 }
224
225 int main(int argc, char **argv)
226 {
227         int i, as_is = 0;
228         unsigned char sha1[20];
229
230         for (i = 1; i < argc; i++) {
231                 char *arg = argv[i];
232                 char *dotdot;
233         
234                 if (as_is) {
235                         show_norev(arg);
236                         continue;
237                 }
238                 if (*arg == '-') {
239                         if (!strcmp(arg, "--")) {
240                                 show_default();
241                                 if (revs_only)
242                                         break;
243                                 as_is = 1;
244                         }
245                         if (!strcmp(arg, "--default")) {
246                                 def = argv[i+1];
247                                 i++;
248                                 continue;
249                         }
250                         if (!strcmp(arg, "--revs-only")) {
251                                 revs_only = 1;
252                                 continue;
253                         }
254                         if (!strcmp(arg, "--no-revs")) {
255                                 no_revs = 1;
256                                 continue;
257                         }
258                         if (!strcmp(arg, "--verify")) {
259                                 revs_only = 1;
260                                 do_rev_argument = 0;
261                                 single_rev = 1;
262                                 continue;
263                         }
264                         if (!strcmp(arg, "--not")) {
265                                 show_type ^= REVERSED;
266                                 continue;
267                         }
268                         if (!strcmp(arg, "--all")) {
269                                 for_each_ref(show_reference);
270                                 continue;
271                         }
272                         show_arg(arg);
273                         continue;
274                 }
275                 dotdot = strstr(arg, "..");
276                 if (dotdot) {
277                         unsigned char end[20];
278                         char *n = dotdot+2;
279                         *dotdot = 0;
280                         if (!get_extended_sha1(arg, sha1)) {
281                                 if (!*n)
282                                         n = "HEAD";
283                                 if (!get_extended_sha1(n, end)) {
284                                         if (no_revs)
285                                                 continue;
286                                         def = NULL;
287                                         show_rev(NORMAL, end);
288                                         show_rev(REVERSED, sha1);
289                                         continue;
290                                 }
291                         }
292                         *dotdot = '.';
293                 }
294                 if (!get_extended_sha1(arg, sha1)) {
295                         if (no_revs)
296                                 continue;
297                         def = NULL;
298                         show_rev(NORMAL, sha1);
299                         continue;
300                 }
301                 if (*arg == '^' && !get_extended_sha1(arg+1, sha1)) {
302                         if (no_revs)
303                                 continue;
304                         def = NULL;
305                         show_rev(REVERSED, sha1);
306                         continue;
307                 }
308                 show_default();
309                 show_norev(arg);
310         }
311         show_default();
312         if (single_rev && output_revs != 1) {
313                 fprintf(stderr, "Needed a single revision\n");
314                 exit(1);
315         }
316         return 0;
317 }