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