| /* |
| * This file is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License, version 2, |
| * as published by the Free Software Foundation. |
| * |
| * In addition to the permissions in the GNU General Public License, |
| * the authors give you unlimited permission to link the compiled |
| * version of this file into combinations with other programs, |
| * and to distribute those combinations without any restriction |
| * coming from the use of this file. (The General Public License |
| * restrictions do apply in other respects; for example, they cover |
| * modification of the file, and distribution when not linked into |
| * a combined executable.) |
| * |
| * This file is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; see the file COPYING. If not, write to |
| * the Free Software Foundation, 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "common.h" |
| #include "commit.h" |
| #include "tree.h" |
| #include "git2/repository.h" |
| #include "git2/object.h" |
| |
| #define DEFAULT_TREE_SIZE 16 |
| #define MAX_FILEMODE 0777777 |
| #define MAX_FILEMODE_BYTES 6 |
| |
| int entry_search_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); |
| } |
| |
| static int valid_attributes(const int attributes) { |
| return attributes >= 0 && attributes <= MAX_FILEMODE; |
| } |
| |
| 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 gitfo_cmp_path(entry_a->filename, strlen(entry_a->filename), |
| entry_a->attr & 040000, |
| entry_b->filename, strlen(entry_b->filename), |
| entry_b->attr & 040000); |
| } |
| |
| void git_tree_clear_entries(git_tree *tree) |
| { |
| unsigned int i; |
| |
| if (tree == NULL) |
| return; |
| |
| for (i = 0; i < tree->entries.length; ++i) { |
| git_tree_entry *e; |
| e = git_vector_get(&tree->entries, i); |
| |
| free(e->filename); |
| free(e); |
| } |
| |
| git_vector_clear(&tree->entries); |
| tree->object.modified = 1; |
| } |
| |
| |
| git_tree *git_tree__new(void) |
| { |
| git_tree *tree; |
| |
| tree = git__malloc(sizeof(struct git_tree)); |
| if (tree == NULL) |
| return NULL; |
| |
| memset(tree, 0x0, sizeof(struct git_tree)); |
| |
| if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < GIT_SUCCESS) { |
| free(tree); |
| return NULL; |
| } |
| |
| return tree; |
| } |
| |
| void git_tree__free(git_tree *tree) |
| { |
| git_tree_clear_entries(tree); |
| git_vector_free(&tree->entries); |
| free(tree); |
| } |
| |
| const git_oid *git_tree_id(git_tree *c) |
| { |
| return git_object_id((git_object *)c); |
| } |
| |
| int git_tree_entry_set_attributes(git_tree_entry *entry, unsigned int attr) |
| { |
| assert(entry && entry->owner); |
| |
| if (!valid_attributes(attr)) { |
| return GIT_ERROR; |
| } |
| |
| entry->attr = attr; |
| entry->owner->object.modified = 1; |
| return GIT_SUCCESS; |
| } |
| |
| void git_tree_entry_set_name(git_tree_entry *entry, const char *name) |
| { |
| assert(entry && entry->owner); |
| |
| free(entry->filename); |
| entry->filename = git__strdup(name); |
| git_vector_sort(&entry->owner->entries); |
| 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(git_tree_entry *entry) |
| { |
| assert(entry); |
| return entry->filename; |
| } |
| |
| const git_oid *git_tree_entry_id(git_tree_entry *entry) |
| { |
| assert(entry); |
| return &entry->oid; |
| } |
| |
| int git_tree_entry_2object(git_object **object_out, git_tree_entry *entry) |
| { |
| assert(entry && object_out); |
| return git_object_lookup(object_out, entry->owner->object.repo, &entry->oid, GIT_OBJ_ANY); |
| } |
| |
| static void sort_entries(git_tree *tree) |
| { |
| git_vector_sort(&tree->entries); |
| } |
| |
| git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename) |
| { |
| int idx; |
| |
| assert(tree && filename); |
| |
| sort_entries(tree); |
| |
| idx = git_vector_bsearch2(&tree->entries, entry_search_cmp, filename); |
| if (idx == GIT_ENOTFOUND) |
| return NULL; |
| |
| return git_vector_get(&tree->entries, idx); |
| } |
| |
| git_tree_entry *git_tree_entry_byindex(git_tree *tree, int idx) |
| { |
| assert(tree); |
| |
| sort_entries(tree); |
| |
| return git_vector_get(&tree->entries, (unsigned int)idx); |
| } |
| |
| size_t git_tree_entrycount(git_tree *tree) |
| { |
| assert(tree); |
| return tree->entries.length; |
| } |
| |
| int git_tree_add_entry(git_tree_entry **entry_out, git_tree *tree, const git_oid *id, const char *filename, int attributes) |
| { |
| git_tree_entry *entry; |
| |
| assert(tree && id && filename); |
| if (!valid_attributes(attributes)) { |
| return GIT_ERROR; |
| } |
| |
| if ((entry = git__malloc(sizeof(git_tree_entry))) == NULL) |
| return GIT_ENOMEM; |
| |
| memset(entry, 0x0, sizeof(git_tree_entry)); |
| |
| entry->filename = git__strdup(filename); |
| git_oid_cpy(&entry->oid, id); |
| entry->attr = attributes; |
| entry->owner = tree; |
| |
| if (git_vector_insert(&tree->entries, entry) < 0) |
| return GIT_ENOMEM; |
| |
| if (entry_out != NULL) |
| *entry_out = entry; |
| |
| tree->object.modified = 1; |
| return GIT_SUCCESS; |
| } |
| |
| int git_tree_remove_entry_byindex(git_tree *tree, int idx) |
| { |
| git_tree_entry *remove_ptr; |
| |
| assert(tree); |
| |
| sort_entries(tree); |
| |
| remove_ptr = git_vector_get(&tree->entries, (unsigned int)idx); |
| if (remove_ptr == NULL) |
| return GIT_ENOTFOUND; |
| |
| free(remove_ptr->filename); |
| free(remove_ptr); |
| |
| tree->object.modified = 1; |
| |
| return git_vector_remove(&tree->entries, (unsigned int)idx); |
| } |
| |
| int git_tree_remove_entry_byname(git_tree *tree, const char *filename) |
| { |
| int idx; |
| |
| assert(tree && filename); |
| |
| sort_entries(tree); |
| |
| idx = git_vector_bsearch2(&tree->entries, entry_search_cmp, filename); |
| if (idx == GIT_ENOTFOUND) |
| return GIT_ENOTFOUND; |
| |
| return git_tree_remove_entry_byindex(tree, idx); |
| } |
| |
| int git_tree__writeback(git_tree *tree, git_odb_source *src) |
| { |
| size_t i; |
| char filemode[MAX_FILEMODE_BYTES + 1 + 1]; |
| |
| assert(tree && src); |
| |
| if (tree->entries.length == 0) |
| return GIT_EMISSINGOBJDATA; |
| |
| sort_entries(tree); |
| |
| for (i = 0; i < tree->entries.length; ++i) { |
| git_tree_entry *entry; |
| |
| entry = git_vector_get(&tree->entries, i); |
| |
| snprintf(filemode, sizeof(filemode), "%o ", entry->attr); |
| |
| git__source_write(src, filemode, strlen(filemode)); |
| git__source_write(src, entry->filename, strlen(entry->filename) + 1); |
| git__source_write(src, entry->oid.id, GIT_OID_RAWSZ); |
| } |
| |
| return GIT_SUCCESS; |
| } |
| |
| |
| static int tree_parse_buffer(git_tree *tree, char *buffer, char *buffer_end) |
| { |
| static const size_t avg_entry_size = 40; |
| unsigned int expected_size; |
| int error = GIT_SUCCESS; |
| |
| expected_size = (tree->object.source.raw.len / avg_entry_size) + 1; |
| |
| git_tree_clear_entries(tree); |
| |
| while (buffer < buffer_end) { |
| git_tree_entry *entry; |
| |
| entry = git__malloc(sizeof(git_tree_entry)); |
| if (entry == NULL) { |
| error = GIT_ENOMEM; |
| break; |
| } |
| |
| if (git_vector_insert(&tree->entries, entry) < GIT_SUCCESS) |
| return GIT_ENOMEM; |
| |
| entry->owner = tree; |
| entry->attr = strtol(buffer, &buffer, 8); |
| |
| if (*buffer++ != ' ') { |
| error = GIT_EOBJCORRUPTED; |
| break; |
| } |
| |
| if (memchr(buffer, 0, buffer_end - buffer) == NULL) { |
| error = GIT_EOBJCORRUPTED; |
| break; |
| } |
| |
| entry->filename = git__strdup(buffer); |
| |
| while (buffer < buffer_end && *buffer != 0) |
| buffer++; |
| |
| buffer++; |
| |
| git_oid_mkraw(&entry->oid, (const unsigned char *)buffer); |
| buffer += GIT_OID_RAWSZ; |
| } |
| |
| return error; |
| } |
| |
| int git_tree__parse(git_tree *tree) |
| { |
| char *buffer, *buffer_end; |
| |
| assert(tree && tree->object.source.open); |
| assert(!tree->object.in_memory); |
| |
| buffer = tree->object.source.raw.data; |
| buffer_end = buffer + tree->object.source.raw.len; |
| |
| return tree_parse_buffer(tree, buffer, buffer_end); |
| } |
| |