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;
16
17#define NORMAL 0
18#define REVERSED 1
19static int show_type = NORMAL;
20
21static 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 */
28static 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
50static 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
58static void show_rev_arg(char *rev)
59{
60 if (no_revs)
61 return;
62 puts(rev);
63}
64
65static void show_norev(char *norev)
66{
67 if (revs_only)
68 return;
69 puts(norev);
70}
71
72static 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
80static 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
105static 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
134static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b)
135{
136 do {
137 if (*a != *b)
138 return 0;
139 a++;
140 b++;
141 len -= 2;
142 } while (len > 1);
143 if (len)
144 if ((*a ^ *b) & 0xf0)
145 return 0;
146 return 1;
147}
148
149static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
150{
151 struct packed_git *p;
152
153 prepare_packed_git();
154 for (p = packed_git; p; p = p->next) {
155 unsigned num = num_packed_objects(p);
156 unsigned first = 0, last = num;
157 while (first < last) {
158 unsigned mid = (first + last) / 2;
159 unsigned char now[20];
160 int cmp;
161
162 nth_packed_object_sha1(p, mid, now);
163 cmp = memcmp(match, now, 20);
164 if (!cmp) {
165 first = mid;
166 break;
167 }
168 if (cmp > 0) {
169 first = mid+1;
170 continue;
171 }
172 last = mid;
173 }
174 if (first < num) {
175 unsigned char now[20], next[20];
176 nth_packed_object_sha1(p, first, now);
177 if (match_sha(len, match, now)) {
178 if (nth_packed_object_sha1(p, first+1, next) || !match_sha(len, match, next)) {
179 memcpy(sha1, now, 20);
180 return 1;
181 }
182 }
183 }
184 }
185 return 0;
186}
187
188static int get_short_sha1(char *name, unsigned char *sha1)
189{
190 int i;
191 char canonical[40];
192 unsigned char res[20];
193
194 memset(res, 0, 20);
195 memset(canonical, 'x', 40);
196 for (i = 0;;i++) {
197 unsigned char c = name[i];
198 unsigned char val;
199 if (!c || i > 40)
200 break;
201 if (c >= '0' && c <= '9')
202 val = c - '0';
203 else if (c >= 'a' && c <= 'f')
204 val = c - 'a' + 10;
205 else if (c >= 'A' && c <='F') {
206 val = c - 'A' + 10;
207 c -= 'A' - 'a';
208 }
209 else
210 return -1;
211 canonical[i] = c;
212 if (!(i & 1))
213 val <<= 4;
214 res[i >> 1] |= val;
215 }
216 if (i < 4)
217 return -1;
218 if (find_short_object_filename(i, canonical, sha1))
219 return 0;
220 if (find_short_packed_object(i, res, sha1))
221 return 0;
222 return -1;
223}
224
225/*
226 * This is like "get_sha1()", except it allows "sha1 expressions",
227 * notably "xyz^" for "parent of xyz"
228 */
229static int get_extended_sha1(char *name, unsigned char *sha1)
230{
231 int parent, ret;
232 int len = strlen(name);
233
234 parent = 1;
235 if (len > 2 && name[len-1] >= '1' && name[len-1] <= '9') {
236 parent = name[len-1] - '0';
237 len--;
238 }
239 if (len > 1 && name[len-1] == '^') {
240 name[len-1] = 0;
241 ret = get_parent(name, sha1, parent);
242 name[len-1] = '^';
243 if (!ret)
244 return 0;
245 }
246 ret = get_sha1(name, sha1);
247 if (!ret)
248 return 0;
249 return get_short_sha1(name, sha1);
250}
251
252static void show_default(void)
253{
254 char *s = def;
255
256 if (s) {
257 unsigned char sha1[20];
258
259 def = NULL;
260 if (!get_extended_sha1(s, sha1)) {
261 show_rev(NORMAL, sha1);
262 return;
263 }
264 show_arg(s);
265 }
266}
267
268static int show_reference(const char *refname, const unsigned char *sha1)
269{
270 show_rev(NORMAL, sha1);
271 return 0;
272}
273
274int main(int argc, char **argv)
275{
276 int i, as_is = 0;
277 unsigned char sha1[20];
278
279 for (i = 1; i < argc; i++) {
280 char *arg = argv[i];
281 char *dotdot;
282
283 if (as_is) {
284 show_norev(arg);
285 continue;
286 }
287 if (*arg == '-') {
288 if (!strcmp(arg, "--")) {
289 show_default();
290 if (revs_only)
291 break;
292 as_is = 1;
293 }
294 if (!strcmp(arg, "--default")) {
295 def = argv[i+1];
296 i++;
297 continue;
298 }
299 if (!strcmp(arg, "--revs-only")) {
300 revs_only = 1;
301 continue;
302 }
303 if (!strcmp(arg, "--no-revs")) {
304 no_revs = 1;
305 continue;
306 }
307 if (!strcmp(arg, "--verify")) {
308 revs_only = 1;
309 do_rev_argument = 0;
310 single_rev = 1;
311 continue;
312 }
313 if (!strcmp(arg, "--not")) {
314 show_type ^= REVERSED;
315 continue;
316 }
317 if (!strcmp(arg, "--all")) {
318 for_each_ref(show_reference);
319 continue;
320 }
321 show_arg(arg);
322 continue;
323 }
324 dotdot = strstr(arg, "..");
325 if (dotdot) {
326 unsigned char end[20];
327 char *n = dotdot+2;
328 *dotdot = 0;
329 if (!get_extended_sha1(arg, sha1)) {
330 if (!*n)
331 n = "HEAD";
332 if (!get_extended_sha1(n, end)) {
333 if (no_revs)
334 continue;
335 def = NULL;
336 show_rev(NORMAL, end);
337 show_rev(REVERSED, sha1);
338 continue;
339 }
340 }
341 *dotdot = '.';
342 }
343 if (!get_extended_sha1(arg, sha1)) {
344 if (no_revs)
345 continue;
346 def = NULL;
347 show_rev(NORMAL, sha1);
348 continue;
349 }
350 if (*arg == '^' && !get_extended_sha1(arg+1, sha1)) {
351 if (no_revs)
352 continue;
353 def = NULL;
354 show_rev(REVERSED, sha1);
355 continue;
356 }
357 show_default();
358 show_norev(arg);
359 }
360 show_default();
361 if (single_rev && output_revs != 1) {
362 fprintf(stderr, "Needed a single revision\n");
363 exit(1);
364 }
365 return 0;
366}