From db9be9457d74a683916f107b39cad05a347b4c2c Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 15 Jul 2012 11:06:15 +0200 Subject: [PATCH] object: introduce git_object_peel() Partially fix #530 --- include/git2/object.h | 17 +++++++++ src/object.c | 69 +++++++++++++++++++++++++++++++++++ tests-clar/object/peel.c | 79 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 tests-clar/object/peel.c diff --git a/include/git2/object.h b/include/git2/object.h index 414325121..d9e653fd4 100644 --- a/include/git2/object.h +++ b/include/git2/object.h @@ -167,6 +167,23 @@ GIT_EXTERN(int) git_object_typeisloose(git_otype type); */ GIT_EXTERN(size_t) git_object__size(git_otype type); +/** + * Recursively peel an object until an object of the specified + * type is met + * + * The retrieved `peeled` object is owned by the repository + * and should be closed with the `git_object_free` method. + * + * @param peeled Pointer to the peeled git_object + * @param object The object to be processed + * @param target_type The type of the requested object + * @return 0 or an error code + */ +GIT_EXTERN(int) git_object_peel( + git_object **peeled, + git_object *object, + git_otype target_type); + /** @} */ GIT_END_DECL diff --git a/src/object.c b/src/object.c index 14d64befe..3ff894212 100644 --- a/src/object.c +++ b/src/object.c @@ -333,3 +333,72 @@ int git_object__resolve_to_type(git_object **obj, git_otype type) *obj = scan; return error; } + +static int dereference_object(git_object **dereferenced, git_object *obj) +{ + git_otype type = git_object_type(obj); + + switch (type) { + case GIT_OBJ_COMMIT: + return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); + break; + + case GIT_OBJ_TAG: + return git_tag_target(dereferenced, (git_tag*)obj); + break; + + default: + return GIT_ENOTFOUND; + break; + } +} + +static int peel_error(int error, const char* msg) +{ + giterr_set(GITERR_INVALID, "The given object cannot be peeled - %s", msg); + return error; +} + +int git_object_peel( + git_object **peeled, + git_object *object, + git_otype target_type) +{ + git_object *source, *deref = NULL; + + assert(object); + + if (git_object_type(object) == target_type) + return git_object__dup(peeled, object); + + if (target_type == GIT_OBJ_BLOB + || target_type == GIT_OBJ_ANY) + return peel_error(GIT_EAMBIGUOUS, "Ambiguous target type"); + + if (git_object_type(object) == GIT_OBJ_BLOB) + return peel_error(GIT_ERROR, "A blob cannot be dereferenced"); + + source = object; + + while (true) { + if (dereference_object(&deref, source) < 0) + goto cleanup; + + if (source != object) + git_object_free(source); + + if (git_object_type(deref) == target_type) { + *peeled = deref; + return 0; + } + + source = deref; + deref = NULL; + } + +cleanup: + if (source != object) + git_object_free(source); + git_object_free(deref); + return -1; +} diff --git a/tests-clar/object/peel.c b/tests-clar/object/peel.c new file mode 100644 index 000000000..f6d2a776f --- /dev/null +++ b/tests-clar/object/peel.c @@ -0,0 +1,79 @@ +#include "clar_libgit2.h" + +static git_repository *g_repo; + +void test_object_peel__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); +} + +void test_object_peel__cleanup(void) +{ + git_repository_free(g_repo); +} + +static void assert_peel(const char* expected_sha, const char *sha, git_otype requested_type) +{ + git_oid oid, expected_oid; + git_object *obj; + git_object *peeled; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_object_peel(&peeled, obj, requested_type)); + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); + cl_assert_equal_i(0, git_oid_cmp(&expected_oid, git_object_id(peeled))); + + git_object_free(peeled); + git_object_free(obj); +} + +static void assert_peel_error(int error, const char *sha, git_otype requested_type) +{ + git_oid oid; + git_object *obj; + git_object *peeled; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_assert_equal_i(error, git_object_peel(&peeled, obj, requested_type)); + + git_object_free(obj); +} + +void test_object_peel__peeling_an_object_into_its_own_type_returns_another_instance_of_it(void) +{ + assert_peel("e90810b8df3e80c413d903f631643c716887138d", "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG); + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); + assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB); +} + +void test_object_peel__can_peel_a_tag(void) +{ + assert_peel("e90810b8df3e80c413d903f631643c716887138d", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_COMMIT); + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TREE); +} + +void test_object_peel__can_peel_a_commit(void) +{ + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_TREE); +} + +void test_object_peel__cannot_peel_a_tree(void) +{ + assert_peel_error(GIT_EAMBIGUOUS, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_BLOB); +} + +void test_object_peel__cannot_peel_a_blob(void) +{ + assert_peel_error(GIT_ERROR, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_COMMIT); +} + +void test_object_peel__cannot_target_any_object(void) +{ + assert_peel_error(GIT_EAMBIGUOUS, "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_ANY); +}