diff --git a/src/revparse.c b/src/revparse.c index 3615ac519..1e78d7623 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -10,6 +10,7 @@ #include "common.h" #include "buffer.h" #include "date.h" +#include "tree.h" #include "git2.h" @@ -19,6 +20,7 @@ typedef enum { REVPARSE_STATE_INIT, REVPARSE_STATE_CARET, REVPARSE_STATE_LINEAR, + REVPARSE_STATE_COLON, REVPARSE_STATE_DONE, } revparse_state; @@ -535,6 +537,45 @@ static int handle_linear_syntax(git_object **out, git_object *obj, const char *m return 0; } +static const git_tree_entry* git_tree_entry_bypath(git_tree *tree, git_repository *repo, const char *path) +{ + char *str = git__strdup(path); + char *tok; + git_tree *tree2 = tree; + const git_tree_entry *entry; + + while ((tok = git__strtok(&str, "/\\")) != NULL) { + entry = git_tree_entry_byname(tree2, tok); + if (tree2 != tree) git_tree_free(tree2); + if (entry_is_tree(entry)) { + if (git_tree_lookup(&tree2, repo, &entry->oid) < 0) { + return NULL; + } + } + } + + return entry; +} + +static int handle_colon_syntax(git_object **out, + git_repository *repo, + git_object *obj, + const char *path) +{ + git_tree *tree; + const git_tree_entry *entry; + + /* Dereference until we reach a tree. */ + if (dereference_to_type(&obj, obj, GIT_OBJ_TREE) < 0) { + return GIT_ERROR; + } + tree = (git_tree*)obj; + + /* Find the blob at the given path. */ + entry = git_tree_entry_bypath(tree, repo, path); + return git_tree_entry_2object(out, repo, entry); +} + int git_revparse_single(git_object **out, git_repository *repo, const char *spec) { revparse_state current_state = REVPARSE_STATE_INIT, next_state = REVPARSE_STATE_INIT; @@ -545,6 +586,13 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec assert(out && repo && spec); + if (spec[0] == ':') { + /* Either a global grep (":/foo") or a merge-stage path lookup (":2:Makefile"). + Neither of these are handled just yet. */ + giterr_set(GITERR_INVALID, "Unimplemented"); + return GIT_ERROR; + } + while (current_state != REVPARSE_STATE_DONE) { switch (current_state) { case REVPARSE_STATE_INIT: @@ -561,6 +609,8 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec next_state = REVPARSE_STATE_CARET; } else if (*spec_cur == '~') { next_state = REVPARSE_STATE_LINEAR; + } else if (*spec_cur == ':') { + next_state = REVPARSE_STATE_COLON; } else { git_buf_putc(&specbuffer, *spec_cur); } @@ -617,6 +667,16 @@ int git_revparse_single(git_object **out, git_repository *repo, const char *spec spec_cur++; break; + case REVPARSE_STATE_COLON: + if (*spec_cur) { + git_buf_putc(&stepbuffer, *spec_cur); + } else { + retcode = handle_colon_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); + next_state = REVPARSE_STATE_DONE; + } + spec_cur++; + break; + case REVPARSE_STATE_DONE: if (cur_obj && *out != cur_obj) git_object_free(cur_obj); if (next_obj && *out != next_obj) git_object_free(next_obj); diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c index 783b03b3a..2da857415 100644 --- a/tests-clar/refs/revparse.c +++ b/tests-clar/refs/revparse.c @@ -153,3 +153,13 @@ void test_refs_revparse__date(void) /* Core git gives a65fedf, because they don't take time zones into account. */ test_object("master@{1335806640}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); } + +void test_refs_revparse__colon(void) +{ + cl_git_fail(git_revparse_single(&g_obj, g_repo, ":/foo")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, ":2:README")); + + test_object("subtrees:ab/4.txt", "d6c93164c249c8000205dd4ec5cbca1b516d487f"); + test_object("master:README", "a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + test_object("master:new.txt", "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"); +}