summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2018-11-12 17:22:47 +0000
committerEdward Thomson <ethomson@edwardthomson.com>2018-11-14 14:03:30 +0000
commitc358bbc5e9f60bb169825c84f2f7f0bf9ee34064 (patch)
tree6a30ec241ef64a1866a33c5569958d6d3242077a
parent11fbead80b425eacf483fe16beaf8891f582f905 (diff)
downloadlibgit2-ethomson/index_iterator.tar.gz
index: introduce git_index_iteratorethomson/index_iterator
Provide a public git_index_iterator API that is backed by an index snapshot. This allows consumers to provide a stable iteration even while manipulating the index during iteration.
-rw-r--r--include/git2/index.h40
-rw-r--r--include/git2/types.h3
-rw-r--r--src/index.c45
-rw-r--r--src/index.h6
-rw-r--r--tests/index/tests.c93
5 files changed, 184 insertions, 3 deletions
diff --git a/include/git2/index.h b/include/git2/index.h
index 35af2e5bf..e43d6f857 100644
--- a/include/git2/index.h
+++ b/include/git2/index.h
@@ -492,6 +492,46 @@ GIT_EXTERN(int) git_index_entry_is_conflict(const git_index_entry *entry);
/**@}*/
+/** @name Index Entry Iteration Functions
+ *
+ * These functions provide an iterator for index entries.
+ */
+/**@{*/
+
+/**
+ * Create an iterator that will return every entry contained in the
+ * index at the time of creation. Entries are returned in order,
+ * sorted by path. This iterator is backed by a snapshot that allows
+ * callers to modify the index while iterating without affecting the
+ * iterator.
+ *
+ * @param iterator_out The newly created iterator
+ * @param index The index to iterate
+ */
+GIT_EXTERN(int) git_index_iterator_new(
+ git_index_iterator **iterator_out,
+ git_index *index);
+
+/**
+ * Return the next index entry in-order from the iterator.
+ *
+ * @param out Pointer to store the index entry in
+ * @param iterator The iterator
+ * @return 0, GIT_ITEROVER on iteration completion or an error code
+ */
+GIT_EXTERN(int) git_index_iterator_next(
+ const git_index_entry **out,
+ git_index_iterator *iterator);
+
+/**
+ * Free the index iterator
+ *
+ * @param iterator The iterator to free
+ */
+GIT_EXTERN(void) git_index_iterator_free(git_index_iterator *iterator);
+
+/**@}*/
+
/** @name Workdir Index Entry Functions
*
* These functions work on index entries specifically in the working
diff --git a/include/git2/types.h b/include/git2/types.h
index 607a62a5a..e77e6288d 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -137,6 +137,9 @@ typedef struct git_treebuilder git_treebuilder;
/** Memory representation of an index file. */
typedef struct git_index git_index;
+/** An iterator for entries in the index. */
+typedef struct git_index_iterator git_index_iterator;
+
/** An iterator for conflicts in the index. */
typedef struct git_index_conflict_iterator git_index_conflict_iterator;
diff --git a/src/index.c b/src/index.c
index 8858d23a1..01f23e5ed 100644
--- a/src/index.c
+++ b/src/index.c
@@ -1976,6 +1976,51 @@ int git_index_has_conflicts(const git_index *index)
return 0;
}
+int git_index_iterator_new(
+ git_index_iterator **iterator_out,
+ git_index *index)
+{
+ git_index_iterator *it;
+ int error;
+
+ assert(iterator_out && index);
+
+ it = git__calloc(1, sizeof(git_index_iterator));
+ GITERR_CHECK_ALLOC(it);
+
+ if ((error = git_index_snapshot_new(&it->snap, index)) < 0) {
+ git__free(it);
+ return error;
+ }
+
+ it->index = index;
+
+ *iterator_out = it;
+ return 0;
+}
+
+int git_index_iterator_next(
+ const git_index_entry **out,
+ git_index_iterator *it)
+{
+ assert(out && it);
+
+ if (it->cur >= git_vector_length(&it->snap))
+ return GIT_ITEROVER;
+
+ *out = (git_index_entry *)git_vector_get(&it->snap, it->cur++);
+ return 0;
+}
+
+void git_index_iterator_free(git_index_iterator *it)
+{
+ if (it == NULL)
+ return;
+
+ git_index_snapshot_release(&it->snap, it->index);
+ git__free(it);
+}
+
int git_index_conflict_iterator_new(
git_index_conflict_iterator **iterator_out,
git_index *index)
diff --git a/src/index.h b/src/index.h
index aa54215dd..982afed3a 100644
--- a/src/index.h
+++ b/src/index.h
@@ -55,6 +55,12 @@ struct git_index {
unsigned int version;
};
+struct git_index_iterator {
+ git_index *index;
+ git_vector snap;
+ size_t cur;
+};
+
struct git_index_conflict_iterator {
git_index *index;
size_t cur;
diff --git a/tests/index/tests.c b/tests/index/tests.c
index a208e2c8f..de57bd898 100644
--- a/tests/index/tests.c
+++ b/tests/index/tests.c
@@ -19,10 +19,10 @@ struct test_entry {
static struct test_entry test_entries[] = {
{4, "Makefile", 5064, 0x4C3F7F33},
- {62, "tests/Makefile", 2631, 0x4C3F7F33},
- {36, "src/index.c", 10014, 0x4C43368D},
{6, "git.git-authors", 2709, 0x4C3F7F33},
- {48, "src/revobject.h", 1448, 0x4C3F7FE2}
+ {36, "src/index.c", 10014, 0x4C43368D},
+ {48, "src/revobject.h", 1448, 0x4C3F7FE2},
+ {62, "tests/Makefile", 2631, 0x4C3F7F33}
};
/* Helpers */
@@ -991,3 +991,90 @@ void test_index_tests__can_lock_index(void)
git_index_free(index);
cl_git_sandbox_cleanup();
}
+
+void test_index_tests__can_iterate(void)
+{
+ git_index *index;
+ git_index_iterator *iterator;
+ const git_index_entry *entry;
+ size_t i, iterator_idx = 0, found = 0;
+ int ret;
+
+ cl_git_pass(git_index_open(&index, TEST_INDEX_PATH));
+ cl_git_pass(git_index_iterator_new(&iterator, index));
+
+ cl_assert(git_vector_is_sorted(&iterator->snap));
+
+ for (i = 0; i < ARRAY_SIZE(test_entries); i++) {
+ /* Advance iterator to next test entry index */
+ do {
+ ret = git_index_iterator_next(&entry, iterator);
+
+ if (ret == GIT_ITEROVER)
+ cl_fail("iterator did not contain all test entries");
+
+ cl_git_pass(ret);
+ } while (iterator_idx++ < test_entries[i].index);
+
+ cl_assert_equal_s(entry->path, test_entries[i].path);
+ cl_assert_equal_i(entry->mtime.seconds, test_entries[i].mtime);
+ cl_assert_equal_i(entry->file_size, test_entries[i].file_size);
+ found++;
+ }
+
+ while ((ret = git_index_iterator_next(&entry, iterator)) == 0)
+ ;
+
+ if (ret != GIT_ITEROVER)
+ cl_git_fail(ret);
+
+ cl_assert_equal_i(found, ARRAY_SIZE(test_entries));
+
+ git_index_iterator_free(iterator);
+ git_index_free(index);
+}
+
+void test_index_tests__can_modify_while_iterating(void)
+{
+ git_index *index;
+ git_index_iterator *iterator;
+ const git_index_entry *entry;
+ git_index_entry new_entry = {{0}};
+ size_t expected = 0, seen = 0;
+ int ret;
+
+ cl_git_pass(git_index_open(&index, TEST_INDEX_PATH));
+ cl_git_pass(git_index_iterator_new(&iterator, index));
+
+ expected = git_index_entrycount(index);
+ cl_assert(git_vector_is_sorted(&iterator->snap));
+
+ /*
+ * After we've counted the entries, add a new one and change another;
+ * ensure that our iterator is backed by a snapshot and thus returns
+ * the number of entries from when the iterator was created.
+ */
+ cl_git_pass(git_oid_fromstr(&new_entry.id, "8312e0a89a9cbab77c732b6bc39b51a783e3a318"));
+ new_entry.path = "newfile";
+ new_entry.mode = GIT_FILEMODE_BLOB;
+ cl_git_pass(git_index_add(index, &new_entry));
+
+ cl_git_pass(git_oid_fromstr(&new_entry.id, "4141414141414141414141414141414141414141"));
+ new_entry.path = "Makefile";
+ new_entry.mode = GIT_FILEMODE_BLOB;
+ cl_git_pass(git_index_add(index, &new_entry));
+
+ while (true) {
+ ret = git_index_iterator_next(&entry, iterator);
+
+ if (ret == GIT_ITEROVER)
+ break;
+
+ seen++;
+ }
+
+ cl_assert_equal_i(expected, seen);
+
+ git_index_iterator_free(iterator);
+ git_index_free(index);
+}