]>
git.proxmox.com Git - libgit2.git/blob - src/revparse.c
2 * Copyright (C) 2009-2012 the libgit2 contributors
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.
16 static int revspec_error(const char *revspec
)
18 giterr_set(GITERR_INVALID
, "Failed to parse revision specifier - Invalid pattern '%s'", revspec
);
22 static int disambiguate_refname(git_reference
**out
, git_repository
*repo
, const char *refname
)
25 bool fallbackmode
= true;
27 git_buf refnamebuf
= GIT_BUF_INIT
, name
= GIT_BUF_INIT
;
29 static const char* formatters
[] = {
35 "refs/remotes/%s/HEAD",
40 git_buf_puts(&name
, refname
);
42 git_buf_puts(&name
, GIT_HEAD_FILE
);
46 for (i
= 0; formatters
[i
] && (fallbackmode
|| i
== 0); i
++) {
48 git_buf_clear(&refnamebuf
);
50 if ((error
= git_buf_printf(&refnamebuf
, formatters
[i
], git_buf_cstr(&name
))) < 0)
53 error
= git_reference_lookup_resolved(&ref
, repo
, git_buf_cstr(&refnamebuf
), -1);
61 if (error
!= GIT_ENOTFOUND
)
67 git_buf_free(&refnamebuf
);
71 static int maybe_sha_or_abbrev(git_object
**out
, git_repository
*repo
, const char *spec
)
74 size_t speclen
= strlen(spec
);
76 if (git_oid_fromstrn(&oid
, spec
, speclen
) < 0)
79 return git_object_lookup_prefix(out
, repo
, &oid
, speclen
, GIT_OBJ_ANY
);
82 static int build_regex(regex_t
*regex
, const char *pattern
)
86 if (*pattern
== '\0') {
87 giterr_set(GITERR_REGEX
, "Empty pattern");
91 error
= regcomp(regex
, pattern
, REG_EXTENDED
);
95 giterr_set_regex(regex
, error
);
101 static int maybe_describe(git_object
**out
, git_repository
*repo
, const char *spec
)
107 substr
= strstr(spec
, "-g");
110 return GIT_ENOTFOUND
;
112 if (build_regex(®ex
, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)
115 error
= regexec(®ex
, spec
, 0, NULL
, 0);
119 return GIT_ENOTFOUND
;
121 return maybe_sha_or_abbrev(out
, repo
, substr
+2);
124 static int revparse_lookup_object(git_object
**out
, git_repository
*repo
, const char *spec
)
129 error
= maybe_describe(out
, repo
, spec
);
133 if (error
< 0 && error
!= GIT_ENOTFOUND
)
136 error
= disambiguate_refname(&ref
, repo
, spec
);
138 error
= git_object_lookup(out
, repo
, git_reference_oid(ref
), GIT_OBJ_ANY
);
139 git_reference_free(ref
);
143 if (error
< 0 && error
!= GIT_ENOTFOUND
)
146 error
= maybe_sha_or_abbrev(out
, repo
, spec
);
150 if (error
< 0 && error
!= GIT_ENOTFOUND
)
153 giterr_set(GITERR_REFERENCE
, "Refspec '%s' not found.", spec
);
154 return GIT_ENOTFOUND
;
157 static int try_parse_numeric(int *n
, const char *curly_braces_content
)
162 if (git__strtol32(&content
, curly_braces_content
, &end_ptr
, 10) < 0)
165 if (*end_ptr
!= '\0')
172 static int retrieve_previously_checked_out_branch_or_revision(git_object
**out
, git_reference
**base_ref
, git_repository
*repo
, const char *spec
, const char *identifier
, unsigned int position
)
174 git_reference
*ref
= NULL
;
175 git_reflog
*reflog
= NULL
;
177 int numentries
, i
, cur
, error
= -1;
178 const git_reflog_entry
*entry
;
180 regmatch_t regexmatches
[2];
181 git_buf buf
= GIT_BUF_INIT
;
185 if (*identifier
!= '\0' || *base_ref
!= NULL
)
186 return revspec_error(spec
);
188 if (build_regex(&preg
, "checkout: moving from (.*) to .*") < 0)
191 if (git_reference_lookup(&ref
, repo
, GIT_HEAD_FILE
) < 0)
194 if (git_reflog_read(&reflog
, ref
) < 0)
197 numentries
= git_reflog_entrycount(reflog
);
199 for (i
= numentries
- 1; i
>= 0; i
--) {
200 entry
= git_reflog_entry_byindex(reflog
, i
);
201 msg
= git_reflog_entry_msg(entry
);
203 if (regexec(&preg
, msg
, 2, regexmatches
, 0))
211 git_buf_put(&buf
, msg
+regexmatches
[1].rm_so
, regexmatches
[1].rm_eo
- regexmatches
[1].rm_so
);
213 if ((error
= disambiguate_refname(base_ref
, repo
, git_buf_cstr(&buf
))) == 0)
216 if (error
< 0 && error
!= GIT_ENOTFOUND
)
219 error
= maybe_sha_or_abbrev(out
, repo
, git_buf_cstr(&buf
));
224 error
= GIT_ENOTFOUND
;
227 git_reference_free(ref
);
230 git_reflog_free(reflog
);
234 static int retrieve_oid_from_reflog(git_oid
*oid
, git_reference
*ref
, unsigned int identifier
)
238 unsigned int numentries
;
239 const git_reflog_entry
*entry
;
240 bool search_by_pos
= (identifier
<= 100000000);
242 if (git_reflog_read(&reflog
, ref
) < 0)
245 numentries
= git_reflog_entrycount(reflog
);
248 if (numentries
< identifier
+ 1) {
251 "Reflog for '%s' has only %d entries, asked for %d",
252 git_reference_name(ref
),
256 error
= GIT_ENOTFOUND
;
260 entry
= git_reflog_entry_byindex(reflog
, identifier
);
261 git_oid_cpy(oid
, git_reflog_entry_oidold(entry
));
267 git_time commit_time
;
269 for (i
= numentries
- 1; i
>= 0; i
--) {
270 entry
= git_reflog_entry_byindex(reflog
, i
);
271 commit_time
= git_reflog_entry_committer(entry
)->when
;
273 if (commit_time
.time
- identifier
> 0)
276 git_oid_cpy(oid
, git_reflog_entry_oidnew(entry
));
281 error
= GIT_ENOTFOUND
;
285 git_reflog_free(reflog
);
289 static int retrieve_revobject_from_reflog(git_object
**out
, git_reference
**base_ref
, git_repository
*repo
, const char *identifier
, unsigned int position
)
295 if (*base_ref
== NULL
) {
296 if ((error
= disambiguate_refname(&ref
, repo
, identifier
)) < 0)
304 error
= git_object_lookup(out
, repo
, git_reference_oid(ref
), GIT_OBJ_ANY
);
308 if ((error
= retrieve_oid_from_reflog(&oid
, ref
, position
)) < 0)
311 error
= git_object_lookup(out
, repo
, &oid
, GIT_OBJ_ANY
);
314 git_reference_free(ref
);
318 static int retrieve_remote_tracking_reference(git_reference
**base_ref
, const char *identifier
, git_repository
*repo
)
320 git_reference
*tracking
, *ref
;
323 if (*base_ref
== NULL
) {
324 if ((error
= disambiguate_refname(&ref
, repo
, identifier
)) < 0)
331 if ((error
= git_reference_remote_tracking_from_branch(&tracking
, ref
)) < 0)
334 *base_ref
= tracking
;
337 git_reference_free(ref
);
341 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
)
344 int parsed
= 0, error
= -1;
345 git_buf identifier
= GIT_BUF_INIT
;
346 git_time_t timestamp
;
348 assert(*out
== NULL
);
350 if (git_buf_put(&identifier
, spec
, identifier_len
) < 0)
353 is_numeric
= !try_parse_numeric(&parsed
, curly_braces_content
);
355 if (*curly_braces_content
== '-' && (!is_numeric
|| parsed
== 0)) {
356 error
= revspec_error(spec
);
362 error
= retrieve_previously_checked_out_branch_or_revision(out
, ref
, repo
, spec
, git_buf_cstr(&identifier
), -parsed
);
364 error
= retrieve_revobject_from_reflog(out
, ref
, repo
, git_buf_cstr(&identifier
), parsed
);
369 if (!strcmp(curly_braces_content
, "u") || !strcmp(curly_braces_content
, "upstream")) {
370 error
= retrieve_remote_tracking_reference(ref
, git_buf_cstr(&identifier
), repo
);
375 if (git__date_parse(×tamp
, curly_braces_content
) < 0)
378 error
= retrieve_revobject_from_reflog(out
, ref
, repo
, git_buf_cstr(&identifier
), (unsigned int)timestamp
);
381 git_buf_free(&identifier
);
385 static git_otype
parse_obj_type(const char *str
)
387 if (!strcmp(str
, "commit"))
388 return GIT_OBJ_COMMIT
;
390 if (!strcmp(str
, "tree"))
393 if (!strcmp(str
, "blob"))
396 if (!strcmp(str
, "tag"))
402 static int dereference_to_non_tag(git_object
**out
, git_object
*obj
)
404 if (git_object_type(obj
) == GIT_OBJ_TAG
)
405 return git_tag_peel(out
, (git_tag
*)obj
);
407 return git_object__dup(out
, obj
);
410 static int handle_caret_parent_syntax(git_object
**out
, git_object
*obj
, int n
)
412 git_object
*temp_commit
= NULL
;
415 if (git_object_peel(&temp_commit
, obj
, GIT_OBJ_COMMIT
) < 0)
423 error
= git_commit_parent((git_commit
**)out
, (git_commit
*)temp_commit
, n
- 1);
425 git_object_free(temp_commit
);
429 static int handle_linear_syntax(git_object
**out
, git_object
*obj
, int n
)
431 git_object
*temp_commit
= NULL
;
434 if (git_object_peel(&temp_commit
, obj
, GIT_OBJ_COMMIT
) < 0)
437 error
= git_commit_nth_gen_ancestor((git_commit
**)out
, (git_commit
*)temp_commit
, n
);
439 git_object_free(temp_commit
);
443 static int handle_colon_syntax(
450 git_tree_entry
*entry
= NULL
;
452 if (git_object_peel(&tree
, obj
, GIT_OBJ_TREE
) < 0)
461 * TODO: Handle the relative path syntax
462 * (:./relative/path and :../relative/path)
464 if ((error
= git_tree_entry_bypath(&entry
, (git_tree
*)tree
, path
)) < 0)
467 error
= git_tree_entry_to_object(out
, git_object_owner(tree
), entry
);
470 git_tree_entry_free(entry
);
471 git_object_free(tree
);
476 static int walk_and_search(git_object
**out
, git_revwalk
*walk
, regex_t
*regex
)
482 while (!(error
= git_revwalk_next(&oid
, walk
))) {
484 if ((error
= git_object_lookup(&obj
, git_revwalk_repository(walk
), &oid
, GIT_OBJ_COMMIT
) < 0) &&
485 (error
!= GIT_ENOTFOUND
))
488 if (!regexec(regex
, git_commit_message((git_commit
*)obj
), 0, NULL
, 0)) {
493 git_object_free(obj
);
496 if (error
< 0 && error
== GIT_REVWALKOVER
)
497 error
= GIT_ENOTFOUND
;
502 static int handle_grep_syntax(git_object
**out
, git_repository
*repo
, const git_oid
*spec_oid
, const char *pattern
)
505 git_revwalk
*walk
= NULL
;
508 if (build_regex(&preg
, pattern
) < 0)
511 if (git_revwalk_new(&walk
, repo
) < 0)
514 git_revwalk_sorting(walk
, GIT_SORT_TIME
);
516 if (spec_oid
== NULL
) {
517 // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails
518 if (git_revwalk_push_glob(walk
, "refs/heads/*") < 0)
520 } else if (git_revwalk_push(walk
, spec_oid
) < 0)
523 error
= walk_and_search(out
, walk
, &preg
);
527 git_revwalk_free(walk
);
532 static int handle_caret_curly_syntax(git_object
**out
, git_object
*obj
, const char *curly_braces_content
)
534 git_otype expected_type
;
536 if (*curly_braces_content
== '\0')
537 return dereference_to_non_tag(out
, obj
);
539 if (*curly_braces_content
== '/')
540 return handle_grep_syntax(out
, git_object_owner(obj
), git_object_id(obj
), curly_braces_content
+ 1);
542 expected_type
= parse_obj_type(curly_braces_content
);
544 if (expected_type
== GIT_OBJ_BAD
)
547 return git_object_peel(out
, obj
, expected_type
);
550 static int extract_curly_braces_content(git_buf
*buf
, const char *spec
, size_t *pos
)
554 assert(spec
[*pos
] == '^' || spec
[*pos
] == '@');
558 if (spec
[*pos
] == '\0' || spec
[*pos
] != '{')
559 return revspec_error(spec
);
563 while (spec
[*pos
] != '}') {
564 if (spec
[*pos
] == '\0')
565 return revspec_error(spec
);
567 git_buf_putc(buf
, spec
[(*pos
)++]);
575 static int extract_path(git_buf
*buf
, const char *spec
, size_t *pos
)
579 assert(spec
[*pos
] == ':');
583 if (git_buf_puts(buf
, spec
+ *pos
) < 0)
586 *pos
+= git_buf_len(buf
);
591 static int extract_how_many(int *n
, const char *spec
, size_t *pos
)
594 int parsed
, accumulated
;
595 char kind
= spec
[*pos
];
597 assert(spec
[*pos
] == '^' || spec
[*pos
] == '~');
605 } while (spec
[(*pos
)] == kind
&& kind
== '~');
607 if (git__isdigit(spec
[*pos
])) {
608 if ((git__strtol32(&parsed
, spec
+ *pos
, &end_ptr
, 10) < 0) < 0)
609 return revspec_error(spec
);
611 accumulated
+= (parsed
- 1);
612 *pos
= end_ptr
- spec
;
615 } while (spec
[(*pos
)] == kind
&& kind
== '~');
622 static int object_from_reference(git_object
**object
, git_reference
*reference
)
624 git_reference
*resolved
= NULL
;
627 if (git_reference_resolve(&resolved
, reference
) < 0)
630 error
= git_object_lookup(object
, reference
->owner
, git_reference_oid(resolved
), GIT_OBJ_ANY
);
631 git_reference_free(resolved
);
636 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
)
639 git_buf identifier
= GIT_BUF_INIT
;
644 if (*reference
!= NULL
) {
645 if ((error
= object_from_reference(object
, *reference
)) < 0)
648 git_reference_free(*reference
);
653 if (!allow_empty_identifier
&& identifier_len
== 0)
654 return revspec_error(spec
);
656 if (git_buf_put(&identifier
, spec
, identifier_len
) < 0)
659 error
= revparse_lookup_object(object
, repo
, git_buf_cstr(&identifier
));
660 git_buf_free(&identifier
);
665 static int ensure_base_rev_is_not_known_yet(git_object
*object
, const char *spec
)
670 return revspec_error(spec
);
673 static bool any_left_hand_identifier(git_object
*object
, git_reference
*reference
, size_t identifier_len
)
678 if (reference
!= NULL
)
681 if (identifier_len
> 0)
687 static int ensure_left_hand_identifier_is_not_known_yet(git_object
*object
, git_reference
*reference
, const char *spec
)
689 if (!ensure_base_rev_is_not_known_yet(object
, spec
) && reference
== NULL
)
692 return revspec_error(spec
);
695 int git_revparse_single(git_object
**out
, git_repository
*repo
, const char *spec
)
697 size_t pos
= 0, identifier_len
= 0;
699 git_buf buf
= GIT_BUF_INIT
;
701 git_reference
*reference
= NULL
;
702 git_object
*base_rev
= NULL
;
704 assert(out
&& repo
&& spec
);
711 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, false)) < 0)
714 if (spec
[pos
+1] == '{') {
715 git_object
*temp_object
= NULL
;
717 if ((error
= extract_curly_braces_content(&buf
, spec
, &pos
)) < 0)
720 if ((error
= handle_caret_curly_syntax(&temp_object
, base_rev
, git_buf_cstr(&buf
))) < 0)
723 git_object_free(base_rev
);
724 base_rev
= temp_object
;
726 git_object
*temp_object
= NULL
;
728 if ((error
= extract_how_many(&n
, spec
, &pos
)) < 0)
731 if ((error
= handle_caret_parent_syntax(&temp_object
, base_rev
, n
)) < 0)
734 git_object_free(base_rev
);
735 base_rev
= temp_object
;
741 git_object
*temp_object
= NULL
;
743 if ((error
= extract_how_many(&n
, spec
, &pos
)) < 0)
746 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, false)) < 0)
749 if ((error
= handle_linear_syntax(&temp_object
, base_rev
, n
)) < 0)
752 git_object_free(base_rev
);
753 base_rev
= temp_object
;
759 git_object
*temp_object
= NULL
;
761 if ((error
= extract_path(&buf
, spec
, &pos
)) < 0)
764 if (any_left_hand_identifier(base_rev
, reference
, identifier_len
)) {
765 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, true)) < 0)
768 if ((error
= handle_colon_syntax(&temp_object
, base_rev
, git_buf_cstr(&buf
))) < 0)
771 if (*git_buf_cstr(&buf
) == '/') {
772 if ((error
= handle_grep_syntax(&temp_object
, repo
, NULL
, git_buf_cstr(&buf
) + 1)) < 0)
777 * TODO: support merge-stage path lookup (":2:Makefile")
778 * and plain index blob lookup (:i-am/a/blob)
780 giterr_set(GITERR_INVALID
, "Unimplemented");
786 git_object_free(base_rev
);
787 base_rev
= temp_object
;
793 git_object
*temp_object
= NULL
;
795 if ((error
= extract_curly_braces_content(&buf
, spec
, &pos
)) < 0)
798 if ((error
= ensure_base_rev_is_not_known_yet(base_rev
, spec
)) < 0)
801 if ((error
= handle_at_syntax(&temp_object
, &reference
, spec
, identifier_len
, repo
, git_buf_cstr(&buf
))) < 0)
804 if (temp_object
!= NULL
)
805 base_rev
= temp_object
;
810 if ((error
= ensure_left_hand_identifier_is_not_known_yet(base_rev
, reference
, spec
)) < 0)
818 if ((error
= ensure_base_rev_loaded(&base_rev
, &reference
, spec
, identifier_len
, repo
, false)) < 0)
826 git_object_free(base_rev
);
827 git_reference_free(reference
);