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