]>
Commit | Line | Data |
---|---|---|
ac250c56 BS |
1 | /* |
2 | * Copyright (C) 2009-2012 the libgit2 contributors | |
3 | * | |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
8 | #include <assert.h> | |
9 | ||
10 | #include "common.h" | |
11 | #include "buffer.h" | |
a346992f | 12 | #include "date.h" |
244d2f6b | 13 | #include "tree.h" |
ac250c56 | 14 | |
a346992f | 15 | #include "git2.h" |
ac250c56 BS |
16 | |
17 | GIT_BEGIN_DECL | |
18 | ||
19 | typedef enum { | |
20 | REVPARSE_STATE_INIT, | |
023c6f69 | 21 | REVPARSE_STATE_CARET, |
023c6f69 | 22 | REVPARSE_STATE_LINEAR, |
244d2f6b | 23 | REVPARSE_STATE_COLON, |
e0887d81 | 24 | REVPARSE_STATE_DONE, |
ac250c56 BS |
25 | } revparse_state; |
26 | ||
023c6f69 BS |
27 | static void set_invalid_syntax_err(const char *spec) |
28 | { | |
29 | giterr_set(GITERR_INVALID, "Refspec '%s' is not valid.", spec); | |
30 | } | |
31 | ||
ac250c56 BS |
32 | static int revparse_lookup_fully_qualifed_ref(git_object **out, git_repository *repo, const char*spec) |
33 | { | |
ec6a632a | 34 | git_oid resolved; |
ac250c56 | 35 | |
ec6a632a BS |
36 | if (git_reference_name_to_oid(&resolved, repo, spec) < 0) |
37 | return GIT_ERROR; | |
ac250c56 | 38 | |
ec6a632a | 39 | return git_object_lookup(out, repo, &resolved, GIT_OBJ_ANY); |
ac250c56 BS |
40 | } |
41 | ||
7e79d389 BS |
42 | /* Returns non-zero if yes */ |
43 | static int spec_looks_like_describe_output(const char *spec) | |
44 | { | |
45 | regex_t regex; | |
46 | int regex_error, retcode; | |
47 | ||
48 | regex_error = regcomp(®ex, ".+-[0-9]+-g[0-9a-fA-F]+", REG_EXTENDED); | |
49 | if (regex_error != 0) { | |
50 | giterr_set_regex(®ex, regex_error); | |
51 | return 1; /* To be safe */ | |
52 | } | |
53 | retcode = regexec(®ex, spec, 0, NULL, 0); | |
54 | regfree(®ex); | |
55 | return retcode == 0; | |
56 | } | |
57 | ||
ac250c56 BS |
58 | static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec) |
59 | { | |
60 | size_t speclen = strlen(spec); | |
61 | git_object *obj = NULL; | |
62 | git_oid oid; | |
63 | git_buf refnamebuf = GIT_BUF_INIT; | |
64 | static const char* formatters[] = { | |
65 | "refs/%s", | |
66 | "refs/tags/%s", | |
67 | "refs/heads/%s", | |
68 | "refs/remotes/%s", | |
69 | "refs/remotes/%s/HEAD", | |
70 | NULL | |
71 | }; | |
72 | unsigned int i; | |
73 | const char *substr; | |
74 | ||
75 | /* "git describe" output; snip everything before/including "-g" */ | |
76 | substr = strstr(spec, "-g"); | |
46c2ead0 | 77 | if (substr && |
7e79d389 | 78 | spec_looks_like_describe_output(spec) && |
46c2ead0 BS |
79 | !revparse_lookup_object(out, repo, substr+2)) { |
80 | return 0; | |
ac250c56 BS |
81 | } |
82 | ||
83 | /* SHA or prefix */ | |
84 | if (!git_oid_fromstrn(&oid, spec, speclen)) { | |
85 | if (!git_object_lookup_prefix(&obj, repo, &oid, speclen, GIT_OBJ_ANY)) { | |
86 | *out = obj; | |
87 | return 0; | |
88 | } | |
89 | } | |
90 | ||
91 | /* Fully-named ref */ | |
92 | if (!revparse_lookup_fully_qualifed_ref(&obj, repo, spec)) { | |
93 | *out = obj; | |
94 | return 0; | |
95 | } | |
96 | ||
97 | /* Partially-named ref; match in this order: */ | |
98 | for (i=0; formatters[i]; i++) { | |
99 | git_buf_clear(&refnamebuf); | |
100 | if (git_buf_printf(&refnamebuf, formatters[i], spec) < 0) { | |
101 | return GIT_ERROR; | |
102 | } | |
103 | ||
104 | if (!revparse_lookup_fully_qualifed_ref(&obj, repo, git_buf_cstr(&refnamebuf))) { | |
105 | git_buf_free(&refnamebuf); | |
106 | *out = obj; | |
107 | return 0; | |
108 | } | |
109 | } | |
110 | git_buf_free(&refnamebuf); | |
111 | ||
112 | giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec); | |
113 | return GIT_ERROR; | |
114 | } | |
115 | ||
116 | ||
a346992f BS |
117 | static int all_chars_are_digits(const char *str, size_t len) |
118 | { | |
119 | size_t i=0; | |
120 | for (i=0; i<len; i++) { | |
121 | if (str[i] < '0' || str[i] > '9') return 0; | |
122 | } | |
123 | return 1; | |
124 | } | |
125 | ||
126 | static void normalize_maybe_empty_refname(git_buf *buf, git_repository *repo, const char *refspec, size_t refspeclen) | |
127 | { | |
128 | git_reference *ref; | |
129 | ||
130 | if (!refspeclen) { | |
131 | /* Empty refspec means current branch (target of HEAD) */ | |
132 | git_reference_lookup(&ref, repo, "HEAD"); | |
133 | git_buf_puts(buf, git_reference_target(ref)); | |
134 | git_reference_free(ref); | |
135 | } else if (strstr(refspec, "HEAD")) { | |
136 | /* Explicit head */ | |
137 | git_buf_puts(buf, refspec); | |
138 | }else { | |
139 | if (git__prefixcmp(refspec, "refs/heads/") != 0) { | |
140 | git_buf_printf(buf, "refs/heads/%s", refspec); | |
141 | } else { | |
142 | git_buf_puts(buf, refspec); | |
143 | } | |
144 | } | |
145 | } | |
146 | ||
a51bdbcf | 147 | static int walk_ref_history(git_object **out, git_repository *repo, const char *refspec, const char *reflogspec) |
ac250c56 | 148 | { |
a51bdbcf BS |
149 | git_reference *ref; |
150 | git_reflog *reflog = NULL; | |
151 | int n, retcode = GIT_ERROR; | |
2b35c45f | 152 | int i, refloglen; |
a51bdbcf BS |
153 | const git_reflog_entry *entry; |
154 | git_buf buf = GIT_BUF_INIT; | |
e88b8bd5 | 155 | size_t refspeclen = strlen(refspec); |
a346992f | 156 | size_t reflogspeclen = strlen(reflogspec); |
a51bdbcf BS |
157 | |
158 | if (git__prefixcmp(reflogspec, "@{") != 0 || | |
159 | git__suffixcmp(reflogspec, "}") != 0) { | |
160 | giterr_set(GITERR_INVALID, "Bad reflogspec '%s'", reflogspec); | |
161 | return GIT_ERROR; | |
162 | } | |
023c6f69 | 163 | |
a51bdbcf | 164 | /* "@{-N}" form means walk back N checkouts. That means the HEAD log. */ |
e88b8bd5 | 165 | if (refspeclen == 0 && !git__prefixcmp(reflogspec, "@{-")) { |
2b35c45f | 166 | regex_t regex; |
c8a33547 | 167 | int regex_error; |
2b35c45f | 168 | |
a51bdbcf BS |
169 | if (git__strtol32(&n, reflogspec+3, NULL, 0) < 0 || |
170 | n < 1) { | |
171 | giterr_set(GITERR_INVALID, "Invalid reflogspec %s", reflogspec); | |
172 | return GIT_ERROR; | |
173 | } | |
023c6f69 | 174 | |
94952ded BS |
175 | if (!git_reference_lookup(&ref, repo, "HEAD")) { |
176 | if (!git_reflog_read(&reflog, ref)) { | |
177 | regex_error = regcomp(®ex, "checkout: moving from (.*) to .*", REG_EXTENDED); | |
178 | if (regex_error != 0) { | |
179 | giterr_set_regex(®ex, regex_error); | |
180 | } else { | |
181 | regmatch_t regexmatches[2]; | |
182 | ||
183 | refloglen = git_reflog_entrycount(reflog); | |
184 | for (i=refloglen-1; i >= 0; i--) { | |
185 | const char *msg; | |
186 | entry = git_reflog_entry_byindex(reflog, i); | |
187 | ||
188 | msg = git_reflog_entry_msg(entry); | |
189 | if (!regexec(®ex, msg, 2, regexmatches, 0)) { | |
190 | n--; | |
191 | if (!n) { | |
192 | git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so); | |
193 | retcode = revparse_lookup_object(out, repo, git_buf_cstr(&buf)); | |
194 | break; | |
195 | } | |
196 | } | |
2b35c45f | 197 | } |
94952ded | 198 | regfree(®ex); |
a51bdbcf BS |
199 | } |
200 | } | |
94952ded | 201 | git_reference_free(ref); |
a51bdbcf BS |
202 | } |
203 | } else { | |
1ce4cc01 | 204 | int date_error = 0; |
dd9e4abc | 205 | git_time_t timestamp; |
a346992f | 206 | git_buf datebuf = GIT_BUF_INIT; |
1ce4cc01 | 207 | |
a346992f | 208 | git_buf_put(&datebuf, reflogspec+2, reflogspeclen-3); |
dd9e4abc | 209 | date_error = git__date_parse(×tamp, git_buf_cstr(&datebuf)); |
a51bdbcf BS |
210 | |
211 | /* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */ | |
212 | if (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}")) { | |
a6346302 BS |
213 | git_config *cfg; |
214 | if (!git_repository_config(&cfg, repo)) { | |
215 | /* Is the ref a tracking branch? */ | |
216 | const char *remote; | |
217 | git_buf_clear(&buf); | |
218 | git_buf_printf(&buf, "branch.%s.remote", refspec); | |
219 | if (!git_config_get_string(cfg, git_buf_cstr(&buf), &remote)) { | |
220 | /* Yes. Find the first merge target name. */ | |
221 | const char *mergetarget; | |
222 | git_buf_clear(&buf); | |
223 | git_buf_printf(&buf, "branch.%s.merge", refspec); | |
224 | if (!git_config_get_string(cfg, git_buf_cstr(&buf), &mergetarget) && | |
225 | !git__prefixcmp(mergetarget, "refs/heads/")) { | |
226 | /* Success. Look up the target and fetch the object. */ | |
227 | git_buf_clear(&buf); | |
228 | git_buf_printf(&buf, "refs/remotes/%s/%s", remote, mergetarget+11); | |
229 | retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf)); | |
230 | } | |
231 | } | |
232 | git_config_free(cfg); | |
233 | } | |
a51bdbcf BS |
234 | } |
235 | ||
236 | /* @{N} -> Nth prior value for the ref (from reflog) */ | |
a346992f BS |
237 | else if (all_chars_are_digits(reflogspec+2, reflogspeclen-3) && |
238 | !git__strtol32(&n, reflogspec+2, NULL, 0) && | |
239 | n <= 100000000) { /* Allow integer time */ | |
240 | normalize_maybe_empty_refname(&buf, repo, refspec, refspeclen); | |
241 | ||
a51bdbcf BS |
242 | if (n == 0) { |
243 | retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf)); | |
244 | } else if (!git_reference_lookup(&ref, repo, git_buf_cstr(&buf))) { | |
245 | if (!git_reflog_read(&reflog, ref)) { | |
a346992f BS |
246 | int numentries = git_reflog_entrycount(reflog); |
247 | if (numentries < n) { | |
248 | giterr_set(GITERR_REFERENCE, "Reflog for '%s' has only %d entries, asked for %d", | |
249 | git_buf_cstr(&buf), numentries, n); | |
250 | retcode = GIT_ERROR; | |
251 | } else { | |
252 | const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, n); | |
253 | const git_oid *oid = git_reflog_entry_oidold(entry); | |
254 | retcode = git_object_lookup(out, repo, oid, GIT_OBJ_ANY); | |
255 | } | |
a51bdbcf BS |
256 | } |
257 | git_reference_free(ref); | |
258 | } | |
259 | } | |
260 | ||
a346992f BS |
261 | else if (!date_error) { |
262 | /* Ref as it was on a certain date */ | |
263 | normalize_maybe_empty_refname(&buf, repo, refspec, refspeclen); | |
264 | ||
265 | if (!git_reference_lookup(&ref, repo, git_buf_cstr(&buf))) { | |
266 | git_reflog *reflog; | |
267 | if (!git_reflog_read(&reflog, ref)) { | |
268 | /* Keep walking until we find an entry older than the given date */ | |
269 | int numentries = git_reflog_entrycount(reflog); | |
270 | int i; | |
271 | ||
272 | /* TODO: clunky. Factor "now" into a utility */ | |
273 | git_signature *sig; | |
1ce4cc01 BS |
274 | git_time as_of; |
275 | ||
a346992f | 276 | git_signature_now(&sig, "blah", "blah"); |
1ce4cc01 | 277 | as_of = sig->when; |
a346992f BS |
278 | git_signature_free(sig); |
279 | ||
280 | as_of.time = (timestamp > 0) | |
281 | ? timestamp | |
282 | : sig->when.time + timestamp; | |
283 | ||
284 | for (i=numentries-1; i>0; i--) { | |
285 | const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, i); | |
286 | git_time commit_time = git_reflog_entry_committer(entry)->when; | |
287 | if (git__time_cmp(&commit_time, &as_of) <= 0 ) { | |
288 | retcode = git_object_lookup(out, repo, git_reflog_entry_oidnew(entry), GIT_OBJ_ANY); | |
289 | break; | |
290 | } | |
291 | } | |
292 | ||
293 | if (!i) { | |
294 | /* Didn't find a match. Use the oldest revision in the reflog. */ | |
295 | const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, 0); | |
296 | retcode = git_object_lookup(out, repo, git_reflog_entry_oidnew(entry), GIT_OBJ_ANY); | |
297 | } | |
b41384b4 BS |
298 | |
299 | git_reflog_free(reflog); | |
a346992f BS |
300 | } |
301 | ||
302 | git_reference_free(ref); | |
303 | } | |
a51bdbcf | 304 | } |
a346992f BS |
305 | |
306 | git_buf_free(&datebuf); | |
a51bdbcf BS |
307 | } |
308 | ||
309 | if (reflog) git_reflog_free(reflog); | |
310 | git_buf_free(&buf); | |
311 | return retcode; | |
023c6f69 BS |
312 | } |
313 | ||
314 | static git_object* dereference_object(git_object *obj) | |
315 | { | |
316 | git_otype type = git_object_type(obj); | |
023c6f69 BS |
317 | |
318 | switch (type) { | |
319 | case GIT_OBJ_COMMIT: | |
27ee8483 BS |
320 | { |
321 | git_tree *tree = NULL; | |
322 | if (0 == git_commit_tree(&tree, (git_commit*)obj)) { | |
323 | return (git_object*)tree; | |
324 | } | |
387d01b8 | 325 | } |
023c6f69 BS |
326 | break; |
327 | case GIT_OBJ_TAG: | |
27ee8483 BS |
328 | { |
329 | git_object *newobj = NULL; | |
330 | if (0 == git_tag_target(&newobj, (git_tag*)obj)) { | |
331 | return newobj; | |
332 | } | |
9d7bdf71 | 333 | } |
023c6f69 | 334 | break; |
f597ea89 BS |
335 | |
336 | default: | |
387d01b8 BS |
337 | case GIT_OBJ_TREE: |
338 | case GIT_OBJ_BLOB: | |
339 | case GIT_OBJ_OFS_DELTA: | |
340 | case GIT_OBJ_REF_DELTA: | |
f597ea89 BS |
341 | break; |
342 | } | |
343 | ||
344 | /* Can't dereference some types */ | |
345 | return NULL; | |
346 | } | |
023c6f69 BS |
347 | |
348 | static int dereference_to_type(git_object **out, git_object *obj, git_otype target_type) | |
349 | { | |
65bc26d5 | 350 | int retcode = 1; |
9d7bdf71 | 351 | git_object *obj1 = obj, *obj2 = obj; |
023c6f69 | 352 | |
65bc26d5 | 353 | while (retcode > 0) { |
9d7bdf71 BS |
354 | git_otype this_type = git_object_type(obj1); |
355 | ||
023c6f69 | 356 | if (this_type == target_type) { |
387d01b8 | 357 | *out = obj1; |
65bc26d5 BS |
358 | retcode = 0; |
359 | } else { | |
360 | /* Dereference once, if possible. */ | |
361 | obj2 = dereference_object(obj1); | |
362 | if (!obj2) { | |
363 | giterr_set(GITERR_REFERENCE, "Can't dereference to type"); | |
364 | retcode = GIT_ERROR; | |
365 | } | |
7149a625 | 366 | } |
bae780e0 | 367 | if (obj1 != obj && obj1 != obj2) { |
387d01b8 | 368 | git_object_free(obj1); |
9d7bdf71 BS |
369 | } |
370 | obj1 = obj2; | |
023c6f69 | 371 | } |
65bc26d5 | 372 | return retcode; |
ac250c56 BS |
373 | } |
374 | ||
9d7bdf71 BS |
375 | static git_otype parse_obj_type(const char *str) |
376 | { | |
377 | if (!strcmp(str, "{commit}")) return GIT_OBJ_COMMIT; | |
378 | if (!strcmp(str, "{tree}")) return GIT_OBJ_TREE; | |
379 | if (!strcmp(str, "{blob}")) return GIT_OBJ_BLOB; | |
380 | if (!strcmp(str, "{tag}")) return GIT_OBJ_TAG; | |
381 | return GIT_OBJ_BAD; | |
382 | } | |
383 | ||
886f183a | 384 | static int handle_caret_syntax(git_object **out, git_repository *repo, git_object *obj, const char *movement) |
023c6f69 | 385 | { |
f597ea89 | 386 | git_commit *commit; |
e88b8bd5 | 387 | size_t movementlen = strlen(movement); |
f597ea89 | 388 | int n; |
023c6f69 BS |
389 | |
390 | if (*movement == '{') { | |
e88b8bd5 | 391 | if (movement[movementlen-1] != '}') { |
023c6f69 BS |
392 | set_invalid_syntax_err(movement); |
393 | return GIT_ERROR; | |
394 | } | |
9d7bdf71 BS |
395 | |
396 | /* {} -> Dereference until we reach an object that isn't a tag. */ | |
e88b8bd5 | 397 | if (movementlen == 2) { |
9d7bdf71 BS |
398 | git_object *newobj = obj; |
399 | git_object *newobj2 = newobj; | |
400 | while (git_object_type(newobj2) == GIT_OBJ_TAG) { | |
401 | newobj2 = dereference_object(newobj); | |
402 | if (newobj != obj) git_object_free(newobj); | |
403 | if (!newobj2) { | |
404 | giterr_set(GITERR_REFERENCE, "Couldn't find object of target type."); | |
405 | return GIT_ERROR; | |
406 | } | |
387d01b8 | 407 | newobj = newobj2; |
9d7bdf71 BS |
408 | } |
409 | *out = newobj2; | |
410 | return 0; | |
411 | } | |
023c6f69 | 412 | |
023c6f69 | 413 | /* {/...} -> Walk all commits until we see a commit msg that matches the phrase. */ |
9d7bdf71 | 414 | if (movement[1] == '/') { |
886f183a BS |
415 | int retcode = GIT_ERROR; |
416 | git_revwalk *walk; | |
417 | if (!git_revwalk_new(&walk, repo)) { | |
418 | git_oid oid; | |
419 | regex_t preg; | |
c8a33547 | 420 | int reg_error; |
886f183a BS |
421 | git_buf buf = GIT_BUF_INIT; |
422 | ||
423 | git_revwalk_sorting(walk, GIT_SORT_TIME); | |
424 | git_revwalk_push(walk, git_object_id(obj)); | |
425 | ||
426 | /* Extract the regex from the movement string */ | |
427 | git_buf_put(&buf, movement+2, strlen(movement)-3); | |
428 | ||
c8a33547 BS |
429 | reg_error = regcomp(&preg, git_buf_cstr(&buf), REG_EXTENDED); |
430 | if (reg_error != 0) { | |
431 | giterr_set_regex(&preg, reg_error); | |
432 | } else { | |
886f183a BS |
433 | while(!git_revwalk_next(&oid, walk)) { |
434 | git_object *walkobj; | |
435 | char str[41]; | |
436 | git_oid_fmt(str, &oid); | |
437 | str[40] = 0; | |
438 | ||
439 | /* Fetch the commit object, and check for matches in the message */ | |
440 | if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) { | |
441 | if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) { | |
442 | /* Found it! */ | |
443 | retcode = 0; | |
444 | *out = walkobj; | |
445 | if (obj == walkobj) { | |
446 | /* Avoid leaking an object */ | |
447 | git_object_free(walkobj); | |
448 | } | |
449 | break; | |
450 | } | |
451 | git_object_free(walkobj); | |
452 | } | |
453 | } | |
c8a33547 BS |
454 | if (retcode < 0) { |
455 | giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", movement); | |
456 | } | |
886f183a BS |
457 | regfree(&preg); |
458 | } | |
459 | ||
460 | git_buf_free(&buf); | |
461 | git_revwalk_free(walk); | |
462 | } | |
463 | return retcode; | |
9d7bdf71 BS |
464 | } |
465 | ||
023c6f69 | 466 | /* {...} -> Dereference until we reach an object of a certain type. */ |
9d7bdf71 | 467 | if (dereference_to_type(out, obj, parse_obj_type(movement)) < 0) { |
9d7bdf71 BS |
468 | return GIT_ERROR; |
469 | } | |
470 | return 0; | |
023c6f69 BS |
471 | } |
472 | ||
473 | /* Dereference until we reach a commit. */ | |
9d7bdf71 | 474 | if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { |
f597ea89 BS |
475 | /* Can't dereference to a commit; fail */ |
476 | return GIT_ERROR; | |
023c6f69 BS |
477 | } |
478 | ||
f597ea89 | 479 | /* "^" is the same as "^1" */ |
e88b8bd5 | 480 | if (movementlen == 0) { |
f597ea89 BS |
481 | n = 1; |
482 | } else { | |
483 | git__strtol32(&n, movement, NULL, 0); | |
484 | } | |
485 | commit = (git_commit*)obj; | |
486 | ||
487 | /* "^0" just returns the input */ | |
488 | if (n == 0) { | |
9d7bdf71 | 489 | *out = obj; |
f597ea89 BS |
490 | return 0; |
491 | } | |
023c6f69 | 492 | |
f597ea89 BS |
493 | if (git_commit_parent(&commit, commit, n-1) < 0) { |
494 | return GIT_ERROR; | |
495 | } | |
496 | ||
497 | *out = (git_object*)commit; | |
023c6f69 BS |
498 | return 0; |
499 | } | |
ac250c56 | 500 | |
38533d5a BS |
501 | static int handle_linear_syntax(git_object **out, git_object *obj, const char *movement) |
502 | { | |
503 | git_commit *commit1, *commit2; | |
504 | int i, n; | |
505 | ||
506 | /* Dereference until we reach a commit. */ | |
507 | if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { | |
508 | /* Can't dereference to a commit; fail */ | |
509 | return GIT_ERROR; | |
510 | } | |
511 | ||
512 | /* "~" is the same as "~1" */ | |
513 | if (strlen(movement) == 0) { | |
514 | n = 1; | |
515 | } else { | |
516 | git__strtol32(&n, movement, NULL, 0); | |
517 | } | |
518 | commit1 = (git_commit*)obj; | |
519 | ||
520 | /* "~0" just returns the input */ | |
521 | if (n == 0) { | |
522 | *out = obj; | |
523 | return 0; | |
524 | } | |
525 | ||
526 | for (i=0; i<n; i++) { | |
527 | if (git_commit_parent(&commit2, commit1, 0) < 0) { | |
528 | return GIT_ERROR; | |
529 | } | |
530 | if (commit1 != (git_commit*)obj) { | |
531 | git_commit_free(commit1); | |
532 | } | |
533 | commit1 = commit2; | |
534 | } | |
535 | ||
536 | *out = (git_object*)commit1; | |
537 | return 0; | |
538 | } | |
539 | ||
244d2f6b BS |
540 | static const git_tree_entry* git_tree_entry_bypath(git_tree *tree, git_repository *repo, const char *path) |
541 | { | |
542 | char *str = git__strdup(path); | |
543 | char *tok; | |
b183a92f | 544 | void *alloc = str; |
244d2f6b BS |
545 | git_tree *tree2 = tree; |
546 | const git_tree_entry *entry; | |
547 | ||
548 | while ((tok = git__strtok(&str, "/\\")) != NULL) { | |
549 | entry = git_tree_entry_byname(tree2, tok); | |
550 | if (tree2 != tree) git_tree_free(tree2); | |
551 | if (entry_is_tree(entry)) { | |
552 | if (git_tree_lookup(&tree2, repo, &entry->oid) < 0) { | |
b183a92f | 553 | free(alloc); |
244d2f6b BS |
554 | return NULL; |
555 | } | |
556 | } | |
557 | } | |
558 | ||
b183a92f | 559 | free(alloc); |
244d2f6b BS |
560 | return entry; |
561 | } | |
562 | ||
563 | static int handle_colon_syntax(git_object **out, | |
564 | git_repository *repo, | |
565 | git_object *obj, | |
566 | const char *path) | |
567 | { | |
568 | git_tree *tree; | |
569 | const git_tree_entry *entry; | |
570 | ||
571 | /* Dereference until we reach a tree. */ | |
572 | if (dereference_to_type(&obj, obj, GIT_OBJ_TREE) < 0) { | |
573 | return GIT_ERROR; | |
574 | } | |
575 | tree = (git_tree*)obj; | |
576 | ||
577 | /* Find the blob at the given path. */ | |
578 | entry = git_tree_entry_bypath(tree, repo, path); | |
b183a92f | 579 | git_tree_free(tree); |
244d2f6b BS |
580 | return git_tree_entry_2object(out, repo, entry); |
581 | } | |
582 | ||
ac250c56 BS |
583 | int git_revparse_single(git_object **out, git_repository *repo, const char *spec) |
584 | { | |
27ee8483 | 585 | revparse_state current_state = REVPARSE_STATE_INIT, next_state = REVPARSE_STATE_INIT; |
ac250c56 | 586 | const char *spec_cur = spec; |
27ee8483 BS |
587 | git_object *cur_obj = NULL, *next_obj = NULL; |
588 | git_buf specbuffer = GIT_BUF_INIT, stepbuffer = GIT_BUF_INIT; | |
023c6f69 | 589 | int retcode = 0; |
ac250c56 BS |
590 | |
591 | assert(out && repo && spec); | |
592 | ||
244d2f6b BS |
593 | if (spec[0] == ':') { |
594 | /* Either a global grep (":/foo") or a merge-stage path lookup (":2:Makefile"). | |
595 | Neither of these are handled just yet. */ | |
596 | giterr_set(GITERR_INVALID, "Unimplemented"); | |
597 | return GIT_ERROR; | |
598 | } | |
599 | ||
e88b8bd5 | 600 | while (current_state != REVPARSE_STATE_DONE) { |
ac250c56 BS |
601 | switch (current_state) { |
602 | case REVPARSE_STATE_INIT: | |
603 | if (!*spec_cur) { | |
604 | /* No operators, just a name. Find it and return. */ | |
27ee8483 BS |
605 | retcode = revparse_lookup_object(out, repo, spec); |
606 | next_state = REVPARSE_STATE_DONE; | |
ac250c56 | 607 | } else if (*spec_cur == '@') { |
f597ea89 | 608 | /* '@' syntax doesn't allow chaining */ |
023c6f69 | 609 | git_buf_puts(&stepbuffer, spec_cur); |
a51bdbcf | 610 | retcode = walk_ref_history(out, repo, git_buf_cstr(&specbuffer), git_buf_cstr(&stepbuffer)); |
e0887d81 | 611 | next_state = REVPARSE_STATE_DONE; |
023c6f69 BS |
612 | } else if (*spec_cur == '^') { |
613 | next_state = REVPARSE_STATE_CARET; | |
614 | } else if (*spec_cur == '~') { | |
615 | next_state = REVPARSE_STATE_LINEAR; | |
244d2f6b BS |
616 | } else if (*spec_cur == ':') { |
617 | next_state = REVPARSE_STATE_COLON; | |
023c6f69 BS |
618 | } else { |
619 | git_buf_putc(&specbuffer, *spec_cur); | |
ac250c56 BS |
620 | } |
621 | spec_cur++; | |
622 | ||
27ee8483 | 623 | if (current_state != next_state && next_state != REVPARSE_STATE_DONE) { |
023c6f69 | 624 | /* Leaving INIT state, find the object specified, in case that state needs it */ |
72b86bae BS |
625 | if (revparse_lookup_object(&next_obj, repo, git_buf_cstr(&specbuffer)) < 0) { |
626 | retcode = GIT_ERROR; | |
627 | next_state = REVPARSE_STATE_DONE; | |
628 | } | |
ac250c56 BS |
629 | } |
630 | break; | |
631 | ||
023c6f69 BS |
632 | |
633 | case REVPARSE_STATE_CARET: | |
634 | /* Gather characters until NULL, '~', or '^' */ | |
635 | if (!*spec_cur) { | |
886f183a | 636 | retcode = handle_caret_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); |
e0887d81 | 637 | next_state = REVPARSE_STATE_DONE; |
023c6f69 | 638 | } else if (*spec_cur == '~') { |
886f183a | 639 | retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); |
f597ea89 | 640 | git_buf_clear(&stepbuffer); |
e0887d81 | 641 | next_state = !retcode ? REVPARSE_STATE_LINEAR : REVPARSE_STATE_DONE; |
023c6f69 | 642 | } else if (*spec_cur == '^') { |
886f183a | 643 | retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); |
f597ea89 | 644 | git_buf_clear(&stepbuffer); |
e0887d81 BS |
645 | if (retcode < 0) { |
646 | next_state = REVPARSE_STATE_DONE; | |
647 | } | |
023c6f69 BS |
648 | } else { |
649 | git_buf_putc(&stepbuffer, *spec_cur); | |
ac250c56 BS |
650 | } |
651 | spec_cur++; | |
ac250c56 BS |
652 | break; |
653 | ||
023c6f69 | 654 | case REVPARSE_STATE_LINEAR: |
9d7bdf71 | 655 | if (!*spec_cur) { |
38533d5a BS |
656 | retcode = handle_linear_syntax(out, cur_obj, git_buf_cstr(&stepbuffer)); |
657 | next_state = REVPARSE_STATE_DONE; | |
9d7bdf71 | 658 | } else if (*spec_cur == '~') { |
38533d5a BS |
659 | retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer)); |
660 | git_buf_clear(&stepbuffer); | |
661 | if (retcode < 0) { | |
662 | next_state = REVPARSE_STATE_DONE; | |
663 | } | |
9d7bdf71 | 664 | } else if (*spec_cur == '^') { |
38533d5a BS |
665 | retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer)); |
666 | git_buf_clear(&stepbuffer); | |
667 | next_state = !retcode ? REVPARSE_STATE_CARET : REVPARSE_STATE_DONE; | |
9d7bdf71 BS |
668 | } else { |
669 | git_buf_putc(&stepbuffer, *spec_cur); | |
670 | } | |
671 | spec_cur++; | |
ac250c56 | 672 | break; |
e0887d81 | 673 | |
244d2f6b BS |
674 | case REVPARSE_STATE_COLON: |
675 | if (*spec_cur) { | |
676 | git_buf_putc(&stepbuffer, *spec_cur); | |
677 | } else { | |
678 | retcode = handle_colon_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); | |
679 | next_state = REVPARSE_STATE_DONE; | |
680 | } | |
681 | spec_cur++; | |
682 | break; | |
683 | ||
e0887d81 | 684 | case REVPARSE_STATE_DONE: |
27ee8483 BS |
685 | if (cur_obj && *out != cur_obj) git_object_free(cur_obj); |
686 | if (next_obj && *out != next_obj) git_object_free(next_obj); | |
e0887d81 | 687 | break; |
ac250c56 BS |
688 | } |
689 | ||
690 | current_state = next_state; | |
f597ea89 | 691 | if (cur_obj != next_obj) { |
27ee8483 | 692 | if (cur_obj) git_object_free(cur_obj); |
f597ea89 BS |
693 | cur_obj = next_obj; |
694 | } | |
ac250c56 | 695 | } |
023c6f69 | 696 | |
65bc26d5 BS |
697 | if (*out != cur_obj) git_object_free(cur_obj); |
698 | if (*out != next_obj && next_obj != cur_obj) git_object_free(next_obj); | |
27ee8483 | 699 | |
023c6f69 BS |
700 | git_buf_free(&specbuffer); |
701 | git_buf_free(&stepbuffer); | |
702 | return retcode; | |
ac250c56 BS |
703 | } |
704 | ||
705 | ||
706 | GIT_END_DECL |