From 2a884588b405c4dee78494119a123fb1878f3490 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 21 Sep 2010 17:17:10 +0300 Subject: [PATCH] Add write-back support for git_tree All the setter methods for git_tree have been added, including the setters for attributes on each git_tree_entry and methods to add/remove entries of the tree. Modified trees and trees created in-memory from scratch can be written back to the repository using git_object_write(). Signed-off-by: Vicent Marti --- src/git/tree.h | 84 +++++++++++++++-- src/repository.c | 3 + src/tree.c | 207 +++++++++++++++++++++++++++++++++-------- src/tree.h | 8 +- tests/t0901-readtree.c | 3 +- 5 files changed, 258 insertions(+), 47 deletions(-) diff --git a/src/git/tree.h b/src/git/tree.h index 32fd6527a..bec49f245 100644 --- a/src/git/tree.h +++ b/src/git/tree.h @@ -65,7 +65,7 @@ GIT_EXTERN(size_t) git_tree_entrycount(git_tree *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); +GIT_EXTERN(git_tree_entry *) git_tree_entry_byname(git_tree *tree, const char *filename); /** * Lookup a tree entry by its position in the tree @@ -73,35 +73,107 @@ GIT_EXTERN(const git_tree_entry *) git_tree_entry_byname(git_tree *tree, const c * @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); +GIT_EXTERN(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(unsigned int) git_tree_entry_attributes(const git_tree_entry *entry); +GIT_EXTERN(unsigned int) git_tree_entry_attributes(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); +GIT_EXTERN(const char *) git_tree_entry_name(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); +GIT_EXTERN(const git_oid *) git_tree_entry_id(git_tree_entry *entry); /** * Convert a tree entry to the git_object it points too. * @param entry a tree entry * @return a reference to the pointed object in the repository */ -GIT_EXTERN(git_object *) git_tree_entry_2object(const git_tree_entry *entry); +GIT_EXTERN(git_object *) git_tree_entry_2object(git_tree_entry *entry); + +/** + * Add a new entry to a tree. + * + * This will mark the tree as modified; the new entry will + * be written back to disk on the next git_object_write() + * + * @param tree Tree object to store the entry + * @iparam id OID for the tree entry + * @param filename Filename for the tree entry + * @param attributes UNIX file attributes for the entry + */ +GIT_EXTERN(void) git_tree_add_entry(git_tree *tree, const git_oid *id, const char *filename, int attributes); + +/** + * Remove an entry by its index. + * + * Index must be >= 0 and < than git_tree_entrycount(). + * + * This will mark the tree as modified; the modified entry will + * be written back to disk on the next git_object_write() + * + * @param tree Tree where to remove the entry + * @param idx index of the entry + * @return 0 on successful removal; GIT_ENOTFOUND if the entry wasn't found + */ +GIT_EXTERN(int) git_tree_remove_entry_byindex(git_tree *tree, int idx); + +/** + * Remove an entry by its filename. + * + * This will mark the tree as modified; the modified entry will + * be written back to disk on the next git_object_write() + * + * @param tree Tree where to remove the entry + * @param filename File name of the entry + * @return 0 on successful removal; GIT_ENOTFOUND if the entry wasn't found + */ +GIT_EXTERN(int) git_tree_remove_entry_byname(git_tree *tree, const char *filename); + +/** + * Change the SHA1 id of a tree entry. + * + * This will mark the tree that contains the entry as modified; + * the modified entry will be written back to disk on the next git_object_write() + * + * @param entry Entry object which will be modified + * @param oid new SHA1 oid for the entry + */ +GIT_EXTERN(void) git_tree_entry_set_id(git_tree_entry *entry, const git_oid *oid); + +/** + * Change the filename of a tree entry. + * + * This will mark the tree that contains the entry as modified; + * the modified entry will be written back to disk on the next git_object_write() + * + * @param entry Entry object which will be modified + * @param oid new filename for the entry + */ +GIT_EXTERN(void) git_tree_entry_set_name(git_tree_entry *entry, const char *name); + +/** + * Change the attributes of a tree entry. + * + * This will mark the tree that contains the entry as modified; + * the modified entry will be written back to disk on the next git_object_write() + * + * @param entry Entry object which will be modified + * @param oid new attributes for the entry + */ +GIT_EXTERN(void) git_tree_entry_set_attributes(git_tree_entry *entry, int attr); /** @} */ GIT_END_DECL diff --git a/src/repository.c b/src/repository.c index dc1fe0e9d..22f2bba40 100644 --- a/src/repository.c +++ b/src/repository.c @@ -270,6 +270,9 @@ int git_object_write(git_object *object) break; case GIT_OBJ_TREE: + error = git_tree__writeback((git_tree *)object, source); + break; + case GIT_OBJ_TAG: default: error = GIT_ERROR; diff --git a/src/tree.c b/src/tree.c index 670f99838..43a50423d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -29,12 +29,49 @@ #include "tree.h" #include "git/repository.h" +static void resize_tree_array(git_tree *tree) +{ + git_tree_entry **new_entries; + + tree->array_size = tree->array_size * 2; + + new_entries = git__malloc(tree->array_size * sizeof(git_tree_entry *)); + memcpy(new_entries, tree->entries, tree->entry_count * sizeof(git_tree_entry *)); + + free(tree->entries); + tree->entries = new_entries; +} + +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); +} + +int entry_sort_cmp(const void *a, const void *b) +{ + const git_tree_entry *entry_a = *(const git_tree_entry **)(a); + const git_tree_entry *entry_b = *(const git_tree_entry **)(b); + + return strcmp(entry_a->filename, entry_b->filename); +} + +static void entry_resort(git_tree *tree) +{ + qsort(tree->entries, tree->entry_count, sizeof(git_tree_entry *), entry_sort_cmp); +} + + + + 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[i]); free(tree->entries); free(tree); @@ -55,45 +92,62 @@ git_tree *git_tree_lookup(git_repository *repo, const git_oid *id) return (git_tree *)git_repository_lookup(repo, id, GIT_OBJ_TREE); } -unsigned int git_tree_entry_attributes(const git_tree_entry *entry) +void git_tree_entry_set_attributes(git_tree_entry *entry, int attr) +{ + assert(entry && entry->owner); + + entry->attr = attr; + entry->owner->object.modified = 1; +} + +void git_tree_entry_set_name(git_tree_entry *entry, const char *name) +{ + assert(entry && entry->owner); + + strncpy(entry->filename, name, GIT_TREE_MAX_FILENAME); + entry_resort(entry->owner); + entry->owner->object.modified = 1; +} + +void git_tree_entry_set_id(git_tree_entry *entry, const git_oid *oid) +{ + assert(entry && entry->owner); + + git_oid_cpy(&entry->oid, oid); + entry->owner->object.modified = 1; +} + +unsigned int git_tree_entry_attributes(git_tree_entry *entry) { return entry->attr; } -const char *git_tree_entry_name(const git_tree_entry *entry) +const char *git_tree_entry_name(git_tree_entry *entry) { return entry->filename; } -const git_oid *git_tree_entry_id(const git_tree_entry *entry) +const git_oid *git_tree_entry_id(git_tree_entry *entry) { return &entry->oid; } -git_object *git_tree_entry_2object(const git_tree_entry *entry) +git_object *git_tree_entry_2object(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) +git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename) { - const char *filename = (const char *)key; - const git_tree_entry *entry = (const git_tree_entry *)array_member; - - return strcmp(filename, entry->filename); + return *(git_tree_entry **)bsearch(filename, tree->entries, tree->entry_count, sizeof(git_tree_entry *), entry_cmp); } -const git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename) -{ - 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) +git_tree_entry *git_tree_entry_byindex(git_tree *tree, int idx) { if (tree->entries == NULL) return NULL; - return (idx >= 0 && idx < (int)tree->entry_count) ? &tree->entries[idx] : NULL; + return (idx >= 0 && idx < (int)tree->entry_count) ? tree->entries[idx] : NULL; } size_t git_tree_entrycount(git_tree *tree) @@ -101,16 +155,97 @@ size_t git_tree_entrycount(git_tree *tree) return tree->entry_count; } +void git_tree_add_entry(git_tree *tree, const git_oid *id, const char *filename, int attributes) +{ + git_tree_entry *entry; + + if (tree->entry_count >= tree->array_size) + resize_tree_array(tree); + + if ((entry = git__malloc(sizeof(git_tree_entry))) == NULL) + return; + + memset(entry, 0x0, sizeof(git_tree_entry)); + + strncpy(entry->filename, filename, GIT_TREE_MAX_FILENAME); + git_oid_cpy(&entry->oid, id); + entry->attr = attributes; + entry->owner = tree; + + tree->entries[tree->entry_count++] = entry; + entry_resort(tree); + + tree->object.modified = 1; +} + +int git_tree_remove_entry_byindex(git_tree *tree, int idx) +{ + git_tree_entry *remove_ptr; + + if (idx < 0 || idx >= (int)tree->entry_count) + return GIT_ENOTFOUND; + + remove_ptr = tree->entries[idx]; + tree->entries[idx] = tree->entries[--tree->entry_count]; + + free(remove_ptr); + entry_resort(tree); + + tree->object.modified = 1; + return GIT_SUCCESS; +} + +int git_tree_remove_entry_byname(git_tree *tree, const char *filename) +{ + git_tree_entry **entry_ptr; + int idx; + + entry_ptr = bsearch(filename, tree->entries, tree->entry_count, sizeof(git_tree_entry *), entry_cmp); + if (entry_ptr == NULL) + return GIT_ENOTFOUND; + + idx = (int)(entry_ptr - tree->entries); + return git_tree_remove_entry_byindex(tree, idx); +} + +int git_tree__writeback(git_tree *tree, git_odb_source *src) +{ + size_t i; + + if (tree->entries == NULL) + return GIT_ERROR; + + entry_resort(tree); + + for (i = 0; i < tree->entry_count; ++i) { + git_tree_entry *entry; + entry = tree->entries[i]; + + git__source_printf(src, "%06o %s\0", entry->attr, entry->filename); + git__source_write(src, entry->oid.id, GIT_OID_RAWSZ); + } + + return GIT_SUCCESS; +} + + int git_tree__parse(git_tree *tree) { static const size_t avg_entry_size = 40; int error = 0; char *buffer, *buffer_end; - size_t entries_size; - if (tree->entries != NULL) - return GIT_SUCCESS; + assert(!tree->object.in_memory); + + if (tree->entries != NULL) { + size_t i; + + for (i = 0; i < tree->entry_count; ++i) + free(tree->entries[i]); + + free(tree->entries); + } error = git_object__source_open((git_object *)tree); if (error < 0) @@ -120,27 +255,24 @@ int git_tree__parse(git_tree *tree) buffer_end = buffer + tree->object.source.raw.len; tree->entry_count = 0; - entries_size = (tree->object.source.raw.len / avg_entry_size) + 1; - tree->entries = git__malloc(entries_size * sizeof(git_tree_entry)); + tree->array_size = (tree->object.source.raw.len / avg_entry_size) + 1; + tree->entries = git__malloc(tree->array_size * sizeof(git_tree_entry *)); while (buffer < buffer_end) { git_tree_entry *entry; - if (tree->entry_count >= entries_size) { - git_tree_entry *new_entries; + if (tree->entry_count >= tree->array_size) + resize_tree_array(tree); - 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 = git__malloc(sizeof(git_tree_entry)); + if (entry == NULL) { + error = GIT_ENOMEM; + break; } - entry = &tree->entries[tree->entry_count++]; - entry->owner = tree; + tree->entries[tree->entry_count++] = entry; + entry->owner = tree; entry->attr = strtol(buffer, &buffer, 8); if (*buffer++ != ' ') { @@ -148,13 +280,12 @@ int git_tree__parse(git_tree *tree) break; } - entry->filename = git__strdup(buffer); + strncpy(entry->filename, buffer, GIT_TREE_MAX_FILENAME); - if (entry->filename == NULL) { - error = GIT_EOBJCORRUPTED; - } + while (buffer < buffer_end && *buffer != 0) + buffer++; - buffer += strlen(entry->filename) + 1; + buffer++; git_oid_mkraw(&entry->oid, (const unsigned char *)buffer); buffer += GIT_OID_RAWSZ; diff --git a/src/tree.h b/src/tree.h index dd15b9b9b..118d2c3d6 100644 --- a/src/tree.h +++ b/src/tree.h @@ -4,9 +4,11 @@ #include #include "repository.h" +#define GIT_TREE_MAX_FILENAME 255 + struct git_tree_entry { unsigned int attr; - char *filename; + char filename[GIT_TREE_MAX_FILENAME]; git_oid oid; git_tree *owner; @@ -15,11 +17,13 @@ struct git_tree_entry { struct git_tree { git_object object; - git_tree_entry *entries; + git_tree_entry **entries; size_t entry_count; + size_t array_size; }; void git_tree__free(git_tree *tree); int git_tree__parse(git_tree *tree); +int git_tree__writeback(git_tree *tree, git_odb_source *src); #endif diff --git a/tests/t0901-readtree.c b/tests/t0901-readtree.c index 307bbea12..c28ad232e 100644 --- a/tests/t0901-readtree.c +++ b/tests/t0901-readtree.c @@ -14,7 +14,7 @@ BEGIN_TEST(tree_read_test) git_oid id; git_repository *repo; git_tree *tree; - const git_tree_entry *entry; + git_tree_entry *entry; must_pass(git_odb_open(&db, odb_dir)); @@ -35,6 +35,7 @@ BEGIN_TEST(tree_read_test) must_be_true(strcmp(git_tree_entry_name(entry), "README") == 0); + must_be_true(git_tree_entry_2object(entry) != NULL); git_repository_free(repo);