Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/attr.c70
-rw-r--r--src/attr.h7
-rw-r--r--src/buffer.c8
-rw-r--r--src/buffer.h1
-rw-r--r--src/fileops.c18
-rw-r--r--src/fileops.h7
-rw-r--r--src/ignore.c114
-rw-r--r--src/ignore.h24
-rw-r--r--src/iterator.c506
-rw-r--r--src/iterator.h94
-rw-r--r--src/path.c65
-rw-r--r--src/path.h32
-rw-r--r--src/vector.c32
-rw-r--r--src/vector.h7
-rw-r--r--tests-clar/diff/diff_helpers.c22
-rw-r--r--tests-clar/diff/diff_helpers.h4
-rw-r--r--tests-clar/diff/iterator.c362
17 files changed, 1312 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
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");
+}