]> git.proxmox.com Git - libgit2.git/blob - src/revparse.c
Fix one error not reported in revparse
[libgit2.git] / src / revparse.c
1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
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 #include "refdb.h"
14
15 #include "git2.h"
16
17 static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname)
18 {
19 int error = 0, i;
20 bool fallbackmode = true, foundvalid = false;
21 git_reference *ref;
22 git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;
23
24 static const char* formatters[] = {
25 "%s",
26 GIT_REFS_DIR "%s",
27 GIT_REFS_TAGS_DIR "%s",
28 GIT_REFS_HEADS_DIR "%s",
29 GIT_REFS_REMOTES_DIR "%s",
30 GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,
31 NULL
32 };
33
34 if (*refname)
35 git_buf_puts(&name, refname);
36 else {
37 git_buf_puts(&name, GIT_HEAD_FILE);
38 fallbackmode = false;
39 }
40
41 for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {
42
43 git_buf_clear(&refnamebuf);
44
45 if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)
46 goto cleanup;
47
48 if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {
49 error = GIT_EINVALIDSPEC;
50 continue;
51 }
52 foundvalid = true;
53
54 error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);
55
56 if (!error) {
57 *out = ref;
58 error = 0;
59 goto cleanup;
60 }
61
62 if (error != GIT_ENOTFOUND)
63 goto cleanup;
64 }
65
66 cleanup:
67 if (error && !foundvalid) {
68 /* never found a valid reference name */
69 giterr_set(GITERR_REFERENCE,
70 "Could not use '%s' as valid reference name", git_buf_cstr(&name));
71 }
72
73 git_buf_free(&name);
74 git_buf_free(&refnamebuf);
75 return error;
76 }
77
78 static int maybe_sha_or_abbrev(git_object** out, git_repository *repo, const char *spec, size_t speclen)
79 {
80 git_oid oid;
81
82 if (git_oid_fromstrn(&oid, spec, speclen) < 0)
83 return GIT_ENOTFOUND;
84
85 return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY);
86 }
87
88 static int maybe_sha(git_object** out, git_repository *repo, const char *spec)
89 {
90 size_t speclen = strlen(spec);
91
92 if (speclen != GIT_OID_HEXSZ)
93 return GIT_ENOTFOUND;
94
95 return maybe_sha_or_abbrev(out, repo, spec, speclen);
96 }
97
98 static int maybe_abbrev(git_object** out, git_repository *repo, const char *spec)
99 {
100 size_t speclen = strlen(spec);
101
102 return maybe_sha_or_abbrev(out, repo, spec, speclen);
103 }
104
105 static int build_regex(regex_t *regex, const char *pattern)
106 {
107 int error;
108
109 if (*pattern == '\0') {
110 giterr_set(GITERR_REGEX, "Empty pattern");
111 return GIT_EINVALIDSPEC;
112 }
113
114 error = regcomp(regex, pattern, REG_EXTENDED);
115 if (!error)
116 return 0;
117
118 error = giterr_set_regex(regex, error);
119
120 regfree(regex);
121
122 return error;
123 }
124
125 static int maybe_describe(git_object**out, git_repository *repo, const char *spec)
126 {
127 const char *substr;
128 int error;
129 regex_t regex;
130
131 substr = strstr(spec, "-g");
132
133 if (substr == NULL)
134 return GIT_ENOTFOUND;
135
136 if (build_regex(&regex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
137 return -1;
138
139 error = regexec(&regex, spec, 0, NULL, 0);
140 regfree(&regex);
141
142 if (error)
143 return GIT_ENOTFOUND;
144
145 return maybe_abbrev(out, repo, substr+2);
146 }
147
148 static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec)
149 {
150 int error;
151 git_reference *ref;
152
153 error = maybe_sha(out, repo, spec);
154 if (!error)
155 return 0;
156
157 if (error < 0 && error != GIT_ENOTFOUND)
158 return error;
159
160 error = disambiguate_refname(&ref, repo, spec);
161 if (!error) {
162 error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);
163 git_reference_free(ref);
164 return error;
165 }
166
167 if (error < 0 && error != GIT_ENOTFOUND)
168 return error;
169
170 error = maybe_abbrev(out, repo, spec);
171 if (!error)
172 return 0;
173
174 if (error < 0 && error != GIT_ENOTFOUND)
175 return error;
176
177 error = maybe_describe(out, repo, spec);
178 if (!error)
179 return 0;
180
181 if (error < 0 && error != GIT_ENOTFOUND)
182 return error;
183
184 giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec);
185 return GIT_ENOTFOUND;
186 }
187
188 static int try_parse_numeric(int *n, const char *curly_braces_content)
189 {
190 int32_t content;
191 const char *end_ptr;
192
193 if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0)
194 return -1;
195
196 if (*end_ptr != '\0')
197 return -1;
198
199 *n = (int)content;
200 return 0;
201 }
202
203 static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
204 {
205 git_reference *ref = NULL;
206 git_reflog *reflog = NULL;
207 regex_t preg;
208 int error = -1;
209 size_t i, numentries, cur;
210 const git_reflog_entry *entry;
211 const char *msg;
212 regmatch_t regexmatches[2];
213 git_buf buf = GIT_BUF_INIT;
214
215 cur = position;
216
217 if (*identifier != '\0' || *base_ref != NULL)
218 return GIT_EINVALIDSPEC;
219
220 if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0)
221 return -1;
222
223 if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)
224 goto cleanup;
225
226 if (git_reflog_read(&reflog, ref) < 0)
227 goto cleanup;
228
229 numentries = git_reflog_entrycount(reflog);
230
231 for (i = 0; i < numentries; i++) {
232 entry = git_reflog_entry_byindex(reflog, i);
233 msg = git_reflog_entry_message(entry);
234
235 if (regexec(&preg, msg, 2, regexmatches, 0))
236 continue;
237
238 cur--;
239
240 if (cur > 0)
241 continue;
242
243 git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so);
244
245 if ((error = disambiguate_refname(base_ref, repo, git_buf_cstr(&buf))) == 0)
246 goto cleanup;
247
248 if (error < 0 && error != GIT_ENOTFOUND)
249 goto cleanup;
250
251 error = maybe_abbrev(out, repo, git_buf_cstr(&buf));
252
253 goto cleanup;
254 }
255
256 error = GIT_ENOTFOUND;
257
258 cleanup:
259 git_reference_free(ref);
260 git_buf_free(&buf);
261 regfree(&preg);
262 git_reflog_free(reflog);
263 return error;
264 }
265
266 static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier)
267 {
268 git_reflog *reflog;
269 int error = -1;
270 size_t numentries;
271 const git_reflog_entry *entry;
272 bool search_by_pos = (identifier <= 100000000);
273
274 if (git_reflog_read(&reflog, ref) < 0)
275 return -1;
276
277 numentries = git_reflog_entrycount(reflog);
278
279 if (search_by_pos) {
280 if (numentries < identifier + 1) {
281 giterr_set(
282 GITERR_REFERENCE,
283 "Reflog for '%s' has only "PRIuZ" entries, asked for "PRIuZ,
284 git_reference_name(ref), numentries, identifier);
285
286 error = GIT_ENOTFOUND;
287 goto cleanup;
288 }
289
290 entry = git_reflog_entry_byindex(reflog, identifier);
291 git_oid_cpy(oid, git_reflog_entry_id_new(entry));
292 error = 0;
293 goto cleanup;
294
295 } else {
296 size_t i;
297 git_time commit_time;
298
299 for (i = 0; i < numentries; i++) {
300 entry = git_reflog_entry_byindex(reflog, i);
301 commit_time = git_reflog_entry_committer(entry)->when;
302
303 if (commit_time.time > (git_time_t)identifier)
304 continue;
305
306 git_oid_cpy(oid, git_reflog_entry_id_new(entry));
307 error = 0;
308 goto cleanup;
309 }
310
311 error = GIT_ENOTFOUND;
312 }
313
314 cleanup:
315 git_reflog_free(reflog);
316 return error;
317 }
318
319 static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)
320 {
321 git_reference *ref;
322 git_oid oid;
323 int error = -1;
324
325 if (*base_ref == NULL) {
326 if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
327 return error;
328 } else {
329 ref = *base_ref;
330 *base_ref = NULL;
331 }
332
333 if (position == 0) {
334 error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);
335 goto cleanup;
336 }
337
338 if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0)
339 goto cleanup;
340
341 error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY);
342
343 cleanup:
344 git_reference_free(ref);
345 return error;
346 }
347
348 static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo)
349 {
350 git_reference *tracking, *ref;
351 int error = -1;
352
353 if (*base_ref == NULL) {
354 if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)
355 return error;
356 } else {
357 ref = *base_ref;
358 *base_ref = NULL;
359 }
360
361 if (!git_reference_is_branch(ref)) {
362 error = GIT_EINVALIDSPEC;
363 goto cleanup;
364 }
365
366 if ((error = git_branch_upstream(&tracking, ref)) < 0)
367 goto cleanup;
368
369 *base_ref = tracking;
370
371 cleanup:
372 git_reference_free(ref);
373 return error;
374 }
375
376 static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository* repo, const char *curly_braces_content)
377 {
378 bool is_numeric;
379 int parsed = 0, error = -1;
380 git_buf identifier = GIT_BUF_INIT;
381 git_time_t timestamp;
382
383 assert(*out == NULL);
384
385 if (git_buf_put(&identifier, spec, identifier_len) < 0)
386 return -1;
387
388 is_numeric = !try_parse_numeric(&parsed, curly_braces_content);
389
390 if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) {
391 error = GIT_EINVALIDSPEC;
392 goto cleanup;
393 }
394
395 if (is_numeric) {
396 if (parsed < 0)
397 error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_buf_cstr(&identifier), -parsed);
398 else
399 error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed);
400
401 goto cleanup;
402 }
403
404 if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) {
405 error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo);
406
407 goto cleanup;
408 }
409
410 if (git__date_parse(&timestamp, curly_braces_content) < 0)
411 goto cleanup;
412
413 error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (size_t)timestamp);
414
415 cleanup:
416 git_buf_free(&identifier);
417 return error;
418 }
419
420 static git_otype parse_obj_type(const char *str)
421 {
422 if (!strcmp(str, "commit"))
423 return GIT_OBJ_COMMIT;
424
425 if (!strcmp(str, "tree"))
426 return GIT_OBJ_TREE;
427
428 if (!strcmp(str, "blob"))
429 return GIT_OBJ_BLOB;
430
431 if (!strcmp(str, "tag"))
432 return GIT_OBJ_TAG;
433
434 return GIT_OBJ_BAD;
435 }
436
437 static int dereference_to_non_tag(git_object **out, git_object *obj)
438 {
439 if (git_object_type(obj) == GIT_OBJ_TAG)
440 return git_tag_peel(out, (git_tag *)obj);
441
442 return git_object_dup(out, obj);
443 }
444
445 static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n)
446 {
447 git_object *temp_commit = NULL;
448 int error;
449
450 if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
451 return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
452 GIT_EINVALIDSPEC : error;
453
454 if (n == 0) {
455 *out = temp_commit;
456 return 0;
457 }
458
459 error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1);
460
461 git_object_free(temp_commit);
462 return error;
463 }
464
465 static int handle_linear_syntax(git_object **out, git_object *obj, int n)
466 {
467 git_object *temp_commit = NULL;
468 int error;
469
470 if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)
471 return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?
472 GIT_EINVALIDSPEC : error;
473
474 error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n);
475
476 git_object_free(temp_commit);
477 return error;
478 }
479
480 static int handle_colon_syntax(
481 git_object **out,
482 git_object *obj,
483 const char *path)
484 {
485 git_object *tree;
486 int error = -1;
487 git_tree_entry *entry = NULL;
488
489 if ((error = git_object_peel(&tree, obj, GIT_OBJ_TREE)) < 0)
490 return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error;
491
492 if (*path == '\0') {
493 *out = tree;
494 return 0;
495 }
496
497 /*
498 * TODO: Handle the relative path syntax
499 * (:./relative/path and :../relative/path)
500 */
501 if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0)
502 goto cleanup;
503
504 error = git_tree_entry_to_object(out, git_object_owner(tree), entry);
505
506 cleanup:
507 git_tree_entry_free(entry);
508 git_object_free(tree);
509
510 return error;
511 }
512
513 static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex)
514 {
515 int error;
516 git_oid oid;
517 git_object *obj;
518
519 while (!(error = git_revwalk_next(&oid, walk))) {
520
521 error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT);
522 if ((error < 0) && (error != GIT_ENOTFOUND))
523 return -1;
524
525 if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) {
526 *out = obj;
527 return 0;
528 }
529
530 git_object_free(obj);
531 }
532
533 if (error < 0 && error == GIT_ITEROVER)
534 error = GIT_ENOTFOUND;
535
536 return error;
537 }
538
539 static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern)
540 {
541 regex_t preg;
542 git_revwalk *walk = NULL;
543 int error;
544
545 if ((error = build_regex(&preg, pattern)) < 0)
546 return error;
547
548 if ((error = git_revwalk_new(&walk, repo)) < 0)
549 goto cleanup;
550
551 git_revwalk_sorting(walk, GIT_SORT_TIME);
552
553 if (spec_oid == NULL) {
554 // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails
555 if ((error = git_revwalk_push_glob(walk, GIT_REFS_HEADS_DIR "*")) < 0)
556 goto cleanup;
557 } else if ((error = git_revwalk_push(walk, spec_oid)) < 0)
558 goto cleanup;
559
560 error = walk_and_search(out, walk, &preg);
561
562 cleanup:
563 regfree(&preg);
564 git_revwalk_free(walk);
565
566 return error;
567 }
568
569 static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content)
570 {
571 git_otype expected_type;
572
573 if (*curly_braces_content == '\0')
574 return dereference_to_non_tag(out, obj);
575
576 if (*curly_braces_content == '/')
577 return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1);
578
579 expected_type = parse_obj_type(curly_braces_content);
580
581 if (expected_type == GIT_OBJ_BAD)
582 return GIT_EINVALIDSPEC;
583
584 return git_object_peel(out, obj, expected_type);
585 }
586
587 static int extract_curly_braces_content(git_buf *buf, const char *spec, size_t *pos)
588 {
589 git_buf_clear(buf);
590
591 assert(spec[*pos] == '^' || spec[*pos] == '@');
592
593 (*pos)++;
594
595 if (spec[*pos] == '\0' || spec[*pos] != '{')
596 return GIT_EINVALIDSPEC;
597
598 (*pos)++;
599
600 while (spec[*pos] != '}') {
601 if (spec[*pos] == '\0')
602 return GIT_EINVALIDSPEC;
603
604 git_buf_putc(buf, spec[(*pos)++]);
605 }
606
607 (*pos)++;
608
609 return 0;
610 }
611
612 static int extract_path(git_buf *buf, const char *spec, size_t *pos)
613 {
614 git_buf_clear(buf);
615
616 assert(spec[*pos] == ':');
617
618 (*pos)++;
619
620 if (git_buf_puts(buf, spec + *pos) < 0)
621 return -1;
622
623 *pos += git_buf_len(buf);
624
625 return 0;
626 }
627
628 static int extract_how_many(int *n, const char *spec, size_t *pos)
629 {
630 const char *end_ptr;
631 int parsed, accumulated;
632 char kind = spec[*pos];
633
634 assert(spec[*pos] == '^' || spec[*pos] == '~');
635
636 accumulated = 0;
637
638 do {
639 do {
640 (*pos)++;
641 accumulated++;
642 } while (spec[(*pos)] == kind && kind == '~');
643
644 if (git__isdigit(spec[*pos])) {
645 if (git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0)
646 return GIT_EINVALIDSPEC;
647
648 accumulated += (parsed - 1);
649 *pos = end_ptr - spec;
650 }
651
652 } while (spec[(*pos)] == kind && kind == '~');
653
654 *n = accumulated;
655
656 return 0;
657 }
658
659 static int object_from_reference(git_object **object, git_reference *reference)
660 {
661 git_reference *resolved = NULL;
662 int error;
663
664 if (git_reference_resolve(&resolved, reference) < 0)
665 return -1;
666
667 error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJ_ANY);
668 git_reference_free(resolved);
669
670 return error;
671 }
672
673 static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier)
674 {
675 int error;
676 git_buf identifier = GIT_BUF_INIT;
677
678 if (*object != NULL)
679 return 0;
680
681 if (*reference != NULL) {
682 if ((error = object_from_reference(object, *reference)) < 0)
683 return error;
684
685 git_reference_free(*reference);
686 *reference = NULL;
687 return 0;
688 }
689
690 if (!allow_empty_identifier && identifier_len == 0)
691 return GIT_EINVALIDSPEC;
692
693 if (git_buf_put(&identifier, spec, identifier_len) < 0)
694 return -1;
695
696 error = revparse_lookup_object(object, repo, git_buf_cstr(&identifier));
697 git_buf_free(&identifier);
698
699 return error;
700 }
701
702 static int ensure_base_rev_is_not_known_yet(git_object *object)
703 {
704 if (object == NULL)
705 return 0;
706
707 return GIT_EINVALIDSPEC;
708 }
709
710 static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len)
711 {
712 if (object != NULL)
713 return true;
714
715 if (reference != NULL)
716 return true;
717
718 if (identifier_len > 0)
719 return true;
720
721 return false;
722 }
723
724 static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference)
725 {
726 if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL)
727 return 0;
728
729 return GIT_EINVALIDSPEC;
730 }
731
732 int git_revparse_single(git_object **out, git_repository *repo, const char *spec)
733 {
734 size_t pos = 0, identifier_len = 0;
735 int error = -1, n;
736 git_buf buf = GIT_BUF_INIT;
737
738 git_reference *reference = NULL;
739 git_object *base_rev = NULL;
740
741 assert(out && repo && spec);
742
743 *out = NULL;
744
745 while (spec[pos]) {
746 switch (spec[pos]) {
747 case '^':
748 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
749 goto cleanup;
750
751 if (spec[pos+1] == '{') {
752 git_object *temp_object = NULL;
753
754 if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
755 goto cleanup;
756
757 if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
758 goto cleanup;
759
760 git_object_free(base_rev);
761 base_rev = temp_object;
762 } else {
763 git_object *temp_object = NULL;
764
765 if ((error = extract_how_many(&n, spec, &pos)) < 0)
766 goto cleanup;
767
768 if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0)
769 goto cleanup;
770
771 git_object_free(base_rev);
772 base_rev = temp_object;
773 }
774 break;
775
776 case '~':
777 {
778 git_object *temp_object = NULL;
779
780 if ((error = extract_how_many(&n, spec, &pos)) < 0)
781 goto cleanup;
782
783 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
784 goto cleanup;
785
786 if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0)
787 goto cleanup;
788
789 git_object_free(base_rev);
790 base_rev = temp_object;
791 break;
792 }
793
794 case ':':
795 {
796 git_object *temp_object = NULL;
797
798 if ((error = extract_path(&buf, spec, &pos)) < 0)
799 goto cleanup;
800
801 if (any_left_hand_identifier(base_rev, reference, identifier_len)) {
802 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0)
803 goto cleanup;
804
805 if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)
806 goto cleanup;
807 } else {
808 if (*git_buf_cstr(&buf) == '/') {
809 if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0)
810 goto cleanup;
811 } else {
812
813 /*
814 * TODO: support merge-stage path lookup (":2:Makefile")
815 * and plain index blob lookup (:i-am/a/blob)
816 */
817 giterr_set(GITERR_INVALID, "Unimplemented");
818 error = GIT_ERROR;
819 goto cleanup;
820 }
821 }
822
823 git_object_free(base_rev);
824 base_rev = temp_object;
825 break;
826 }
827
828 case '@':
829 {
830 if (spec[pos+1] == '{') {
831 git_object *temp_object = NULL;
832
833 if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)
834 goto cleanup;
835
836 if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0)
837 goto cleanup;
838
839 if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0)
840 goto cleanup;
841
842 if (temp_object != NULL)
843 base_rev = temp_object;
844 break;
845 } else {
846 /* Fall through */
847 }
848 }
849
850 default:
851 if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0)
852 goto cleanup;
853
854 pos++;
855 identifier_len++;
856 }
857 }
858
859 if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)
860 goto cleanup;
861
862 *out = base_rev;
863 error = 0;
864
865 cleanup:
866 if (error) {
867 if (error == GIT_EINVALIDSPEC)
868 giterr_set(GITERR_INVALID,
869 "Failed to parse revision specifier - Invalid pattern '%s'", spec);
870
871 git_object_free(base_rev);
872 }
873 git_reference_free(reference);
874 git_buf_free(&buf);
875 return error;
876 }
877
878
879 int git_revparse(
880 git_revspec *revspec,
881 git_repository *repo,
882 const char *spec)
883 {
884 const char *dotdot;
885 int error = 0;
886
887 assert(revspec && repo && spec);
888
889 memset(revspec, 0x0, sizeof(*revspec));
890
891 if ((dotdot = strstr(spec, "..")) != NULL) {
892 char *lstr;
893 const char *rstr;
894 revspec->flags = GIT_REVPARSE_RANGE;
895
896 lstr = git__substrdup(spec, dotdot - spec);
897 rstr = dotdot + 2;
898 if (dotdot[2] == '.') {
899 revspec->flags |= GIT_REVPARSE_MERGE_BASE;
900 rstr++;
901 }
902
903 if ((error = git_revparse_single(&revspec->from, repo, lstr)) < 0) {
904 return error;
905 }
906
907 if ((error = git_revparse_single(&revspec->to, repo, rstr)) < 0) {
908 return error;
909 }
910
911 git__free((void*)lstr);
912 } else {
913 revspec->flags = GIT_REVPARSE_SINGLE;
914 error = git_revparse_single(&revspec->from, repo, spec);
915 }
916
917 return error;
918 }
919