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