File Coverage

deps/libgit2/src/libgit2/revparse.c
Criterion Covered Total %
statement 210 479 43.8
branch 89 310 28.7
condition n/a
subroutine n/a
pod n/a
total 299 789 37.9


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