diff options
author | Russell Belfer <arrbee@arrbee.com> | 2012-02-22 02:46:24 +0400 |
---|---|---|
committer | Russell Belfer <arrbee@arrbee.com> | 2012-02-22 02:46:24 +0400 |
commit | b6c93aef4276051f9c4536ecbed48f4cd093bd1b (patch) | |
tree | a15962c672890c0c8cc021dafa7d29487f81c75a /src | |
parent | 9c94a356cc61daa85e17c6342db9b3d62f788802 (diff) |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/attr.c | 70 | ||||
-rw-r--r-- | src/attr.h | 7 | ||||
-rw-r--r-- | src/buffer.c | 8 | ||||
-rw-r--r-- | src/buffer.h | 1 | ||||
-rw-r--r-- | src/fileops.c | 18 | ||||
-rw-r--r-- | src/fileops.h | 7 | ||||
-rw-r--r-- | src/ignore.c | 114 | ||||
-rw-r--r-- | src/ignore.h | 24 | ||||
-rw-r--r-- | src/iterator.c | 506 | ||||
-rw-r--r-- | src/iterator.h | 94 | ||||
-rw-r--r-- | src/path.c | 65 | ||||
-rw-r--r-- | src/path.h | 32 | ||||
-rw-r--r-- | src/vector.c | 32 | ||||
-rw-r--r-- | src/vector.h | 7 |
14 files changed, 924 insertions, 61 deletions
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,13 +85,18 @@ 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 * style mapping over some form of malloc()+read(), as the 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); - *ignored = 0; + /* first process builtins */ + error = ignore_lookup_in_rules( + &ignores->ign_internal->rules, &path, ignored); + if (error == GIT_SUCCESS) + return error; - 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; - } - } + /* 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; } -found: - 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; + + 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 @@ -129,6 +130,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. * * @param parent Directory path that might contain subdir @@ -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 |