Commit db9be9457d74a683916f107b39cad05a347b4c2c

nulltoken 2012-07-15T11:06:15

object: introduce git_object_peel() Partially fix #530

diff --git a/include/git2/object.h b/include/git2/object.h
index 4143251..d9e653f 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 14d64be..3ff8942 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 0000000..f6d2a77
--- /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);
+}