Commit 5a0b8803 by Vicent Martí

Merge pull request #1929 from libgit2/rb/misc-diff-fixes

Fix some observed problems with incorrect diffs
parents becb13c0 3e57069e
......@@ -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)
......
......@@ -352,7 +352,7 @@ typedef struct {
/* options controlling which files are in the diff */
git_submodule_ignore_t ignore_submodules; /** << submodule ignore rule */
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;
......@@ -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);
/**@}*/
......
......@@ -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
......
......@@ -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 \
......
......@@ -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_CHECKOUT = 1,
GIT_SUBMODULE_UPDATE_REBASE = 2,
GIT_SUBMODULE_UPDATE_MERGE = 3,
GIT_SUBMODULE_UPDATE_NONE = 4
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_ALL = 4, /* never dirty */
GIT_SUBMODULE_IGNORE_DEFAULT = 0
} git_submodule_ignore_t;
/** @} */
......
......@@ -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 */
......
......@@ -70,6 +70,9 @@ static int diff_delta__from_one(
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;
const char *tmp_prefix = diff->opts.old_prefix;
diff->opts.old_prefix = diff->opts.new_prefix;
diff->opts.new_prefix = swap;
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)
{
......
......@@ -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? */
......
......@@ -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,6 +195,10 @@ int git_diff_merge(
if (!error) {
git_vector_swap(&onto->deltas, &onto_new);
git_pool_swap(&onto->pool, &onto_pool);
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.*/
......
......@@ -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) {
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);
......
......@@ -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 &&
......
......@@ -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 =
......
......@@ -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);
......
......@@ -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));
......
......@@ -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;
}
......
......@@ -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)
{
......
......@@ -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);
}
......@@ -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;
......
......@@ -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)
......
......@@ -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 */
......
......@@ -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));
......
......@@ -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[] = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment