Commit 003c26909451715cf36ea37ae4a6237813b4a25e

Vicent Marti 2010-08-12T18:45:31

Finish the tree object API The interface for loading and parsing tree objects from a repository has been completed with all the required accesor methods for attributes, support for manipulating individual tree entries and a new unit test t0901-readtree which tries to load and parse a tree object from a repository. Signed-off-by: Vicent Marti <tanoku@gmail.com>

diff --git a/src/git/tree.h b/src/git/tree.h
index 95b2330..bd589ea 100644
--- a/src/git/tree.h
+++ b/src/git/tree.h
@@ -14,6 +14,10 @@
  */
 GIT_BEGIN_DECL
 
+
+/** Representation of each one of the entries in a tree object. */
+typedef struct git_tree_entry git_tree_entry;
+
 /** Representation of a tree object. */
 typedef struct git_tree git_tree;
 
@@ -35,6 +39,58 @@ GIT_EXTERN(git_tree *) git_tree_lookup(git_repository *repo, const git_oid *id);
  */
 GIT_EXTERN(const git_oid *) git_tree_id(git_tree *tree);
 
+
+/**
+ * Get the number of entries listed in a tree
+ * @param tree a previously loaded tree.
+ * @return the number of entries in the tree
+ */
+GIT_EXTERN(size_t) git_tree_entrycount(git_tree *tree);
+
+/**
+ * Lookup a tree entry by its filename
+ * @param tree a previously loaded tree.
+ * @param filename the filename of the desired entry
+ * @return the tree entry; NULL if not found
+ */
+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byname(git_tree *tree, const char *filename);
+
+/**
+ * Lookup a tree entry by its position in the tree
+ * @param tree a previously loaded tree.
+ * @param idx the position in the entry list
+ * @return the tree entry; NULL if not found
+ */
+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(git_tree *tree, int idx);
+
+/**
+ * Get the UNIX file attributes of a tree entry
+ * @param entry a tree entry
+ * @return attributes as an integer
+ */
+GIT_EXTERN(uint32_t) git_tree_entry_attributes(const git_tree_entry *entry);
+
+/**
+ * Get the filename of a tree entry
+ * @param entry a tree entry
+ * @return the name of the file
+ */
+GIT_EXTERN(const char *) git_tree_entry_name(const git_tree_entry *entry);
+
+/**
+ * Get the id of the object pointed by the entry
+ * @param entry a tree entry
+ * @return the oid of the object
+ */
+GIT_EXTERN(const git_oid *) git_tree_entry_id(const git_tree_entry *entry);
+
+/**
+ * Convert a tree entry to the git_repository_object it points too.
+ * @param entry a tree entry
+ * @return a reference to the pointed object in the repository
+ */
+GIT_EXTERN(git_repository_object *) git_tree_entry_2object(const git_tree_entry *entry);
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/src/tree.c b/src/tree.c
index 15603e7..0373f3c 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -31,6 +31,12 @@
 
 void git_tree__free(git_tree *tree)
 {
+	size_t i;
+
+	for (i = 0; i < tree->entry_count; ++i)
+		free(tree->entries[i].filename);
+
+	free(tree->entries);
 	free(tree);
 }
 
@@ -44,13 +50,67 @@ git_tree *git_tree_lookup(git_repository *repo, const git_oid *id)
 	return (git_tree *)git_repository_lookup(repo, id, GIT_OBJ_TREE);
 }
 
+uint32_t git_tree_entry_attributes(const git_tree_entry *entry)
+{
+	return entry->attr;
+}
+
+const char *git_tree_entry_name(const git_tree_entry *entry)
+{
+	return entry->filename;
+}
+
+const git_oid *git_tree_entry_id(const git_tree_entry *entry)
+{
+	return &entry->oid;
+}
+
+git_repository_object *git_tree_entry_2object(const git_tree_entry *entry)
+{
+	return git_repository_lookup(entry->owner->object.repo, &entry->oid, GIT_OBJ_ANY);
+}
+
+int entry_cmp(const void *key, const void *array_member)
+{
+	const char *filename = (const char *)key;
+	const git_tree_entry *entry = (const git_tree_entry *)array_member;
+
+	return strcmp(filename, entry->filename);
+}
+
+const git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename)
+{
+	if (tree->entries == NULL)
+		git_tree__parse(tree);
+
+	return bsearch(filename, tree->entries, tree->entry_count, sizeof(git_tree_entry), entry_cmp);
+}
+
+const git_tree_entry *git_tree_entry_byindex(git_tree *tree, int idx)
+{
+	if (tree->entries == NULL)
+		git_tree__parse(tree);
+
+	return (tree->entries && idx >= 0 && idx < (int)tree->entry_count) ? 
+		&tree->entries[idx] : NULL;
+}
+
+size_t git_tree_entrycount(git_tree *tree)
+{
+	return tree->entry_count;
+}
+
 int git_tree__parse(git_tree *tree)
 {
-	static const char tree_header[] = {'t', 'r', 'e', 'e', ' '};
+	static const size_t avg_entry_size = 40;
 
 	int error = 0;
 	git_obj odb_object;
 	char *buffer, *buffer_end;
+	size_t entries_size;
+
+	if (tree->entries != NULL)
+		return GIT_SUCCESS;
 
 	error = git_odb_read(&odb_object, tree->object.repo->db, &tree->object.id);
 	if (error < 0)
@@ -59,23 +119,29 @@ int git_tree__parse(git_tree *tree)
 	buffer = odb_object.data;
 	buffer_end = odb_object.data + odb_object.len;
 
-	if (memcmp(buffer, tree_header, 5) != 0)
-		return GIT_EOBJCORRUPTED;
+	tree->entry_count = 0;
+	entries_size = (odb_object.len / avg_entry_size) + 1;
+	tree->entries = git__malloc(entries_size * sizeof(git_tree_entry));
 
-	buffer += 5;
+	while (buffer < buffer_end) {
+		git_tree_entry *entry;
 
-	tree->byte_size = strtol(buffer, &buffer, 10);
+		if (tree->entry_count >= entries_size) {
+			git_tree_entry *new_entries;
 
-	if (*buffer++ != 0)
-		return GIT_EOBJCORRUPTED;
+			entries_size = entries_size * 2;
 
-	while (buffer < buffer_end) {
-		git_tree_entry *entry;
+			new_entries = git__malloc(entries_size * sizeof(git_tree_entry));
+			memcpy(new_entries, tree->entries, tree->entry_count * sizeof(git_tree_entry));
 
-		entry = git__malloc(sizeof(git_tree_entry));
-		entry->next = tree->entries;
+			free(tree->entries);
+			tree->entries = new_entries;
+		}
 
-		entry->attr = strtol(buffer, &buffer, 10);
+		entry = &tree->entries[tree->entry_count++];
+		entry->owner = tree;
+
+		entry->attr = strtol(buffer, &buffer, 8);
 
 		if (*buffer++ != ' ') {
 			error = GIT_EOBJCORRUPTED;
@@ -86,15 +152,14 @@ int git_tree__parse(git_tree *tree)
 
 		if (entry->filename == NULL) {
 			error = GIT_EOBJCORRUPTED;
-			break;
 		}
-		buffer += strlen(entry->filename);
+
+		buffer += strlen(entry->filename) + 1;
 
 		git_oid_mkraw(&entry->oid, (const unsigned char *)buffer);
 		buffer += GIT_OID_RAWSZ;
-
-		tree->entries = entry;
 	}
 
+	git_obj_close(&odb_object);
 	return error;
 }
diff --git a/src/tree.h b/src/tree.h
index f216685..2222b03 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -5,22 +5,18 @@
 #include "repository.h"
 
 struct git_tree_entry {
-
-	unsigned int attr;
+	uint32_t attr;
 	char *filename;
 	git_oid oid;
 
-	struct git_tree_entry *next;
+	git_tree *owner;
 };
 
-typedef struct git_tree_entry git_tree_entry;
-
 struct git_tree {
 	git_repository_object object;
 
-	size_t byte_size;
 	git_tree_entry *entries;
-	unsigned int entry_count;
+	size_t entry_count;
 };
 
 void git_tree__free(git_tree *tree);
diff --git a/tests/t0901-readtree.c b/tests/t0901-readtree.c
new file mode 100644
index 0000000..307bbea
--- /dev/null
+++ b/tests/t0901-readtree.c
@@ -0,0 +1,42 @@
+#include "test_lib.h"
+#include "test_helpers.h"
+#include "commit.h"
+
+#include <git/odb.h>
+#include <git/commit.h>
+#include <git/revwalk.h>
+
+static const char *odb_dir = "../resources/sample-odb";
+static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd";
+
+BEGIN_TEST(tree_read_test)
+	git_odb *db;
+	git_oid id;
+	git_repository *repo;
+	git_tree *tree;
+	const git_tree_entry *entry;
+
+	must_pass(git_odb_open(&db, odb_dir));
+
+	repo = git_repository_alloc(db);
+	must_be_true(repo != NULL);
+
+	git_oid_mkstr(&id, tree_oid);
+
+	tree = git_tree_lookup(repo, &id);
+	must_be_true(tree != NULL);
+
+	must_pass(git_tree__parse(tree));
+
+	must_be_true(git_tree_entrycount(tree) == 3);
+
+	entry = git_tree_entry_byname(tree, "README");
+	must_be_true(entry != NULL);
+
+	must_be_true(strcmp(git_tree_entry_name(entry), "README") == 0);
+
+	must_be_true(git_tree_entry_2object(entry) != NULL);
+
+	git_repository_free(repo);
+	git_odb_close(db);
+END_TEST