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