Commit 9bea03ce by Russell Belfer

Add INCLUDE_TREES, DONT_AUTOEXPAND iterator flags

This standardizes iterator behavior across all three iterators
(index, tree, and working directory).  Previously the working
directory iterator behaved differently from the other two.

Each iterator can now operate in one of three modes:

1. *No tree results, auto expand trees* means that only non-
   tree items will be returned and when a tree/directory is
   encountered, we will automatically descend into it.
2. *Tree results, auto expand trees* means that results will
   be given for every item found, including trees, but you
   only need to call normal git_iterator_advance to yield
   every item (i.e. trees returned with pre-order iteration).
3. *Tree results, no auto expand* means that calling the
   normal git_iterator_advance when looking at a tree will
   not descend into the tree, but will skip over it to the
   next entry in the parent.

Previously, behavior 1 was the only option for index and tree
iterators, and behavior 3 was the only option for workdir.

The main public API implications of this are that the
`git_iterator_advance_into()` call is now valid for all
iterators, not just working directory iterators, and all the
existing uses of working directory iterators explicitly use
the GIT_ITERATOR_DONT_AUTOEXPAND (for now).

Interestingly, the majority of the implementation was in the
index iterator, since there are no tree entries there and now
have to fake them.  The tree and working directory iterators
only required small modifications.
parent cc216a01
......@@ -1243,7 +1243,8 @@ int git_checkout_iterator(
if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 ||
(error = git_iterator_for_workdir(
&workdir, data.repo, iterflags, data.pfx, data.pfx)) < 0 ||
&workdir, data.repo, iterflags | GIT_ITERATOR_DONT_AUTOEXPAND,
data.pfx, data.pfx)) < 0 ||
(error = git_iterator_for_tree(
&baseline, data.opts.baseline, iterflags, data.pfx, data.pfx)) < 0)
goto cleanup;
......
......@@ -705,9 +705,19 @@ int git_diff__from_iterators(
git_iterator_current_is_ignored(new_iter))
git_buf_sets(&ignore_prefix, nitem->path);
if (git_iterator_advance_into(&nitem, new_iter) < 0)
goto fail;
/* advance into directory */
error = git_iterator_advance_into(&nitem, new_iter);
/* if directory is empty, can't advance into it, so skip */
if (error == GIT_ENOTFOUND) {
giterr_clear();
error = git_iterator_advance(&nitem, new_iter);
git_buf_clear(&ignore_prefix);
}
if (error < 0)
goto fail;
continue;
}
}
......@@ -791,7 +801,7 @@ fail:
git_iterator *a = NULL, *b = NULL; \
char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \
GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
error = git_diff__from_iterators(diff, repo, a, b, opts); \
git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
} while (0)
......@@ -831,7 +841,7 @@ int git_diff_tree_to_index(
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
git_iterator_for_index(&b, index, 0, pfx, pfx)
git_iterator_for_index(&b, index, 0, pfx, pfx)
);
return error;
......@@ -852,7 +862,8 @@ int git_diff_index_to_workdir(
DIFF_FROM_ITERATORS(
git_iterator_for_index(&a, index, 0, pfx, pfx),
git_iterator_for_workdir(&b, repo, 0, pfx, pfx)
git_iterator_for_workdir(
&b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
);
return error;
......@@ -871,7 +882,8 @@ int git_diff_tree_to_workdir(
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),
git_iterator_for_workdir(&b, repo, 0, pfx, pfx)
git_iterator_for_workdir(
&b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)
);
return error;
......
......@@ -26,11 +26,16 @@ typedef enum {
GIT_ITERATOR_IGNORE_CASE = (1 << 0),
/** force case sensitivity for entry sort order */
GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1),
/** return tree items in addition to blob items */
GIT_ITERATOR_INCLUDE_TREES = (1 << 2),
/** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */
GIT_ITERATOR_DONT_AUTOEXPAND = (1 << 3),
} git_iterator_flag_t;
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 *);
......@@ -102,12 +107,40 @@ GIT_INLINE(int) git_iterator_current(
return iter->cb->current(entry, iter);
}
/**
* Advance to the next item for the iterator.
*
* If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If
* GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree
* item will skip over all the items under that tree.
*/
GIT_INLINE(int) git_iterator_advance(
const git_index_entry **entry, git_iterator *iter)
{
return iter->cb->advance(entry, iter);
}
/**
* Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set).
*
* git_iterator_advance() steps through all items being iterated over
* (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES),
* but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next
* sibling of a tree instead of going to the first child of the tree. In
* that case, use this function to advance to the first child of the tree.
*
* If the current item is not a tree, this is a no-op.
*
* For working directory iterators only, a tree (i.e. directory) can be
* empty. In that case, this function returns GIT_ENOTFOUND and does not
* advance. That can't happen for tree and index iterators.
*/
GIT_INLINE(int) git_iterator_advance_into(
const git_index_entry **entry, git_iterator *iter)
{
return iter->cb->advance_into(entry, iter);
}
GIT_INLINE(int) git_iterator_seek(
git_iterator *iter, const char *prefix)
{
......@@ -148,33 +181,13 @@ GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter)
extern int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case);
extern int git_iterator_current_tree_entry(
const git_tree_entry **tree_entry, git_iterator *iter);
const git_tree_entry **entry_out, git_iterator *iter);
extern int git_iterator_current_parent_tree(
const git_tree **tree_ptr, git_iterator *iter, const char *parent_path);
const git_tree **tree_out, git_iterator *iter, const char *parent_path);
extern bool git_iterator_current_is_ignored(git_iterator *iter);
/**
* Iterate into a directory.
*
* Workdir iterators do not automatically descend into directories (so that
* when comparing two iterator entries you can detect a newly created
* directory in the workdir). As a result, you may get S_ISDIR items from
* a workdir iterator. If you wish to iterate over the contents of the
* directories you encounter, then call this function when you encounter
* a directory.
*
* If there are no files in the directory, this will end up acting like a
* regular advance and will skip past the directory, so you should be
* prepared for that case.
*
* On non-workdir iterators or if not pointing at a directory, this is a
* no-op and will not advance the iterator.
*/
extern int git_iterator_advance_into(
const git_index_entry **entry, git_iterator *iter);
extern int git_iterator_cmp(
git_iterator *iter, const char *path_prefix);
......
......@@ -538,7 +538,8 @@ static void workdir_iterator_test(
int count = 0, count_all = 0, count_all_post_reset = 0;
git_repository *repo = cl_git_sandbox_init(sandbox);
cl_git_pass(git_iterator_for_workdir(&i, repo, 0, start, end));
cl_git_pass(git_iterator_for_workdir(
&i, repo, GIT_ITERATOR_DONT_AUTOEXPAND, start, end));
cl_git_pass(git_iterator_current(&entry, i));
while (entry != NULL) {
......@@ -735,8 +736,8 @@ void test_diff_iterator__workdir_builtin_ignores(void)
cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777));
cl_git_mkfile("attr/sub/.git", "whatever");
cl_git_pass(
git_iterator_for_workdir(&i, repo, 0, "dir", "sub/sub/file"));
cl_git_pass(git_iterator_for_workdir(
&i, repo, GIT_ITERATOR_DONT_AUTOEXPAND, "dir", "sub/sub/file"));
cl_git_pass(git_iterator_current(&entry, i));
for (idx = 0; entry != NULL; ++idx) {
......@@ -771,10 +772,7 @@ static void check_wd_first_through_third_range(
for (idx = 0; entry != NULL; ++idx) {
cl_assert_equal_s(expected[idx], entry->path);
if (S_ISDIR(entry->mode))
cl_git_pass(git_iterator_advance_into(&entry, i));
else
cl_git_pass(git_iterator_advance(&entry, i));
cl_git_pass(git_iterator_advance(&entry, i));
}
cl_assert(expected[idx] == NULL);
......
......@@ -20,11 +20,15 @@ static void expect_iterator_items(
{
const git_index_entry *entry;
int count;
int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES);
count = 0;
cl_git_pass(git_iterator_current(&entry, i));
while (entry != NULL) {
if (no_trees)
cl_assert(entry->mode != GIT_FILEMODE_TREE);
count++;
cl_git_pass(git_iterator_advance(&entry, i));
......@@ -41,6 +45,9 @@ static void expect_iterator_items(
cl_git_pass(git_iterator_current(&entry, i));
while (entry != NULL) {
if (no_trees)
cl_assert(entry->mode != GIT_FILEMODE_TREE);
count++;
if (entry->mode == GIT_FILEMODE_TREE)
......@@ -79,11 +86,23 @@ void test_repo_iterator__index(void)
cl_git_pass(git_repository_index(&index, g_repo));
/* normal index iteration */
/* autoexpand with no tree entries for index */
cl_git_pass(git_iterator_for_index(&i, index, 0, NULL, NULL));
expect_iterator_items(i, 20, 20);
git_iterator_free(i);
/* auto expand with tree entries */
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
expect_iterator_items(i, 22, 22);
git_iterator_free(i);
/* no auto expand (implies trees included) */
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
expect_iterator_items(i, 12, 22);
git_iterator_free(i);
git_index_free(index);
}
......@@ -99,7 +118,7 @@ void test_repo_iterator__index_icase(void)
/* force case sensitivity */
cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE));
/* normal index iteration with range */
/* autoexpand with no tree entries over range */
cl_git_pass(git_iterator_for_index(&i, index, 0, "c", "k/D"));
expect_iterator_items(i, 7, 7);
git_iterator_free(i);
......@@ -108,10 +127,31 @@ void test_repo_iterator__index_icase(void)
expect_iterator_items(i, 3, 3);
git_iterator_free(i);
/* auto expand with tree entries */
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
expect_iterator_items(i, 8, 8);
git_iterator_free(i);
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
expect_iterator_items(i, 4, 4);
git_iterator_free(i);
/* no auto expand (implies trees included) */
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
expect_iterator_items(i, 5, 8);
git_iterator_free(i);
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
expect_iterator_items(i, 1, 4);
git_iterator_free(i);
/* force case insensitivity */
cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE));
/* normal index iteration with range */
/* autoexpand with no tree entries over range */
cl_git_pass(git_iterator_for_index(&i, index, 0, "c", "k/D"));
expect_iterator_items(i, 13, 13);
git_iterator_free(i);
......@@ -120,6 +160,28 @@ void test_repo_iterator__index_icase(void)
expect_iterator_items(i, 5, 5);
git_iterator_free(i);
/* auto expand with tree entries */
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
expect_iterator_items(i, 14, 14);
git_iterator_free(i);
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
expect_iterator_items(i, 6, 6);
git_iterator_free(i);
/* no auto expand (implies trees included) */
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
expect_iterator_items(i, 9, 14);
git_iterator_free(i);
cl_git_pass(git_iterator_for_index(
&i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
expect_iterator_items(i, 1, 6);
git_iterator_free(i);
cl_git_pass(git_index_set_caps(index, caps));
git_index_free(index);
}
......@@ -131,11 +193,23 @@ void test_repo_iterator__tree(void)
cl_git_pass(git_repository_head_tree(&head, g_repo));
/* normal tree iteration */
/* auto expand with no tree entries */
cl_git_pass(git_iterator_for_tree(&i, head, 0, NULL, NULL));
expect_iterator_items(i, 20, 20);
git_iterator_free(i);
/* auto expand with tree entries */
cl_git_pass(git_iterator_for_tree(
&i, head, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
expect_iterator_items(i, 22, 22);
git_iterator_free(i);
/* no auto expand (implies trees included) */
cl_git_pass(git_iterator_for_tree(
&i, head, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
expect_iterator_items(i, 12, 22);
git_iterator_free(i);
git_tree_free(head);
}
......@@ -149,7 +223,7 @@ void test_repo_iterator__tree_icase(void)
flag = GIT_ITERATOR_DONT_IGNORE_CASE;
/* normal tree iteration with range */
/* auto expand with no tree entries */
cl_git_pass(git_iterator_for_tree(&i, head, flag, "c", "k/D"));
expect_iterator_items(i, 7, 7);
git_iterator_free(i);
......@@ -158,9 +232,31 @@ void test_repo_iterator__tree_icase(void)
expect_iterator_items(i, 3, 3);
git_iterator_free(i);
/* auto expand with tree entries */
cl_git_pass(git_iterator_for_tree(
&i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
expect_iterator_items(i, 8, 8);
git_iterator_free(i);
cl_git_pass(git_iterator_for_tree(
&i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
expect_iterator_items(i, 4, 4);
git_iterator_free(i);
/* no auto expand (implies trees included) */
cl_git_pass(git_iterator_for_tree(
&i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
expect_iterator_items(i, 5, 8);
git_iterator_free(i);
cl_git_pass(git_iterator_for_tree(
&i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
expect_iterator_items(i, 1, 4);
git_iterator_free(i);
flag = GIT_ITERATOR_IGNORE_CASE;
/* normal tree iteration with range */
/* auto expand with no tree entries */
cl_git_pass(git_iterator_for_tree(&i, head, flag, "c", "k/D"));
expect_iterator_items(i, 13, 13);
git_iterator_free(i);
......@@ -168,15 +264,48 @@ void test_repo_iterator__tree_icase(void)
cl_git_pass(git_iterator_for_tree(&i, head, flag, "k", "k/Z"));
expect_iterator_items(i, 5, 5);
git_iterator_free(i);
/* auto expand with tree entries */
cl_git_pass(git_iterator_for_tree(
&i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
expect_iterator_items(i, 14, 14);
git_iterator_free(i);
cl_git_pass(git_iterator_for_tree(
&i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
expect_iterator_items(i, 6, 6);
git_iterator_free(i);
/* no auto expand (implies trees included) */
cl_git_pass(git_iterator_for_tree(
&i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
expect_iterator_items(i, 9, 14);
git_iterator_free(i);
cl_git_pass(git_iterator_for_tree(
&i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
expect_iterator_items(i, 1, 6);
git_iterator_free(i);
}
void test_repo_iterator__workdir(void)
{
git_iterator *i;
/* normal workdir iteration uses explicit tree expansion */
/* auto expand with no tree entries */
cl_git_pass(git_iterator_for_workdir(&i, g_repo, 0, NULL, NULL));
expect_iterator_items(i, 20, 20);
git_iterator_free(i);
/* auto expand with tree entries */
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL));
expect_iterator_items(i, 22, 22);
git_iterator_free(i);
/* no auto expand (implies trees included) */
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, 0, NULL, NULL));
&i, g_repo, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
expect_iterator_items(i, 12, 22);
git_iterator_free(i);
}
......@@ -188,23 +317,67 @@ void test_repo_iterator__workdir_icase(void)
flag = GIT_ITERATOR_DONT_IGNORE_CASE;
/* normal workdir iteration with range */
/* auto expand with no tree entries */
cl_git_pass(git_iterator_for_workdir(&i, g_repo, flag, "c", "k/D"));
expect_iterator_items(i, 5, 8);
expect_iterator_items(i, 7, 7);
git_iterator_free(i);
cl_git_pass(git_iterator_for_workdir(&i, g_repo, flag, "k", "k/Z"));
expect_iterator_items(i, 3, 3);
git_iterator_free(i);
/* auto expand with tree entries */
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
expect_iterator_items(i, 8, 8);
git_iterator_free(i);
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
expect_iterator_items(i, 4, 4);
git_iterator_free(i);
/* no auto expand (implies trees included) */
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
expect_iterator_items(i, 5, 8);
git_iterator_free(i);
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
expect_iterator_items(i, 1, 4);
git_iterator_free(i);
flag = GIT_ITERATOR_IGNORE_CASE;
/* normal workdir iteration with range */
/* auto expand with no tree entries */
cl_git_pass(git_iterator_for_workdir(&i, g_repo, flag, "c", "k/D"));
expect_iterator_items(i, 9, 14);
expect_iterator_items(i, 13, 13);
git_iterator_free(i);
cl_git_pass(git_iterator_for_workdir(&i, g_repo, flag, "k", "k/Z"));
expect_iterator_items(i, 5, 5);
git_iterator_free(i);
/* auto expand with tree entries */
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D"));
expect_iterator_items(i, 14, 14);
git_iterator_free(i);
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z"));
expect_iterator_items(i, 6, 6);
git_iterator_free(i);
/* no auto expand (implies trees included) */
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D"));
expect_iterator_items(i, 9, 14);
git_iterator_free(i);
cl_git_pass(git_iterator_for_workdir(
&i, g_repo, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z"));
expect_iterator_items(i, 1, 6);
git_iterator_free(i);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment