diff options
author | Vicent Martà <tanoku@gmail.com> | 2012-02-22 16:06:33 -0800 |
---|---|---|
committer | Vicent Martà <tanoku@gmail.com> | 2012-02-22 16:06:33 -0800 |
commit | 36d72a5125b28a381b240b1bc506bcf2d59c94b7 (patch) | |
tree | 9c9d5ffe6d0c344d1a0eb6f8015efebc4da47308 /src | |
parent | 8d36b253e2a96b493f45ebc89260627c002b0bab (diff) | |
parent | 0534641dfec001794ae9a83cfd1cfc7acaef97b7 (diff) | |
download | libgit2-36d72a5125b28a381b240b1bc506bcf2d59c94b7.tar.gz |
Merge pull request #570 from arrbee/uniform-iterators
Uniform iterators for trees, index, and workdir
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 | 492 | ||||
-rw-r--r-- | src/iterator.h | 99 | ||||
-rw-r--r-- | src/path.c | 83 | ||||
-rw-r--r-- | src/path.h | 38 | ||||
-rw-r--r-- | src/repository.c | 10 | ||||
-rw-r--r-- | src/vector.c | 7 | ||||
-rw-r--r-- | src/vector.h | 6 |
15 files changed, 902 insertions, 82 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..30f86b822 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); + 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..c2b88ab84 --- /dev/null +++ b/src/iterator.c @@ -0,0 +1,492 @@ +/* + * 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" + +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 base; + git_repository *repo; + tree_iterator_frame *stack; + git_index_entry entry; + git_buf path; +} tree_iterator; + +static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti) +{ + return (ti->stack == NULL) ? NULL : + git_tree_entry_byindex(ti->stack->tree, ti->stack->index); +} + +static int tree_iterator__current( + git_iterator *self, const git_index_entry **entry) +{ + int error; + tree_iterator *ti = (tree_iterator *)self; + const git_tree_entry *te = tree_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 tree_iterator__at_end(git_iterator *self) +{ + return (tree_iterator__tree_entry((tree_iterator *)self) == NULL); +} + +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 *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 ((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 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; + tree_iterator *ti = (tree_iterator *)self; + const git_tree_entry *te; + + if (entry != NULL) + *entry = NULL; + + while (ti->stack != NULL) { + /* remove old entry filename */ + git_buf_rtruncate_at_char(&ti->path, '/'); + + te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index); + if (te != NULL) + break; + + tree_iterator__pop_frame(ti); + git_buf_rtruncate_at_char(&ti->path, '/'); + } + + if (te && entry_is_tree(te)) + error = tree_iterator__expand_tree(ti); + + if (error == GIT_SUCCESS && entry != NULL) + error = tree_iterator__current(self, entry); + + return error; +} + +static void tree_iterator__free(git_iterator *self) +{ + 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 error; + tree_iterator *ti = git__calloc(1, sizeof(tree_iterator)); + if (!ti) + return GIT_ENOMEM; + + 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 = tree_iterator__expand_tree(ti)) < GIT_SUCCESS) + git_iterator_free((git_iterator *)ti); + else + *iter = (git_iterator *)ti; + + return error; +} + + +typedef struct { + git_iterator base; + git_index *index; + unsigned int current; +} index_iterator; + +static int index_iterator__current( + git_iterator *self, const git_index_entry **entry) +{ + index_iterator *ii = (index_iterator *)self; + *entry = git_index_get(ii->index, ii->current); + return GIT_SUCCESS; +} + +static int index_iterator__at_end(git_iterator *self) +{ + index_iterator *ii = (index_iterator *)self; + return (ii->current >= git_index_entrycount(ii->index)); +} + +static int index_iterator__advance( + git_iterator *self, const git_index_entry **entry) +{ + index_iterator *ii = (index_iterator *)self; + if (ii->current < git_index_entrycount(ii->index)) + ii->current++; + if (entry) + *entry = git_index_get(ii->index, ii->current); + return GIT_SUCCESS; +} + +static void index_iterator__free(git_iterator *self) +{ + index_iterator *ii = (index_iterator *)self; + git_index_free(ii->index); + ii->index = NULL; +} + +int git_iterator_for_index(git_repository *repo, git_iterator **iter) +{ + int error; + index_iterator *ii = git__calloc(1, sizeof(index_iterator)); + if (!ii) + return GIT_ENOMEM; + + 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); + else + *iter = (git_iterator *)ii; + return error; +} + + +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 base; + git_repository *repo; + size_t root_len; + workdir_iterator_frame *stack; + git_ignores ignores; + git_index_entry entry; + git_buf path; + int is_ignored; +} workdir_iterator; + +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(&wf->entries, i, path) + git__free(path); + git_vector_free(&wf->entries); + git__free(wf); +} + +static int workdir_iterator__update_entry(workdir_iterator *wi); + +static int workdir_iterator__expand_dir(workdir_iterator *wi) +{ + int error; + workdir_iterator_frame *wf = workdir_iterator__alloc_frame(); + if (wf == NULL) + return GIT_ENOMEM; + + /* 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, &wf->entries); + if (error < GIT_SUCCESS || wf->entries.length == 0) { + workdir_iterator__free_frame(wf); + return GIT_ENOTFOUND; + } + + git_vector_sort(&wf->entries); + wf->next = wi->stack; + wi->stack = wf; + + /* 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 workdir_iterator__update_entry(wi); +} + +static int workdir_iterator__current( + git_iterator *self, const git_index_entry **entry) +{ + workdir_iterator *wi = (workdir_iterator *)self; + *entry = (wi->entry.path == NULL) ? NULL : &wi->entry; + return GIT_SUCCESS; +} + +static int workdir_iterator__at_end(git_iterator *self) +{ + return (((workdir_iterator *)self)->entry.path == NULL); +} + +static int workdir_iterator__advance( + git_iterator *self, const git_index_entry **entry) +{ + int error; + workdir_iterator *wi = (workdir_iterator *)self; + workdir_iterator_frame *wf; + const char *next; + + if (entry != NULL) + *entry = NULL; + + if (wi->entry.path == NULL) + return GIT_SUCCESS; + + while ((wf = wi->stack) != NULL) { + next = git_vector_get(&wf->entries, ++wf->index); + if (next != NULL) { + if (strcmp(next, DOT_GIT) == 0) + continue; + /* else found a good entry */ + break; + } + + /* 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 = workdir_iterator__update_entry(wi); + + if (error == GIT_SUCCESS && entry != NULL) + error = workdir_iterator__current(self, entry); + + return error; +} + +static void workdir_iterator__free(git_iterator *self) +{ + workdir_iterator *wi = (workdir_iterator *)self; + + while (wi->stack != NULL) { + workdir_iterator_frame *wf = wi->stack; + wi->stack = wf->next; + workdir_iterator__free_frame(wf); + } + + git_ignore__free(&wi->ignores); + git_buf_free(&wi->path); +} + +static int workdir_iterator__update_entry(workdir_iterator *wi) +{ + int error; + struct stat st; + char *relpath = git_vector_get(&wi->stack->entries, wi->stack->index); + + 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; + + /* skip over .git directory */ + if (strcmp(relpath, DOT_GIT) == 0) + return workdir_iterator__advance((git_iterator *)wi, NULL); + + /* if there is an error processing the entry, treat as ignored */ + wi->is_ignored = 1; + + 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; + + /* 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 */ + wi->entry.mode = S_IFGITLINK; + } 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; + } + } + + return GIT_SUCCESS; +} + +int git_iterator_for_workdir(git_repository *repo, git_iterator **iter) +{ + int error; + workdir_iterator *wi = git__calloc(1, sizeof(workdir_iterator)); + if (!wi) + return GIT_ENOMEM; + + 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; + + 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 = workdir_iterator__expand_dir(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) +{ + *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) +{ + 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/iterator.h b/src/iterator.h new file mode 100644 index 000000000..ac30b4ded --- /dev/null +++ b/src/iterator.h @@ -0,0 +1,99 @@ +/* + * 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 *, const git_index_entry **); + 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, const git_index_entry **entry) +{ + return iter->advance(iter, entry); +} + +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 a workdir directory. + * + * 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. + * + * 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_directory( + git_iterator *iter, const git_index_entry **entry); + +#endif diff --git a/src/path.c b/src/path.c index 042332c45..88ea95a97 100644 --- a/src/path.c +++ b/src/path.c @@ -398,37 +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_dir(git_buf *base, const char *subdir, int append_if_exists) +int git_path_contains(git_buf *dir, const char *item) +{ + return _check_dir_contents(dir, item, &git_path_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) @@ -522,3 +523,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..abe6c2217 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,24 +130,31 @@ 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 * @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. @@ -216,4 +224,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/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 ba8499d4e..e109704ab 100644 --- a/src/vector.c +++ b/src/vector.c @@ -25,7 +25,6 @@ static int resize_vector(git_vector *v) return GIT_SUCCESS; } - void git_vector_free(git_vector *v) { assert(v); @@ -188,6 +187,12 @@ int git_vector_remove(git_vector *v, unsigned int idx) return GIT_SUCCESS; } +void git_vector_pop(git_vector *v) +{ + if (v->length > 0) + v->length--; +} + void git_vector_uniq(git_vector *v) { git_vector_cmp cmp; diff --git a/src/vector.h b/src/vector.h index ae3882558..44635ae14 100644 --- a/src/vector.h +++ b/src/vector.h @@ -38,6 +38,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 +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); +void git_vector_pop(git_vector *v); void git_vector_uniq(git_vector *v); #endif |