diff --git a/examples/showindex.c b/examples/showindex.c index bf852384c..f4c92f068 100644 --- a/examples/showindex.c +++ b/examples/showindex.c @@ -33,7 +33,7 @@ int main (int argc, char** argv) git_repository_free(repo); } - git_index_read(index); + git_index_read(index, 0); ecount = git_index_entrycount(index); if (!ecount) diff --git a/include/git2/diff.h b/include/git2/diff.h index 5d360bedb..a16f86a46 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -352,10 +352,10 @@ typedef struct { /* options controlling which files are in the diff */ - git_submodule_ignore_t ignore_submodules; /** << submodule ignore rule */ - git_strarray pathspec; /**< defaults to include all paths */ + git_submodule_ignore_t ignore_submodules; /**< submodule ignore rule */ + git_strarray pathspec; /**< defaults to include all paths */ git_diff_notify_cb notify_cb; - void *notify_payload; + void *notify_payload; /* options controlling how to diff text is generated */ @@ -367,9 +367,14 @@ typedef struct { const char *new_prefix; /**< defaults to "b" */ } git_diff_options; +/* The current version of the diff options structure */ #define GIT_DIFF_OPTIONS_VERSION 1 + +/* Stack initializer for diff options. Alternatively use + * `git_diff_options_init` programmatic initialization. + */ #define GIT_DIFF_OPTIONS_INIT \ - {GIT_DIFF_OPTIONS_VERSION, 0, 0, {NULL,0}, NULL, NULL, 3} + {GIT_DIFF_OPTIONS_VERSION, 0, GIT_SUBMODULE_IGNORE_DEFAULT, {NULL,0}, NULL, NULL, 3} /** * When iterating over a diff, callback that will be made per file. @@ -607,6 +612,10 @@ GIT_EXTERN(int) git_diff_tree_to_tree( * The tree you pass will be used for the "old_file" side of the delta, and * the index will be used for the "new_file" side of the delta. * + * If you pass NULL for the index, then the existing index of the `repo` + * will be used. In this case, the index will be refreshed from disk + * (if it has changed) before the diff is generated. + * * @param diff Output pointer to a git_diff pointer to be allocated. * @param repo The repository containing the tree and index. * @param old_tree A git_tree object to diff from, or NULL for empty tree. @@ -631,6 +640,10 @@ GIT_EXTERN(int) git_diff_tree_to_index( * The index will be used for the "old_file" side of the delta, and the * working directory will be used for the "new_file" side of the delta. * + * If you pass NULL for the index, then the existing index of the `repo` + * will be used. In this case, the index will be refreshed from disk + * (if it has changed) before the diff is generated. + * * @param diff Output pointer to a git_diff pointer to be allocated. * @param repo The repository. * @param index The index to diff from; repo index used if NULL. @@ -730,6 +743,23 @@ GIT_EXTERN(int) git_diff_find_similar( git_diff *diff, const git_diff_find_options *options); +/** + * Initialize diff options structure + * + * In most cases, you can probably just use `GIT_DIFF_OPTIONS_INIT` to + * initialize the diff options structure, but in some cases that is not + * going to work. You can call this function instead. Note that you + * must pass both a pointer to the structure to be initialized and the + * `GIT_DIFF_OPTIONS_VERSION` value from the header you compiled with. + * + * @param options Pointer to git_diff_options memory to be initialized + * @param version Should be `GIT_DIFF_OPTIONS_VERSION` + * @return 0 on success, negative on failure (such as unsupported version) + */ +GIT_EXTERN(int) git_diff_options_init( + git_diff_options *options, + unsigned int version); + /**@}*/ diff --git a/include/git2/index.h b/include/git2/index.h index 8064a62ff..a60db370a 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -222,16 +222,23 @@ GIT_EXTERN(unsigned int) git_index_caps(const git_index *index); GIT_EXTERN(int) git_index_set_caps(git_index *index, unsigned int caps); /** - * Update the contents of an existing index object in memory - * by reading from the hard disk. + * Update the contents of an existing index object in memory by reading + * from the hard disk. * - * If the file doesn't exist on the filesystem, the index - * will be cleared from its current content. + * If `force` is true, this performs a "hard" read that discards in-memory + * changes and always reloads the on-disk index data. If there is no + * on-disk version, the index will be cleared. + * + * If `force` is false, this does a "soft" read that reloads the index + * data from disk only if it has changed since the last time it was + * loaded. Purely in-memory index data will be untouched. Be aware: if + * there are changes on disk, unwritten in-memory changes are discarded. * * @param index an existing index object + * @param force if true, always reload, vs. only read if file has changed * @return 0 or an error code */ -GIT_EXTERN(int) git_index_read(git_index *index); +GIT_EXTERN(int) git_index_read(git_index *index, int force); /** * Write an existing index object from memory back to disk diff --git a/include/git2/status.h b/include/git2/status.h index aa934d96b..4ec3432df 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -118,6 +118,9 @@ typedef enum { * case-insensitive order * - GIT_STATUS_OPT_RENAMES_FROM_REWRITES indicates that rename detection * should include rewritten files + * - GIT_STATUS_OPT_NO_REFRESH bypasses the default status behavior of + * doing a "soft" index reload (i.e. reloading the index data if the + * file on disk has been modified outside libgit2). * * Calling `git_status_foreach()` is like calling the extended version * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, @@ -137,6 +140,7 @@ typedef enum { GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9), GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10), GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11), + GIT_STATUS_OPT_NO_REFRESH = (1u << 12), } git_status_opt_t; #define GIT_STATUS_OPT_DEFAULTS \ diff --git a/include/git2/types.h b/include/git2/types.h index 2d18d385a..71d5374d3 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -268,13 +268,18 @@ typedef struct git_submodule git_submodule; * superproject into the current checkout out branch of the submodule. * - GIT_SUBMODULE_UPDATE_NONE: do not update this submodule even when * the commit in the superproject is updated. + * - GIT_SUBMODULE_UPDATE_DEFAULT: not used except as static initializer + * when we don't want any particular update rule to be specified. */ typedef enum { - GIT_SUBMODULE_UPDATE_RESET = -1, + GIT_SUBMODULE_UPDATE_RESET = -1, + GIT_SUBMODULE_UPDATE_CHECKOUT = 1, - GIT_SUBMODULE_UPDATE_REBASE = 2, - GIT_SUBMODULE_UPDATE_MERGE = 3, - GIT_SUBMODULE_UPDATE_NONE = 4 + GIT_SUBMODULE_UPDATE_REBASE = 2, + GIT_SUBMODULE_UPDATE_MERGE = 3, + GIT_SUBMODULE_UPDATE_NONE = 4, + + GIT_SUBMODULE_UPDATE_DEFAULT = 0 } git_submodule_update_t; /** @@ -301,13 +306,18 @@ typedef enum { * only considering changes if the HEAD of submodule has moved from the * value in the superproject. * - GIT_SUBMODULE_IGNORE_ALL: never check if the submodule is dirty + * - GIT_SUBMODULE_IGNORE_DEFAULT: not used except as static initializer + * when we don't want any particular ignore rule to be specified. */ typedef enum { - GIT_SUBMODULE_IGNORE_RESET = -1, /* reset to on-disk value */ - GIT_SUBMODULE_IGNORE_NONE = 1, /* any change or untracked == dirty */ - GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */ - GIT_SUBMODULE_IGNORE_DIRTY = 3, /* only dirty if HEAD moved */ - GIT_SUBMODULE_IGNORE_ALL = 4 /* never dirty */ + GIT_SUBMODULE_IGNORE_RESET = -1, /* reset to on-disk value */ + + GIT_SUBMODULE_IGNORE_NONE = 1, /* any change or untracked == dirty */ + GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */ + GIT_SUBMODULE_IGNORE_DIRTY = 3, /* only dirty if HEAD moved */ + GIT_SUBMODULE_IGNORE_ALL = 4, /* never dirty */ + + GIT_SUBMODULE_IGNORE_DEFAULT = 0 } git_submodule_ignore_t; /** @} */ diff --git a/src/checkout.c b/src/checkout.c index 76edc6a72..dce9afdf4 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -1837,7 +1837,7 @@ static int checkout_data_init( } else { /* otherwise, grab and reload the index */ if ((error = git_repository_index(&data->index, data->repo)) < 0 || - (error = git_index_read(data->index)) < 0) + (error = git_index_read(data->index, true)) < 0) goto cleanup; /* cannot checkout if unresolved conflicts exist */ diff --git a/src/diff.c b/src/diff.c index 37bc737d6..4c33a0213 100644 --- a/src/diff.c +++ b/src/diff.c @@ -63,13 +63,16 @@ static int diff_notify( static int diff_delta__from_one( git_diff *diff, - git_delta_t status, + git_delta_t status, const git_index_entry *entry) { git_diff_delta *delta; const char *matched_pathspec; int notify_res; + if ((entry->flags & GIT_IDXENTRY_VALID) != 0) + return 0; + if (status == GIT_DELTA_IGNORED && DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) return 0; @@ -426,7 +429,7 @@ static int diff_list_apply_options( diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORESTAT) && val) - diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED; + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT; if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && !git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE) && val) @@ -448,6 +451,13 @@ static int diff_list_apply_options( /* add other defaults here */ } + /* Reverse src info if diff is reversed */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + git_iterator_type_t tmp_src = diff->old_src; + diff->old_src = diff->new_src; + diff->new_src = tmp_src; + } + /* if ignore_submodules not explicitly set, check diff config */ if (diff->opts.ignore_submodules <= 0) { const char *str; @@ -484,9 +494,9 @@ static int diff_list_apply_options( return -1; if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - const char *swap = diff->opts.old_prefix; - diff->opts.old_prefix = diff->opts.new_prefix; - diff->opts.new_prefix = swap; + const char *tmp_prefix = diff->opts.old_prefix; + diff->opts.old_prefix = diff->opts.new_prefix; + diff->opts.new_prefix = tmp_prefix; } return 0; @@ -694,9 +704,8 @@ static int maybe_modified( nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); /* support "assume unchanged" (poorly, b/c we still stat everything) */ - if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0) - status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ? - GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED; + if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) + status = GIT_DELTA_UNMODIFIED; /* support "skip worktree" index bit */ else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) @@ -1177,6 +1186,17 @@ int git_diff_tree_to_tree( return error; } +static int diff_load_index(git_index **index, git_repository *repo) +{ + int error = git_repository_index__weakptr(index, repo); + + /* reload the repository index when user did not pass one in */ + if (!error && git_index_read(*index, false) < 0) + giterr_clear(); + + return error; +} + int git_diff_tree_to_index( git_diff **diff, git_repository *repo, @@ -1189,7 +1209,7 @@ int git_diff_tree_to_index( assert(diff && repo); - if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) + if (!index && (error = diff_load_index(&index, repo)) < 0) return error; if (index->ignore_case) { @@ -1232,7 +1252,7 @@ int git_diff_index_to_workdir( assert(diff && repo); - if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) + if (!index && (error = diff_load_index(&index, repo)) < 0) return error; DIFF_FROM_ITERATORS( @@ -1271,11 +1291,15 @@ int git_diff_tree_to_workdir_with_index( { int error = 0; git_diff *d1 = NULL, *d2 = NULL; + git_index *index = NULL; assert(diff && repo); - if (!(error = git_diff_tree_to_index(&d1, repo, old_tree, NULL, opts)) && - !(error = git_diff_index_to_workdir(&d2, repo, NULL, opts))) + if ((error = diff_load_index(&index, repo)) < 0) + return error; + + if (!(error = git_diff_tree_to_index(&d1, repo, old_tree, index, opts)) && + !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) error = git_diff_merge(d1, d2); git_diff_free(d2); @@ -1289,6 +1313,19 @@ int git_diff_tree_to_workdir_with_index( return error; } +int git_diff_options_init(git_diff_options *options, unsigned int version) +{ + git_diff_options template = GIT_DIFF_OPTIONS_INIT; + + if (version != template.version) { + giterr_set(GITERR_INVALID, + "Invalid version %d for git_diff_options", (int)version); + return -1; + } + + memcpy(options, &template, sizeof(*options)); + return 0; +} size_t git_diff_num_deltas(const git_diff *diff) { diff --git a/src/diff.h b/src/diff.h index 270bea071..2c9298a5f 100644 --- a/src/diff.h +++ b/src/diff.h @@ -23,7 +23,7 @@ enum { GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ - GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */ + GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ diff --git a/src/diff_tform.c b/src/diff_tform.c index 9461ca2c8..28a9cc70d 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -46,7 +46,9 @@ fail: } static git_diff_delta *diff_delta__merge_like_cgit( - const git_diff_delta *a, const git_diff_delta *b, git_pool *pool) + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool) { git_diff_delta *dup; @@ -96,15 +98,46 @@ static git_diff_delta *diff_delta__merge_like_cgit( return dup; } -int git_diff_merge( - git_diff *onto, - const git_diff *from) +static git_diff_delta *diff_delta__merge_like_cgit_reversed( + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool) +{ + git_diff_delta *dup; + + /* reversed version of above logic */ + + if (a->status == GIT_DELTA_UNMODIFIED) + return diff_delta__dup(b, pool); + + if ((dup = diff_delta__dup(a, pool)) == NULL) + return NULL; + + if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED) + return dup; + + if (dup->status == GIT_DELTA_DELETED) { + if (b->status == GIT_DELTA_ADDED) + dup->status = GIT_DELTA_UNMODIFIED; + } else { + dup->status = b->status; + } + + git_oid_cpy(&dup->old_file.oid, &b->old_file.oid); + dup->old_file.mode = b->old_file.mode; + dup->old_file.size = b->old_file.size; + dup->old_file.flags = b->old_file.flags; + + return dup; +} + +int git_diff_merge(git_diff *onto, const git_diff *from) { int error = 0; git_pool onto_pool; git_vector onto_new; git_diff_delta *delta; - bool ignore_case = false; + bool ignore_case, reversed; unsigned int i, j; assert(onto && from); @@ -112,26 +145,26 @@ int git_diff_merge( if (!from->deltas.length) return 0; + ignore_case = ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0); + reversed = ((onto->opts.flags & GIT_DIFF_REVERSE) != 0); + + if (ignore_case != ((from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) || + reversed != ((from->opts.flags & GIT_DIFF_REVERSE) != 0)) { + giterr_set(GITERR_INVALID, + "Attempt to merge diffs created with conflicting options"); + return -1; + } + if (git_vector_init( &onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 || git_pool_init(&onto_pool, 1, 0) < 0) return -1; - if ((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0 || - (from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0) - { - ignore_case = true; - - /* This function currently only supports merging diff lists that - * are sorted identically. */ - assert((onto->opts.flags & GIT_DIFF_IGNORE_CASE) != 0 && - (from->opts.flags & GIT_DIFF_IGNORE_CASE) != 0); - } - for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); - int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); + int cmp = !f ? -1 : !o ? 1 : + STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); if (cmp < 0) { delta = diff_delta__dup(o, &onto_pool); @@ -140,7 +173,9 @@ int git_diff_merge( delta = diff_delta__dup(f, &onto_pool); j++; } else { - delta = diff_delta__merge_like_cgit(o, f, &onto_pool); + delta = reversed ? + diff_delta__merge_like_cgit_reversed(o, f, &onto_pool) : + diff_delta__merge_like_cgit(o, f, &onto_pool); i++; j++; } @@ -160,7 +195,11 @@ int git_diff_merge( if (!error) { git_vector_swap(&onto->deltas, &onto_new); git_pool_swap(&onto->pool, &onto_pool); - onto->new_src = from->new_src; + + if ((onto->opts.flags & GIT_DIFF_REVERSE) != 0) + onto->old_src = from->old_src; + else + onto->new_src = from->new_src; /* prefix strings also come from old pool, so recreate those.*/ onto->opts.old_prefix = diff --git a/src/index.c b/src/index.c index 5cdd40aaa..dbf1ab529 100644 --- a/src/index.c +++ b/src/index.c @@ -349,7 +349,7 @@ int git_index_open(git_index **index_out, const char *index_path) *index_out = index; GIT_REFCOUNT_INC(index); - return (index_path != NULL) ? git_index_read(index) : 0; + return (index_path != NULL) ? git_index_read(index, true) : 0; } int git_index_new(git_index **out) @@ -451,11 +451,11 @@ unsigned int git_index_caps(const git_index *index) (index->no_symlinks ? GIT_INDEXCAP_NO_SYMLINKS : 0)); } -int git_index_read(git_index *index) +int git_index_read(git_index *index, int force) { int error = 0, updated; git_buf buffer = GIT_BUF_INIT; - git_futils_filestamp stamp = {0}; + git_futils_filestamp stamp = index->stamp; if (!index->index_file_path) return create_index_error(-1, @@ -464,12 +464,13 @@ int git_index_read(git_index *index) index->on_disk = git_path_exists(index->index_file_path); if (!index->on_disk) { - git_index_clear(index); + if (force) + git_index_clear(index); return 0; } updated = git_futils_filestamp_check(&stamp, index->index_file_path); - if (updated <= 0) + if (updated < 0 || (!updated && !force)) return updated; error = git_futils_readbuffer(&buffer, index->index_file_path); diff --git a/src/status.c b/src/status.c index 2b84794b5..07fdcb5c3 100644 --- a/src/status.c +++ b/src/status.c @@ -251,12 +251,17 @@ int git_status_list_new( return error; /* if there is no HEAD, that's okay - we'll make an empty iterator */ - if (((error = git_repository_head_tree(&head, repo)) < 0) && - error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) { - git_index_free(index); /* release index */ - return error; + if ((error = git_repository_head_tree(&head, repo)) < 0) { + if (error != GIT_ENOTFOUND && error != GIT_EUNBORNBRANCH) + goto done; + giterr_clear(); } + /* refresh index from disk unless prevented */ + if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 && + git_index_read(index, false) < 0) + giterr_clear(); + status = git_status_list_alloc(index); GITERR_CHECK_ALLOC(status); @@ -291,7 +296,7 @@ int git_status_list_new( if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { if ((error = git_diff_tree_to_index( - &status->head2idx, repo, head, NULL, &diffopt)) < 0) + &status->head2idx, repo, head, index, &diffopt)) < 0) goto done; if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && @@ -301,7 +306,7 @@ int git_status_list_new( if (show != GIT_STATUS_SHOW_INDEX_ONLY) { if ((error = git_diff_index_to_workdir( - &status->idx2wd, repo, NULL, &diffopt)) < 0) + &status->idx2wd, repo, index, &diffopt)) < 0) goto done; if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && diff --git a/src/submodule.c b/src/submodule.c index 18d80f0a9..586494fed 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -1527,6 +1527,7 @@ static void submodule_get_wd_status( const git_oid *wd_oid = (sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL; git_tree *sm_head = NULL; + git_index *index = NULL; git_diff_options opt = GIT_DIFF_OPTIONS_INIT; git_diff *diff; @@ -1558,12 +1559,14 @@ static void submodule_get_wd_status( if (ign == GIT_SUBMODULE_IGNORE_NONE) opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + (void)git_repository_index__weakptr(&index, sm_repo); + /* if we don't have an unborn head, check diff with index */ if (git_repository_head_tree(&sm_head, sm_repo) < 0) giterr_clear(); else { /* perform head to index diff on submodule */ - if (git_diff_tree_to_index(&diff, sm_repo, sm_head, NULL, &opt) < 0) + if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0) giterr_clear(); else { if (git_diff_num_deltas(diff) > 0) @@ -1576,7 +1579,7 @@ static void submodule_get_wd_status( } /* perform index-to-workdir diff on submodule */ - if (git_diff_index_to_workdir(&diff, sm_repo, NULL, &opt) < 0) + if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0) giterr_clear(); else { size_t untracked = diff --git a/tests-clar/checkout/head.c b/tests-clar/checkout/head.c index 74c6fb87a..a7a7e9071 100644 --- a/tests-clar/checkout/head.c +++ b/tests-clar/checkout/head.c @@ -54,7 +54,6 @@ void test_checkout_head__with_index_only_tree(void) cl_git_pass(git_checkout_head(g_repo, &opts)); cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read(index)); /* reload if needed */ cl_assert(!git_path_isfile("testrepo/newdir/newfile.txt")); cl_assert(git_index_get_bypath(index, "newdir/newfile.txt", 0) == NULL); diff --git a/tests-clar/diff/blob.c b/tests-clar/diff/blob.c index b51bc0f38..93f20711c 100644 --- a/tests-clar/diff/blob.c +++ b/tests-clar/diff/blob.c @@ -26,9 +26,8 @@ void test_diff_blob__initialize(void) g_repo = cl_git_sandbox_init("attr"); - GIT_INIT_STRUCTURE(&opts, GIT_DIFF_OPTIONS_VERSION); + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); opts.context_lines = 1; - opts.interhunk_lines = 0; memset(&expected, 0, sizeof(expected)); diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index 466d0ef54..33bb561f6 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -221,11 +221,15 @@ static int diff_print_cb( const git_diff_line *line, void *payload) { - GIT_UNUSED(payload); - GIT_UNUSED(delta); - GIT_UNUSED(hunk); - fprintf((FILE *)payload, "%c%.*s", - line->origin, (int)line->content_len, line->content); + FILE *fp = payload; + + GIT_UNUSED(delta); GIT_UNUSED(hunk); + + if (line->origin == GIT_DIFF_LINE_CONTEXT || + line->origin == GIT_DIFF_LINE_ADDITION || + line->origin == GIT_DIFF_LINE_DELETION) + fputc(line->origin, fp); + fwrite(line->content, 1, line->content_len, fp); return 0; } diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c index ca2daf5fb..582174b8b 100644 --- a/tests-clar/diff/tree.c +++ b/tests-clar/diff/tree.c @@ -9,9 +9,7 @@ static diff_expects expect; void test_diff_tree__initialize(void) { - GIT_INIT_STRUCTURE(&opts, GIT_DIFF_OPTIONS_VERSION); - /* The default context lines is set by _INIT which we can't use here */ - opts.context_lines = 3; + cl_git_pass(git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION)); memset(&expect, 0, sizeof(expect)); @@ -91,7 +89,8 @@ void test_diff_tree__0(void) } #define DIFF_OPTS(FLAGS, CTXT) \ - {GIT_DIFF_OPTIONS_VERSION, (FLAGS), 0, {NULL,0}, NULL, NULL, (CTXT), 1} + {GIT_DIFF_OPTIONS_VERSION, (FLAGS), GIT_SUBMODULE_IGNORE_DEFAULT, \ + {NULL,0}, NULL, NULL, (CTXT), 1} void test_diff_tree__options(void) { diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index 60acd365d..df31e7322 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -63,6 +63,60 @@ void test_diff_workdir__to_index(void) git_diff_free(diff); } +void test_diff_workdir__to_index_with_assume_unchanged(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_index *idx = NULL; + diff_expects exp; + const git_index_entry *iep; + git_index_entry ie; + + g_repo = cl_git_sandbox_init("status"); + + /* do initial diff */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + git_diff_free(diff); + + /* mark a couple of entries with ASSUME_UNCHANGED */ + + cl_git_pass(git_repository_index(&idx, g_repo)); + + cl_assert((iep = git_index_get_bypath(idx, "modified_file", 0)) != NULL); + memcpy(&ie, iep, sizeof(ie)); + ie.flags |= GIT_IDXENTRY_VALID; + cl_git_pass(git_index_add(idx, &ie)); + + cl_assert((iep = git_index_get_bypath(idx, "file_deleted", 0)) != NULL); + memcpy(&ie, iep, sizeof(ie)); + ie.flags |= GIT_IDXENTRY_VALID; + cl_git_pass(git_index_add(idx, &ie)); + + cl_git_pass(git_index_write(idx)); + git_index_free(idx); + + /* redo diff and see that entries are skipped */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + git_diff_free(diff); + +} + void test_diff_workdir__to_tree(void) { /* grabbed a couple of commit oids from the history of the attr repo */ @@ -196,6 +250,38 @@ void test_diff_workdir__to_tree(void) git_diff_free(diff); + /* Let's try that once more with a reversed diff */ + + opts.flags |= GIT_DIFF_REVERSE; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, b, NULL, &opts)); + cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_free(diff2); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(16, exp.files); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + + cl_assert_equal_i(12, exp.hunks); + + cl_assert_equal_i(19, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(12, exp.line_dels); + cl_assert_equal_i(4, exp.line_adds); + + git_diff_free(diff); + + /* all done now */ + git_tree_free(a); git_tree_free(b); } @@ -1330,3 +1416,73 @@ void test_diff_workdir__patience_diff(void) git_patch_free(patch); git_diff_free(diff); } + +void test_diff_workdir__with_stale_index(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_index *idx = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("status"); + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* make the in-memory index invalid */ + { + git_repository *r2; + git_index *idx2; + cl_git_pass(git_repository_open(&r2, "status")); + cl_git_pass(git_repository_index(&idx2, r2)); + cl_git_pass(git_index_add_bypath(idx2, "new_file")); + cl_git_pass(git_index_add_bypath(idx2, "subdir/new_file")); + cl_git_pass(git_index_remove_bypath(idx2, "staged_new_file")); + cl_git_pass(git_index_remove_bypath(idx2, "staged_changes_file_deleted")); + cl_git_pass(git_index_write(idx2)); + git_index_free(idx2); + git_repository_free(r2); + } + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_INCLUDE_UNMODIFIED; + + /* first try with index pointer which should prevent reload */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, idx, &opts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(17, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNMODIFIED]); + + git_diff_free(diff); + + /* now let's try without the index pointer which should trigger reload */ + + /* two files that were UNTRACKED should have become UNMODIFIED */ + /* one file that was UNMODIFIED should now have become UNTRACKED */ + /* one file that was DELETED should now be gone completely */ + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(16, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); + cl_assert_equal_i(6, exp.file_status[GIT_DELTA_UNMODIFIED]); + + git_index_free(idx); +} diff --git a/tests-clar/index/addall.c b/tests-clar/index/addall.c index 3b5f5f22d..44c51279d 100644 --- a/tests-clar/index/addall.c +++ b/tests-clar/index/addall.c @@ -68,10 +68,11 @@ static int index_status_cb( return 0; } -static void check_status( +static void check_status_at_line( git_repository *repo, size_t index_adds, size_t index_dels, size_t index_mods, - size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores) + size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores, + const char *file, int line) { index_status_counts vals; @@ -79,15 +80,25 @@ static void check_status( cl_git_pass(git_status_foreach(repo, index_status_cb, &vals)); - cl_assert_equal_sz(index_adds, vals.index_adds); - cl_assert_equal_sz(index_dels, vals.index_dels); - cl_assert_equal_sz(index_mods, vals.index_mods); - cl_assert_equal_sz(wt_adds, vals.wt_adds); - cl_assert_equal_sz(wt_dels, vals.wt_dels); - cl_assert_equal_sz(wt_mods, vals.wt_mods); - cl_assert_equal_sz(ignores, vals.ignores); + clar__assert_equal( + file,line,"wrong index adds", 1, "%"PRIuZ, index_adds, vals.index_adds); + clar__assert_equal( + file,line,"wrong index dels", 1, "%"PRIuZ, index_dels, vals.index_dels); + clar__assert_equal( + file,line,"wrong index mods", 1, "%"PRIuZ, index_mods, vals.index_mods); + clar__assert_equal( + file,line,"wrong workdir adds", 1, "%"PRIuZ, wt_adds, vals.wt_adds); + clar__assert_equal( + file,line,"wrong workdir dels", 1, "%"PRIuZ, wt_dels, vals.wt_dels); + clar__assert_equal( + file,line,"wrong workdir mods", 1, "%"PRIuZ, wt_mods, vals.wt_mods); + clar__assert_equal( + file,line,"wrong ignores", 1, "%"PRIuZ, ignores, vals.ignores); } +#define check_status(R,IA,ID,IM,WA,WD,WM,IG) \ + check_status_at_line(R,IA,ID,IM,WA,WD,WM,IG,__FILE__,__LINE__) + static void check_stat_data(git_index *index, const char *path, bool match) { const git_index_entry *entry; diff --git a/tests-clar/index/names.c b/tests-clar/index/names.c index 87453ecbf..9007b1b15 100644 --- a/tests-clar/index/names.c +++ b/tests-clar/index/names.c @@ -63,7 +63,7 @@ void test_index_names__roundtrip(void) git_index_clear(repo_index); cl_assert(git_index_name_entrycount(repo_index) == 0); - cl_git_pass(git_index_read(repo_index)); + cl_git_pass(git_index_read(repo_index, true)); cl_assert(git_index_name_entrycount(repo_index) == 3); conflict_name = git_index_name_get_byindex(repo_index, 0); @@ -120,7 +120,7 @@ void test_index_names__cleaned_on_checkout_tree(void) git_reference_name_to_id(&oid, repo, "refs/heads/master"); git_object_lookup(&obj, repo, &oid, GIT_OBJ_ANY); git_checkout_tree(repo, obj, &opts); - cl_assert(git_index_name_entrycount(repo_index) == 0); + cl_assert_equal_sz(0, git_index_name_entrycount(repo_index)); git_object_free(obj); } @@ -133,7 +133,7 @@ void test_index_names__cleaned_on_checkout_head(void) test_index_names__add(); git_checkout_head(repo, &opts); - cl_assert(git_index_name_entrycount(repo_index) == 0); + cl_assert_equal_sz(0, git_index_name_entrycount(repo_index)); } void test_index_names__retained_on_checkout_index(void) diff --git a/tests-clar/index/reuc.c b/tests-clar/index/reuc.c index 69ed4a933..a18d5602e 100644 --- a/tests-clar/index/reuc.c +++ b/tests-clar/index/reuc.c @@ -276,8 +276,6 @@ void test_index_reuc__write(void) 0100644, &their_oid)); cl_git_pass(git_index_write(repo_index)); - - cl_git_pass(git_index_read(repo_index)); cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); /* ensure sort order was round-tripped correct */ diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index 009d393d7..09b05bf6e 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -8,7 +8,7 @@ static const size_t index_entry_count_2 = 1437; #define TEST_INDEXBIG_PATH cl_fixture("big.index") -// Suite data +/* Suite data */ struct test_entry { size_t index; char path[128]; @@ -24,7 +24,7 @@ static struct test_entry test_entries[] = { {48, "src/revobject.h", 1448, 0x4C3F7FE2} }; -// Helpers +/* Helpers */ static void copy_file(const char *src, const char *dst) { git_buf source_buf = GIT_BUF_INIT; @@ -32,7 +32,7 @@ static void copy_file(const char *src, const char *dst) cl_git_pass(git_futils_readbuffer(&source_buf, src)); - dst_fd = git_futils_creat_withpath(dst, 0777, 0666); //-V536 + dst_fd = git_futils_creat_withpath(dst, 0777, 0666); /* -V536 */ if (dst_fd < 0) goto cleanup; @@ -66,7 +66,7 @@ static void files_are_equal(const char *a, const char *b) } -// Fixture setup and teardown +/* Fixture setup and teardown */ void test_index_tests__initialize(void) { } @@ -173,7 +173,8 @@ void test_index_tests__write(void) void test_index_tests__sort0(void) { - // sort the entires in an index + /* sort the entires in an index */ + /* * TODO: This no longer applies: * index sorting in Git uses some specific changes to the way @@ -187,7 +188,7 @@ void test_index_tests__sort0(void) void test_index_tests__sort1(void) { - // sort the entires in an empty index + /* sort the entires in an empty index */ git_index *index; cl_git_pass(git_index_open(&index, "fake-index")); @@ -349,14 +350,14 @@ void test_index_tests__remove_entry(void) cl_git_pass(git_index_add_bypath(index, "hello")); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert(git_index_entrycount(index) == 1); cl_assert(git_index_get_bypath(index, "hello", 0) != NULL); cl_git_pass(git_index_remove(index, "hello", 0)); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert(git_index_entrycount(index) == 0); cl_assert(git_index_get_bypath(index, "hello", 0) == NULL); @@ -388,7 +389,7 @@ void test_index_tests__remove_directory(void) cl_git_pass(git_index_add_bypath(index, "b.txt")); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert_equal_i(4, (int)git_index_entrycount(index)); cl_assert(git_index_get_bypath(index, "a/1.txt", 0) != NULL); cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL); @@ -397,7 +398,7 @@ void test_index_tests__remove_directory(void) cl_git_pass(git_index_remove(index, "a/1.txt", 0)); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert_equal_i(3, (int)git_index_entrycount(index)); cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL); cl_assert(git_index_get_bypath(index, "a/2.txt", 0) != NULL); @@ -406,7 +407,7 @@ void test_index_tests__remove_directory(void) cl_git_pass(git_index_remove_directory(index, "a", 0)); cl_git_pass(git_index_write(index)); - cl_git_pass(git_index_read(index)); /* reload */ + cl_git_pass(git_index_read(index, true)); /* reload */ cl_assert_equal_i(1, (int)git_index_entrycount(index)); cl_assert(git_index_get_bypath(index, "a/1.txt", 0) == NULL); cl_assert(git_index_get_bypath(index, "a/2.txt", 0) == NULL); @@ -517,7 +518,7 @@ void test_index_tests__reload_from_disk(void) /* Sync the changes back into the read_index */ cl_assert_equal_sz(0, git_index_entrycount(read_index)); - cl_git_pass(git_index_read(read_index)); + cl_git_pass(git_index_read(read_index, true)); cl_assert_equal_i(true, read_index->on_disk); cl_assert_equal_sz(2, git_index_entrycount(read_index)); @@ -526,7 +527,7 @@ void test_index_tests__reload_from_disk(void) cl_git_pass(p_unlink(write_index->index_file_path)); /* Sync the changes back into the read_index */ - cl_git_pass(git_index_read(read_index)); + cl_git_pass(git_index_read(read_index, true)); cl_assert_equal_i(false, read_index->on_disk); cl_assert_equal_sz(0, git_index_entrycount(read_index)); diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 3b569c7ba..34be6d34c 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -470,16 +470,15 @@ void test_status_worktree__conflict_with_diff3(void) cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_remove(index, "modified_file", 0)); - cl_git_pass(git_index_conflict_add(index, &ancestor_entry, - &our_entry, &their_entry)); + cl_git_pass(git_index_conflict_add( + index, &ancestor_entry, &our_entry, &their_entry)); + cl_git_pass(git_index_write(index)); + git_index_free(index); cl_git_pass(git_status_file(&status, repo, "modified_file")); cl_assert_equal_i(GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW, status); - - git_index_free(index); } static const char *filemode_paths[] = {