summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Martín Nieto <carlosmn@github.com>2016-03-26 05:39:07 -0700
committerCarlos Martín Nieto <carlosmn@github.com>2016-03-26 05:39:07 -0700
commit7b29be31c2f03b9c85dd4545f62acd5c95f56e96 (patch)
tree2759908e297aba465c935ef20cc705bbc12cfaa7
parentd4763c9861ed517ec8c0e08fe0b474d3554a13ee (diff)
parentd6713ec64e5249cd07d2a00faf8fd0cc2baa8845 (diff)
downloadlibgit2-7b29be31c2f03b9c85dd4545f62acd5c95f56e96.tar.gz
Merge pull request #3691 from ethomson/iterators
Some FANTASTIC iterator refactoring
-rw-r--r--include/git2/blob.h9
-rw-r--r--include/git2/commit.h9
-rw-r--r--include/git2/tag.h9
-rw-r--r--include/git2/tree.h9
-rw-r--r--src/checkout.c102
-rw-r--r--src/commit.c2
-rw-r--r--src/describe.c2
-rw-r--r--src/diff.c19
-rw-r--r--src/iterator.c3004
-rw-r--r--src/iterator.h121
-rw-r--r--src/object_api.c20
-rw-r--r--src/path.c14
-rw-r--r--src/path.h12
-rw-r--r--src/pathspec.c2
-rw-r--r--tests/core/path.c20
-rw-r--r--tests/diff/iterator.c1012
-rw-r--r--tests/diff/racediffiter.c129
-rw-r--r--tests/iterator/index.c1383
-rw-r--r--tests/iterator/iterator_helpers.c146
-rw-r--r--tests/iterator/iterator_helpers.h16
-rw-r--r--tests/iterator/tree.c1076
-rw-r--r--tests/iterator/workdir.c1458
-rw-r--r--tests/repo/iterator.c1544
-rw-r--r--tests/status/worktree.c147
24 files changed, 6103 insertions, 4162 deletions
diff --git a/include/git2/blob.h b/include/git2/blob.h
index f451593cd..6377fc2a2 100644
--- a/include/git2/blob.h
+++ b/include/git2/blob.h
@@ -259,6 +259,15 @@ GIT_EXTERN(int) git_blob_create_frombuffer(
*/
GIT_EXTERN(int) git_blob_is_binary(const git_blob *blob);
+/**
+ * Create an in-memory copy of a blob. The copy must be explicitly
+ * free'd or it will leak.
+ *
+ * @param out Pointer to store the copy of the object
+ * @param source Original object to copy
+ */
+GIT_EXTERN(int) git_blob_dup(git_blob **out, git_blob *source);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/commit.h b/include/git2/commit.h
index f63a90685..4cc637466 100644
--- a/include/git2/commit.h
+++ b/include/git2/commit.h
@@ -461,6 +461,15 @@ GIT_EXTERN(int) git_commit_create_with_signature(
const char *signature,
const char *signature_field);
+/**
+ * Create an in-memory copy of a commit. The copy must be explicitly
+ * free'd or it will leak.
+ *
+ * @param out Pointer to store the copy of the commit
+ * @param source Original commit to copy
+ */
+GIT_EXTERN(int) git_commit_dup(git_commit **out, git_commit *source);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/tag.h b/include/git2/tag.h
index c822cee7c..cb95fb5ef 100644
--- a/include/git2/tag.h
+++ b/include/git2/tag.h
@@ -347,6 +347,15 @@ GIT_EXTERN(int) git_tag_peel(
git_object **tag_target_out,
const git_tag *tag);
+/**
+ * Create an in-memory copy of a tag. The copy must be explicitly
+ * free'd or it will leak.
+ *
+ * @param out Pointer to store the copy of the tag
+ * @param source Original tag to copy
+ */
+GIT_EXTERN(int) git_tag_dup(git_tag **out, git_tag *source);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/tree.h b/include/git2/tree.h
index 550a44857..8a2be2102 100644
--- a/include/git2/tree.h
+++ b/include/git2/tree.h
@@ -409,6 +409,15 @@ GIT_EXTERN(int) git_tree_walk(
git_treewalk_cb callback,
void *payload);
+/**
+ * Create an in-memory copy of a tree. The copy must be explicitly
+ * free'd or it will leak.
+ *
+ * @param out Pointer to store the copy of the tree
+ * @param source Original tree to copy
+ */
+GIT_EXTERN(int) git_tree_dup(git_tree **out, git_tree *source);
+
/** @} */
GIT_END_DECL
diff --git a/src/checkout.c b/src/checkout.c
index deeee62e0..fed1819aa 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -66,8 +66,8 @@ typedef struct {
git_vector update_conflicts;
git_vector *update_reuc;
git_vector *update_names;
- git_buf path;
- size_t workdir_len;
+ git_buf target_path;
+ size_t target_len;
git_buf tmp;
unsigned int strategy;
int can_symlink;
@@ -294,14 +294,30 @@ static int checkout_action_no_wd(
return checkout_action_common(action, data, delta, NULL);
}
-static bool wd_item_is_removable(git_iterator *iter, const git_index_entry *wd)
+static int checkout_target_fullpath(
+ git_buf **out, checkout_data *data, const char *path)
{
- git_buf *full = NULL;
+ git_buf_truncate(&data->target_path, data->target_len);
+
+ if (path && git_buf_puts(&data->target_path, path) < 0)
+ return -1;
+
+ *out = &data->target_path;
+
+ return 0;
+}
+
+static bool wd_item_is_removable(
+ checkout_data *data, const git_index_entry *wd)
+{
+ git_buf *full;
if (wd->mode != GIT_FILEMODE_TREE)
return true;
- if (git_iterator_current_workdir_path(&full, iter) < 0)
- return true;
+
+ if (checkout_target_fullpath(&full, data, wd->path) < 0)
+ return false;
+
return !full || !git_path_contains(full, DOT_GIT);
}
@@ -363,14 +379,14 @@ static int checkout_action_wd_only(
if ((error = checkout_notify(data, notify, NULL, wd)) != 0)
return error;
- if (remove && wd_item_is_removable(workdir, wd))
+ if (remove && wd_item_is_removable(data, wd))
error = checkout_queue_remove(data, wd->path);
if (!error)
error = git_iterator_advance(wditem, workdir);
} else {
/* untracked or ignored - can't know which until we advance through */
- bool over = false, removable = wd_item_is_removable(workdir, wd);
+ bool over = false, removable = wd_item_is_removable(data, wd);
git_iterator_status_t untracked_state;
/* copy the entry for issuing notification callback later */
@@ -378,7 +394,7 @@ static int checkout_action_wd_only(
git_buf_sets(&data->tmp, wd->path);
saved_wd.path = data->tmp.ptr;
- error = git_iterator_advance_over_with_status(
+ error = git_iterator_advance_over(
wditem, &untracked_state, workdir);
if (error == GIT_ITEROVER)
over = true;
@@ -428,10 +444,12 @@ static bool submodule_is_config_only(
static bool checkout_is_empty_dir(checkout_data *data, const char *path)
{
- git_buf_truncate(&data->path, data->workdir_len);
- if (git_buf_puts(&data->path, path) < 0)
+ git_buf *fullpath;
+
+ if (checkout_target_fullpath(&fullpath, data, path) < 0)
return false;
- return git_path_is_empty_dir(data->path.ptr);
+
+ return git_path_is_empty_dir(fullpath->ptr);
}
static int checkout_action_with_wd(
@@ -639,7 +657,7 @@ static int checkout_action(
if (cmp == 0) {
if (wd->mode == GIT_FILEMODE_TREE) {
/* case 2 - entry prefixed by workdir tree */
- error = git_iterator_advance_into_or_over(wditem, workdir);
+ error = git_iterator_advance_into(wditem, workdir);
if (error < 0 && error != GIT_ITEROVER)
goto done;
continue;
@@ -912,7 +930,7 @@ static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, g
git_index *index;
/* Only write conficts from sources that have them: indexes. */
- if ((index = git_iterator_get_index(data->target)) == NULL)
+ if ((index = git_iterator_index(data->target)) == NULL)
return 0;
data->update_conflicts._cmp = checkout_conflictdata_cmp;
@@ -1062,7 +1080,7 @@ static int checkout_conflicts_coalesce_renames(
size_t i, names;
int error = 0;
- if ((index = git_iterator_get_index(data->target)) == NULL)
+ if ((index = git_iterator_index(data->target)) == NULL)
return 0;
/* Juggle entries based on renames */
@@ -1120,7 +1138,7 @@ static int checkout_conflicts_mark_directoryfile(
const char *path;
int prefixed, error = 0;
- if ((index = git_iterator_get_index(data->target)) == NULL)
+ if ((index = git_iterator_index(data->target)) == NULL)
return 0;
len = git_index_entrycount(index);
@@ -1582,18 +1600,18 @@ static int checkout_submodule_update_index(
checkout_data *data,
const git_diff_file *file)
{
+ git_buf *fullpath;
struct stat st;
/* update the index unless prevented */
if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0)
return 0;
- git_buf_truncate(&data->path, data->workdir_len);
- if (git_buf_puts(&data->path, file->path) < 0)
+ if (checkout_target_fullpath(&fullpath, data, file->path) < 0)
return -1;
data->perfdata.stat_calls++;
- if (p_stat(git_buf_cstr(&data->path), &st) < 0) {
+ if (p_stat(fullpath->ptr, &st) < 0) {
giterr_set(
GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
return GIT_ENOTFOUND;
@@ -1718,22 +1736,23 @@ static int checkout_blob(
checkout_data *data,
const git_diff_file *file)
{
- int error = 0;
+ git_buf *fullpath;
struct stat st;
+ int error = 0;
- git_buf_truncate(&data->path, data->workdir_len);
- if (git_buf_puts(&data->path, file->path) < 0)
+ if (checkout_target_fullpath(&fullpath, data, file->path) < 0)
return -1;
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) {
int rval = checkout_safe_for_update_only(
- data, git_buf_cstr(&data->path), file->mode);
+ data, fullpath->ptr, file->mode);
+
if (rval <= 0)
return rval;
}
error = checkout_write_content(
- data, &file->id, git_buf_cstr(&data->path), NULL, file->mode, &st);
+ data, &file->id, fullpath->ptr, NULL, file->mode, &st);
/* update the index unless prevented */
if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
@@ -1754,18 +1773,21 @@ static int checkout_remove_the_old(
git_diff_delta *delta;
const char *str;
size_t i;
- const char *workdir = git_buf_cstr(&data->path);
+ git_buf *fullpath;
uint32_t flg = GIT_RMDIR_EMPTY_PARENTS |
GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;
if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES)
flg |= GIT_RMDIR_SKIP_NONEMPTY;
- git_buf_truncate(&data->path, data->workdir_len);
+ if (checkout_target_fullpath(&fullpath, data, NULL) < 0)
+ return -1;
git_vector_foreach(&data->diff->deltas, i, delta) {
if (actions[i] & CHECKOUT_ACTION__REMOVE) {
- error = git_futils_rmdir_r(delta->old_file.path, workdir, flg);
+ error = git_futils_rmdir_r(
+ delta->old_file.path, fullpath->ptr, flg);
+
if (error < 0)
return error;
@@ -1782,7 +1804,7 @@ static int checkout_remove_the_old(
}
git_vector_foreach(&data->removes, i, str) {
- error = git_futils_rmdir_r(str, workdir, flg);
+ error = git_futils_rmdir_r(str, fullpath->ptr, flg);
if (error < 0)
return error;
@@ -1949,13 +1971,13 @@ static int checkout_write_entry(
const git_index_entry *side)
{
const char *hint_path = NULL, *suffix;
+ git_buf *fullpath;
struct stat st;
int error;
assert (side == conflict->ours || side == conflict->theirs);
- git_buf_truncate(&data->path, data->workdir_len);
- if (git_buf_puts(&data->path, side->path) < 0)
+ if (checkout_target_fullpath(&fullpath, data, side->path) < 0)
return -1;
if ((conflict->name_collision || conflict->directoryfile) &&
@@ -1969,18 +1991,18 @@ static int checkout_write_entry(
suffix = data->opts.their_label ? data->opts.their_label :
"theirs";
- if (checkout_path_suffixed(&data->path, suffix) < 0)
+ if (checkout_path_suffixed(fullpath, suffix) < 0)
return -1;
hint_path = side->path;
}
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 &&
- (error = checkout_safe_for_update_only(data, git_buf_cstr(&data->path), side->mode)) <= 0)
+ (error = checkout_safe_for_update_only(data, fullpath->ptr, side->mode)) <= 0)
return error;
return checkout_write_content(data,
- &side->id, git_buf_cstr(&data->path), hint_path, side->mode, &st);
+ &side->id, fullpath->ptr, hint_path, side->mode, &st);
}
static int checkout_write_entries(
@@ -2293,7 +2315,7 @@ static void checkout_data_clear(checkout_data *data)
git_strmap_free(data->mkdir_map);
- git_buf_free(&data->path);
+ git_buf_free(&data->target_path);
git_buf_free(&data->tmp);
git_index_free(data->index);
@@ -2356,7 +2378,7 @@ static int checkout_data_init(
if ((error = git_repository_index(&data->index, data->repo)) < 0)
goto cleanup;
- if (data->index != git_iterator_get_index(target)) {
+ if (data->index != git_iterator_index(target)) {
if ((error = git_index_read(data->index, true)) < 0)
goto cleanup;
@@ -2447,12 +2469,12 @@ static int checkout_data_init(
if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||
(error = git_vector_init(&data->remove_conflicts, 0, NULL)) < 0 ||
(error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 ||
- (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 ||
- (error = git_path_to_dir(&data->path)) < 0 ||
+ (error = git_buf_puts(&data->target_path, data->opts.target_directory)) < 0 ||
+ (error = git_path_to_dir(&data->target_path)) < 0 ||
(error = git_strmap_alloc(&data->mkdir_map)) < 0)
goto cleanup;
- data->workdir_len = git_buf_len(&data->path);
+ data->target_len = git_buf_len(&data->target_path);
git_attr_session__init(&data->attr_session, data->repo);
@@ -2508,7 +2530,7 @@ int git_checkout_iterator(
workdir_opts.start = data.pfx;
workdir_opts.end = data.pfx;
- if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 ||
+ if ((error = git_iterator_reset_range(target, data.pfx, data.pfx)) < 0 ||
(error = git_iterator_for_workdir_ext(
&workdir, data.repo, data.opts.target_directory, index, NULL,
&workdir_opts)) < 0)
@@ -2578,7 +2600,7 @@ int git_checkout_iterator(
(error = checkout_create_conflicts(&data)) < 0)
goto cleanup;
- if (data.index != git_iterator_get_index(target) &&
+ if (data.index != git_iterator_index(target) &&
(error = checkout_extensions_update_index(&data)) < 0)
goto cleanup;
diff --git a/src/commit.c b/src/commit.c
index aaefacdab..5456751fe 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -615,7 +615,7 @@ int git_commit_nth_gen_ancestor(
assert(ancestor && commit);
- if (git_object_dup((git_object **) &current, (git_object *) commit) < 0)
+ if (git_commit_dup(&current, (git_commit *)commit) < 0)
return -1;
if (n == 0) {
diff --git a/src/describe.c b/src/describe.c
index 13ddad5be..fc48fbde4 100644
--- a/src/describe.c
+++ b/src/describe.c
@@ -197,7 +197,7 @@ static int commit_name_dup(struct commit_name **out, struct commit_name *in)
name->tag = NULL;
name->path = NULL;
- if (in->tag && git_object_dup((git_object **) &name->tag, (git_object *) in->tag) < 0)
+ if (in->tag && git_tag_dup(&name->tag, in->tag) < 0)
return -1;
name->path = git__strdup(in->path);
diff --git a/src/diff.c b/src/diff.c
index 9ac5b9250..64641daab 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -826,8 +826,7 @@ static int maybe_modified(
*/
} else if (git_oid_iszero(&nitem->id) && new_is_workdir) {
bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
- git_index *index;
- git_iterator_index(&index, info->new_iter);
+ git_index *index = git_iterator_index(info->new_iter);
status = GIT_DELTA_UNMODIFIED;
@@ -980,15 +979,14 @@ static int iterator_advance_into(
return error;
}
-static int iterator_advance_over_with_status(
+static int iterator_advance_over(
const git_index_entry **entry,
git_iterator_status_t *status,
git_iterator *iterator)
{
- int error;
+ int error = git_iterator_advance_over(entry, status, iterator);
- if ((error = git_iterator_advance_over_with_status(
- entry, status, iterator)) == GIT_ITEROVER) {
+ if (error == GIT_ITEROVER) {
*entry = NULL;
error = 0;
}
@@ -1056,7 +1054,7 @@ static int handle_unmatched_new_item(
return iterator_advance(&info->nitem, info->new_iter);
/* iterate into dir looking for an actual untracked file */
- if ((error = iterator_advance_over_with_status(
+ if ((error = iterator_advance_over(
&info->nitem, &untracked_state, info->new_iter)) < 0)
return error;
@@ -1093,7 +1091,7 @@ static int handle_unmatched_new_item(
/* if directory is empty, can't advance into it, so either skip
* it or ignore it
*/
- if (contains_oitem)
+ if (error == GIT_ENOTFOUND || contains_oitem)
return iterator_advance(&info->nitem, info->new_iter);
delta_type = GIT_DELTA_IGNORED;
}
@@ -1231,9 +1229,8 @@ int git_diff__from_iterators(
/* make iterators have matching icase behavior */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) {
- if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 ||
- (error = git_iterator_set_ignore_case(new_iter, true)) < 0)
- goto cleanup;
+ git_iterator_set_ignore_case(old_iter, true);
+ git_iterator_set_ignore_case(new_iter, true);
}
/* finish initialization */
diff --git a/src/iterator.c b/src/iterator.c
index cb1ea6a87..4202e00cd 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -8,288 +8,359 @@
#include "iterator.h"
#include "tree.h"
#include "index.h"
-#include "ignore.h"
-#include "buffer.h"
-#include "submodule.h"
-#include <ctype.h>
-
-#define ITERATOR_SET_CB(P,NAME_LC) do { \
- (P)->cb.current = NAME_LC ## _iterator__current; \
- (P)->cb.advance = NAME_LC ## _iterator__advance; \
- (P)->cb.advance_into = NAME_LC ## _iterator__advance_into; \
- (P)->cb.seek = NAME_LC ## _iterator__seek; \
- (P)->cb.reset = NAME_LC ## _iterator__reset; \
- (P)->cb.at_end = NAME_LC ## _iterator__at_end; \
- (P)->cb.free = NAME_LC ## _iterator__free; \
- } while (0)
-
-#define ITERATOR_CASE_FLAGS \
- (GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_IGNORE_CASE)
-
-#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC,REPO) do { \
- (P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \
- (P)->base.cb = &(P)->cb; \
- ITERATOR_SET_CB(P,NAME_LC); \
- (P)->base.repo = (REPO); \
- (P)->base.start = options && options->start ? \
- git__strdup(options->start) : NULL; \
- (P)->base.end = options && options->end ? \
- git__strdup(options->end) : NULL; \
- if ((options && options->start && !(P)->base.start) || \
- (options && options->end && !(P)->base.end)) { \
- git__free(P); return -1; } \
- (P)->base.strcomp = git__strcmp; \
- (P)->base.strncomp = git__strncmp; \
- (P)->base.prefixcomp = git__prefixcmp; \
- (P)->base.flags = options ? options->flags & ~ITERATOR_CASE_FLAGS : 0; \
- if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \
- (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \
- if (options && options->pathlist.count && \
- iterator_pathlist__init(&P->base, &options->pathlist) < 0) { \
- git__free(P); return -1; } \
- } while (0)
-#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
-#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE)
-#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES)
-#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)
-#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)
-#define iterator__include_conflicts(I) iterator__flag(I, INCLUDE_CONFLICTS)
+#define GIT_ITERATOR_FIRST_ACCESS (1 << 15)
+#define GIT_ITERATOR_HONOR_IGNORES (1 << 16)
+#define GIT_ITERATOR_IGNORE_DOT_GIT (1 << 17)
-#define GIT_ITERATOR_FIRST_ACCESS (1 << 15)
+#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
+#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE)
+#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES)
+#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)
+#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)
+#define iterator__include_conflicts(I) iterator__flag(I,INCLUDE_CONFLICTS)
#define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS)
+#define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES)
+#define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT)
-#define iterator__end(I) ((git_iterator *)(I))->end
-#define iterator__past_end(I,PATH) \
- (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0)
+static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
+{
+ if (ignore_case)
+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;
+ else
+ iter->flags &= ~GIT_ITERATOR_IGNORE_CASE;
+
+ iter->strcomp = ignore_case ? git__strcasecmp : git__strcmp;
+ iter->strncomp = ignore_case ? git__strncasecmp : git__strncmp;
+ iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp;
+ iter->entry_srch = ignore_case ? git_index_entry_srch : git_index_entry_isrch;
-typedef enum {
- ITERATOR_PATHLIST_NONE = 0,
- ITERATOR_PATHLIST_MATCH = 1,
- ITERATOR_PATHLIST_MATCH_DIRECTORY = 2,
- ITERATOR_PATHLIST_MATCH_CHILD = 3,
-} iterator_pathlist__match_t;
+ git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp);
+}
+
+static int iterator_range_init(
+ git_iterator *iter, const char *start, const char *end)
+{
+ if (start && *start) {
+ iter->start = git__strdup(start);
+ GITERR_CHECK_ALLOC(iter->start);
+
+ iter->start_len = strlen(iter->start);
+ }
+
+ if (end && *end) {
+ iter->end = git__strdup(end);
+ GITERR_CHECK_ALLOC(iter->end);
+
+ iter->end_len = strlen(iter->end);
+ }
+
+ iter->started = (iter->start == NULL);
+ iter->ended = false;
+
+ return 0;
+}
+
+static void iterator_range_free(git_iterator *iter)
+{
+ if (iter->start) {
+ git__free(iter->start);
+ iter->start = NULL;
+ iter->start_len = 0;
+ }
+
+ if (iter->end) {
+ git__free(iter->end);
+ iter->end = NULL;
+ iter->end_len = 0;
+ }
+}
+
+static int iterator_reset_range(
+ git_iterator *iter, const char *start, const char *end)
+{
+ iterator_range_free(iter);
+ return iterator_range_init(iter, start, end);
+}
-static int iterator_pathlist__init(git_iterator *iter, git_strarray *pathspec)
+static int iterator_pathlist_init(git_iterator *iter, git_strarray *pathlist)
{
size_t i;
- if (git_vector_init(&iter->pathlist, pathspec->count,
- (git_vector_cmp)iter->strcomp) < 0)
+ if (git_vector_init(&iter->pathlist, pathlist->count, NULL) < 0)
return -1;
- for (i = 0; i < pathspec->count; i++) {
- if (!pathspec->strings[i])
+ for (i = 0; i < pathlist->count; i++) {
+ if (!pathlist->strings[i])
continue;
- if (git_vector_insert(&iter->pathlist, pathspec->strings[i]) < 0)
+ if (git_vector_insert(&iter->pathlist, pathlist->strings[i]) < 0)
return -1;
}
- git_vector_sort(&iter->pathlist);
-
return 0;
}
-static iterator_pathlist__match_t iterator_pathlist__match(
- git_iterator *iter, const char *path, size_t path_len)
+static int iterator_init_common(
+ git_iterator *iter,
+ git_repository *repo,
+ git_index *index,
+ git_iterator_options *given_opts)
{
- const char *p;
- size_t idx;
+ static git_iterator_options default_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_iterator_options *options = given_opts ? given_opts : &default_opts;
+ bool ignore_case;
+ int precompose;
int error;
- error = git_vector_bsearch2(&idx, &iter->pathlist,
- (git_vector_cmp)iter->strcomp, path);
+ iter->repo = repo;
+ iter->index = index;
+ iter->flags = options->flags;
- if (error == 0)
- return ITERATOR_PATHLIST_MATCH;
+ if ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) {
+ ignore_case = true;
+ } else if ((iter->flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) {
+ ignore_case = false;
+ } else if (repo) {
+ git_index *index;
- /* at this point, the path we're examining may be a directory (though we
- * don't know that yet, since we're avoiding a stat unless it's necessary)
- * so see if the pathlist contains a file beneath this directory.
- */
- while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) {
- if (iter->prefixcomp(p, path) != 0)
- break;
+ if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0)
+ return error;
- /* an exact match would have been matched by the bsearch above */
- assert(p[path_len]);
+ ignore_case = !!index->ignore_case;
- /* is this a literal directory entry (eg `foo/`) or a file beneath */
- if (p[path_len] == '/') {
- return (p[path_len+1] == '\0') ?
- ITERATOR_PATHLIST_MATCH_DIRECTORY :
- ITERATOR_PATHLIST_MATCH_CHILD;
- }
+ if (ignore_case == 1)
+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;
+ else
+ iter->flags |= GIT_ITERATOR_DONT_IGNORE_CASE;
+ } else {
+ ignore_case = false;
+ }
- if (p[path_len] > '/')
- break;
+ /* try to look up precompose and set flag if appropriate */
+ if (repo &&
+ (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 &&
+ (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) {
- idx++;
+ if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0)
+ giterr_clear();
+ else if (precompose)
+ iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
}
- return ITERATOR_PATHLIST_NONE;
+ if ((iter->flags & GIT_ITERATOR_DONT_AUTOEXPAND))
+ iter->flags |= GIT_ITERATOR_INCLUDE_TREES;
+
+ if ((error = iterator_range_init(iter, options->start, options->end)) < 0 ||
+ (error = iterator_pathlist_init(iter, &options->pathlist)) < 0)
+ return error;
+
+ iterator_set_ignore_case(iter, ignore_case);
+ return 0;
}
-static void iterator_pathlist_walk__reset(git_iterator *iter)
+static void iterator_clear(git_iterator *iter)
{
+ iter->started = false;
+ iter->ended = false;
+ iter->stat_calls = 0;
iter->pathlist_walk_idx = 0;
+ iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+}
+
+GIT_INLINE(bool) iterator_has_started(git_iterator *iter, const char *path)
+{
+ size_t path_len;
+
+ if (iter->start == NULL || iter->started == true)
+ return true;
+
+ /* the starting path is generally a prefix - we have started once we
+ * are prefixed by this path
+ */
+ iter->started = (iter->prefixcomp(path, iter->start) >= 0);
+
+ /* if, however, our current path is a directory, and our starting path
+ * is _beneath_ that directory, then recurse into the directory (even
+ * though we have not yet "started")
+ */
+ if (!iter->started &&
+ (path_len = strlen(path)) > 0 && path[path_len-1] == '/' &&
+ iter->strncomp(path, iter->start, path_len) == 0)
+ return true;
+
+ return iter->started;
+}
+
+GIT_INLINE(bool) iterator_has_ended(git_iterator *iter, const char *path)
+{
+ if (iter->end == NULL)
+ return false;
+ else if (iter->ended)
+ return true;
+
+ iter->ended = (iter->prefixcomp(path, iter->end) > 0);
+ return iter->ended;
}
-/* walker for the index iterator that allows it to walk the sorted pathlist
- * entries alongside the sorted index entries. the `iter->pathlist_walk_idx`
- * stores the starting position for subsequent calls, the position is advanced
- * along with the index iterator, with a special case for handling directories
- * in the pathlist that are specified without trailing '/'. (eg, `foo`).
- * we do not advance over these entries until we're certain that the index
- * iterator will not ask us for a file beneath that directory (eg, `foo/bar`).
+/* walker for the index and tree iterator that allows it to walk the sorted
+ * pathlist entries alongside sorted iterator entries.
*/
-static bool iterator_pathlist_walk__contains(git_iterator *iter, const char *path)
+static bool iterator_pathlist_next_is(git_iterator *iter, const char *path)
{
- size_t i;
char *p;
- size_t p_len;
+ size_t path_len, p_len, cmp_len, i;
int cmp;
+ if (iter->pathlist.length == 0)
+ return true;
+
+ git_vector_sort(&iter->pathlist);
+
+ path_len = strlen(path);
+
+ /* for comparison, drop the trailing slash on the current '/' */
+ if (path_len && path[path_len-1] == '/')
+ path_len--;
+
for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) {
p = iter->pathlist.contents[i];
p_len = strlen(p);
+ if (p_len && p[p_len-1] == '/')
+ p_len--;
+
+ cmp_len = min(path_len, p_len);
+
/* see if the pathlist entry is a prefix of this path */
- cmp = iter->strncomp(p, path, p_len);
+ cmp = iter->strncomp(p, path, cmp_len);
+
+ /* prefix match - see if there's an exact match, or if we were
+ * given a path that matches the directory
+ */
+ if (cmp == 0) {
+ /* if this pathlist entry is not suffixed with a '/' then
+ * it matches a path that is a file or a directory.
+ * (eg, pathlist = "foo" and path is "foo" or "foo/" or
+ * "foo/something")
+ */
+ if (p[cmp_len] == '\0' &&
+ (path[cmp_len] == '\0' || path[cmp_len] == '/'))
+ return true;
+
+ /* if this pathlist entry _is_ suffixed with a '/' then
+ * it matches only paths that are directories.
+ * (eg, pathlist = "foo/" and path is "foo/" or "foo/something")
+ */
+ if (p[cmp_len] == '/' && path[cmp_len] == '/')
+ return true;
+ }
/* this pathlist entry sorts before the given path, try the next */
- if (!p_len || cmp < 0)
+ else if (cmp < 0) {
iter->pathlist_walk_idx++;
+ continue;
+ }
/* this pathlist sorts after the given path, no match. */
- else if (cmp > 0)
- return false;
-
- /* match! an exact match (`foo` vs `foo`), the path is a child of an
- * explicit directory in the pathlist (`foo/` vs `foo/bar`) or the path
- * is a child of an entry in the pathlist (`foo` vs `foo/bar`)
- */
- else if (path[p_len] == '\0' || p[p_len - 1] == '/' || path[p_len] == '/')
- return true;
-
- /* only advance the start index for future callers if we know that we
- * will not see a child of this path. eg, a pathlist entry `foo` is
- * a prefix for `foo.txt` and `foo/bar`. don't advance the start
- * pathlist index when we see `foo.txt` or we would miss a subsequent
- * inspection of `foo/bar`. only advance when there are no more
- * potential children.
- */
- else if (path[p_len] > '/')
- iter->pathlist_walk_idx++;
+ else if (cmp > 0) {
+ break;
+ }
}
return false;
}
-static void iterator_pathlist__update_ignore_case(git_iterator *iter)
-{
- git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp);
- git_vector_sort(&iter->pathlist);
-
- iter->pathlist_walk_idx = 0;
-}
-
+typedef enum {
+ ITERATOR_PATHLIST_NONE = 0,
+ ITERATOR_PATHLIST_IS_FILE = 1,
+ ITERATOR_PATHLIST_IS_DIR = 2,
+ ITERATOR_PATHLIST_IS_PARENT = 3,
+ ITERATOR_PATHLIST_FULL = 4,
+} iterator_pathlist_search_t;
-static int iterator__reset_range(
- git_iterator *iter, const char *start, const char *end)
+static iterator_pathlist_search_t iterator_pathlist_search(
+ git_iterator *iter, const char *path, size_t path_len)
{
- if (start) {
- if (iter->start)
- git__free(iter->start);
- iter->start = git__strdup(start);
- GITERR_CHECK_ALLOC(iter->start);
- }
-
- if (end) {
- if (iter->end)
- git__free(iter->end);
- iter->end = git__strdup(end);
- GITERR_CHECK_ALLOC(iter->end);
- }
-
- iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+ const char *p;
+ size_t idx;
+ int error;
- return 0;
-}
+ if (iter->pathlist.length == 0)
+ return ITERATOR_PATHLIST_FULL;
-static int iterator__update_ignore_case(
- git_iterator *iter,
- git_iterator_flag_t flags)
-{
- bool ignore_case;
- int error;
+ git_vector_sort(&iter->pathlist);
- if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
- ignore_case = true;
- else if ((flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0)
- ignore_case = false;
- else {
- git_index *index;
+ error = git_vector_bsearch2(&idx, &iter->pathlist,
+ (git_vector_cmp)iter->strcomp, path);
- if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0)
- return error;
+ /* the given path was found in the pathlist. since the pathlist only
+ * matches directories when they're suffixed with a '/', analyze the
+ * path string to determine whether it's a directory or not.
+ */
+ if (error == 0) {
+ if (path_len && path[path_len-1] == '/')
+ return ITERATOR_PATHLIST_IS_DIR;
- ignore_case = (index->ignore_case == 1);
+ return ITERATOR_PATHLIST_IS_FILE;
}
- if (ignore_case) {
- iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE);
+ /* at this point, the path we're examining may be a directory (though we
+ * don't know that yet, since we're avoiding a stat unless it's necessary)
+ * so walk the pathlist looking for the given path with a '/' after it,
+ */
+ while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) {
+ if (iter->prefixcomp(p, path) != 0)
+ break;
- iter->strcomp = git__strcasecmp;
- iter->strncomp = git__strncasecmp;
- iter->prefixcomp = git__prefixcmp_icase;
- } else {
- iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE);
+ /* an exact match would have been matched by the bsearch above */
+ assert(p[path_len]);
- iter->strcomp = git__strcmp;
- iter->strncomp = git__strncmp;
- iter->prefixcomp = git__prefixcmp;
- }
+ /* is this a literal directory entry (eg `foo/`) or a file beneath */
+ if (p[path_len] == '/') {
+ return (p[path_len+1] == '\0') ?
+ ITERATOR_PATHLIST_IS_DIR :
+ ITERATOR_PATHLIST_IS_PARENT;
+ }
- iterator_pathlist__update_ignore_case(iter);
+ if (p[path_len] > '/')
+ break;
- return 0;
-}
+ idx++;
+ }
-GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry)
-{
- if (entry) *entry = NULL;
+ return ITERATOR_PATHLIST_NONE;
}
+/* Empty iterator */
-static int empty_iterator__noop(const git_index_entry **e, git_iterator *i)
+static int empty_iterator_noop(const git_index_entry **e, git_iterator *i)
{
GIT_UNUSED(i);
- iterator__clear_entry(e);
- return GIT_ITEROVER;
-}
-static int empty_iterator__seek(git_iterator *i, const char *p)
-{
- GIT_UNUSED(i); GIT_UNUSED(p);
- return -1;
+ if (e)
+ *e = NULL;
+
+ return GIT_ITEROVER;
}
-static int empty_iterator__reset(git_iterator *i, const char *s, const char *e)
+static int empty_iterator_advance_over(
+ const git_index_entry **e,
+ git_iterator_status_t *s,
+ git_iterator *i)
{
- GIT_UNUSED(i); GIT_UNUSED(s); GIT_UNUSED(e);
- return 0;
+ *s = GIT_ITERATOR_STATUS_EMPTY;
+ return empty_iterator_noop(e, i);
}
-static int empty_iterator__at_end(git_iterator *i)
+static int empty_iterator_reset(git_iterator *i)
{
GIT_UNUSED(i);
- return 1;
+ return 0;
}
-static void empty_iterator__free(git_iterator *i)
+static void empty_iterator_free(git_iterator *i)
{
GIT_UNUSED(i);
}
@@ -300,1516 +371,1483 @@ typedef struct {
} empty_iterator;
int git_iterator_for_nothing(
- git_iterator **iter,
+ git_iterator **out,
git_iterator_options *options)
{
- empty_iterator *i = git__calloc(1, sizeof(empty_iterator));
- GITERR_CHECK_ALLOC(i);
+ empty_iterator *iter;
+
+ static git_iterator_callbacks callbacks = {
+ empty_iterator_noop,
+ empty_iterator_noop,
+ empty_iterator_noop,
+ empty_iterator_advance_over,
+ empty_iterator_reset,
+ empty_iterator_free
+ };
-#define empty_iterator__current empty_iterator__noop
-#define empty_iterator__advance empty_iterator__noop
-#define empty_iterator__advance_into empty_iterator__noop
+ *out = NULL;
- ITERATOR_BASE_INIT(i, empty, EMPTY, NULL);
+ iter = git__calloc(1, sizeof(empty_iterator));
+ GITERR_CHECK_ALLOC(iter);
- if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0)
- i->base.flags |= GIT_ITERATOR_IGNORE_CASE;
+ iter->base.type = GIT_ITERATOR_TYPE_EMPTY;
+ iter->base.cb = &callbacks;
+ iter->base.flags = options->flags;
- *iter = (git_iterator *)i;
+ *out = &iter->base;
return 0;
}
+/* Tree iterator */
-typedef struct tree_iterator_entry tree_iterator_entry;
-struct tree_iterator_entry {
- tree_iterator_entry *parent;
- const git_tree_entry *te;
- git_tree *tree;
-};
+typedef struct {
+ git_tree_entry *tree_entry;
+ const char *parent_path;
+} tree_iterator_entry;
-typedef struct tree_iterator_frame tree_iterator_frame;
-struct tree_iterator_frame {
- tree_iterator_frame *up, *down;
+typedef struct {
+ git_tree *tree;
- size_t n_entries; /* items in this frame */
- size_t current; /* start of currently active range in frame */
- size_t next; /* start of next range in frame */
+ /* a sorted list of the entries for this frame (folder), these are
+ * actually pointers to the iterator's entry pool.
+ */
+ git_vector entries;
+ tree_iterator_entry *current;
- const char *start;
- size_t startlen;
+ size_t next_idx;
- tree_iterator_entry *entries[GIT_FLEX_ARRAY];
-};
+ /* the path to this particular frame (folder); on case insensitive
+ * iterations, we also have an array of other paths that we were
+ * case insensitively equal to this one, whose contents we have
+ * coalesced into this frame. a child `tree_iterator_entry` will
+ * contain a pointer to its actual parent path.
+ */
+ git_buf path;
+ git_array_t(git_buf) similar_paths;
+} tree_iterator_frame;
typedef struct {
git_iterator base;
- git_iterator_callbacks cb;
- tree_iterator_frame *head, *root;
- git_pool pool;
+ git_tree *root;
+ git_array_t(tree_iterator_frame) frames;
+
git_index_entry entry;
- git_buf path;
- int path_ambiguities;
- bool path_has_filename;
- bool entry_is_current;
+ git_buf entry_path;
+
+ /* a pool of entries to reduce the number of allocations */
+ git_pool entry_pool;
} tree_iterator;
-static char *tree_iterator__current_filename(
- tree_iterator *ti, const git_tree_entry *te)
+GIT_INLINE(tree_iterator_frame *) tree_iterator_parent_frame(
+ tree_iterator *iter)
{
- if (!ti->path_has_filename) {
- if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)
- return NULL;
-
- if (git_tree_entry__is_tree(te) && git_buf_putc(&ti->path, '/') < 0)
- return NULL;
-
- ti->path_has_filename = true;
- }
-
- return ti->path.ptr;
+ return iter->frames.size > 1 ?
+ &iter->frames.ptr[iter->frames.size-2] : NULL;
}
-static void tree_iterator__rewrite_filename(tree_iterator *ti)
+GIT_INLINE(tree_iterator_frame *) tree_iterator_current_frame(
+ tree_iterator *iter)
{
- tree_iterator_entry *scan = ti->head->entries[ti->head->current];
- ssize_t strpos = ti->path.size;
- const git_tree_entry *te;
-
- if (strpos && ti->path.ptr[strpos - 1] == '/')
- strpos--;
-
- for (; scan && (te = scan->te); scan = scan->parent) {
- strpos -= te->filename_len;
- memcpy(&ti->path.ptr[strpos], te->filename, te->filename_len);
- strpos -= 1; /* separator */
- }
+ return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL;
}
-static int tree_iterator__te_cmp(
- const git_tree_entry *a,
- const git_tree_entry *b,
- int (*compare)(const char *, const char *, size_t))
+GIT_INLINE(int) tree_entry_cmp(
+ const git_tree_entry *a, const git_tree_entry *b, bool icase)
{
return git_path_cmp(
a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE,
b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE,
- compare);
+ icase ? git__strncasecmp : git__strncmp);
}
-static int tree_iterator__ci_cmp(const void *a, const void *b, void *p)
+GIT_INLINE(int) tree_iterator_entry_cmp(const void *ptr_a, const void *ptr_b)
{
- const tree_iterator_entry *ae = a, *be = b;
- int cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncasecmp);
+ const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a;
+ const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b;
- if (!cmp) {
- /* stabilize sort order among equivalent names */
- if (!ae->parent->te || !be->parent->te)
- cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncmp);
- else
- cmp = tree_iterator__ci_cmp(ae->parent, be->parent, p);
- }
-
- return cmp;
+ return tree_entry_cmp(a->tree_entry, b->tree_entry, false);
}
-static int tree_iterator__search_cmp(const void *key, const void *val, void *p)
+GIT_INLINE(int) tree_iterator_entry_cmp_icase(
+ const void *ptr_a, const void *ptr_b)
{
- const tree_iterator_frame *tf = key;
- const git_tree_entry *te = ((tree_iterator_entry *)val)->te;
+ const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a;
+ const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b;
- return git_path_cmp(
- tf->start, tf->startlen, false,
- te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE,
- ((git_iterator *)p)->strncomp);
+ return tree_entry_cmp(a->tree_entry, b->tree_entry, true);
}
-static bool tree_iterator__move_to_next(
- tree_iterator *ti, tree_iterator_frame *tf)
+static int tree_iterator_entry_sort_icase(const void *ptr_a, const void *ptr_b)
{
- if (tf->next > tf->current + 1)
- ti->path_ambiguities--;
+ const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a;
+ const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b;
- if (!tf->up) { /* at root */
- tf->current = tf->next;
- return false;
- }
+ int c = tree_entry_cmp(a->tree_entry, b->tree_entry, true);
- for (; tf->current < tf->next; tf->current++) {
- git_tree_free(tf->entries[tf->current]->tree);
- tf->entries[tf->current]->tree = NULL;
- }
+ /* stabilize the sort order for filenames that are (case insensitively)
+ * the same by examining the parent path (case sensitively) before
+ * falling back to a case sensitive sort of the filename.
+ */
+ if (!c && a->parent_path != b->parent_path)
+ c = git__strcmp(a->parent_path, b->parent_path);
+
+ if (!c)
+ c = tree_entry_cmp(a->tree_entry, b->tree_entry, false);
- return (tf->current < tf->n_entries);
+ return c;
}
-static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf)
+static int tree_iterator_compute_path(
+ git_buf *out,
+ tree_iterator_entry *entry)
{
- int error = 0;
- const git_tree_entry *te, *last = NULL;
-
- tf->next = tf->current;
-
- for (; tf->next < tf->n_entries; tf->next++, last = te) {
- te = tf->entries[tf->next]->te;
-
- if (last && tree_iterator__te_cmp(last, te, ti->base.strncomp))
- break;
-
- /* try to load trees for items in [current,next) range */
- if (!error && git_tree_entry__is_tree(te))
- error = git_tree_lookup(
- &tf->entries[tf->next]->tree, ti->base.repo, te->oid);
- }
+ git_buf_clear(out);
- if (tf->next > tf->current + 1)
- ti->path_ambiguities++;
+ if (entry->parent_path)
+ git_buf_joinpath(out, entry->parent_path, entry->tree_entry->filename);
+ else
+ git_buf_puts(out, entry->tree_entry->filename);
- /* if a tree lookup failed, advance over this span and return failure */
- if (error < 0) {
- tree_iterator__move_to_next(ti, tf);
- return error;
- }
+ if (git_tree_entry__is_tree(entry->tree_entry))
+ git_buf_putc(out, '/');
- if (last && !tree_iterator__current_filename(ti, last))
- return -1; /* must have been allocation failure */
+ if (git_buf_oom(out))
+ return -1;
return 0;
}
-GIT_INLINE(bool) tree_iterator__at_tree(tree_iterator *ti)
-{
- return (ti->head->current < ti->head->n_entries &&
- ti->head->entries[ti->head->current]->tree != NULL);
-}
-
-static int tree_iterator__push_frame(tree_iterator *ti)
+static int tree_iterator_frame_init(
+ tree_iterator *iter,
+ git_tree *tree,
+ tree_iterator_entry *frame_entry)
{
+ tree_iterator_frame *new_frame = NULL;
+ tree_iterator_entry *new_entry;
+ git_tree *dup = NULL;
+ git_tree_entry *tree_entry;
+ git_vector_cmp cmp;
+ size_t i;
int error = 0;
- tree_iterator_frame *head = ti->head, *tf = NULL;
- size_t i, n_entries = 0, alloclen;
- if (head->current >= head->n_entries || !head->entries[head->current]->tree)
- return GIT_ITEROVER;
+ new_frame = git_array_alloc(iter->frames);
+ GITERR_CHECK_ALLOC(new_frame);
- for (i = head->current; i < head->next; ++i)
- n_entries += git_tree_entrycount(head->entries[i]->tree);
+ memset(new_frame, 0, sizeof(tree_iterator_frame));
- GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, sizeof(tree_iterator_entry *), n_entries);
- GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, sizeof(tree_iterator_frame));
+ if ((error = git_tree_dup(&dup, tree)) < 0)
+ goto done;
- tf = git__calloc(1, alloclen);
- GITERR_CHECK_ALLOC(tf);
+ memset(new_frame, 0x0, sizeof(tree_iterator_frame));
+ new_frame->tree = dup;
- tf->n_entries = n_entries;
+ if (frame_entry &&
+ (error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0)
+ goto done;
- tf->up = head;
- head->down = tf;
- ti->head = tf;
+ cmp = iterator__ignore_case(&iter->base) ?
+ tree_iterator_entry_sort_icase : NULL;
- for (i = head->current, n_entries = 0; i < head->next; ++i) {
- git_tree *tree = head->entries[i]->tree;
- size_t j, max_j = git_tree_entrycount(tree);
+ if ((error = git_vector_init(
+ &new_frame->entries, dup->entries.size, cmp)) < 0)
+ goto done;
- for (j = 0; j < max_j; ++j) {
- tree_iterator_entry *entry = git_pool_malloc(&ti->pool, 1);
- GITERR_CHECK_ALLOC(entry);
+ git_array_foreach(dup->entries, i, tree_entry) {
+ new_entry = git_pool_malloc(&iter->entry_pool, 1);
+ GITERR_CHECK_ALLOC(new_entry);
- entry->parent = head->entries[i];
- entry->te = git_tree_entry_byindex(tree, j);
- entry->tree = NULL;
+ new_entry->tree_entry = tree_entry;
+ new_entry->parent_path = new_frame->path.ptr;
- tf->entries[n_entries++] = entry;
- }
+ if ((error = git_vector_insert(&new_frame->entries, new_entry)) < 0)
+ goto done;
}
- /* if ignore_case, sort entries case insensitively */
- if (iterator__ignore_case(ti))
- git__tsort_r(
- (void **)tf->entries, tf->n_entries, tree_iterator__ci_cmp, tf);
-
- /* pick tf->current based on "start" (or start at zero) */
- if (head->startlen > 0) {
- git__bsearch_r((void **)tf->entries, tf->n_entries, head,
- tree_iterator__search_cmp, ti, &tf->current);
-
- while (tf->current &&
- !tree_iterator__search_cmp(head, tf->entries[tf->current-1], ti))
- tf->current--;
+ git_vector_set_sorted(&new_frame->entries,
+ !iterator__ignore_case(&iter->base));
- if ((tf->start = strchr(head->start, '/')) != NULL) {
- tf->start++;
- tf->startlen = strlen(tf->start);
- }
+done:
+ if (error < 0) {
+ git_tree_free(dup);
+ git_array_pop(iter->frames);
}
- ti->path_has_filename = ti->entry_is_current = false;
-
- if ((error = tree_iterator__set_next(ti, tf)) < 0)
- return error;
-
- /* autoexpand as needed */
- if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
- return tree_iterator__push_frame(ti);
+ return error;
+}
- return 0;
+GIT_INLINE(tree_iterator_entry *) tree_iterator_current_entry(
+ tree_iterator_frame *frame)
+{
+ return frame->current;
}
-static bool tree_iterator__pop_frame(tree_iterator *ti, bool final)
+GIT_INLINE(int) tree_iterator_frame_push_neighbors(
+ tree_iterator *iter,
+ tree_iterator_frame *parent_frame,
+ tree_iterator_frame *frame,
+ const char *filename)
{
- tree_iterator_frame *tf = ti->head;
+ tree_iterator_entry *entry, *new_entry;
+ git_tree *tree = NULL;
+ git_tree_entry *tree_entry;
+ git_buf *path;
+ size_t new_size, i;
+ int error = 0;
- assert(tf);
+ while (parent_frame->next_idx < parent_frame->entries.length) {
+ entry = parent_frame->entries.contents[parent_frame->next_idx];
- if (!tf->up)
- return false;
+ if (strcasecmp(filename, entry->tree_entry->filename) != 0)
+ break;
- ti->head = tf->up;
- ti->head->down = NULL;
+ if ((error = git_tree_lookup(&tree,
+ iter->base.repo, entry->tree_entry->oid)) < 0)
+ break;
- tree_iterator__move_to_next(ti, tf);
+ path = git_array_alloc(parent_frame->similar_paths);
+ GITERR_CHECK_ALLOC(path);
- if (!final) { /* if final, don't bother to clean up */
- // TODO: maybe free the pool so far?
- git_buf_rtruncate_at_char(&ti->path, '/');
- }
+ memset(path, 0, sizeof(git_buf));
- git__free(tf);
+ if ((error = tree_iterator_compute_path(path, entry)) < 0)
+ break;
- return true;
-}
+ GITERR_CHECK_ALLOC_ADD(&new_size,
+ frame->entries.length, tree->entries.size);
+ git_vector_size_hint(&frame->entries, new_size);
-static void tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final)
-{
- while (tree_iterator__pop_frame(ti, final)) /* pop to root */;
+ git_array_foreach(tree->entries, i, tree_entry) {
+ new_entry = git_pool_malloc(&iter->entry_pool, 1);
+ GITERR_CHECK_ALLOC(new_entry);
+
+ new_entry->tree_entry = tree_entry;
+ new_entry->parent_path = path->ptr;
- if (!final) {
- assert(ti->head);
+ if ((error = git_vector_insert(&frame->entries, new_entry)) < 0)
+ break;
+ }
- ti->head->current = to_end ? ti->head->n_entries : 0;
- ti->path_ambiguities = 0;
- git_buf_clear(&ti->path);
+ if (error)
+ break;
+
+ parent_frame->next_idx++;
}
+
+ return error;
}
-static int tree_iterator__update_entry(tree_iterator *ti)
+GIT_INLINE(int) tree_iterator_frame_push(
+ tree_iterator *iter, tree_iterator_entry *entry)
{
- tree_iterator_frame *tf;
- const git_tree_entry *te;
+ tree_iterator_frame *parent_frame, *frame;
+ git_tree *tree = NULL;
+ int error;
- if (ti->entry_is_current)
- return 0;
+ if ((error = git_tree_lookup(&tree,
+ iter->base.repo, entry->tree_entry->oid)) < 0 ||
+ (error = tree_iterator_frame_init(iter, tree, entry)) < 0)
+ goto done;
- tf = ti->head;
- te = tf->entries[tf->current]->te;
+ parent_frame = tree_iterator_parent_frame(iter);
+ frame = tree_iterator_current_frame(iter);
- ti->entry.mode = te->attr;
- git_oid_cpy(&ti->entry.id, te->oid);
+ /* if we're case insensitive, then we may have another directory that
+ * is (case insensitively) equal to this one. coalesce those children
+ * into this tree.
+ */
+ if (iterator__ignore_case(&iter->base))
+ error = tree_iterator_frame_push_neighbors(iter,
+ parent_frame, frame, entry->tree_entry->filename);
- ti->entry.path = tree_iterator__current_filename(ti, te);
- GITERR_CHECK_ALLOC(ti->entry.path);
+done:
+ git_tree_free(tree);
+ return error;
+}
- if (ti->path_ambiguities > 0)
- tree_iterator__rewrite_filename(ti);
+static void tree_iterator_frame_pop(tree_iterator *iter)
+{
+ tree_iterator_frame *frame;
- if (iterator__past_end(ti, ti->entry.path)) {
- tree_iterator__pop_all(ti, true, false);
- return GIT_ITEROVER;
- }
+ assert(iter->frames.size);
- ti->entry_is_current = true;
+ frame = git_array_pop(iter->frames);
- return 0;
+ git_vector_free(&frame->entries);
+ git_tree_free(frame->tree);
}
-static int tree_iterator__current_internal(
- const git_index_entry **entry, git_iterator *self)
+static int tree_iterator_current(
+ const git_index_entry **out, git_iterator *i)
{
- int error;
- tree_iterator *ti = (tree_iterator *)self;
- tree_iterator_frame *tf = ti->head;
+ tree_iterator *iter = (tree_iterator *)i;
- iterator__clear_entry(entry);
+ if (!iterator__has_been_accessed(i))
+ return iter->base.cb->advance(out, i);
- if (tf->current >= tf->n_entries)
+ if (!iter->frames.size) {
+ *out = NULL;
return GIT_ITEROVER;
+ }
- if ((error = tree_iterator__update_entry(ti)) < 0)
- return error;
-
- if (entry)
- *entry = &ti->entry;
-
- ti->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
-
+ *out = &iter->entry;
return 0;
}
-static int tree_iterator__advance_into_internal(git_iterator *self)
+static void tree_iterator_set_current(
+ tree_iterator *iter,
+ tree_iterator_frame *frame,
+ tree_iterator_entry *entry)
{
- int error = 0;
- tree_iterator *ti = (tree_iterator *)self;
+ git_tree_entry *tree_entry = entry->tree_entry;
- if (tree_iterator__at_tree(ti))
- error = tree_iterator__push_frame(ti);
+ frame->current = entry;
- return error;
+ memset(&iter->entry, 0x0, sizeof(git_index_entry));
+
+ iter->entry.mode = tree_entry->attr;
+ iter->entry.path = iter->entry_path.ptr;
+ git_oid_cpy(&iter->entry.id, tree_entry->oid);
}
-static int tree_iterator__advance_internal(git_iterator *self)
+static int tree_iterator_advance(const git_index_entry **out, git_iterator *i)
{
- int error;
- tree_iterator *ti = (tree_iterator *)self;
- tree_iterator_frame *tf = ti->head;
+ tree_iterator *iter = (tree_iterator *)i;
+ int error = 0;
- if (tf->current >= tf->n_entries)
- return GIT_ITEROVER;
+ iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
- if (!iterator__has_been_accessed(ti))
- return 0;
+ /* examine tree entries until we find the next one to return */
+ while (true) {
+ tree_iterator_entry *prev_entry, *entry;
+ tree_iterator_frame *frame;
+ bool is_tree;
- if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) &&
- tree_iterator__at_tree(ti))
- return tree_iterator__advance_into_internal(self);
+ if ((frame = tree_iterator_current_frame(iter)) == NULL) {
+ error = GIT_ITEROVER;
+ break;
+ }
- if (ti->path_has_filename) {
- git_buf_rtruncate_at_char(&ti->path, '/');
- ti->path_has_filename = ti->entry_is_current = false;
- }
+ /* no more entries in this frame. pop the frame out */
+ if (frame->next_idx == frame->entries.length) {
+ tree_iterator_frame_pop(iter);
+ continue;
+ }
- /* scan forward and up, advancing in frame or popping frame when done */
- while (!tree_iterator__move_to_next(ti, tf) &&
- tree_iterator__pop_frame(ti, false))
- tf = ti->head;
+ /* we may have coalesced the contents of case-insensitively same-named
+ * directories, so do the sort now.
+ */
+ if (frame->next_idx == 0 && !git_vector_is_sorted(&frame->entries))
+ git_vector_sort(&frame->entries);
- /* find next and load trees */
- if ((error = tree_iterator__set_next(ti, tf)) < 0)
- return error;
+ /* we have more entries in the current frame, that's our next entry */
+ prev_entry = tree_iterator_current_entry(frame);
+ entry = frame->entries.contents[frame->next_idx];
+ frame->next_idx++;
- /* deal with include_trees / auto_expand as needed */
- if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
- return tree_iterator__advance_into_internal(self);
+ /* we can have collisions when iterating case insensitively. (eg,
+ * 'A/a' and 'a/A'). squash this one if it's already been seen.
+ */
+ if (iterator__ignore_case(&iter->base) &&
+ prev_entry &&
+ tree_iterator_entry_cmp_icase(prev_entry, entry) == 0)
+ continue;
- return 0;
-}
+ if ((error = tree_iterator_compute_path(&iter->entry_path, entry)) < 0)
+ break;
-static int tree_iterator__current(
- const git_index_entry **out, git_iterator *self)
-{
- const git_index_entry *entry = NULL;
- iterator_pathlist__match_t m;
- int error;
+ /* if this path is before our start, advance over this entry */
+ if (!iterator_has_started(&iter->base, iter->entry_path.ptr))
+ continue;
- do {
- if ((error = tree_iterator__current_internal(&entry, self)) < 0)
- return error;
+ /* if this path is after our end, stop */
+ if (iterator_has_ended(&iter->base, iter->entry_path.ptr)) {
+ error = GIT_ITEROVER;
+ break;
+ }
- if (self->pathlist.length) {
- m = iterator_pathlist__match(
- self, entry->path, strlen(entry->path));
+ /* if we have a list of paths we're interested in, examine it */
+ if (!iterator_pathlist_next_is(&iter->base, iter->entry_path.ptr))
+ continue;
+
+ is_tree = git_tree_entry__is_tree(entry->tree_entry);
- if (m != ITERATOR_PATHLIST_MATCH) {
- if ((error = tree_iterator__advance_internal(self)) < 0)
- return error;
+ /* if we are *not* including trees then advance over this entry */
+ if (is_tree && !iterator__include_trees(iter)) {
- entry = NULL;
+ /* if we've found a tree (and are not returning it to the caller)
+ * and we are autoexpanding, then we want to return the first
+ * child. push the new directory and advance.
+ */
+ if (iterator__do_autoexpand(iter)) {
+ if ((error = tree_iterator_frame_push(iter, entry)) < 0)
+ break;
}
+
+ continue;
}
- } while (!entry);
+
+ tree_iterator_set_current(iter, frame, entry);
+
+ /* if we are autoexpanding, then push this as a new frame, so that
+ * the next call to `advance` will dive into this directory.
+ */
+ if (is_tree && iterator__do_autoexpand(iter))
+ error = tree_iterator_frame_push(iter, entry);
+
+ break;
+ }
if (out)
- *out = entry;
+ *out = (error == 0) ? &iter->entry : NULL;
return error;
}
-static int tree_iterator__advance(
- const git_index_entry **entry, git_iterator *self)
+static int tree_iterator_advance_into(
+ const git_index_entry **out, git_iterator *i)
{
- int error = tree_iterator__advance_internal(self);
+ tree_iterator *iter = (tree_iterator *)i;
+ tree_iterator_frame *frame;
+ tree_iterator_entry *prev_entry;
+ int error;
- iterator__clear_entry(entry);
+ if (out)
+ *out = NULL;
- if (error < 0)
- return error;
+ if ((frame = tree_iterator_current_frame(iter)) == NULL)
+ return GIT_ITEROVER;
- return tree_iterator__current(entry, self);
-}
+ /* get the last seen entry */
+ prev_entry = tree_iterator_current_entry(frame);
-static int tree_iterator__advance_into(
- const git_index_entry **entry, git_iterator *self)
-{
- int error = tree_iterator__advance_into_internal(self);
+ /* it's legal to call advance_into when auto-expand is on. in this case,
+ * we will have pushed a new (empty) frame on to the stack for this
+ * new directory. since it's empty, its current_entry should be null.
+ */
+ assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL));
- iterator__clear_entry(entry);
+ if (prev_entry) {
+ if (!git_tree_entry__is_tree(prev_entry->tree_entry))
+ return 0;
- if (error < 0)
- return error;
+ if ((error = tree_iterator_frame_push(iter, prev_entry)) < 0)
+ return error;
+ }
- return tree_iterator__current(entry, self);
+ /* we've advanced into the directory in question, let advance
+ * find the first entry
+ */
+ return tree_iterator_advance(out, i);
}
-static int tree_iterator__seek(git_iterator *self, const char *prefix)
+static int tree_iterator_advance_over(
+ const git_index_entry **out,
+ git_iterator_status_t *status,
+ git_iterator *i)
{
- GIT_UNUSED(self); GIT_UNUSED(prefix);
- return -1;
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+ return git_iterator_advance(out, i);
}
-static int tree_iterator__reset(
- git_iterator *self, const char *start, const char *end)
+static void tree_iterator_clear(tree_iterator *iter)
{
- tree_iterator *ti = (tree_iterator *)self;
+ while (iter->frames.size)
+ tree_iterator_frame_pop(iter);
- tree_iterator__pop_all(ti, false, false);
+ git_array_clear(iter->frames);
- if (iterator__reset_range(self, start, end) < 0)
- return -1;
+ git_pool_clear(&iter->entry_pool);
+ git_buf_clear(&iter->entry_path);
- return tree_iterator__push_frame(ti); /* re-expand root tree */
+ iterator_clear(&iter->base);
}
-static int tree_iterator__at_end(git_iterator *self)
+static int tree_iterator_init(tree_iterator *iter)
{
- tree_iterator *ti = (tree_iterator *)self;
- return (ti->head->current >= ti->head->n_entries);
-}
+ int error;
-static void tree_iterator__free(git_iterator *self)
-{
- tree_iterator *ti = (tree_iterator *)self;
+ git_pool_init(&iter->entry_pool, sizeof(tree_iterator_entry));
- if (ti->head) {
- tree_iterator__pop_all(ti, true, false);
- git_tree_free(ti->head->entries[0]->tree);
- git__free(ti->head);
- }
+ if ((error = tree_iterator_frame_init(iter, iter->root, NULL)) < 0)
+ return error;
- git_pool_clear(&ti->pool);
- git_buf_free(&ti->path);
+ iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+
+ return 0;
}
-static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree)
+static int tree_iterator_reset(git_iterator *i)
{
- size_t sz = sizeof(tree_iterator_frame) + sizeof(tree_iterator_entry);
- tree_iterator_frame *root = git__calloc(sz, sizeof(char));
- GITERR_CHECK_ALLOC(root);
+ tree_iterator *iter = (tree_iterator *)i;
- root->n_entries = 1;
- root->next = 1;
- root->start = ti->base.start;
- root->startlen = root->start ? strlen(root->start) : 0;
- root->entries[0] = git_pool_mallocz(&ti->pool, 1);
- GITERR_CHECK_ALLOC(root->entries[0]);
- root->entries[0]->tree = tree;
+ tree_iterator_clear(iter);
+ return tree_iterator_init(iter);
+}
- ti->head = ti->root = root;
+static void tree_iterator_free(git_iterator *i)
+{
+ tree_iterator *iter = (tree_iterator *)i;
- return 0;
+ tree_iterator_clear(iter);
+
+ git_tree_free(iter->root);
+ git_buf_free(&iter->entry_path);
}
int git_iterator_for_tree(
- git_iterator **iter,
+ git_iterator **out,
git_tree *tree,
git_iterator_options *options)
{
+ tree_iterator *iter;
int error;
- tree_iterator *ti;
- if (tree == NULL)
- return git_iterator_for_nothing(iter, options);
-
- if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0)
- return error;
+ static git_iterator_callbacks callbacks = {
+ tree_iterator_current,
+ tree_iterator_advance,
+ tree_iterator_advance_into,
+ tree_iterator_advance_over,
+ tree_iterator_reset,
+ tree_iterator_free
+ };
- ti = git__calloc(1, sizeof(tree_iterator));
- GITERR_CHECK_ALLOC(ti);
+ *out = NULL;
- ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree));
+ if (tree == NULL)
+ return git_iterator_for_nothing(out, options);
- if ((error = iterator__update_ignore_case((git_iterator *)ti, options ? options->flags : 0)) < 0)
- goto fail;
+ iter = git__calloc(1, sizeof(tree_iterator));
+ GITERR_CHECK_ALLOC(iter);
- git_pool_init(&ti->pool, sizeof(tree_iterator_entry));
+ iter->base.type = GIT_ITERATOR_TYPE_TREE;
+ iter->base.cb = &callbacks;
- if ((error = tree_iterator__create_root_frame(ti, tree)) < 0 ||
- (error = tree_iterator__push_frame(ti)) < 0) /* expand root now */
- goto fail;
+ if ((error = iterator_init_common(&iter->base,
+ git_tree_owner(tree), NULL, options)) < 0 ||
+ (error = git_tree_dup(&iter->root, tree)) < 0 ||
+ (error = tree_iterator_init(iter)) < 0)
+ goto on_error;
- *iter = (git_iterator *)ti;
+ *out = &iter->base;
return 0;
-fail:
- git_iterator_free((git_iterator *)ti);
+on_error:
+ git_iterator_free(&iter->base);
return error;
}
-
-typedef struct {
- git_iterator base;
- git_iterator_callbacks cb;
- git_index *index;
- git_vector entries;
- git_vector_cmp entry_srch;
- size_t current;
- /* when limiting with a pathlist, this is the current index into it */
- size_t pathlist_idx;
- /* when not in autoexpand mode, use these to represent "tree" state */
- git_buf partial;
- size_t partial_pos;
- char restore_terminator;
- git_index_entry tree_entry;
-} index_iterator;
-
-static const git_index_entry *index_iterator__index_entry(index_iterator *ii)
-{
- const git_index_entry *ie = git_vector_get(&ii->entries, ii->current);
-
- if (ie != NULL && iterator__past_end(ii, ie->path)) {
- ii->current = git_vector_length(&ii->entries);
- ie = NULL;
- }
-
- return ie;
-}
-
-static const git_index_entry *index_iterator__advance_over_unwanted(
- index_iterator *ii)
+int git_iterator_current_tree_entry(
+ const git_tree_entry **tree_entry, git_iterator *i)
{
- const git_index_entry *ie = index_iterator__index_entry(ii);
- bool match;
-
- while (ie) {
- if (!iterator__include_conflicts(ii) &&
- git_index_entry_is_conflict(ie)) {
- ii->current++;
- ie = index_iterator__index_entry(ii);
- continue;
- }
+ tree_iterator *iter;
+ tree_iterator_frame *frame;
+ tree_iterator_entry *entry;
- /* if we have a pathlist, this entry's path must be in it to be
- * returned. walk the pathlist in unison with the index to
- * compare paths.
- */
- if (ii->base.pathlist.length) {
- match = iterator_pathlist_walk__contains(&ii->base, ie->path);
+ assert(i->type == GIT_ITERATOR_TYPE_TREE);
- if (!match) {
- ii->current++;
- ie = index_iterator__index_entry(ii);
- continue;
- }
- }
+ iter = (tree_iterator *)i;
- break;
- }
+ frame = tree_iterator_current_frame(iter);
+ entry = tree_iterator_current_entry(frame);
- return ie;
+ *tree_entry = entry->tree_entry;
+ return 0;
}
-static void index_iterator__next_prefix_tree(index_iterator *ii)
+int git_iterator_current_parent_tree(
+ const git_tree **parent_tree, git_iterator *i, size_t depth)
{
- const char *slash;
+ tree_iterator *iter;
+ tree_iterator_frame *frame;
- if (!iterator__include_trees(ii))
- return;
+ assert(i->type == GIT_ITERATOR_TYPE_TREE);
- slash = strchr(&ii->partial.ptr[ii->partial_pos], '/');
+ iter = (tree_iterator *)i;
- if (slash != NULL) {
- ii->partial_pos = (slash - ii->partial.ptr) + 1;
- ii->restore_terminator = ii->partial.ptr[ii->partial_pos];
- ii->partial.ptr[ii->partial_pos] = '\0';
- } else {
- ii->partial_pos = ii->partial.size;
- }
+ assert(depth < iter->frames.size);
+ frame = &iter->frames.ptr[iter->frames.size-depth-1];
- if (index_iterator__index_entry(ii) == NULL)
- ii->partial_pos = ii->partial.size;
+ *parent_tree = frame->tree;
+ return 0;
}
-static int index_iterator__first_prefix_tree(index_iterator *ii)
-{
- const git_index_entry *ie = index_iterator__advance_over_unwanted(ii);
- const char *scan, *prior, *slash;
+/* Filesystem iterator */
- if (!ie || !iterator__include_trees(ii))
- return 0;
+typedef struct {
+ struct stat st;
+ size_t path_len;
+ iterator_pathlist_search_t match;
+ char path[GIT_FLEX_ARRAY];
+} filesystem_iterator_entry;
- /* find longest common prefix with prior index entry */
- for (scan = slash = ie->path, prior = ii->partial.ptr;
- *scan && *scan == *prior; ++scan, ++prior)
- if (*scan == '/')
- slash = scan;
+typedef struct {
+ git_vector entries;
+ git_pool entry_pool;
+ size_t next_idx;
- if (git_buf_sets(&ii->partial, ie->path) < 0)
- return -1;
+ size_t path_len;
+ int is_ignored;
+} filesystem_iterator_frame;
- ii->partial_pos = (slash - ie->path) + 1;
- index_iterator__next_prefix_tree(ii);
+typedef struct {
+ git_iterator base;
+ char *root;
+ size_t root_len;
- return 0;
-}
+ unsigned int dirload_flags;
-#define index_iterator__at_tree(I) \
- (iterator__include_trees(I) && (I)->partial_pos < (I)->partial.size)
+ git_tree *tree;
+ git_index *index;
+ git_vector index_snapshot;
-static int index_iterator__current(
- const git_index_entry **entry, git_iterator *self)
-{
- index_iterator *ii = (index_iterator *)self;
- const git_index_entry *ie = git_vector_get(&ii->entries, ii->current);
+ git_array_t(filesystem_iterator_frame) frames;
+ git_ignores ignores;
- if (ie != NULL && index_iterator__at_tree(ii)) {
- ii->tree_entry.path = ii->partial.ptr;
- ie = &ii->tree_entry;
- }
+ /* info about the current entry */
+ git_index_entry entry;
+ git_buf current_path;
+ int current_is_ignored;
- if (entry)
- *entry = ie;
+ /* temporary buffer for advance_over */
+ git_buf tmp_buf;
+} filesystem_iterator;
- ii->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
- return (ie != NULL) ? 0 : GIT_ITEROVER;
+GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_parent_frame(
+ filesystem_iterator *iter)
+{
+ return iter->frames.size > 1 ?
+ &iter->frames.ptr[iter->frames.size-2] : NULL;
}
-static int index_iterator__at_end(git_iterator *self)
+GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_current_frame(
+ filesystem_iterator *iter)
{
- index_iterator *ii = (index_iterator *)self;
- return (ii->current >= git_vector_length(&ii->entries));
+ return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL;
}
-static int index_iterator__advance(
- const git_index_entry **entry, git_iterator *self)
+GIT_INLINE(filesystem_iterator_entry *) filesystem_iterator_current_entry(
+ filesystem_iterator_frame *frame)
{
- index_iterator *ii = (index_iterator *)self;
- size_t entrycount = git_vector_length(&ii->entries);
- const git_index_entry *ie;
-
- if (!iterator__has_been_accessed(ii))
- return index_iterator__current(entry, self);
-
- if (index_iterator__at_tree(ii)) {
- if (iterator__do_autoexpand(ii)) {
- ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
- index_iterator__next_prefix_tree(ii);
- } else {
- /* advance to sibling tree (i.e. find entry with new prefix) */
- while (ii->current < entrycount) {
- ii->current++;
-
- if (!(ie = git_vector_get(&ii->entries, ii->current)) ||
- ii->base.prefixcomp(ie->path, ii->partial.ptr) != 0)
- break;
- }
-
- if (index_iterator__first_prefix_tree(ii) < 0)
- return -1;
- }
- } else {
- if (ii->current < entrycount)
- ii->current++;
-
- if (index_iterator__first_prefix_tree(ii) < 0)
- return -1;
- }
-
- return index_iterator__current(entry, self);
+ return frame->next_idx == 0 ?
+ NULL : frame->entries.contents[frame->next_idx-1];
}
-static int index_iterator__advance_into(
- const git_index_entry **entry, git_iterator *self)
+static int filesystem_iterator_entry_cmp(const void *_a, const void *_b)
{
- index_iterator *ii = (index_iterator *)self;
- const git_index_entry *ie = git_vector_get(&ii->entries, ii->current);
+ const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a;
+ const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b;
- if (ie != NULL && index_iterator__at_tree(ii)) {
- if (ii->restore_terminator)
- ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;
- index_iterator__next_prefix_tree(ii);
- }
-
- return index_iterator__current(entry, self);
+ return git__strcmp(a->path, b->path);
}
-static int index_iterator__seek(git_iterator *self, const char *prefix)
+static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b)
{
- GIT_UNUSED(self); GIT_UNUSED(prefix);
- return -1;
+ const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a;
+ const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b;
+
+ return git__strcasecmp(a->path, b->path);
}
-static int index_iterator__reset(
- git_iterator *self, const char *start, const char *end)
+#define FILESYSTEM_MAX_DEPTH 100
+
+/**
+ * Figure out if an entry is a submodule.
+ *
+ * We consider it a submodule if the path is listed as a submodule in
+ * either the tree or the index.
+ */
+static int filesystem_iterator_is_submodule(
+ bool *out, filesystem_iterator *iter, const char *path, size_t path_len)
{
- index_iterator *ii = (index_iterator *)self;
- const git_index_entry *ie;
+ bool is_submodule = false;
+ int error;
- if (iterator__reset_range(self, start, end) < 0)
- return -1;
+ *out = false;
- ii->current = 0;
+ /* first see if this path is a submodule in HEAD */
+ if (iter->tree) {
+ git_tree_entry *entry;
- iterator_pathlist_walk__reset(self);
+ error = git_tree_entry_bypath(&entry, iter->tree, path);
- /* if we're given a start prefix, find it; if we're given a pathlist, find
- * the first of those. start at the later of the two.
- */
- if (ii->base.start)
- git_index_snapshot_find(
- &ii->current, &ii->entries, ii->entry_srch, ii->base.start, 0, 0);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
- if ((ie = index_iterator__advance_over_unwanted(ii)) == NULL)
- return 0;
+ if (!error) {
+ is_submodule = (entry->attr == GIT_FILEMODE_COMMIT);
+ git_tree_entry_free(entry);
+ }
+ }
- if (git_buf_sets(&ii->partial, ie->path) < 0)
- return -1;
+ if (!is_submodule && iter->base.index) {
+ size_t pos;
- ii->partial_pos = 0;
+ error = git_index_snapshot_find(&pos,
+ &iter->index_snapshot, iter->base.entry_srch, path, path_len, 0);
- if (ii->base.start) {
- size_t startlen = strlen(ii->base.start);
+ if (error < 0 && error != GIT_ENOTFOUND)
+ return error;
- ii->partial_pos = (startlen > ii->partial.size) ?
- ii->partial.size : startlen;
+ if (!error) {
+ git_index_entry *e = git_vector_get(&iter->index_snapshot, pos);
+ is_submodule = (e->mode == GIT_FILEMODE_COMMIT);
+ }
}
- index_iterator__next_prefix_tree(ii);
-
+ *out = is_submodule;
return 0;
}
-static void index_iterator__free(git_iterator *self)
+static void filesystem_iterator_frame_push_ignores(
+ filesystem_iterator *iter,
+ filesystem_iterator_entry *frame_entry,
+ filesystem_iterator_frame *new_frame)
{
- index_iterator *ii = (index_iterator *)self;
- git_index_snapshot_release(&ii->entries, ii->index);
- ii->index = NULL;
- git_buf_free(&ii->partial);
-}
+ filesystem_iterator_frame *previous_frame;
+ const char *path = frame_entry ? frame_entry->path : "";
-int git_iterator_for_index(
- git_iterator **iter,
- git_repository *repo,
- git_index *index,
- git_iterator_options *options)
-{
- int error = 0;
- index_iterator *ii = git__calloc(1, sizeof(index_iterator));
- GITERR_CHECK_ALLOC(ii);
+ if (!iterator__honor_ignores(&iter->base))
+ return;
- if ((error = git_index_snapshot_new(&ii->entries, index)) < 0) {
- git__free(ii);
- return error;
+ if (git_ignore__lookup(&new_frame->is_ignored,
+ &iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) {
+ giterr_clear();
+ new_frame->is_ignored = GIT_IGNORE_NOTFOUND;
}
- ii->index = index;
- ITERATOR_BASE_INIT(ii, index, INDEX, repo);
-
- if ((error = iterator__update_ignore_case((git_iterator *)ii, options ? options->flags : 0)) < 0) {
- git_iterator_free((git_iterator *)ii);
- return error;
- }
+ /* if this is not the top level directory... */
+ if (frame_entry) {
+ const char *relative_path;
- ii->entry_srch = iterator__ignore_case(ii) ?
- git_index_entry_isrch : git_index_entry_srch;
+ previous_frame = filesystem_iterator_parent_frame(iter);
- git_vector_set_cmp(&ii->entries, iterator__ignore_case(ii) ?
- git_index_entry_icmp : git_index_entry_cmp);
- git_vector_sort(&ii->entries);
+ /* push new ignores for files in this directory */
+ relative_path = frame_entry->path + previous_frame->path_len;
- git_buf_init(&ii->partial, 0);
- ii->tree_entry.mode = GIT_FILEMODE_TREE;
+ /* inherit ignored from parent if no rule specified */
+ if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND)
+ new_frame->is_ignored = previous_frame->is_ignored;
- index_iterator__reset((git_iterator *)ii, NULL, NULL);
+ git_ignore__push_dir(&iter->ignores, relative_path);
+ }
+}
- *iter = (git_iterator *)ii;
- return 0;
+static void filesystem_iterator_frame_pop_ignores(
+ filesystem_iterator *iter)
+{
+ if (iterator__honor_ignores(&iter->base))
+ git_ignore__pop_dir(&iter->ignores);
}
+GIT_INLINE(bool) filesystem_iterator_examine_path(
+ bool *is_dir_out,
+ iterator_pathlist_search_t *match_out,
+ filesystem_iterator *iter,
+ filesystem_iterator_entry *frame_entry,
+ const char *path,
+ size_t path_len)
+{
+ bool is_dir = 0;
+ iterator_pathlist_search_t match = ITERATOR_PATHLIST_FULL;
-typedef struct fs_iterator_frame fs_iterator_frame;
-struct fs_iterator_frame {
- fs_iterator_frame *next;
- git_vector entries;
- size_t index;
- int is_ignored;
-};
+ *is_dir_out = false;
+ *match_out = ITERATOR_PATHLIST_NONE;
-typedef struct fs_iterator fs_iterator;
-struct fs_iterator {
- git_iterator base;
- git_iterator_callbacks cb;
- fs_iterator_frame *stack;
- git_index_entry entry;
- git_buf path;
- size_t root_len;
- uint32_t dirload_flags;
- int depth;
- iterator_pathlist__match_t pathlist_match;
+ if (iter->base.start_len) {
+ int cmp = iter->base.strncomp(path, iter->base.start, path_len);
- int (*enter_dir_cb)(fs_iterator *self);
- int (*leave_dir_cb)(fs_iterator *self);
- int (*update_entry_cb)(fs_iterator *self);
-};
+ /* we haven't stat'ed `path` yet, so we don't yet know if it's a
+ * directory or not. special case if the current path may be a
+ * directory that matches the start prefix.
+ */
+ if (cmp == 0) {
+ if (iter->base.start[path_len] == '/')
+ is_dir = true;
-#define FS_MAX_DEPTH 100
+ else if (iter->base.start[path_len] != '\0')
+ cmp = -1;
+ }
-typedef struct {
- struct stat st;
- iterator_pathlist__match_t pathlist_match;
- size_t path_len;
- char path[GIT_FLEX_ARRAY];
-} fs_iterator_path_with_stat;
+ if (cmp < 0)
+ return false;
+ }
-static int fs_iterator_path_with_stat_cmp(const void *a, const void *b)
-{
- const fs_iterator_path_with_stat *psa = a, *psb = b;
- return strcmp(psa->path, psb->path);
-}
+ if (iter->base.end_len) {
+ int cmp = iter->base.strncomp(path, iter->base.end, iter->base.end_len);
-static int fs_iterator_path_with_stat_cmp_icase(const void *a, const void *b)
-{
- const fs_iterator_path_with_stat *psa = a, *psb = b;
- return strcasecmp(psa->path, psb->path);
-}
+ if (cmp > 0)
+ return false;
+ }
-static fs_iterator_frame *fs_iterator__alloc_frame(fs_iterator *fi)
-{
- fs_iterator_frame *ff = git__calloc(1, sizeof(fs_iterator_frame));
- git_vector_cmp entry_compare = CASESELECT(
- iterator__ignore_case(fi),
- fs_iterator_path_with_stat_cmp_icase,
- fs_iterator_path_with_stat_cmp);
+ /* if we have a pathlist that we're limiting to, examine this path now
+ * to avoid a `stat` if we're not interested in the path.
+ */
+ if (iter->base.pathlist.length) {
+ /* if our parent was explicitly included, so too are we */
+ if (frame_entry && frame_entry->match != ITERATOR_PATHLIST_IS_PARENT)
+ match = ITERATOR_PATHLIST_FULL;
+ else
+ match = iterator_pathlist_search(&iter->base, path, path_len);
- if (ff && git_vector_init(&ff->entries, 0, entry_compare) < 0) {
- git__free(ff);
- ff = NULL;
- }
+ if (match == ITERATOR_PATHLIST_NONE)
+ return false;
- return ff;
-}
+ /* Ensure that the pathlist entry lines up with what we expected */
+ if (match == ITERATOR_PATHLIST_IS_DIR ||
+ match == ITERATOR_PATHLIST_IS_PARENT)
+ is_dir = true;
+ }
-static void fs_iterator__free_frame(fs_iterator_frame *ff)
-{
- git_vector_free_deep(&ff->entries);
- git__free(ff);
+ *is_dir_out = is_dir;
+ *match_out = match;
+ return true;
}
-static void fs_iterator__pop_frame(
- fs_iterator *fi, fs_iterator_frame *ff, bool pop_last)
+GIT_INLINE(bool) filesystem_iterator_is_dot_git(
+ filesystem_iterator *iter, const char *path, size_t path_len)
{
- if (fi && fi->stack == ff) {
- if (!ff->next && !pop_last) {
- memset(&fi->entry, 0, sizeof(fi->entry));
- return;
- }
+ size_t len;
- if (fi->leave_dir_cb)
- (void)fi->leave_dir_cb(fi);
+ if (!iterator__ignore_dot_git(&iter->base))
+ return false;
- fi->stack = ff->next;
- fi->depth--;
- }
+ if ((len = path_len) < 4)
+ return false;
- fs_iterator__free_frame(ff);
-}
+ if (path[len - 1] == '/')
+ len--;
-static int fs_iterator__update_entry(fs_iterator *fi);
-static int fs_iterator__advance_over(
- const git_index_entry **entry, git_iterator *self);
+ if (git__tolower(path[len - 1]) != 't' ||
+ git__tolower(path[len - 2]) != 'i' ||
+ git__tolower(path[len - 3]) != 'g' ||
+ git__tolower(path[len - 4]) != '.')
+ return false;
-static int fs_iterator__entry_cmp(const void *i, const void *item)
-{
- const fs_iterator *fi = (const fs_iterator *)i;
- const fs_iterator_path_with_stat *ps = item;
- return fi->base.prefixcomp(fi->base.start, ps->path);
+ return (len == 4 || path[len - 5] == '/');
}
-static void fs_iterator__seek_frame_start(
- fs_iterator *fi, fs_iterator_frame *ff)
+static filesystem_iterator_entry *filesystem_iterator_entry_init(
+ filesystem_iterator_frame *frame,
+ const char *path,
+ size_t path_len,
+ struct stat *statbuf,
+ iterator_pathlist_search_t pathlist_match)
{
- if (!ff)
- return;
+ filesystem_iterator_entry *entry;
+ size_t entry_size;
- if (fi->base.start)
- git_vector_bsearch2(
- &ff->index, &ff->entries, fs_iterator__entry_cmp, fi);
- else
- ff->index = 0;
+ /* Make sure to append two bytes, one for the path's null
+ * termination, one for a possible trailing '/' for folders.
+ */
+ if (GIT_ADD_SIZET_OVERFLOW(&entry_size,
+ sizeof(filesystem_iterator_entry), path_len) ||
+ GIT_ADD_SIZET_OVERFLOW(&entry_size, entry_size, 2) ||
+ (entry = git_pool_malloc(&frame->entry_pool, entry_size)) == NULL)
+ return NULL;
+
+ entry->path_len = path_len;
+ entry->match = pathlist_match;
+ memcpy(entry->path, path, path_len);
+ memcpy(&entry->st, statbuf, sizeof(struct stat));
+
+ /* Suffix directory paths with a '/' */
+ if (S_ISDIR(entry->st.st_mode))
+ entry->path[entry->path_len++] = '/';
+
+ entry->path[entry->path_len] = '\0';
+
+ return entry;
}
-static int dirload_with_stat(git_vector *contents, fs_iterator *fi)
+static int filesystem_iterator_frame_push(
+ filesystem_iterator *iter,
+ filesystem_iterator_entry *frame_entry)
{
+ filesystem_iterator_frame *new_frame = NULL;
git_path_diriter diriter = GIT_PATH_DIRITER_INIT;
+ git_buf root = GIT_BUF_INIT;
const char *path;
- size_t start_len = fi->base.start ? strlen(fi->base.start) : 0;
- size_t end_len = fi->base.end ? strlen(fi->base.end) : 0;
- fs_iterator_path_with_stat *ps;
- size_t path_len, cmp_len, ps_size;
- iterator_pathlist__match_t pathlist_match = ITERATOR_PATHLIST_MATCH;
+ filesystem_iterator_entry *entry;
+ struct stat statbuf;
+ size_t path_len;
int error;
+ if (iter->frames.size == FILESYSTEM_MAX_DEPTH) {
+ giterr_set(GITERR_REPOSITORY,
+ "directory nesting too deep (%d)", iter->frames.size);
+ return -1;
+ }
+
+ new_frame = git_array_alloc(iter->frames);
+ GITERR_CHECK_ALLOC(new_frame);
+
+ memset(new_frame, 0, sizeof(filesystem_iterator_frame));
+
+ if (frame_entry)
+ git_buf_joinpath(&root, iter->root, frame_entry->path);
+ else
+ git_buf_puts(&root, iter->root);
+
+ if (git_buf_oom(&root)) {
+ error = -1;
+ goto done;
+ }
+
+ new_frame->path_len = frame_entry ? frame_entry->path_len : 0;
+
/* Any error here is equivalent to the dir not existing, skip over it */
if ((error = git_path_diriter_init(
- &diriter, fi->path.ptr, fi->dirload_flags)) < 0) {
+ &diriter, root.ptr, iter->dirload_flags)) < 0) {
error = GIT_ENOTFOUND;
goto done;
}
+ if ((error = git_vector_init(&new_frame->entries, 64,
+ iterator__ignore_case(&iter->base) ?
+ filesystem_iterator_entry_cmp_icase :
+ filesystem_iterator_entry_cmp)) < 0)
+ goto done;
+
+ git_pool_init(&new_frame->entry_pool, 1);
+
+ /* check if this directory is ignored */
+ filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame);
+
while ((error = git_path_diriter_next(&diriter)) == 0) {
+ iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL;
+ bool dir_expected = false;
+
if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0)
goto done;
- assert(path_len > fi->root_len);
+ assert(path_len > iter->root_len);
/* remove the prefix if requested */
- path += fi->root_len;
- path_len -= fi->root_len;
+ path += iter->root_len;
+ path_len -= iter->root_len;
- /* skip if before start_stat or after end_stat */
- cmp_len = min(start_len, path_len);
- if (cmp_len && fi->base.strncomp(path, fi->base.start, cmp_len) < 0)
- continue;
- /* skip if after end_stat */
- cmp_len = min(end_len, path_len);
- if (cmp_len && fi->base.strncomp(path, fi->base.end, cmp_len) > 0)
- continue;
-
- /* if we have a pathlist that we're limiting to, examine this path.
- * if the frame has already deemed us inside the path (eg, we're in
- * `foo/bar` and the pathlist previously was detected to say `foo/`)
- * then simply continue. otherwise, examine the pathlist looking for
- * this path or children of this path.
+ /* examine start / end and the pathlist to see if this path is in it.
+ * note that since we haven't yet stat'ed the path, we cannot know
+ * whether it's a directory yet or not, so this can give us an
+ * expected type (S_IFDIR or S_IFREG) that we should examine)
*/
- if (fi->base.pathlist.length &&
- fi->pathlist_match != ITERATOR_PATHLIST_MATCH &&
- fi->pathlist_match != ITERATOR_PATHLIST_MATCH_DIRECTORY &&
- !(pathlist_match = iterator_pathlist__match(&fi->base, path, path_len)))
+ if (!filesystem_iterator_examine_path(&dir_expected, &pathlist_match,
+ iter, frame_entry, path, path_len))
continue;
- /* Make sure to append two bytes, one for the path's null
- * termination, one for a possible trailing '/' for folders.
+ /* TODO: don't need to stat if assume unchanged for this path and
+ * we have an index, we can just copy the data out of it.
*/
- GITERR_CHECK_ALLOC_ADD(&ps_size, sizeof(fs_iterator_path_with_stat), path_len);
- GITERR_CHECK_ALLOC_ADD(&ps_size, ps_size, 2);
- ps = git__calloc(1, ps_size);
- ps->path_len = path_len;
+ if ((error = git_path_diriter_stat(&statbuf, &diriter)) < 0) {
+ /* file was removed between readdir and lstat */
+ if (error == GIT_ENOTFOUND)
+ continue;
- memcpy(ps->path, path, path_len);
+ /* treat the file as unreadable */
+ memset(&statbuf, 0, sizeof(statbuf));
+ statbuf.st_mode = GIT_FILEMODE_UNREADABLE;
- /* TODO: don't stat if assume unchanged for this path */
+ error = 0;
+ }
- if ((error = git_path_diriter_stat(&ps->st, &diriter)) < 0) {
- if (error == GIT_ENOTFOUND) {
- /* file was removed between readdir and lstat */
- git__free(ps);
- continue;
- }
+ iter->base.stat_calls++;
- if (pathlist_match == ITERATOR_PATHLIST_MATCH_DIRECTORY) {
- /* were looking for a directory, but this is a file */
- git__free(ps);
- continue;
- }
-
- /* Treat the file as unreadable if we get any other error */
- memset(&ps->st, 0, sizeof(ps->st));
- ps->st.st_mode = GIT_FILEMODE_UNREADABLE;
+ /* Ignore wacky things in the filesystem */
+ if (!S_ISDIR(statbuf.st_mode) &&
+ !S_ISREG(statbuf.st_mode) &&
+ !S_ISLNK(statbuf.st_mode) &&
+ statbuf.st_mode != GIT_FILEMODE_UNREADABLE)
+ continue;
- giterr_clear();
- error = 0;
- } else if (S_ISDIR(ps->st.st_mode)) {
- /* Suffix directory paths with a '/' */
- ps->path[ps->path_len++] = '/';
- ps->path[ps->path_len] = '\0';
- } else if(!S_ISREG(ps->st.st_mode) && !S_ISLNK(ps->st.st_mode)) {
- /* Ignore wacky things in the filesystem */
- git__free(ps);
+ if (filesystem_iterator_is_dot_git(iter, path, path_len))
continue;
+
+ /* convert submodules to GITLINK and remove trailing slashes */
+ if (S_ISDIR(statbuf.st_mode)) {
+ bool submodule = false;
+
+ if ((error = filesystem_iterator_is_submodule(&submodule,
+ iter, path, path_len)) < 0)
+ goto done;
+
+ if (submodule)
+ statbuf.st_mode = GIT_FILEMODE_COMMIT;
}
- /* record whether this path was explicitly found in the path list
- * or whether we're only examining it because something beneath it
- * is in the path list.
- */
- ps->pathlist_match = pathlist_match;
- git_vector_insert(contents, ps);
+ /* Ensure that the pathlist entry lines up with what we expected */
+ if (dir_expected && !S_ISDIR(statbuf.st_mode))
+ continue;
+
+ entry = filesystem_iterator_entry_init(new_frame,
+ path, path_len, &statbuf, pathlist_match);
+ GITERR_CHECK_ALLOC(entry);
+
+ git_vector_insert(&new_frame->entries, entry);
}
if (error == GIT_ITEROVER)
error = 0;
/* sort now that directory suffix is added */
- git_vector_sort(contents);
+ git_vector_sort(&new_frame->entries);
done:
+ if (error < 0)
+ git_array_pop(iter->frames);
+
+ git_buf_free(&root);
git_path_diriter_free(&diriter);
return error;
}
-
-static int fs_iterator__expand_dir(fs_iterator *fi)
+GIT_INLINE(void) filesystem_iterator_frame_pop(filesystem_iterator *iter)
{
- int error;
- fs_iterator_frame *ff;
-
- if (fi->depth > FS_MAX_DEPTH) {
- giterr_set(GITERR_REPOSITORY,
- "Directory nesting is too deep (%d)", fi->depth);
- return -1;
- }
+ filesystem_iterator_frame *frame;
- ff = fs_iterator__alloc_frame(fi);
- GITERR_CHECK_ALLOC(ff);
+ assert(iter->frames.size);
- error = dirload_with_stat(&ff->entries, fi);
+ frame = git_array_pop(iter->frames);
+ filesystem_iterator_frame_pop_ignores(iter);
- if (error < 0) {
- git_error_state last_error = { 0 };
- giterr_state_capture(&last_error, error);
-
- /* these callbacks may clear the error message */
- fs_iterator__free_frame(ff);
- fs_iterator__advance_over(NULL, (git_iterator *)fi);
- /* next time return value we skipped to */
- fi->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
-
- return giterr_state_restore(&last_error);
- }
+ git_pool_clear(&frame->entry_pool);
+ git_vector_free(&frame->entries);
+}
- if (ff->entries.length == 0) {
- fs_iterator__free_frame(ff);
- return GIT_ENOTFOUND;
- }
- fi->base.stat_calls += ff->entries.length;
+static void filesystem_iterator_set_current(
+ filesystem_iterator *iter,
+ filesystem_iterator_entry *entry)
+{
+ iter->entry.ctime.seconds = entry->st.st_ctime;
+ iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec;
- fs_iterator__seek_frame_start(fi, ff);
+ iter->entry.mtime.seconds = entry->st.st_mtime;
+ iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec;
- ff->next = fi->stack;
- fi->stack = ff;
- fi->depth++;
+ iter->entry.dev = entry->st.st_dev;
+ iter->entry.ino = entry->st.st_ino;
+ iter->entry.mode = git_futils_canonical_mode(entry->st.st_mode);
+ iter->entry.uid = entry->st.st_uid;
+ iter->entry.gid = entry->st.st_gid;
+ iter->entry.file_size = entry->st.st_size;
- if (fi->enter_dir_cb && (error = fi->enter_dir_cb(fi)) < 0)
- return error;
+ iter->entry.path = entry->path;
- return fs_iterator__update_entry(fi);
+ iter->current_is_ignored = GIT_IGNORE_UNCHECKED;
}
-static int fs_iterator__current(
- const git_index_entry **entry, git_iterator *self)
+static int filesystem_iterator_current(
+ const git_index_entry **out, git_iterator *i)
{
- fs_iterator *fi = (fs_iterator *)self;
- const git_index_entry *fe = (fi->entry.path == NULL) ? NULL : &fi->entry;
+ filesystem_iterator *iter = (filesystem_iterator *)i;
- if (entry)
- *entry = fe;
+ if (!iterator__has_been_accessed(i))
+ return iter->base.cb->advance(out, i);
- fi->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
-
- return (fe != NULL) ? 0 : GIT_ITEROVER;
-}
+ if (!iter->frames.size) {
+ *out = NULL;
+ return GIT_ITEROVER;
+ }
-static int fs_iterator__at_end(git_iterator *self)
-{
- return (((fs_iterator *)self)->entry.path == NULL);
+ *out = &iter->entry;
+ return 0;
}
-static int fs_iterator__advance_into(
- const git_index_entry **entry, git_iterator *iter)
+static int filesystem_iterator_advance(
+ const git_index_entry **out, git_iterator *i)
{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
int error = 0;
- fs_iterator *fi = (fs_iterator *)iter;
- iterator__clear_entry(entry);
+ iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
- /* Allow you to explicitly advance into a commit/submodule (as well as a
- * tree) to avoid cases where an entry is mislabeled as a submodule in
- * the working directory. The fs iterator will never have COMMMIT
- * entries on it's own, but a wrapper might add them.
- */
- if (fi->entry.path != NULL &&
- (fi->entry.mode == GIT_FILEMODE_TREE ||
- fi->entry.mode == GIT_FILEMODE_COMMIT))
- /* returns GIT_ENOTFOUND if the directory is empty */
- error = fs_iterator__expand_dir(fi);
+ /* examine filesystem entries until we find the next one to return */
+ while (true) {
+ filesystem_iterator_frame *frame;
+ filesystem_iterator_entry *entry;
- if (!error && entry)
- error = fs_iterator__current(entry, iter);
+ if ((frame = filesystem_iterator_current_frame(iter)) == NULL) {
+ error = GIT_ITEROVER;
+ break;
+ }
- if (!error && !fi->entry.path)
- error = GIT_ITEROVER;
+ /* no more entries in this frame. pop the frame out */
+ if (frame->next_idx == frame->entries.length) {
+ filesystem_iterator_frame_pop(iter);
+ continue;
+ }
- return error;
-}
+ /* we have more entries in the current frame, that's our next entry */
+ entry = frame->entries.contents[frame->next_idx];
+ frame->next_idx++;
-static void fs_iterator__advance_over_internal(git_iterator *self)
-{
- fs_iterator *fi = (fs_iterator *)self;
- fs_iterator_frame *ff;
- fs_iterator_path_with_stat *next;
+ if (S_ISDIR(entry->st.st_mode)) {
+ if (iterator__do_autoexpand(iter)) {
+ error = filesystem_iterator_frame_push(iter, entry);
- while (fi->entry.path != NULL) {
- ff = fi->stack;
- next = git_vector_get(&ff->entries, ++ff->index);
+ /* may get GIT_ENOTFOUND due to races or permission problems
+ * that we want to quietly swallow
+ */
+ if (error == GIT_ENOTFOUND)
+ continue;
+ else if (error < 0)
+ break;
+ }
- if (next != NULL)
- break;
+ if (!iterator__include_trees(iter))
+ continue;
+ }
- fs_iterator__pop_frame(fi, ff, false);
+ filesystem_iterator_set_current(iter, entry);
+ break;
}
+
+ if (out)
+ *out = (error == 0) ? &iter->entry : NULL;
+
+ return error;
}
-static int fs_iterator__advance_over(
- const git_index_entry **entry, git_iterator *self)
+static int filesystem_iterator_advance_into(
+ const git_index_entry **out, git_iterator *i)
{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ filesystem_iterator_frame *frame;
+ filesystem_iterator_entry *prev_entry;
int error;
- if (entry != NULL)
- *entry = NULL;
+ if (out)
+ *out = NULL;
- fs_iterator__advance_over_internal(self);
+ if ((frame = filesystem_iterator_current_frame(iter)) == NULL)
+ return GIT_ITEROVER;
- error = fs_iterator__update_entry((fs_iterator *)self);
+ /* get the last seen entry */
+ prev_entry = filesystem_iterator_current_entry(frame);
- if (!error && entry != NULL)
- error = fs_iterator__current(entry, self);
+ /* it's legal to call advance_into when auto-expand is on. in this case,
+ * we will have pushed a new (empty) frame on to the stack for this
+ * new directory. since it's empty, its current_entry should be null.
+ */
+ assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL));
- return error;
+ if (prev_entry) {
+ if (prev_entry->st.st_mode != GIT_FILEMODE_COMMIT &&
+ !S_ISDIR(prev_entry->st.st_mode))
+ return 0;
+
+ if ((error = filesystem_iterator_frame_push(iter, prev_entry)) < 0)
+ return error;
+ }
+
+ /* we've advanced into the directory in question, let advance
+ * find the first entry
+ */
+ return filesystem_iterator_advance(out, i);
}
-static int fs_iterator__advance(
- const git_index_entry **entry, git_iterator *self)
+int git_iterator_current_workdir_path(git_buf **out, git_iterator *i)
{
- fs_iterator *fi = (fs_iterator *)self;
-
- if (!iterator__has_been_accessed(fi))
- return fs_iterator__current(entry, self);
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ const git_index_entry *entry;
- /* given include_trees & autoexpand, we might have to go into a tree */
- if (iterator__do_autoexpand(fi) &&
- fi->entry.path != NULL &&
- fi->entry.mode == GIT_FILEMODE_TREE)
- {
- int error = fs_iterator__advance_into(entry, self);
- if (error != GIT_ENOTFOUND)
- return error;
- /* continue silently past empty directories if autoexpanding */
- giterr_clear();
+ if (i->type != GIT_ITERATOR_TYPE_FS &&
+ i->type != GIT_ITERATOR_TYPE_WORKDIR) {
+ *out = NULL;
+ return 0;
}
- return fs_iterator__advance_over(entry, self);
+ git_buf_truncate(&iter->current_path, iter->root_len);
+
+ if (git_iterator_current(&entry, i) < 0 ||
+ git_buf_puts(&iter->current_path, entry->path) < 0)
+ return -1;
+
+ *out = &iter->current_path;
+ return 0;
}
-static int fs_iterator__seek(git_iterator *self, const char *prefix)
+GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry)
{
- GIT_UNUSED(self);
- GIT_UNUSED(prefix);
- /* pop stack until matching prefix */
- /* find prefix item in current frame */
- /* push subdirectories as deep as possible while matching */
- return 0;
+#if defined(GIT_WIN32) && !defined(__MINGW32__)
+ return (entry && entry->mode) ?
+ (S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE) :
+ GIT_DIR_FLAG_UNKNOWN;
+#else
+ GIT_UNUSED(entry);
+ return GIT_DIR_FLAG_UNKNOWN;
+#endif
}
-static int fs_iterator__reset(
- git_iterator *self, const char *start, const char *end)
+static void filesystem_iterator_update_ignored(filesystem_iterator *iter)
{
- int error;
- fs_iterator *fi = (fs_iterator *)self;
+ filesystem_iterator_frame *frame;
+ git_dir_flag dir_flag = entry_dir_flag(&iter->entry);
- while (fi->stack != NULL && fi->stack->next != NULL)
- fs_iterator__pop_frame(fi, fi->stack, false);
- fi->depth = 0;
+ if (git_ignore__lookup(&iter->current_is_ignored,
+ &iter->ignores, iter->entry.path, dir_flag) < 0) {
+ giterr_clear();
+ iter->current_is_ignored = GIT_IGNORE_NOTFOUND;
+ }
- if ((error = iterator__reset_range(self, start, end)) < 0)
- return error;
+ /* use ignore from containing frame stack */
+ if (iter->current_is_ignored <= GIT_IGNORE_NOTFOUND) {
+ frame = filesystem_iterator_current_frame(iter);
+ iter->current_is_ignored = frame->is_ignored;
+ }
+}
- fs_iterator__seek_frame_start(fi, fi->stack);
+GIT_INLINE(bool) filesystem_iterator_current_is_ignored(
+ filesystem_iterator *iter)
+{
+ if (iter->current_is_ignored == GIT_IGNORE_UNCHECKED)
+ filesystem_iterator_update_ignored(iter);
- error = fs_iterator__update_entry(fi);
- if (error == GIT_ITEROVER)
- error = 0;
+ return (iter->current_is_ignored == GIT_IGNORE_TRUE);
+}
- return error;
+bool git_iterator_current_is_ignored(git_iterator *i)
+{
+ if (i->type != GIT_ITERATOR_TYPE_WORKDIR)
+ return false;
+
+ return filesystem_iterator_current_is_ignored((filesystem_iterator *)i);
}
-static void fs_iterator__free(git_iterator *self)
+bool git_iterator_current_tree_is_ignored(git_iterator *i)
{
- fs_iterator *fi = (fs_iterator *)self;
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ filesystem_iterator_frame *frame;
- while (fi->stack != NULL)
- fs_iterator__pop_frame(fi, fi->stack, true);
+ if (i->type != GIT_ITERATOR_TYPE_WORKDIR)
+ return false;
- git_buf_free(&fi->path);
+ frame = filesystem_iterator_current_frame(iter);
+ return (frame->is_ignored == GIT_IGNORE_TRUE);
}
-static int fs_iterator__update_entry(fs_iterator *fi)
+static int filesystem_iterator_advance_over(
+ const git_index_entry **out,
+ git_iterator_status_t *status,
+ git_iterator *i)
{
- fs_iterator_path_with_stat *ps;
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ filesystem_iterator_frame *current_frame;
+ filesystem_iterator_entry *current_entry;
+ const git_index_entry *entry = NULL;
+ const char *base;
+ int error = 0;
- while (true) {
- memset(&fi->entry, 0, sizeof(fi->entry));
+ *out = NULL;
+ *status = GIT_ITERATOR_STATUS_NORMAL;
- if (!fi->stack)
- return GIT_ITEROVER;
+ assert(iterator__has_been_accessed(i));
- ps = git_vector_get(&fi->stack->entries, fi->stack->index);
- if (!ps)
- return GIT_ITEROVER;
+ current_frame = filesystem_iterator_current_frame(iter);
+ assert(current_frame);
+ current_entry = filesystem_iterator_current_entry(current_frame);
+ assert(current_entry);
- git_buf_truncate(&fi->path, fi->root_len);
- if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0)
- return -1;
+ if ((error = git_iterator_current(&entry, i)) < 0)
+ return error;
- if (iterator__past_end(fi, fi->path.ptr + fi->root_len))
- return GIT_ITEROVER;
+ if (!S_ISDIR(entry->mode)) {
+ if (filesystem_iterator_current_is_ignored(iter))
+ *status = GIT_ITERATOR_STATUS_IGNORED;
- fi->entry.path = ps->path;
- fi->pathlist_match = ps->pathlist_match;
- git_index_entry__init_from_stat(&fi->entry, &ps->st, true);
+ return filesystem_iterator_advance(out, i);
+ }
- /* need different mode here to keep directories during iteration */
- fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
+ git_buf_clear(&iter->tmp_buf);
+ if ((error = git_buf_puts(&iter->tmp_buf, entry->path)) < 0)
+ return error;
- /* allow wrapper to check/update the entry (can force skip) */
- if (fi->update_entry_cb &&
- fi->update_entry_cb(fi) == GIT_ENOTFOUND) {
- fs_iterator__advance_over_internal(&fi->base);
- continue;
- }
+ base = iter->tmp_buf.ptr;
+
+ /* scan inside the directory looking for files. if we find nothing,
+ * we will remain EMPTY. if we find any ignored item, upgrade EMPTY to
+ * IGNORED. if we find a real actual item, upgrade all the way to NORMAL
+ * and then stop.
+ *
+ * however, if we're here looking for a pathlist item (but are not
+ * actually in the pathlist ourselves) then start at FILTERED instead of
+ * EMPTY. callers then know that this path was not something they asked
+ * about.
+ */
+ *status = current_entry->match == ITERATOR_PATHLIST_IS_PARENT ?
+ GIT_ITERATOR_STATUS_FILTERED : GIT_ITERATOR_STATUS_EMPTY;
+
+ while (entry && !iter->base.prefixcomp(entry->path, base)) {
+ if (filesystem_iterator_current_is_ignored(iter)) {
+ /* if we found an explicitly ignored item, then update from
+ * EMPTY to IGNORED
+ */
+ *status = GIT_ITERATOR_STATUS_IGNORED;
+ } else if (S_ISDIR(entry->mode)) {
+ error = filesystem_iterator_advance_into(&entry, i);
- /* if this is a tree and trees aren't included, then skip */
- if (fi->entry.mode == GIT_FILEMODE_TREE && !iterator__include_trees(fi)) {
- int error = fs_iterator__advance_into(NULL, &fi->base);
+ if (!error)
+ continue;
- if (error != GIT_ENOTFOUND)
- return error;
+ /* this directory disappeared, ignore it */
+ else if (error == GIT_ENOTFOUND)
+ error = 0;
- giterr_clear();
- fs_iterator__advance_over_internal(&fi->base);
- continue;
+ /* a real error occurred */
+ else
+ break;
+ } else {
+ /* we found a non-ignored item, treat parent as untracked */
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+ break;
}
- break;
+ if ((error = git_iterator_advance(&entry, i)) < 0)
+ break;
}
- return 0;
-}
-
-static int fs_iterator__initialize(
- git_iterator **out, fs_iterator *fi, const char *root)
-{
- int error;
-
- if (git_buf_sets(&fi->path, root) < 0 || git_path_to_dir(&fi->path) < 0) {
- git__free(fi);
- return -1;
+ /* wrap up scan back to base directory */
+ while (entry && !iter->base.prefixcomp(entry->path, base)) {
+ if ((error = git_iterator_advance(&entry, i)) < 0)
+ break;
}
- fi->root_len = fi->path.size;
- fi->pathlist_match = ITERATOR_PATHLIST_MATCH_CHILD;
- fi->dirload_flags =
- (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) |
- (iterator__flag(fi, PRECOMPOSE_UNICODE) ?
- GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0);
-
- if ((error = fs_iterator__expand_dir(fi)) < 0) {
- if (error == GIT_ENOTFOUND || error == GIT_ITEROVER) {
- giterr_clear();
- error = 0;
- } else {
- git_iterator_free((git_iterator *)fi);
- fi = NULL;
- }
- }
+ if (!error)
+ *out = entry;
- *out = (git_iterator *)fi;
return error;
}
-int git_iterator_for_filesystem(
- git_iterator **out,
- const char *root,
- git_iterator_options *options)
+static void filesystem_iterator_clear(filesystem_iterator *iter)
{
- fs_iterator *fi = git__calloc(1, sizeof(fs_iterator));
- GITERR_CHECK_ALLOC(fi);
+ while (iter->frames.size)
+ filesystem_iterator_frame_pop(iter);
- ITERATOR_BASE_INIT(fi, fs, FS, NULL);
+ git_array_clear(iter->frames);
+ git_ignore__free(&iter->ignores);
- if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0)
- fi->base.flags |= GIT_ITERATOR_IGNORE_CASE;
+ git_buf_free(&iter->tmp_buf);
- return fs_iterator__initialize(out, fi, root);
+ iterator_clear(&iter->base);
}
-
-typedef struct {
- fs_iterator fi;
- git_ignores ignores;
- int is_ignored;
-
- /*
- * We may have a tree or the index+snapshot to compare against
- * when checking for submodules.
- */
- git_tree *tree;
- git_index *index;
- git_vector index_snapshot;
- git_vector_cmp entry_srch;
-
-} workdir_iterator;
-
-GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path)
+static int filesystem_iterator_init(filesystem_iterator *iter)
{
- size_t len;
+ int error;
- if (!path || (len = path->size) < 4)
- return false;
+ if (iterator__honor_ignores(&iter->base) &&
+ (error = git_ignore__for_path(iter->base.repo,
+ ".gitignore", &iter->ignores)) < 0)
+ return error;
- if (path->ptr[len - 1] == '/')
- len--;
+ if ((error = filesystem_iterator_frame_push(iter, NULL)) < 0)
+ return error;
- if (git__tolower(path->ptr[len - 1]) != 't' ||
- git__tolower(path->ptr[len - 2]) != 'i' ||
- git__tolower(path->ptr[len - 3]) != 'g' ||
- git__tolower(path->ptr[len - 4]) != '.')
- return false;
+ iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
- return (len == 4 || path->ptr[len - 5] == '/');
+ return 0;
}
-/**
- * Figure out if an entry is a submodule.
- *
- * We consider it a submodule if the path is listed as a submodule in
- * either the tree or the index.
- */
-static int is_submodule(workdir_iterator *wi, fs_iterator_path_with_stat *ie)
+static int filesystem_iterator_reset(git_iterator *i)
{
- int error, is_submodule = 0;
-
- if (wi->tree) {
- git_tree_entry *e;
-
- /* remove the trailing slash for finding */
- ie->path[ie->path_len-1] = '\0';
- error = git_tree_entry_bypath(&e, wi->tree, ie->path);
- ie->path[ie->path_len-1] = '/';
- if (error < 0 && error != GIT_ENOTFOUND)
- return 0;
- if (!error) {
- is_submodule = e->attr == GIT_FILEMODE_COMMIT;
- git_tree_entry_free(e);
- }
- }
-
- if (!is_submodule && wi->index) {
- git_index_entry *e;
- size_t pos;
-
- error = git_index_snapshot_find(&pos, &wi->index_snapshot, wi->entry_srch, ie->path, ie->path_len-1, 0);
- if (error < 0 && error != GIT_ENOTFOUND)
- return 0;
-
- if (!error) {
- e = git_vector_get(&wi->index_snapshot, pos);
+ filesystem_iterator *iter = (filesystem_iterator *)i;
- is_submodule = e->mode == GIT_FILEMODE_COMMIT;
- }
- }
-
- return is_submodule;
+ filesystem_iterator_clear(iter);
+ return filesystem_iterator_init(iter);
}
-GIT_INLINE(git_dir_flag) git_entry__dir_flag(git_index_entry *entry) {
-#if defined(GIT_WIN32) && !defined(__MINGW32__)
- return (entry && entry->mode)
- ? S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE
- : GIT_DIR_FLAG_UNKNOWN;
-#else
- GIT_UNUSED(entry);
- return GIT_DIR_FLAG_UNKNOWN;
-#endif
+static void filesystem_iterator_free(git_iterator *i)
+{
+ filesystem_iterator *iter = (filesystem_iterator *)i;
+ filesystem_iterator_clear(iter);
}
-static int workdir_iterator__enter_dir(fs_iterator *fi)
+static int iterator_for_filesystem(
+ git_iterator **out,
+ git_repository *repo,
+ const char *root,
+ git_index *index,
+ git_tree *tree,
+ git_iterator_type_t type,
+ git_iterator_options *options)
{
- workdir_iterator *wi = (workdir_iterator *)fi;
- fs_iterator_frame *ff = fi->stack;
- size_t pos;
- fs_iterator_path_with_stat *entry;
- bool found_submodules = false;
+ filesystem_iterator *iter;
+ size_t root_len;
+ int error;
- git_dir_flag dir_flag = git_entry__dir_flag(&fi->entry);
+ static git_iterator_callbacks callbacks = {
+ filesystem_iterator_current,
+ filesystem_iterator_advance,
+ filesystem_iterator_advance_into,
+ filesystem_iterator_advance_over,
+ filesystem_iterator_reset,
+ filesystem_iterator_free
+ };
- /* check if this directory is ignored */
- if (git_ignore__lookup(&ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len, dir_flag) < 0) {
- giterr_clear();
- ff->is_ignored = GIT_IGNORE_NOTFOUND;
- }
+ *out = NULL;
- /* if this is not the top level directory... */
- if (ff->next != NULL) {
- ssize_t slash_pos = git_buf_rfind_next(&fi->path, '/');
+ if (root == NULL)
+ return git_iterator_for_nothing(out, options);
- /* inherit ignored from parent if no rule specified */
- if (ff->is_ignored <= GIT_IGNORE_NOTFOUND)
- ff->is_ignored = ff->next->is_ignored;
+ iter = git__calloc(1, sizeof(filesystem_iterator));
+ GITERR_CHECK_ALLOC(iter);
- /* push new ignores for files in this directory */
- (void)git_ignore__push_dir(&wi->ignores, &fi->path.ptr[slash_pos + 1]);
- }
+ root_len = strlen(root);
- /* convert submodules to GITLINK and remove trailing slashes */
- git_vector_foreach(&ff->entries, pos, entry) {
- if (!S_ISDIR(entry->st.st_mode) || !strcmp(GIT_DIR, entry->path))
- continue;
+ iter->root = git__malloc(root_len+2);
+ GITERR_CHECK_ALLOC(iter->root);
- if (is_submodule(wi, entry)) {
- entry->st.st_mode = GIT_FILEMODE_COMMIT;
- entry->path_len--;
- entry->path[entry->path_len] = '\0';
- found_submodules = true;
- }
- }
+ memcpy(iter->root, root, root_len);
- /* if we renamed submodules, re-sort and re-seek to start */
- if (found_submodules) {
- git_vector_set_sorted(&ff->entries, 0);
- git_vector_sort(&ff->entries);
- fs_iterator__seek_frame_start(fi, ff);
+ if (root_len == 0 || root[root_len-1] != '/') {
+ iter->root[root_len] = '/';
+ root_len++;
}
+ iter->root[root_len] = '\0';
+ iter->root_len = root_len;
- return 0;
-}
+ if ((error = git_buf_puts(&iter->current_path, iter->root)) < 0)
+ goto on_error;
-static int workdir_iterator__leave_dir(fs_iterator *fi)
-{
- workdir_iterator *wi = (workdir_iterator *)fi;
- git_ignore__pop_dir(&wi->ignores);
- return 0;
-}
+ iter->base.type = type;
+ iter->base.cb = &callbacks;
-static int workdir_iterator__update_entry(fs_iterator *fi)
-{
- workdir_iterator *wi = (workdir_iterator *)fi;
+ if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0)
+ goto on_error;
- /* skip over .git entries */
- if (workdir_path_is_dotgit(&fi->path))
- return GIT_ENOTFOUND;
+ if (tree && (error = git_tree_dup(&iter->tree, tree)) < 0)
+ goto on_error;
- /* reset is_ignored since we haven't checked yet */
- wi->is_ignored = GIT_IGNORE_UNCHECKED;
+ if (index &&
+ (error = git_index_snapshot_new(&iter->index_snapshot, index)) < 0)
+ goto on_error;
+ iter->dirload_flags =
+ (iterator__ignore_case(&iter->base) ? GIT_PATH_DIR_IGNORE_CASE : 0) |
+ (iterator__flag(&iter->base, PRECOMPOSE_UNICODE) ?
+ GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0);
+
+ if ((error = filesystem_iterator_init(iter)) < 0)
+ goto on_error;
+
+ *out = &iter->base;
return 0;
+
+on_error:
+ git__free(iter->root);
+ git_buf_free(&iter->current_path);
+ git_iterator_free(&iter->base);
+ return error;
}
-static void workdir_iterator__free(git_iterator *self)
+int git_iterator_for_filesystem(
+ git_iterator **out,
+ const char *root,
+ git_iterator_options *options)
{
- workdir_iterator *wi = (workdir_iterator *)self;
- if (wi->index)
- git_index_snapshot_release(&wi->index_snapshot, wi->index);
- git_tree_free(wi->tree);
- fs_iterator__free(self);
- git_ignore__free(&wi->ignores);
+ return iterator_for_filesystem(out,
+ NULL, root, NULL, NULL, GIT_ITERATOR_TYPE_FS, options);
}
int git_iterator_for_workdir_ext(
@@ -1818,299 +1856,323 @@ int git_iterator_for_workdir_ext(
const char *repo_workdir,
git_index *index,
git_tree *tree,
- git_iterator_options *options)
+ git_iterator_options *given_opts)
{
- int error, precompose = 0;
- workdir_iterator *wi;
+ git_iterator_options options = GIT_ITERATOR_OPTIONS_INIT;
if (!repo_workdir) {
if (git_repository__ensure_not_bare(repo, "scan working directory") < 0)
return GIT_EBAREREPO;
- repo_workdir = git_repository_workdir(repo);
- }
- /* initialize as an fs iterator then do overrides */
- wi = git__calloc(1, sizeof(workdir_iterator));
- GITERR_CHECK_ALLOC(wi);
- ITERATOR_BASE_INIT((&wi->fi), fs, FS, repo);
-
- wi->fi.base.type = GIT_ITERATOR_TYPE_WORKDIR;
- wi->fi.cb.free = workdir_iterator__free;
- wi->fi.enter_dir_cb = workdir_iterator__enter_dir;
- wi->fi.leave_dir_cb = workdir_iterator__leave_dir;
- wi->fi.update_entry_cb = workdir_iterator__update_entry;
-
- if ((error = iterator__update_ignore_case((git_iterator *)wi, options ? options->flags : 0)) < 0 ||
- (error = git_ignore__for_path(repo, ".gitignore", &wi->ignores)) < 0)
- {
- git_iterator_free((git_iterator *)wi);
- return error;
- }
-
- if (tree && (error = git_object_dup((git_object **)&wi->tree, (git_object *)tree)) < 0)
- return error;
-
- wi->index = index;
- if (index && (error = git_index_snapshot_new(&wi->index_snapshot, index)) < 0) {
- git_iterator_free((git_iterator *)wi);
- return error;
+ repo_workdir = git_repository_workdir(repo);
}
- wi->entry_srch = iterator__ignore_case(wi) ?
- git_index_entry_isrch : git_index_entry_srch;
+ /* upgrade to a workdir iterator, adding necessary internal flags */
+ if (given_opts)
+ memcpy(&options, given_opts, sizeof(git_iterator_options));
- /* try to look up precompose and set flag if appropriate */
- if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0)
- giterr_clear();
- else if (precompose)
- wi->fi.base.flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE;
+ options.flags |= GIT_ITERATOR_HONOR_IGNORES |
+ GIT_ITERATOR_IGNORE_DOT_GIT;
- return fs_iterator__initialize(out, &wi->fi, repo_workdir);
+ return iterator_for_filesystem(out,
+ repo, repo_workdir, index, tree, GIT_ITERATOR_TYPE_WORKDIR, &options);
}
-void git_iterator_free(git_iterator *iter)
-{
- if (iter == NULL)
- return;
- iter->cb->free(iter);
+/* Index iterator */
- git_vector_free(&iter->pathlist);
- git__free(iter->start);
- git__free(iter->end);
- memset(iter, 0, sizeof(*iter));
+typedef struct {
+ git_iterator base;
+ git_vector entries;
+ size_t next_idx;
- git__free(iter);
-}
+ /* the pseudotree entry */
+ git_index_entry tree_entry;
+ git_buf tree_buf;
+ bool skip_tree;
-int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case)
+ const git_index_entry *entry;
+} index_iterator;
+
+static int index_iterator_current(
+ const git_index_entry **out, git_iterator *i)
{
- bool desire_ignore_case = (ignore_case != 0);
+ index_iterator *iter = (index_iterator *)i;
- if (iterator__ignore_case(iter) == desire_ignore_case)
- return 0;
+ if (!iterator__has_been_accessed(i))
+ return iter->base.cb->advance(out, i);
- if (iter->type == GIT_ITERATOR_TYPE_EMPTY) {
- if (desire_ignore_case)
- iter->flags |= GIT_ITERATOR_IGNORE_CASE;
- else
- iter->flags &= ~GIT_ITERATOR_IGNORE_CASE;
- } else {
- giterr_set(GITERR_INVALID,
- "Cannot currently set ignore case on non-empty iterators");
- return -1;
+ if (iter->entry == NULL) {
+ *out = NULL;
+ return GIT_ITEROVER;
}
+ *out = iter->entry;
return 0;
}
-git_index *git_iterator_get_index(git_iterator *iter)
+static bool index_iterator_create_pseudotree(
+ const git_index_entry **out,
+ index_iterator *iter,
+ const char *path)
{
- if (iter->type == GIT_ITERATOR_TYPE_INDEX)
- return ((index_iterator *)iter)->index;
- return NULL;
-}
+ const char *prev_path, *relative_path, *dirsep;
+ size_t common_len;
-int git_iterator_current_tree_entry(
- const git_tree_entry **tree_entry, git_iterator *iter)
-{
- if (iter->type != GIT_ITERATOR_TYPE_TREE)
- *tree_entry = NULL;
- else {
- tree_iterator_frame *tf = ((tree_iterator *)iter)->head;
- *tree_entry = (tf->current < tf->n_entries) ?
- tf->entries[tf->current]->te : NULL;
- }
+ prev_path = iter->entry ? iter->entry->path : "";
- return 0;
+ /* determine if the new path is in a different directory from the old */
+ common_len = git_path_common_dirlen(prev_path, path);
+ relative_path = path + common_len;
+
+ if ((dirsep = strchr(relative_path, '/')) == NULL)
+ return false;
+
+ git_buf_clear(&iter->tree_buf);
+ git_buf_put(&iter->tree_buf, path, (dirsep - path) + 1);
+
+ iter->tree_entry.mode = GIT_FILEMODE_TREE;
+ iter->tree_entry.path = iter->tree_buf.ptr;
+
+ *out = &iter->tree_entry;
+ return true;
}
-int git_iterator_current_parent_tree(
- const git_tree **tree_ptr,
- git_iterator *iter,
- const char *parent_path)
+static int index_iterator_skip_pseudotree(index_iterator *iter)
{
- tree_iterator *ti = (tree_iterator *)iter;
- tree_iterator_frame *tf;
- const char *scan = parent_path;
- const git_tree_entry *te;
+ assert(iterator__has_been_accessed(&iter->base));
+ assert(S_ISDIR(iter->entry->mode));
- *tree_ptr = NULL;
+ while (true) {
+ const git_index_entry *next_entry = NULL;
- if (iter->type != GIT_ITERATOR_TYPE_TREE)
- return 0;
+ if (++iter->next_idx >= iter->entries.length)
+ return GIT_ITEROVER;
- for (tf = ti->root; *scan; ) {
- if (!(tf = tf->down) ||
- tf->current >= tf->n_entries ||
- !(te = tf->entries[tf->current]->te) ||
- ti->base.strncomp(scan, te->filename, te->filename_len) != 0)
- return 0;
+ next_entry = iter->entries.contents[iter->next_idx];
- scan += te->filename_len;
- if (*scan == '/')
- scan++;
+ if (iter->base.strncomp(iter->tree_buf.ptr, next_entry->path,
+ iter->tree_buf.size) != 0)
+ break;
}
- *tree_ptr = tf->entries[tf->current]->tree;
+ iter->skip_tree = false;
return 0;
}
-static void workdir_iterator_update_is_ignored(workdir_iterator *wi)
+static int index_iterator_advance(
+ const git_index_entry **out, git_iterator *i)
{
- git_dir_flag dir_flag = git_entry__dir_flag(&wi->fi.entry);
+ index_iterator *iter = (index_iterator *)i;
+ const git_index_entry *entry = NULL;
+ int error = 0;
- if (git_ignore__lookup(&wi->is_ignored, &wi->ignores, wi->fi.entry.path, dir_flag) < 0) {
- giterr_clear();
- wi->is_ignored = GIT_IGNORE_NOTFOUND;
- }
+ iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS;
- /* use ignore from containing frame stack */
- if (wi->is_ignored <= GIT_IGNORE_NOTFOUND)
- wi->is_ignored = wi->fi.stack->is_ignored;
-}
+ while (true) {
+ if (iter->next_idx >= iter->entries.length) {
+ error = GIT_ITEROVER;
+ break;
+ }
-bool git_iterator_current_is_ignored(git_iterator *iter)
-{
- workdir_iterator *wi = (workdir_iterator *)iter;
+ /* we were not asked to expand this pseudotree. advance over it. */
+ if (iter->skip_tree) {
+ index_iterator_skip_pseudotree(iter);
+ continue;
+ }
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
- return false;
+ entry = iter->entries.contents[iter->next_idx];
+
+ if (!iterator_has_started(&iter->base, entry->path)) {
+ iter->next_idx++;
+ continue;
+ }
+
+ if (iterator_has_ended(&iter->base, entry->path)) {
+ error = GIT_ITEROVER;
+ break;
+ }
+
+ /* if we have a list of paths we're interested in, examine it */
+ if (!iterator_pathlist_next_is(&iter->base, entry->path)) {
+ iter->next_idx++;
+ continue;
+ }
+
+ /* if this is a conflict, skip it unless we're including conflicts */
+ if (git_index_entry_is_conflict(entry) &&
+ !iterator__include_conflicts(&iter->base)) {
+ iter->next_idx++;
+ continue;
+ }
- if (wi->is_ignored != GIT_IGNORE_UNCHECKED)
- return (bool)(wi->is_ignored == GIT_IGNORE_TRUE);
+ /* we've found what will be our next _file_ entry. but if we are
+ * returning trees entries, we may need to return a pseudotree
+ * entry that will contain this. don't advance over this entry,
+ * though, we still need to return it on the next `advance`.
+ */
+ if (iterator__include_trees(&iter->base) &&
+ index_iterator_create_pseudotree(&entry, iter, entry->path)) {
- workdir_iterator_update_is_ignored(wi);
+ /* Note whether this pseudo tree should be expanded or not */
+ iter->skip_tree = iterator__dont_autoexpand(&iter->base);
+ break;
+ }
- return (bool)(wi->is_ignored == GIT_IGNORE_TRUE);
+ iter->next_idx++;
+ break;
+ }
+
+ iter->entry = (error == 0) ? entry : NULL;
+
+ if (out)
+ *out = iter->entry;
+
+ return error;
}
-bool git_iterator_current_tree_is_ignored(git_iterator *iter)
+static int index_iterator_advance_into(
+ const git_index_entry **out, git_iterator *i)
{
- workdir_iterator *wi = (workdir_iterator *)iter;
+ index_iterator *iter = (index_iterator *)i;
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
- return false;
+ if (! S_ISDIR(iter->tree_entry.mode)) {
+ if (out)
+ *out = NULL;
+
+ return 0;
+ }
- return (bool)(wi->fi.stack->is_ignored == GIT_IGNORE_TRUE);
+ iter->skip_tree = false;
+ return index_iterator_advance(out, i);
}
-int git_iterator_cmp(git_iterator *iter, const char *path_prefix)
+static int index_iterator_advance_over(
+ const git_index_entry **out,
+ git_iterator_status_t *status,
+ git_iterator *i)
{
+ index_iterator *iter = (index_iterator *)i;
const git_index_entry *entry;
+ int error;
- /* a "done" iterator is after every prefix */
- if (git_iterator_current(&entry, iter) < 0 || entry == NULL)
- return 1;
+ if ((error = index_iterator_current(&entry, i)) < 0)
+ return error;
- /* a NULL prefix is after any valid iterator */
- if (!path_prefix)
- return -1;
+ if (S_ISDIR(entry->mode))
+ index_iterator_skip_pseudotree(iter);
- return iter->prefixcomp(entry->path, path_prefix);
+ *status = GIT_ITERATOR_STATUS_NORMAL;
+ return index_iterator_advance(out, i);
}
-int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter)
+static void index_iterator_clear(index_iterator *iter)
{
- workdir_iterator *wi = (workdir_iterator *)iter;
-
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR || !wi->fi.entry.path)
- *path = NULL;
- else
- *path = &wi->fi.path;
+ iterator_clear(&iter->base);
+}
+static int index_iterator_init(index_iterator *iter)
+{
+ iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+ iter->next_idx = 0;
+ iter->skip_tree = false;
return 0;
}
-int git_iterator_index(git_index **out, git_iterator *iter)
+static int index_iterator_reset(git_iterator *i)
{
- workdir_iterator *wi = (workdir_iterator *)iter;
+ index_iterator *iter = (index_iterator *)i;
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
- *out = NULL;
+ index_iterator_clear(iter);
+ return index_iterator_init(iter);
+}
- *out = wi->index;
+static void index_iterator_free(git_iterator *i)
+{
+ index_iterator *iter = (index_iterator *)i;
- return 0;
+ git_index_snapshot_release(&iter->entries, iter->base.index);
}
-int git_iterator_advance_over_with_status(
- const git_index_entry **entryptr,
- git_iterator_status_t *status,
- git_iterator *iter)
+int git_iterator_for_index(
+ git_iterator **out,
+ git_repository *repo,
+ git_index *index,
+ git_iterator_options *options)
{
- int error = 0;
- workdir_iterator *wi = (workdir_iterator *)iter;
- char *base = NULL;
- const git_index_entry *entry;
+ index_iterator *iter;
+ int error;
- *status = GIT_ITERATOR_STATUS_NORMAL;
+ static git_iterator_callbacks callbacks = {
+ index_iterator_current,
+ index_iterator_advance,
+ index_iterator_advance_into,
+ index_iterator_advance_over,
+ index_iterator_reset,
+ index_iterator_free
+ };
- if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)
- return git_iterator_advance(entryptr, iter);
- if ((error = git_iterator_current(&entry, iter)) < 0)
- return error;
+ *out = NULL;
- if (!S_ISDIR(entry->mode)) {
- workdir_iterator_update_is_ignored(wi);
- if (wi->is_ignored == GIT_IGNORE_TRUE)
- *status = GIT_ITERATOR_STATUS_IGNORED;
- return git_iterator_advance(entryptr, iter);
- }
+ if (index == NULL)
+ return git_iterator_for_nothing(out, options);
- *status = GIT_ITERATOR_STATUS_EMPTY;
+ iter = git__calloc(1, sizeof(index_iterator));
+ GITERR_CHECK_ALLOC(iter);
- base = git__strdup(entry->path);
- GITERR_CHECK_ALLOC(base);
+ iter->base.type = GIT_ITERATOR_TYPE_INDEX;
+ iter->base.cb = &callbacks;
- /* scan inside directory looking for a non-ignored item */
- while (entry && !iter->prefixcomp(entry->path, base)) {
- workdir_iterator_update_is_ignored(wi);
+ if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 ||
+ (error = git_index_snapshot_new(&iter->entries, index)) < 0 ||
+ (error = index_iterator_init(iter)) < 0)
+ goto on_error;
- /* if we found an explicitly ignored item, then update from
- * EMPTY to IGNORED
- */
- if (wi->is_ignored == GIT_IGNORE_TRUE)
- *status = GIT_ITERATOR_STATUS_IGNORED;
- else if (S_ISDIR(entry->mode)) {
- error = git_iterator_advance_into(&entry, iter);
+ git_vector_set_cmp(&iter->entries, iterator__ignore_case(&iter->base) ?
+ git_index_entry_icmp : git_index_entry_cmp);
+ git_vector_sort(&iter->entries);
- if (!error)
- continue;
+ *out = &iter->base;
+ return 0;
- else if (error == GIT_ENOTFOUND) {
- /* we entered this directory only hoping to find child matches to
- * our pathlist (eg, this is `foo` and we had a pathlist entry for
- * `foo/bar`). it should not be ignored, it should be excluded.
- */
- if (wi->fi.pathlist_match == ITERATOR_PATHLIST_MATCH_CHILD)
- *status = GIT_ITERATOR_STATUS_FILTERED;
- else
- wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */
+on_error:
+ git_iterator_free(&iter->base);
+ return error;
+}
- error = 0;
- } else
- break; /* real error, stop here */
- } else {
- /* we found a non-ignored item, treat parent as untracked */
- *status = GIT_ITERATOR_STATUS_NORMAL;
- break;
- }
- if ((error = git_iterator_advance(&entry, iter)) < 0)
- break;
- }
+/* Iterator API */
- /* wrap up scan back to base directory */
- while (entry && !iter->prefixcomp(entry->path, base))
- if ((error = git_iterator_advance(&entry, iter)) < 0)
- break;
+int git_iterator_reset_range(
+ git_iterator *i, const char *start, const char *end)
+{
+ if (iterator_reset_range(i, start, end) < 0)
+ return -1;
- *entryptr = entry;
- git__free(base);
+ return i->cb->reset(i);
+}
- return error;
+void git_iterator_set_ignore_case(git_iterator *i, bool ignore_case)
+{
+ assert(!iterator__has_been_accessed(i));
+ iterator_set_ignore_case(i, ignore_case);
+}
+
+void git_iterator_free(git_iterator *iter)
+{
+ if (iter == NULL)
+ return;
+
+ iter->cb->free(iter);
+
+ git_vector_free(&iter->pathlist);
+ git__free(iter->start);
+ git__free(iter->end);
+
+ memset(iter, 0, sizeof(*iter));
+
+ git__free(iter);
}
int git_iterator_walk(
diff --git a/src/iterator.h b/src/iterator.h
index ac17d2970..0b239a5bd 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -34,10 +34,19 @@ typedef enum {
GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3),
/** convert precomposed unicode to decomposed unicode */
GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4),
+ /** never convert precomposed unicode to decomposed unicode */
+ GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE = (1u << 5),
/** include conflicts */
- GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 5),
+ GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 6),
} git_iterator_flag_t;
+typedef enum {
+ GIT_ITERATOR_STATUS_NORMAL = 0,
+ GIT_ITERATOR_STATUS_IGNORED = 1,
+ GIT_ITERATOR_STATUS_EMPTY = 2,
+ GIT_ITERATOR_STATUS_FILTERED = 3
+} git_iterator_status_t;
+
typedef struct {
const char *start;
const char *end;
@@ -57,23 +66,33 @@ typedef struct {
int (*current)(const git_index_entry **, git_iterator *);
int (*advance)(const git_index_entry **, git_iterator *);
int (*advance_into)(const git_index_entry **, git_iterator *);
- int (*seek)(git_iterator *, const char *prefix);
- int (*reset)(git_iterator *, const char *start, const char *end);
- int (*at_end)(git_iterator *);
+ int (*advance_over)(
+ const git_index_entry **, git_iterator_status_t *, git_iterator *);
+ int (*reset)(git_iterator *);
void (*free)(git_iterator *);
} git_iterator_callbacks;
struct git_iterator {
git_iterator_type_t type;
git_iterator_callbacks *cb;
+
git_repository *repo;
+ git_index *index;
+
char *start;
+ size_t start_len;
+
char *end;
+ size_t end_len;
+
+ bool started;
+ bool ended;
git_vector pathlist;
size_t pathlist_walk_idx;
int (*strcomp)(const char *a, const char *b);
int (*strncomp)(const char *a, const char *b, size_t n);
int (*prefixcomp)(const char *str, const char *prefix);
+ int (*entry_srch)(const void *key, const void *array_member);
size_t stat_calls;
unsigned int flags;
};
@@ -181,54 +200,38 @@ GIT_INLINE(int) git_iterator_advance_into(
return iter->cb->advance_into(entry, iter);
}
-/**
- * Advance into a tree or skip over it if it is empty.
+/* Advance over a directory and check if it contains no files or just
+ * ignored files.
*
- * Because `git_iterator_advance_into` may return GIT_ENOTFOUND if the
- * directory is empty (only with filesystem and working directory
- * iterators) and a common response is to just call `git_iterator_advance`
- * when that happens, this bundles the two into a single simple call.
+ * In a tree or the index, all directories will contain files, but in the
+ * working directory it is possible to have an empty directory tree or a
+ * tree that only contains ignored files. Many Git operations treat these
+ * cases specially. This advances over a directory (presumably an
+ * untracked directory) but checks during the scan if there are any files
+ * and any non-ignored files.
*/
-GIT_INLINE(int) git_iterator_advance_into_or_over(
- const git_index_entry **entry, git_iterator *iter)
-{
- int error = iter->cb->advance_into(entry, iter);
- if (error == GIT_ENOTFOUND) {
- giterr_clear();
- error = iter->cb->advance(entry, iter);
- }
- return error;
-}
-
-/* Seek is currently unimplemented */
-GIT_INLINE(int) git_iterator_seek(
- git_iterator *iter, const char *prefix)
+GIT_INLINE(int) git_iterator_advance_over(
+ const git_index_entry **entry,
+ git_iterator_status_t *status,
+ git_iterator *iter)
{
- return iter->cb->seek(iter, prefix);
+ return iter->cb->advance_over(entry, status, iter);
}
/**
* Go back to the start of the iteration.
- *
- * This resets the iterator to the start of the iteration. It also allows
- * you to reset the `start` and `end` pathname boundaries of the iteration
- * when doing so.
*/
-GIT_INLINE(int) git_iterator_reset(
- git_iterator *iter, const char *start, const char *end)
+GIT_INLINE(int) git_iterator_reset(git_iterator *iter)
{
- return iter->cb->reset(iter, start, end);
+ return iter->cb->reset(iter);
}
/**
- * Check if the iterator is at the end
- *
- * @return 0 if not at end, >0 if at end
+ * Go back to the start of the iteration after updating the `start` and
+ * `end` pathname boundaries of the iteration.
*/
-GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)
-{
- return iter->cb->at_end(iter);
-}
+extern int git_iterator_reset_range(
+ git_iterator *iter, const char *start, const char *end);
GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
{
@@ -240,6 +243,11 @@ GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter)
return iter->repo;
}
+GIT_INLINE(git_index *) git_iterator_index(git_iterator *iter)
+{
+ return iter->index;
+}
+
GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter)
{
return iter->flags;
@@ -250,21 +258,19 @@ GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter)
return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0);
}
-extern int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case);
+extern void git_iterator_set_ignore_case(
+ git_iterator *iter, bool ignore_case);
extern int git_iterator_current_tree_entry(
const git_tree_entry **entry_out, git_iterator *iter);
extern int git_iterator_current_parent_tree(
- const git_tree **tree_out, git_iterator *iter, const char *parent_path);
+ const git_tree **tree_out, git_iterator *iter, size_t depth);
extern bool git_iterator_current_is_ignored(git_iterator *iter);
extern bool git_iterator_current_tree_is_ignored(git_iterator *iter);
-extern int git_iterator_cmp(
- git_iterator *iter, const char *path_prefix);
-
/**
* Get full path of the current item from a workdir iterator. This will
* return NULL for a non-workdir iterator. The git_buf is still owned by
@@ -273,35 +279,12 @@ extern int git_iterator_cmp(
extern int git_iterator_current_workdir_path(
git_buf **path, git_iterator *iter);
-/* Return index pointer if index iterator, else NULL */
-extern git_index *git_iterator_get_index(git_iterator *iter);
-
-typedef enum {
- GIT_ITERATOR_STATUS_NORMAL = 0,
- GIT_ITERATOR_STATUS_IGNORED = 1,
- GIT_ITERATOR_STATUS_EMPTY = 2,
- GIT_ITERATOR_STATUS_FILTERED = 3
-} git_iterator_status_t;
-
-/* Advance over a directory and check if it contains no files or just
- * ignored files.
- *
- * In a tree or the index, all directories will contain files, but in the
- * working directory it is possible to have an empty directory tree or a
- * tree that only contains ignored files. Many Git operations treat these
- * cases specially. This advances over a directory (presumably an
- * untracked directory) but checks during the scan if there are any files
- * and any non-ignored files.
- */
-extern int git_iterator_advance_over_with_status(
- const git_index_entry **entry, git_iterator_status_t *status, git_iterator *iter);
-
/**
* Retrieve the index stored in the iterator.
*
- * Only implemented for the workdir iterator
+ * Only implemented for the workdir and index iterators.
*/
-extern int git_iterator_index(git_index **out, git_iterator *iter);
+extern git_index *git_iterator_index(git_iterator *iter);
typedef int (*git_iterator_walk_cb)(
const git_index_entry **entries,
diff --git a/src/object_api.c b/src/object_api.c
index 838bba323..e0d8760e7 100644
--- a/src/object_api.c
+++ b/src/object_api.c
@@ -15,7 +15,7 @@
#include "tag.h"
/**
- * Blob
+ * Commit
*/
int git_commit_lookup(git_commit **out, git_repository *repo, const git_oid *id)
{
@@ -42,6 +42,10 @@ git_repository *git_commit_owner(const git_commit *obj)
return git_object_owner((const git_object *)obj);
}
+int git_commit_dup(git_commit **out, git_commit *obj)
+{
+ return git_object_dup((git_object **)out, (git_object *)obj);
+}
/**
* Tree
@@ -71,6 +75,10 @@ git_repository *git_tree_owner(const git_tree *obj)
return git_object_owner((const git_object *)obj);
}
+int git_tree_dup(git_tree **out, git_tree *obj)
+{
+ return git_object_dup((git_object **)out, (git_object *)obj);
+}
/**
* Tag
@@ -100,6 +108,11 @@ git_repository *git_tag_owner(const git_tag *obj)
return git_object_owner((const git_object *)obj);
}
+int git_tag_dup(git_tag **out, git_tag *obj)
+{
+ return git_object_dup((git_object **)out, (git_object *)obj);
+}
+
/**
* Blob
*/
@@ -127,3 +140,8 @@ git_repository *git_blob_owner(const git_blob *obj)
{
return git_object_owner((const git_object *)obj);
}
+
+int git_blob_dup(git_blob **out, git_blob *obj)
+{
+ return git_object_dup((git_object **)out, (git_object *)obj);
+}
diff --git a/src/path.c b/src/path.c
index 1fd14fcb9..4133985a4 100644
--- a/src/path.c
+++ b/src/path.c
@@ -810,6 +810,20 @@ int git_path_cmp(
return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
}
+size_t git_path_common_dirlen(const char *one, const char *two)
+{
+ const char *p, *q, *dirsep = NULL;
+
+ for (p = one, q = two; *p && *q; p++, q++) {
+ if (*p == '/' && *q == '/')
+ dirsep = p;
+ else if (*p != *q)
+ break;
+ }
+
+ return dirsep ? (dirsep - one) + 1 : 0;
+}
+
int git_path_make_relative(git_buf *path, const char *parent)
{
const char *p, *q, *p_dirsep, *q_dirsep;
diff --git a/src/path.h b/src/path.h
index 875c8cb7e..f31cacc70 100644
--- a/src/path.h
+++ b/src/path.h
@@ -203,6 +203,18 @@ extern bool git_path_contains(git_buf *dir, const char *item);
extern bool git_path_contains_dir(git_buf *parent, const char *subdir);
/**
+ * Determine the common directory length between two paths, including
+ * the final path separator. For example, given paths 'a/b/c/1.txt
+ * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this
+ * will return the length of the string 'a/b/c/', which is 6.
+ *
+ * @param one The first path
+ * @param two The second path
+ * @return The length of the common directory
+ */
+extern size_t git_path_common_dirlen(const char *one, const char *two);
+
+/**
* Make the path relative to the given parent path.
*
* @param path The path to make relative
diff --git a/src/pathspec.c b/src/pathspec.c
index 8a93cdd50..361b398b8 100644
--- a/src/pathspec.c
+++ b/src/pathspec.c
@@ -418,7 +418,7 @@ static int pathspec_match_from_iterator(
GITERR_CHECK_ALLOC(m);
}
- if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0)
+ if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0)
goto done;
if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR &&
diff --git a/tests/core/path.c b/tests/core/path.c
index c3e622f02..71c6eda58 100644
--- a/tests/core/path.c
+++ b/tests/core/path.c
@@ -652,3 +652,23 @@ void test_core_path__15_resolve_relative(void)
git_buf_free(&buf);
}
+
+#define assert_common_dirlen(i, p, q) \
+ cl_assert_equal_i((i), git_path_common_dirlen((p), (q)));
+
+void test_core_path__16_resolve_relative(void)
+{
+ assert_common_dirlen(0, "", "");
+ assert_common_dirlen(0, "", "bar.txt");
+ assert_common_dirlen(0, "foo.txt", "bar.txt");
+ assert_common_dirlen(0, "foo.txt", "");
+ assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt");
+ assert_common_dirlen(0, "foo/bar.txt", "../foo.txt");
+
+ assert_common_dirlen(1, "/one.txt", "/two.txt");
+ assert_common_dirlen(4, "foo/one.txt", "foo/two.txt");
+ assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt");
+
+ assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt");
+ assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt");
+}
diff --git a/tests/diff/iterator.c b/tests/diff/iterator.c
deleted file mode 100644
index 25a23eda7..000000000
--- a/tests/diff/iterator.c
+++ /dev/null
@@ -1,1012 +0,0 @@
-#include "clar_libgit2.h"
-#include "diff_helpers.h"
-#include "iterator.h"
-#include "tree.h"
-
-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)
-{
- cl_git_sandbox_cleanup();
-}
-
-
-/* -- TREE ITERATOR TESTS -- */
-
-static void tree_iterator_test(
- const char *sandbox,
- const char *treeish,
- const char *start,
- const char *end,
- int expected_count,
- const char **expected_values)
-{
- git_tree *t;
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- const git_index_entry *entry;
- int error, count = 0, count_post_reset = 0;
- git_repository *repo = cl_git_sandbox_init(sandbox);
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
- i_opts.start = start;
- i_opts.end = end;
-
- cl_assert(t = resolve_commit_oid_to_tree(repo, treeish));
- cl_git_pass(git_iterator_for_tree(&i, t, &i_opts));
-
- /* test loop */
- while (!(error = git_iterator_advance(&entry, i))) {
- cl_assert(entry);
- if (expected_values != NULL)
- cl_assert_equal_s(expected_values[count], entry->path);
- count++;
- }
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert(!entry);
- cl_assert_equal_i(expected_count, count);
-
- /* test reset */
- cl_git_pass(git_iterator_reset(i, NULL, NULL));
-
- while (!(error = git_iterator_advance(&entry, i))) {
- cl_assert(entry);
- if (expected_values != NULL)
- cl_assert_equal_s(expected_values[count_post_reset], entry->path);
- count_post_reset++;
- }
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert(!entry);
- cl_assert_equal_i(count, count_post_reset);
-
- git_iterator_free(i);
- 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", NULL, NULL, 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", NULL, NULL, 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", NULL, NULL, 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", NULL, NULL, 8, expected_tree_3);
-}
-
-/* $ git ls-tree -r --name-only 24fa9a9fc4e202313e24b648087495441dab432b */
-const char *expected_tree_4[] = {
- "attr0",
- "attr1",
- "attr2",
- "attr3",
- "binfile",
- "gitattributes",
- "macro_bad",
- "macro_test",
- "root_test1",
- "root_test2",
- "root_test3",
- "root_test4.txt",
- "sub/abc",
- "sub/file",
- "sub/sub/file",
- "sub/sub/subsub.txt",
- "sub/subdir_test1",
- "sub/subdir_test2.txt",
- "subdir/.gitattributes",
- "subdir/abc",
- "subdir/subdir_test1",
- "subdir/subdir_test2.txt",
- "subdir2/subdir2_test1",
- NULL
-};
-
-void test_diff_iterator__tree_4(void)
-{
- tree_iterator_test(
- "attr", "24fa9a9fc4e202313e24b648087495441dab432b", NULL, NULL,
- 23, expected_tree_4);
-}
-
-void test_diff_iterator__tree_4_ranged(void)
-{
- tree_iterator_test(
- "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
- "sub", "sub",
- 11, &expected_tree_4[12]);
-}
-
-const char *expected_tree_ranged_0[] = {
- "gitattributes",
- "macro_bad",
- "macro_test",
- "root_test1",
- "root_test2",
- "root_test3",
- "root_test4.txt",
- NULL
-};
-
-void test_diff_iterator__tree_ranged_0(void)
-{
- tree_iterator_test(
- "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
- "git", "root",
- 7, expected_tree_ranged_0);
-}
-
-const char *expected_tree_ranged_1[] = {
- "sub/subdir_test2.txt",
- NULL
-};
-
-void test_diff_iterator__tree_ranged_1(void)
-{
- tree_iterator_test(
- "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
- "sub/subdir_test2.txt", "sub/subdir_test2.txt",
- 1, expected_tree_ranged_1);
-}
-
-void test_diff_iterator__tree_range_empty_0(void)
-{
- tree_iterator_test(
- "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
- "empty", "empty", 0, NULL);
-}
-
-void test_diff_iterator__tree_range_empty_1(void)
-{
- tree_iterator_test(
- "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
- "z_empty_after", NULL, 0, NULL);
-}
-
-void test_diff_iterator__tree_range_empty_2(void)
-{
- tree_iterator_test(
- "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
- NULL, ".aaa_empty_before", 0, NULL);
-}
-
-static void check_tree_entry(
- git_iterator *i,
- const char *oid,
- const char *oid_p,
- const char *oid_pp,
- const char *oid_ppp)
-{
- const git_index_entry *ie;
- const git_tree_entry *te;
- const git_tree *tree;
- git_buf path = GIT_BUF_INIT;
-
- cl_git_pass(git_iterator_current_tree_entry(&te, i));
- cl_assert(te);
- cl_assert(git_oid_streq(te->oid, oid) == 0);
-
- cl_git_pass(git_iterator_current(&ie, i));
- cl_git_pass(git_buf_sets(&path, ie->path));
-
- if (oid_p) {
- git_buf_rtruncate_at_char(&path, '/');
- cl_git_pass(git_iterator_current_parent_tree(&tree, i, path.ptr));
- cl_assert(tree);
- cl_assert(git_oid_streq(git_tree_id(tree), oid_p) == 0);
- }
-
- if (oid_pp) {
- git_buf_rtruncate_at_char(&path, '/');
- cl_git_pass(git_iterator_current_parent_tree(&tree, i, path.ptr));
- cl_assert(tree);
- cl_assert(git_oid_streq(git_tree_id(tree), oid_pp) == 0);
- }
-
- if (oid_ppp) {
- git_buf_rtruncate_at_char(&path, '/');
- cl_git_pass(git_iterator_current_parent_tree(&tree, i, path.ptr));
- cl_assert(tree);
- cl_assert(git_oid_streq(git_tree_id(tree), oid_ppp) == 0);
- }
-
- git_buf_free(&path);
-}
-
-void test_diff_iterator__tree_special_functions(void)
-{
- git_tree *t;
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- const git_index_entry *entry;
- git_repository *repo = cl_git_sandbox_init("attr");
- int error, cases = 0;
- const char *rootoid = "ce39a97a7fb1fa90bcf5e711249c1e507476ae0e";
-
- t = resolve_commit_oid_to_tree(
- repo, "24fa9a9fc4e202313e24b648087495441dab432b");
- cl_assert(t != NULL);
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
-
- cl_git_pass(git_iterator_for_tree(&i, t, &i_opts));
-
- while (!(error = git_iterator_advance(&entry, i))) {
- cl_assert(entry);
-
- if (strcmp(entry->path, "sub/file") == 0) {
- cases++;
- check_tree_entry(
- i, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
- "ecb97df2a174987475ac816e3847fc8e9f6c596b",
- rootoid, NULL);
- }
- else if (strcmp(entry->path, "sub/sub/subsub.txt") == 0) {
- cases++;
- check_tree_entry(
- i, "9e5bdc47d6a80f2be0ea3049ad74231b94609242",
- "4e49ba8c5b6c32ff28cd9dcb60be34df50fcc485",
- "ecb97df2a174987475ac816e3847fc8e9f6c596b", rootoid);
- }
- else if (strcmp(entry->path, "subdir/.gitattributes") == 0) {
- cases++;
- check_tree_entry(
- i, "99eae476896f4907224978b88e5ecaa6c5bb67a9",
- "9fb40b6675dde60b5697afceae91b66d908c02d9",
- rootoid, NULL);
- }
- else if (strcmp(entry->path, "subdir2/subdir2_test1") == 0) {
- cases++;
- check_tree_entry(
- i, "dccada462d3df8ac6de596fb8c896aba9344f941",
- "2929de282ce999e95183aedac6451d3384559c4b",
- rootoid, NULL);
- }
- }
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert(!entry);
- cl_assert_equal_i(4, cases);
-
- git_iterator_free(i);
- git_tree_free(t);
-}
-
-/* -- INDEX ITERATOR TESTS -- */
-
-static void index_iterator_test(
- const char *sandbox,
- const char *start,
- const char *end,
- git_iterator_flag_t flags,
- int expected_count,
- const char **expected_names,
- const char **expected_oids)
-{
- git_index *index;
- git_iterator *i;
- const git_index_entry *entry;
- int error, count = 0, caps;
- git_repository *repo = cl_git_sandbox_init(sandbox);
- git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- cl_git_pass(git_repository_index(&index, repo));
- caps = git_index_caps(index);
-
- iter_opts.flags = flags;
- iter_opts.start = start;
- iter_opts.end = end;
-
- cl_git_pass(git_iterator_for_index(&i, repo, index, &iter_opts));
-
- while (!(error = git_iterator_advance(&entry, i))) {
- cl_assert(entry);
-
- if (expected_names != NULL)
- cl_assert_equal_s(expected_names[count], entry->path);
-
- if (expected_oids != NULL) {
- git_oid oid;
- cl_git_pass(git_oid_fromstr(&oid, expected_oids[count]));
- cl_assert_equal_oid(&oid, &entry->id);
- }
-
- count++;
- }
-
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert(!entry);
- cl_assert_equal_i(expected_count, count);
-
- git_iterator_free(i);
-
- cl_assert(caps == git_index_caps(index));
- git_index_free(index);
-}
-
-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",
- "sub/abc",
- "sub/file",
- "sub/sub/file",
- "sub/sub/subsub.txt",
- "sub/subdir_test1",
- "sub/subdir_test2.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",
- "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d",
- "108bb4e7fd7b16490dc33ff7d972151e73d7166e",
- "a0f7217ae99f5ac3e88534f5cea267febc5fa85b",
- "3e42ffc54a663f9401cc25843d6c0e71a33e4249",
- "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
- "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
- "9e5bdc47d6a80f2be0ea3049ad74231b94609242",
- "e563cf4758f0d646f1b14b76016aa17fa9e549a4",
- "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78",
- "99eae476896f4907224978b88e5ecaa6c5bb67a9",
- "3e42ffc54a663f9401cc25843d6c0e71a33e4249",
- "e563cf4758f0d646f1b14b76016aa17fa9e549a4",
- "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78",
- "dccada462d3df8ac6de596fb8c896aba9344f941"
-};
-
-void test_diff_iterator__index_0(void)
-{
- index_iterator_test(
- "attr", NULL, NULL, 0, ARRAY_SIZE(expected_index_0),
- expected_index_0, expected_index_oids_0);
-}
-
-static const char *expected_index_range[] = {
- "root_test1",
- "root_test2",
- "root_test3",
- "root_test4.txt",
-};
-
-static const char *expected_index_oids_range[] = {
- "45141a79a77842c59a63229403220a4e4be74e3d",
- "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d",
- "108bb4e7fd7b16490dc33ff7d972151e73d7166e",
- "a0f7217ae99f5ac3e88534f5cea267febc5fa85b",
-};
-
-void test_diff_iterator__index_range(void)
-{
- index_iterator_test(
- "attr", "root", "root", 0, ARRAY_SIZE(expected_index_range),
- expected_index_range, expected_index_oids_range);
-}
-
-void test_diff_iterator__index_range_empty_0(void)
-{
- index_iterator_test(
- "attr", "empty", "empty", 0, 0, NULL, NULL);
-}
-
-void test_diff_iterator__index_range_empty_1(void)
-{
- index_iterator_test(
- "attr", "z_empty_after", NULL, 0, 0, NULL, NULL);
-}
-
-void test_diff_iterator__index_range_empty_2(void)
-{
- index_iterator_test(
- "attr", NULL, ".aaa_empty_before", 0, 0, NULL, NULL);
-}
-
-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", NULL, NULL, 0, ARRAY_SIZE(expected_index_1),
- expected_index_1, expected_index_oids_1);
-}
-
-static const char *expected_index_cs[] = {
- "B", "D", "F", "H", "J", "L/1", "L/B", "L/D", "L/a", "L/c",
- "a", "c", "e", "g", "i", "k/1", "k/B", "k/D", "k/a", "k/c",
-};
-
-static const char *expected_index_ci[] = {
- "a", "B", "c", "D", "e", "F", "g", "H", "i", "J",
- "k/1", "k/a", "k/B", "k/c", "k/D", "L/1", "L/a", "L/B", "L/c", "L/D",
-};
-
-void test_diff_iterator__index_case_folding(void)
-{
- git_buf path = GIT_BUF_INIT;
- int fs_is_ci = 0;
-
- cl_git_pass(git_buf_joinpath(&path, cl_fixture("icase"), ".gitted/CoNfIg"));
- fs_is_ci = git_path_exists(path.ptr);
- git_buf_free(&path);
-
- index_iterator_test(
- "icase", NULL, NULL, 0, ARRAY_SIZE(expected_index_cs),
- fs_is_ci ? expected_index_ci : expected_index_cs, NULL);
-
- cl_git_sandbox_cleanup();
-
- index_iterator_test(
- "icase", NULL, NULL, GIT_ITERATOR_IGNORE_CASE,
- ARRAY_SIZE(expected_index_ci), expected_index_ci, NULL);
-
- cl_git_sandbox_cleanup();
-
- index_iterator_test(
- "icase", NULL, NULL, GIT_ITERATOR_DONT_IGNORE_CASE,
- ARRAY_SIZE(expected_index_cs), expected_index_cs, NULL);
-}
-
-/* -- WORKDIR ITERATOR TESTS -- */
-
-static void workdir_iterator_test(
- const char *sandbox,
- const char *start,
- const char *end,
- int expected_count,
- int expected_ignores,
- const char **expected_names,
- const char *an_ignored_name)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- const git_index_entry *entry;
- int error, count = 0, count_all = 0, count_all_post_reset = 0;
- git_repository *repo = cl_git_sandbox_init(sandbox);
-
- i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
- i_opts.start = start;
- i_opts.end = end;
-
- cl_git_pass(git_iterator_for_workdir(&i, repo, NULL, NULL, &i_opts));
-
- error = git_iterator_current(&entry, i);
- cl_assert((error == 0 && entry != NULL) ||
- (error == GIT_ITEROVER && entry == NULL));
-
- while (entry != NULL) {
- int ignored = git_iterator_current_is_ignored(i);
-
- if (S_ISDIR(entry->mode)) {
- cl_git_pass(git_iterator_advance_into(&entry, i));
- continue;
- }
-
- if (expected_names != NULL)
- cl_assert_equal_s(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++;
-
- error = git_iterator_advance(&entry, i);
-
- cl_assert((error == 0 && entry != NULL) ||
- (error == GIT_ITEROVER && entry == NULL));
- }
-
- cl_assert_equal_i(expected_count, count);
- cl_assert_equal_i(expected_count + expected_ignores, count_all);
-
- cl_git_pass(git_iterator_reset(i, NULL, NULL));
-
- error = git_iterator_current(&entry, i);
- cl_assert((error == 0 && entry != NULL) ||
- (error == GIT_ITEROVER && entry == NULL));
-
- while (entry != NULL) {
- if (S_ISDIR(entry->mode)) {
- cl_git_pass(git_iterator_advance_into(&entry, i));
- continue;
- }
-
- if (expected_names != NULL)
- cl_assert_equal_s(
- expected_names[count_all_post_reset], entry->path);
- count_all_post_reset++;
-
- error = git_iterator_advance(&entry, i);
- cl_assert(error == 0 || error == GIT_ITEROVER);
- }
-
- cl_assert_equal_i(count_all, count_all_post_reset);
-
- git_iterator_free(i);
-}
-
-void test_diff_iterator__workdir_0(void)
-{
- workdir_iterator_test("attr", NULL, NULL, 23, 5, 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.txt",
- "subdir/current_file",
- "subdir/modified_file",
- "subdir/new_file",
- "\xe8\xbf\x99",
- NULL
-};
-
-void test_diff_iterator__workdir_1(void)
-{
- workdir_iterator_test(
- "status", NULL, NULL, 13, 1, status_paths, "ignored_file");
-}
-
-static const char *status_paths_range_0[] = {
- "staged_changes",
- "staged_changes_modified_file",
- "staged_delete_modified_file",
- "staged_new_file",
- "staged_new_file_modified_file",
- NULL
-};
-
-void test_diff_iterator__workdir_1_ranged_0(void)
-{
- workdir_iterator_test(
- "status", "staged", "staged", 5, 0, status_paths_range_0, NULL);
-}
-
-static const char *status_paths_range_1[] = {
- "modified_file", NULL
-};
-
-void test_diff_iterator__workdir_1_ranged_1(void)
-{
- workdir_iterator_test(
- "status", "modified_file", "modified_file",
- 1, 0, status_paths_range_1, NULL);
-}
-
-static const char *status_paths_range_3[] = {
- "subdir.txt",
- "subdir/current_file",
- "subdir/modified_file",
- NULL
-};
-
-void test_diff_iterator__workdir_1_ranged_3(void)
-{
- workdir_iterator_test(
- "status", "subdir", "subdir/modified_file",
- 3, 0, status_paths_range_3, NULL);
-}
-
-static const char *status_paths_range_4[] = {
- "subdir/current_file",
- "subdir/modified_file",
- "subdir/new_file",
- "\xe8\xbf\x99",
- NULL
-};
-
-void test_diff_iterator__workdir_1_ranged_4(void)
-{
- workdir_iterator_test(
- "status", "subdir/", NULL, 4, 0, status_paths_range_4, NULL);
-}
-
-static const char *status_paths_range_5[] = {
- "subdir/modified_file",
- NULL
-};
-
-void test_diff_iterator__workdir_1_ranged_5(void)
-{
- workdir_iterator_test(
- "status", "subdir/modified_file", "subdir/modified_file",
- 1, 0, status_paths_range_5, NULL);
-}
-
-void test_diff_iterator__workdir_1_ranged_empty_0(void)
-{
- workdir_iterator_test(
- "status", "\xff_does_not_exist", NULL,
- 0, 0, NULL, NULL);
-}
-
-void test_diff_iterator__workdir_1_ranged_empty_1(void)
-{
- workdir_iterator_test(
- "status", "empty", "empty",
- 0, 0, NULL, NULL);
-}
-
-void test_diff_iterator__workdir_1_ranged_empty_2(void)
-{
- workdir_iterator_test(
- "status", NULL, "aaaa_empty_before",
- 0, 0, NULL, NULL);
-}
-
-void test_diff_iterator__workdir_builtin_ignores(void)
-{
- git_repository *repo = cl_git_sandbox_init("attr");
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- const git_index_entry *entry;
- int idx;
- static struct {
- const char *path;
- bool ignored;
- } expected[] = {
- { "dir/", true },
- { "file", false },
- { "ign", true },
- { "macro_bad", false },
- { "macro_test", false },
- { "root_test1", false },
- { "root_test2", false },
- { "root_test3", false },
- { "root_test4.txt", false },
- { "sub/", false },
- { "sub/.gitattributes", false },
- { "sub/abc", false },
- { "sub/dir/", true },
- { "sub/file", false },
- { "sub/ign/", true },
- { "sub/sub/", false },
- { "sub/sub/.gitattributes", false },
- { "sub/sub/dir", false }, /* file is not actually a dir */
- { "sub/sub/file", false },
- { NULL, false }
- };
-
- cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777));
- cl_git_mkfile("attr/sub/.git", "whatever");
-
- i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
- i_opts.start = "dir";
- i_opts.end = "sub/sub/file";
-
- cl_git_pass(git_iterator_for_workdir(
- &i, repo, NULL, NULL, &i_opts));
- cl_git_pass(git_iterator_current(&entry, i));
-
- for (idx = 0; entry != NULL; ++idx) {
- int ignored = git_iterator_current_is_ignored(i);
-
- cl_assert_equal_s(expected[idx].path, entry->path);
- cl_assert_(ignored == expected[idx].ignored, expected[idx].path);
-
- if (!ignored &&
- (entry->mode == GIT_FILEMODE_TREE ||
- entry->mode == GIT_FILEMODE_COMMIT))
- {
- /* it is possible to advance "into" a submodule */
- cl_git_pass(git_iterator_advance_into(&entry, i));
- } else {
- int error = git_iterator_advance(&entry, i);
- cl_assert(!error || error == GIT_ITEROVER);
- }
- }
-
- cl_assert(expected[idx].path == NULL);
-
- git_iterator_free(i);
-}
-
-static void check_wd_first_through_third_range(
- git_repository *repo, const char *start, const char *end)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- const git_index_entry *entry;
- int error, idx;
- static const char *expected[] = { "FIRST", "second", "THIRD", NULL };
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
- i_opts.start = start;
- i_opts.end = end;
-
- cl_git_pass(git_iterator_for_workdir(
- &i, repo, NULL, NULL, &i_opts));
- cl_git_pass(git_iterator_current(&entry, i));
-
- for (idx = 0; entry != NULL; ++idx) {
- cl_assert_equal_s(expected[idx], entry->path);
-
- error = git_iterator_advance(&entry, i);
- cl_assert(!error || error == GIT_ITEROVER);
- }
-
- cl_assert(expected[idx] == NULL);
-
- git_iterator_free(i);
-}
-
-void test_diff_iterator__workdir_handles_icase_range(void)
-{
- git_repository *repo;
-
- repo = cl_git_sandbox_init("empty_standard_repo");
- cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt");
-
- cl_git_mkfile("empty_standard_repo/before", "whatever\n");
- cl_git_mkfile("empty_standard_repo/FIRST", "whatever\n");
- cl_git_mkfile("empty_standard_repo/second", "whatever\n");
- cl_git_mkfile("empty_standard_repo/THIRD", "whatever\n");
- cl_git_mkfile("empty_standard_repo/zafter", "whatever\n");
- cl_git_mkfile("empty_standard_repo/Zlast", "whatever\n");
-
- check_wd_first_through_third_range(repo, "first", "third");
- check_wd_first_through_third_range(repo, "FIRST", "THIRD");
- check_wd_first_through_third_range(repo, "first", "THIRD");
- check_wd_first_through_third_range(repo, "FIRST", "third");
- check_wd_first_through_third_range(repo, "FirSt", "tHiRd");
-}
-
-static void check_tree_range(
- git_repository *repo,
- const char *start,
- const char *end,
- bool ignore_case,
- int expected_count)
-{
- git_tree *head;
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- int error, count;
-
- i_opts.flags = ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;
- i_opts.start = start;
- i_opts.end = end;
-
- cl_git_pass(git_repository_head_tree(&head, repo));
-
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
-
- for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count)
- /* count em up */;
-
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert_equal_i(expected_count, count);
-
- git_iterator_free(i);
- git_tree_free(head);
-}
-
-void test_diff_iterator__tree_handles_icase_range(void)
-{
- git_repository *repo;
-
- repo = cl_git_sandbox_init("testrepo");
-
- check_tree_range(repo, "B", "C", false, 0);
- check_tree_range(repo, "B", "C", true, 1);
- check_tree_range(repo, "b", "c", false, 1);
- check_tree_range(repo, "b", "c", true, 1);
-
- check_tree_range(repo, "a", "z", false, 3);
- check_tree_range(repo, "a", "z", true, 4);
- check_tree_range(repo, "A", "Z", false, 1);
- check_tree_range(repo, "A", "Z", true, 4);
- check_tree_range(repo, "a", "Z", false, 0);
- check_tree_range(repo, "a", "Z", true, 4);
- check_tree_range(repo, "A", "z", false, 4);
- check_tree_range(repo, "A", "z", true, 4);
-
- check_tree_range(repo, "new.txt", "new.txt", true, 1);
- check_tree_range(repo, "new.txt", "new.txt", false, 1);
- check_tree_range(repo, "README", "README", true, 1);
- check_tree_range(repo, "README", "README", false, 1);
-}
-
-static void check_index_range(
- git_repository *repo,
- const char *start,
- const char *end,
- bool ignore_case,
- int expected_count)
-{
- git_index *index;
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- int error, count, caps;
- bool is_ignoring_case;
-
- cl_git_pass(git_repository_index(&index, repo));
-
- caps = git_index_caps(index);
- is_ignoring_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0);
-
- if (ignore_case != is_ignoring_case)
- cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEXCAP_IGNORE_CASE));
-
- i_opts.flags = 0;
- i_opts.start = start;
- i_opts.end = end;
-
- cl_git_pass(git_iterator_for_index(&i, repo, index, &i_opts));
-
- cl_assert(git_iterator_ignore_case(i) == ignore_case);
-
- for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count)
- /* count em up */;
-
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert_equal_i(expected_count, count);
-
- git_iterator_free(i);
- git_index_free(index);
-}
-
-void test_diff_iterator__index_handles_icase_range(void)
-{
- git_repository *repo;
- git_index *index;
- git_tree *head;
-
- repo = cl_git_sandbox_init("testrepo");
-
- /* reset index to match HEAD */
- cl_git_pass(git_repository_head_tree(&head, repo));
- cl_git_pass(git_repository_index(&index, repo));
- cl_git_pass(git_index_read_tree(index, head));
- cl_git_pass(git_index_write(index));
- git_tree_free(head);
- git_index_free(index);
-
- /* do some ranged iterator checks toggling case sensitivity */
- check_index_range(repo, "B", "C", false, 0);
- check_index_range(repo, "B", "C", true, 1);
- check_index_range(repo, "a", "z", false, 3);
- check_index_range(repo, "a", "z", true, 4);
-}
diff --git a/tests/diff/racediffiter.c b/tests/diff/racediffiter.c
new file mode 100644
index 000000000..d364d6b21
--- /dev/null
+++ b/tests/diff/racediffiter.c
@@ -0,0 +1,129 @@
+/* This test exercises the problem described in
+** https://github.com/libgit2/libgit2/pull/3568
+** where deleting a directory during a diff/status
+** operation can cause an access violation.
+**
+** The "test_diff_racediffiter__basic() test confirms
+** the normal operation of diff on the given repo.
+**
+** The "test_diff_racediffiter__racy_rmdir() test
+** uses the new diff progress callback to delete
+** a directory (after the initial readdir() and
+** before the directory itself is visited) causing
+** the recursion and iteration to fail.
+*/
+
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
+
+void test_diff_racediffiter__initialize(void)
+{
+}
+
+void test_diff_racediffiter__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+typedef struct
+{
+ const char *path;
+ git_delta_t t;
+
+} basic_payload;
+
+static int notify_cb__basic(
+ const git_diff *diff_so_far,
+ const git_diff_delta *delta_to_add,
+ const char *matched_pathspec,
+ void *payload)
+{
+ basic_payload *exp = (basic_payload *)payload;
+ basic_payload *e;
+
+ GIT_UNUSED(diff_so_far);
+ GIT_UNUSED(matched_pathspec);
+
+ for (e = exp; e->path; e++) {
+ if (strcmp(e->path, delta_to_add->new_file.path) == 0) {
+ cl_assert_equal_i(e->t, delta_to_add->status);
+ return 0;
+ }
+ }
+ cl_assert(0);
+ return GIT_ENOTFOUND;
+}
+
+void test_diff_racediffiter__basic(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_repository *repo = cl_git_sandbox_init("diff");
+ git_diff *diff;
+
+ basic_payload exp_a[] = {
+ { "another.txt", GIT_DELTA_MODIFIED },
+ { "readme.txt", GIT_DELTA_MODIFIED },
+ { "zzzzz/", GIT_DELTA_IGNORED },
+ { NULL, 0 }
+ };
+
+ cl_must_pass(p_mkdir("diff/zzzzz", 0777));
+
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ opts.notify_cb = notify_cb__basic;
+ opts.payload = exp_a;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+
+ git_diff_free(diff);
+}
+
+
+typedef struct {
+ bool first_time;
+ const char *dir;
+ basic_payload *basic_payload;
+} racy_payload;
+
+static int notify_cb__racy_rmdir(
+ const git_diff *diff_so_far,
+ const git_diff_delta *delta_to_add,
+ const char *matched_pathspec,
+ void *payload)
+{
+ racy_payload *pay = (racy_payload *)payload;
+
+ if (pay->first_time) {
+ cl_must_pass(p_rmdir(pay->dir));
+ pay->first_time = false;
+ }
+
+ return notify_cb__basic(diff_so_far, delta_to_add, matched_pathspec, pay->basic_payload);
+}
+
+void test_diff_racediffiter__racy(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_repository *repo = cl_git_sandbox_init("diff");
+ git_diff *diff;
+
+ basic_payload exp_a[] = {
+ { "another.txt", GIT_DELTA_MODIFIED },
+ { "readme.txt", GIT_DELTA_MODIFIED },
+ { NULL, 0 }
+ };
+
+ racy_payload pay = { true, "diff/zzzzz", exp_a };
+
+ cl_must_pass(p_mkdir("diff/zzzzz", 0777));
+
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ opts.notify_cb = notify_cb__racy_rmdir;
+ opts.payload = &pay;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+
+ git_diff_free(diff);
+}
diff --git a/tests/iterator/index.c b/tests/iterator/index.c
new file mode 100644
index 000000000..64e7b14ba
--- /dev/null
+++ b/tests/iterator/index.c
@@ -0,0 +1,1383 @@
+#include "clar_libgit2.h"
+#include "iterator.h"
+#include "repository.h"
+#include "fileops.h"
+#include "iterator_helpers.h"
+#include "../submodule/submodule_helpers.h"
+#include <stdarg.h>
+
+static git_repository *g_repo;
+
+void test_iterator_index__initialize(void)
+{
+}
+
+void test_iterator_index__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+static void index_iterator_test(
+ const char *sandbox,
+ const char *start,
+ const char *end,
+ git_iterator_flag_t flags,
+ int expected_count,
+ const char **expected_names,
+ const char **expected_oids)
+{
+ git_index *index;
+ git_iterator *i;
+ const git_index_entry *entry;
+ int error, count = 0, caps;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ g_repo = cl_git_sandbox_init(sandbox);
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ caps = git_index_caps(index);
+
+ iter_opts.flags = flags;
+ iter_opts.start = start;
+ iter_opts.end = end;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &iter_opts));
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ cl_assert(entry);
+
+ if (expected_names != NULL)
+ cl_assert_equal_s(expected_names[count], entry->path);
+
+ if (expected_oids != NULL) {
+ git_oid oid;
+ cl_git_pass(git_oid_fromstr(&oid, expected_oids[count]));
+ cl_assert_equal_oid(&oid, &entry->id);
+ }
+
+ count++;
+ }
+
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert(!entry);
+ cl_assert_equal_i(expected_count, count);
+
+ git_iterator_free(i);
+
+ cl_assert(caps == git_index_caps(index));
+ git_index_free(index);
+}
+
+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",
+ "sub/abc",
+ "sub/file",
+ "sub/sub/file",
+ "sub/sub/subsub.txt",
+ "sub/subdir_test1",
+ "sub/subdir_test2.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",
+ "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d",
+ "108bb4e7fd7b16490dc33ff7d972151e73d7166e",
+ "a0f7217ae99f5ac3e88534f5cea267febc5fa85b",
+ "3e42ffc54a663f9401cc25843d6c0e71a33e4249",
+ "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
+ "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
+ "9e5bdc47d6a80f2be0ea3049ad74231b94609242",
+ "e563cf4758f0d646f1b14b76016aa17fa9e549a4",
+ "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78",
+ "99eae476896f4907224978b88e5ecaa6c5bb67a9",
+ "3e42ffc54a663f9401cc25843d6c0e71a33e4249",
+ "e563cf4758f0d646f1b14b76016aa17fa9e549a4",
+ "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78",
+ "dccada462d3df8ac6de596fb8c896aba9344f941"
+};
+
+void test_iterator_index__0(void)
+{
+ index_iterator_test(
+ "attr", NULL, NULL, 0, ARRAY_SIZE(expected_index_0),
+ 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_iterator_index__1(void)
+{
+ index_iterator_test(
+ "status", NULL, NULL, 0, ARRAY_SIZE(expected_index_1),
+ expected_index_1, expected_index_oids_1);
+}
+
+static const char *expected_index_range[] = {
+ "root_test1",
+ "root_test2",
+ "root_test3",
+ "root_test4.txt",
+};
+
+static const char *expected_index_oids_range[] = {
+ "45141a79a77842c59a63229403220a4e4be74e3d",
+ "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d",
+ "108bb4e7fd7b16490dc33ff7d972151e73d7166e",
+ "a0f7217ae99f5ac3e88534f5cea267febc5fa85b",
+};
+
+void test_iterator_index__range(void)
+{
+ index_iterator_test(
+ "attr", "root", "root", 0, ARRAY_SIZE(expected_index_range),
+ expected_index_range, expected_index_oids_range);
+}
+
+void test_iterator_index__range_empty_0(void)
+{
+ index_iterator_test(
+ "attr", "empty", "empty", 0, 0, NULL, NULL);
+}
+
+void test_iterator_index__range_empty_1(void)
+{
+ index_iterator_test(
+ "attr", "z_empty_after", NULL, 0, 0, NULL, NULL);
+}
+
+void test_iterator_index__range_empty_2(void)
+{
+ index_iterator_test(
+ "attr", NULL, ".aaa_empty_before", 0, 0, NULL, NULL);
+}
+
+static void check_index_range(
+ git_repository *repo,
+ const char *start,
+ const char *end,
+ bool ignore_case,
+ int expected_count)
+{
+ git_index *index;
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error, count, caps;
+ bool is_ignoring_case;
+
+ cl_git_pass(git_repository_index(&index, repo));
+
+ caps = git_index_caps(index);
+ is_ignoring_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0);
+
+ if (ignore_case != is_ignoring_case)
+ cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEXCAP_IGNORE_CASE));
+
+ i_opts.flags = 0;
+ i_opts.start = start;
+ i_opts.end = end;
+
+ cl_git_pass(git_iterator_for_index(&i, repo, index, &i_opts));
+
+ cl_assert(git_iterator_ignore_case(i) == ignore_case);
+
+ for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count)
+ /* count em up */;
+
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert_equal_i(expected_count, count);
+
+ git_iterator_free(i);
+ git_index_free(index);
+}
+
+void test_iterator_index__range_icase(void)
+{
+ git_index *index;
+ git_tree *head;
+
+ g_repo = cl_git_sandbox_init("testrepo");
+
+ /* reset index to match HEAD */
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_index_read_tree(index, head));
+ cl_git_pass(git_index_write(index));
+ git_tree_free(head);
+ git_index_free(index);
+
+ /* do some ranged iterator checks toggling case sensitivity */
+ check_index_range(g_repo, "B", "C", false, 0);
+ check_index_range(g_repo, "B", "C", true, 1);
+ check_index_range(g_repo, "a", "z", false, 3);
+ check_index_range(g_repo, "a", "z", true, 4);
+}
+
+static const char *expected_index_cs[] = {
+ "B", "D", "F", "H", "J", "L/1", "L/B", "L/D", "L/a", "L/c",
+ "a", "c", "e", "g", "i", "k/1", "k/B", "k/D", "k/a", "k/c",
+};
+
+static const char *expected_index_ci[] = {
+ "a", "B", "c", "D", "e", "F", "g", "H", "i", "J",
+ "k/1", "k/a", "k/B", "k/c", "k/D", "L/1", "L/a", "L/B", "L/c", "L/D",
+};
+
+void test_iterator_index__case_folding(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fs_is_ci = 0;
+
+ cl_git_pass(git_buf_joinpath(&path, cl_fixture("icase"), ".gitted/CoNfIg"));
+ fs_is_ci = git_path_exists(path.ptr);
+ git_buf_free(&path);
+
+ index_iterator_test(
+ "icase", NULL, NULL, 0, ARRAY_SIZE(expected_index_cs),
+ fs_is_ci ? expected_index_ci : expected_index_cs, NULL);
+
+ cl_git_sandbox_cleanup();
+
+ index_iterator_test(
+ "icase", NULL, NULL, GIT_ITERATOR_IGNORE_CASE,
+ ARRAY_SIZE(expected_index_ci), expected_index_ci, NULL);
+
+ cl_git_sandbox_cleanup();
+
+ index_iterator_test(
+ "icase", NULL, NULL, GIT_ITERATOR_DONT_IGNORE_CASE,
+ ARRAY_SIZE(expected_index_cs), expected_index_cs, NULL);
+}
+
+/* Index contents (including pseudotrees):
+ *
+ * 0: a 5: F 10: k/ 16: L/
+ * 1: B 6: g 11: k/1 17: L/1
+ * 2: c 7: H 12: k/a 18: L/a
+ * 3: D 8: i 13: k/B 19: L/B
+ * 4: e 9: J 14: k/c 20: L/c
+ * 15: k/D 21: L/D
+ *
+ * 0: B 5: L/ 11: a 16: k/
+ * 1: D 6: L/1 12: c 17: k/1
+ * 2: F 7: L/B 13: e 18: k/B
+ * 3: H 8: L/D 14: g 19: k/D
+ * 4: J 9: L/a 15: i 20: k/a
+ * 10: L/c 21: k/c
+ */
+
+void test_iterator_index__icase_0(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ /* autoexpand with no tree entries for index */
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, NULL));
+ expect_iterator_items(i, 20, NULL, 20, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 22, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 12, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ git_index_free(index);
+}
+
+void test_iterator_index__icase_1(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+ int caps;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ caps = git_index_caps(index);
+
+ /* force case sensitivity */
+ cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE));
+
+ /* autoexpand with no tree entries over range */
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 7, NULL, 7, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 3, NULL, 3, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 8, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 4, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 5, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 1, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* force case insensitivity */
+ cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE));
+
+ /* autoexpand with no tree entries over range */
+ i_opts.flags = 0;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 13, NULL, 13, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 5, NULL, 5, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 14, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 6, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 9, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 1, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_index_set_caps(index, caps));
+ git_index_free(index);
+}
+
+void test_iterator_index__pathlist(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+ git_vector filelist;
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "a"));
+ cl_git_pass(git_vector_insert(&filelist, "B"));
+ cl_git_pass(git_vector_insert(&filelist, "c"));
+ cl_git_pass(git_vector_insert(&filelist, "D"));
+ cl_git_pass(git_vector_insert(&filelist, "e"));
+ cl_git_pass(git_vector_insert(&filelist, "k/1"));
+ cl_git_pass(git_vector_insert(&filelist, "k/a"));
+ cl_git_pass(git_vector_insert(&filelist, "L/1"));
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ /* Case sensitive */
+ {
+ const char *expected[] = {
+ "B", "D", "L/1", "a", "c", "e", "k/1", "k/a" };
+ size_t expected_len = 8;
+
+ i_opts.start = NULL;
+ i_opts.end = NULL;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Case INsensitive */
+ {
+ const char *expected[] = {
+ "a", "B", "c", "D", "e", "k/1", "k/a", "L/1" };
+ size_t expected_len = 8;
+
+ i_opts.start = NULL;
+ i_opts.end = NULL;
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Set a start, but no end. Case sensitive. */
+ {
+ const char *expected[] = { "c", "e", "k/1", "k/a" };
+ size_t expected_len = 4;
+
+ i_opts.start = "c";
+ i_opts.end = NULL;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Set a start, but no end. Case INsensitive. */
+ {
+ const char *expected[] = { "c", "D", "e", "k/1", "k/a", "L/1" };
+ size_t expected_len = 6;
+
+ i_opts.start = "c";
+ i_opts.end = NULL;
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Set no start, but an end. Case sensitive. */
+ {
+ const char *expected[] = { "B", "D", "L/1", "a", "c", "e" };
+ size_t expected_len = 6;
+
+ i_opts.start = NULL;
+ i_opts.end = "e";
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Set no start, but an end. Case INsensitive. */
+ {
+ const char *expected[] = { "a", "B", "c", "D", "e" };
+ size_t expected_len = 5;
+
+ i_opts.start = NULL;
+ i_opts.end = "e";
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Start and an end, case sensitive */
+ {
+ const char *expected[] = { "c", "e", "k/1" };
+ size_t expected_len = 3;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Start and an end, case sensitive */
+ {
+ const char *expected[] = { "k/1" };
+ size_t expected_len = 1;
+
+ i_opts.start = "k";
+ i_opts.end = "k/D";
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Start and an end, case INsensitive */
+ {
+ const char *expected[] = { "c", "D", "e", "k/1", "k/a" };
+ size_t expected_len = 5;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Start and an end, case INsensitive */
+ {
+ const char *expected[] = { "k/1", "k/a" };
+ size_t expected_len = 2;
+
+ i_opts.start = "k";
+ i_opts.end = "k/D";
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ git_index_free(index);
+ git_vector_free(&filelist);
+}
+
+void test_iterator_index__pathlist_with_dirs(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+ git_vector filelist;
+
+ cl_git_pass(git_vector_init(&filelist, 5, NULL));
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ /* Test that a prefix `k` matches folders, even without trailing slash */
+ {
+ const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "k"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Test that a `k/` matches a folder */
+ {
+ const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "k/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* When the iterator is case sensitive, ensure we can't lookup the
+ * directory with the wrong case.
+ */
+ {
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "K/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+ }
+
+ /* Test that case insensitive matching works. */
+ {
+ const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "K/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Test that case insensitive matching works without trailing slash. */
+ {
+ const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "K"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ git_index_free(index);
+ git_vector_free(&filelist);
+}
+
+void test_iterator_index__pathlist_with_dirs_include_trees(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+ git_vector filelist;
+
+ const char *expected[] = { "k/", "k/1", "k/B", "k/D", "k/a", "k/c" };
+ size_t expected_len = 6;
+
+ cl_git_pass(git_vector_init(&filelist, 5, NULL));
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "k"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+
+ git_index_free(index);
+ git_vector_free(&filelist);
+}
+
+void test_iterator_index__pathlist_1(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+ git_vector filelist = GIT_VECTOR_INIT;
+ int default_icase, expect;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "0"));
+ cl_git_pass(git_vector_insert(&filelist, "c"));
+ cl_git_pass(git_vector_insert(&filelist, "D"));
+ cl_git_pass(git_vector_insert(&filelist, "e"));
+ cl_git_pass(git_vector_insert(&filelist, "k/1"));
+ cl_git_pass(git_vector_insert(&filelist, "k/a"));
+
+ /* In this test we DO NOT force a case setting on the index. */
+ default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0);
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ i_opts.start = "b";
+ i_opts.end = "k/D";
+
+ /* (c D e k/1 k/a ==> 5) vs (c e k/1 ==> 3) */
+ expect = default_icase ? 5 : 3;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expect, NULL, expect, NULL);
+ git_iterator_free(i);
+
+ git_index_free(index);
+ git_vector_free(&filelist);
+}
+
+void test_iterator_index__pathlist_2(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+ git_vector filelist = GIT_VECTOR_INIT;
+ int default_icase, expect;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "0"));
+ cl_git_pass(git_vector_insert(&filelist, "c"));
+ cl_git_pass(git_vector_insert(&filelist, "D"));
+ cl_git_pass(git_vector_insert(&filelist, "e"));
+ cl_git_pass(git_vector_insert(&filelist, "k/"));
+ cl_git_pass(git_vector_insert(&filelist, "k.a"));
+ cl_git_pass(git_vector_insert(&filelist, "k.b"));
+ cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ"));
+
+ /* In this test we DO NOT force a case setting on the index. */
+ default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0);
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ i_opts.start = "b";
+ i_opts.end = "k/D";
+
+ /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */
+ expect = default_icase ? 8 : 5;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expect, NULL, expect, NULL);
+ git_iterator_free(i);
+
+ git_index_free(index);
+ git_vector_free(&filelist);
+}
+
+void test_iterator_index__pathlist_four(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+ git_vector filelist = GIT_VECTOR_INIT;
+ int default_icase, expect;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "0"));
+ cl_git_pass(git_vector_insert(&filelist, "c"));
+ cl_git_pass(git_vector_insert(&filelist, "D"));
+ cl_git_pass(git_vector_insert(&filelist, "e"));
+ cl_git_pass(git_vector_insert(&filelist, "k"));
+ cl_git_pass(git_vector_insert(&filelist, "k.a"));
+ cl_git_pass(git_vector_insert(&filelist, "k.b"));
+ cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ"));
+
+ /* In this test we DO NOT force a case setting on the index. */
+ default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0);
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ i_opts.start = "b";
+ i_opts.end = "k/D";
+
+ /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */
+ expect = default_icase ? 8 : 5;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expect, NULL, expect, NULL);
+ git_iterator_free(i);
+
+ git_index_free(index);
+ git_vector_free(&filelist);
+}
+
+void test_iterator_index__pathlist_icase(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+ int caps;
+ git_vector filelist;
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "a"));
+ cl_git_pass(git_vector_insert(&filelist, "B"));
+ cl_git_pass(git_vector_insert(&filelist, "c"));
+ cl_git_pass(git_vector_insert(&filelist, "D"));
+ cl_git_pass(git_vector_insert(&filelist, "e"));
+ cl_git_pass(git_vector_insert(&filelist, "k/1"));
+ cl_git_pass(git_vector_insert(&filelist, "k/a"));
+ cl_git_pass(git_vector_insert(&filelist, "L/1"));
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ caps = git_index_caps(index);
+
+ /* force case sensitivity */
+ cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE));
+
+ /* All indexfilelist iterator tests are "autoexpand with no tree entries" */
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 3, NULL, 3, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 1, NULL, 1, NULL);
+ git_iterator_free(i);
+
+ /* force case insensitivity */
+ cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE));
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 5, NULL, 5, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 2, NULL, 2, NULL);
+ git_iterator_free(i);
+
+ cl_git_pass(git_index_set_caps(index, caps));
+ git_index_free(index);
+ git_vector_free(&filelist);
+}
+
+void test_iterator_index__pathlist_with_directory(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+ git_tree *tree;
+ git_index *index;
+
+ g_repo = cl_git_sandbox_init("testrepo2");
+ git_repository_head_tree(&tree, g_repo);
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "subdir"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, 4, NULL, 4, NULL);
+ git_iterator_free(i);
+ git_index_free(index);
+ git_vector_free(&filelist);
+}
+
+static void create_paths(git_index *index, const char *root, int depth)
+{
+ git_buf fullpath = GIT_BUF_INIT;
+ git_index_entry entry;
+ size_t root_len;
+ int i;
+
+ if (root) {
+ cl_git_pass(git_buf_puts(&fullpath, root));
+ cl_git_pass(git_buf_putc(&fullpath, '/'));
+ }
+
+ root_len = fullpath.size;
+
+ for (i = 0; i < 8; i++) {
+ bool file = (depth == 0 || (i % 2) == 0);
+ git_buf_truncate(&fullpath, root_len);
+ cl_git_pass(git_buf_printf(&fullpath, "item%d", i));
+
+ if (file) {
+ memset(&entry, 0, sizeof(git_index_entry));
+ entry.path = fullpath.ptr;
+ entry.mode = GIT_FILEMODE_BLOB;
+ git_oid_fromstr(&entry.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6");
+
+ cl_git_pass(git_index_add(index, &entry));
+ } else if (depth > 0) {
+ create_paths(index, fullpath.ptr, (depth - 1));
+ }
+ }
+
+ git_buf_free(&fullpath);
+}
+
+void test_iterator_index__pathlist_for_deeply_nested_item(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+ git_vector filelist;
+
+ cl_git_pass(git_vector_init(&filelist, 5, NULL));
+
+ g_repo = cl_git_sandbox_init("icase");
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ create_paths(index, NULL, 3);
+
+ /* Ensure that we find the single path we're interested in */
+ {
+ const char *expected[] = { "item1/item3/item5/item7" };
+ size_t expected_len = 1;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/item7"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ {
+ const char *expected[] = {
+ "item1/item3/item5/item0", "item1/item3/item5/item1",
+ "item1/item3/item5/item2", "item1/item3/item5/item3",
+ "item1/item3/item5/item4", "item1/item3/item5/item5",
+ "item1/item3/item5/item6", "item1/item3/item5/item7",
+ };
+ size_t expected_len = 8;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ {
+ const char *expected[] = {
+ "item1/item3/item0",
+ "item1/item3/item1/item0", "item1/item3/item1/item1",
+ "item1/item3/item1/item2", "item1/item3/item1/item3",
+ "item1/item3/item1/item4", "item1/item3/item1/item5",
+ "item1/item3/item1/item6", "item1/item3/item1/item7",
+ "item1/item3/item2",
+ "item1/item3/item3/item0", "item1/item3/item3/item1",
+ "item1/item3/item3/item2", "item1/item3/item3/item3",
+ "item1/item3/item3/item4", "item1/item3/item3/item5",
+ "item1/item3/item3/item6", "item1/item3/item3/item7",
+ "item1/item3/item4",
+ "item1/item3/item5/item0", "item1/item3/item5/item1",
+ "item1/item3/item5/item2", "item1/item3/item5/item3",
+ "item1/item3/item5/item4", "item1/item3/item5/item5",
+ "item1/item3/item5/item6", "item1/item3/item5/item7",
+ "item1/item3/item6",
+ "item1/item3/item7/item0", "item1/item3/item7/item1",
+ "item1/item3/item7/item2", "item1/item3/item7/item3",
+ "item1/item3/item7/item4", "item1/item3/item7/item5",
+ "item1/item3/item7/item6", "item1/item3/item7/item7",
+ };
+ size_t expected_len = 36;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "item1/item3/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Ensure that we find the single path we're interested in, and we find
+ * it efficiently, and don't stat the entire world to get there.
+ */
+ {
+ const char *expected[] = {
+ "item0", "item1/item2", "item5/item7/item4", "item6",
+ "item7/item3/item1/item6" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "item7/item3/item1/item6"));
+ cl_git_pass(git_vector_insert(&filelist, "item6"));
+ cl_git_pass(git_vector_insert(&filelist, "item5/item7/item4"));
+ cl_git_pass(git_vector_insert(&filelist, "item1/item2"));
+ cl_git_pass(git_vector_insert(&filelist, "item0"));
+
+ /* also add some things that don't exist or don't match the right type */
+ cl_git_pass(git_vector_insert(&filelist, "item2/"));
+ cl_git_pass(git_vector_insert(&filelist, "itemN"));
+ cl_git_pass(git_vector_insert(&filelist, "item1/itemA"));
+ cl_git_pass(git_vector_insert(&filelist, "item5/item3/item4/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ git_index_free(index);
+ git_vector_free(&filelist);
+}
+
+void test_iterator_index__advance_over(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ g_repo = cl_git_sandbox_init("icase");
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ create_paths(index, NULL, 1);
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+
+ expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item0", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item1/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item2", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item3/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item4", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item5/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item6", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item7/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL);
+
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+ git_index_free(index);
+}
+
+void test_iterator_index__advance_into(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_advance_into(i, "B");
+ expect_advance_into(i, "D");
+ expect_advance_into(i, "F");
+ expect_advance_into(i, "H");
+ expect_advance_into(i, "J");
+ expect_advance_into(i, "L/");
+ expect_advance_into(i, "L/1");
+ expect_advance_into(i, "L/B");
+ expect_advance_into(i, "L/D");
+ expect_advance_into(i, "L/a");
+ expect_advance_into(i, "L/c");
+ expect_advance_into(i, "a");
+ expect_advance_into(i, "c");
+ expect_advance_into(i, "e");
+ expect_advance_into(i, "g");
+ expect_advance_into(i, "i");
+ expect_advance_into(i, "k/");
+ expect_advance_into(i, "k/1");
+ expect_advance_into(i, "k/B");
+ expect_advance_into(i, "k/D");
+ expect_advance_into(i, "k/a");
+ expect_advance_into(i, "k/c");
+
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+ git_index_free(index);
+}
+
+void test_iterator_index__advance_into_and_over(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ create_paths(index, NULL, 2);
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+ expect_advance_into(i, "B");
+ expect_advance_into(i, "D");
+ expect_advance_into(i, "F");
+ expect_advance_into(i, "H");
+ expect_advance_into(i, "J");
+ expect_advance_into(i, "L/");
+ expect_advance_into(i, "L/1");
+ expect_advance_into(i, "L/B");
+ expect_advance_into(i, "L/D");
+ expect_advance_into(i, "L/a");
+ expect_advance_into(i, "L/c");
+ expect_advance_into(i, "a");
+ expect_advance_into(i, "c");
+ expect_advance_into(i, "e");
+ expect_advance_into(i, "g");
+ expect_advance_into(i, "i");
+ expect_advance_into(i, "item0");
+ expect_advance_into(i, "item1/");
+ expect_advance_into(i, "item1/item0");
+ expect_advance_into(i, "item1/item1/");
+ expect_advance_into(i, "item1/item1/item0");
+ expect_advance_into(i, "item1/item1/item1");
+ expect_advance_into(i, "item1/item1/item2");
+ expect_advance_into(i, "item1/item1/item3");
+ expect_advance_into(i, "item1/item1/item4");
+ expect_advance_into(i, "item1/item1/item5");
+ expect_advance_into(i, "item1/item1/item6");
+ expect_advance_into(i, "item1/item1/item7");
+ expect_advance_into(i, "item1/item2");
+ expect_advance_over(i, "item1/item3/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item1/item4", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item1/item5/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item1/item6", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item1/item7/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_into(i, "item2");
+ expect_advance_over(i, "item3/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item4", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item5/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item6", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "item7/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_into(i, "k/");
+ expect_advance_into(i, "k/1");
+ expect_advance_into(i, "k/B");
+ expect_advance_into(i, "k/D");
+ expect_advance_into(i, "k/a");
+ expect_advance_into(i, "k/c");
+
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+ git_index_free(index);
+}
+
+static void add_conflict(
+ git_index *index,
+ const char *ancestor_path,
+ const char *our_path,
+ const char *their_path)
+{
+ git_index_entry ancestor = {{0}}, ours = {{0}}, theirs = {{0}};
+
+ ancestor.path = ancestor_path;
+ ancestor.mode = GIT_FILEMODE_BLOB;
+ git_oid_fromstr(&ancestor.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6");
+ GIT_IDXENTRY_STAGE_SET(&ancestor, 1);
+
+ ours.path = our_path;
+ ours.mode = GIT_FILEMODE_BLOB;
+ git_oid_fromstr(&ours.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6");
+ GIT_IDXENTRY_STAGE_SET(&ours, 2);
+
+ theirs.path = their_path;
+ theirs.mode = GIT_FILEMODE_BLOB;
+ git_oid_fromstr(&theirs.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6");
+ GIT_IDXENTRY_STAGE_SET(&theirs, 3);
+
+ cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs));
+}
+
+void test_iterator_index__include_conflicts(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_index *index;
+
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ g_repo = cl_git_sandbox_init("icase");
+ cl_git_pass(git_repository_index(&index, g_repo));
+
+ add_conflict(index, "CONFLICT1", "CONFLICT1" ,"CONFLICT1");
+ add_conflict(index, "ZZZ-CONFLICT2.ancestor", "ZZZ-CONFLICT2.ours", "ZZZ-CONFLICT2.theirs");
+ add_conflict(index, "ancestor.conflict3", "ours.conflict3", "theirs.conflict3");
+ add_conflict(index, "zzz-conflict4", "zzz-conflict4", "zzz-conflict4");
+
+ /* Iterate the index, ensuring that conflicts are not included */
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+
+ expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL);
+
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+
+ /* Try again, returning conflicts */
+ i_opts.flags |= GIT_ITERATOR_INCLUDE_CONFLICTS;
+
+ cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
+
+ expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "ZZZ-CONFLICT2.ancestor", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "ZZZ-CONFLICT2.ours", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "ZZZ-CONFLICT2.theirs", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "ancestor.conflict3", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "ours.conflict3", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "theirs.conflict3", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL);
+
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+
+ git_index_free(index);
+}
diff --git a/tests/iterator/iterator_helpers.c b/tests/iterator/iterator_helpers.c
new file mode 100644
index 000000000..a3e803299
--- /dev/null
+++ b/tests/iterator/iterator_helpers.c
@@ -0,0 +1,146 @@
+#include "clar_libgit2.h"
+#include "iterator.h"
+#include "repository.h"
+#include "fileops.h"
+#include "iterator_helpers.h"
+#include <stdarg.h>
+
+static void assert_at_end(git_iterator *i, bool verbose)
+{
+ const git_index_entry *end;
+ int error = git_iterator_advance(&end, i);
+
+ if (verbose && error != GIT_ITEROVER)
+ fprintf(stderr, "Expected end of iterator, got '%s'\n", end->path);
+
+ cl_git_fail_with(GIT_ITEROVER, error);
+}
+
+void expect_iterator_items(
+ git_iterator *i,
+ int expected_flat,
+ const char **expected_flat_paths,
+ int expected_total,
+ const char **expected_total_paths)
+{
+ const git_index_entry *entry;
+ int count, error;
+ int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES);
+ bool v = false;
+
+ if (expected_flat < 0) { v = true; expected_flat = -expected_flat; }
+ if (expected_total < 0) { v = true; expected_total = -expected_total; }
+
+ if (v) fprintf(stderr, "== %s ==\n", no_trees ? "notrees" : "trees");
+
+ count = 0;
+
+ while (!git_iterator_advance(&entry, i)) {
+ if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode);
+
+ if (no_trees)
+ cl_assert(entry->mode != GIT_FILEMODE_TREE);
+
+ if (expected_flat_paths) {
+ const char *expect_path = expected_flat_paths[count];
+ size_t expect_len = strlen(expect_path);
+
+ cl_assert_equal_s(expect_path, entry->path);
+
+ if (expect_path[expect_len - 1] == '/')
+ cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode);
+ else
+ cl_assert(entry->mode != GIT_FILEMODE_TREE);
+ }
+
+ if (++count >= expected_flat)
+ break;
+ }
+
+ assert_at_end(i, v);
+ cl_assert_equal_i(expected_flat, count);
+
+ cl_git_pass(git_iterator_reset(i));
+
+ count = 0;
+ cl_git_pass(git_iterator_current(&entry, i));
+
+ if (v) fprintf(stderr, "-- %s --\n", no_trees ? "notrees" : "trees");
+
+ while (entry != NULL) {
+ if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode);
+
+ if (no_trees)
+ cl_assert(entry->mode != GIT_FILEMODE_TREE);
+
+ if (expected_total_paths) {
+ const char *expect_path = expected_total_paths[count];
+ size_t expect_len = strlen(expect_path);
+
+ cl_assert_equal_s(expect_path, entry->path);
+
+ if (expect_path[expect_len - 1] == '/')
+ cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode);
+ else
+ cl_assert(entry->mode != GIT_FILEMODE_TREE);
+ }
+
+ if (entry->mode == GIT_FILEMODE_TREE) {
+ error = git_iterator_advance_into(&entry, i);
+
+ /* could return NOTFOUND if directory is empty */
+ cl_assert(!error || error == GIT_ENOTFOUND);
+
+ if (error == GIT_ENOTFOUND) {
+ error = git_iterator_advance(&entry, i);
+ cl_assert(!error || error == GIT_ITEROVER);
+ }
+ } else {
+ error = git_iterator_advance(&entry, i);
+ cl_assert(!error || error == GIT_ITEROVER);
+ }
+
+ if (++count >= expected_total)
+ break;
+ }
+
+ assert_at_end(i, v);
+ cl_assert_equal_i(expected_total, count);
+}
+
+
+void expect_advance_over(
+ git_iterator *i,
+ const char *expected_path,
+ git_iterator_status_t expected_status)
+{
+ const git_index_entry *entry;
+ git_iterator_status_t status;
+ int error;
+
+ cl_git_pass(git_iterator_current(&entry, i));
+ cl_assert_equal_s(expected_path, entry->path);
+
+ error = git_iterator_advance_over(&entry, &status, i);
+ cl_assert(!error || error == GIT_ITEROVER);
+ cl_assert_equal_i(expected_status, status);
+}
+
+void expect_advance_into(
+ git_iterator *i,
+ const char *expected_path)
+{
+ const git_index_entry *entry;
+ int error;
+
+ cl_git_pass(git_iterator_current(&entry, i));
+ cl_assert_equal_s(expected_path, entry->path);
+
+ if (S_ISDIR(entry->mode))
+ error = git_iterator_advance_into(&entry, i);
+ else
+ error = git_iterator_advance(&entry, i);
+
+ cl_assert(!error || error == GIT_ITEROVER);
+}
+
diff --git a/tests/iterator/iterator_helpers.h b/tests/iterator/iterator_helpers.h
new file mode 100644
index 000000000..8d0a17014
--- /dev/null
+++ b/tests/iterator/iterator_helpers.h
@@ -0,0 +1,16 @@
+
+extern void expect_iterator_items(
+ git_iterator *i,
+ int expected_flat,
+ const char **expected_flat_paths,
+ int expected_total,
+ const char **expected_total_paths);
+
+extern void expect_advance_over(
+ git_iterator *i,
+ const char *expected_path,
+ git_iterator_status_t expected_status);
+
+void expect_advance_into(
+ git_iterator *i,
+ const char *expected_path);
diff --git a/tests/iterator/tree.c b/tests/iterator/tree.c
new file mode 100644
index 000000000..b4d0f40f3
--- /dev/null
+++ b/tests/iterator/tree.c
@@ -0,0 +1,1076 @@
+#include "clar_libgit2.h"
+#include "iterator.h"
+#include "repository.h"
+#include "fileops.h"
+#include "tree.h"
+#include "../submodule/submodule_helpers.h"
+#include "../diff/diff_helpers.h"
+#include "iterator_helpers.h"
+#include <stdarg.h>
+
+static git_repository *g_repo;
+
+void test_iterator_tree__initialize(void)
+{
+}
+
+void test_iterator_tree__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+static void tree_iterator_test(
+ const char *sandbox,
+ const char *treeish,
+ const char *start,
+ const char *end,
+ int expected_count,
+ const char **expected_values)
+{
+ git_tree *t;
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ const git_index_entry *entry;
+ int error, count = 0, count_post_reset = 0;
+
+ g_repo = cl_git_sandbox_init(sandbox);
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+ i_opts.start = start;
+ i_opts.end = end;
+
+ cl_assert(t = resolve_commit_oid_to_tree(g_repo, treeish));
+ cl_git_pass(git_iterator_for_tree(&i, t, &i_opts));
+
+ /* test loop */
+ while (!(error = git_iterator_advance(&entry, i))) {
+ cl_assert(entry);
+ if (expected_values != NULL)
+ cl_assert_equal_s(expected_values[count], entry->path);
+ count++;
+ }
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert(!entry);
+ cl_assert_equal_i(expected_count, count);
+
+ /* test reset */
+ cl_git_pass(git_iterator_reset(i));
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ cl_assert(entry);
+ if (expected_values != NULL)
+ cl_assert_equal_s(expected_values[count_post_reset], entry->path);
+ count_post_reset++;
+ }
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert(!entry);
+ cl_assert_equal_i(count, count_post_reset);
+
+ git_iterator_free(i);
+ 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_iterator_tree__0(void)
+{
+ tree_iterator_test("attr", "605812a", NULL, NULL, 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_iterator_tree__1(void)
+{
+ tree_iterator_test("attr", "6bab5c79cd5", NULL, NULL, 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_iterator_tree__2(void)
+{
+ tree_iterator_test("status", "26a125ee1", NULL, NULL, 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_iterator_tree__3(void)
+{
+ tree_iterator_test("status", "0017bd4ab1e", NULL, NULL, 8, expected_tree_3);
+}
+
+/* $ git ls-tree -r --name-only 24fa9a9fc4e202313e24b648087495441dab432b */
+const char *expected_tree_4[] = {
+ "attr0",
+ "attr1",
+ "attr2",
+ "attr3",
+ "binfile",
+ "gitattributes",
+ "macro_bad",
+ "macro_test",
+ "root_test1",
+ "root_test2",
+ "root_test3",
+ "root_test4.txt",
+ "sub/abc",
+ "sub/file",
+ "sub/sub/file",
+ "sub/sub/subsub.txt",
+ "sub/subdir_test1",
+ "sub/subdir_test2.txt",
+ "subdir/.gitattributes",
+ "subdir/abc",
+ "subdir/subdir_test1",
+ "subdir/subdir_test2.txt",
+ "subdir2/subdir2_test1",
+ NULL
+};
+
+void test_iterator_tree__4(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b", NULL, NULL,
+ 23, expected_tree_4);
+}
+
+void test_iterator_tree__4_ranged(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "sub", "sub",
+ 11, &expected_tree_4[12]);
+}
+
+const char *expected_tree_ranged_0[] = {
+ "gitattributes",
+ "macro_bad",
+ "macro_test",
+ "root_test1",
+ "root_test2",
+ "root_test3",
+ "root_test4.txt",
+ NULL
+};
+
+void test_iterator_tree__ranged_0(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "git", "root",
+ 7, expected_tree_ranged_0);
+}
+
+const char *expected_tree_ranged_1[] = {
+ "sub/subdir_test2.txt",
+ NULL
+};
+
+void test_iterator_tree__ranged_1(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "sub/subdir_test2.txt", "sub/subdir_test2.txt",
+ 1, expected_tree_ranged_1);
+}
+
+void test_iterator_tree__range_empty_0(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "empty", "empty", 0, NULL);
+}
+
+void test_iterator_tree__range_empty_1(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ "z_empty_after", NULL, 0, NULL);
+}
+
+void test_iterator_tree__range_empty_2(void)
+{
+ tree_iterator_test(
+ "attr", "24fa9a9fc4e202313e24b648087495441dab432b",
+ NULL, ".aaa_empty_before", 0, NULL);
+}
+
+static void check_tree_entry(
+ git_iterator *i,
+ const char *oid,
+ const char *oid_p,
+ const char *oid_pp,
+ const char *oid_ppp)
+{
+ const git_index_entry *ie;
+ const git_tree_entry *te;
+ const git_tree *tree;
+
+ cl_git_pass(git_iterator_current_tree_entry(&te, i));
+ cl_assert(te);
+ cl_assert(git_oid_streq(te->oid, oid) == 0);
+
+ cl_git_pass(git_iterator_current(&ie, i));
+
+ if (oid_p) {
+ cl_git_pass(git_iterator_current_parent_tree(&tree, i, 0));
+ cl_assert(tree);
+ cl_assert(git_oid_streq(git_tree_id(tree), oid_p) == 0);
+ }
+
+ if (oid_pp) {
+ cl_git_pass(git_iterator_current_parent_tree(&tree, i, 1));
+ cl_assert(tree);
+ cl_assert(git_oid_streq(git_tree_id(tree), oid_pp) == 0);
+ }
+
+ if (oid_ppp) {
+ cl_git_pass(git_iterator_current_parent_tree(&tree, i, 2));
+ cl_assert(tree);
+ cl_assert(git_oid_streq(git_tree_id(tree), oid_ppp) == 0);
+ }
+}
+
+void test_iterator_tree__special_functions(void)
+{
+ git_tree *t;
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ const git_index_entry *entry;
+ int error, cases = 0;
+ const char *rootoid = "ce39a97a7fb1fa90bcf5e711249c1e507476ae0e";
+
+ g_repo = cl_git_sandbox_init("attr");
+
+ t = resolve_commit_oid_to_tree(
+ g_repo, "24fa9a9fc4e202313e24b648087495441dab432b");
+ cl_assert(t != NULL);
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_tree(&i, t, &i_opts));
+
+ while (!(error = git_iterator_advance(&entry, i))) {
+ cl_assert(entry);
+
+ if (strcmp(entry->path, "sub/file") == 0) {
+ cases++;
+ check_tree_entry(
+ i, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
+ "ecb97df2a174987475ac816e3847fc8e9f6c596b",
+ rootoid, NULL);
+ }
+ else if (strcmp(entry->path, "sub/sub/subsub.txt") == 0) {
+ cases++;
+ check_tree_entry(
+ i, "9e5bdc47d6a80f2be0ea3049ad74231b94609242",
+ "4e49ba8c5b6c32ff28cd9dcb60be34df50fcc485",
+ "ecb97df2a174987475ac816e3847fc8e9f6c596b", rootoid);
+ }
+ else if (strcmp(entry->path, "subdir/.gitattributes") == 0) {
+ cases++;
+ check_tree_entry(
+ i, "99eae476896f4907224978b88e5ecaa6c5bb67a9",
+ "9fb40b6675dde60b5697afceae91b66d908c02d9",
+ rootoid, NULL);
+ }
+ else if (strcmp(entry->path, "subdir2/subdir2_test1") == 0) {
+ cases++;
+ check_tree_entry(
+ i, "dccada462d3df8ac6de596fb8c896aba9344f941",
+ "2929de282ce999e95183aedac6451d3384559c4b",
+ rootoid, NULL);
+ }
+ }
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert(!entry);
+ cl_assert_equal_i(4, cases);
+
+ git_iterator_free(i);
+ git_tree_free(t);
+}
+
+static void check_tree_range(
+ git_repository *repo,
+ const char *start,
+ const char *end,
+ bool ignore_case,
+ int expected_count)
+{
+ git_tree *head;
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ int error, count;
+
+ i_opts.flags = ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;
+ i_opts.start = start;
+ i_opts.end = end;
+
+ cl_git_pass(git_repository_head_tree(&head, repo));
+
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+
+ for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count)
+ /* count em up */;
+
+ cl_assert_equal_i(GIT_ITEROVER, error);
+ cl_assert_equal_i(expected_count, count);
+
+ git_iterator_free(i);
+ git_tree_free(head);
+}
+
+void test_iterator_tree__range_icase(void)
+{
+ g_repo = cl_git_sandbox_init("testrepo");
+
+ check_tree_range(g_repo, "B", "C", false, 0);
+ check_tree_range(g_repo, "B", "C", true, 1);
+ check_tree_range(g_repo, "b", "c", false, 1);
+ check_tree_range(g_repo, "b", "c", true, 1);
+
+ check_tree_range(g_repo, "a", "z", false, 3);
+ check_tree_range(g_repo, "a", "z", true, 4);
+ check_tree_range(g_repo, "A", "Z", false, 1);
+ check_tree_range(g_repo, "A", "Z", true, 4);
+ check_tree_range(g_repo, "a", "Z", false, 0);
+ check_tree_range(g_repo, "a", "Z", true, 4);
+ check_tree_range(g_repo, "A", "z", false, 4);
+ check_tree_range(g_repo, "A", "z", true, 4);
+
+ check_tree_range(g_repo, "new.txt", "new.txt", true, 1);
+ check_tree_range(g_repo, "new.txt", "new.txt", false, 1);
+ check_tree_range(g_repo, "README", "README", true, 1);
+ check_tree_range(g_repo, "README", "README", false, 1);
+}
+
+void test_iterator_tree__icase_0(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_tree *head;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_tree(&i, head, NULL));
+ expect_iterator_items(i, 20, NULL, 20, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
+
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 22, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 12, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ git_tree_free(head);
+}
+
+void test_iterator_tree__icase_1(void)
+{
+ git_iterator *i;
+ git_tree *head;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ /* auto expand with no tree entries */
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 7, NULL, 7, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 3, NULL, 3, NULL);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+
+ /* auto expand with tree entries */
+ i_opts.start = "c";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 8, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 4, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 5, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 1, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with no tree entries */
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 13, NULL, 13, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 5, NULL, 5, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 14, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 6, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 9, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 1, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ git_tree_free(head);
+}
+
+void test_iterator_tree__icase_2(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_tree *head;
+ static const char *expect_basic[] = {
+ "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,
+ };
+ static const char *expect_trees[] = {
+ "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/",
+ "subdir/current_file",
+ "subdir/deleted_file",
+ "subdir/modified_file",
+ NULL,
+ };
+ static const char *expect_noauto[] = {
+ "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/",
+ NULL
+ };
+
+ g_repo = cl_git_sandbox_init("status");
+
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_tree(&i, head, NULL));
+ expect_iterator_items(i, 12, expect_basic, 12, expect_basic);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
+
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 13, expect_trees, 13, expect_trees);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
+ expect_iterator_items(i, 10, expect_noauto, 13, expect_trees);
+ git_iterator_free(i);
+
+ git_tree_free(head);
+}
+
+/* "b=name,t=name", blob_id, tree_id */
+static void build_test_tree(
+ git_oid *out, git_repository *repo, const char *fmt, ...)
+{
+ git_oid *id;
+ git_treebuilder *builder;
+ const char *scan = fmt, *next;
+ char type, delimiter;
+ git_filemode_t mode = GIT_FILEMODE_BLOB;
+ git_buf name = GIT_BUF_INIT;
+ va_list arglist;
+
+ cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); /* start builder */
+
+ va_start(arglist, fmt);
+ while (*scan) {
+ switch (type = *scan++) {
+ case 't': case 'T': mode = GIT_FILEMODE_TREE; break;
+ case 'b': case 'B': mode = GIT_FILEMODE_BLOB; break;
+ default:
+ cl_assert(type == 't' || type == 'T' || type == 'b' || type == 'B');
+ }
+
+ delimiter = *scan++; /* read and skip delimiter */
+ for (next = scan; *next && *next != delimiter; ++next)
+ /* seek end */;
+ cl_git_pass(git_buf_set(&name, scan, (size_t)(next - scan)));
+ for (scan = next; *scan && (*scan == delimiter || *scan == ','); ++scan)
+ /* skip delimiter and optional comma */;
+
+ id = va_arg(arglist, git_oid *);
+
+ cl_git_pass(git_treebuilder_insert(NULL, builder, name.ptr, id, mode));
+ }
+ va_end(arglist);
+
+ cl_git_pass(git_treebuilder_write(out, builder));
+
+ git_treebuilder_free(builder);
+ git_buf_free(&name);
+}
+
+void test_iterator_tree__case_conflicts_0(void)
+{
+ const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6";
+ git_tree *tree;
+ git_oid blob_id, biga_id, littlea_id, tree_id;
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ const char *expect_cs[] = {
+ "A/1.file", "A/3.file", "a/2.file", "a/4.file" };
+ const char *expect_ci[] = {
+ "A/1.file", "a/2.file", "A/3.file", "a/4.file" };
+ const char *expect_cs_trees[] = {
+ "A/", "A/1.file", "A/3.file", "a/", "a/2.file", "a/4.file" };
+ const char *expect_ci_trees[] = {
+ "A/", "A/1.file", "a/2.file", "A/3.file", "a/4.file" };
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */
+
+ /* create tree with: A/1.file, A/3.file, a/2.file, a/4.file */
+ build_test_tree(
+ &biga_id, g_repo, "b|1.file|,b|3.file|", &blob_id, &blob_id);
+ build_test_tree(
+ &littlea_id, g_repo, "b|2.file|,b|4.file|", &blob_id, &blob_id);
+ build_test_tree(
+ &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 4, expect_cs, 4, expect_cs);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 4, expect_ci, 4, expect_ci);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 6, expect_cs_trees, 6, expect_cs_trees);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 5, expect_ci_trees, 5, expect_ci_trees);
+ git_iterator_free(i);
+
+ git_tree_free(tree);
+}
+
+void test_iterator_tree__case_conflicts_1(void)
+{
+ const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6";
+ git_tree *tree;
+ git_oid blob_id, Ab_id, biga_id, littlea_id, tree_id;
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ const char *expect_cs[] = {
+ "A/a", "A/b/1", "A/c", "a/C", "a/a", "a/b" };
+ const char *expect_ci[] = {
+ "A/a", "a/b", "A/b/1", "A/c" };
+ const char *expect_cs_trees[] = {
+ "A/", "A/a", "A/b/", "A/b/1", "A/c", "a/", "a/C", "a/a", "a/b" };
+ const char *expect_ci_trees[] = {
+ "A/", "A/a", "a/b", "A/b/", "A/b/1", "A/c" };
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */
+
+ /* create: A/a A/b/1 A/c a/a a/b a/C */
+ build_test_tree(&Ab_id, g_repo, "b|1|", &blob_id);
+ build_test_tree(
+ &biga_id, g_repo, "b|a|,t|b|,b|c|", &blob_id, &Ab_id, &blob_id);
+ build_test_tree(
+ &littlea_id, g_repo, "b|a|,b|b|,b|C|", &blob_id, &blob_id, &blob_id);
+ build_test_tree(
+ &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 6, expect_cs, 6, expect_cs);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 4, expect_ci, 4, expect_ci);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 9, expect_cs_trees, 9, expect_cs_trees);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 6, expect_ci_trees, 6, expect_ci_trees);
+ git_iterator_free(i);
+
+ git_tree_free(tree);
+}
+
+void test_iterator_tree__case_conflicts_2(void)
+{
+ const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6";
+ git_tree *tree;
+ git_oid blob_id, d1, d2, c1, c2, b1, b2, a1, a2, tree_id;
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ const char *expect_cs[] = {
+ "A/B/C/D/16", "A/B/C/D/foo", "A/B/C/d/15", "A/B/C/d/FOO",
+ "A/B/c/D/14", "A/B/c/D/foo", "A/B/c/d/13", "A/B/c/d/FOO",
+ "A/b/C/D/12", "A/b/C/D/foo", "A/b/C/d/11", "A/b/C/d/FOO",
+ "A/b/c/D/10", "A/b/c/D/foo", "A/b/c/d/09", "A/b/c/d/FOO",
+ "a/B/C/D/08", "a/B/C/D/foo", "a/B/C/d/07", "a/B/C/d/FOO",
+ "a/B/c/D/06", "a/B/c/D/foo", "a/B/c/d/05", "a/B/c/d/FOO",
+ "a/b/C/D/04", "a/b/C/D/foo", "a/b/C/d/03", "a/b/C/d/FOO",
+ "a/b/c/D/02", "a/b/c/D/foo", "a/b/c/d/01", "a/b/c/d/FOO", };
+ const char *expect_ci[] = {
+ "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04",
+ "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08",
+ "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12",
+ "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16",
+ "A/B/C/D/foo", };
+ const char *expect_ci_trees[] = {
+ "A/", "A/B/", "A/B/C/", "A/B/C/D/",
+ "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04",
+ "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08",
+ "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12",
+ "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16",
+ "A/B/C/D/foo", };
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */
+
+ build_test_tree(&d1, g_repo, "b|16|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|15|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&d1, g_repo, "b|14|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|13|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2);
+
+ build_test_tree(&d1, g_repo, "b|12|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|11|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&d1, g_repo, "b|10|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|09|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2);
+
+ build_test_tree(&a1, g_repo, "t|B|,t|b|", &b1, &b2);
+
+ build_test_tree(&d1, g_repo, "b|08|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|07|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&d1, g_repo, "b|06|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|05|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2);
+
+ build_test_tree(&d1, g_repo, "b|04|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|03|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&d1, g_repo, "b|02|,b|foo|", &blob_id, &blob_id);
+ build_test_tree(&d2, g_repo, "b|01|,b|FOO|", &blob_id, &blob_id);
+ build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
+ build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2);
+
+ build_test_tree(&a2, g_repo, "t|B|,t|b|", &b1, &b2);
+
+ build_test_tree(&tree_id, g_repo, "t/A/,t/a/", &a1, &a2);
+
+ cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 32, expect_cs, 32, expect_cs);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 17, expect_ci, 17, expect_ci);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 21, expect_ci_trees, 21, expect_ci_trees);
+ git_iterator_free(i);
+
+ git_tree_free(tree);
+}
+
+void test_iterator_tree__pathlist(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+ git_tree *tree;
+ bool default_icase;
+ int expect;
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "a"));
+ cl_git_pass(git_vector_insert(&filelist, "B"));
+ cl_git_pass(git_vector_insert(&filelist, "c"));
+ cl_git_pass(git_vector_insert(&filelist, "D"));
+ cl_git_pass(git_vector_insert(&filelist, "e"));
+ cl_git_pass(git_vector_insert(&filelist, "k.a"));
+ cl_git_pass(git_vector_insert(&filelist, "k.b"));
+ cl_git_pass(git_vector_insert(&filelist, "k/1"));
+ cl_git_pass(git_vector_insert(&filelist, "k/a"));
+ cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ"));
+ cl_git_pass(git_vector_insert(&filelist, "L/1"));
+
+ g_repo = cl_git_sandbox_init("icase");
+ git_repository_head_tree(&tree, g_repo);
+
+ /* All indexfilelist iterator tests are "autoexpand with no tree entries" */
+ /* In this test we DO NOT force a case on the iterators and verify default behavior. */
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 8, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "c";
+ i_opts.end = NULL;
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ default_icase = git_iterator_ignore_case(i);
+ /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */
+ expect = ((default_icase) ? 6 : 4);
+ expect_iterator_items(i, expect, NULL, expect, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = NULL;
+ i_opts.end = "e";
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ default_icase = git_iterator_ignore_case(i);
+ /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */
+ expect = ((default_icase) ? 5 : 6);
+ expect_iterator_items(i, expect, NULL, expect, NULL);
+ git_iterator_free(i);
+
+ git_vector_free(&filelist);
+ git_tree_free(tree);
+}
+
+void test_iterator_tree__pathlist_icase(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+ git_tree *tree;
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "a"));
+ cl_git_pass(git_vector_insert(&filelist, "B"));
+ cl_git_pass(git_vector_insert(&filelist, "c"));
+ cl_git_pass(git_vector_insert(&filelist, "D"));
+ cl_git_pass(git_vector_insert(&filelist, "e"));
+ cl_git_pass(git_vector_insert(&filelist, "k.a"));
+ cl_git_pass(git_vector_insert(&filelist, "k.b"));
+ cl_git_pass(git_vector_insert(&filelist, "k/1"));
+ cl_git_pass(git_vector_insert(&filelist, "k/a"));
+ cl_git_pass(git_vector_insert(&filelist, "kZZZZ"));
+ cl_git_pass(git_vector_insert(&filelist, "L/1"));
+
+ g_repo = cl_git_sandbox_init("icase");
+ git_repository_head_tree(&tree, g_repo);
+
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 3, NULL, 3, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 1, NULL, 1, NULL);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 5, NULL, 5, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, 2, NULL, 2, NULL);
+ git_iterator_free(i);
+
+ git_vector_free(&filelist);
+ git_tree_free(tree);
+}
+
+void test_iterator_tree__pathlist_with_directory(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+ git_tree *tree;
+
+ const char *expected[] = { "subdir/README", "subdir/new.txt",
+ "subdir/subdir2/README", "subdir/subdir2/new.txt" };
+ size_t expected_len = 4;
+
+ const char *expected2[] = { "subdir/subdir2/README", "subdir/subdir2/new.txt" };
+ size_t expected_len2 = 2;
+
+ g_repo = cl_git_sandbox_init("testrepo2");
+ git_repository_head_tree(&tree, g_repo);
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "subdir"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "subdir/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "subdir/subdir2"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, expected_len2, expected2, expected_len2, expected2);
+ git_iterator_free(i);
+
+ git_vector_free(&filelist);
+}
+
+void test_iterator_tree__pathlist_with_directory_include_tree_nodes(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+ git_tree *tree;
+
+ const char *expected[] = { "subdir/", "subdir/README", "subdir/new.txt",
+ "subdir/subdir2/", "subdir/subdir2/README", "subdir/subdir2/new.txt" };
+ size_t expected_len = 6;
+
+ g_repo = cl_git_sandbox_init("testrepo2");
+ git_repository_head_tree(&tree, g_repo);
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "subdir"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+
+ git_vector_free(&filelist);
+}
+
+void test_iterator_tree__pathlist_no_match(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+ git_tree *tree;
+ const git_index_entry *entry;
+
+ g_repo = cl_git_sandbox_init("testrepo2");
+ git_repository_head_tree(&tree, g_repo);
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "nonexistent/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
+ cl_assert_equal_i(GIT_ITEROVER, git_iterator_current(&entry, i));
+
+ git_vector_free(&filelist);
+}
+
diff --git a/tests/iterator/workdir.c b/tests/iterator/workdir.c
new file mode 100644
index 000000000..3abaee65c
--- /dev/null
+++ b/tests/iterator/workdir.c
@@ -0,0 +1,1458 @@
+#include "clar_libgit2.h"
+#include "iterator.h"
+#include "repository.h"
+#include "fileops.h"
+#include "../submodule/submodule_helpers.h"
+#include "iterator_helpers.h"
+#include <stdarg.h>
+
+static git_repository *g_repo;
+
+void test_iterator_workdir__initialize(void)
+{
+}
+
+void test_iterator_workdir__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ g_repo = NULL;
+}
+
+static void workdir_iterator_test(
+ const char *sandbox,
+ const char *start,
+ const char *end,
+ int expected_count,
+ int expected_ignores,
+ const char **expected_names,
+ const char *an_ignored_name)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ const git_index_entry *entry;
+ int error, count = 0, count_all = 0, count_all_post_reset = 0;
+
+ g_repo = cl_git_sandbox_init(sandbox);
+
+ i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
+ i_opts.start = start;
+ i_opts.end = end;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+
+ error = git_iterator_current(&entry, i);
+ cl_assert((error == 0 && entry != NULL) ||
+ (error == GIT_ITEROVER && entry == NULL));
+
+ while (entry != NULL) {
+ int ignored = git_iterator_current_is_ignored(i);
+
+ if (S_ISDIR(entry->mode)) {
+ cl_git_pass(git_iterator_advance_into(&entry, i));
+ continue;
+ }
+
+ if (expected_names != NULL)
+ cl_assert_equal_s(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++;
+
+ error = git_iterator_advance(&entry, i);
+
+ cl_assert((error == 0 && entry != NULL) ||
+ (error == GIT_ITEROVER && entry == NULL));
+ }
+
+ cl_assert_equal_i(expected_count, count);
+ cl_assert_equal_i(expected_count + expected_ignores, count_all);
+
+ cl_git_pass(git_iterator_reset(i));
+
+ error = git_iterator_current(&entry, i);
+ cl_assert((error == 0 && entry != NULL) ||
+ (error == GIT_ITEROVER && entry == NULL));
+
+ while (entry != NULL) {
+ if (S_ISDIR(entry->mode)) {
+ cl_git_pass(git_iterator_advance_into(&entry, i));
+ continue;
+ }
+
+ if (expected_names != NULL)
+ cl_assert_equal_s(
+ expected_names[count_all_post_reset], entry->path);
+ count_all_post_reset++;
+
+ error = git_iterator_advance(&entry, i);
+ cl_assert(error == 0 || error == GIT_ITEROVER);
+ }
+
+ cl_assert_equal_i(count_all, count_all_post_reset);
+
+ git_iterator_free(i);
+}
+
+void test_iterator_workdir__0(void)
+{
+ workdir_iterator_test("attr", NULL, NULL, 23, 5, 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.txt",
+ "subdir/current_file",
+ "subdir/modified_file",
+ "subdir/new_file",
+ "\xe8\xbf\x99",
+ NULL
+};
+
+void test_iterator_workdir__1(void)
+{
+ workdir_iterator_test(
+ "status", NULL, NULL, 13, 1, status_paths, "ignored_file");
+}
+
+static const char *status_paths_range_0[] = {
+ "staged_changes",
+ "staged_changes_modified_file",
+ "staged_delete_modified_file",
+ "staged_new_file",
+ "staged_new_file_modified_file",
+ NULL
+};
+
+void test_iterator_workdir__1_ranged_0(void)
+{
+ workdir_iterator_test(
+ "status", "staged", "staged", 5, 0, status_paths_range_0, NULL);
+}
+
+static const char *status_paths_range_1[] = {
+ "modified_file", NULL
+};
+
+void test_iterator_workdir__1_ranged_1(void)
+{
+ workdir_iterator_test(
+ "status", "modified_file", "modified_file",
+ 1, 0, status_paths_range_1, NULL);
+}
+
+static const char *status_paths_range_3[] = {
+ "subdir.txt",
+ "subdir/current_file",
+ "subdir/modified_file",
+ NULL
+};
+
+void test_iterator_workdir__1_ranged_3(void)
+{
+ workdir_iterator_test(
+ "status", "subdir", "subdir/modified_file",
+ 3, 0, status_paths_range_3, NULL);
+}
+
+static const char *status_paths_range_4[] = {
+ "subdir/current_file",
+ "subdir/modified_file",
+ "subdir/new_file",
+ "\xe8\xbf\x99",
+ NULL
+};
+
+void test_iterator_workdir__1_ranged_4(void)
+{
+ workdir_iterator_test(
+ "status", "subdir/", NULL, 4, 0, status_paths_range_4, NULL);
+}
+
+static const char *status_paths_range_5[] = {
+ "subdir/modified_file",
+ NULL
+};
+
+void test_iterator_workdir__1_ranged_5(void)
+{
+ workdir_iterator_test(
+ "status", "subdir/modified_file", "subdir/modified_file",
+ 1, 0, status_paths_range_5, NULL);
+}
+
+void test_iterator_workdir__1_ranged_5_1_ranged_empty_0(void)
+{
+ workdir_iterator_test(
+ "status", "\xff_does_not_exist", NULL,
+ 0, 0, NULL, NULL);
+}
+
+void test_iterator_workdir__1_ranged_empty_1(void)
+{
+ workdir_iterator_test(
+ "status", "empty", "empty",
+ 0, 0, NULL, NULL);
+}
+
+void test_iterator_workdir__1_ranged_empty_2(void)
+{
+ workdir_iterator_test(
+ "status", NULL, "aaaa_empty_before",
+ 0, 0, NULL, NULL);
+}
+
+void test_iterator_workdir__builtin_ignores(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ const git_index_entry *entry;
+ int idx;
+ static struct {
+ const char *path;
+ bool ignored;
+ } expected[] = {
+ { "dir/", true },
+ { "file", false },
+ { "ign", true },
+ { "macro_bad", false },
+ { "macro_test", false },
+ { "root_test1", false },
+ { "root_test2", false },
+ { "root_test3", false },
+ { "root_test4.txt", false },
+ { "sub/", false },
+ { "sub/.gitattributes", false },
+ { "sub/abc", false },
+ { "sub/dir/", true },
+ { "sub/file", false },
+ { "sub/ign/", true },
+ { "sub/sub/", false },
+ { "sub/sub/.gitattributes", false },
+ { "sub/sub/dir", false }, /* file is not actually a dir */
+ { "sub/sub/file", false },
+ { NULL, false }
+ };
+
+ g_repo = cl_git_sandbox_init("attr");
+
+ cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777));
+ cl_git_mkfile("attr/sub/.git", "whatever");
+
+ i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
+ i_opts.start = "dir";
+ i_opts.end = "sub/sub/file";
+
+ cl_git_pass(git_iterator_for_workdir(
+ &i, g_repo, NULL, NULL, &i_opts));
+ cl_git_pass(git_iterator_current(&entry, i));
+
+ for (idx = 0; entry != NULL; ++idx) {
+ int ignored = git_iterator_current_is_ignored(i);
+
+ cl_assert_equal_s(expected[idx].path, entry->path);
+ cl_assert_(ignored == expected[idx].ignored, expected[idx].path);
+
+ if (!ignored &&
+ (entry->mode == GIT_FILEMODE_TREE ||
+ entry->mode == GIT_FILEMODE_COMMIT))
+ {
+ /* it is possible to advance "into" a submodule */
+ cl_git_pass(git_iterator_advance_into(&entry, i));
+ } else {
+ int error = git_iterator_advance(&entry, i);
+ cl_assert(!error || error == GIT_ITEROVER);
+ }
+ }
+
+ cl_assert(expected[idx].path == NULL);
+
+ git_iterator_free(i);
+}
+
+static void check_wd_first_through_third_range(
+ git_repository *repo, const char *start, const char *end)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ const git_index_entry *entry;
+ int error, idx;
+ static const char *expected[] = { "FIRST", "second", "THIRD", NULL };
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+ i_opts.start = start;
+ i_opts.end = end;
+
+ cl_git_pass(git_iterator_for_workdir(
+ &i, repo, NULL, NULL, &i_opts));
+ cl_git_pass(git_iterator_current(&entry, i));
+
+ for (idx = 0; entry != NULL; ++idx) {
+ cl_assert_equal_s(expected[idx], entry->path);
+
+ error = git_iterator_advance(&entry, i);
+ cl_assert(!error || error == GIT_ITEROVER);
+ }
+
+ cl_assert(expected[idx] == NULL);
+
+ git_iterator_free(i);
+}
+
+void test_iterator_workdir__handles_icase_range(void)
+{
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+ cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt");
+
+ cl_git_mkfile("empty_standard_repo/before", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/FIRST", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/second", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/THIRD", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/zafter", "whatever\n");
+ cl_git_mkfile("empty_standard_repo/Zlast", "whatever\n");
+
+ check_wd_first_through_third_range(g_repo, "first", "third");
+ check_wd_first_through_third_range(g_repo, "FIRST", "THIRD");
+ check_wd_first_through_third_range(g_repo, "first", "THIRD");
+ check_wd_first_through_third_range(g_repo, "FIRST", "third");
+ check_wd_first_through_third_range(g_repo, "FirSt", "tHiRd");
+}
+
+/*
+ * The workdir iterator is like the filesystem iterator, but honors
+ * special git type constructs (ignores, submodules, etc).
+ */
+
+void test_iterator_workdir__icase(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 20, NULL, 20, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 22, NULL, 22, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 12, NULL, 22, NULL);
+ git_iterator_free(i);
+}
+
+void test_iterator_workdir__icase_starts_and_ends(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ /* auto expand with no tree entries */
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 7, NULL, 7, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 3, NULL, 3, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 8, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 4, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 5, NULL, 8, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 1, NULL, 4, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with no tree entries */
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 13, NULL, 13, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 5, NULL, 5, NULL);
+ git_iterator_free(i);
+
+ /* auto expand with tree entries */
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 14, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 6, NULL, 6, NULL);
+ git_iterator_free(i);
+
+ /* no auto expand (implies trees included) */
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 9, NULL, 14, NULL);
+ git_iterator_free(i);
+
+ i_opts.start = "k";
+ i_opts.end = "k/Z";
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, 1, NULL, 6, NULL);
+ git_iterator_free(i);
+}
+
+static void build_workdir_tree(const char *root, int dirs, int subs)
+{
+ int i, j;
+ char buf[64], sub[64];
+
+ for (i = 0; i < dirs; ++i) {
+ if (i % 2 == 0) {
+ p_snprintf(buf, sizeof(buf), "%s/dir%02d", root, i);
+ cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH));
+
+ p_snprintf(buf, sizeof(buf), "%s/dir%02d/file", root, i);
+ cl_git_mkfile(buf, buf);
+ buf[strlen(buf) - 5] = '\0';
+ } else {
+ p_snprintf(buf, sizeof(buf), "%s/DIR%02d", root, i);
+ cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH));
+ }
+
+ for (j = 0; j < subs; ++j) {
+ switch (j % 4) {
+ case 0: p_snprintf(sub, sizeof(sub), "%s/sub%02d", buf, j); break;
+ case 1: p_snprintf(sub, sizeof(sub), "%s/sUB%02d", buf, j); break;
+ case 2: p_snprintf(sub, sizeof(sub), "%s/Sub%02d", buf, j); break;
+ case 3: p_snprintf(sub, sizeof(sub), "%s/SUB%02d", buf, j); break;
+ }
+ cl_git_pass(git_futils_mkdir(sub, 0775, GIT_MKDIR_PATH));
+
+ if (j % 2 == 0) {
+ size_t sublen = strlen(sub);
+ memcpy(&sub[sublen], "/file", sizeof("/file"));
+ cl_git_mkfile(sub, sub);
+ sub[sublen] = '\0';
+ }
+ }
+ }
+}
+
+void test_iterator_workdir__depth(void)
+{
+ git_iterator *iter;
+ git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ build_workdir_tree("icase", 10, 10);
+ build_workdir_tree("icase/DIR01/sUB01", 50, 0);
+ build_workdir_tree("icase/dir02/sUB01", 50, 0);
+
+ /* auto expand with no tree entries */
+ cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts));
+ expect_iterator_items(iter, 125, NULL, 125, NULL);
+ git_iterator_free(iter);
+
+ /* auto expand with tree entries (empty dirs silently skipped) */
+ iter_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts));
+ expect_iterator_items(iter, 337, NULL, 337, NULL);
+ git_iterator_free(iter);
+}
+
+/* The filesystem iterator is a workdir iterator without any special
+ * workdir handling capabilities (ignores, submodules, etc).
+ */
+void test_iterator_workdir__filesystem(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ static const char *expect_base[] = {
+ "DIR01/Sub02/file",
+ "DIR01/sub00/file",
+ "current_file",
+ "dir00/Sub02/file",
+ "dir00/file",
+ "dir00/sub00/file",
+ "modified_file",
+ "new_file",
+ NULL,
+ };
+ static const char *expect_trees[] = {
+ "DIR01/",
+ "DIR01/SUB03/",
+ "DIR01/Sub02/",
+ "DIR01/Sub02/file",
+ "DIR01/sUB01/",
+ "DIR01/sub00/",
+ "DIR01/sub00/file",
+ "current_file",
+ "dir00/",
+ "dir00/SUB03/",
+ "dir00/Sub02/",
+ "dir00/Sub02/file",
+ "dir00/file",
+ "dir00/sUB01/",
+ "dir00/sub00/",
+ "dir00/sub00/file",
+ "modified_file",
+ "new_file",
+ NULL,
+ };
+ static const char *expect_noauto[] = {
+ "DIR01/",
+ "current_file",
+ "dir00/",
+ "modified_file",
+ "new_file",
+ NULL,
+ };
+
+ g_repo = cl_git_sandbox_init("status");
+
+ build_workdir_tree("status/subdir", 2, 4);
+
+ cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", NULL));
+ expect_iterator_items(i, 8, expect_base, 8, expect_base);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
+ expect_iterator_items(i, 18, expect_trees, 18, expect_trees);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
+ cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
+ expect_iterator_items(i, 5, expect_noauto, 18, expect_trees);
+ git_iterator_free(i);
+
+ git__tsort((void **)expect_base, 8, (git__tsort_cmp)git__strcasecmp);
+ git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp);
+ git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+ cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
+ expect_iterator_items(i, 8, expect_base, 8, expect_base);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+ cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
+ expect_iterator_items(i, 18, expect_trees, 18, expect_trees);
+ git_iterator_free(i);
+
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
+ cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
+ expect_iterator_items(i, 5, expect_noauto, 18, expect_trees);
+ git_iterator_free(i);
+}
+
+void test_iterator_workdir__filesystem2(void)
+{
+ git_iterator *i;
+ static const char *expect_base[] = {
+ "heads/br2",
+ "heads/dir",
+ "heads/ident",
+ "heads/long-file-name",
+ "heads/master",
+ "heads/packed-test",
+ "heads/subtrees",
+ "heads/test",
+ "tags/e90810b",
+ "tags/foo/bar",
+ "tags/foo/foo/bar",
+ "tags/point_to_blob",
+ "tags/test",
+ NULL,
+ };
+
+ g_repo = cl_git_sandbox_init("testrepo");
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "testrepo/.git/refs", NULL));
+ expect_iterator_items(i, 13, expect_base, 13, expect_base);
+ git_iterator_free(i);
+}
+
+/* Lots of empty dirs, or nearly empty ones, make the old workdir
+ * iterator cry. Also, segfault.
+ */
+void test_iterator_workdir__filesystem_gunk(void)
+{
+ git_iterator *i;
+ git_buf parent = GIT_BUF_INIT;
+ int n;
+
+ if (!cl_is_env_set("GITTEST_INVASIVE_SPEED"))
+ cl_skip();
+
+ g_repo = cl_git_sandbox_init("testrepo");
+
+ for (n = 0; n < 100000; n++) {
+ git_buf_clear(&parent);
+ git_buf_printf(&parent, "%s/refs/heads/foo/%d/subdir",
+ git_repository_path(g_repo), n);
+ cl_assert(!git_buf_oom(&parent));
+
+ cl_git_pass(git_futils_mkdir(parent.ptr, 0775, GIT_MKDIR_PATH));
+ }
+
+ cl_git_pass(git_iterator_for_filesystem(&i, "testrepo/.git/refs", NULL));
+
+ /* should only have 13 items, since we're not asking for trees to be
+ * returned. the goal of this test is simply to not crash.
+ */
+ expect_iterator_items(i, 13, NULL, 13, NULL);
+ git_iterator_free(i);
+ git_buf_free(&parent);
+}
+
+void test_iterator_workdir__skips_unreadable_dirs(void)
+{
+ git_iterator *i;
+ const git_index_entry *e;
+
+ if (!cl_is_chmod_supported())
+ return;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_must_pass(p_mkdir("empty_standard_repo/r", 0777));
+ cl_git_mkfile("empty_standard_repo/r/a", "hello");
+ cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777));
+ cl_git_mkfile("empty_standard_repo/r/b/problem", "not me");
+ cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000));
+ cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777));
+ cl_git_mkfile("empty_standard_repo/r/c/foo", "aloha");
+ cl_git_mkfile("empty_standard_repo/r/d", "final");
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "empty_standard_repo/r", NULL));
+
+ cl_git_pass(git_iterator_advance(&e, i)); /* a */
+ cl_assert_equal_s("a", e->path);
+
+ cl_git_pass(git_iterator_advance(&e, i)); /* c/foo */
+ cl_assert_equal_s("c/foo", e->path);
+
+ cl_git_pass(git_iterator_advance(&e, i)); /* d */
+ cl_assert_equal_s("d", e->path);
+
+ cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777));
+
+ cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i));
+ git_iterator_free(i);
+}
+
+void test_iterator_workdir__skips_fifos_and_special_files(void)
+{
+#ifndef GIT_WIN32
+ git_iterator *i;
+ const git_index_entry *e;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_must_pass(p_mkdir("empty_standard_repo/dir", 0777));
+ cl_git_mkfile("empty_standard_repo/file", "not me");
+
+ cl_assert(!mkfifo("empty_standard_repo/fifo", 0777));
+ cl_assert(!access("empty_standard_repo/fifo", F_OK));
+
+ i_opts.flags = GIT_ITERATOR_INCLUDE_TREES |
+ GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "empty_standard_repo", &i_opts));
+
+ cl_git_pass(git_iterator_advance(&e, i)); /* .git */
+ cl_assert(S_ISDIR(e->mode));
+ cl_git_pass(git_iterator_advance(&e, i)); /* dir */
+ cl_assert(S_ISDIR(e->mode));
+ /* skips fifo */
+ cl_git_pass(git_iterator_advance(&e, i)); /* file */
+ cl_assert(S_ISREG(e->mode));
+
+ cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i));
+
+ git_iterator_free(i);
+#endif
+}
+
+void test_iterator_workdir__pathlist(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+
+ cl_git_pass(git_vector_init(&filelist, 100, NULL));
+ cl_git_pass(git_vector_insert(&filelist, "a"));
+ cl_git_pass(git_vector_insert(&filelist, "B"));
+ cl_git_pass(git_vector_insert(&filelist, "c"));
+ cl_git_pass(git_vector_insert(&filelist, "D"));
+ cl_git_pass(git_vector_insert(&filelist, "e"));
+ cl_git_pass(git_vector_insert(&filelist, "k.a"));
+ cl_git_pass(git_vector_insert(&filelist, "k.b"));
+ cl_git_pass(git_vector_insert(&filelist, "k/1"));
+ cl_git_pass(git_vector_insert(&filelist, "k/a"));
+ cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ"));
+ cl_git_pass(git_vector_insert(&filelist, "L/1"));
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ /* Test iterators without returning tree entries (but autoexpanding.) */
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+
+ /* Case sensitive */
+ {
+ const char *expected[] = {
+ "B", "D", "L/1", "a", "c", "e", "k/1", "k/a" };
+ size_t expected_len = 8;
+
+ i_opts.start = NULL;
+ i_opts.end = NULL;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Case INsensitive */
+ {
+ const char *expected[] = {
+ "a", "B", "c", "D", "e", "k/1", "k/a", "L/1" };
+ size_t expected_len = 8;
+
+ i_opts.start = NULL;
+ i_opts.end = NULL;
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Set a start, but no end. Case sensitive. */
+ {
+ const char *expected[] = { "c", "e", "k/1", "k/a" };
+ size_t expected_len = 4;
+
+ i_opts.start = "c";
+ i_opts.end = NULL;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Set a start, but no end. Case INsensitive. */
+ {
+ const char *expected[] = { "c", "D", "e", "k/1", "k/a", "L/1" };
+ size_t expected_len = 6;
+
+ i_opts.start = "c";
+ i_opts.end = NULL;
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Set no start, but an end. Case sensitive. */
+ {
+ const char *expected[] = { "B", "D", "L/1", "a", "c", "e" };
+ size_t expected_len = 6;
+
+ i_opts.start = NULL;
+ i_opts.end = "e";
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Set no start, but an end. Case INsensitive. */
+ {
+ const char *expected[] = { "a", "B", "c", "D", "e" };
+ size_t expected_len = 5;
+
+ i_opts.start = NULL;
+ i_opts.end = "e";
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Start and an end, case sensitive */
+ {
+ const char *expected[] = { "c", "e", "k/1" };
+ size_t expected_len = 3;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Start and an end, case sensitive */
+ {
+ const char *expected[] = { "k/1" };
+ size_t expected_len = 1;
+
+ i_opts.start = "k";
+ i_opts.end = "k/D";
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Start and an end, case INsensitive */
+ {
+ const char *expected[] = { "c", "D", "e", "k/1", "k/a" };
+ size_t expected_len = 5;
+
+ i_opts.start = "c";
+ i_opts.end = "k/D";
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Start and an end, case INsensitive */
+ {
+ const char *expected[] = { "k/1", "k/a" };
+ size_t expected_len = 2;
+
+ i_opts.start = "k";
+ i_opts.end = "k/D";
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ git_vector_free(&filelist);
+}
+
+void test_iterator_workdir__pathlist_with_dirs(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+
+ cl_git_pass(git_vector_init(&filelist, 5, NULL));
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ /* Test that a prefix `k` matches folders, even without trailing slash */
+ {
+ const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "k"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Test that a `k/` matches a folder */
+ {
+ const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "k/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* When the iterator is case sensitive, ensure we can't lookup the
+ * directory with the wrong case.
+ */
+ {
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "K/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+ }
+
+ /* Test that case insensitive matching works. */
+ {
+ const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "K/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Test that case insensitive matching works without trailing slash. */
+ {
+ const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "K"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ git_vector_free(&filelist);
+}
+
+static void create_paths(const char *root, int depth)
+{
+ git_buf fullpath = GIT_BUF_INIT;
+ size_t root_len;
+ int i;
+
+ cl_git_pass(git_buf_puts(&fullpath, root));
+ cl_git_pass(git_buf_putc(&fullpath, '/'));
+
+ root_len = fullpath.size;
+
+ for (i = 0; i < 8; i++) {
+ bool file = (depth == 0 || (i % 2) == 0);
+ git_buf_truncate(&fullpath, root_len);
+ cl_git_pass(git_buf_printf(&fullpath, "item%d", i));
+
+ if (file) {
+ cl_git_rewritefile(fullpath.ptr, "This is a file!\n");
+ } else {
+ cl_must_pass(p_mkdir(fullpath.ptr, 0777));
+
+ if (depth > 0)
+ create_paths(fullpath.ptr, (depth - 1));
+ }
+ }
+}
+
+void test_iterator_workdir__pathlist_for_deeply_nested_item(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+
+ cl_git_pass(git_vector_init(&filelist, 5, NULL));
+
+ g_repo = cl_git_sandbox_init("icase");
+ create_paths(git_repository_workdir(g_repo), 3);
+
+ /* Ensure that we find the single path we're interested in, and we find
+ * it efficiently, and don't stat the entire world to get there.
+ */
+ {
+ const char *expected[] = { "item1/item3/item5/item7" };
+ size_t expected_len = 1;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/item7"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ cl_assert_equal_i(4, i->stat_calls);
+ git_iterator_free(i);
+ }
+
+ /* Ensure that we find the single path we're interested in, and we find
+ * it efficiently, and don't stat the entire world to get there.
+ */
+ {
+ const char *expected[] = {
+ "item1/item3/item5/item0", "item1/item3/item5/item1",
+ "item1/item3/item5/item2", "item1/item3/item5/item3",
+ "item1/item3/item5/item4", "item1/item3/item5/item5",
+ "item1/item3/item5/item6", "item1/item3/item5/item7",
+ };
+ size_t expected_len = 8;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ cl_assert_equal_i(11, i->stat_calls);
+ git_iterator_free(i);
+ }
+
+ /* Ensure that we find the single path we're interested in, and we find
+ * it efficiently, and don't stat the entire world to get there.
+ */
+ {
+ const char *expected[] = {
+ "item1/item3/item0",
+ "item1/item3/item1/item0", "item1/item3/item1/item1",
+ "item1/item3/item1/item2", "item1/item3/item1/item3",
+ "item1/item3/item1/item4", "item1/item3/item1/item5",
+ "item1/item3/item1/item6", "item1/item3/item1/item7",
+ "item1/item3/item2",
+ "item1/item3/item3/item0", "item1/item3/item3/item1",
+ "item1/item3/item3/item2", "item1/item3/item3/item3",
+ "item1/item3/item3/item4", "item1/item3/item3/item5",
+ "item1/item3/item3/item6", "item1/item3/item3/item7",
+ "item1/item3/item4",
+ "item1/item3/item5/item0", "item1/item3/item5/item1",
+ "item1/item3/item5/item2", "item1/item3/item5/item3",
+ "item1/item3/item5/item4", "item1/item3/item5/item5",
+ "item1/item3/item5/item6", "item1/item3/item5/item7",
+ "item1/item3/item6",
+ "item1/item3/item7/item0", "item1/item3/item7/item1",
+ "item1/item3/item7/item2", "item1/item3/item7/item3",
+ "item1/item3/item7/item4", "item1/item3/item7/item5",
+ "item1/item3/item7/item6", "item1/item3/item7/item7",
+ };
+ size_t expected_len = 36;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "item1/item3/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ cl_assert_equal_i(42, i->stat_calls);
+ git_iterator_free(i);
+ }
+
+ /* Ensure that we find the single path we're interested in, and we find
+ * it efficiently, and don't stat the entire world to get there.
+ */
+ {
+ const char *expected[] = {
+ "item0", "item1/item2", "item5/item7/item4", "item6",
+ "item7/item3/item1/item6" };
+ size_t expected_len = 5;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "item7/item3/item1/item6"));
+ cl_git_pass(git_vector_insert(&filelist, "item6"));
+ cl_git_pass(git_vector_insert(&filelist, "item5/item7/item4"));
+ cl_git_pass(git_vector_insert(&filelist, "item1/item2"));
+ cl_git_pass(git_vector_insert(&filelist, "item0"));
+
+ /* also add some things that don't exist or don't match the right type */
+ cl_git_pass(git_vector_insert(&filelist, "item2/"));
+ cl_git_pass(git_vector_insert(&filelist, "itemN"));
+ cl_git_pass(git_vector_insert(&filelist, "item1/itemA"));
+ cl_git_pass(git_vector_insert(&filelist, "item5/item3/item4/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ cl_assert_equal_i(14, i->stat_calls);
+ git_iterator_free(i);
+ }
+
+ git_vector_free(&filelist);
+}
+
+void test_iterator_workdir__bounded_submodules(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+ git_index *index;
+ git_tree *head;
+
+ cl_git_pass(git_vector_init(&filelist, 5, NULL));
+
+ g_repo = setup_fixture_submod2();
+ cl_git_pass(git_repository_index(&index, g_repo));
+ cl_git_pass(git_repository_head_tree(&head, g_repo));
+
+ /* Test that a submodule matches */
+ {
+ const char *expected[] = { "sm_changed_head" };
+ size_t expected_len = 1;
+
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "sm_changed_head"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Test that a submodule never matches when suffixed with a '/' */
+ {
+ git_vector_clear(&filelist);
+ cl_git_pass(git_vector_insert(&filelist, "sm_changed_head/"));
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts));
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+ }
+
+ /* Test that start/end work with a submodule */
+ {
+ const char *expected[] = { "sm_changed_head", "sm_changed_index" };
+ size_t expected_len = 2;
+
+ i_opts.start = "sm_changed_head";
+ i_opts.end = "sm_changed_index";
+ i_opts.pathlist.strings = NULL;
+ i_opts.pathlist.count = 0;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+ }
+
+ /* Test that start and end do not allow '/' suffixes of submodules */
+ {
+ i_opts.start = "sm_changed_head/";
+ i_opts.end = "sm_changed_head/";
+ i_opts.pathlist.strings = NULL;
+ i_opts.pathlist.count = 0;
+ i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts));
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+ }
+
+ git_vector_free(&filelist);
+ git_index_free(index);
+ git_tree_free(head);
+}
+
+void test_iterator_workdir__advance_over(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ /* create an empty directory */
+ cl_must_pass(p_mkdir("icase/empty", 0777));
+
+ /* create a directory in which all contents are ignored */
+ cl_must_pass(p_mkdir("icase/all_ignored", 0777));
+ cl_git_rewritefile("icase/all_ignored/one", "This is ignored\n");
+ cl_git_rewritefile("icase/all_ignored/two", "This, too, is ignored\n");
+ cl_git_rewritefile("icase/all_ignored/.gitignore", ".gitignore\none\ntwo\n");
+
+ /* create a directory in which not all contents are ignored */
+ cl_must_pass(p_mkdir("icase/some_ignored", 0777));
+ cl_git_rewritefile("icase/some_ignored/one", "This is ignored\n");
+ cl_git_rewritefile("icase/some_ignored/two", "This is not ignored\n");
+ cl_git_rewritefile("icase/some_ignored/.gitignore", ".gitignore\none\n");
+
+ /* create a directory which has some empty children */
+ cl_must_pass(p_mkdir("icase/empty_children", 0777));
+ cl_must_pass(p_mkdir("icase/empty_children/empty1", 0777));
+ cl_must_pass(p_mkdir("icase/empty_children/empty2", 0777));
+ cl_must_pass(p_mkdir("icase/empty_children/empty3", 0777));
+
+ /* create a directory which will disappear! */
+ cl_must_pass(p_mkdir("icase/missing_directory", 0777));
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+
+ cl_must_pass(p_rmdir("icase/missing_directory"));
+
+ expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "all_ignored/", GIT_ITERATOR_STATUS_IGNORED);
+ expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "empty/", GIT_ITERATOR_STATUS_EMPTY);
+ expect_advance_over(i, "empty_children/", GIT_ITERATOR_STATUS_EMPTY);
+ expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "missing_directory/", GIT_ITERATOR_STATUS_EMPTY);
+ expect_advance_over(i, "some_ignored/", GIT_ITERATOR_STATUS_NORMAL);
+
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+}
+
+void test_iterator_workdir__advance_over_with_pathlist(void)
+{
+ git_vector pathlist = GIT_VECTOR_INIT;
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ git_vector_insert(&pathlist, "dirA/subdir1/subdir2/file");
+ git_vector_insert(&pathlist, "dirB/subdir1/subdir2");
+ git_vector_insert(&pathlist, "dirC/subdir1/nonexistent");
+ git_vector_insert(&pathlist, "dirD/subdir1/nonexistent");
+ git_vector_insert(&pathlist, "dirD/subdir1/subdir2");
+ git_vector_insert(&pathlist, "dirD/nonexistent");
+
+ i_opts.pathlist.strings = (char **)pathlist.contents;
+ i_opts.pathlist.count = pathlist.length;
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ /* Create a directory that has a file that is included in our pathlist */
+ cl_must_pass(p_mkdir("icase/dirA", 0777));
+ cl_must_pass(p_mkdir("icase/dirA/subdir1", 0777));
+ cl_must_pass(p_mkdir("icase/dirA/subdir1/subdir2", 0777));
+ cl_git_rewritefile("icase/dirA/subdir1/subdir2/file", "foo!");
+
+ /* Create a directory that has a directory that is included in our pathlist */
+ cl_must_pass(p_mkdir("icase/dirB", 0777));
+ cl_must_pass(p_mkdir("icase/dirB/subdir1", 0777));
+ cl_must_pass(p_mkdir("icase/dirB/subdir1/subdir2", 0777));
+ cl_git_rewritefile("icase/dirB/subdir1/subdir2/file", "foo!");
+
+ /* Create a directory that would contain an entry in our pathlist, but
+ * that entry does not actually exist. We don't know this until we
+ * advance_over it. We want to distinguish this from an actually empty
+ * or ignored directory.
+ */
+ cl_must_pass(p_mkdir("icase/dirC", 0777));
+ cl_must_pass(p_mkdir("icase/dirC/subdir1", 0777));
+ cl_must_pass(p_mkdir("icase/dirC/subdir1/subdir2", 0777));
+ cl_git_rewritefile("icase/dirC/subdir1/subdir2/file", "foo!");
+
+ /* Create a directory that has a mix of actual and nonexistent paths */
+ cl_must_pass(p_mkdir("icase/dirD", 0777));
+ cl_must_pass(p_mkdir("icase/dirD/subdir1", 0777));
+ cl_must_pass(p_mkdir("icase/dirD/subdir1/subdir2", 0777));
+ cl_git_rewritefile("icase/dirD/subdir1/subdir2/file", "foo!");
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+
+ expect_advance_over(i, "dirA/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "dirB/", GIT_ITERATOR_STATUS_NORMAL);
+ expect_advance_over(i, "dirC/", GIT_ITERATOR_STATUS_FILTERED);
+ expect_advance_over(i, "dirD/", GIT_ITERATOR_STATUS_NORMAL);
+
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+ git_vector_free(&pathlist);
+}
+
+void test_iterator_workdir__advance_into(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+
+ g_repo = cl_git_sandbox_init("icase");
+
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE |
+ GIT_ITERATOR_DONT_AUTOEXPAND;
+
+ cl_must_pass(p_mkdir("icase/Empty", 0777));
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_advance_into(i, "B");
+ expect_advance_into(i, "D");
+ expect_advance_into(i, "Empty/");
+ expect_advance_into(i, "F");
+ expect_advance_into(i, "H");
+ expect_advance_into(i, "J");
+ expect_advance_into(i, "L/");
+ expect_advance_into(i, "L/1");
+ expect_advance_into(i, "L/B");
+ expect_advance_into(i, "L/D");
+ expect_advance_into(i, "L/a");
+ expect_advance_into(i, "L/c");
+ expect_advance_into(i, "a");
+ expect_advance_into(i, "c");
+ expect_advance_into(i, "e");
+ expect_advance_into(i, "g");
+ expect_advance_into(i, "i");
+ expect_advance_into(i, "k/");
+ expect_advance_into(i, "k/1");
+ expect_advance_into(i, "k/B");
+ expect_advance_into(i, "k/D");
+ expect_advance_into(i, "k/a");
+ expect_advance_into(i, "k/c");
+
+ cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i));
+ git_iterator_free(i);
+}
+
+void test_iterator_workdir__pathlist_with_directory(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+
+ const char *expected[] = { "subdir/README", "subdir/new.txt",
+ "subdir/subdir2/README", "subdir/subdir2/new.txt" };
+ size_t expected_len = 4;
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "subdir/"));
+
+ g_repo = cl_git_sandbox_init("testrepo2");
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+
+ git_vector_free(&filelist);
+}
+
+void test_iterator_workdir__pathlist_with_directory_include_trees(void)
+{
+ git_iterator *i;
+ git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
+ git_vector filelist;
+
+ const char *expected[] = { "subdir/", "subdir/README", "subdir/new.txt",
+ "subdir/subdir2/", "subdir/subdir2/README", "subdir/subdir2/new.txt", };
+ size_t expected_len = 6;
+
+ cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
+ cl_git_pass(git_vector_insert(&filelist, "subdir/"));
+
+ g_repo = cl_git_sandbox_init("testrepo2");
+
+ i_opts.pathlist.strings = (char **)filelist.contents;
+ i_opts.pathlist.count = filelist.length;
+ i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
+
+ cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
+ expect_iterator_items(i, expected_len, expected, expected_len, expected);
+ git_iterator_free(i);
+
+ git_vector_free(&filelist);
+}
+
diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c
deleted file mode 100644
index c18e24a4f..000000000
--- a/tests/repo/iterator.c
+++ /dev/null
@@ -1,1544 +0,0 @@
-#include "clar_libgit2.h"
-#include "iterator.h"
-#include "repository.h"
-#include "fileops.h"
-#include <stdarg.h>
-
-static git_repository *g_repo;
-
-void test_repo_iterator__initialize(void)
-{
-}
-
-void test_repo_iterator__cleanup(void)
-{
- cl_git_sandbox_cleanup();
- g_repo = NULL;
-}
-
-static void expect_iterator_items(
- git_iterator *i,
- int expected_flat,
- const char **expected_flat_paths,
- int expected_total,
- const char **expected_total_paths)
-{
- const git_index_entry *entry;
- int count, error;
- int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES);
- bool v = false;
-
- if (expected_flat < 0) { v = true; expected_flat = -expected_flat; }
- if (expected_total < 0) { v = true; expected_total = -expected_total; }
-
- if (v) fprintf(stderr, "== %s ==\n", no_trees ? "notrees" : "trees");
-
- count = 0;
-
- while (!git_iterator_advance(&entry, i)) {
- if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode);
-
- if (no_trees)
- cl_assert(entry->mode != GIT_FILEMODE_TREE);
-
- if (expected_flat_paths) {
- const char *expect_path = expected_flat_paths[count];
- size_t expect_len = strlen(expect_path);
-
- cl_assert_equal_s(expect_path, entry->path);
-
- if (expect_path[expect_len - 1] == '/')
- cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode);
- else
- cl_assert(entry->mode != GIT_FILEMODE_TREE);
- }
-
- if (++count > expected_flat)
- break;
- }
-
- cl_assert_equal_i(expected_flat, count);
-
- cl_git_pass(git_iterator_reset(i, NULL, NULL));
-
- count = 0;
- cl_git_pass(git_iterator_current(&entry, i));
-
- if (v) fprintf(stderr, "-- %s --\n", no_trees ? "notrees" : "trees");
-
- while (entry != NULL) {
- if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode);
-
- if (no_trees)
- cl_assert(entry->mode != GIT_FILEMODE_TREE);
-
- if (expected_total_paths) {
- const char *expect_path = expected_total_paths[count];
- size_t expect_len = strlen(expect_path);
-
- cl_assert_equal_s(expect_path, entry->path);
-
- if (expect_path[expect_len - 1] == '/')
- cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode);
- else
- cl_assert(entry->mode != GIT_FILEMODE_TREE);
- }
-
- if (entry->mode == GIT_FILEMODE_TREE) {
- error = git_iterator_advance_into(&entry, i);
-
- /* could return NOTFOUND if directory is empty */
- cl_assert(!error || error == GIT_ENOTFOUND);
-
- if (error == GIT_ENOTFOUND) {
- error = git_iterator_advance(&entry, i);
- cl_assert(!error || error == GIT_ITEROVER);
- }
- } else {
- error = git_iterator_advance(&entry, i);
- cl_assert(!error || error == GIT_ITEROVER);
- }
-
- if (++count > expected_total)
- break;
- }
-
- cl_assert_equal_i(expected_total, count);
-}
-
-/* Index contents (including pseudotrees):
- *
- * 0: a 5: F 10: k/ 16: L/
- * 1: B 6: g 11: k/1 17: L/1
- * 2: c 7: H 12: k/a 18: L/a
- * 3: D 8: i 13: k/B 19: L/B
- * 4: e 9: J 14: k/c 20: L/c
- * 15: k/D 21: L/D
- *
- * 0: B 5: L/ 11: a 16: k/
- * 1: D 6: L/1 12: c 17: k/1
- * 2: F 7: L/B 13: e 18: k/B
- * 3: H 8: L/D 14: g 19: k/D
- * 4: J 9: L/a 15: i 20: k/a
- * 10: L/c 21: k/c
- */
-
-void test_repo_iterator__index(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_index *index;
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_repository_index(&index, g_repo));
-
- /* autoexpand with no tree entries for index */
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, NULL));
- expect_iterator_items(i, 20, NULL, 20, NULL);
- git_iterator_free(i);
-
- /* auto expand with tree entries */
- i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 22, NULL, 22, NULL);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 12, NULL, 22, NULL);
- git_iterator_free(i);
-
- git_index_free(index);
-}
-
-void test_repo_iterator__index_icase(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_index *index;
- int caps;
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_repository_index(&index, g_repo));
- caps = git_index_caps(index);
-
- /* force case sensitivity */
- cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE));
-
- /* autoexpand with no tree entries over range */
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 7, NULL, 7, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 3, NULL, 3, NULL);
- git_iterator_free(i);
-
- /* auto expand with tree entries */
- i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 8, NULL, 8, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 4, NULL, 4, NULL);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 5, NULL, 8, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 1, NULL, 4, NULL);
- git_iterator_free(i);
-
- /* force case insensitivity */
- cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE));
-
- /* autoexpand with no tree entries over range */
- i_opts.flags = 0;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 13, NULL, 13, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 5, NULL, 5, NULL);
- git_iterator_free(i);
-
- /* auto expand with tree entries */
- i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 14, NULL, 14, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 6, NULL, 6, NULL);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 9, NULL, 14, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 1, NULL, 6, NULL);
- git_iterator_free(i);
-
- cl_git_pass(git_index_set_caps(index, caps));
- git_index_free(index);
-}
-
-void test_repo_iterator__tree(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_tree *head;
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_repository_head_tree(&head, g_repo));
-
- /* auto expand with no tree entries */
- cl_git_pass(git_iterator_for_tree(&i, head, NULL));
- expect_iterator_items(i, 20, NULL, 20, NULL);
- git_iterator_free(i);
-
- /* auto expand with tree entries */
- i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
-
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 22, NULL, 22, NULL);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
-
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 12, NULL, 22, NULL);
- git_iterator_free(i);
-
- git_tree_free(head);
-}
-
-void test_repo_iterator__tree_icase(void)
-{
- git_iterator *i;
- git_tree *head;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_repository_head_tree(&head, g_repo));
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
-
- /* auto expand with no tree entries */
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 7, NULL, 7, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 3, NULL, 3, NULL);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
-
- /* auto expand with tree entries */
- i_opts.start = "c";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 8, NULL, 8, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 4, NULL, 4, NULL);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 5, NULL, 8, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 1, NULL, 4, NULL);
- git_iterator_free(i);
-
- /* auto expand with no tree entries */
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 13, NULL, 13, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 5, NULL, 5, NULL);
- git_iterator_free(i);
-
- /* auto expand with tree entries */
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 14, NULL, 14, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 6, NULL, 6, NULL);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 9, NULL, 14, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 1, NULL, 6, NULL);
- git_iterator_free(i);
-
- git_tree_free(head);
-}
-
-void test_repo_iterator__tree_more(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_tree *head;
- static const char *expect_basic[] = {
- "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,
- };
- static const char *expect_trees[] = {
- "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/",
- "subdir/current_file",
- "subdir/deleted_file",
- "subdir/modified_file",
- NULL,
- };
- static const char *expect_noauto[] = {
- "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/",
- NULL
- };
-
- g_repo = cl_git_sandbox_init("status");
-
- cl_git_pass(git_repository_head_tree(&head, g_repo));
-
- /* auto expand with no tree entries */
- cl_git_pass(git_iterator_for_tree(&i, head, NULL));
- expect_iterator_items(i, 12, expect_basic, 12, expect_basic);
- git_iterator_free(i);
-
- /* auto expand with tree entries */
- i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
-
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 13, expect_trees, 13, expect_trees);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
-
- cl_git_pass(git_iterator_for_tree(&i, head, &i_opts));
- expect_iterator_items(i, 10, expect_noauto, 13, expect_trees);
- git_iterator_free(i);
-
- git_tree_free(head);
-}
-
-/* "b=name,t=name", blob_id, tree_id */
-static void build_test_tree(
- git_oid *out, git_repository *repo, const char *fmt, ...)
-{
- git_oid *id;
- git_treebuilder *builder;
- const char *scan = fmt, *next;
- char type, delimiter;
- git_filemode_t mode = GIT_FILEMODE_BLOB;
- git_buf name = GIT_BUF_INIT;
- va_list arglist;
-
- cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); /* start builder */
-
- va_start(arglist, fmt);
- while (*scan) {
- switch (type = *scan++) {
- case 't': case 'T': mode = GIT_FILEMODE_TREE; break;
- case 'b': case 'B': mode = GIT_FILEMODE_BLOB; break;
- default:
- cl_assert(type == 't' || type == 'T' || type == 'b' || type == 'B');
- }
-
- delimiter = *scan++; /* read and skip delimiter */
- for (next = scan; *next && *next != delimiter; ++next)
- /* seek end */;
- cl_git_pass(git_buf_set(&name, scan, (size_t)(next - scan)));
- for (scan = next; *scan && (*scan == delimiter || *scan == ','); ++scan)
- /* skip delimiter and optional comma */;
-
- id = va_arg(arglist, git_oid *);
-
- cl_git_pass(git_treebuilder_insert(NULL, builder, name.ptr, id, mode));
- }
- va_end(arglist);
-
- cl_git_pass(git_treebuilder_write(out, builder));
-
- git_treebuilder_free(builder);
- git_buf_free(&name);
-}
-
-void test_repo_iterator__tree_case_conflicts_0(void)
-{
- const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6";
- git_tree *tree;
- git_oid blob_id, biga_id, littlea_id, tree_id;
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- const char *expect_cs[] = {
- "A/1.file", "A/3.file", "a/2.file", "a/4.file" };
- const char *expect_ci[] = {
- "A/1.file", "a/2.file", "A/3.file", "a/4.file" };
- const char *expect_cs_trees[] = {
- "A/", "A/1.file", "A/3.file", "a/", "a/2.file", "a/4.file" };
- const char *expect_ci_trees[] = {
- "A/", "A/1.file", "a/2.file", "A/3.file", "a/4.file" };
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */
-
- /* create tree with: A/1.file, A/3.file, a/2.file, a/4.file */
- build_test_tree(
- &biga_id, g_repo, "b|1.file|,b|3.file|", &blob_id, &blob_id);
- build_test_tree(
- &littlea_id, g_repo, "b|2.file|,b|4.file|", &blob_id, &blob_id);
- build_test_tree(
- &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id);
-
- cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 4, expect_cs, 4, expect_cs);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 4, expect_ci, 4, expect_ci);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 6, expect_cs_trees, 6, expect_cs_trees);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 5, expect_ci_trees, 5, expect_ci_trees);
- git_iterator_free(i);
-
- git_tree_free(tree);
-}
-
-void test_repo_iterator__tree_case_conflicts_1(void)
-{
- const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6";
- git_tree *tree;
- git_oid blob_id, Ab_id, biga_id, littlea_id, tree_id;
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- const char *expect_cs[] = {
- "A/a", "A/b/1", "A/c", "a/C", "a/a", "a/b" };
- const char *expect_ci[] = {
- "A/a", "a/b", "A/b/1", "A/c" };
- const char *expect_cs_trees[] = {
- "A/", "A/a", "A/b/", "A/b/1", "A/c", "a/", "a/C", "a/a", "a/b" };
- const char *expect_ci_trees[] = {
- "A/", "A/a", "a/b", "A/b/", "A/b/1", "A/c" };
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */
-
- /* create: A/a A/b/1 A/c a/a a/b a/C */
- build_test_tree(&Ab_id, g_repo, "b|1|", &blob_id);
- build_test_tree(
- &biga_id, g_repo, "b|a|,t|b|,b|c|", &blob_id, &Ab_id, &blob_id);
- build_test_tree(
- &littlea_id, g_repo, "b|a|,b|b|,b|C|", &blob_id, &blob_id, &blob_id);
- build_test_tree(
- &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id);
-
- cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 6, expect_cs, 6, expect_cs);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 4, expect_ci, 4, expect_ci);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 9, expect_cs_trees, 9, expect_cs_trees);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 6, expect_ci_trees, 6, expect_ci_trees);
- git_iterator_free(i);
-
- git_tree_free(tree);
-}
-
-void test_repo_iterator__tree_case_conflicts_2(void)
-{
- const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6";
- git_tree *tree;
- git_oid blob_id, d1, d2, c1, c2, b1, b2, a1, a2, tree_id;
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- const char *expect_cs[] = {
- "A/B/C/D/16", "A/B/C/D/foo", "A/B/C/d/15", "A/B/C/d/FOO",
- "A/B/c/D/14", "A/B/c/D/foo", "A/B/c/d/13", "A/B/c/d/FOO",
- "A/b/C/D/12", "A/b/C/D/foo", "A/b/C/d/11", "A/b/C/d/FOO",
- "A/b/c/D/10", "A/b/c/D/foo", "A/b/c/d/09", "A/b/c/d/FOO",
- "a/B/C/D/08", "a/B/C/D/foo", "a/B/C/d/07", "a/B/C/d/FOO",
- "a/B/c/D/06", "a/B/c/D/foo", "a/B/c/d/05", "a/B/c/d/FOO",
- "a/b/C/D/04", "a/b/C/D/foo", "a/b/C/d/03", "a/b/C/d/FOO",
- "a/b/c/D/02", "a/b/c/D/foo", "a/b/c/d/01", "a/b/c/d/FOO", };
- const char *expect_ci[] = {
- "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04",
- "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08",
- "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12",
- "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16",
- "A/B/C/D/foo", };
- const char *expect_ci_trees[] = {
- "A/", "A/B/", "A/B/C/", "A/B/C/D/",
- "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04",
- "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08",
- "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12",
- "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16",
- "A/B/C/D/foo", };
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */
-
- build_test_tree(&d1, g_repo, "b|16|,b|foo|", &blob_id, &blob_id);
- build_test_tree(&d2, g_repo, "b|15|,b|FOO|", &blob_id, &blob_id);
- build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
- build_test_tree(&d1, g_repo, "b|14|,b|foo|", &blob_id, &blob_id);
- build_test_tree(&d2, g_repo, "b|13|,b|FOO|", &blob_id, &blob_id);
- build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
- build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2);
-
- build_test_tree(&d1, g_repo, "b|12|,b|foo|", &blob_id, &blob_id);
- build_test_tree(&d2, g_repo, "b|11|,b|FOO|", &blob_id, &blob_id);
- build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
- build_test_tree(&d1, g_repo, "b|10|,b|foo|", &blob_id, &blob_id);
- build_test_tree(&d2, g_repo, "b|09|,b|FOO|", &blob_id, &blob_id);
- build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
- build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2);
-
- build_test_tree(&a1, g_repo, "t|B|,t|b|", &b1, &b2);
-
- build_test_tree(&d1, g_repo, "b|08|,b|foo|", &blob_id, &blob_id);
- build_test_tree(&d2, g_repo, "b|07|,b|FOO|", &blob_id, &blob_id);
- build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
- build_test_tree(&d1, g_repo, "b|06|,b|foo|", &blob_id, &blob_id);
- build_test_tree(&d2, g_repo, "b|05|,b|FOO|", &blob_id, &blob_id);
- build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
- build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2);
-
- build_test_tree(&d1, g_repo, "b|04|,b|foo|", &blob_id, &blob_id);
- build_test_tree(&d2, g_repo, "b|03|,b|FOO|", &blob_id, &blob_id);
- build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2);
- build_test_tree(&d1, g_repo, "b|02|,b|foo|", &blob_id, &blob_id);
- build_test_tree(&d2, g_repo, "b|01|,b|FOO|", &blob_id, &blob_id);
- build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2);
- build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2);
-
- build_test_tree(&a2, g_repo, "t|B|,t|b|", &b1, &b2);
-
- build_test_tree(&tree_id, g_repo, "t/A/,t/a/", &a1, &a2);
-
- cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 32, expect_cs, 32, expect_cs);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 17, expect_ci, 17, expect_ci);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 21, expect_ci_trees, 21, expect_ci_trees);
- git_iterator_free(i);
-
- git_tree_free(tree);
-}
-
-void test_repo_iterator__workdir(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- g_repo = cl_git_sandbox_init("icase");
-
- /* auto expand with no tree entries */
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 20, NULL, 20, NULL);
- git_iterator_free(i);
-
- /* auto expand with tree entries */
- i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 22, NULL, 22, NULL);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 12, NULL, 22, NULL);
- git_iterator_free(i);
-}
-
-void test_repo_iterator__workdir_icase(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- g_repo = cl_git_sandbox_init("icase");
-
- /* auto expand with no tree entries */
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 7, NULL, 7, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 3, NULL, 3, NULL);
- git_iterator_free(i);
-
- /* auto expand with tree entries */
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 8, NULL, 8, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 4, NULL, 4, NULL);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 5, NULL, 8, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 1, NULL, 4, NULL);
- git_iterator_free(i);
-
- /* auto expand with no tree entries */
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 13, NULL, 13, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 5, NULL, 5, NULL);
- git_iterator_free(i);
-
- /* auto expand with tree entries */
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 14, NULL, 14, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 6, NULL, 6, NULL);
- git_iterator_free(i);
-
- /* no auto expand (implies trees included) */
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 9, NULL, 14, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 1, NULL, 6, NULL);
- git_iterator_free(i);
-}
-
-static void build_workdir_tree(const char *root, int dirs, int subs)
-{
- int i, j;
- char buf[64], sub[64];
-
- for (i = 0; i < dirs; ++i) {
- if (i % 2 == 0) {
- p_snprintf(buf, sizeof(buf), "%s/dir%02d", root, i);
- cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH));
-
- p_snprintf(buf, sizeof(buf), "%s/dir%02d/file", root, i);
- cl_git_mkfile(buf, buf);
- buf[strlen(buf) - 5] = '\0';
- } else {
- p_snprintf(buf, sizeof(buf), "%s/DIR%02d", root, i);
- cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH));
- }
-
- for (j = 0; j < subs; ++j) {
- switch (j % 4) {
- case 0: p_snprintf(sub, sizeof(sub), "%s/sub%02d", buf, j); break;
- case 1: p_snprintf(sub, sizeof(sub), "%s/sUB%02d", buf, j); break;
- case 2: p_snprintf(sub, sizeof(sub), "%s/Sub%02d", buf, j); break;
- case 3: p_snprintf(sub, sizeof(sub), "%s/SUB%02d", buf, j); break;
- }
- cl_git_pass(git_futils_mkdir(sub, 0775, GIT_MKDIR_PATH));
-
- if (j % 2 == 0) {
- size_t sublen = strlen(sub);
- memcpy(&sub[sublen], "/file", sizeof("/file"));
- cl_git_mkfile(sub, sub);
- sub[sublen] = '\0';
- }
- }
- }
-}
-
-void test_repo_iterator__workdir_depth(void)
-{
- git_iterator *iter;
- git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- g_repo = cl_git_sandbox_init("icase");
-
- build_workdir_tree("icase", 10, 10);
- build_workdir_tree("icase/DIR01/sUB01", 50, 0);
- build_workdir_tree("icase/dir02/sUB01", 50, 0);
-
- /* auto expand with no tree entries */
- cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts));
- expect_iterator_items(iter, 125, NULL, 125, NULL);
- git_iterator_free(iter);
-
- /* auto expand with tree entries (empty dirs silently skipped) */
- iter_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts));
- expect_iterator_items(iter, 337, NULL, 337, NULL);
- git_iterator_free(iter);
-}
-
-void test_repo_iterator__fs(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- static const char *expect_base[] = {
- "DIR01/Sub02/file",
- "DIR01/sub00/file",
- "current_file",
- "dir00/Sub02/file",
- "dir00/file",
- "dir00/sub00/file",
- "modified_file",
- "new_file",
- NULL,
- };
- static const char *expect_trees[] = {
- "DIR01/",
- "DIR01/SUB03/",
- "DIR01/Sub02/",
- "DIR01/Sub02/file",
- "DIR01/sUB01/",
- "DIR01/sub00/",
- "DIR01/sub00/file",
- "current_file",
- "dir00/",
- "dir00/SUB03/",
- "dir00/Sub02/",
- "dir00/Sub02/file",
- "dir00/file",
- "dir00/sUB01/",
- "dir00/sub00/",
- "dir00/sub00/file",
- "modified_file",
- "new_file",
- NULL,
- };
- static const char *expect_noauto[] = {
- "DIR01/",
- "current_file",
- "dir00/",
- "modified_file",
- "new_file",
- NULL,
- };
-
- g_repo = cl_git_sandbox_init("status");
-
- build_workdir_tree("status/subdir", 2, 4);
-
- cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", NULL));
- expect_iterator_items(i, 8, expect_base, 8, expect_base);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
- expect_iterator_items(i, 18, expect_trees, 18, expect_trees);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND;
- cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
- expect_iterator_items(i, 5, expect_noauto, 18, expect_trees);
- git_iterator_free(i);
-
- git__tsort((void **)expect_base, 8, (git__tsort_cmp)git__strcasecmp);
- git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp);
- git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
- cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
- expect_iterator_items(i, 8, expect_base, 8, expect_base);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES;
- cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
- expect_iterator_items(i, 18, expect_trees, 18, expect_trees);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND;
- cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts));
- expect_iterator_items(i, 5, expect_noauto, 18, expect_trees);
- git_iterator_free(i);
-}
-
-void test_repo_iterator__fs2(void)
-{
- git_iterator *i;
- static const char *expect_base[] = {
- "heads/br2",
- "heads/dir",
- "heads/ident",
- "heads/long-file-name",
- "heads/master",
- "heads/packed-test",
- "heads/subtrees",
- "heads/test",
- "tags/e90810b",
- "tags/foo/bar",
- "tags/foo/foo/bar",
- "tags/point_to_blob",
- "tags/test",
- NULL,
- };
-
- g_repo = cl_git_sandbox_init("testrepo");
-
- cl_git_pass(git_iterator_for_filesystem(
- &i, "testrepo/.git/refs", NULL));
- expect_iterator_items(i, 13, expect_base, 13, expect_base);
- git_iterator_free(i);
-}
-
-void test_repo_iterator__unreadable_dir(void)
-{
- git_iterator *i;
- const git_index_entry *e;
-
- if (!cl_is_chmod_supported())
- return;
-
- g_repo = cl_git_sandbox_init("empty_standard_repo");
-
- cl_must_pass(p_mkdir("empty_standard_repo/r", 0777));
- cl_git_mkfile("empty_standard_repo/r/a", "hello");
- cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777));
- cl_git_mkfile("empty_standard_repo/r/b/problem", "not me");
- cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000));
- cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777));
- cl_git_mkfile("empty_standard_repo/r/d", "final");
-
- cl_git_pass(git_iterator_for_filesystem(
- &i, "empty_standard_repo/r", NULL));
-
- cl_git_pass(git_iterator_advance(&e, i)); /* a */
- cl_git_fail(git_iterator_advance(&e, i)); /* b */
- cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i));
-
- cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777));
-
- git_iterator_free(i);
-}
-
-void test_repo_iterator__skips_fifos_and_such(void)
-{
-#ifndef GIT_WIN32
- git_iterator *i;
- const git_index_entry *e;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
-
- g_repo = cl_git_sandbox_init("empty_standard_repo");
-
- cl_must_pass(p_mkdir("empty_standard_repo/dir", 0777));
- cl_git_mkfile("empty_standard_repo/file", "not me");
-
- cl_assert(!mkfifo("empty_standard_repo/fifo", 0777));
- cl_assert(!access("empty_standard_repo/fifo", F_OK));
-
- i_opts.flags = GIT_ITERATOR_INCLUDE_TREES |
- GIT_ITERATOR_DONT_AUTOEXPAND;
-
- cl_git_pass(git_iterator_for_filesystem(
- &i, "empty_standard_repo", &i_opts));
-
- cl_git_pass(git_iterator_advance(&e, i)); /* .git */
- cl_assert(S_ISDIR(e->mode));
- cl_git_pass(git_iterator_advance(&e, i)); /* dir */
- cl_assert(S_ISDIR(e->mode));
- /* skips fifo */
- cl_git_pass(git_iterator_advance(&e, i)); /* file */
- cl_assert(S_ISREG(e->mode));
-
- cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i));
-
- git_iterator_free(i);
-#endif
-}
-
-void test_repo_iterator__indexfilelist(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_index *index;
- git_vector filelist;
- int default_icase;
- int expect;
-
- cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
- cl_git_pass(git_vector_insert(&filelist, "a"));
- cl_git_pass(git_vector_insert(&filelist, "B"));
- cl_git_pass(git_vector_insert(&filelist, "c"));
- cl_git_pass(git_vector_insert(&filelist, "D"));
- cl_git_pass(git_vector_insert(&filelist, "e"));
- cl_git_pass(git_vector_insert(&filelist, "k/1"));
- cl_git_pass(git_vector_insert(&filelist, "k/a"));
- cl_git_pass(git_vector_insert(&filelist, "L/1"));
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_repository_index(&index, g_repo));
-
- /* In this test we DO NOT force a case setting on the index. */
- default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0);
-
- i_opts.pathlist.strings = (char **)filelist.contents;
- i_opts.pathlist.count = filelist.length;
-
- /* All indexfilelist iterator tests are "autoexpand with no tree entries" */
-
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 8, NULL, 8, NULL);
- git_iterator_free(i);
-
- i_opts.start = "c";
- i_opts.end = NULL;
-
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */
- expect = ((default_icase) ? 6 : 4);
- expect_iterator_items(i, expect, NULL, expect, NULL);
- git_iterator_free(i);
-
- i_opts.start = NULL;
- i_opts.end = "e";
-
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */
- expect = ((default_icase) ? 5 : 6);
- expect_iterator_items(i, expect, NULL, expect, NULL);
- git_iterator_free(i);
-
- git_index_free(index);
- git_vector_free(&filelist);
-}
-
-void test_repo_iterator__indexfilelist_2(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_index *index;
- git_vector filelist = GIT_VECTOR_INIT;
- int default_icase, expect;
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_repository_index(&index, g_repo));
-
- cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
- cl_git_pass(git_vector_insert(&filelist, "0"));
- cl_git_pass(git_vector_insert(&filelist, "c"));
- cl_git_pass(git_vector_insert(&filelist, "D"));
- cl_git_pass(git_vector_insert(&filelist, "e"));
- cl_git_pass(git_vector_insert(&filelist, "k/1"));
- cl_git_pass(git_vector_insert(&filelist, "k/a"));
-
- /* In this test we DO NOT force a case setting on the index. */
- default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0);
-
- i_opts.pathlist.strings = (char **)filelist.contents;
- i_opts.pathlist.count = filelist.length;
-
- i_opts.start = "b";
- i_opts.end = "k/D";
-
- /* (c D e k/1 k/a ==> 5) vs (c e k/1 ==> 3) */
- expect = default_icase ? 5 : 3;
-
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, expect, NULL, expect, NULL);
- git_iterator_free(i);
-
- git_index_free(index);
- git_vector_free(&filelist);
-}
-
-void test_repo_iterator__indexfilelist_3(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_index *index;
- git_vector filelist = GIT_VECTOR_INIT;
- int default_icase, expect;
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_repository_index(&index, g_repo));
-
- cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
- cl_git_pass(git_vector_insert(&filelist, "0"));
- cl_git_pass(git_vector_insert(&filelist, "c"));
- cl_git_pass(git_vector_insert(&filelist, "D"));
- cl_git_pass(git_vector_insert(&filelist, "e"));
- cl_git_pass(git_vector_insert(&filelist, "k/"));
- cl_git_pass(git_vector_insert(&filelist, "k.a"));
- cl_git_pass(git_vector_insert(&filelist, "k.b"));
- cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ"));
-
- /* In this test we DO NOT force a case setting on the index. */
- default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0);
-
- i_opts.pathlist.strings = (char **)filelist.contents;
- i_opts.pathlist.count = filelist.length;
-
- i_opts.start = "b";
- i_opts.end = "k/D";
-
- /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */
- expect = default_icase ? 8 : 5;
-
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, expect, NULL, expect, NULL);
- git_iterator_free(i);
-
- git_index_free(index);
- git_vector_free(&filelist);
-}
-
-void test_repo_iterator__indexfilelist_4(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_index *index;
- git_vector filelist = GIT_VECTOR_INIT;
- int default_icase, expect;
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_repository_index(&index, g_repo));
-
- cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
- cl_git_pass(git_vector_insert(&filelist, "0"));
- cl_git_pass(git_vector_insert(&filelist, "c"));
- cl_git_pass(git_vector_insert(&filelist, "D"));
- cl_git_pass(git_vector_insert(&filelist, "e"));
- cl_git_pass(git_vector_insert(&filelist, "k"));
- cl_git_pass(git_vector_insert(&filelist, "k.a"));
- cl_git_pass(git_vector_insert(&filelist, "k.b"));
- cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ"));
-
- /* In this test we DO NOT force a case setting on the index. */
- default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0);
-
- i_opts.pathlist.strings = (char **)filelist.contents;
- i_opts.pathlist.count = filelist.length;
-
- i_opts.start = "b";
- i_opts.end = "k/D";
-
- /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */
- expect = default_icase ? 8 : 5;
-
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, expect, NULL, expect, NULL);
- git_iterator_free(i);
-
- git_index_free(index);
- git_vector_free(&filelist);
-}
-
-void test_repo_iterator__indexfilelist_icase(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_index *index;
- int caps;
- git_vector filelist;
-
- cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
- cl_git_pass(git_vector_insert(&filelist, "a"));
- cl_git_pass(git_vector_insert(&filelist, "B"));
- cl_git_pass(git_vector_insert(&filelist, "c"));
- cl_git_pass(git_vector_insert(&filelist, "D"));
- cl_git_pass(git_vector_insert(&filelist, "e"));
- cl_git_pass(git_vector_insert(&filelist, "k/1"));
- cl_git_pass(git_vector_insert(&filelist, "k/a"));
- cl_git_pass(git_vector_insert(&filelist, "L/1"));
-
- g_repo = cl_git_sandbox_init("icase");
-
- cl_git_pass(git_repository_index(&index, g_repo));
- caps = git_index_caps(index);
-
- /* force case sensitivity */
- cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE));
-
- /* All indexfilelist iterator tests are "autoexpand with no tree entries" */
-
- i_opts.pathlist.strings = (char **)filelist.contents;
- i_opts.pathlist.count = filelist.length;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 3, NULL, 3, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 1, NULL, 1, NULL);
- git_iterator_free(i);
-
- /* force case insensitivity */
- cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE));
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 5, NULL, 5, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts));
- expect_iterator_items(i, 2, NULL, 2, NULL);
- git_iterator_free(i);
-
- cl_git_pass(git_index_set_caps(index, caps));
- git_index_free(index);
- git_vector_free(&filelist);
-}
-
-void test_repo_iterator__workdirfilelist(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_vector filelist;
- bool default_icase;
- int expect;
-
- cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
- cl_git_pass(git_vector_insert(&filelist, "a"));
- cl_git_pass(git_vector_insert(&filelist, "B"));
- cl_git_pass(git_vector_insert(&filelist, "c"));
- cl_git_pass(git_vector_insert(&filelist, "D"));
- cl_git_pass(git_vector_insert(&filelist, "e"));
- cl_git_pass(git_vector_insert(&filelist, "k.a"));
- cl_git_pass(git_vector_insert(&filelist, "k.b"));
- cl_git_pass(git_vector_insert(&filelist, "k/1"));
- cl_git_pass(git_vector_insert(&filelist, "k/a"));
- cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ"));
- cl_git_pass(git_vector_insert(&filelist, "L/1"));
-
- g_repo = cl_git_sandbox_init("icase");
-
- /* All indexfilelist iterator tests are "autoexpand with no tree entries" */
- /* In this test we DO NOT force a case on the iteratords and verify default behavior. */
-
- i_opts.pathlist.strings = (char **)filelist.contents;
- i_opts.pathlist.count = filelist.length;
-
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 8, NULL, 8, NULL);
- git_iterator_free(i);
-
- i_opts.start = "c";
- i_opts.end = NULL;
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- default_icase = git_iterator_ignore_case(i);
- /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */
- expect = ((default_icase) ? 6 : 4);
- expect_iterator_items(i, expect, NULL, expect, NULL);
- git_iterator_free(i);
-
- i_opts.start = NULL;
- i_opts.end = "e";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- default_icase = git_iterator_ignore_case(i);
- /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */
- expect = ((default_icase) ? 5 : 6);
- expect_iterator_items(i, expect, NULL, expect, NULL);
- git_iterator_free(i);
-
- git_vector_free(&filelist);
-}
-
-void test_repo_iterator__workdirfilelist_icase(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_vector filelist;
-
- cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
- cl_git_pass(git_vector_insert(&filelist, "a"));
- cl_git_pass(git_vector_insert(&filelist, "B"));
- cl_git_pass(git_vector_insert(&filelist, "c"));
- cl_git_pass(git_vector_insert(&filelist, "D"));
- cl_git_pass(git_vector_insert(&filelist, "e"));
- cl_git_pass(git_vector_insert(&filelist, "k.a"));
- cl_git_pass(git_vector_insert(&filelist, "k.b"));
- cl_git_pass(git_vector_insert(&filelist, "k/1"));
- cl_git_pass(git_vector_insert(&filelist, "k/a"));
- cl_git_pass(git_vector_insert(&filelist, "kZZZZ"));
- cl_git_pass(git_vector_insert(&filelist, "L/1"));
-
- g_repo = cl_git_sandbox_init("icase");
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
- i_opts.pathlist.strings = (char **)filelist.contents;
- i_opts.pathlist.count = filelist.length;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 3, NULL, 3, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 1, NULL, 1, NULL);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 5, NULL, 5, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts));
- expect_iterator_items(i, 2, NULL, 2, NULL);
- git_iterator_free(i);
-
- git_vector_free(&filelist);
-}
-
-void test_repo_iterator__treefilelist(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_vector filelist;
- git_tree *tree;
- bool default_icase;
- int expect;
-
- cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
- cl_git_pass(git_vector_insert(&filelist, "a"));
- cl_git_pass(git_vector_insert(&filelist, "B"));
- cl_git_pass(git_vector_insert(&filelist, "c"));
- cl_git_pass(git_vector_insert(&filelist, "D"));
- cl_git_pass(git_vector_insert(&filelist, "e"));
- cl_git_pass(git_vector_insert(&filelist, "k.a"));
- cl_git_pass(git_vector_insert(&filelist, "k.b"));
- cl_git_pass(git_vector_insert(&filelist, "k/1"));
- cl_git_pass(git_vector_insert(&filelist, "k/a"));
- cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ"));
- cl_git_pass(git_vector_insert(&filelist, "L/1"));
-
- g_repo = cl_git_sandbox_init("icase");
- git_repository_head_tree(&tree, g_repo);
-
- /* All indexfilelist iterator tests are "autoexpand with no tree entries" */
- /* In this test we DO NOT force a case on the iteratords and verify default behavior. */
-
- i_opts.pathlist.strings = (char **)filelist.contents;
- i_opts.pathlist.count = filelist.length;
-
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 8, NULL, 8, NULL);
- git_iterator_free(i);
-
- i_opts.start = "c";
- i_opts.end = NULL;
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- default_icase = git_iterator_ignore_case(i);
- /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */
- expect = ((default_icase) ? 6 : 4);
- expect_iterator_items(i, expect, NULL, expect, NULL);
- git_iterator_free(i);
-
- i_opts.start = NULL;
- i_opts.end = "e";
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- default_icase = git_iterator_ignore_case(i);
- /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */
- expect = ((default_icase) ? 5 : 6);
- expect_iterator_items(i, expect, NULL, expect, NULL);
- git_iterator_free(i);
-
- git_vector_free(&filelist);
- git_tree_free(tree);
-}
-
-void test_repo_iterator__treefilelist_icase(void)
-{
- git_iterator *i;
- git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT;
- git_vector filelist;
- git_tree *tree;
-
- cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb));
- cl_git_pass(git_vector_insert(&filelist, "a"));
- cl_git_pass(git_vector_insert(&filelist, "B"));
- cl_git_pass(git_vector_insert(&filelist, "c"));
- cl_git_pass(git_vector_insert(&filelist, "D"));
- cl_git_pass(git_vector_insert(&filelist, "e"));
- cl_git_pass(git_vector_insert(&filelist, "k.a"));
- cl_git_pass(git_vector_insert(&filelist, "k.b"));
- cl_git_pass(git_vector_insert(&filelist, "k/1"));
- cl_git_pass(git_vector_insert(&filelist, "k/a"));
- cl_git_pass(git_vector_insert(&filelist, "kZZZZ"));
- cl_git_pass(git_vector_insert(&filelist, "L/1"));
-
- g_repo = cl_git_sandbox_init("icase");
- git_repository_head_tree(&tree, g_repo);
-
- i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE;
- i_opts.pathlist.strings = (char **)filelist.contents;
- i_opts.pathlist.count = filelist.length;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 3, NULL, 3, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 1, NULL, 1, NULL);
- git_iterator_free(i);
-
- i_opts.flags = GIT_ITERATOR_IGNORE_CASE;
-
- i_opts.start = "c";
- i_opts.end = "k/D";
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 5, NULL, 5, NULL);
- git_iterator_free(i);
-
- i_opts.start = "k";
- i_opts.end = "k/Z";
- cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts));
- expect_iterator_items(i, 2, NULL, 2, NULL);
- git_iterator_free(i);
-
- git_vector_free(&filelist);
- git_tree_free(tree);
-}
diff --git a/tests/status/worktree.c b/tests/status/worktree.c
index fc4afc6be..97eff0b5c 100644
--- a/tests/status/worktree.c
+++ b/tests/status/worktree.c
@@ -218,6 +218,58 @@ void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void)
cl_assert_equal_i(0, counts.wrong_sorted_path);
}
+static void stage_and_commit(git_repository *repo, const char *path)
+{
+ git_index *index;
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_add_bypath(index, path));
+ cl_repo_commit_from_index(NULL, repo, NULL, 1323847743, "Initial commit\n");
+ git_index_free(index);
+}
+
+void test_status_worktree__within_subdir(void)
+{
+ status_entry_counts counts;
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ char *paths[] = { "zzz_new_dir" };
+ git_strarray pathsArray;
+
+ /* first alter the contents of the worktree */
+ cl_git_mkfile("status/.new_file", "dummy");
+ cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777));
+ cl_git_mkfile("status/zzz_new_dir/new_file", "dummy");
+ cl_git_mkfile("status/zzz_new_file", "dummy");
+ cl_git_mkfile("status/wut", "dummy");
+
+ stage_and_commit(repo, "zzz_new_dir/new_file");
+
+ /* now get status */
+ memset(&counts, 0x0, sizeof(status_entry_counts));
+ counts.expected_entry_count = entry_count4;
+ counts.expected_paths = entry_paths4;
+ counts.expected_statuses = entry_statuses4;
+ counts.debug = true;
+
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
+
+ pathsArray.count = 1;
+ pathsArray.strings = paths;
+ opts.pathspec = pathsArray;
+
+ // We committed zzz_new_dir/new_file above. It shouldn't be reported.
+ cl_git_pass(
+ git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
+ );
+
+ cl_assert_equal_i(0, counts.entry_count);
+ cl_assert_equal_i(0, counts.wrong_status_flags_count);
+ cl_assert_equal_i(0, counts.wrong_sorted_path);
+}
+
/* this test is equivalent to t18-status.c:singlestatus0 */
void test_status_worktree__single_file(void)
{
@@ -657,7 +709,7 @@ void test_status_worktree__conflict_has_no_oid(void)
entry.mode = 0100644;
entry.path = "modified_file";
- git_oid_fromstr(&entry.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ git_oid_fromstr(&entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a");
cl_git_pass(git_repository_index(&index, repo));
cl_git_pass(git_index_conflict_add(index, &entry, &entry, &entry));
@@ -692,16 +744,6 @@ void test_status_worktree__conflict_has_no_oid(void)
git_status_list_free(statuslist);
}
-static void stage_and_commit(git_repository *repo, const char *path)
-{
- git_index *index;
-
- cl_git_pass(git_repository_index(&index, repo));
- cl_git_pass(git_index_add_bypath(index, path));
- cl_repo_commit_from_index(NULL, repo, NULL, 1323847743, "Initial commit\n");
- git_index_free(index);
-}
-
static void assert_ignore_case(
bool should_ignore_case,
int expected_lower_cased_file_status,
@@ -1146,3 +1188,86 @@ void test_status_worktree__update_index_with_symlink_doesnt_change_mode(void)
git_reference_free(head);
}
+static const char *testrepo2_subdir_paths[] = {
+ "subdir/README",
+ "subdir/new.txt",
+ "subdir/subdir2/README",
+ "subdir/subdir2/new.txt",
+};
+
+static const char *testrepo2_subdir_paths_icase[] = {
+ "subdir/new.txt",
+ "subdir/README",
+ "subdir/subdir2/new.txt",
+ "subdir/subdir2/README"
+};
+
+void test_status_worktree__with_directory_in_pathlist(void)
+{
+ git_repository *repo = cl_git_sandbox_init("testrepo2");
+ git_index *index;
+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+ git_status_list *statuslist;
+ const git_status_entry *status;
+ size_t i, entrycount;
+ bool native_ignore_case;
+
+ cl_git_pass(git_repository_index(&index, repo));
+ native_ignore_case =
+ (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0;
+ git_index_free(index);
+
+ opts.pathspec.count = 1;
+ opts.pathspec.strings = malloc(opts.pathspec.count * sizeof(char *));
+ opts.pathspec.strings[0] = "subdir";
+ opts.flags =
+ GIT_STATUS_OPT_DEFAULTS |
+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED |
+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
+
+ opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
+ git_status_list_new(&statuslist, repo, &opts);
+
+ entrycount = git_status_list_entrycount(statuslist);
+ cl_assert_equal_i(4, entrycount);
+
+ for (i = 0; i < entrycount; i++) {
+ status = git_status_byindex(statuslist, i);
+ cl_assert_equal_i(0, status->status);
+ cl_assert_equal_s(native_ignore_case ?
+ testrepo2_subdir_paths_icase[i] :
+ testrepo2_subdir_paths[i],
+ status->index_to_workdir->old_file.path);
+ }
+
+ opts.show = GIT_STATUS_SHOW_INDEX_ONLY;
+ git_status_list_new(&statuslist, repo, &opts);
+
+ entrycount = git_status_list_entrycount(statuslist);
+ cl_assert_equal_i(4, entrycount);
+
+ for (i = 0; i < entrycount; i++) {
+ status = git_status_byindex(statuslist, i);
+ cl_assert_equal_i(0, status->status);
+ cl_assert_equal_s(native_ignore_case ?
+ testrepo2_subdir_paths_icase[i] :
+ testrepo2_subdir_paths[i],
+ status->head_to_index->old_file.path);
+ }
+
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ git_status_list_new(&statuslist, repo, &opts);
+
+ entrycount = git_status_list_entrycount(statuslist);
+ cl_assert_equal_i(4, entrycount);
+
+ for (i = 0; i < entrycount; i++) {
+ status = git_status_byindex(statuslist, i);
+ cl_assert_equal_i(0, status->status);
+ cl_assert_equal_s(native_ignore_case ?
+ testrepo2_subdir_paths_icase[i] :
+ testrepo2_subdir_paths[i],
+ status->index_to_workdir->old_file.path);
+ }
+}
+