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