2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
18 static int maybe_sha_or_abbrev(git_object
**out
, git_repository
*repo
, const char *spec
, size_t speclen
)
22 if (git_oid_fromstrn(&oid
, spec
, speclen
) < 0)
25 return git_object_lookup_prefix(out
, repo
, &oid
, speclen
, GIT_OBJECT_ANY
);
28 static int maybe_sha(git_object
**out
, git_repository
*repo
, const char *spec
)
30 size_t speclen
= strlen(spec
);
32 if (speclen
!= GIT_OID_HEXSZ
)
35 return maybe_sha_or_abbrev(out
, repo
, spec
, speclen
);
38 static int maybe_abbrev(git_object
**out
, git_repository
*repo
, const char *spec
)
40 size_t speclen
= strlen(spec
);
42 return maybe_sha_or_abbrev(out
, repo
, spec
, speclen
);
45 static int build_regex(git_regexp
*regex
, const char *pattern
)
49 if (*pattern
== '\0') {
50 git_error_set(GIT_ERROR_REGEX
, "empty pattern");
51 return GIT_EINVALIDSPEC
;
54 error
= git_regexp_compile(regex
, pattern
, 0);
58 git_regexp_dispose(regex
);
63 static int maybe_describe(git_object
**out
, git_repository
*repo
, const char *spec
)
69 substr
= strstr(spec
, "-g");
74 if (build_regex(®ex
, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
77 error
= git_regexp_match(®ex
, spec
);
78 git_regexp_dispose(®ex
);
83 return maybe_abbrev(out
, repo
, substr
+2);
86 static int revparse_lookup_object(
87 git_object
**object_out
,
88 git_reference
**reference_out
,
95 if ((error
= maybe_sha(object_out
, repo
, spec
)) != GIT_ENOTFOUND
)
98 error
= git_reference_dwim(&ref
, repo
, spec
);
101 error
= git_object_lookup(
102 object_out
, repo
, git_reference_target(ref
), GIT_OBJECT_ANY
);
105 *reference_out
= ref
;
110 if (error
!= GIT_ENOTFOUND
)
113 if ((strlen(spec
) < GIT_OID_HEXSZ
) &&
114 ((error
= maybe_abbrev(object_out
, repo
, spec
)) != GIT_ENOTFOUND
))
117 if ((error
= maybe_describe(object_out
, repo
, spec
)) != GIT_ENOTFOUND
)
120 git_error_set(GIT_ERROR_REFERENCE
, "revspec '%s' not found", spec
);
121 return GIT_ENOTFOUND
;
124 static int try_parse_numeric(int *n
, const char *curly_braces_content
)
129 if (git__strntol32(&content
, curly_braces_content
, strlen(curly_braces_content
),
133 if (*end_ptr
!= '\0')
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
)
142 git_reference
*ref
= NULL
;
143 git_reflog
*reflog
= NULL
;
146 size_t i
, numentries
, cur
;
147 const git_reflog_entry
*entry
;
149 git_str buf
= GIT_STR_INIT
;
153 if (*identifier
!= '\0' || *base_ref
!= NULL
)
154 return GIT_EINVALIDSPEC
;
156 if (build_regex(&preg
, "checkout: moving from (.*) to .*") < 0)
159 if (git_reference_lookup(&ref
, repo
, GIT_HEAD_FILE
) < 0)
162 if (git_reflog_read(&reflog
, repo
, GIT_HEAD_FILE
) < 0)
165 numentries
= git_reflog_entrycount(reflog
);
167 for (i
= 0; i
< numentries
; i
++) {
168 git_regmatch regexmatches
[2];
170 entry
= git_reflog_entry_byindex(reflog
, i
);
171 msg
= git_reflog_entry_message(entry
);
175 if (git_regexp_search(&preg
, msg
, 2, regexmatches
) < 0)
183 if ((git_str_put(&buf
, msg
+regexmatches
[1].start
, regexmatches
[1].end
- regexmatches
[1].start
)) < 0)
186 if ((error
= git_reference_dwim(base_ref
, repo
, git_str_cstr(&buf
))) == 0)
189 if (error
< 0 && error
!= GIT_ENOTFOUND
)
192 error
= maybe_abbrev(out
, repo
, git_str_cstr(&buf
));
197 error
= GIT_ENOTFOUND
;
200 git_reference_free(ref
);
201 git_str_dispose(&buf
);
202 git_regexp_dispose(&preg
);
203 git_reflog_free(reflog
);
207 static int retrieve_oid_from_reflog(git_oid
*oid
, git_reference
*ref
, size_t identifier
)
211 const git_reflog_entry
*entry
= NULL
;
212 bool search_by_pos
= (identifier
<= 100000000);
214 if (git_reflog_read(&reflog
, git_reference_owner(ref
), git_reference_name(ref
)) < 0)
217 numentries
= git_reflog_entrycount(reflog
);
220 if (numentries
< identifier
+ 1)
223 entry
= git_reflog_entry_byindex(reflog
, identifier
);
224 git_oid_cpy(oid
, git_reflog_entry_id_new(entry
));
227 git_time commit_time
;
229 for (i
= 0; i
< numentries
; i
++) {
230 entry
= git_reflog_entry_byindex(reflog
, i
);
231 commit_time
= git_reflog_entry_committer(entry
)->when
;
233 if (commit_time
.time
> (git_time_t
)identifier
)
236 git_oid_cpy(oid
, git_reflog_entry_id_new(entry
));
240 if (i
== numentries
) {
245 * TODO: emit a warning (log for 'branch' only goes back to ...)
247 git_oid_cpy(oid
, git_reflog_entry_id_new(entry
));
251 git_reflog_free(reflog
);
257 "reflog for '%s' has only %"PRIuZ
" entries, asked for %"PRIuZ
,
258 git_reference_name(ref
), numentries
, identifier
);
260 git_reflog_free(reflog
);
261 return GIT_ENOTFOUND
;
264 static int retrieve_revobject_from_reflog(git_object
**out
, git_reference
**base_ref
, git_repository
*repo
, const char *identifier
, size_t position
)
270 if (*base_ref
== NULL
) {
271 if ((error
= git_reference_dwim(&ref
, repo
, identifier
)) < 0)
279 error
= git_object_lookup(out
, repo
, git_reference_target(ref
), GIT_OBJECT_ANY
);
283 if ((error
= retrieve_oid_from_reflog(&oid
, ref
, position
)) < 0)
286 error
= git_object_lookup(out
, repo
, &oid
, GIT_OBJECT_ANY
);
289 git_reference_free(ref
);
293 static int retrieve_remote_tracking_reference(git_reference
**base_ref
, const char *identifier
, git_repository
*repo
)
295 git_reference
*tracking
, *ref
;
298 if (*base_ref
== NULL
) {
299 if ((error
= git_reference_dwim(&ref
, repo
, identifier
)) < 0)
306 if (!git_reference_is_branch(ref
)) {
307 error
= GIT_EINVALIDSPEC
;
311 if ((error
= git_branch_upstream(&tracking
, ref
)) < 0)
314 *base_ref
= tracking
;
317 git_reference_free(ref
);
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
)
324 int parsed
= 0, error
= -1;
325 git_str identifier
= GIT_STR_INIT
;
326 git_time_t timestamp
;
328 GIT_ASSERT(*out
== NULL
);
330 if (git_str_put(&identifier
, spec
, identifier_len
) < 0)
333 is_numeric
= !try_parse_numeric(&parsed
, curly_braces_content
);
335 if (*curly_braces_content
== '-' && (!is_numeric
|| parsed
== 0)) {
336 error
= GIT_EINVALIDSPEC
;
342 error
= retrieve_previously_checked_out_branch_or_revision(out
, ref
, repo
, git_str_cstr(&identifier
), -parsed
);
344 error
= retrieve_revobject_from_reflog(out
, ref
, repo
, git_str_cstr(&identifier
), parsed
);
349 if (!strcmp(curly_braces_content
, "u") || !strcmp(curly_braces_content
, "upstream")) {
350 error
= retrieve_remote_tracking_reference(ref
, git_str_cstr(&identifier
), repo
);
355 if (git_date_parse(×tamp
, curly_braces_content
) < 0) {
356 error
= GIT_EINVALIDSPEC
;
360 error
= retrieve_revobject_from_reflog(out
, ref
, repo
, git_str_cstr(&identifier
), (size_t)timestamp
);
363 git_str_dispose(&identifier
);
367 static git_object_t
parse_obj_type(const char *str
)
369 if (!strcmp(str
, "commit"))
370 return GIT_OBJECT_COMMIT
;
372 if (!strcmp(str
, "tree"))
373 return GIT_OBJECT_TREE
;
375 if (!strcmp(str
, "blob"))
376 return GIT_OBJECT_BLOB
;
378 if (!strcmp(str
, "tag"))
379 return GIT_OBJECT_TAG
;
381 return GIT_OBJECT_INVALID
;
384 static int dereference_to_non_tag(git_object
**out
, git_object
*obj
)
386 if (git_object_type(obj
) == GIT_OBJECT_TAG
)
387 return git_tag_peel(out
, (git_tag
*)obj
);
389 return git_object_dup(out
, obj
);
392 static int handle_caret_parent_syntax(git_object
**out
, git_object
*obj
, int n
)
394 git_object
*temp_commit
= NULL
;
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
;
406 error
= git_commit_parent((git_commit
**)out
, (git_commit
*)temp_commit
, n
- 1);
408 git_object_free(temp_commit
);
412 static int handle_linear_syntax(git_object
**out
, git_object
*obj
, int n
)
414 git_object
*temp_commit
= NULL
;
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
;
421 error
= git_commit_nth_gen_ancestor((git_commit
**)out
, (git_commit
*)temp_commit
, n
);
423 git_object_free(temp_commit
);
427 static int handle_colon_syntax(
434 git_tree_entry
*entry
= NULL
;
436 if ((error
= git_object_peel(&tree
, obj
, GIT_OBJECT_TREE
)) < 0)
437 return error
== GIT_ENOTFOUND
? GIT_EINVALIDSPEC
: error
;
445 * TODO: Handle the relative path syntax
446 * (:./relative/path and :../relative/path)
448 if ((error
= git_tree_entry_bypath(&entry
, (git_tree
*)tree
, path
)) < 0)
451 error
= git_tree_entry_to_object(out
, git_object_owner(tree
), entry
);
454 git_tree_entry_free(entry
);
455 git_object_free(tree
);
460 static int walk_and_search(git_object
**out
, git_revwalk
*walk
, git_regexp
*regex
)
466 while (!(error
= git_revwalk_next(&oid
, walk
))) {
468 error
= git_object_lookup(&obj
, git_revwalk_repository(walk
), &oid
, GIT_OBJECT_COMMIT
);
469 if ((error
< 0) && (error
!= GIT_ENOTFOUND
))
472 if (!git_regexp_match(regex
, git_commit_message((git_commit
*)obj
))) {
477 git_object_free(obj
);
480 if (error
< 0 && error
== GIT_ITEROVER
)
481 error
= GIT_ENOTFOUND
;
486 static int handle_grep_syntax(git_object
**out
, git_repository
*repo
, const git_oid
*spec_oid
, const char *pattern
)
489 git_revwalk
*walk
= NULL
;
492 if ((error
= build_regex(&preg
, pattern
)) < 0)
495 if ((error
= git_revwalk_new(&walk
, repo
)) < 0)
498 git_revwalk_sorting(walk
, GIT_SORT_TIME
);
500 if (spec_oid
== NULL
) {
501 if ((error
= git_revwalk_push_glob(walk
, "refs/*")) < 0)
503 } else if ((error
= git_revwalk_push(walk
, spec_oid
)) < 0)
506 error
= walk_and_search(out
, walk
, &preg
);
509 git_regexp_dispose(&preg
);
510 git_revwalk_free(walk
);
515 static int handle_caret_curly_syntax(git_object
**out
, git_object
*obj
, const char *curly_braces_content
)
517 git_object_t expected_type
;
519 if (*curly_braces_content
== '\0')
520 return dereference_to_non_tag(out
, obj
);
522 if (*curly_braces_content
== '/')
523 return handle_grep_syntax(out
, git_object_owner(obj
), git_object_id(obj
), curly_braces_content
+ 1);
525 expected_type
= parse_obj_type(curly_braces_content
);
527 if (expected_type
== GIT_OBJECT_INVALID
)
528 return GIT_EINVALIDSPEC
;
530 return git_object_peel(out
, obj
, expected_type
);
533 static int extract_curly_braces_content(git_str
*buf
, const char *spec
, size_t *pos
)
537 GIT_ASSERT_ARG(spec
[*pos
] == '^' || spec
[*pos
] == '@');
541 if (spec
[*pos
] == '\0' || spec
[*pos
] != '{')
542 return GIT_EINVALIDSPEC
;
546 while (spec
[*pos
] != '}') {
547 if (spec
[*pos
] == '\0')
548 return GIT_EINVALIDSPEC
;
550 if (git_str_putc(buf
, spec
[(*pos
)++]) < 0)
559 static int extract_path(git_str
*buf
, const char *spec
, size_t *pos
)
563 GIT_ASSERT_ARG(spec
[*pos
] == ':');
567 if (git_str_puts(buf
, spec
+ *pos
) < 0)
570 *pos
+= git_str_len(buf
);
575 static int extract_how_many(int *n
, const char *spec
, size_t *pos
)
578 int parsed
, accumulated
;
579 char kind
= spec
[*pos
];
581 GIT_ASSERT_ARG(spec
[*pos
] == '^' || spec
[*pos
] == '~');
589 } while (spec
[(*pos
)] == kind
&& kind
== '~');
591 if (git__isdigit(spec
[*pos
])) {
592 if (git__strntol32(&parsed
, spec
+ *pos
, strlen(spec
+ *pos
), &end_ptr
, 10) < 0)
593 return GIT_EINVALIDSPEC
;
595 accumulated
+= (parsed
- 1);
596 *pos
= end_ptr
- spec
;
599 } while (spec
[(*pos
)] == kind
&& kind
== '~');
606 static int object_from_reference(git_object
**object
, git_reference
*reference
)
608 git_reference
*resolved
= NULL
;
611 if (git_reference_resolve(&resolved
, reference
) < 0)
614 error
= git_object_lookup(object
, reference
->db
->repo
, git_reference_target(resolved
), GIT_OBJECT_ANY
);
615 git_reference_free(resolved
);
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
)
623 git_str identifier
= GIT_STR_INIT
;
628 if (*reference
!= NULL
)
629 return object_from_reference(object
, *reference
);
631 if (!allow_empty_identifier
&& identifier_len
== 0)
632 return GIT_EINVALIDSPEC
;
634 if (git_str_put(&identifier
, spec
, identifier_len
) < 0)
637 error
= revparse_lookup_object(object
, reference
, repo
, git_str_cstr(&identifier
));
638 git_str_dispose(&identifier
);
643 static int ensure_base_rev_is_not_known_yet(git_object
*object
)
648 return GIT_EINVALIDSPEC
;
651 static bool any_left_hand_identifier(git_object
*object
, git_reference
*reference
, size_t identifier_len
)
656 if (reference
!= NULL
)
659 if (identifier_len
> 0)
665 static int ensure_left_hand_identifier_is_not_known_yet(git_object
*object
, git_reference
*reference
)
667 if (!ensure_base_rev_is_not_known_yet(object
) && reference
== NULL
)
670 return GIT_EINVALIDSPEC
;
674 git_object
**object_out
,
675 git_reference
**reference_out
,
676 size_t *identifier_len_out
,
677 git_repository
*repo
,
680 size_t pos
= 0, identifier_len
= 0;
682 git_str buf
= GIT_STR_INIT
;
684 git_reference
*reference
= NULL
;
685 git_object
*base_rev
= NULL
;
687 bool should_return_reference
= true;
689 GIT_ASSERT_ARG(object_out
);
690 GIT_ASSERT_ARG(reference_out
);
691 GIT_ASSERT_ARG(repo
);
692 GIT_ASSERT_ARG(spec
);
695 *reference_out
= NULL
;
700 should_return_reference
= false;
702 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, false)) < 0)
705 if (spec
[pos
+1] == '{') {
706 git_object
*temp_object
= NULL
;
708 if ((error
= extract_curly_braces_content(&buf
, spec
, &pos
)) < 0)
711 if ((error
= handle_caret_curly_syntax(&temp_object
, base_rev
, git_str_cstr(&buf
))) < 0)
714 git_object_free(base_rev
);
715 base_rev
= temp_object
;
717 git_object
*temp_object
= NULL
;
719 if ((error
= extract_how_many(&n
, spec
, &pos
)) < 0)
722 if ((error
= handle_caret_parent_syntax(&temp_object
, base_rev
, n
)) < 0)
725 git_object_free(base_rev
);
726 base_rev
= temp_object
;
732 git_object
*temp_object
= NULL
;
734 should_return_reference
= false;
736 if ((error
= extract_how_many(&n
, spec
, &pos
)) < 0)
739 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, false)) < 0)
742 if ((error
= handle_linear_syntax(&temp_object
, base_rev
, n
)) < 0)
745 git_object_free(base_rev
);
746 base_rev
= temp_object
;
752 git_object
*temp_object
= NULL
;
754 should_return_reference
= false;
756 if ((error
= extract_path(&buf
, spec
, &pos
)) < 0)
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)
763 if ((error
= handle_colon_syntax(&temp_object
, base_rev
, git_str_cstr(&buf
))) < 0)
766 if (*git_str_cstr(&buf
) == '/') {
767 if ((error
= handle_grep_syntax(&temp_object
, repo
, NULL
, git_str_cstr(&buf
) + 1)) < 0)
772 * TODO: support merge-stage path lookup (":2:Makefile")
773 * and plain index blob lookup (:i-am/a/blob)
775 git_error_set(GIT_ERROR_INVALID
, "unimplemented");
781 git_object_free(base_rev
);
782 base_rev
= temp_object
;
787 if (spec
[pos
+1] == '{') {
788 git_object
*temp_object
= NULL
;
790 if ((error
= extract_curly_braces_content(&buf
, spec
, &pos
)) < 0)
793 if ((error
= ensure_base_rev_is_not_known_yet(base_rev
)) < 0)
796 if ((error
= handle_at_syntax(&temp_object
, &reference
, spec
, identifier_len
, repo
, git_str_cstr(&buf
))) < 0)
799 if (temp_object
!= NULL
)
800 base_rev
= temp_object
;
802 } else if (spec
[pos
+1] == '\0') {
809 if ((error
= ensure_left_hand_identifier_is_not_known_yet(base_rev
, reference
)) < 0)
817 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, false)) < 0)
820 if (!should_return_reference
) {
821 git_reference_free(reference
);
825 *object_out
= base_rev
;
826 *reference_out
= reference
;
827 *identifier_len_out
= identifier_len
;
832 if (error
== GIT_EINVALIDSPEC
)
833 git_error_set(GIT_ERROR_INVALID
,
834 "failed to parse revision specifier - Invalid pattern '%s'", spec
);
836 git_object_free(base_rev
);
837 git_reference_free(reference
);
840 git_str_dispose(&buf
);
844 int git_revparse_ext(
845 git_object
**object_out
,
846 git_reference
**reference_out
,
847 git_repository
*repo
,
851 size_t identifier_len
;
852 git_object
*obj
= NULL
;
853 git_reference
*ref
= NULL
;
855 if ((error
= revparse(&obj
, &ref
, &identifier_len
, repo
, spec
)) < 0)
859 *reference_out
= ref
;
860 GIT_UNUSED(identifier_len
);
865 git_object_free(obj
);
866 git_reference_free(ref
);
870 int git_revparse_single(git_object
**out
, git_repository
*repo
, const char *spec
)
873 git_object
*obj
= NULL
;
874 git_reference
*ref
= NULL
;
878 if ((error
= git_revparse_ext(&obj
, &ref
, repo
, spec
)) < 0)
881 git_reference_free(ref
);
888 git_object_free(obj
);
889 git_reference_free(ref
);
894 git_revspec
*revspec
,
895 git_repository
*repo
,
901 GIT_ASSERT_ARG(revspec
);
902 GIT_ASSERT_ARG(repo
);
903 GIT_ASSERT_ARG(spec
);
905 memset(revspec
, 0x0, sizeof(*revspec
));
907 if ((dotdot
= strstr(spec
, "..")) != NULL
) {
910 revspec
->flags
= GIT_REVSPEC_RANGE
;
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
918 if (!git__strcmp(spec
, "..")) {
919 git_error_set(GIT_ERROR_INVALID
, "Invalid pattern '..'");
920 return GIT_EINVALIDSPEC
;
923 lstr
= git__substrdup(spec
, dotdot
- spec
);
925 if (dotdot
[2] == '.') {
926 revspec
->flags
|= GIT_REVSPEC_MERGE_BASE
;
930 error
= git_revparse_single(
933 *lstr
== '\0' ? "HEAD" : lstr
);
936 error
= git_revparse_single(
939 *rstr
== '\0' ? "HEAD" : rstr
);
942 git__free((void*)lstr
);
944 revspec
->flags
= GIT_REVSPEC_SINGLE
;
945 error
= git_revparse_single(&revspec
->from
, repo
, spec
);