diff options
41 files changed, 6594 insertions, 4490 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e9ca156a..924cfa187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,17 @@ v0.24 + 1 * `git_commit_create_buffer()` creates a commit and writes it into a user-provided buffer instead of writing it into the object db. +* `git_blob_create_fromstream()` and + `git_blob_create_fromstream_commit()` allow you to create a blob by + writing into a stream. Useful when you do not know the final size or + want to copy the contents from another stream. + ### API removals +* `git_blob_create_fromchunks()` has been removed in favour of + `git_blob_create_fromstream()`. + + ### Breaking API changes v0.24 @@ -122,12 +131,15 @@ v0.24 `GIT_CONFIG_LEVEL_PROGRAMDATA` which represent a rough Windows equivalent to the system level configuration. -* `git_rebase_init()` not also takes a merge options. +* `git_rebase_options` now has a `merge_options` field. * The index no longer performs locking itself. This is not something users of the library should have been relying on as it's not part of the concurrency guarantees. +* `git_remote_connect()` now takes a `custom_headers` argument to set + the extra HTTP header fields to send. + v0.23 ------ diff --git a/CMakeLists.txt b/CMakeLists.txt index 144e34857..17b5fba7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -580,8 +580,10 @@ IF (CMAKE_SIZEOF_VOID_P EQUAL 8) ADD_DEFINITIONS(-DGIT_ARCH_64) ELSEIF (CMAKE_SIZEOF_VOID_P EQUAL 4) ADD_DEFINITIONS(-DGIT_ARCH_32) +ELSEIF (CMAKE_SIZEOF_VOID_P) + MESSAGE(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") ELSE() - MESSAGE(FATAL_ERROR "Unsupported architecture") + MESSAGE(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") ENDIF() # Compile and link libgit2 diff --git a/include/git2/blob.h b/include/git2/blob.h index 9a57c37f5..6377fc2a2 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -192,6 +192,49 @@ GIT_EXTERN(int) git_blob_create_fromchunks( void *payload); /** + * Create a stream to write a new blob into the object db + * + * This function may need to buffer the data on disk and will in + * general not be the right choice if you know the size of the data + * to write. If you have data in memory, use + * `git_blob_create_frombuffer()`. If you do not, but know the size of + * the contents (and don't want/need to perform filtering), use + * `git_odb_open_wstream()`. + * + * Don't close this stream yourself but pass it to + * `git_blob_create_fromstream_commit()` to commit the write to the + * object db and get the object id. + * + * If the `hintpath` parameter is filled, it will be used to determine + * what git filters should be applied to the object before it is written + * to the object database. + * + * @param out the stream into which to write + * @param repo Repository where the blob will be written. + * This repository can be bare or not. + * @param hintpath If not NULL, will be used to select data filters + * to apply onto the content of the blob to be created. + * @return 0 or error code + */ +GIT_EXTERN(int) git_blob_create_fromstream( + git_writestream **out, + git_repository *repo, + const char *hintpath); + +/** + * Close the stream and write the blob to the object db + * + * The stream will be closed and freed. + * + * @param out the id of the new blob + * @param stream the stream to close + * @return 0 or an error code + */ +GIT_EXTERN(int) git_blob_create_fromstream_commit( + git_oid *out, + git_writestream *stream); + +/** * Write an in-memory buffer to the ODB as a blob * * @param id return the id of the written blob @@ -216,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/array.h b/src/array.h index 7cd9b7153..78d321e82 100644 --- a/src/array.h +++ b/src/array.h @@ -82,4 +82,44 @@ on_oom: #define git_array_valid_index(a, i) ((i) < (a).size) +#define git_array_foreach(a, i, element) \ + for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++) + + +GIT_INLINE(int) git_array__search( + size_t *out, + void *array_ptr, + size_t item_size, + size_t array_len, + int (*compare)(const void *, const void *), + const void *key) +{ + size_t lim; + unsigned char *part, *array = array_ptr, *base = array_ptr; + int cmp = -1; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1) * item_size; + cmp = (*compare)(key, part); + + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1 * item_size; + lim--; + } /* else take left partition */ + } + + if (out) + *out = (base - array) / item_size; + + return (cmp == 0) ? 0 : GIT_ENOTFOUND; +} + +#define git_array_search(out, a, cmp, key) \ + git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \ + (cmp), (key)) + #endif diff --git a/src/blob.c b/src/blob.c index ad0f4ac62..1926c9e58 100644 --- a/src/blob.c +++ b/src/blob.c @@ -274,64 +274,96 @@ int git_blob_create_fromdisk( return error; } -#define BUFFER_SIZE 4096 +typedef struct { + git_writestream parent; + git_filebuf fbuf; + git_repository *repo; + char *hintpath; +} blob_writestream; + +static int blob_writestream_close(git_writestream *_stream) +{ + blob_writestream *stream = (blob_writestream *) _stream; -int git_blob_create_fromchunks( - git_oid *id, - git_repository *repo, - const char *hintpath, - int (*source_cb)(char *content, size_t max_length, void *payload), - void *payload) + git_filebuf_cleanup(&stream->fbuf); + return 0; +} + +static void blob_writestream_free(git_writestream *_stream) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + git_filebuf_cleanup(&stream->fbuf); + git__free(stream->hintpath); + git__free(stream); +} + +static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + return git_filebuf_write(&stream->fbuf, buffer, len); +} + +int git_blob_create_fromstream(git_writestream **out, git_repository *repo, const char *hintpath) { int error; - char *content = NULL; - git_filebuf file = GIT_FILEBUF_INIT; git_buf path = GIT_BUF_INIT; + blob_writestream *stream; - assert(id && repo && source_cb); + assert(out && repo); - if ((error = git_buf_joinpath( - &path, git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0) - goto cleanup; + stream = git__calloc(1, sizeof(blob_writestream)); + GITERR_CHECK_ALLOC(stream); - content = git__malloc(BUFFER_SIZE); - GITERR_CHECK_ALLOC(content); + if (hintpath) { + stream->hintpath = git__strdup(hintpath); + GITERR_CHECK_ALLOC(stream->hintpath); + } - if ((error = git_filebuf_open( - &file, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY, 0666)) < 0) + stream->repo = repo; + stream->parent.write = blob_writestream_write; + stream->parent.close = blob_writestream_close; + stream->parent.free = blob_writestream_free; + + if ((error = git_buf_joinpath(&path, + git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0) goto cleanup; - while (1) { - int read_bytes = source_cb(content, BUFFER_SIZE, payload); + if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY, + 0666, 2 * 1024 * 1024)) < 0) + goto cleanup; - if (!read_bytes) - break; + *out = (git_writestream *) stream; - if (read_bytes > BUFFER_SIZE) { - giterr_set(GITERR_OBJECT, "Invalid chunk size while creating blob"); - error = GIT_EBUFS; - } else if (read_bytes < 0) { - error = giterr_set_after_callback(read_bytes); - } else { - error = git_filebuf_write(&file, content, read_bytes); - } +cleanup: + if (error < 0) + blob_writestream_free((git_writestream *) stream); - if (error < 0) - goto cleanup; - } + git_buf_free(&path); + return error; +} - if ((error = git_filebuf_flush(&file)) < 0) +int git_blob_create_fromstream_commit(git_oid *out, git_writestream *_stream) +{ + int error; + blob_writestream *stream = (blob_writestream *) _stream; + + /* + * We can make this more officient by avoiding writing to + * disk, but for now let's re-use the helper functions we + * have. + */ + if ((error = git_filebuf_flush(&stream->fbuf)) < 0) goto cleanup; - error = git_blob__create_from_paths( - id, NULL, repo, file.path_lock, hintpath, 0, hintpath != NULL); + error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock, + stream->hintpath, 0, !!stream->hintpath); cleanup: - git_buf_free(&path); - git_filebuf_cleanup(&file); - git__free(content); - + blob_writestream_free(_stream); return error; + } int git_blob_is_binary(const git_blob *blob) 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 **) ¤t, (git_object *) commit) < 0) + if (git_commit_dup(¤t, (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/filebuf.c b/src/filebuf.c index 101d5082a..6eee530ee 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -273,6 +273,11 @@ cleanup: int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) { + return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE); +} + +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size) +{ int compression, error = -1; size_t path_len, alloc_len; @@ -286,7 +291,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode if (flags & GIT_FILEBUF_DO_NOT_BUFFER) file->do_not_buffer = true; - file->buf_size = WRITE_BUFFER_SIZE; + file->buf_size = size; file->buf_pos = 0; file->fd = -1; file->last_error = BUFERR_OK; diff --git a/src/filebuf.h b/src/filebuf.h index f4d255b0a..467708d45 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -79,6 +79,7 @@ int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode); +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size); int git_filebuf_commit(git_filebuf *lock); int git_filebuf_commit_at(git_filebuf *lock, const char *path); void git_filebuf_cleanup(git_filebuf *lock); diff --git a/src/index.c b/src/index.c index 62aacf959..63e47965a 100644 --- a/src/index.c +++ b/src/index.c @@ -2836,7 +2836,7 @@ static int read_tree_cb( return -1; entry->mode = tentry->attr; - entry->id = tentry->oid; + git_oid_cpy(&entry->id, git_tree_entry_id(tentry)); /* look for corresponding old entry and copy data to new entry */ if (data->old_entries != NULL && diff --git a/src/indexer.c b/src/indexer.c index 1ffbc2790..ce039257d 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -914,12 +914,17 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) git_filebuf index_file = {0}; void *packfile_trailer; + if (!idx->parsed_header) { + giterr_set(GITERR_INDEXER, "incomplete pack header"); + return -1; + } + if (git_hash_ctx_init(&ctx) < 0) return -1; /* Test for this before resolve_deltas(), as it plays with idx->off */ - if (idx->off < idx->pack->mwf.size - 20) { - giterr_set(GITERR_INDEXER, "Unexpected data at the end of the pack"); + if (idx->off + 20 < idx->pack->mwf.size) { + giterr_set(GITERR_INDEXER, "unexpected data at the end of the pack"); return -1; } diff --git a/src/iterator.c b/src/iterator.c index 024a97573..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/src/push.c b/src/push.c index 3c9fa2f1b..0747259c8 100644 --- a/src/push.c +++ b/src/push.c @@ -374,9 +374,9 @@ static int enqueue_object( case GIT_OBJ_COMMIT: return 0; case GIT_OBJ_TREE: - return git_packbuilder_insert_tree(pb, &entry->oid); + return git_packbuilder_insert_tree(pb, entry->oid); default: - return git_packbuilder_insert(pb, &entry->oid, entry->filename); + return git_packbuilder_insert(pb, entry->oid, entry->filename); } } @@ -396,7 +396,7 @@ static int queue_differences( const git_tree_entry *d_entry = git_tree_entry_byindex(delta, j); int cmp = 0; - if (!git_oid__cmp(&b_entry->oid, &d_entry->oid)) + if (!git_oid__cmp(b_entry->oid, d_entry->oid)) goto loop; cmp = strcmp(b_entry->filename, d_entry->filename); @@ -407,15 +407,15 @@ static int queue_differences( git_tree_entry__is_tree(b_entry) && git_tree_entry__is_tree(d_entry)) { /* Add the right-hand entry */ - if ((error = git_packbuilder_insert(pb, &d_entry->oid, + if ((error = git_packbuilder_insert(pb, d_entry->oid, d_entry->filename)) < 0) goto on_error; /* Acquire the subtrees and recurse */ if ((error = git_tree_lookup(&b_child, - git_tree_owner(base), &b_entry->oid)) < 0 || + git_tree_owner(base), b_entry->oid)) < 0 || (error = git_tree_lookup(&d_child, - git_tree_owner(delta), &d_entry->oid)) < 0 || + git_tree_owner(delta), d_entry->oid)) < 0 || (error = queue_differences(b_child, d_child, pb)) < 0) goto on_error; diff --git a/src/submodule.c b/src/submodule.c index 3f39b9ef0..c903cf939 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -1417,7 +1417,7 @@ static int submodule_update_head(git_submodule *submodule) git_tree_entry_bypath(&te, head, submodule->path) < 0) giterr_clear(); else - submodule_update_from_head_data(submodule, te->attr, &te->oid); + submodule_update_from_head_data(submodule, te->attr, git_tree_entry_id(te)); git_tree_entry_free(te); git_tree_free(head); diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 6363378ec..02e1ecf74 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -721,18 +721,39 @@ static int add_push_report_pkt(git_push *push, git_pkt *pkt) return 0; } -static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt) +static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt, git_buf *data_pkt_buf) { git_pkt *pkt; - const char *line = data_pkt->data, *line_end; - size_t line_len = data_pkt->len; + const char *line, *line_end; + size_t line_len; int error; + int reading_from_buf = data_pkt_buf->size > 0; + + if (reading_from_buf) { + /* We had an existing partial packet, so add the new + * packet to the buffer and parse the whole thing */ + git_buf_put(data_pkt_buf, data_pkt->data, data_pkt->len); + line = data_pkt_buf->ptr; + line_len = data_pkt_buf->size; + } + else { + line = data_pkt->data; + line_len = data_pkt->len; + } while (line_len > 0) { error = git_pkt_parse_line(&pkt, line, &line_end, line_len); - if (error < 0) - return error; + if (error == GIT_EBUFS) { + /* Buffer the data when the inner packet is split + * across multiple sideband packets */ + if (!reading_from_buf) + git_buf_put(data_pkt_buf, line, line_len); + error = 0; + goto done; + } + else if (error < 0) + goto done; /* Advance in the buffer */ line_len -= (line_end - line); @@ -743,10 +764,15 @@ static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt) git_pkt_free(pkt); if (error < 0 && error != GIT_ITEROVER) - return error; + goto done; } - return 0; + error = 0; + +done: + if (reading_from_buf) + git_buf_consume(data_pkt_buf, line_end); + return error; } static int parse_report(transport_smart *transport, git_push *push) @@ -755,6 +781,7 @@ static int parse_report(transport_smart *transport, git_push *push) const char *line_end = NULL; gitno_buffer *buf = &transport->buffer; int error, recvd; + git_buf data_pkt_buf = GIT_BUF_INIT; for (;;) { if (buf->offset > 0) @@ -763,16 +790,21 @@ static int parse_report(transport_smart *transport, git_push *push) else error = GIT_EBUFS; - if (error < 0 && error != GIT_EBUFS) - return -1; + if (error < 0 && error != GIT_EBUFS) { + error = -1; + goto done; + } if (error == GIT_EBUFS) { - if ((recvd = gitno_recv(buf)) < 0) - return recvd; + if ((recvd = gitno_recv(buf)) < 0) { + error = recvd; + goto done; + } if (recvd == 0) { giterr_set(GITERR_NET, "early EOF"); - return GIT_EEOF; + error = GIT_EEOF; + goto done; } continue; } @@ -784,7 +816,7 @@ static int parse_report(transport_smart *transport, git_push *push) switch (pkt->type) { case GIT_PKT_DATA: /* This is a sideband packet which contains other packets */ - error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt); + error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt, &data_pkt_buf); break; case GIT_PKT_ERR: giterr_set(GITERR_NET, "report-status: Error reported: %s", @@ -805,12 +837,24 @@ static int parse_report(transport_smart *transport, git_push *push) git_pkt_free(pkt); /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */ - if (error == GIT_ITEROVER) - return 0; + if (error == GIT_ITEROVER) { + error = 0; + if (data_pkt_buf.size > 0) { + /* If there was data remaining in the pack data buffer, + * then the server sent a partial pkt-line */ + giterr_set(GITERR_NET, "Incomplete pack data pkt-line"); + error = GIT_ERROR; + } + goto done; + } - if (error < 0) - return error; + if (error < 0) { + goto done; + } } +done: + git_buf_free(&data_pkt_buf); + return error; } static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec) diff --git a/src/tree.c b/src/tree.c index 48b9f121d..c1bd4597f 100644 --- a/src/tree.c +++ b/src/tree.c @@ -85,9 +85,10 @@ int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2) } /** - * Allocate either from the pool or from the system allocator + * Allocate a new self-contained entry, with enough space after it to + * store the filename and the id. */ -static git_tree_entry *alloc_entry_base(git_pool *pool, const char *filename, size_t filename_len) +static git_tree_entry *alloc_entry(const char *filename, size_t filename_len, const git_oid *id) { git_tree_entry *entry = NULL; size_t tree_len; @@ -95,44 +96,32 @@ static git_tree_entry *alloc_entry_base(git_pool *pool, const char *filename, si TREE_ENTRY_CHECK_NAMELEN(filename_len); if (GIT_ADD_SIZET_OVERFLOW(&tree_len, sizeof(git_tree_entry), filename_len) || - GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1)) + GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, 1) || + GIT_ADD_SIZET_OVERFLOW(&tree_len, tree_len, GIT_OID_RAWSZ)) return NULL; - entry = pool ? git_pool_malloc(pool, tree_len) : - git__malloc(tree_len); + entry = git__calloc(1, tree_len); if (!entry) return NULL; - memset(entry, 0x0, sizeof(git_tree_entry)); - memcpy(entry->filename, filename, filename_len); - entry->filename[filename_len] = 0; - entry->filename_len = (uint16_t)filename_len; - - return entry; -} + { + char *filename_ptr; + void *id_ptr; -/** - * Allocate a tree entry, using the poolin the tree which owns - * it. This is useful when reading trees, so we don't allocate a ton - * of small strings but can use the pool. - */ -static git_tree_entry *alloc_entry_pooled(git_pool *pool, const char *filename, size_t filename_len) -{ - git_tree_entry *entry = NULL; + filename_ptr = ((char *) entry) + sizeof(git_tree_entry); + memcpy(filename_ptr, filename, filename_len); + entry->filename = filename_ptr; - if (!(entry = alloc_entry_base(pool, filename, filename_len))) - return NULL; + id_ptr = filename_ptr + filename_len + 1; + git_oid_cpy(id_ptr, id); + entry->oid = id_ptr; + } - entry->pooled = true; + entry->filename_len = (uint16_t)filename_len; return entry; } -static git_tree_entry *alloc_entry(const char *filename) -{ - return alloc_entry_base(NULL, filename, strlen(filename)); -} - struct tree_key_search { const char *filename; uint16_t filename_len; @@ -174,7 +163,10 @@ static int homing_search_cmp(const void *key, const void *array_member) * around the area for our target file. */ static int tree_key_search( - size_t *at_pos, git_vector *entries, const char *filename, size_t filename_len) + size_t *at_pos, + const git_tree *tree, + const char *filename, + size_t filename_len) { struct tree_key_search ksearch; const git_tree_entry *entry; @@ -187,13 +179,15 @@ static int tree_key_search( /* Initial homing search; find an entry on the tree with * the same prefix as the filename we're looking for */ - if (git_vector_bsearch2(&homing, entries, &homing_search_cmp, &ksearch) < 0) + + if (git_array_search(&homing, + tree->entries, &homing_search_cmp, &ksearch) < 0) return GIT_ENOTFOUND; /* just a signal error; not passed back to user */ /* We found a common prefix. Look forward as long as * there are entries that share the common prefix */ - for (i = homing; i < entries->length; ++i) { - entry = entries->contents[i]; + for (i = homing; i < tree->entries.size; ++i) { + entry = git_array_get(tree->entries, i); if (homing_search_cmp(&ksearch, entry) < 0) break; @@ -213,7 +207,7 @@ static int tree_key_search( i = homing - 1; do { - entry = entries->contents[i]; + entry = git_array_get(tree->entries, i); if (homing_search_cmp(&ksearch, entry) > 0) break; @@ -234,7 +228,7 @@ static int tree_key_search( void git_tree_entry_free(git_tree_entry *entry) { - if (entry == NULL || entry->pooled) + if (entry == NULL) return; git__free(entry); @@ -242,36 +236,26 @@ void git_tree_entry_free(git_tree_entry *entry) int git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source) { - size_t total_size; - git_tree_entry *copy; + git_tree_entry *cpy; assert(source); - GITERR_CHECK_ALLOC_ADD(&total_size, sizeof(git_tree_entry), source->filename_len); - GITERR_CHECK_ALLOC_ADD(&total_size, total_size, 1); - - copy = git__malloc(total_size); - GITERR_CHECK_ALLOC(copy); - - memcpy(copy, source, total_size); + cpy = alloc_entry(source->filename, source->filename_len, source->oid); + if (cpy == NULL) + return -1; - copy->pooled = 0; + cpy->attr = source->attr; - *dest = copy; + *dest = cpy; return 0; } void git_tree__free(void *_tree) { git_tree *tree = _tree; - size_t i; - git_tree_entry *e; - - git_vector_foreach(&tree->entries, i, e) - git_tree_entry_free(e); - git_vector_free(&tree->entries); - git_pool_clear(&tree->pool); + git_odb_object_free(tree->odb_obj); + git_array_clear(tree->entries); git__free(tree); } @@ -294,7 +278,7 @@ const char *git_tree_entry_name(const git_tree_entry *entry) const git_oid *git_tree_entry_id(const git_tree_entry *entry) { assert(entry); - return &entry->oid; + return entry->oid; } git_otype git_tree_entry_type(const git_tree_entry *entry) @@ -315,7 +299,7 @@ int git_tree_entry_to_object( const git_tree_entry *entry) { assert(entry && object_out); - return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJ_ANY); + return git_object_lookup(object_out, repo, entry->oid, GIT_OBJ_ANY); } static const git_tree_entry *entry_fromname( @@ -323,13 +307,10 @@ static const git_tree_entry *entry_fromname( { size_t idx; - /* be safe when we cast away constness - i.e. don't trigger a sort */ - assert(git_vector_is_sorted(&tree->entries)); - - if (tree_key_search(&idx, (git_vector *)&tree->entries, name, name_len) < 0) + if (tree_key_search(&idx, tree, name, name_len) < 0) return NULL; - return git_vector_get(&tree->entries, idx); + return git_array_get(tree->entries, idx); } const git_tree_entry *git_tree_entry_byname( @@ -344,7 +325,7 @@ const git_tree_entry *git_tree_entry_byindex( const git_tree *tree, size_t idx) { assert(tree); - return git_vector_get(&tree->entries, idx); + return git_array_get(tree->entries, idx); } const git_tree_entry *git_tree_entry_byid( @@ -355,8 +336,8 @@ const git_tree_entry *git_tree_entry_byid( assert(tree); - git_vector_foreach(&tree->entries, i, e) { - if (memcmp(&e->oid.id, &id->id, sizeof(id->id)) == 0) + git_array_foreach(tree->entries, i, e) { + if (memcmp(&e->oid->id, &id->id, sizeof(id->id)) == 0) return e; } @@ -365,7 +346,6 @@ const git_tree_entry *git_tree_entry_byid( int git_tree__prefix_position(const git_tree *tree, const char *path) { - const git_vector *entries = &tree->entries; struct tree_key_search ksearch; size_t at_pos, path_len; @@ -378,21 +358,20 @@ int git_tree__prefix_position(const git_tree *tree, const char *path) ksearch.filename = path; ksearch.filename_len = (uint16_t)path_len; - /* be safe when we cast away constness - i.e. don't trigger a sort */ - assert(git_vector_is_sorted(&tree->entries)); - /* Find tree entry with appropriate prefix */ - git_vector_bsearch2( - &at_pos, (git_vector *)entries, &homing_search_cmp, &ksearch); + git_array_search( + &at_pos, tree->entries, &homing_search_cmp, &ksearch); - for (; at_pos < entries->length; ++at_pos) { - const git_tree_entry *entry = entries->contents[at_pos]; + for (; at_pos < tree->entries.size; ++at_pos) { + const git_tree_entry *entry = git_array_get(tree->entries, at_pos); if (homing_search_cmp(&ksearch, entry) < 0) break; } for (; at_pos > 0; --at_pos) { - const git_tree_entry *entry = entries->contents[at_pos - 1]; + const git_tree_entry *entry = + git_array_get(tree->entries, at_pos - 1); + if (homing_search_cmp(&ksearch, entry) > 0) break; } @@ -403,7 +382,7 @@ int git_tree__prefix_position(const git_tree *tree, const char *path) size_t git_tree_entrycount(const git_tree *tree) { assert(tree); - return tree->entries.length; + return tree->entries.size; } unsigned int git_treebuilder_entrycount(git_treebuilder *bld) @@ -444,13 +423,18 @@ static int parse_mode(unsigned int *modep, const char *buffer, const char **buff int git_tree__parse(void *_tree, git_odb_object *odb_obj) { git_tree *tree = _tree; - const char *buffer = git_odb_object_data(odb_obj); - const char *buffer_end = buffer + git_odb_object_size(odb_obj); + const char *buffer; + const char *buffer_end; - git_pool_init(&tree->pool, 1); - if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < 0) + if (git_odb_object_dup(&tree->odb_obj, odb_obj) < 0) return -1; + buffer = git_odb_object_data(tree->odb_obj); + buffer_end = buffer + git_odb_object_size(tree->odb_obj); + + git_array_init_to_size(tree->entries, DEFAULT_TREE_SIZE); + GITERR_CHECK_ARRAY(tree->entries); + while (buffer < buffer_end) { git_tree_entry *entry; size_t filename_len; @@ -464,27 +448,21 @@ int git_tree__parse(void *_tree, git_odb_object *odb_obj) return tree_error("Failed to parse tree. Object is corrupted", NULL); filename_len = nul - buffer; - /** Allocate the entry and store it in the entries vector */ + /* Allocate the entry */ { - entry = alloc_entry_pooled(&tree->pool, buffer, filename_len); + entry = git_array_alloc(tree->entries); GITERR_CHECK_ALLOC(entry); - if (git_vector_insert(&tree->entries, entry) < 0) - return -1; - entry->attr = attr; + entry->filename_len = filename_len; + entry->filename = buffer; + entry->oid = (git_oid *) ((char *) buffer + filename_len + 1); } - /* Advance to the ID just after the path */ buffer += filename_len + 1; - - git_oid_fromraw(&entry->oid, (const unsigned char *)buffer); buffer += GIT_OID_RAWSZ; } - /* The tree is sorted by definition. Bad inputs give bad outputs */ - tree->entries.flags |= GIT_VECTOR_SORTED; - return 0; } @@ -517,10 +495,9 @@ static int append_entry( if (!valid_entry_name(bld->repo, filename)) return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); - entry = alloc_entry(filename); + entry = alloc_entry(filename, strlen(filename), id); GITERR_CHECK_ALLOC(entry); - git_oid_cpy(&entry->oid, id); entry->attr = (uint16_t)filemode; git_strmap_insert(bld->map, entry->filename, entry, error); @@ -709,10 +686,10 @@ int git_treebuilder_new( if (source != NULL) { git_tree_entry *entry_src; - git_vector_foreach(&source->entries, i, entry_src) { + git_array_foreach(source->entries, i, entry_src) { if (append_entry( bld, entry_src->filename, - &entry_src->oid, + entry_src->oid, entry_src->attr) < 0) goto on_error; } @@ -764,8 +741,9 @@ int git_treebuilder_insert( pos = git_strmap_lookup_index(bld->map, filename); if (git_strmap_valid_index(bld->map, pos)) { entry = git_strmap_value_at(bld->map, pos); + git_oid_cpy((git_oid *) entry->oid, id); } else { - entry = alloc_entry(filename); + entry = alloc_entry(filename, strlen(filename), id); GITERR_CHECK_ALLOC(entry); git_strmap_insert(bld->map, entry->filename, entry, error); @@ -777,7 +755,6 @@ int git_treebuilder_insert( } } - git_oid_cpy(&entry->oid, id); entry->attr = filemode; if (entry_out) @@ -848,13 +825,12 @@ int git_treebuilder_write(git_oid *oid, git_treebuilder *bld) git_buf_printf(&tree, "%o ", entry->attr); git_buf_put(&tree, entry->filename, entry->filename_len + 1); - git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ); + git_buf_put(&tree, (char *)entry->oid->id, GIT_OID_RAWSZ); if (git_buf_oom(&tree)) error = -1; } - git_vector_free(&entries); if (!error && !(error = git_repository_odb__weakptr(&odb, bld->repo))) @@ -960,7 +936,7 @@ int git_tree_entry_bypath( return git_tree_entry_dup(entry_out, entry); } - if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0) + if (git_tree_lookup(&subtree, root->object.repo, entry->oid) < 0) return -1; error = git_tree_entry_bypath( @@ -984,7 +960,7 @@ static int tree_walk( size_t i; const git_tree_entry *entry; - git_vector_foreach(&tree->entries, i, entry) { + git_array_foreach(tree->entries, i, entry) { if (preorder) { error = callback(path->ptr, entry, payload); if (error < 0) { /* negative value stops iteration */ @@ -1001,7 +977,7 @@ static int tree_walk( git_tree *subtree; size_t path_len = git_buf_len(path); - error = git_tree_lookup(&subtree, tree->object.repo, &entry->oid); + error = git_tree_lookup(&subtree, tree->object.repo, entry->oid); if (error < 0) break; diff --git a/src/tree.h b/src/tree.h index 914d788c8..5e7a66e04 100644 --- a/src/tree.h +++ b/src/tree.h @@ -17,15 +17,14 @@ struct git_tree_entry { uint16_t attr; uint16_t filename_len; - git_oid oid; - bool pooled; - char filename[GIT_FLEX_ARRAY]; + const git_oid *oid; + const char *filename; }; struct git_tree { git_object object; - git_vector entries; - git_pool pool; + git_odb_object *odb_obj; + git_array_t(git_tree_entry) entries; }; struct git_treebuilder { diff --git a/tests/core/array.c b/tests/core/array.c new file mode 100644 index 000000000..375cc8df3 --- /dev/null +++ b/tests/core/array.c @@ -0,0 +1,55 @@ +#include "clar_libgit2.h" +#include "array.h" + +static int int_lookup(const void *k, const void *a) +{ + const int *one = (const int *)k; + int *two = (int *)a; + + return *one - *two; +} + +#define expect_pos(k, n, ret) \ + key = (k); \ + cl_assert_equal_i((ret), \ + git_array_search(&p, integers, int_lookup, &key)); \ + cl_assert_equal_i((n), p); + +void test_core_array__bsearch2(void) +{ + git_array_t(int) integers = GIT_ARRAY_INIT; + int *i, key; + size_t p; + + i = git_array_alloc(integers); *i = 2; + i = git_array_alloc(integers); *i = 3; + i = git_array_alloc(integers); *i = 5; + i = git_array_alloc(integers); *i = 7; + i = git_array_alloc(integers); *i = 7; + i = git_array_alloc(integers); *i = 8; + i = git_array_alloc(integers); *i = 13; + i = git_array_alloc(integers); *i = 21; + i = git_array_alloc(integers); *i = 25; + i = git_array_alloc(integers); *i = 42; + i = git_array_alloc(integers); *i = 69; + i = git_array_alloc(integers); *i = 121; + i = git_array_alloc(integers); *i = 256; + i = git_array_alloc(integers); *i = 512; + i = git_array_alloc(integers); *i = 513; + i = git_array_alloc(integers); *i = 514; + i = git_array_alloc(integers); *i = 516; + i = git_array_alloc(integers); *i = 516; + i = git_array_alloc(integers); *i = 517; + + /* value to search for, expected position, return code */ + expect_pos(3, 1, GIT_OK); + expect_pos(2, 0, GIT_OK); + expect_pos(1, 0, GIT_ENOTFOUND); + expect_pos(25, 8, GIT_OK); + expect_pos(26, 9, GIT_ENOTFOUND); + expect_pos(42, 9, GIT_OK); + expect_pos(50, 10, GIT_ENOTFOUND); + expect_pos(68, 10, GIT_ENOTFOUND); + expect_pos(256, 12, GIT_OK); +} + 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 8417e8ed4..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/object/blob/fromchunks.c b/tests/object/blob/fromchunks.c deleted file mode 100644 index b61cabfe1..000000000 --- a/tests/object/blob/fromchunks.c +++ /dev/null @@ -1,156 +0,0 @@ -#include "clar_libgit2.h" -#include "buffer.h" -#include "posix.h" -#include "path.h" -#include "fileops.h" - -static git_repository *repo; -static char textual_content[] = "libgit2\n\r\n\0"; - -void test_object_blob_fromchunks__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_object_blob_fromchunks__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int text_chunked_source_cb(char *content, size_t max_length, void *payload) -{ - int *count; - - GIT_UNUSED(max_length); - - count = (int *)payload; - (*count)--; - - if (*count == 0) - return 0; - - strcpy(content, textual_content); - return (int)strlen(textual_content); -} - -void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provider(void) -{ - git_oid expected_oid, oid; - git_object *blob; - int howmany = 7; - - cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); - - cl_git_fail_with( - git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY), - GIT_ENOTFOUND); - - cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); - - cl_git_pass(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY)); - cl_assert(git_oid_cmp(&expected_oid, git_object_id(blob)) == 0); - - git_object_free(blob); -} - -void test_object_blob_fromchunks__doesnot_overwrite_an_already_existing_object(void) -{ - git_buf path = GIT_BUF_INIT; - git_buf content = GIT_BUF_INIT; - git_oid expected_oid, oid; - int howmany = 7; - - cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); - - cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); - - /* Let's replace the content of the blob file storage with something else... */ - cl_git_pass(git_buf_joinpath(&path, git_repository_path(repo), "objects/32/1cbdf08803c744082332332838df6bd160f8f9")); - cl_git_pass(p_unlink(git_buf_cstr(&path))); - cl_git_mkfile(git_buf_cstr(&path), "boom"); - - /* ...request a creation of the same blob... */ - howmany = 7; - cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); - - /* ...and ensure the content of the faked blob file hasn't been altered */ - cl_git_pass(git_futils_readbuffer(&content, git_buf_cstr(&path))); - cl_assert(!git__strcmp("boom", git_buf_cstr(&content))); - - git_buf_free(&path); - git_buf_free(&content); -} - -#define GITATTR "* text=auto\n" \ - "*.txt text\n" \ - "*.data binary\n" - -static void write_attributes(git_repository *repo) -{ - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_buf_joinpath(&buf, git_repository_path(repo), "info")); - cl_git_pass(git_buf_joinpath(&buf, git_buf_cstr(&buf), "attributes")); - - cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&buf), 0777)); - cl_git_rewritefile(git_buf_cstr(&buf), GITATTR); - - git_buf_free(&buf); -} - -static void assert_named_chunked_blob(const char *expected_sha, const char *fake_name) -{ - git_oid expected_oid, oid; - int howmany = 7; - - cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); - - cl_git_pass(git_blob_create_fromchunks(&oid, repo, fake_name, text_chunked_source_cb, &howmany)); - cl_assert(git_oid_cmp(&expected_oid, &oid) == 0); -} - -void test_object_blob_fromchunks__creating_a_blob_from_chunks_honors_the_attributes_directives(void) -{ - write_attributes(repo); - - assert_named_chunked_blob("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data"); - assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt"); - assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.dunno"); -} - -static int failing_chunked_source_cb( - char *content, size_t max_length, void *payload) -{ - int *count = (int *)payload; - - GIT_UNUSED(max_length); - - (*count)--; - if (*count == 0) - return -1234; - - strcpy(content, textual_content); - return (int)strlen(textual_content); -} - -void test_object_blob_fromchunks__can_stop_with_error(void) -{ - git_oid expected_oid, oid; - git_object *blob; - int howmany = 7; - - cl_git_pass(git_oid_fromstr( - &expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); - - cl_git_fail_with( - git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY), - GIT_ENOTFOUND); - - cl_git_fail_with(git_blob_create_fromchunks( - &oid, repo, NULL, failing_chunked_source_cb, &howmany), -1234); - - cl_git_fail_with( - git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY), - GIT_ENOTFOUND); -} - diff --git a/tests/object/blob/fromstream.c b/tests/object/blob/fromstream.c new file mode 100644 index 000000000..fb6b0784c --- /dev/null +++ b/tests/object/blob/fromstream.c @@ -0,0 +1,103 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "posix.h" +#include "path.h" +#include "fileops.h" + +static git_repository *repo; +static char textual_content[] = "libgit2\n\r\n\0"; + +void test_object_blob_fromstream__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_object_blob_fromstream__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int text_chunked_source_cb(char *content, size_t max_length, void *payload) +{ + int *count; + + GIT_UNUSED(max_length); + + count = (int *)payload; + (*count)--; + + if (*count == 0) + return 0; + + strcpy(content, textual_content); + return (int)strlen(textual_content); +} + +void test_object_blob_fromstream__multiple_write(void) +{ + git_oid expected_id, id; + git_object *blob; + git_writestream *stream; + int i, howmany = 6; + + cl_git_pass(git_oid_fromstr(&expected_id, "321cbdf08803c744082332332838df6bd160f8f9")); + + cl_git_fail_with(GIT_ENOTFOUND, + git_object_lookup(&blob, repo, &expected_id, GIT_OBJ_ANY)); + + cl_git_pass(git_blob_create_fromstream(&stream, repo, NULL)); + + for (i = 0; i < howmany; i++) + cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); + + cl_git_pass(git_blob_create_fromstream_commit(&id, stream)); + cl_assert_equal_oid(&expected_id, &id); + + cl_git_pass(git_object_lookup(&blob, repo, &expected_id, GIT_OBJ_BLOB)); + + git_object_free(blob); +} + +#define GITATTR "* text=auto\n" \ + "*.txt text\n" \ + "*.data binary\n" + +static void write_attributes(git_repository *repo) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&buf, git_repository_path(repo), "info")); + cl_git_pass(git_buf_joinpath(&buf, git_buf_cstr(&buf), "attributes")); + + cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&buf), 0777)); + cl_git_rewritefile(git_buf_cstr(&buf), GITATTR); + + git_buf_free(&buf); +} + +static void assert_named_chunked_blob(const char *expected_sha, const char *fake_name) +{ + git_oid expected_id, id; + git_writestream *stream; + int i, howmany = 6; + + cl_git_pass(git_oid_fromstr(&expected_id, expected_sha)); + + cl_git_pass(git_blob_create_fromstream(&stream, repo, fake_name)); + + for (i = 0; i < howmany; i++) + cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); + + cl_git_pass(git_blob_create_fromstream_commit(&id, stream)); + + cl_assert_equal_oid(&expected_id, &id); +} + +void test_object_blob_fromstream__creating_a_blob_from_chunks_honors_the_attributes_directives(void) +{ + write_attributes(repo); + + assert_named_chunked_blob("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.dunno"); +} diff --git a/tests/object/tree/attributes.c b/tests/object/tree/attributes.c index 413514c48..8654dfa31 100644 --- a/tests/object/tree/attributes.c +++ b/tests/object/tree/attributes.c @@ -82,6 +82,7 @@ void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from cl_git_pass(git_treebuilder_new(&builder, repo, tree)); entry = git_treebuilder_get(builder, "old_mode.txt"); + cl_assert(entry != NULL); cl_assert_equal_i( GIT_FILEMODE_BLOB, git_tree_entry_filemode(entry)); @@ -92,6 +93,7 @@ void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from cl_git_pass(git_tree_lookup(&tree, repo, &tid2)); entry = git_tree_entry_byname(tree, "old_mode.txt"); + cl_assert(entry != NULL); cl_assert_equal_i( GIT_FILEMODE_BLOB, git_tree_entry_filemode(entry)); 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); + } +} + |