diff --git a/src/git/tree.h b/src/git/tree.h index 95b233009..bd589ea11 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 15603e7a9..0373f3c22 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; - - buffer += 5; - - tree->byte_size = strtol(buffer, &buffer, 10); - - if (*buffer++ != 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)); while (buffer < buffer_end) { git_tree_entry *entry; - entry = git__malloc(sizeof(git_tree_entry)); - entry->next = tree->entries; + if (tree->entry_count >= entries_size) { + git_tree_entry *new_entries; - entry->attr = strtol(buffer, &buffer, 10); + entries_size = entries_size * 2; + + new_entries = git__malloc(entries_size * sizeof(git_tree_entry)); + memcpy(new_entries, tree->entries, tree->entry_count * sizeof(git_tree_entry)); + + free(tree->entries); + tree->entries = new_entries; + } + + 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 f21668586..2222b0349 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 000000000..307bbea12 --- /dev/null +++ b/tests/t0901-readtree.c @@ -0,0 +1,42 @@ +#include "test_lib.h" +#include "test_helpers.h" +#include "commit.h" + +#include +#include +#include + +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