Commit b1aca6eae084ebe59c5a092314e94019c59ecec6

nulltoken 2012-07-11T16:14:12

commit: introduce git_commit_nth_gen_ancestor()

diff --git a/include/git2/commit.h b/include/git2/commit.h
index 640adf5..5b6da52 100644
--- a/include/git2/commit.h
+++ b/include/git2/commit.h
@@ -179,6 +179,25 @@ GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsig
 GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned int n);
 
 /**
+ * Get the commit object that is the <n>th generation ancestor
+ * of the named commit object, following only the first parents.
+ * The returned commit has to be freed by the caller.
+ *
+ * Passing `0` as the generation number returns another instance of the
+ * base commit itself.
+ *
+ * @param ancestor Pointer where to store the ancestor commit
+ * @param commit a previously loaded commit.
+ * @param n the requested generation
+ * @return 0 on success; GIT_ENOTFOUND if no matching ancestor exists
+ * or an error code
+ */
+int git_commit_nth_gen_ancestor(
+	git_commit **ancestor,
+	const git_commit *commit,
+	unsigned int n);
+
+/**
  * Create a new commit in the repository using `git_object`
  * instances as parameters.
  *
diff --git a/src/commit.c b/src/commit.c
index 5acbbc3..32c4794 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -255,3 +255,37 @@ int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
 
 	return git_commit_lookup(parent, commit->object.repo, parent_oid);
 }
+
+int git_commit_nth_gen_ancestor(
+	git_commit **ancestor,
+	const git_commit *commit,
+	unsigned int n)
+{
+	git_commit *current, *parent;
+	int error;
+
+	assert(ancestor && commit);
+
+	current = (git_commit *)commit;
+
+	if (n == 0)
+		return git_commit_lookup(
+			ancestor,
+			commit->object.repo,
+			git_object_id((const git_object *)commit));
+
+	while (n--) {
+		error = git_commit_parent(&parent, (git_commit *)current, 0);
+
+		if (current != commit)
+			git_commit_free(current);
+
+		if (error < 0)
+			return error;
+
+		current = parent;
+	}
+
+	*ancestor = parent;
+	return 0;
+}
diff --git a/tests-clar/commit/parent.c b/tests-clar/commit/parent.c
new file mode 100644
index 0000000..a007577
--- /dev/null
+++ b/tests-clar/commit/parent.c
@@ -0,0 +1,57 @@
+#include "clar_libgit2.h"
+
+static git_repository *_repo;
+static git_commit *commit;
+
+void test_commit_parent__initialize(void)
+{
+	git_oid oid;
+
+	cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+
+	git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+	cl_git_pass(git_commit_lookup(&commit, _repo, &oid));
+}
+
+void test_commit_parent__cleanup(void)
+{
+	git_commit_free(commit);
+	git_repository_free(_repo);
+}
+
+static void assert_nth_gen_parent(unsigned int gen, const char *expected_oid)
+{
+	git_commit *parent = NULL;
+	int error;
+	
+	error = git_commit_nth_gen_ancestor(&parent, commit, gen);
+
+	if (expected_oid != NULL) {
+		cl_assert_equal_i(0, error);
+		cl_assert_equal_i(0, git_oid_streq(git_commit_id(parent), expected_oid));
+	} else
+		cl_assert_equal_i(GIT_ENOTFOUND, error);
+
+	git_commit_free(parent);
+}
+
+/*
+ * $ git show be35~0
+ * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644
+ *
+ * $ git show be35~1
+ * commit 9fd738e8f7967c078dceed8190330fc8648ee56a
+ *
+ * $ git show be35~3
+ * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644
+ *
+ * $ git show be35~42
+ * fatal: ambiguous argument 'be35~42': unknown revision or path not in the working tree.
+ */
+void test_commit_parent__can_retrieve_nth_generation_parent(void)
+{
+	assert_nth_gen_parent(0, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+	assert_nth_gen_parent(1, "9fd738e8f7967c078dceed8190330fc8648ee56a");
+	assert_nth_gen_parent(3, "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+	assert_nth_gen_parent(42, NULL);
+}