From b6c93aef4276051f9c4536ecbed48f4cd093bd1b Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 21 Feb 2012 14:46:24 -0800 Subject: [PATCH 1/3] Uniform iterators for trees, index, and workdir This create a new git_iterator type of object that provides a uniform interface for iterating over the index, an arbitrary tree, or the working directory of a repository. As part of this, git ignore support was extended to support push and pop of directory-based ignore files as the working directory is being traversed (so the array of ignores does not have to be recreated at each directory during traveral). There are a number of other small utility functions in buffer, path, vector, and fileops that are included in this patch that made the iterator implementation cleaner. --- src/attr.c | 70 +++-- src/attr.h | 7 + src/buffer.c | 8 +- src/buffer.h | 1 + src/fileops.c | 18 ++ src/fileops.h | 7 +- src/ignore.c | 118 +++++--- src/ignore.h | 24 +- src/iterator.c | 506 +++++++++++++++++++++++++++++++++ src/iterator.h | 94 ++++++ src/path.c | 65 +++++ src/path.h | 32 +++ src/vector.c | 32 +++ src/vector.h | 7 + tests-clar/diff/diff_helpers.c | 22 ++ tests-clar/diff/diff_helpers.h | 4 + tests-clar/diff/iterator.c | 362 +++++++++++++++++++++++ 17 files changed, 1314 insertions(+), 63 deletions(-) create mode 100644 src/iterator.c create mode 100644 src/iterator.h create mode 100644 tests-clar/diff/diff_helpers.c create mode 100644 tests-clar/diff/diff_helpers.h create mode 100644 tests-clar/diff/iterator.c diff --git a/src/attr.c b/src/attr.c index 17571f6a8..a7c65f94c 100644 --- a/src/attr.c +++ b/src/attr.c @@ -218,6 +218,48 @@ int git_attr_cache__is_cached(git_repository *repo, const char *path) return (git_hashtable_lookup(repo->attrcache.files, cache_key) == NULL); } +int git_attr_cache__lookup_or_create_file( + git_repository *repo, + const char *key, + const char *filename, + int (*loader)(git_repository *, const char *, git_attr_file *), + git_attr_file **file_ptr) +{ + int error; + git_attr_cache *cache = &repo->attrcache; + git_attr_file *file = NULL; + + file = git_hashtable_lookup(cache->files, key); + if (file) { + *file_ptr = file; + return GIT_SUCCESS; + } + + if (loader && git_path_exists(filename) != GIT_SUCCESS) { + *file_ptr = NULL; + return GIT_SUCCESS; + } + + if ((error = git_attr_file__new(&file)) < GIT_SUCCESS) + return error; + + if (loader) + error = loader(repo, filename, file); + else + error = git_attr_file__set_path(repo, key, file); + + if (error == GIT_SUCCESS) + error = git_hashtable_insert(cache->files, file->path, file); + + if (error < GIT_SUCCESS) { + git_attr_file__free(file); + file = NULL; + } + + *file_ptr = file; + return error; +} + /* add git_attr_file to vector of files, loading if needed */ int git_attr_cache__push_file( git_repository *repo, @@ -226,16 +268,14 @@ int git_attr_cache__push_file( const char *filename, int (*loader)(git_repository *, const char *, git_attr_file *)) { - int error = GIT_SUCCESS; - git_attr_cache *cache = &repo->attrcache; + int error; git_buf path = GIT_BUF_INIT; git_attr_file *file = NULL; - int add_to_cache = 0; const char *cache_key; if (base != NULL) { if ((error = git_buf_joinpath(&path, base, filename)) < GIT_SUCCESS) - goto cleanup; + return error; filename = path.ptr; } @@ -244,28 +284,12 @@ int git_attr_cache__push_file( if (repo && git__prefixcmp(cache_key, git_repository_workdir(repo)) == 0) cache_key += strlen(git_repository_workdir(repo)); - file = git_hashtable_lookup(cache->files, cache_key); - if (file == NULL && git_path_exists(filename) == GIT_SUCCESS) { - if ((error = git_attr_file__new(&file)) == GIT_SUCCESS) { - if ((error = loader(repo, filename, file)) < GIT_SUCCESS) { - git_attr_file__free(file); - file = NULL; - } - } - add_to_cache = (error == GIT_SUCCESS); - } + error = git_attr_cache__lookup_or_create_file( + repo, cache_key, filename, loader, &file); - if (error == GIT_SUCCESS && file != NULL) { - /* add file to vector, if we found it */ + if (error == GIT_SUCCESS && file != NULL) error = git_vector_insert(stack, file); - /* add file to cache, if it is new */ - /* do this after above step b/c it is not critical */ - if (error == GIT_SUCCESS && add_to_cache && file->path != NULL) - error = git_hashtable_insert(cache->files, file->path, file); - } - -cleanup: git_buf_free(&path); return error; } diff --git a/src/attr.h b/src/attr.h index 6ae2e28dc..5dbbb2366 100644 --- a/src/attr.h +++ b/src/attr.h @@ -20,6 +20,13 @@ extern int git_attr_cache__init(git_repository *repo); extern int git_attr_cache__insert_macro( git_repository *repo, git_attr_rule *macro); +extern int git_attr_cache__lookup_or_create_file( + git_repository *repo, + const char *key, + const char *filename, + int (*loader)(git_repository *, const char *, git_attr_file *), + git_attr_file **file_ptr); + extern int git_attr_cache__push_file( git_repository *repo, git_vector *stack, diff --git a/src/buffer.c b/src/buffer.c index 7a186ebd8..183da7c5f 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -213,6 +213,12 @@ void git_buf_truncate(git_buf *buf, ssize_t len) } } +void git_buf_rtruncate_at_char(git_buf *buf, char separator) +{ + int idx = git_buf_rfind_next(buf, separator); + git_buf_truncate(buf, idx < 0 ? 0 : idx); +} + void git_buf_swap(git_buf *buf_a, git_buf *buf_b) { git_buf t = *buf_a; @@ -327,7 +333,7 @@ int git_buf_join( const char *str_b) { int error = GIT_SUCCESS; - size_t strlen_a = strlen(str_a); + size_t strlen_a = str_a ? strlen(str_a) : 0; size_t strlen_b = strlen(str_b); int need_sep = 0; ssize_t offset_a = -1; diff --git a/src/buffer.h b/src/buffer.h index 3a003ce3c..3969f461e 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -84,6 +84,7 @@ int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3 void git_buf_clear(git_buf *buf); void git_buf_consume(git_buf *buf, const char *end); void git_buf_truncate(git_buf *buf, ssize_t len); +void git_buf_rtruncate_at_char(git_buf *path, char separator); int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...); int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b); diff --git a/src/fileops.c b/src/fileops.c index cea954def..3241c68b1 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -79,6 +79,24 @@ git_off_t git_futils_filesize(git_file fd) return sb.st_size; } +#define GIT_MODE_PERMS_MASK 0777 +#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644) +#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK) + +mode_t git_futils_canonical_mode(mode_t raw_mode) +{ + if (S_ISREG(raw_mode)) + return S_IFREG | GIT_CANONICAL_PERMS(raw_mode); + else if (S_ISLNK(raw_mode)) + return S_IFLNK; + else if (S_ISDIR(raw_mode)) + return S_IFDIR; + else if (S_ISGITLINK(raw_mode)) + return S_IFGITLINK; + else + return 0; +} + int git_futils_readbuffer_updated(git_fbuffer *obj, const char *path, time_t *mtime, int *updated) { git_file fd; diff --git a/src/fileops.h b/src/fileops.h index c9ed05de3..4c114026b 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -85,12 +85,17 @@ extern int git_futils_mktmp(git_buf *path_out, const char *filename); */ extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); - /** * Get the filesize in bytes of a file */ extern git_off_t git_futils_filesize(git_file fd); +/** + * Convert a mode_t from the OS to a legal git mode_t value. + */ +extern mode_t git_futils_canonical_mode(mode_t raw_mode); + + /** * Read-only map all or part of a file into memory. * When possible this function should favor a virtual memory diff --git a/src/ignore.c b/src/ignore.c index 9690eba08..ecdd76005 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -69,38 +69,44 @@ static int load_ignore_file( static int push_one_ignore(void *ref, git_buf *path) { git_ignores *ign = (git_ignores *)ref; - return push_ignore(ign->repo, &ign->stack, path->ptr, GIT_IGNORE_FILE); + return push_ignore(ign->repo, &ign->ign_path, path->ptr, GIT_IGNORE_FILE); } int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ignores) { int error = GIT_SUCCESS; - git_buf dir = GIT_BUF_INIT; git_config *cfg; const char *workdir = git_repository_workdir(repo); assert(ignores); + ignores->repo = repo; + git_buf_init(&ignores->dir, 0); + ignores->ign_internal = NULL; + git_vector_init(&ignores->ign_path, 8, NULL); + git_vector_init(&ignores->ign_global, 2, NULL); + if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS) goto cleanup; - if ((error = git_path_find_dir(&dir, path, workdir)) < GIT_SUCCESS) + if ((error = git_path_find_dir(&ignores->dir, path, workdir)) < GIT_SUCCESS) goto cleanup; - ignores->repo = repo; - ignores->dir = NULL; - git_vector_init(&ignores->stack, 2, NULL); - - /* insert internals */ - if ((error = push_ignore(repo, &ignores->stack, NULL, GIT_IGNORE_INTERNAL)) < GIT_SUCCESS) + /* set up internals */ + error = git_attr_cache__lookup_or_create_file( + repo, GIT_IGNORE_INTERNAL, NULL, NULL, &ignores->ign_internal); + if (error < GIT_SUCCESS) goto cleanup; /* load .gitignore up the path */ - if ((error = git_path_walk_up(&dir, workdir, push_one_ignore, ignores)) < GIT_SUCCESS) + error = git_path_walk_up(&ignores->dir, workdir, push_one_ignore, ignores); + if (error < GIT_SUCCESS) goto cleanup; /* load .git/info/exclude */ - if ((error = push_ignore(repo, &ignores->stack, repo->path_repository, GIT_IGNORE_FILE_INREPO)) < GIT_SUCCESS) + error = push_ignore(repo, &ignores->ign_global, + repo->path_repository, GIT_IGNORE_FILE_INREPO); + if (error < GIT_SUCCESS) goto cleanup; /* load core.excludesfile */ @@ -108,7 +114,7 @@ int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ig const char *core_ignore; error = git_config_get_string(cfg, GIT_IGNORE_CONFIG, &core_ignore); if (error == GIT_SUCCESS && core_ignore != NULL) - error = push_ignore(repo, &ignores->stack, NULL, core_ignore); + error = push_ignore(repo, &ignores->ign_global, NULL, core_ignore); else { error = GIT_SUCCESS; git_clearerror(); /* don't care if attributesfile is not set */ @@ -117,46 +123,92 @@ int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ig } cleanup: - if (error < GIT_SUCCESS) + if (error < GIT_SUCCESS) { + git_ignore__free(ignores); git__rethrow(error, "Could not get ignore files for '%s'", path); - else - ignores->dir = git_buf_detach(&dir); - - git_buf_free(&dir); + } return error; } +int git_ignore__push_dir(git_ignores *ign, const char *dir) +{ + int error = git_buf_joinpath(&ign->dir, ign->dir.ptr, dir); + + if (error == GIT_SUCCESS) + error = push_ignore( + ign->repo, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); + + return error; +} + +int git_ignore__pop_dir(git_ignores *ign) +{ + if (ign->ign_path.length > 0) { + git_attr_file *file = git_vector_last(&ign->ign_path); + if (git__suffixcmp(ign->dir.ptr, file->path) == 0) + git_vector_pop(&ign->ign_path, NULL); + git_buf_rtruncate_at_char(&ign->dir, '/'); + } + return GIT_SUCCESS; +} + void git_ignore__free(git_ignores *ignores) { - git__free(ignores->dir); - ignores->dir = NULL; - git_vector_free(&ignores->stack); + /* don't need to free ignores->ign_internal since it is in cache */ + git_vector_free(&ignores->ign_path); + git_vector_free(&ignores->ign_global); + git_buf_free(&ignores->dir); +} + +static int ignore_lookup_in_rules( + git_vector *rules, git_attr_path *path, int *ignored) +{ + unsigned int j; + git_attr_fnmatch *match; + + git_vector_rforeach(rules, j, match) { + if (git_attr_fnmatch__match(match, path) == GIT_SUCCESS) { + *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); + return GIT_SUCCESS; + } + } + + return GIT_ENOTFOUND; } int git_ignore__lookup(git_ignores *ignores, const char *pathname, int *ignored) { int error; - unsigned int i, j; + unsigned int i; git_attr_file *file; git_attr_path path; - git_attr_fnmatch *match; if ((error = git_attr_path__init( &path, pathname, git_repository_workdir(ignores->repo))) < GIT_SUCCESS) return git__rethrow(error, "Could not get attribute for '%s'", pathname); + /* first process builtins */ + error = ignore_lookup_in_rules( + &ignores->ign_internal->rules, &path, ignored); + if (error == GIT_SUCCESS) + return error; + + /* next process files in the path */ + git_vector_foreach(&ignores->ign_path, i, file) { + error = ignore_lookup_in_rules(&file->rules, &path, ignored); + if (error == GIT_SUCCESS) + return error; + } + + /* last process global ignores */ + git_vector_foreach(&ignores->ign_global, i, file) { + error = ignore_lookup_in_rules(&file->rules, &path, ignored); + if (error == GIT_SUCCESS) + return error; + } + *ignored = 0; - git_vector_foreach(&ignores->stack, i, file) { - git_vector_rforeach(&file->rules, j, match) { - if (git_attr_fnmatch__match(match, &path) == GIT_SUCCESS) { - *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); - goto found; - } - } - } -found: - - return error; + return GIT_SUCCESS; } diff --git a/src/ignore.h b/src/ignore.h index 386322ff2..49f72bf25 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -10,14 +10,28 @@ #include "repository.h" #include "vector.h" +/* The git_ignores structure maintains three sets of ignores: + * - internal ignores + * - per directory ignores + * - global ignores (at lower priority than the others) + * As you traverse from one directory to another, you can push and pop + * directories onto git_ignores list efficiently. + */ typedef struct { git_repository *repo; - char *dir; - git_vector stack; + git_buf dir; + git_attr_file *ign_internal; + git_vector ign_path; + git_vector ign_global; } git_ignores; -extern int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *stack); -extern void git_ignore__free(git_ignores *stack); -extern int git_ignore__lookup(git_ignores *stack, const char *path, int *ignored); +extern int git_ignore__for_path( + git_repository *repo, const char *path, git_ignores *ign); + +extern int git_ignore__push_dir(git_ignores *ign, const char *dir); +extern int git_ignore__pop_dir(git_ignores *ign); + +extern void git_ignore__free(git_ignores *ign); +extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored); #endif diff --git a/src/iterator.c b/src/iterator.c new file mode 100644 index 000000000..8511d53eb --- /dev/null +++ b/src/iterator.c @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "iterator.h" +#include "tree.h" +#include "ignore.h" +#include "buffer.h" + +#define IDX_AS_PTR(I) (void *)((uint64_t)(I)) +#define PTR_AS_IDX(P) (unsigned int)((uint64_t)(P)) + +typedef struct { + git_iterator cb; + git_repository *repo; + git_vector tree_stack; + git_vector idx_stack; + git_index_entry entry; + git_buf path; +} git_iterator_tree; + +static const git_tree_entry *git_iterator__tree_entry(git_iterator_tree *ti) +{ + git_tree *tree; + unsigned int tree_idx; + + if ((tree = git_vector_last(&ti->tree_stack)) == NULL) + return NULL; + + tree_idx = PTR_AS_IDX(git_vector_last(&ti->idx_stack)); + return git_tree_entry_byindex(tree, tree_idx); +} + +static int git_iterator__tree_current( + git_iterator *self, const git_index_entry **entry) +{ + int error; + git_iterator_tree *ti = (git_iterator_tree *)self; + const git_tree_entry *te = git_iterator__tree_entry(ti); + + *entry = NULL; + + if (te == NULL) + return GIT_SUCCESS; + + ti->entry.mode = te->attr; + git_oid_cpy(&ti->entry.oid, &te->oid); + error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename); + if (error < GIT_SUCCESS) + return error; + ti->entry.path = ti->path.ptr; + + *entry = &ti->entry; + + return GIT_SUCCESS; +} + +static int git_iterator__tree_at_end(git_iterator *self) +{ + git_iterator_tree *ti = (git_iterator_tree *)self; + git_tree *tree; + return ((tree = git_vector_last(&ti->tree_stack)) == NULL || + git_tree_entry_byindex( + tree, PTR_AS_IDX(git_vector_last(&ti->idx_stack))) == NULL); +} + +static int expand_tree_if_needed(git_iterator_tree *ti) +{ + int error; + git_tree *tree, *subtree; + unsigned int tree_idx; + const git_tree_entry *te; + + while (1) { + tree = git_vector_last(&ti->tree_stack); + tree_idx = PTR_AS_IDX(git_vector_last(&ti->idx_stack)); + te = git_tree_entry_byindex(tree, tree_idx); + + if (!entry_is_tree(te)) + return GIT_SUCCESS; + + error = git_tree_lookup(&subtree, ti->repo, &te->oid); + if (error != GIT_SUCCESS) + return error; + + if ((error = git_vector_insert(&ti->tree_stack, subtree)) < GIT_SUCCESS || + (error = git_vector_insert(&ti->idx_stack, IDX_AS_PTR(0))) < GIT_SUCCESS || + (error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename)) < GIT_SUCCESS) + { + git_tree_free(subtree); + return error; + } + } + + return GIT_SUCCESS; +} + +static int git_iterator__tree_advance(git_iterator *self) +{ + git_iterator_tree *ti = (git_iterator_tree *)self; + git_tree *tree = git_vector_last(&ti->tree_stack); + unsigned int tree_idx = PTR_AS_IDX(git_vector_last(&ti->idx_stack)); + const git_tree_entry *te = git_tree_entry_byindex(tree, tree_idx); + + if (te == NULL) + return GIT_SUCCESS; + + while (1) { + /* advance this tree */ + tree_idx++; + ti->idx_stack.contents[ti->idx_stack.length - 1] = IDX_AS_PTR(tree_idx); + + /* remove old entry filename */ + git_buf_rtruncate_at_char(&ti->path, '/'); + + if ((te = git_tree_entry_byindex(tree, tree_idx)) != NULL) + break; + + /* no entry - either we are done or we are done with this subtree */ + if (ti->tree_stack.length == 1) + return GIT_SUCCESS; + + git_tree_free(tree); + git_vector_remove(&ti->tree_stack, ti->tree_stack.length - 1); + git_vector_remove(&ti->idx_stack, ti->idx_stack.length - 1); + git_buf_rtruncate_at_char(&ti->path, '/'); + + tree = git_vector_last(&ti->tree_stack); + tree_idx = PTR_AS_IDX(git_vector_last(&ti->idx_stack)); + } + + if (te && entry_is_tree(te)) + return expand_tree_if_needed(ti); + + return GIT_SUCCESS; +} + +static void git_iterator__tree_free(git_iterator *self) +{ + git_iterator_tree *ti = (git_iterator_tree *)self; + + while (ti->tree_stack.length > 1) { + git_tree *tree = git_vector_last(&ti->tree_stack); + git_tree_free(tree); + git_vector_remove(&ti->tree_stack, ti->tree_stack.length - 1); + } + + git_vector_clear(&ti->tree_stack); + git_vector_clear(&ti->idx_stack); + git_buf_free(&ti->path); +} + +int git_iterator_for_tree(git_repository *repo, git_tree *tree, git_iterator **iter) +{ + int error; + git_iterator_tree *ti = git__calloc(1, sizeof(git_iterator_tree)); + if (!ti) + return GIT_ENOMEM; + + ti->cb.type = GIT_ITERATOR_TREE; + ti->cb.current = git_iterator__tree_current; + ti->cb.at_end = git_iterator__tree_at_end; + ti->cb.advance = git_iterator__tree_advance; + ti->cb.free = git_iterator__tree_free; + ti->repo = repo; + + if (!(error = git_vector_init(&ti->tree_stack, 0, NULL)) && + !(error = git_vector_insert(&ti->tree_stack, tree)) && + !(error = git_vector_init(&ti->idx_stack, 0, NULL))) + error = git_vector_insert(&ti->idx_stack, IDX_AS_PTR(0)); + + if (error == GIT_SUCCESS) + error = expand_tree_if_needed(ti); + + if (error != GIT_SUCCESS) + git_iterator_free((git_iterator *)ti); + else + *iter = (git_iterator *)ti; + + return error; +} + + +typedef struct { + git_iterator cb; + git_index *index; + unsigned int current; +} git_iterator_index; + +static int git_iterator__index_current( + git_iterator *self, const git_index_entry **entry) +{ + git_iterator_index *ii = (git_iterator_index *)self; + *entry = git_index_get(ii->index, ii->current); + return GIT_SUCCESS; +} + +static int git_iterator__index_at_end(git_iterator *self) +{ + git_iterator_index *ii = (git_iterator_index *)self; + return (ii->current >= git_index_entrycount(ii->index)); +} + +static int git_iterator__index_advance(git_iterator *self) +{ + git_iterator_index *ii = (git_iterator_index *)self; + if (ii->current < git_index_entrycount(ii->index)) + ii->current++; + return GIT_SUCCESS; +} + +static void git_iterator__index_free(git_iterator *self) +{ + git_iterator_index *ii = (git_iterator_index *)self; + git_index_free(ii->index); + ii->index = NULL; +} + +int git_iterator_for_index(git_repository *repo, git_iterator **iter) +{ + int error; + git_iterator_index *ii = git__calloc(1, sizeof(git_iterator_index)); + if (!ii) + return GIT_ENOMEM; + + ii->cb.type = GIT_ITERATOR_INDEX; + ii->cb.current = git_iterator__index_current; + ii->cb.at_end = git_iterator__index_at_end; + ii->cb.advance = git_iterator__index_advance; + ii->cb.free = git_iterator__index_free; + ii->current = 0; + + if ((error = git_repository_index(&ii->index, repo)) < GIT_SUCCESS) + git__free(ii); + else + *iter = (git_iterator *)ii; + return error; +} + + +typedef struct { + git_iterator cb; + git_repository *repo; + size_t root_len; + git_vector dir_stack; /* vector of vectors of paths */ + git_vector idx_stack; + git_ignores ignores; + git_index_entry entry; + git_buf path; + int is_ignored; +} git_iterator_workdir; + +static void free_directory(git_vector *dir) +{ + unsigned int i; + char *path; + + git_vector_foreach(dir, i, path) + git__free(path); + git_vector_free(dir); + git__free(dir); +} + +static int load_workdir_entry(git_iterator_workdir *wi); + +static int push_directory(git_iterator_workdir *wi) +{ + int error; + git_vector *dir = NULL; + + error = git_vector_alloc(&dir, 0, git__strcmp_cb); + if (error < GIT_SUCCESS) + return error; + + /* allocate dir entries with extra byte (the "1" param) so later on we + * can suffix directories with a "/" as needed. + */ + error = git_path_dirload(wi->path.ptr, wi->root_len, 1, dir); + if (error < GIT_SUCCESS || dir->length == 0) { + free_directory(dir); + return GIT_ENOTFOUND; + } + + if ((error = git_vector_insert(&wi->dir_stack, dir)) || + (error = git_vector_insert(&wi->idx_stack, IDX_AS_PTR(0)))) + { + free_directory(dir); + return error; + } + + git_vector_sort(dir); + + if (wi->dir_stack.length > 1) { + int slash_pos = git_buf_rfind_next(&wi->path, '/'); + (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]); + } + + return load_workdir_entry(wi); +} + +static int git_iterator__workdir_current( + git_iterator *self, const git_index_entry **entry) +{ + git_iterator_workdir *wi = (git_iterator_workdir *)self; + *entry = (wi->entry.path == NULL) ? NULL : &wi->entry; + return GIT_SUCCESS; +} + +static int git_iterator__workdir_at_end(git_iterator *self) +{ + git_iterator_workdir *wi = (git_iterator_workdir *)self; + return (wi->entry.path == NULL); +} + +static int git_iterator__workdir_advance(git_iterator *self) +{ + git_iterator_workdir *wi = (git_iterator_workdir *)self; + git_vector *dir; + unsigned int pos; + const char *next; + + if (wi->entry.path == NULL) + return GIT_SUCCESS; + + while (1) { + dir = git_vector_last(&wi->dir_stack); + pos = 1 + PTR_AS_IDX(git_vector_last(&wi->idx_stack)); + wi->idx_stack.contents[wi->idx_stack.length - 1] = IDX_AS_PTR(pos); + + next = git_vector_get(dir, pos); + if (next != NULL) { + if (strcmp(next, DOT_GIT) == 0) + continue; + /* else found a good entry */ + break; + } + + memset(&wi->entry, 0, sizeof(wi->entry)); + if (wi->dir_stack.length == 1) + return GIT_SUCCESS; + + free_directory(dir); + git_vector_remove(&wi->dir_stack, wi->dir_stack.length - 1); + git_vector_remove(&wi->idx_stack, wi->idx_stack.length - 1); + git_ignore__pop_dir(&wi->ignores); + } + + return load_workdir_entry(wi); +} + +int git_iterator_advance_into_ignored_directory(git_iterator *iter) +{ + git_iterator_workdir *wi = (git_iterator_workdir *)iter; + + if (iter->type != GIT_ITERATOR_WORKDIR) + return GIT_SUCCESS; + + /* Loop because the first entry in the ignored directory could itself be + * an ignored directory, but we want to descend to find an actual entry. + */ + while (wi->entry.path && wi->is_ignored && S_ISDIR(wi->entry.mode)) { + int error = push_directory(wi); + if (error != GIT_SUCCESS) + return error; + } + + return GIT_SUCCESS; +} + +static void git_iterator__workdir_free(git_iterator *self) +{ + git_iterator_workdir *wi = (git_iterator_workdir *)self; + + while (wi->dir_stack.length) { + git_vector *dir = git_vector_last(&wi->dir_stack); + free_directory(dir); + git_vector_remove(&wi->dir_stack, wi->dir_stack.length - 1); + } + + git_vector_clear(&wi->dir_stack); + git_vector_clear(&wi->idx_stack); + git_ignore__free(&wi->ignores); + git_buf_free(&wi->path); +} + +static int load_workdir_entry(git_iterator_workdir *wi) +{ + int error; + char *relpath; + git_vector *dir = git_vector_last(&wi->dir_stack); + unsigned int pos = PTR_AS_IDX(git_vector_last(&wi->idx_stack)); + struct stat st; + + relpath = git_vector_get(dir, pos); + error = git_buf_joinpath( + &wi->path, git_repository_workdir(wi->repo), relpath); + if (error < GIT_SUCCESS) + return error; + + memset(&wi->entry, 0, sizeof(wi->entry)); + wi->entry.path = relpath; + + if (strcmp(relpath, DOT_GIT) == 0) + return git_iterator__workdir_advance((git_iterator *)wi); + + /* if there is an error processing the entry, treat as ignored */ + wi->is_ignored = 1; + error = git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored); + if (error != GIT_SUCCESS) + return GIT_SUCCESS; + + if (p_lstat(wi->path.ptr, &st) < 0) + return GIT_SUCCESS; + + /* TODO: remove shared code for struct stat conversion with index.c */ + wi->entry.ctime.seconds = (git_time_t)st.st_ctime; + wi->entry.mtime.seconds = (git_time_t)st.st_mtime; + wi->entry.dev = st.st_rdev; + wi->entry.ino = st.st_ino; + wi->entry.mode = git_futils_canonical_mode(st.st_mode); + wi->entry.uid = st.st_uid; + wi->entry.gid = st.st_gid; + wi->entry.file_size = st.st_size; + + /* if this is a file type we don't handle, treat as ignored */ + if (st.st_mode == 0) + return GIT_SUCCESS; + + if (S_ISDIR(st.st_mode)) { + if (git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS) { + /* create submodule entry */ + wi->entry.mode = S_IFGITLINK; + } else if (wi->is_ignored) { + /* create path entry (which is otherwise impossible), but is + * needed in case descent into ignore dir is required. + */ + size_t pathlen = strlen(wi->entry.path); + wi->entry.path[pathlen] = '/'; + wi->entry.path[pathlen + 1] = '\0'; + wi->entry.mode = S_IFDIR; + } else if ((error = push_directory(wi)) < GIT_SUCCESS) { + /* if there is an error loading the directory or if empty + * then skip over the directory completely. + */ + return git_iterator__workdir_advance((git_iterator *)wi); + } + } + + return GIT_SUCCESS; +} + +int git_iterator_for_workdir(git_repository *repo, git_iterator **iter) +{ + int error; + git_iterator_workdir *wi = git__calloc(1, sizeof(git_iterator_workdir)); + if (!wi) + return GIT_ENOMEM; + + wi->cb.type = GIT_ITERATOR_WORKDIR; + wi->cb.current = git_iterator__workdir_current; + wi->cb.at_end = git_iterator__workdir_at_end; + wi->cb.advance = git_iterator__workdir_advance; + wi->cb.free = git_iterator__workdir_free; + wi->repo = repo; + + if ((error = git_buf_sets( + &wi->path, git_repository_workdir(repo))) < GIT_SUCCESS || + (error = git_vector_init(&wi->dir_stack, 0, NULL)) < GIT_SUCCESS || + (error = git_vector_init(&wi->idx_stack, 0, NULL)) < GIT_SUCCESS || + (error = git_ignore__for_path(repo, "", &wi->ignores)) < GIT_SUCCESS) + { + git__free(wi); + return error; + } + + wi->root_len = wi->path.size; + + if ((error = push_directory(wi)) < GIT_SUCCESS) + git_iterator_free((git_iterator *)wi); + else + *iter = (git_iterator *)wi; + + return error; +} + +int git_iterator_current_tree_entry( + git_iterator *iter, const git_tree_entry **tree_entry) +{ + if (iter->type != GIT_ITERATOR_TREE) + *tree_entry = NULL; + else + *tree_entry = git_iterator__tree_entry((git_iterator_tree *)iter); + + return GIT_SUCCESS; +} + +int git_iterator_current_is_ignored(git_iterator *iter) +{ + if (iter->type != GIT_ITERATOR_WORKDIR) + return 0; + else + return ((git_iterator_workdir *)iter)->is_ignored; +} diff --git a/src/iterator.h b/src/iterator.h new file mode 100644 index 000000000..4aa1df52d --- /dev/null +++ b/src/iterator.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_iterator_h__ +#define INCLUDE_iterator_h__ + +#include "common.h" +#include "git2/index.h" + +typedef struct git_iterator git_iterator; + +typedef enum { + GIT_ITERATOR_TREE = 1, + GIT_ITERATOR_INDEX = 2, + GIT_ITERATOR_WORKDIR = 3 +} git_iterator_type_t; + +struct git_iterator { + git_iterator_type_t type; + int (*current)(git_iterator *, const git_index_entry **); + int (*at_end)(git_iterator *); + int (*advance)(git_iterator *); + void (*free)(git_iterator *); +}; + +int git_iterator_for_tree( + git_repository *repo, git_tree *tree, git_iterator **iter); + +int git_iterator_for_index( + git_repository *repo, git_iterator **iter); + +int git_iterator_for_workdir( + git_repository *repo, git_iterator **iter); + +/* Entry is not guaranteed to be fully populated. For a tree iterator, + * we will only populate the mode, oid and path, for example. For a workdir + * iterator, we will not populate the oid. + * + * You do not need to free the entry. It is still "owned" by the iterator. + * Once you call `git_iterator_advance`, then content of the old entry is + * no longer guaranteed to be valid. + */ +GIT_INLINE(int) git_iterator_current( + git_iterator *iter, const git_index_entry **entry) +{ + return iter->current(iter, entry); +} + +GIT_INLINE(int) git_iterator_at_end(git_iterator *iter) +{ + return iter->at_end(iter); +} + +GIT_INLINE(int) git_iterator_advance(git_iterator *iter) +{ + return iter->advance(iter); +} + +GIT_INLINE(void) git_iterator_free(git_iterator *iter) +{ + iter->free(iter); + git__free(iter); +} + +GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter) +{ + return iter->type; +} + +extern int git_iterator_current_tree_entry( + git_iterator *iter, const git_tree_entry **tree_entry); + +extern int git_iterator_current_is_ignored(git_iterator *iter); + +/** + * Iterate into an ignored workdir directory. + * + * When a workdir iterator encounters a directory that is ignored, it will + * just return a current entry for the directory with is_ignored returning + * true. If you are iterating over the index or a tree in parallel and a + * file in the ignored directory has been added to the index/tree already, + * then it may be necessary to iterate into the directory even though it is + * ignored. Call this function to do that. + * + * Note that if the tracked file in the ignored directory has been deleted, + * this may end up acting like a full "advance" call and advance past the + * directory completely. You must handle that case. + */ +extern int git_iterator_advance_into_ignored_directory(git_iterator *iter); + +#endif diff --git a/src/path.c b/src/path.c index 042332c45..6f46dc95e 100644 --- a/src/path.c +++ b/src/path.c @@ -421,6 +421,11 @@ static int _check_dir_contents( return error; } +int git_path_contains(git_buf *dir, const char *item) +{ + return _check_dir_contents(dir, item, 0, &git_path_exists); +} + int git_path_contains_dir(git_buf *base, const char *subdir, int append_if_exists) { return _check_dir_contents(base, subdir, append_if_exists, &git_path_isdir); @@ -522,3 +527,63 @@ int git_path_direach( closedir(dir); return GIT_SUCCESS; } + +int git_path_dirload( + const char *path, + size_t prefix_len, + size_t alloc_extra, + git_vector *contents) +{ + int error, need_slash; + DIR *dir; + struct dirent de_buf, *de; + size_t path_len; + + assert(path != NULL && contents != NULL); + path_len = strlen(path); + assert(path_len > 0 && path_len >= prefix_len); + + if ((dir = opendir(path)) == NULL) + return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure." + " An error occured while opening the directory", path); + + path += prefix_len; + path_len -= prefix_len; + need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0; + + while ((error = readdir_r(dir, &de_buf, &de)) == 0 && de != NULL) { + char *entry_path; + size_t entry_len; + + if (is_dot_or_dotdot(de->d_name)) + continue; + + entry_len = strlen(de->d_name); + + entry_path = git__malloc( + path_len + need_slash + entry_len + 1 + alloc_extra); + if (entry_path == NULL) + return GIT_ENOMEM; + + if (path_len) + memcpy(entry_path, path, path_len); + if (need_slash) + entry_path[path_len] = '/'; + memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len); + entry_path[path_len + need_slash + entry_len] = '\0'; + + if ((error = git_vector_insert(contents, entry_path)) < GIT_SUCCESS) { + git__free(entry_path); + return error; + } + } + + closedir(dir); + + if (error != GIT_SUCCESS) + return git__throw( + GIT_EOSERR, "Failed to process directory entry in `%s`", path); + + return GIT_SUCCESS; +} + diff --git a/src/path.h b/src/path.h index 0f7ebb732..7a4f1f4fd 100644 --- a/src/path.h +++ b/src/path.h @@ -9,6 +9,7 @@ #include "common.h" #include "buffer.h" +#include "vector.h" /** * Path manipulation utils @@ -128,6 +129,15 @@ extern int git_path_isdir(const char *path); */ extern int git_path_isfile(const char *path); +/** + * Check if the parent directory contains the item. + * + * @param dir Directory to check. + * @param item Item that might be in the directory. + * @return GIT_SUCCESS if item exists in directory, <0 otherwise. + */ +extern int git_path_contains(git_buf *dir, const char *item); + /** * Check if the given path contains the given subdirectory. * @@ -216,4 +226,26 @@ extern int git_path_walk_up( int (*fn)(void *state, git_buf *), void *state); +/** + * Load all directory entries (except '.' and '..') into a vector. + * + * For cases where `git_path_direach()` is not appropriate, this + * allows you to load the filenames in a directory into a vector + * of strings. That vector can then be sorted, iterated, or whatever. + * Remember to free alloc of the allocated strings when you are done. + * + * @param path The directory to read from. + * @param prefix_len When inserting entries, the trailing part of path + * will be prefixed after this length. I.e. given path "/a/b" and + * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. + * @param alloc_extra Extra bytes to add to each string allocation in + * case you want to append anything funny. + * @param contents Vector to fill with directory entry names. + */ +extern int git_path_dirload( + const char *path, + size_t prefix_len, + size_t alloc_extra, + git_vector *contents); + #endif diff --git a/src/vector.c b/src/vector.c index ba8499d4e..49909bbad 100644 --- a/src/vector.c +++ b/src/vector.c @@ -25,6 +25,23 @@ static int resize_vector(git_vector *v) return GIT_SUCCESS; } +int git_vector_alloc( + git_vector **vptr, unsigned int initial_size, git_vector_cmp cmp) +{ + int error; + git_vector *v = git__malloc(sizeof(git_vector)); + if (!v) { + *vptr = NULL; + return GIT_ENOMEM; + } + + if ((error = git_vector_init(v, initial_size, cmp)) < GIT_SUCCESS) { + git__free(v); + v = NULL; + } + *vptr = v; + return error; +} void git_vector_free(git_vector *v) { @@ -188,6 +205,21 @@ int git_vector_remove(git_vector *v, unsigned int idx) return GIT_SUCCESS; } +int git_vector_pop(git_vector *v, void **element) +{ + assert(v); + + if (v->length == 0) + return git__throw(GIT_ENOTFOUND, "Can't remove element from empty list"); + + if (element != NULL) + *element = v->contents[v->length - 1]; + + v->length--; + + return GIT_SUCCESS; +} + void git_vector_uniq(git_vector *v) { git_vector_cmp cmp; diff --git a/src/vector.h b/src/vector.h index ae3882558..c11e801cc 100644 --- a/src/vector.h +++ b/src/vector.h @@ -22,6 +22,7 @@ typedef struct git_vector { #define GIT_VECTOR_INIT {0} int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp); +int git_vector_alloc(git_vector **v, unsigned int initial_size, git_vector_cmp cmp); void git_vector_free(git_vector *v); void git_vector_clear(git_vector *v); @@ -38,6 +39,11 @@ GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position) return (position < v->length) ? v->contents[position] : NULL; } +GIT_INLINE(void *) git_vector_last(git_vector *v) +{ + return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; +} + #define git_vector_foreach(v, iter, elem) \ for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) @@ -48,6 +54,7 @@ int git_vector_insert(git_vector *v, void *element); int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void **old, void *new)); int git_vector_remove(git_vector *v, unsigned int idx); +int git_vector_pop(git_vector *v, void **element); void git_vector_uniq(git_vector *v); #endif diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c new file mode 100644 index 000000000..b2dbe9ee7 --- /dev/null +++ b/tests-clar/diff/diff_helpers.c @@ -0,0 +1,22 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +git_tree *resolve_commit_oid_to_tree( + git_repository *repo, + const char *partial_oid) +{ + size_t len = strlen(partial_oid); + git_oid oid; + git_object *obj; + git_tree *tree; + + if (git_oid_fromstrn(&oid, partial_oid, len) == 0) + git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJ_ANY); + cl_assert(obj); + if (git_object_type(obj) == GIT_OBJ_TREE) + return (git_tree *)obj; + cl_assert(git_object_type(obj) == GIT_OBJ_COMMIT); + cl_git_pass(git_commit_tree(&tree, (git_commit *)obj)); + git_object_free(obj); + return tree; +} diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h new file mode 100644 index 000000000..a75dd912c --- /dev/null +++ b/tests-clar/diff/diff_helpers.h @@ -0,0 +1,4 @@ +#include "fileops.h" + +extern git_tree *resolve_commit_oid_to_tree( + git_repository *repo, const char *partial_oid); diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c new file mode 100644 index 000000000..e13e3e2bb --- /dev/null +++ b/tests-clar/diff/iterator.c @@ -0,0 +1,362 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" +#include "iterator.h" + +static git_repository *g_repo = NULL; +static const char *g_sandbox = NULL; + +static void setup_sandbox(const char *sandbox) +{ + cl_fixture_sandbox(sandbox); + g_sandbox = sandbox; + + p_chdir(sandbox); + cl_git_pass(p_rename(".gitted", ".git")); + if (p_access("gitattributes", F_OK) == 0) + cl_git_pass(p_rename("gitattributes", ".gitattributes")); + if (p_access("gitignore", F_OK) == 0) + cl_git_pass(p_rename("gitignore", ".gitignore")); + p_chdir(".."); + + cl_git_pass(git_repository_open(&g_repo, sandbox)); +} + +static void cleanup_sandbox(void) +{ + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } + if (g_sandbox) { + cl_fixture_cleanup(g_sandbox); + g_sandbox = NULL; + } +} + +void test_diff_iterator__initialize(void) +{ + /* since we are doing tests with different sandboxes, defer setup + * to the actual tests. cleanup will still be done in the global + * cleanup function so that assertion failures don't result in a + * missed cleanup. + */ +} + +void test_diff_iterator__cleanup(void) +{ + cleanup_sandbox(); +} + + +/* -- TREE ITERATOR TESTS -- */ + +static void tree_iterator_test( + const char *sandbox, + const char *treeish, + int expected_count, + const char **expected_values) +{ + git_tree *t; + git_iterator *i; + const git_index_entry *entry; + int count = 0; + + setup_sandbox(sandbox); + + cl_assert(t = resolve_commit_oid_to_tree(g_repo, treeish)); + cl_git_pass(git_iterator_for_tree(g_repo, t, &i)); + cl_git_pass(git_iterator_current(i, &entry)); + + while (entry != NULL) { + if (expected_values != NULL) + cl_assert_strequal(expected_values[count], entry->path); + + count++; + + cl_git_pass(git_iterator_advance(i)); + cl_git_pass(git_iterator_current(i, &entry)); + } + + git_iterator_free(i); + + cl_assert(expected_count == count); + + git_tree_free(t); +} + +/* results of: git ls-tree -r --name-only 605812a */ +const char *expected_tree_0[] = { + ".gitattributes", + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_diff_iterator__tree_0(void) +{ + tree_iterator_test("attr", "605812a", 16, expected_tree_0); +} + +/* results of: git ls-tree -r --name-only 6bab5c79 */ +const char *expected_tree_1[] = { + ".gitattributes", + "attr0", + "attr1", + "attr2", + "attr3", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "subdir/.gitattributes", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_diff_iterator__tree_1(void) +{ + tree_iterator_test("attr", "6bab5c79cd5", 13, expected_tree_1); +} + +/* results of: git ls-tree -r --name-only 26a125ee1 */ +const char *expected_tree_2[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + NULL +}; + +void test_diff_iterator__tree_2(void) +{ + tree_iterator_test("status", "26a125ee1", 12, expected_tree_2); +} + +/* $ git ls-tree -r --name-only 0017bd4ab1e */ +const char *expected_tree_3[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file" +}; + +void test_diff_iterator__tree_3(void) +{ + tree_iterator_test("status", "0017bd4ab1e", 8, expected_tree_3); +} + + +/* -- INDEX ITERATOR TESTS -- */ + +static void index_iterator_test( + const char *sandbox, + int expected_count, + const char **expected_names, + const char **expected_oids) +{ + git_iterator *i; + const git_index_entry *entry; + int count = 0; + + setup_sandbox(sandbox); + + cl_git_pass(git_iterator_for_index(g_repo, &i)); + cl_git_pass(git_iterator_current(i, &entry)); + + while (entry != NULL) { + if (expected_names != NULL) + cl_assert_strequal(expected_names[count], entry->path); + + if (expected_oids != NULL) { + git_oid oid; + cl_git_pass(git_oid_fromstr(&oid, expected_oids[count])); + cl_assert(git_oid_cmp(&oid, &entry->oid) == 0); + } + + count++; + cl_git_pass(git_iterator_advance(i)); + cl_git_pass(git_iterator_current(i, &entry)); + } + + git_iterator_free(i); + + cl_assert(count == expected_count); +} + +static const char *expected_index_0[] = { + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", +}; + +static const char *expected_index_oids_0[] = { + "556f8c827b8e4a02ad5cab77dca2bcb3e226b0b3", + "3b74db7ab381105dc0d28f8295a77f6a82989292", + "2c66e14f77196ea763fb1e41612c1aa2bc2d8ed2", + "c485abe35abd4aa6fd83b076a78bbea9e2e7e06c", + "d800886d9c86731ae5c4a62b0b77c437015e00d2", + "2b40c5aca159b04ea8d20ffe36cdf8b09369b14a", + "5819a185d77b03325aaf87cafc771db36f6ddca7", + "ff69f8639ce2e6010b3f33a74160aad98b48da2b", + "45141a79a77842c59a63229403220a4e4be74e3d", + "45141a79a77842c59a63229403220a4e4be74e3d", + "45141a79a77842c59a63229403220a4e4be74e3d", + "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", + "99eae476896f4907224978b88e5ecaa6c5bb67a9", + "3e42ffc54a663f9401cc25843d6c0e71a33e4249", + "e563cf4758f0d646f1b14b76016aa17fa9e549a4", + "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", + "dccada462d3df8ac6de596fb8c896aba9344f941" +}; + +void test_diff_iterator__index_0(void) +{ + index_iterator_test("attr", 17, expected_index_0, expected_index_oids_0); +} + +static const char *expected_index_1[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", +}; + +static const char* expected_index_oids_1[] = { + "a0de7e0ac200c489c41c59dfa910154a70264e6e", + "5452d32f1dd538eb0405e8a83cc185f79e25e80f", + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a", + "55d316c9ba708999f1918e9677d01dfcae69c6b9", + "a6be623522ce87a1d862128ac42672604f7b468b", + "906ee7711f4f4928ddcb2a5f8fbc500deba0d2a8", + "529a16e8e762d4acb7b9636ff540a00831f9155a", + "90b8c29d8ba39434d1c63e1b093daaa26e5bd972", + "ed062903b8f6f3dccb2fa81117ba6590944ef9bd", + "e8ee89e15bbe9b20137715232387b3de5b28972e", + "53ace0d1cc1145a5f4fe4f78a186a60263190733", + "1888c805345ba265b0ee9449b8877b6064592058", + "a6191982709b746d5650e93c2acf34ef74e11504" +}; + +void test_diff_iterator__index_1(void) +{ + index_iterator_test("status", 13, expected_index_1, expected_index_oids_1); +} + + +/* -- WORKDIR ITERATOR TESTS -- */ + +static void workdir_iterator_test( + const char *sandbox, + int expected_count, + int expected_ignores, + const char **expected_names, + const char *an_ignored_name) +{ + git_iterator *i; + const git_index_entry *entry; + int count = 0, count_all = 0; + + setup_sandbox(sandbox); + + cl_git_pass(git_iterator_for_workdir(g_repo, &i)); + cl_git_pass(git_iterator_current(i, &entry)); + + while (entry != NULL) { + int ignored = git_iterator_current_is_ignored(i); + + if (expected_names != NULL) + cl_assert_strequal(expected_names[count_all], entry->path); + + if (an_ignored_name && strcmp(an_ignored_name,entry->path)==0) + cl_assert(ignored); + + if (!ignored) + count++; + count_all++; + + cl_git_pass(git_iterator_advance(i)); + cl_git_pass(git_iterator_current(i, &entry)); + } + + git_iterator_free(i); + + cl_assert(count == expected_count); + cl_assert(count_all == expected_count + expected_ignores); +} + +void test_diff_iterator__workdir_0(void) +{ + workdir_iterator_test("attr", 15, 4, NULL, "ign"); +} + +static const char *status_paths[] = { + "current_file", + "ignored_file", + "modified_file", + "new_file", + "staged_changes", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_modified_file", + "subdir/current_file", + "subdir/modified_file", + "subdir/new_file", + "subdir.txt", + NULL +}; + +void test_diff_iterator__workdir_1(void) +{ + workdir_iterator_test("status", 12, 1, status_paths, "ignored_file"); +} From da337c806468d2d8a27dfa9ee5e75e476f5ad546 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 22 Feb 2012 11:22:33 -0800 Subject: [PATCH 2/3] Iterator improvements from diff implementation This makes two changes to iterator behavior: first, advance can optionally do the work of returning the new current value. This is such a common pattern that it really cleans up usage. Second, for workdir iterators, this removes automatically iterating into directories. That seemed like a good idea, but when an entirely new directory hierarchy is introduced into the workdir, there is no reason to iterate into it if there are no corresponding entries in the tree/index that it is being compared to. This second change actually wasn't a lot of code because not descending into directories was already the behavior for ignored directories. This just extends that to all directories. --- src/iterator.c | 63 +++++++++++++++++++++++--------------- src/iterator.h | 33 +++++++++++--------- tests-clar/diff/iterator.c | 14 +++++---- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/src/iterator.c b/src/iterator.c index 8511d53eb..805ff643e 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -80,7 +80,7 @@ static int expand_tree_if_needed(git_iterator_tree *ti) te = git_tree_entry_byindex(tree, tree_idx); if (!entry_is_tree(te)) - return GIT_SUCCESS; + break; error = git_tree_lookup(&subtree, ti->repo, &te->oid); if (error != GIT_SUCCESS) @@ -98,13 +98,18 @@ static int expand_tree_if_needed(git_iterator_tree *ti) return GIT_SUCCESS; } -static int git_iterator__tree_advance(git_iterator *self) +static int git_iterator__tree_advance( + git_iterator *self, const git_index_entry **entry) { + int error = GIT_SUCCESS; git_iterator_tree *ti = (git_iterator_tree *)self; git_tree *tree = git_vector_last(&ti->tree_stack); unsigned int tree_idx = PTR_AS_IDX(git_vector_last(&ti->idx_stack)); const git_tree_entry *te = git_tree_entry_byindex(tree, tree_idx); + if (entry != NULL) + *entry = NULL; + if (te == NULL) return GIT_SUCCESS; @@ -133,9 +138,12 @@ static int git_iterator__tree_advance(git_iterator *self) } if (te && entry_is_tree(te)) - return expand_tree_if_needed(ti); + error = expand_tree_if_needed(ti); - return GIT_SUCCESS; + if (error == GIT_SUCCESS && entry != NULL) + error = git_iterator__tree_current(self, entry); + + return error; } static void git_iterator__tree_free(git_iterator *self) @@ -204,11 +212,14 @@ static int git_iterator__index_at_end(git_iterator *self) return (ii->current >= git_index_entrycount(ii->index)); } -static int git_iterator__index_advance(git_iterator *self) +static int git_iterator__index_advance( + git_iterator *self, const git_index_entry **entry) { git_iterator_index *ii = (git_iterator_index *)self; if (ii->current < git_index_entrycount(ii->index)) ii->current++; + if (entry) + *entry = git_index_get(ii->index, ii->current); return GIT_SUCCESS; } @@ -315,13 +326,18 @@ static int git_iterator__workdir_at_end(git_iterator *self) return (wi->entry.path == NULL); } -static int git_iterator__workdir_advance(git_iterator *self) +static int git_iterator__workdir_advance( + git_iterator *self, const git_index_entry **entry) { + int error; git_iterator_workdir *wi = (git_iterator_workdir *)self; git_vector *dir; unsigned int pos; const char *next; + if (entry) + *entry = NULL; + if (wi->entry.path == NULL) return GIT_SUCCESS; @@ -348,26 +364,32 @@ static int git_iterator__workdir_advance(git_iterator *self) git_ignore__pop_dir(&wi->ignores); } - return load_workdir_entry(wi); + error = load_workdir_entry(wi); + + if (error == GIT_SUCCESS && entry) + return git_iterator__workdir_current(self, entry); + + return error; } -int git_iterator_advance_into_ignored_directory(git_iterator *iter) +int git_iterator_advance_into_directory( + git_iterator *iter, const git_index_entry **entry) { git_iterator_workdir *wi = (git_iterator_workdir *)iter; if (iter->type != GIT_ITERATOR_WORKDIR) - return GIT_SUCCESS; + return git_iterator_current(iter, entry); /* Loop because the first entry in the ignored directory could itself be * an ignored directory, but we want to descend to find an actual entry. */ - while (wi->entry.path && wi->is_ignored && S_ISDIR(wi->entry.mode)) { - int error = push_directory(wi); - if (error != GIT_SUCCESS) - return error; + if (wi->entry.path && S_ISDIR(wi->entry.mode)) { + if (push_directory(wi) < GIT_SUCCESS) + /* If error loading or if empty, skip the directory. */ + return git_iterator__workdir_advance((git_iterator *)wi, entry); } - return GIT_SUCCESS; + return git_iterator__workdir_current(iter, entry); } static void git_iterator__workdir_free(git_iterator *self) @@ -404,7 +426,7 @@ static int load_workdir_entry(git_iterator_workdir *wi) wi->entry.path = relpath; if (strcmp(relpath, DOT_GIT) == 0) - return git_iterator__workdir_advance((git_iterator *)wi); + return git_iterator__workdir_advance((git_iterator *)wi, NULL); /* if there is an error processing the entry, treat as ignored */ wi->is_ignored = 1; @@ -433,19 +455,12 @@ static int load_workdir_entry(git_iterator_workdir *wi) if (git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS) { /* create submodule entry */ wi->entry.mode = S_IFGITLINK; - } else if (wi->is_ignored) { - /* create path entry (which is otherwise impossible), but is - * needed in case descent into ignore dir is required. - */ + } else { + /* create directory entry that can be advanced into as needed */ size_t pathlen = strlen(wi->entry.path); wi->entry.path[pathlen] = '/'; wi->entry.path[pathlen + 1] = '\0'; wi->entry.mode = S_IFDIR; - } else if ((error = push_directory(wi)) < GIT_SUCCESS) { - /* if there is an error loading the directory or if empty - * then skip over the directory completely. - */ - return git_iterator__workdir_advance((git_iterator *)wi); } } diff --git a/src/iterator.h b/src/iterator.h index 4aa1df52d..ac30b4ded 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -22,7 +22,7 @@ struct git_iterator { git_iterator_type_t type; int (*current)(git_iterator *, const git_index_entry **); int (*at_end)(git_iterator *); - int (*advance)(git_iterator *); + int (*advance)(git_iterator *, const git_index_entry **); void (*free)(git_iterator *); }; @@ -54,9 +54,10 @@ GIT_INLINE(int) git_iterator_at_end(git_iterator *iter) return iter->at_end(iter); } -GIT_INLINE(int) git_iterator_advance(git_iterator *iter) +GIT_INLINE(int) git_iterator_advance( + git_iterator *iter, const git_index_entry **entry) { - return iter->advance(iter); + return iter->advance(iter, entry); } GIT_INLINE(void) git_iterator_free(git_iterator *iter) @@ -76,19 +77,23 @@ extern int git_iterator_current_tree_entry( extern int git_iterator_current_is_ignored(git_iterator *iter); /** - * Iterate into an ignored workdir directory. + * Iterate into a workdir directory. * - * When a workdir iterator encounters a directory that is ignored, it will - * just return a current entry for the directory with is_ignored returning - * true. If you are iterating over the index or a tree in parallel and a - * file in the ignored directory has been added to the index/tree already, - * then it may be necessary to iterate into the directory even though it is - * ignored. Call this function to do that. + * Workdir iterators do not automatically descend into directories (so that + * when comparing two iterator entries you can detect a newly created + * directory in the workdir). As a result, you may get S_ISDIR items from + * a workdir iterator. If you wish to iterate over the contents of the + * directories you encounter, then call this function when you encounter + * a directory. * - * Note that if the tracked file in the ignored directory has been deleted, - * this may end up acting like a full "advance" call and advance past the - * directory completely. You must handle that case. + * If there are no files in the directory, this will end up acting like a + * regular advance and will skip past the directory, so you should be + * prepared for that case. + * + * On non-workdir iterators or if not pointing at a directory, this is a + * no-op and will not advance the iterator. */ -extern int git_iterator_advance_into_ignored_directory(git_iterator *iter); +extern int git_iterator_advance_into_directory( + git_iterator *iter, const git_index_entry **entry); #endif diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c index e13e3e2bb..46f8f59fb 100644 --- a/tests-clar/diff/iterator.c +++ b/tests-clar/diff/iterator.c @@ -73,8 +73,7 @@ static void tree_iterator_test( count++; - cl_git_pass(git_iterator_advance(i)); - cl_git_pass(git_iterator_current(i, &entry)); + cl_git_pass(git_iterator_advance(i, &entry)); } git_iterator_free(i); @@ -201,8 +200,7 @@ static void index_iterator_test( } count++; - cl_git_pass(git_iterator_advance(i)); - cl_git_pass(git_iterator_current(i, &entry)); + cl_git_pass(git_iterator_advance(i, &entry)); } git_iterator_free(i); @@ -314,6 +312,11 @@ static void workdir_iterator_test( while (entry != NULL) { int ignored = git_iterator_current_is_ignored(i); + if (!ignored && S_ISDIR(entry->mode)) { + cl_git_pass(git_iterator_advance_into_directory(i, &entry)); + continue; + } + if (expected_names != NULL) cl_assert_strequal(expected_names[count_all], entry->path); @@ -324,8 +327,7 @@ static void workdir_iterator_test( count++; count_all++; - cl_git_pass(git_iterator_advance(i)); - cl_git_pass(git_iterator_current(i, &entry)); + cl_git_pass(git_iterator_advance(i, &entry)); } git_iterator_free(i); From 0534641dfec001794ae9a83cfd1cfc7acaef97b7 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 22 Feb 2012 15:15:35 -0800 Subject: [PATCH 3/3] Fix iterators based on pull request feedback This update addresses all of the feedback in pull request #570. The biggest change was to create actual linked list stacks for storing the tree and workdir iterator state. This cleaned up the code a ton. Additionally, all of the static functions had their 'git_' prefix removed, and a lot of other unnecessary changes were removed from the original patch. --- src/ignore.c | 2 +- src/iterator.c | 429 ++++++++++++++++++++++------------------------- src/path.c | 20 +-- src/path.h | 6 +- src/repository.c | 10 +- src/vector.c | 33 +--- src/vector.h | 3 +- 7 files changed, 220 insertions(+), 283 deletions(-) diff --git a/src/ignore.c b/src/ignore.c index ecdd76005..30f86b822 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -147,7 +147,7 @@ int git_ignore__pop_dir(git_ignores *ign) if (ign->ign_path.length > 0) { git_attr_file *file = git_vector_last(&ign->ign_path); if (git__suffixcmp(ign->dir.ptr, file->path) == 0) - git_vector_pop(&ign->ign_path, NULL); + git_vector_pop(&ign->ign_path); git_buf_rtruncate_at_char(&ign->dir, '/'); } return GIT_SUCCESS; diff --git a/src/iterator.c b/src/iterator.c index 805ff643e..c2b88ab84 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -10,36 +10,33 @@ #include "ignore.h" #include "buffer.h" -#define IDX_AS_PTR(I) (void *)((uint64_t)(I)) -#define PTR_AS_IDX(P) (unsigned int)((uint64_t)(P)) +typedef struct tree_iterator_frame tree_iterator_frame; +struct tree_iterator_frame { + tree_iterator_frame *next; + git_tree *tree; + unsigned int index; +}; typedef struct { - git_iterator cb; + git_iterator base; git_repository *repo; - git_vector tree_stack; - git_vector idx_stack; + tree_iterator_frame *stack; git_index_entry entry; git_buf path; -} git_iterator_tree; +} tree_iterator; -static const git_tree_entry *git_iterator__tree_entry(git_iterator_tree *ti) +static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti) { - git_tree *tree; - unsigned int tree_idx; - - if ((tree = git_vector_last(&ti->tree_stack)) == NULL) - return NULL; - - tree_idx = PTR_AS_IDX(git_vector_last(&ti->idx_stack)); - return git_tree_entry_byindex(tree, tree_idx); + return (ti->stack == NULL) ? NULL : + git_tree_entry_byindex(ti->stack->tree, ti->stack->index); } -static int git_iterator__tree_current( +static int tree_iterator__current( git_iterator *self, const git_index_entry **entry) { int error; - git_iterator_tree *ti = (git_iterator_tree *)self; - const git_tree_entry *te = git_iterator__tree_entry(ti); + tree_iterator *ti = (tree_iterator *)self; + const git_tree_entry *te = tree_iterator__tree_entry(ti); *entry = NULL; @@ -58,132 +55,111 @@ static int git_iterator__tree_current( return GIT_SUCCESS; } -static int git_iterator__tree_at_end(git_iterator *self) +static int tree_iterator__at_end(git_iterator *self) { - git_iterator_tree *ti = (git_iterator_tree *)self; - git_tree *tree; - return ((tree = git_vector_last(&ti->tree_stack)) == NULL || - git_tree_entry_byindex( - tree, PTR_AS_IDX(git_vector_last(&ti->idx_stack))) == NULL); + return (tree_iterator__tree_entry((tree_iterator *)self) == NULL); } -static int expand_tree_if_needed(git_iterator_tree *ti) +static tree_iterator_frame *tree_iterator__alloc_frame(git_tree *tree) +{ + tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame)); + tf->tree = tree; + return tf; +} + +static int tree_iterator__expand_tree(tree_iterator *ti) { int error; - git_tree *tree, *subtree; - unsigned int tree_idx; - const git_tree_entry *te; - - while (1) { - tree = git_vector_last(&ti->tree_stack); - tree_idx = PTR_AS_IDX(git_vector_last(&ti->idx_stack)); - te = git_tree_entry_byindex(tree, tree_idx); - - if (!entry_is_tree(te)) - break; + git_tree *subtree; + const git_tree_entry *te = tree_iterator__tree_entry(ti); + tree_iterator_frame *tf; + while (te != NULL && entry_is_tree(te)) { error = git_tree_lookup(&subtree, ti->repo, &te->oid); if (error != GIT_SUCCESS) return error; - if ((error = git_vector_insert(&ti->tree_stack, subtree)) < GIT_SUCCESS || - (error = git_vector_insert(&ti->idx_stack, IDX_AS_PTR(0))) < GIT_SUCCESS || - (error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename)) < GIT_SUCCESS) - { - git_tree_free(subtree); + if ((tf = tree_iterator__alloc_frame(subtree)) == NULL) + return GIT_ENOMEM; + + tf->next = ti->stack; + ti->stack = tf; + + error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename); + if (error < GIT_SUCCESS) return error; - } + + te = tree_iterator__tree_entry(ti); } return GIT_SUCCESS; } -static int git_iterator__tree_advance( +static void tree_iterator__pop_frame(tree_iterator *ti) +{ + tree_iterator_frame *tf = ti->stack; + ti->stack = tf->next; + if (ti->stack != NULL) /* don't free the initial tree */ + git_tree_free(tf->tree); + git__free(tf); +} + +static int tree_iterator__advance( git_iterator *self, const git_index_entry **entry) { int error = GIT_SUCCESS; - git_iterator_tree *ti = (git_iterator_tree *)self; - git_tree *tree = git_vector_last(&ti->tree_stack); - unsigned int tree_idx = PTR_AS_IDX(git_vector_last(&ti->idx_stack)); - const git_tree_entry *te = git_tree_entry_byindex(tree, tree_idx); + tree_iterator *ti = (tree_iterator *)self; + const git_tree_entry *te; if (entry != NULL) *entry = NULL; - if (te == NULL) - return GIT_SUCCESS; - - while (1) { - /* advance this tree */ - tree_idx++; - ti->idx_stack.contents[ti->idx_stack.length - 1] = IDX_AS_PTR(tree_idx); - + while (ti->stack != NULL) { /* remove old entry filename */ git_buf_rtruncate_at_char(&ti->path, '/'); - if ((te = git_tree_entry_byindex(tree, tree_idx)) != NULL) + te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index); + if (te != NULL) break; - /* no entry - either we are done or we are done with this subtree */ - if (ti->tree_stack.length == 1) - return GIT_SUCCESS; - - git_tree_free(tree); - git_vector_remove(&ti->tree_stack, ti->tree_stack.length - 1); - git_vector_remove(&ti->idx_stack, ti->idx_stack.length - 1); + tree_iterator__pop_frame(ti); git_buf_rtruncate_at_char(&ti->path, '/'); - - tree = git_vector_last(&ti->tree_stack); - tree_idx = PTR_AS_IDX(git_vector_last(&ti->idx_stack)); } if (te && entry_is_tree(te)) - error = expand_tree_if_needed(ti); + error = tree_iterator__expand_tree(ti); if (error == GIT_SUCCESS && entry != NULL) - error = git_iterator__tree_current(self, entry); + error = tree_iterator__current(self, entry); return error; } -static void git_iterator__tree_free(git_iterator *self) +static void tree_iterator__free(git_iterator *self) { - git_iterator_tree *ti = (git_iterator_tree *)self; - - while (ti->tree_stack.length > 1) { - git_tree *tree = git_vector_last(&ti->tree_stack); - git_tree_free(tree); - git_vector_remove(&ti->tree_stack, ti->tree_stack.length - 1); - } - - git_vector_clear(&ti->tree_stack); - git_vector_clear(&ti->idx_stack); + tree_iterator *ti = (tree_iterator *)self; + while (ti->stack != NULL) + tree_iterator__pop_frame(ti); git_buf_free(&ti->path); } -int git_iterator_for_tree(git_repository *repo, git_tree *tree, git_iterator **iter) +int git_iterator_for_tree( + git_repository *repo, git_tree *tree, git_iterator **iter) { int error; - git_iterator_tree *ti = git__calloc(1, sizeof(git_iterator_tree)); + tree_iterator *ti = git__calloc(1, sizeof(tree_iterator)); if (!ti) return GIT_ENOMEM; - ti->cb.type = GIT_ITERATOR_TREE; - ti->cb.current = git_iterator__tree_current; - ti->cb.at_end = git_iterator__tree_at_end; - ti->cb.advance = git_iterator__tree_advance; - ti->cb.free = git_iterator__tree_free; - ti->repo = repo; + ti->base.type = GIT_ITERATOR_TREE; + ti->base.current = tree_iterator__current; + ti->base.at_end = tree_iterator__at_end; + ti->base.advance = tree_iterator__advance; + ti->base.free = tree_iterator__free; + ti->repo = repo; + ti->stack = tree_iterator__alloc_frame(tree); - if (!(error = git_vector_init(&ti->tree_stack, 0, NULL)) && - !(error = git_vector_insert(&ti->tree_stack, tree)) && - !(error = git_vector_init(&ti->idx_stack, 0, NULL))) - error = git_vector_insert(&ti->idx_stack, IDX_AS_PTR(0)); - - if (error == GIT_SUCCESS) - error = expand_tree_if_needed(ti); - - if (error != GIT_SUCCESS) + if ((error = tree_iterator__expand_tree(ti)) < GIT_SUCCESS) git_iterator_free((git_iterator *)ti); else *iter = (git_iterator *)ti; @@ -193,29 +169,29 @@ int git_iterator_for_tree(git_repository *repo, git_tree *tree, git_iterator **i typedef struct { - git_iterator cb; + git_iterator base; git_index *index; unsigned int current; -} git_iterator_index; +} index_iterator; -static int git_iterator__index_current( +static int index_iterator__current( git_iterator *self, const git_index_entry **entry) { - git_iterator_index *ii = (git_iterator_index *)self; + index_iterator *ii = (index_iterator *)self; *entry = git_index_get(ii->index, ii->current); return GIT_SUCCESS; } -static int git_iterator__index_at_end(git_iterator *self) +static int index_iterator__at_end(git_iterator *self) { - git_iterator_index *ii = (git_iterator_index *)self; + index_iterator *ii = (index_iterator *)self; return (ii->current >= git_index_entrycount(ii->index)); } -static int git_iterator__index_advance( +static int index_iterator__advance( git_iterator *self, const git_index_entry **entry) { - git_iterator_index *ii = (git_iterator_index *)self; + index_iterator *ii = (index_iterator *)self; if (ii->current < git_index_entrycount(ii->index)) ii->current++; if (entry) @@ -223,9 +199,9 @@ static int git_iterator__index_advance( return GIT_SUCCESS; } -static void git_iterator__index_free(git_iterator *self) +static void index_iterator__free(git_iterator *self) { - git_iterator_index *ii = (git_iterator_index *)self; + index_iterator *ii = (index_iterator *)self; git_index_free(ii->index); ii->index = NULL; } @@ -233,16 +209,16 @@ static void git_iterator__index_free(git_iterator *self) int git_iterator_for_index(git_repository *repo, git_iterator **iter) { int error; - git_iterator_index *ii = git__calloc(1, sizeof(git_iterator_index)); + index_iterator *ii = git__calloc(1, sizeof(index_iterator)); if (!ii) return GIT_ENOMEM; - ii->cb.type = GIT_ITERATOR_INDEX; - ii->cb.current = git_iterator__index_current; - ii->cb.at_end = git_iterator__index_at_end; - ii->cb.advance = git_iterator__index_advance; - ii->cb.free = git_iterator__index_free; - ii->current = 0; + ii->base.type = GIT_ITERATOR_INDEX; + ii->base.current = index_iterator__current; + ii->base.at_end = index_iterator__at_end; + ii->base.advance = index_iterator__advance; + ii->base.free = index_iterator__free; + ii->current = 0; if ((error = git_repository_index(&ii->index, repo)) < GIT_SUCCESS) git__free(ii); @@ -252,101 +228,107 @@ int git_iterator_for_index(git_repository *repo, git_iterator **iter) } +typedef struct workdir_iterator_frame workdir_iterator_frame; +struct workdir_iterator_frame { + workdir_iterator_frame *next; + git_vector entries; + unsigned int index; +}; + typedef struct { - git_iterator cb; + git_iterator base; git_repository *repo; size_t root_len; - git_vector dir_stack; /* vector of vectors of paths */ - git_vector idx_stack; + workdir_iterator_frame *stack; git_ignores ignores; git_index_entry entry; git_buf path; int is_ignored; -} git_iterator_workdir; +} workdir_iterator; -static void free_directory(git_vector *dir) +static workdir_iterator_frame *workdir_iterator__alloc_frame(void) +{ + workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame)); + if (wf == NULL) + return wf; + if (git_vector_init(&wf->entries, 0, git__strcmp_cb) != GIT_SUCCESS) { + git__free(wf); + return NULL; + } + return wf; +} + +static void workdir_iterator__free_frame(workdir_iterator_frame *wf) { unsigned int i; char *path; - git_vector_foreach(dir, i, path) + git_vector_foreach(&wf->entries, i, path) git__free(path); - git_vector_free(dir); - git__free(dir); + git_vector_free(&wf->entries); + git__free(wf); } -static int load_workdir_entry(git_iterator_workdir *wi); +static int workdir_iterator__update_entry(workdir_iterator *wi); -static int push_directory(git_iterator_workdir *wi) +static int workdir_iterator__expand_dir(workdir_iterator *wi) { int error; - git_vector *dir = NULL; + workdir_iterator_frame *wf = workdir_iterator__alloc_frame(); + if (wf == NULL) + return GIT_ENOMEM; - error = git_vector_alloc(&dir, 0, git__strcmp_cb); - if (error < GIT_SUCCESS) - return error; - - /* allocate dir entries with extra byte (the "1" param) so later on we - * can suffix directories with a "/" as needed. + /* allocate dir entries with extra byte (the "1" param) so we + * can suffix directory names with a "/". */ - error = git_path_dirload(wi->path.ptr, wi->root_len, 1, dir); - if (error < GIT_SUCCESS || dir->length == 0) { - free_directory(dir); + error = git_path_dirload(wi->path.ptr, wi->root_len, 1, &wf->entries); + if (error < GIT_SUCCESS || wf->entries.length == 0) { + workdir_iterator__free_frame(wf); return GIT_ENOTFOUND; } - if ((error = git_vector_insert(&wi->dir_stack, dir)) || - (error = git_vector_insert(&wi->idx_stack, IDX_AS_PTR(0)))) - { - free_directory(dir); - return error; - } + git_vector_sort(&wf->entries); + wf->next = wi->stack; + wi->stack = wf; - git_vector_sort(dir); - - if (wi->dir_stack.length > 1) { + /* only push new ignores if this is not top level directory */ + if (wi->stack->next != NULL) { int slash_pos = git_buf_rfind_next(&wi->path, '/'); (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]); } - return load_workdir_entry(wi); + return workdir_iterator__update_entry(wi); } -static int git_iterator__workdir_current( +static int workdir_iterator__current( git_iterator *self, const git_index_entry **entry) { - git_iterator_workdir *wi = (git_iterator_workdir *)self; + workdir_iterator *wi = (workdir_iterator *)self; *entry = (wi->entry.path == NULL) ? NULL : &wi->entry; return GIT_SUCCESS; } -static int git_iterator__workdir_at_end(git_iterator *self) +static int workdir_iterator__at_end(git_iterator *self) { - git_iterator_workdir *wi = (git_iterator_workdir *)self; - return (wi->entry.path == NULL); + return (((workdir_iterator *)self)->entry.path == NULL); } -static int git_iterator__workdir_advance( +static int workdir_iterator__advance( git_iterator *self, const git_index_entry **entry) { int error; - git_iterator_workdir *wi = (git_iterator_workdir *)self; - git_vector *dir; - unsigned int pos; + workdir_iterator *wi = (workdir_iterator *)self; + workdir_iterator_frame *wf; const char *next; - if (entry) + if (entry != NULL) *entry = NULL; if (wi->entry.path == NULL) return GIT_SUCCESS; - while (1) { - dir = git_vector_last(&wi->dir_stack); - pos = 1 + PTR_AS_IDX(git_vector_last(&wi->idx_stack)); - wi->idx_stack.contents[wi->idx_stack.length - 1] = IDX_AS_PTR(pos); - - next = git_vector_get(dir, pos); + while ((wf = wi->stack) != NULL) { + next = git_vector_get(&wf->entries, ++wf->index); if (next != NULL) { if (strcmp(next, DOT_GIT) == 0) continue; @@ -354,69 +336,45 @@ static int git_iterator__workdir_advance( break; } - memset(&wi->entry, 0, sizeof(wi->entry)); - if (wi->dir_stack.length == 1) - return GIT_SUCCESS; - - free_directory(dir); - git_vector_remove(&wi->dir_stack, wi->dir_stack.length - 1); - git_vector_remove(&wi->idx_stack, wi->idx_stack.length - 1); + /* pop workdir directory stack */ + wi->stack = wf->next; + workdir_iterator__free_frame(wf); git_ignore__pop_dir(&wi->ignores); + + if (wi->stack == NULL) { + memset(&wi->entry, 0, sizeof(wi->entry)); + return GIT_SUCCESS; + } } - error = load_workdir_entry(wi); + error = workdir_iterator__update_entry(wi); - if (error == GIT_SUCCESS && entry) - return git_iterator__workdir_current(self, entry); + if (error == GIT_SUCCESS && entry != NULL) + error = workdir_iterator__current(self, entry); return error; } -int git_iterator_advance_into_directory( - git_iterator *iter, const git_index_entry **entry) +static void workdir_iterator__free(git_iterator *self) { - git_iterator_workdir *wi = (git_iterator_workdir *)iter; + workdir_iterator *wi = (workdir_iterator *)self; - if (iter->type != GIT_ITERATOR_WORKDIR) - return git_iterator_current(iter, entry); - - /* Loop because the first entry in the ignored directory could itself be - * an ignored directory, but we want to descend to find an actual entry. - */ - if (wi->entry.path && S_ISDIR(wi->entry.mode)) { - if (push_directory(wi) < GIT_SUCCESS) - /* If error loading or if empty, skip the directory. */ - return git_iterator__workdir_advance((git_iterator *)wi, entry); + while (wi->stack != NULL) { + workdir_iterator_frame *wf = wi->stack; + wi->stack = wf->next; + workdir_iterator__free_frame(wf); } - return git_iterator__workdir_current(iter, entry); -} - -static void git_iterator__workdir_free(git_iterator *self) -{ - git_iterator_workdir *wi = (git_iterator_workdir *)self; - - while (wi->dir_stack.length) { - git_vector *dir = git_vector_last(&wi->dir_stack); - free_directory(dir); - git_vector_remove(&wi->dir_stack, wi->dir_stack.length - 1); - } - - git_vector_clear(&wi->dir_stack); - git_vector_clear(&wi->idx_stack); git_ignore__free(&wi->ignores); git_buf_free(&wi->path); } -static int load_workdir_entry(git_iterator_workdir *wi) +static int workdir_iterator__update_entry(workdir_iterator *wi) { int error; - char *relpath; - git_vector *dir = git_vector_last(&wi->dir_stack); - unsigned int pos = PTR_AS_IDX(git_vector_last(&wi->idx_stack)); struct stat st; + char *relpath = git_vector_get(&wi->stack->entries, wi->stack->index); - relpath = git_vector_get(dir, pos); error = git_buf_joinpath( &wi->path, git_repository_workdir(wi->repo), relpath); if (error < GIT_SUCCESS) @@ -425,14 +383,12 @@ static int load_workdir_entry(git_iterator_workdir *wi) memset(&wi->entry, 0, sizeof(wi->entry)); wi->entry.path = relpath; + /* skip over .git directory */ if (strcmp(relpath, DOT_GIT) == 0) - return git_iterator__workdir_advance((git_iterator *)wi, NULL); + return workdir_iterator__advance((git_iterator *)wi, NULL); /* if there is an error processing the entry, treat as ignored */ wi->is_ignored = 1; - error = git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored); - if (error != GIT_SUCCESS) - return GIT_SUCCESS; if (p_lstat(wi->path.ptr, &st) < 0) return GIT_SUCCESS; @@ -451,6 +407,11 @@ static int load_workdir_entry(git_iterator_workdir *wi) if (st.st_mode == 0) return GIT_SUCCESS; + /* okay, we are far enough along to look up real ignore rule */ + error = git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored); + if (error != GIT_SUCCESS) + return GIT_SUCCESS; + if (S_ISDIR(st.st_mode)) { if (git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS) { /* create submodule entry */ @@ -470,30 +431,28 @@ static int load_workdir_entry(git_iterator_workdir *wi) int git_iterator_for_workdir(git_repository *repo, git_iterator **iter) { int error; - git_iterator_workdir *wi = git__calloc(1, sizeof(git_iterator_workdir)); + workdir_iterator *wi = git__calloc(1, sizeof(workdir_iterator)); if (!wi) return GIT_ENOMEM; - wi->cb.type = GIT_ITERATOR_WORKDIR; - wi->cb.current = git_iterator__workdir_current; - wi->cb.at_end = git_iterator__workdir_at_end; - wi->cb.advance = git_iterator__workdir_advance; - wi->cb.free = git_iterator__workdir_free; - wi->repo = repo; + wi->base.type = GIT_ITERATOR_WORKDIR; + wi->base.current = workdir_iterator__current; + wi->base.at_end = workdir_iterator__at_end; + wi->base.advance = workdir_iterator__advance; + wi->base.free = workdir_iterator__free; + wi->repo = repo; - if ((error = git_buf_sets( - &wi->path, git_repository_workdir(repo))) < GIT_SUCCESS || - (error = git_vector_init(&wi->dir_stack, 0, NULL)) < GIT_SUCCESS || - (error = git_vector_init(&wi->idx_stack, 0, NULL)) < GIT_SUCCESS || - (error = git_ignore__for_path(repo, "", &wi->ignores)) < GIT_SUCCESS) - { + error = git_buf_sets(&wi->path, git_repository_workdir(repo)); + if (error == GIT_SUCCESS) + error = git_ignore__for_path(repo, "", &wi->ignores); + if (error != GIT_SUCCESS) { git__free(wi); return error; } wi->root_len = wi->path.size; - if ((error = push_directory(wi)) < GIT_SUCCESS) + if ((error = workdir_iterator__expand_dir(wi)) < GIT_SUCCESS) git_iterator_free((git_iterator *)wi); else *iter = (git_iterator *)wi; @@ -501,21 +460,33 @@ int git_iterator_for_workdir(git_repository *repo, git_iterator **iter) return error; } + int git_iterator_current_tree_entry( git_iterator *iter, const git_tree_entry **tree_entry) { - if (iter->type != GIT_ITERATOR_TREE) - *tree_entry = NULL; - else - *tree_entry = git_iterator__tree_entry((git_iterator_tree *)iter); - + *tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL : + tree_iterator__tree_entry((tree_iterator *)iter); return GIT_SUCCESS; } int git_iterator_current_is_ignored(git_iterator *iter) { - if (iter->type != GIT_ITERATOR_WORKDIR) - return 0; - else - return ((git_iterator_workdir *)iter)->is_ignored; + return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 : + ((workdir_iterator *)iter)->is_ignored; +} + +int git_iterator_advance_into_directory( + git_iterator *iter, const git_index_entry **entry) +{ + workdir_iterator *wi = (workdir_iterator *)iter; + + if (iter->type == GIT_ITERATOR_WORKDIR && + wi->entry.path && S_ISDIR(wi->entry.mode)) + { + if (workdir_iterator__expand_dir(wi) < GIT_SUCCESS) + /* if error loading or if empty, skip the directory. */ + return workdir_iterator__advance(iter, entry); + } + + return entry ? git_iterator_current(iter, entry) : GIT_SUCCESS; } diff --git a/src/path.c b/src/path.c index 6f46dc95e..88ea95a97 100644 --- a/src/path.c +++ b/src/path.c @@ -398,42 +398,38 @@ int git_path_isfile(const char *path) static int _check_dir_contents( git_buf *dir, const char *sub, - int append_on_success, int (*predicate)(const char *)) { int error = GIT_SUCCESS; size_t dir_size = dir->size; size_t sub_size = strlen(sub); - /* leave base valid even if we could not make space for subdir */ + /* separate allocation and join, so we can always leave git_buf valid */ if ((error = git_buf_try_grow(dir, dir_size + sub_size + 2)) < GIT_SUCCESS) return error; - - /* save excursion */ git_buf_joinpath(dir, dir->ptr, sub); error = (*predicate)(dir->ptr); - /* restore excursion */ - if (!append_on_success || error != GIT_SUCCESS) - git_buf_truncate(dir, dir_size); + /* restore path */ + git_buf_truncate(dir, dir_size); return error; } int git_path_contains(git_buf *dir, const char *item) { - return _check_dir_contents(dir, item, 0, &git_path_exists); + return _check_dir_contents(dir, item, &git_path_exists); } -int git_path_contains_dir(git_buf *base, const char *subdir, int append_if_exists) +int git_path_contains_dir(git_buf *base, const char *subdir) { - return _check_dir_contents(base, subdir, append_if_exists, &git_path_isdir); + return _check_dir_contents(base, subdir, &git_path_isdir); } -int git_path_contains_file(git_buf *base, const char *file, int append_if_exists) +int git_path_contains_file(git_buf *base, const char *file) { - return _check_dir_contents(base, file, append_if_exists, &git_path_isfile); + return _check_dir_contents(base, file, &git_path_isfile); } int git_path_find_dir(git_buf *dir, const char *path, const char *base) diff --git a/src/path.h b/src/path.h index 7a4f1f4fd..abe6c2217 100644 --- a/src/path.h +++ b/src/path.h @@ -143,20 +143,18 @@ extern int git_path_contains(git_buf *dir, const char *item); * * @param parent Directory path that might contain subdir * @param subdir Subdirectory name to look for in parent - * @param append_if_exists If true, then subdir will be appended to the parent path if it does exist * @return GIT_SUCCESS if subdirectory exists, < 0 otherwise. */ -extern int git_path_contains_dir(git_buf *parent, const char *subdir, int append_if_exists); +extern int git_path_contains_dir(git_buf *parent, const char *subdir); /** * Check if the given path contains the given file. * * @param dir Directory path that might contain file * @param file File name to look for in parent - * @param append_if_exists If true, then file will be appended to the path if it does exist * @return GIT_SUCCESS if file exists, < 0 otherwise. */ -extern int git_path_contains_file(git_buf *dir, const char *file, int append_if_exists); +extern int git_path_contains_file(git_buf *dir, const char *file); /** * Clean up path, prepending base if it is not already rooted. diff --git a/src/repository.c b/src/repository.c index 13ad7eb02..f394d06fe 100644 --- a/src/repository.c +++ b/src/repository.c @@ -81,14 +81,14 @@ void git_repository_free(git_repository *repo) static int quickcheck_repository_dir(git_buf *repository_path) { /* Check OBJECTS_DIR first, since it will generate the longest path name */ - if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR, 0) < 0) + if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) < 0) return GIT_ERROR; /* Ensure HEAD file exists */ - if (git_path_contains_file(repository_path, GIT_HEAD_FILE, 0) < 0) + if (git_path_contains_file(repository_path, GIT_HEAD_FILE) < 0) return GIT_ERROR; - if (git_path_contains_dir(repository_path, GIT_REFS_DIR, 0) < 0) + if (git_path_contains_dir(repository_path, GIT_REFS_DIR) < 0) return GIT_ERROR; return GIT_SUCCESS; @@ -166,8 +166,8 @@ int git_repository_open(git_repository **repo_out, const char *path) * of the working dir, by testing if it contains a `.git` * folder inside of it. */ - git_path_contains_dir(&path_buf, GIT_DIR, 1); /* append on success */ - /* ignore error, since it just means `path/.git` doesn't exist */ + if (git_path_contains_dir(&path_buf, GIT_DIR) == GIT_SUCCESS) + git_buf_joinpath(&path_buf, path_buf.ptr, GIT_DIR); if (quickcheck_repository_dir(&path_buf) < GIT_SUCCESS) { error = git__throw(GIT_ENOTAREPO, diff --git a/src/vector.c b/src/vector.c index 49909bbad..e109704ab 100644 --- a/src/vector.c +++ b/src/vector.c @@ -25,24 +25,6 @@ static int resize_vector(git_vector *v) return GIT_SUCCESS; } -int git_vector_alloc( - git_vector **vptr, unsigned int initial_size, git_vector_cmp cmp) -{ - int error; - git_vector *v = git__malloc(sizeof(git_vector)); - if (!v) { - *vptr = NULL; - return GIT_ENOMEM; - } - - if ((error = git_vector_init(v, initial_size, cmp)) < GIT_SUCCESS) { - git__free(v); - v = NULL; - } - *vptr = v; - return error; -} - void git_vector_free(git_vector *v) { assert(v); @@ -205,19 +187,10 @@ int git_vector_remove(git_vector *v, unsigned int idx) return GIT_SUCCESS; } -int git_vector_pop(git_vector *v, void **element) +void git_vector_pop(git_vector *v) { - assert(v); - - if (v->length == 0) - return git__throw(GIT_ENOTFOUND, "Can't remove element from empty list"); - - if (element != NULL) - *element = v->contents[v->length - 1]; - - v->length--; - - return GIT_SUCCESS; + if (v->length > 0) + v->length--; } void git_vector_uniq(git_vector *v) diff --git a/src/vector.h b/src/vector.h index c11e801cc..44635ae14 100644 --- a/src/vector.h +++ b/src/vector.h @@ -22,7 +22,6 @@ typedef struct git_vector { #define GIT_VECTOR_INIT {0} int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp); -int git_vector_alloc(git_vector **v, unsigned int initial_size, git_vector_cmp cmp); void git_vector_free(git_vector *v); void git_vector_clear(git_vector *v); @@ -54,7 +53,7 @@ int git_vector_insert(git_vector *v, void *element); int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void **old, void *new)); int git_vector_remove(git_vector *v, unsigned int idx); -int git_vector_pop(git_vector *v, void **element); +void git_vector_pop(git_vector *v); void git_vector_uniq(git_vector *v); #endif