]>
git.proxmox.com Git - libgit2.git/blob - src/revparse.c
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.
17 static int disambiguate_refname(git_reference
**out
, git_repository
*repo
, const char *refname
)
20 bool fallbackmode
= true, foundvalid
= false;
22 git_buf refnamebuf
= GIT_BUF_INIT
, name
= GIT_BUF_INIT
;
24 static const char* formatters
[] = {
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
,
35 git_buf_puts(&name
, refname
);
37 git_buf_puts(&name
, GIT_HEAD_FILE
);
41 for (i
= 0; formatters
[i
] && (fallbackmode
|| i
== 0); i
++) {
43 git_buf_clear(&refnamebuf
);
45 if ((error
= git_buf_printf(&refnamebuf
, formatters
[i
], git_buf_cstr(&name
))) < 0)
48 if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf
))) {
49 error
= GIT_EINVALIDSPEC
;
54 error
= git_reference_lookup_resolved(&ref
, repo
, git_buf_cstr(&refnamebuf
), -1);
62 if (error
!= GIT_ENOTFOUND
)
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
));
74 git_buf_free(&refnamebuf
);
78 static int maybe_sha_or_abbrev(git_object
** out
, git_repository
*repo
, const char *spec
, size_t speclen
)
82 if (git_oid_fromstrn(&oid
, spec
, speclen
) < 0)
85 return git_object_lookup_prefix(out
, repo
, &oid
, speclen
, GIT_OBJ_ANY
);
88 static int maybe_sha(git_object
** out
, git_repository
*repo
, const char *spec
)
90 size_t speclen
= strlen(spec
);
92 if (speclen
!= GIT_OID_HEXSZ
)
95 return maybe_sha_or_abbrev(out
, repo
, spec
, speclen
);
98 static int maybe_abbrev(git_object
** out
, git_repository
*repo
, const char *spec
)
100 size_t speclen
= strlen(spec
);
102 return maybe_sha_or_abbrev(out
, repo
, spec
, speclen
);
105 static int build_regex(regex_t
*regex
, const char *pattern
)
109 if (*pattern
== '\0') {
110 giterr_set(GITERR_REGEX
, "Empty pattern");
111 return GIT_EINVALIDSPEC
;
114 error
= regcomp(regex
, pattern
, REG_EXTENDED
);
118 error
= giterr_set_regex(regex
, error
);
125 static int maybe_describe(git_object
**out
, git_repository
*repo
, const char *spec
)
131 substr
= strstr(spec
, "-g");
134 return GIT_ENOTFOUND
;
136 if (build_regex(®ex
, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
139 error
= regexec(®ex
, spec
, 0, NULL
, 0);
143 return GIT_ENOTFOUND
;
145 return maybe_abbrev(out
, repo
, substr
+2);
148 static int revparse_lookup_object(git_object
**out
, git_repository
*repo
, const char *spec
)
153 error
= maybe_sha(out
, repo
, spec
);
157 if (error
< 0 && error
!= GIT_ENOTFOUND
)
160 error
= disambiguate_refname(&ref
, repo
, spec
);
162 error
= git_object_lookup(out
, repo
, git_reference_target(ref
), GIT_OBJ_ANY
);
163 git_reference_free(ref
);
167 if (error
< 0 && error
!= GIT_ENOTFOUND
)
170 error
= maybe_abbrev(out
, repo
, spec
);
174 if (error
< 0 && error
!= GIT_ENOTFOUND
)
177 error
= maybe_describe(out
, repo
, spec
);
181 if (error
< 0 && error
!= GIT_ENOTFOUND
)
184 giterr_set(GITERR_REFERENCE
, "Refspec '%s' not found.", spec
);
185 return GIT_ENOTFOUND
;
188 static int try_parse_numeric(int *n
, const char *curly_braces_content
)
193 if (git__strtol32(&content
, curly_braces_content
, &end_ptr
, 10) < 0)
196 if (*end_ptr
!= '\0')
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
)
205 git_reference
*ref
= NULL
;
206 git_reflog
*reflog
= NULL
;
209 size_t i
, numentries
, cur
;
210 const git_reflog_entry
*entry
;
212 regmatch_t regexmatches
[2];
213 git_buf buf
= GIT_BUF_INIT
;
217 if (*identifier
!= '\0' || *base_ref
!= NULL
)
218 return GIT_EINVALIDSPEC
;
220 if (build_regex(&preg
, "checkout: moving from (.*) to .*") < 0)
223 if (git_reference_lookup(&ref
, repo
, GIT_HEAD_FILE
) < 0)
226 if (git_reflog_read(&reflog
, ref
) < 0)
229 numentries
= git_reflog_entrycount(reflog
);
231 for (i
= 0; i
< numentries
; i
++) {
232 entry
= git_reflog_entry_byindex(reflog
, i
);
233 msg
= git_reflog_entry_message(entry
);
235 if (regexec(&preg
, msg
, 2, regexmatches
, 0))
243 git_buf_put(&buf
, msg
+regexmatches
[1].rm_so
, regexmatches
[1].rm_eo
- regexmatches
[1].rm_so
);
245 if ((error
= disambiguate_refname(base_ref
, repo
, git_buf_cstr(&buf
))) == 0)
248 if (error
< 0 && error
!= GIT_ENOTFOUND
)
251 error
= maybe_abbrev(out
, repo
, git_buf_cstr(&buf
));
256 error
= GIT_ENOTFOUND
;
259 git_reference_free(ref
);
262 git_reflog_free(reflog
);
266 static int retrieve_oid_from_reflog(git_oid
*oid
, git_reference
*ref
, size_t identifier
)
271 const git_reflog_entry
*entry
;
272 bool search_by_pos
= (identifier
<= 100000000);
274 if (git_reflog_read(&reflog
, ref
) < 0)
277 numentries
= git_reflog_entrycount(reflog
);
280 if (numentries
< identifier
+ 1) {
283 "Reflog for '%s' has only "PRIuZ
" entries, asked for "PRIuZ
,
284 git_reference_name(ref
), numentries
, identifier
);
286 error
= GIT_ENOTFOUND
;
290 entry
= git_reflog_entry_byindex(reflog
, identifier
);
291 git_oid_cpy(oid
, git_reflog_entry_id_new(entry
));
297 git_time commit_time
;
299 for (i
= 0; i
< numentries
; i
++) {
300 entry
= git_reflog_entry_byindex(reflog
, i
);
301 commit_time
= git_reflog_entry_committer(entry
)->when
;
303 if (commit_time
.time
> (git_time_t
)identifier
)
306 git_oid_cpy(oid
, git_reflog_entry_id_new(entry
));
311 error
= GIT_ENOTFOUND
;
315 git_reflog_free(reflog
);
319 static int retrieve_revobject_from_reflog(git_object
**out
, git_reference
**base_ref
, git_repository
*repo
, const char *identifier
, size_t position
)
325 if (*base_ref
== NULL
) {
326 if ((error
= disambiguate_refname(&ref
, repo
, identifier
)) < 0)
334 error
= git_object_lookup(out
, repo
, git_reference_target(ref
), GIT_OBJ_ANY
);
338 if ((error
= retrieve_oid_from_reflog(&oid
, ref
, position
)) < 0)
341 error
= git_object_lookup(out
, repo
, &oid
, GIT_OBJ_ANY
);
344 git_reference_free(ref
);
348 static int retrieve_remote_tracking_reference(git_reference
**base_ref
, const char *identifier
, git_repository
*repo
)
350 git_reference
*tracking
, *ref
;
353 if (*base_ref
== NULL
) {
354 if ((error
= disambiguate_refname(&ref
, repo
, identifier
)) < 0)
361 if (!git_reference_is_branch(ref
)) {
362 error
= GIT_EINVALIDSPEC
;
366 if ((error
= git_branch_upstream(&tracking
, ref
)) < 0)
369 *base_ref
= tracking
;
372 git_reference_free(ref
);
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
)
379 int parsed
= 0, error
= -1;
380 git_buf identifier
= GIT_BUF_INIT
;
381 git_time_t timestamp
;
383 assert(*out
== NULL
);
385 if (git_buf_put(&identifier
, spec
, identifier_len
) < 0)
388 is_numeric
= !try_parse_numeric(&parsed
, curly_braces_content
);
390 if (*curly_braces_content
== '-' && (!is_numeric
|| parsed
== 0)) {
391 error
= GIT_EINVALIDSPEC
;
397 error
= retrieve_previously_checked_out_branch_or_revision(out
, ref
, repo
, git_buf_cstr(&identifier
), -parsed
);
399 error
= retrieve_revobject_from_reflog(out
, ref
, repo
, git_buf_cstr(&identifier
), parsed
);
404 if (!strcmp(curly_braces_content
, "u") || !strcmp(curly_braces_content
, "upstream")) {
405 error
= retrieve_remote_tracking_reference(ref
, git_buf_cstr(&identifier
), repo
);
410 if (git__date_parse(×tamp
, curly_braces_content
) < 0)
413 error
= retrieve_revobject_from_reflog(out
, ref
, repo
, git_buf_cstr(&identifier
), (size_t)timestamp
);
416 git_buf_free(&identifier
);
420 static git_otype
parse_obj_type(const char *str
)
422 if (!strcmp(str
, "commit"))
423 return GIT_OBJ_COMMIT
;
425 if (!strcmp(str
, "tree"))
428 if (!strcmp(str
, "blob"))
431 if (!strcmp(str
, "tag"))
437 static int dereference_to_non_tag(git_object
**out
, git_object
*obj
)
439 if (git_object_type(obj
) == GIT_OBJ_TAG
)
440 return git_tag_peel(out
, (git_tag
*)obj
);
442 return git_object_dup(out
, obj
);
445 static int handle_caret_parent_syntax(git_object
**out
, git_object
*obj
, int n
)
447 git_object
*temp_commit
= NULL
;
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
;
459 error
= git_commit_parent((git_commit
**)out
, (git_commit
*)temp_commit
, n
- 1);
461 git_object_free(temp_commit
);
465 static int handle_linear_syntax(git_object
**out
, git_object
*obj
, int n
)
467 git_object
*temp_commit
= NULL
;
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
;
474 error
= git_commit_nth_gen_ancestor((git_commit
**)out
, (git_commit
*)temp_commit
, n
);
476 git_object_free(temp_commit
);
480 static int handle_colon_syntax(
487 git_tree_entry
*entry
= NULL
;
489 if ((error
= git_object_peel(&tree
, obj
, GIT_OBJ_TREE
)) < 0)
490 return error
== GIT_ENOTFOUND
? GIT_EINVALIDSPEC
: error
;
498 * TODO: Handle the relative path syntax
499 * (:./relative/path and :../relative/path)
501 if ((error
= git_tree_entry_bypath(&entry
, (git_tree
*)tree
, path
)) < 0)
504 error
= git_tree_entry_to_object(out
, git_object_owner(tree
), entry
);
507 git_tree_entry_free(entry
);
508 git_object_free(tree
);
513 static int walk_and_search(git_object
**out
, git_revwalk
*walk
, regex_t
*regex
)
519 while (!(error
= git_revwalk_next(&oid
, walk
))) {
521 error
= git_object_lookup(&obj
, git_revwalk_repository(walk
), &oid
, GIT_OBJ_COMMIT
);
522 if ((error
< 0) && (error
!= GIT_ENOTFOUND
))
525 if (!regexec(regex
, git_commit_message((git_commit
*)obj
), 0, NULL
, 0)) {
530 git_object_free(obj
);
533 if (error
< 0 && error
== GIT_ITEROVER
)
534 error
= GIT_ENOTFOUND
;
539 static int handle_grep_syntax(git_object
**out
, git_repository
*repo
, const git_oid
*spec_oid
, const char *pattern
)
542 git_revwalk
*walk
= NULL
;
545 if ((error
= build_regex(&preg
, pattern
)) < 0)
548 if ((error
= git_revwalk_new(&walk
, repo
)) < 0)
551 git_revwalk_sorting(walk
, GIT_SORT_TIME
);
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)
557 } else if ((error
= git_revwalk_push(walk
, spec_oid
)) < 0)
560 error
= walk_and_search(out
, walk
, &preg
);
564 git_revwalk_free(walk
);
569 static int handle_caret_curly_syntax(git_object
**out
, git_object
*obj
, const char *curly_braces_content
)
571 git_otype expected_type
;
573 if (*curly_braces_content
== '\0')
574 return dereference_to_non_tag(out
, obj
);
576 if (*curly_braces_content
== '/')
577 return handle_grep_syntax(out
, git_object_owner(obj
), git_object_id(obj
), curly_braces_content
+ 1);
579 expected_type
= parse_obj_type(curly_braces_content
);
581 if (expected_type
== GIT_OBJ_BAD
)
582 return GIT_EINVALIDSPEC
;
584 return git_object_peel(out
, obj
, expected_type
);
587 static int extract_curly_braces_content(git_buf
*buf
, const char *spec
, size_t *pos
)
591 assert(spec
[*pos
] == '^' || spec
[*pos
] == '@');
595 if (spec
[*pos
] == '\0' || spec
[*pos
] != '{')
596 return GIT_EINVALIDSPEC
;
600 while (spec
[*pos
] != '}') {
601 if (spec
[*pos
] == '\0')
602 return GIT_EINVALIDSPEC
;
604 git_buf_putc(buf
, spec
[(*pos
)++]);
612 static int extract_path(git_buf
*buf
, const char *spec
, size_t *pos
)
616 assert(spec
[*pos
] == ':');
620 if (git_buf_puts(buf
, spec
+ *pos
) < 0)
623 *pos
+= git_buf_len(buf
);
628 static int extract_how_many(int *n
, const char *spec
, size_t *pos
)
631 int parsed
, accumulated
;
632 char kind
= spec
[*pos
];
634 assert(spec
[*pos
] == '^' || spec
[*pos
] == '~');
642 } while (spec
[(*pos
)] == kind
&& kind
== '~');
644 if (git__isdigit(spec
[*pos
])) {
645 if (git__strtol32(&parsed
, spec
+ *pos
, &end_ptr
, 10) < 0)
646 return GIT_EINVALIDSPEC
;
648 accumulated
+= (parsed
- 1);
649 *pos
= end_ptr
- spec
;
652 } while (spec
[(*pos
)] == kind
&& kind
== '~');
659 static int object_from_reference(git_object
**object
, git_reference
*reference
)
661 git_reference
*resolved
= NULL
;
664 if (git_reference_resolve(&resolved
, reference
) < 0)
667 error
= git_object_lookup(object
, reference
->db
->repo
, git_reference_target(resolved
), GIT_OBJ_ANY
);
668 git_reference_free(resolved
);
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
)
676 git_buf identifier
= GIT_BUF_INIT
;
681 if (*reference
!= NULL
) {
682 if ((error
= object_from_reference(object
, *reference
)) < 0)
685 git_reference_free(*reference
);
690 if (!allow_empty_identifier
&& identifier_len
== 0)
691 return GIT_EINVALIDSPEC
;
693 if (git_buf_put(&identifier
, spec
, identifier_len
) < 0)
696 error
= revparse_lookup_object(object
, repo
, git_buf_cstr(&identifier
));
697 git_buf_free(&identifier
);
702 static int ensure_base_rev_is_not_known_yet(git_object
*object
)
707 return GIT_EINVALIDSPEC
;
710 static bool any_left_hand_identifier(git_object
*object
, git_reference
*reference
, size_t identifier_len
)
715 if (reference
!= NULL
)
718 if (identifier_len
> 0)
724 static int ensure_left_hand_identifier_is_not_known_yet(git_object
*object
, git_reference
*reference
)
726 if (!ensure_base_rev_is_not_known_yet(object
) && reference
== NULL
)
729 return GIT_EINVALIDSPEC
;
732 int git_revparse_single(git_object
**out
, git_repository
*repo
, const char *spec
)
734 size_t pos
= 0, identifier_len
= 0;
736 git_buf buf
= GIT_BUF_INIT
;
738 git_reference
*reference
= NULL
;
739 git_object
*base_rev
= NULL
;
741 assert(out
&& repo
&& spec
);
748 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, false)) < 0)
751 if (spec
[pos
+1] == '{') {
752 git_object
*temp_object
= NULL
;
754 if ((error
= extract_curly_braces_content(&buf
, spec
, &pos
)) < 0)
757 if ((error
= handle_caret_curly_syntax(&temp_object
, base_rev
, git_buf_cstr(&buf
))) < 0)
760 git_object_free(base_rev
);
761 base_rev
= temp_object
;
763 git_object
*temp_object
= NULL
;
765 if ((error
= extract_how_many(&n
, spec
, &pos
)) < 0)
768 if ((error
= handle_caret_parent_syntax(&temp_object
, base_rev
, n
)) < 0)
771 git_object_free(base_rev
);
772 base_rev
= temp_object
;
778 git_object
*temp_object
= NULL
;
780 if ((error
= extract_how_many(&n
, spec
, &pos
)) < 0)
783 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, false)) < 0)
786 if ((error
= handle_linear_syntax(&temp_object
, base_rev
, n
)) < 0)
789 git_object_free(base_rev
);
790 base_rev
= temp_object
;
796 git_object
*temp_object
= NULL
;
798 if ((error
= extract_path(&buf
, spec
, &pos
)) < 0)
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)
805 if ((error
= handle_colon_syntax(&temp_object
, base_rev
, git_buf_cstr(&buf
))) < 0)
808 if (*git_buf_cstr(&buf
) == '/') {
809 if ((error
= handle_grep_syntax(&temp_object
, repo
, NULL
, git_buf_cstr(&buf
) + 1)) < 0)
814 * TODO: support merge-stage path lookup (":2:Makefile")
815 * and plain index blob lookup (:i-am/a/blob)
817 giterr_set(GITERR_INVALID
, "Unimplemented");
823 git_object_free(base_rev
);
824 base_rev
= temp_object
;
830 if (spec
[pos
+1] == '{') {
831 git_object
*temp_object
= NULL
;
833 if ((error
= extract_curly_braces_content(&buf
, spec
, &pos
)) < 0)
836 if ((error
= ensure_base_rev_is_not_known_yet(base_rev
)) < 0)
839 if ((error
= handle_at_syntax(&temp_object
, &reference
, spec
, identifier_len
, repo
, git_buf_cstr(&buf
))) < 0)
842 if (temp_object
!= NULL
)
843 base_rev
= temp_object
;
851 if ((error
= ensure_left_hand_identifier_is_not_known_yet(base_rev
, reference
)) < 0)
859 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, false)) < 0)
867 if (error
== GIT_EINVALIDSPEC
)
868 giterr_set(GITERR_INVALID
,
869 "Failed to parse revision specifier - Invalid pattern '%s'", spec
);
871 git_object_free(base_rev
);
873 git_reference_free(reference
);
880 git_revspec
*revspec
,
881 git_repository
*repo
,
887 assert(revspec
&& repo
&& spec
);
889 memset(revspec
, 0x0, sizeof(*revspec
));
891 if ((dotdot
= strstr(spec
, "..")) != NULL
) {
894 revspec
->flags
= GIT_REVPARSE_RANGE
;
896 lstr
= git__substrdup(spec
, dotdot
- spec
);
898 if (dotdot
[2] == '.') {
899 revspec
->flags
|= GIT_REVPARSE_MERGE_BASE
;
903 if ((error
= git_revparse_single(&revspec
->from
, repo
, lstr
)) < 0) {
907 if ((error
= git_revparse_single(&revspec
->to
, repo
, rstr
)) < 0) {
911 git__free((void*)lstr
);
913 revspec
->flags
= GIT_REVPARSE_SINGLE
;
914 error
= git_revparse_single(&revspec
->from
, repo
, spec
);