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.
19 REVPARSE_STATE_LINEAR
,
24 static int revspec_error(const char *revspec
)
26 giterr_set(GITERR_INVALID
, "Failed to parse revision specifier - Invalid pattern '%s'", revspec
);
30 static int revparse_lookup_fully_qualifed_ref(git_object
**out
, git_repository
*repo
, const char*spec
)
35 if ((error
= git_reference_name_to_oid(&resolved
, repo
, spec
)) < 0)
38 return git_object_lookup(out
, repo
, &resolved
, GIT_OBJ_ANY
);
41 /* Returns non-zero if yes */
42 static int spec_looks_like_describe_output(const char *spec
)
45 int regex_error
, retcode
;
47 regex_error
= regcomp(®ex
, ".+-[0-9]+-g[0-9a-fA-F]+", REG_EXTENDED
);
48 if (regex_error
!= 0) {
49 giterr_set_regex(®ex
, regex_error
);
50 return 1; /* To be safe */
52 retcode
= regexec(®ex
, spec
, 0, NULL
, 0);
57 static int revparse_lookup_object(git_object
**out
, git_repository
*repo
, const char *spec
)
59 size_t speclen
= strlen(spec
);
60 git_object
*obj
= NULL
;
62 git_buf refnamebuf
= GIT_BUF_INIT
;
63 static const char* formatters
[] = {
68 "refs/remotes/%s/HEAD",
74 /* "git describe" output; snip everything before/including "-g" */
75 substr
= strstr(spec
, "-g");
77 spec_looks_like_describe_output(spec
) &&
78 !revparse_lookup_object(out
, repo
, substr
+2)) {
83 if (!git_oid_fromstrn(&oid
, spec
, speclen
)) {
84 if (!git_object_lookup_prefix(&obj
, repo
, &oid
, speclen
, GIT_OBJ_ANY
)) {
91 if (!revparse_lookup_fully_qualifed_ref(&obj
, repo
, spec
)) {
96 /* Partially-named ref; match in this order: */
97 for (i
=0; formatters
[i
]; i
++) {
98 git_buf_clear(&refnamebuf
);
99 if (git_buf_printf(&refnamebuf
, formatters
[i
], spec
) < 0) {
103 if (!revparse_lookup_fully_qualifed_ref(&obj
, repo
, git_buf_cstr(&refnamebuf
))) {
104 git_buf_free(&refnamebuf
);
109 git_buf_free(&refnamebuf
);
111 giterr_set(GITERR_REFERENCE
, "Refspec '%s' not found.", spec
);
112 return GIT_ENOTFOUND
;
116 static int all_chars_are_digits(const char *str
, size_t len
)
120 for (i
= 0; i
< len
; i
++)
121 if (!git__isdigit(str
[i
])) return 0;
126 static void normalize_maybe_empty_refname(git_buf
*buf
, git_repository
*repo
, const char *refspec
, size_t refspeclen
)
131 /* Empty refspec means current branch (target of HEAD) */
132 git_reference_lookup(&ref
, repo
, "HEAD");
133 git_buf_puts(buf
, git_reference_target(ref
));
134 git_reference_free(ref
);
135 } else if (strstr(refspec
, "HEAD")) {
137 git_buf_puts(buf
, refspec
);
139 if (git__prefixcmp(refspec
, "refs/heads/") != 0) {
140 git_buf_printf(buf
, "refs/heads/%s", refspec
);
142 git_buf_puts(buf
, refspec
);
147 static int walk_ref_history(git_object
**out
, git_repository
*repo
, const char *refspec
, const char *reflogspec
)
150 git_reflog
*reflog
= NULL
;
151 int n
, retcode
= GIT_ERROR
;
153 const git_reflog_entry
*entry
;
154 git_buf buf
= GIT_BUF_INIT
;
155 size_t refspeclen
= strlen(refspec
);
156 size_t reflogspeclen
= strlen(reflogspec
);
158 if (git__prefixcmp(reflogspec
, "@{") != 0 ||
159 git__suffixcmp(reflogspec
, "}") != 0)
160 return revspec_error(reflogspec
);
162 /* "@{-N}" form means walk back N checkouts. That means the HEAD log. */
163 if (!git__prefixcmp(reflogspec
, "@{-")) {
168 return revspec_error(reflogspec
);
170 if (git__strtol32(&n
, reflogspec
+3, NULL
, 0) < 0 || n
< 1)
171 return revspec_error(reflogspec
);
173 if (!git_reference_lookup(&ref
, repo
, "HEAD")) {
174 if (!git_reflog_read(&reflog
, ref
)) {
175 regex_error
= regcomp(®ex
, "checkout: moving from (.*) to .*", REG_EXTENDED
);
176 if (regex_error
!= 0) {
177 giterr_set_regex(®ex
, regex_error
);
179 regmatch_t regexmatches
[2];
181 refloglen
= git_reflog_entrycount(reflog
);
182 for (i
=refloglen
-1; i
>= 0; i
--) {
184 entry
= git_reflog_entry_byindex(reflog
, i
);
186 msg
= git_reflog_entry_msg(entry
);
187 if (!regexec(®ex
, msg
, 2, regexmatches
, 0)) {
190 git_buf_put(&buf
, msg
+regexmatches
[1].rm_so
, regexmatches
[1].rm_eo
- regexmatches
[1].rm_so
);
191 retcode
= revparse_lookup_object(out
, repo
, git_buf_cstr(&buf
));
199 git_reference_free(ref
);
203 git_time_t timestamp
;
204 git_buf datebuf
= GIT_BUF_INIT
;
206 git_buf_put(&datebuf
, reflogspec
+2, reflogspeclen
-3);
207 date_error
= git__date_parse(×tamp
, git_buf_cstr(&datebuf
));
209 /* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */
210 if (!strcmp(reflogspec
, "@{u}") || !strcmp(reflogspec
, "@{upstream}")) {
212 if (!git_repository_config(&cfg
, repo
)) {
213 /* Is the ref a tracking branch? */
216 git_buf_printf(&buf
, "branch.%s.remote", refspec
);
217 if (!git_config_get_string(&remote
, cfg
, git_buf_cstr(&buf
))) {
218 /* Yes. Find the first merge target name. */
219 const char *mergetarget
;
221 git_buf_printf(&buf
, "branch.%s.merge", refspec
);
222 if (!git_config_get_string(&mergetarget
, cfg
, git_buf_cstr(&buf
)) &&
223 !git__prefixcmp(mergetarget
, "refs/heads/")) {
224 /* Success. Look up the target and fetch the object. */
226 git_buf_printf(&buf
, "refs/remotes/%s/%s", remote
, mergetarget
+11);
227 retcode
= revparse_lookup_fully_qualifed_ref(out
, repo
, git_buf_cstr(&buf
));
230 git_config_free(cfg
);
234 /* @{N} -> Nth prior value for the ref (from reflog) */
235 else if (all_chars_are_digits(reflogspec
+2, reflogspeclen
-3) &&
236 !git__strtol32(&n
, reflogspec
+2, NULL
, 0) &&
237 n
<= 100000000) { /* Allow integer time */
238 normalize_maybe_empty_refname(&buf
, repo
, refspec
, refspeclen
);
241 retcode
= revparse_lookup_fully_qualifed_ref(out
, repo
, git_buf_cstr(&buf
));
242 } else if (!git_reference_lookup(&ref
, repo
, git_buf_cstr(&buf
))) {
243 if (!git_reflog_read(&reflog
, ref
)) {
244 int numentries
= git_reflog_entrycount(reflog
);
245 if (numentries
< n
) {
246 giterr_set(GITERR_REFERENCE
, "Reflog for '%s' has only %d entries, asked for %d",
247 git_buf_cstr(&buf
), numentries
, n
);
248 retcode
= GIT_ENOTFOUND
;
250 const git_reflog_entry
*entry
= git_reflog_entry_byindex(reflog
, n
);
251 const git_oid
*oid
= git_reflog_entry_oidold(entry
);
252 retcode
= git_object_lookup(out
, repo
, oid
, GIT_OBJ_ANY
);
255 git_reference_free(ref
);
259 else if (!date_error
) {
260 /* Ref as it was on a certain date */
261 normalize_maybe_empty_refname(&buf
, repo
, refspec
, refspeclen
);
263 if (!git_reference_lookup(&ref
, repo
, git_buf_cstr(&buf
))) {
265 if (!git_reflog_read(&reflog
, ref
)) {
266 /* Keep walking until we find an entry older than the given date */
267 int numentries
= git_reflog_entrycount(reflog
);
270 for (i
= numentries
- 1; i
>= 0; i
--) {
271 const git_reflog_entry
*entry
= git_reflog_entry_byindex(reflog
, i
);
272 git_time commit_time
= git_reflog_entry_committer(entry
)->when
;
273 if (commit_time
.time
- timestamp
<= 0) {
274 retcode
= git_object_lookup(out
, repo
, git_reflog_entry_oidnew(entry
), GIT_OBJ_ANY
);
280 /* Didn't find a match */
281 retcode
= GIT_ENOTFOUND
;
284 git_reflog_free(reflog
);
287 git_reference_free(ref
);
291 git_buf_free(&datebuf
);
294 if (reflog
) git_reflog_free(reflog
);
299 static git_object
* dereference_object(git_object
*obj
)
301 git_otype type
= git_object_type(obj
);
306 git_tree
*tree
= NULL
;
307 if (0 == git_commit_tree(&tree
, (git_commit
*)obj
)) {
308 return (git_object
*)tree
;
314 git_object
*newobj
= NULL
;
315 if (0 == git_tag_target(&newobj
, (git_tag
*)obj
)) {
324 case GIT_OBJ_OFS_DELTA
:
325 case GIT_OBJ_REF_DELTA
:
329 /* Can't dereference some types */
333 static int dereference_to_type(git_object
**out
, git_object
*obj
, git_otype target_type
)
336 git_object
*obj1
= obj
, *obj2
= obj
;
338 while (retcode
> 0) {
339 git_otype this_type
= git_object_type(obj1
);
341 if (this_type
== target_type
) {
345 /* Dereference once, if possible. */
346 obj2
= dereference_object(obj1
);
348 giterr_set(GITERR_REFERENCE
, "Can't dereference to type");
352 if (obj1
!= obj
&& obj1
!= obj2
) {
353 git_object_free(obj1
);
360 static git_otype
parse_obj_type(const char *str
)
362 if (!strcmp(str
, "{commit}")) return GIT_OBJ_COMMIT
;
363 if (!strcmp(str
, "{tree}")) return GIT_OBJ_TREE
;
364 if (!strcmp(str
, "{blob}")) return GIT_OBJ_BLOB
;
365 if (!strcmp(str
, "{tag}")) return GIT_OBJ_TAG
;
369 static int handle_caret_syntax(git_object
**out
, git_repository
*repo
, git_object
*obj
, const char *movement
)
372 size_t movementlen
= strlen(movement
);
375 if (*movement
== '{') {
376 if (movement
[movementlen
-1] != '}')
377 return revspec_error(movement
);
379 /* {} -> Dereference until we reach an object that isn't a tag. */
380 if (movementlen
== 2) {
381 git_object
*newobj
= obj
;
382 git_object
*newobj2
= newobj
;
383 while (git_object_type(newobj2
) == GIT_OBJ_TAG
) {
384 newobj2
= dereference_object(newobj
);
385 if (newobj
!= obj
) git_object_free(newobj
);
387 giterr_set(GITERR_REFERENCE
, "Couldn't find object of target type.");
396 /* {/...} -> Walk all commits until we see a commit msg that matches the phrase. */
397 if (movement
[1] == '/') {
398 int retcode
= GIT_ERROR
;
400 if (!git_revwalk_new(&walk
, repo
)) {
404 git_buf buf
= GIT_BUF_INIT
;
406 git_revwalk_sorting(walk
, GIT_SORT_TIME
);
407 git_revwalk_push(walk
, git_object_id(obj
));
409 /* Extract the regex from the movement string */
410 git_buf_put(&buf
, movement
+2, strlen(movement
)-3);
412 reg_error
= regcomp(&preg
, git_buf_cstr(&buf
), REG_EXTENDED
);
413 if (reg_error
!= 0) {
414 giterr_set_regex(&preg
, reg_error
);
416 while(!git_revwalk_next(&oid
, walk
)) {
419 /* Fetch the commit object, and check for matches in the message */
420 if (!git_object_lookup(&walkobj
, repo
, &oid
, GIT_OBJ_COMMIT
)) {
421 if (!regexec(&preg
, git_commit_message((git_commit
*)walkobj
), 0, NULL
, 0)) {
425 if (obj
== walkobj
) {
426 /* Avoid leaking an object */
427 git_object_free(walkobj
);
431 git_object_free(walkobj
);
435 giterr_set(GITERR_REFERENCE
, "Couldn't find a match for %s", movement
);
441 git_revwalk_free(walk
);
446 /* {...} -> Dereference until we reach an object of a certain type. */
447 if (dereference_to_type(out
, obj
, parse_obj_type(movement
)) < 0) {
453 /* Dereference until we reach a commit. */
454 if (dereference_to_type(&obj
, obj
, GIT_OBJ_COMMIT
) < 0) {
455 /* Can't dereference to a commit; fail */
459 /* "^" is the same as "^1" */
460 if (movementlen
== 0) {
463 git__strtol32(&n
, movement
, NULL
, 0);
465 commit
= (git_commit
*)obj
;
467 /* "^0" just returns the input */
473 if (git_commit_parent(&commit
, commit
, n
-1) < 0) {
474 return GIT_ENOTFOUND
;
477 *out
= (git_object
*)commit
;
481 static int handle_linear_syntax(git_object
**out
, git_object
*obj
, const char *movement
)
483 git_commit
*commit1
, *commit2
;
486 /* Dereference until we reach a commit. */
487 if (dereference_to_type(&obj
, obj
, GIT_OBJ_COMMIT
) < 0) {
488 /* Can't dereference to a commit; fail */
492 /* "~" is the same as "~1" */
493 if (*movement
== '\0') {
495 } else if (git__strtol32(&n
, movement
, NULL
, 0) < 0) {
498 commit1
= (git_commit
*)obj
;
500 /* "~0" just returns the input */
506 for (i
=0; i
<n
; i
++) {
507 if (git_commit_parent(&commit2
, commit1
, 0) < 0) {
510 if (commit1
!= (git_commit
*)obj
) {
511 git_commit_free(commit1
);
516 *out
= (git_object
*)commit1
;
520 static int oid_for_tree_path(git_oid
*out
, git_tree
*tree
, git_repository
*repo
, const char *path
)
524 git_tree
*tree2
= tree
;
525 const git_tree_entry
*entry
= NULL
;
529 git_oid_cpy(out
, git_object_id((git_object
*)tree
));
533 alloc
= str
= git__strdup(path
);
535 while ((tok
= git__strtok(&str
, "/\\")) != NULL
) {
536 entry
= git_tree_entry_byname(tree2
, tok
);
537 if (tree2
!= tree
) git_tree_free(tree2
);
542 type
= git_tree_entry_type(entry
);
548 if (git_tree_lookup(&tree2
, repo
, &entry
->oid
) < 0) {
560 /* TODO: support submodules? */
561 giterr_set(GITERR_INVALID
, "Unimplemented");
569 giterr_set(GITERR_INVALID
, "Invalid tree path '%s'", path
);
571 return GIT_ENOTFOUND
;
574 git_oid_cpy(out
, git_tree_entry_id(entry
));
579 static int handle_colon_syntax(git_object
**out
,
580 git_repository
*repo
,
588 /* Dereference until we reach a tree. */
589 if (dereference_to_type(&obj
, obj
, GIT_OBJ_TREE
) < 0) {
592 tree
= (git_tree
*)obj
;
594 /* Find the blob or tree at the given path. */
595 error
= oid_for_tree_path(&oid
, tree
, repo
, path
);
601 return git_object_lookup(out
, repo
, &oid
, GIT_OBJ_ANY
);
604 static int revparse_global_grep(git_object
**out
, git_repository
*repo
, const char *pattern
)
607 int retcode
= GIT_ERROR
;
610 giterr_set(GITERR_REGEX
, "Empty pattern");
614 if (!git_revwalk_new(&walk
, repo
)) {
619 git_revwalk_sorting(walk
, GIT_SORT_TIME
);
620 git_revwalk_push_glob(walk
, "refs/heads/*");
622 reg_error
= regcomp(&preg
, pattern
, REG_EXTENDED
);
623 if (reg_error
!= 0) {
624 giterr_set_regex(&preg
, reg_error
);
626 git_object
*walkobj
= NULL
, *resultobj
= NULL
;
627 while(!git_revwalk_next(&oid
, walk
)) {
628 /* Fetch the commit object, and check for matches in the message */
629 if (walkobj
!= resultobj
) git_object_free(walkobj
);
630 if (!git_object_lookup(&walkobj
, repo
, &oid
, GIT_OBJ_COMMIT
)) {
631 if (!regexec(&preg
, git_commit_message((git_commit
*)walkobj
), 0, NULL
, 0)) {
640 giterr_set(GITERR_REFERENCE
, "Couldn't find a match for %s", pattern
);
641 retcode
= GIT_ENOTFOUND
;
642 git_object_free(walkobj
);
647 git_revwalk_free(walk
);
654 int git_revparse_single(git_object
**out
, git_repository
*repo
, const char *spec
)
656 revparse_state current_state
= REVPARSE_STATE_INIT
, next_state
= REVPARSE_STATE_INIT
;
657 const char *spec_cur
= spec
;
658 git_object
*cur_obj
= NULL
, *next_obj
= NULL
;
659 git_buf specbuffer
= GIT_BUF_INIT
, stepbuffer
= GIT_BUF_INIT
;
662 assert(out
&& repo
&& spec
);
664 if (spec
[0] == ':') {
665 if (spec
[1] == '/') {
666 return revparse_global_grep(out
, repo
, spec
+2);
668 /* TODO: support merge-stage path lookup (":2:Makefile"). */
669 giterr_set(GITERR_INVALID
, "Unimplemented");
673 while (current_state
!= REVPARSE_STATE_DONE
) {
674 switch (current_state
) {
675 case REVPARSE_STATE_INIT
:
677 /* No operators, just a name. Find it and return. */
678 retcode
= revparse_lookup_object(out
, repo
, spec
);
679 next_state
= REVPARSE_STATE_DONE
;
680 } else if (*spec_cur
== '@') {
681 /* '@' syntax doesn't allow chaining */
682 git_buf_puts(&stepbuffer
, spec_cur
);
683 retcode
= walk_ref_history(out
, repo
, git_buf_cstr(&specbuffer
), git_buf_cstr(&stepbuffer
));
684 next_state
= REVPARSE_STATE_DONE
;
685 } else if (*spec_cur
== '^') {
686 next_state
= REVPARSE_STATE_CARET
;
687 } else if (*spec_cur
== '~') {
688 next_state
= REVPARSE_STATE_LINEAR
;
689 } else if (*spec_cur
== ':') {
690 next_state
= REVPARSE_STATE_COLON
;
692 git_buf_putc(&specbuffer
, *spec_cur
);
696 if (current_state
!= next_state
&& next_state
!= REVPARSE_STATE_DONE
) {
697 /* Leaving INIT state, find the object specified, in case that state needs it */
698 if (revparse_lookup_object(&next_obj
, repo
, git_buf_cstr(&specbuffer
)) < 0) {
700 next_state
= REVPARSE_STATE_DONE
;
706 case REVPARSE_STATE_CARET
:
707 /* Gather characters until NULL, '~', or '^' */
709 retcode
= handle_caret_syntax(out
, repo
, cur_obj
, git_buf_cstr(&stepbuffer
));
710 next_state
= REVPARSE_STATE_DONE
;
711 } else if (*spec_cur
== '~') {
712 retcode
= handle_caret_syntax(&next_obj
, repo
, cur_obj
, git_buf_cstr(&stepbuffer
));
713 git_buf_clear(&stepbuffer
);
714 next_state
= !retcode
? REVPARSE_STATE_LINEAR
: REVPARSE_STATE_DONE
;
715 } else if (*spec_cur
== '^') {
716 retcode
= handle_caret_syntax(&next_obj
, repo
, cur_obj
, git_buf_cstr(&stepbuffer
));
717 git_buf_clear(&stepbuffer
);
719 next_state
= REVPARSE_STATE_DONE
;
721 } else if (*spec_cur
== ':') {
722 retcode
= handle_caret_syntax(&next_obj
, repo
, cur_obj
, git_buf_cstr(&stepbuffer
));
723 git_buf_clear(&stepbuffer
);
724 next_state
= !retcode
? REVPARSE_STATE_COLON
: REVPARSE_STATE_DONE
;
726 git_buf_putc(&stepbuffer
, *spec_cur
);
731 case REVPARSE_STATE_LINEAR
:
733 retcode
= handle_linear_syntax(out
, cur_obj
, git_buf_cstr(&stepbuffer
));
734 next_state
= REVPARSE_STATE_DONE
;
735 } else if (*spec_cur
== '~') {
736 retcode
= handle_linear_syntax(&next_obj
, cur_obj
, git_buf_cstr(&stepbuffer
));
737 git_buf_clear(&stepbuffer
);
739 next_state
= REVPARSE_STATE_DONE
;
741 } else if (*spec_cur
== '^') {
742 retcode
= handle_linear_syntax(&next_obj
, cur_obj
, git_buf_cstr(&stepbuffer
));
743 git_buf_clear(&stepbuffer
);
744 next_state
= !retcode
? REVPARSE_STATE_CARET
: REVPARSE_STATE_DONE
;
746 git_buf_putc(&stepbuffer
, *spec_cur
);
751 case REVPARSE_STATE_COLON
:
753 git_buf_putc(&stepbuffer
, *spec_cur
);
755 retcode
= handle_colon_syntax(out
, repo
, cur_obj
, git_buf_cstr(&stepbuffer
));
756 next_state
= REVPARSE_STATE_DONE
;
761 case REVPARSE_STATE_DONE
:
762 if (cur_obj
&& *out
!= cur_obj
) git_object_free(cur_obj
);
763 if (next_obj
&& *out
!= next_obj
) git_object_free(next_obj
);
767 current_state
= next_state
;
768 if (cur_obj
!= next_obj
) {
769 if (cur_obj
) git_object_free(cur_obj
);
774 if (*out
!= cur_obj
) git_object_free(cur_obj
);
775 if (*out
!= next_obj
&& next_obj
!= cur_obj
) git_object_free(next_obj
);
777 git_buf_free(&specbuffer
);
778 git_buf_free(&stepbuffer
);