]> git.proxmox.com Git - libgit2.git/blame - src/revparse.c
Merge pull request #774 from benstraub/revparse-colon-segfault
[libgit2.git] / src / revparse.c
CommitLineData
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
16typedef 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
24static void set_invalid_syntax_err(const char *spec)
25{
26 giterr_set(GITERR_INVALID, "Refspec '%s' is not valid.", spec);
27}
28
ac250c56
BS
29static 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 */
40static int spec_looks_like_describe_output(const char *spec)
41{
42 regex_t regex;
43 int regex_error, retcode;
44
45 regex_error = regcomp(&regex, ".+-[0-9]+-g[0-9a-fA-F]+", REG_EXTENDED);
46 if (regex_error != 0) {
47 giterr_set_regex(&regex, regex_error);
48 return 1; /* To be safe */
49 }
50 retcode = regexec(&regex, spec, 0, NULL, 0);
51 regfree(&regex);
52 return retcode == 0;
53}
54
ac250c56
BS
55static 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
114static 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
123static 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 144static 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(&regex, "checkout: moving from (.*) to .*", REG_EXTENDED);
175 if (regex_error != 0) {
176 giterr_set_regex(&regex, 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(&regex, 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(&regex);
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(&timestamp, 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
311static 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
345static 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
372static 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 381static 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
495static 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 534static 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
563static 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 583static 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
631int 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}