Unverified Commit c43658f6 by Edward Thomson Committed by GitHub

Merge pull request #4536 from libgit2/ethomson/index_dirty

Add a "dirty" state to the index when it has unsaved changes
parents 68e73791 243d40df
......@@ -16,6 +16,18 @@ v0.27 + 1
* You can now swap out memory allocators via the
`GIT_OPT_SET_ALLOCATOR` option with `git_libgit2_opts()`.
* You can now ensure that functions do not discard unwritten changes to the
index via the `GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY` option to
`git_libgit2_opts()`. This will cause functions that implicitly re-read
the index (eg, `git_checkout`) to fail if you have staged changes to the
index but you have not written the index to disk. (Unless the checkout
has the FORCE flag specified.)
At present, this defaults to off, but we intend to enable this more
broadly in the future, as a warning or error. We encourage you to
examine your code to ensure that you are not relying on the current
behavior that implicitly removes staged changes.
### API removals
### Breaking API changes
......
......@@ -194,7 +194,8 @@ typedef enum {
GIT_OPT_GET_WINDOWS_SHAREMODE,
GIT_OPT_SET_WINDOWS_SHAREMODE,
GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION,
GIT_OPT_SET_ALLOCATOR
GIT_OPT_SET_ALLOCATOR,
GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY
} git_libgit2_opt_t;
/**
......@@ -363,6 +364,14 @@ typedef enum {
* > allocator will then be used to make all memory allocations for
* > libgit2 operations.
*
* opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, int enabled)
*
* > Ensure that there are no unsaved changes in the index before
* > beginning any operation that reloads the index from disk (eg,
* > checkout). If there are unsaved changes, the instruction will
* > fail. (Using the FORCE flag to checkout will still overwrite
* > these changes.)
*
* @param option Option key
* @param ... value to set the option
* @return 0 on success, <0 on failure
......
......@@ -55,6 +55,7 @@ typedef enum {
GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */
GIT_RETRY = -32, /**< Internal only */
GIT_EMISMATCH = -33, /**< Hashsum mismatch in object */
GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */
} git_error_code;
/**
......
......@@ -2397,6 +2397,9 @@ static int checkout_data_init(
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
goto cleanup;
if ((error = git_repository_index(&data->index, data->repo)) < 0)
goto cleanup;
/* refresh config and index content unless NO_REFRESH is given */
if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) {
git_config *cfg;
......@@ -2404,24 +2407,30 @@ static int checkout_data_init(
if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
goto cleanup;
/* Get the repository index and reload it (unless we're checking
* out the index; then it has the changes we're trying to check
* out and those should not be overwritten.)
/* Reload the repository index (unless we're checking out the
* index; then it has the changes we're trying to check out
* and those should not be overwritten.)
*/
if ((error = git_repository_index(&data->index, data->repo)) < 0)
goto cleanup;
if (data->index != git_iterator_index(target)) {
if ((error = git_index_read(data->index, true)) < 0)
goto cleanup;
/* cannot checkout if unresolved conflicts exist */
if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) == 0 &&
git_index_has_conflicts(data->index)) {
error = GIT_ECONFLICT;
giterr_set(GITERR_CHECKOUT,
"unresolved conflicts exist in the index");
goto cleanup;
if (data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) {
/* When forcing, we can blindly re-read the index */
if ((error = git_index_read(data->index, false)) < 0)
goto cleanup;
} else {
/*
* When not being forced, we need to check for unresolved
* conflicts and unsaved changes in the index before
* proceeding.
*/
if (git_index_has_conflicts(data->index)) {
error = GIT_ECONFLICT;
giterr_set(GITERR_CHECKOUT,
"unresolved conflicts exist in the index");
goto cleanup;
}
if ((error = git_index_read_safely(data->index)) < 0)
goto cleanup;
}
/* clean conflict data in the current index */
......
......@@ -135,6 +135,8 @@ struct reuc_entry_internal {
char path[GIT_FLEX_ARRAY];
};
bool git_index__enforce_unsaved_safety = false;
/* local declarations */
static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size);
static int read_header(struct index_header *dest, const void *buffer);
......@@ -516,6 +518,8 @@ static int index_remove_entry(git_index *index, size_t pos)
} else {
index_entry_free(entry);
}
index->dirty = 1;
}
return error;
......@@ -527,6 +531,7 @@ int git_index_clear(git_index *index)
assert(index);
index->dirty = 1;
index->tree = NULL;
git_pool_clear(&index->tree_pool);
......@@ -637,8 +642,10 @@ int git_index_read(git_index *index, int force)
index->on_disk = git_path_exists(index->index_file_path);
if (!index->on_disk) {
if (force)
return git_index_clear(index);
if (force && (error = git_index_clear(index)) < 0)
return error;
index->dirty = 0;
return 0;
}
......@@ -650,6 +657,7 @@ int git_index_read(git_index *index, int force)
index->index_file_path);
return updated;
}
if (!updated && !force)
return 0;
......@@ -665,13 +673,26 @@ int git_index_read(git_index *index, int force)
if (!error)
error = parse_index(index, buffer.ptr, buffer.size);
if (!error)
if (!error) {
git_futils_filestamp_set(&index->stamp, &stamp);
index->dirty = 0;
}
git_buf_dispose(&buffer);
return error;
}
int git_index_read_safely(git_index *index)
{
if (git_index__enforce_unsaved_safety && index->dirty) {
giterr_set(GITERR_INDEX,
"the index has unsaved changes that would be overwritten by this operation");
return GIT_EINDEXDIRTY;
}
return git_index_read(index, false);
}
int git_index__changed_relative_to(
git_index *index, const git_oid *checksum)
{
......@@ -735,8 +756,10 @@ static int truncate_racily_clean(git_index *index)
/* Ensure that we have a stage 0 for this file (ie, it's not a
* conflict), otherwise smudging it is quite pointless.
*/
if (entry)
if (entry) {
entry->file_size = 0;
index->dirty = 1;
}
}
done:
......@@ -774,8 +797,9 @@ int git_index_write(git_index *index)
truncate_racily_clean(index);
if ((error = git_indexwriter_init(&writer, index)) == 0)
error = git_indexwriter_commit(&writer);
if ((error = git_indexwriter_init(&writer, index)) == 0 &&
(error = git_indexwriter_commit(&writer)) == 0)
index->dirty = 0;
git_indexwriter_cleanup(&writer);
......@@ -1389,6 +1413,8 @@ static int index_insert(
if (error < 0) {
index_entry_free(*entry_ptr);
*entry_ptr = NULL;
} else {
index->dirty = 1;
}
return error;
......@@ -1616,6 +1642,8 @@ int git_index__fill(git_index *index, const git_vector *source_entries)
INSERT_IN_MAP(index, entry, &ret);
if (ret < 0)
break;
index->dirty = 1;
}
if (!ret)
......@@ -2053,6 +2081,7 @@ int git_index_name_add(git_index *index,
return -1;
}
index->dirty = 1;
return 0;
}
......@@ -2067,6 +2096,8 @@ void git_index_name_clear(git_index *index)
index_name_entry_free(conflict_name);
git_vector_clear(&index->names);
index->dirty = 1;
}
size_t git_index_reuc_entrycount(git_index *index)
......@@ -2092,6 +2123,8 @@ static int index_reuc_insert(
assert(git_vector_is_sorted(&index->reuc));
res = git_vector_insert_sorted(&index->reuc, reuc, &index_reuc_on_dup);
index->dirty = 1;
return res == GIT_EEXISTS ? 0 : res;
}
......@@ -2157,6 +2190,7 @@ int git_index_reuc_remove(git_index *index, size_t position)
if (!error)
index_entry_reuc_free(reuc);
index->dirty = 1;
return error;
}
......@@ -2170,6 +2204,8 @@ void git_index_reuc_clear(git_index *index)
index_entry_reuc_free(git__swap(index->reuc.contents[i], NULL));
git_vector_clear(&index->reuc);
index->dirty = 1;
}
static int index_error_invalid(const char *message)
......@@ -2604,6 +2640,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
git_vector_set_sorted(&index->entries, !index->ignore_case);
git_vector_sort(&index->entries);
index->dirty = 0;
done:
return error;
}
......@@ -3070,6 +3107,8 @@ int git_index_read_tree(git_index *index, const git_tree *tree)
entries_map = git__swap(index->entries_map, entries_map);
}
index->dirty = 1;
cleanup:
git_vector_free(&entries);
git_idxmap_free(entries_map);
......@@ -3209,6 +3248,7 @@ static int git_index_read_iterator(
clear_uptodate(index);
index->dirty = 1;
error = 0;
done:
......@@ -3601,6 +3641,7 @@ int git_indexwriter_commit(git_indexwriter *writer)
return -1;
}
writer->index->dirty = 0;
writer->index->on_disk = 1;
git_oid_cpy(&writer->index->checksum, &checksum);
......
......@@ -20,6 +20,8 @@
#define GIT_INDEX_FILE "index"
#define GIT_INDEX_FILE_MODE 0666
extern bool git_index__enforce_unsaved_safety;
struct git_index {
git_refcount rc;
......@@ -37,6 +39,7 @@ struct git_index {
unsigned int ignore_case:1;
unsigned int distrust_filemode:1;
unsigned int no_symlinks:1;
unsigned int dirty:1; /* whether we have unsaved changes */
git_tree_cache *tree;
git_pool tree_pool;
......@@ -143,6 +146,13 @@ extern int git_index_snapshot_find(
/* Replace an index with a new index */
int git_index_read_index(git_index *index, const git_index *new_index);
GIT_INLINE(int) git_index_is_dirty(git_index *index)
{
return index->dirty;
}
extern int git_index_read_safely(git_index *index);
typedef struct {
git_index *index;
git_filebuf file;
......
......@@ -23,6 +23,7 @@
#include "object.h"
#include "odb.h"
#include "refs.h"
#include "index.h"
#include "transports/smart.h"
#include "streams/openssl.h"
#include "streams/mbedtls.h"
......@@ -265,6 +266,10 @@ int git_libgit2_opts(int key, ...)
error = git_allocator_setup(va_arg(ap, git_allocator *));
break;
case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY:
git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0);
break;
default:
giterr_set(GITERR_INVALID, "invalid option key");
error = -1;
......
......@@ -12,6 +12,7 @@
#include "message.h"
#include "tree.h"
#include "reflog.h"
#include "blob.h"
#include "git2/diff.h"
#include "git2/stash.h"
#include "git2/status.h"
......@@ -103,19 +104,23 @@ cleanup:
return error;
}
static int build_tree_from_index(git_tree **out, git_index *index)
static int build_tree_from_index(
git_tree **out,
git_repository *repo,
git_index *index)
{
int error;
git_oid i_tree_oid;
if ((error = git_index_write_tree(&i_tree_oid, index)) < 0)
if ((error = git_index_write_tree_to(&i_tree_oid, index, repo)) < 0)
return error;
return git_tree_lookup(out, git_index_owner(index), &i_tree_oid);
return git_tree_lookup(out, repo, &i_tree_oid);
}
static int commit_index(
git_commit **i_commit,
git_repository *repo,
git_index *index,
const git_signature *stasher,
const char *message,
......@@ -126,7 +131,7 @@ static int commit_index(
git_buf msg = GIT_BUF_INIT;
int error;
if ((error = build_tree_from_index(&i_tree, index)) < 0)
if ((error = build_tree_from_index(&i_tree, repo, index)) < 0)
goto cleanup;
if ((error = git_buf_printf(&msg, "index on %s\n", message)) < 0)
......@@ -159,7 +164,38 @@ struct stash_update_rules {
bool include_ignored;
};
/*
* Similar to git_index_add_bypath but able to operate on any
* index without making assumptions about the repository's index
*/
static int stash_to_index(
git_repository *repo,
git_index *index,
const char *path)
{
git_index *repo_index;
git_index_entry entry = {{0}};
struct stat st;
int error;
if (!git_repository_is_bare(repo) &&
(error = git_repository_index__weakptr(&repo_index, repo)) < 0)
return error;
if ((error = git_blob__create_from_paths(
&entry.id, &st, repo, NULL, path, 0, true)) < 0)
return error;
git_index_entry__init_from_stat(&entry, &st,
(repo_index != NULL || !repo_index->distrust_filemode));
entry.path = path;
return git_index_add(index, &entry);
}
static int stash_update_index_from_diff(
git_repository *repo,
git_index *index,
const git_diff *diff,
struct stash_update_rules *data)
......@@ -205,7 +241,7 @@ static int stash_update_index_from_diff(
}
if (add_path != NULL)
error = git_index_add_bypath(index, add_path);
error = stash_to_index(repo, index, add_path);
}
return error;
......@@ -213,17 +249,19 @@ static int stash_update_index_from_diff(
static int build_untracked_tree(
git_tree **tree_out,
git_index *index,
git_repository *repo,
git_commit *i_commit,
uint32_t flags)
{
git_index *i_index = NULL;
git_tree *i_tree = NULL;
git_diff *diff = NULL;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
struct stash_update_rules data = {0};
int error;
git_index_clear(index);
if ((error = git_index_new(&i_index)) < 0)
goto cleanup;
if (flags & GIT_STASH_INCLUDE_UNTRACKED) {
opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED |
......@@ -240,24 +278,24 @@ static int build_untracked_tree(
if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
goto cleanup;
if ((error = git_diff_tree_to_workdir(
&diff, git_index_owner(index), i_tree, &opts)) < 0)
if ((error = git_diff_tree_to_workdir(&diff, repo, i_tree, &opts)) < 0)
goto cleanup;
if ((error = stash_update_index_from_diff(index, diff, &data)) < 0)
if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0)
goto cleanup;
error = build_tree_from_index(tree_out, index);
error = build_tree_from_index(tree_out, repo, i_index);
cleanup:
git_diff_free(diff);
git_tree_free(i_tree);
git_index_free(i_index);
return error;
}
static int commit_untracked(
git_commit **u_commit,
git_index *index,
git_repository *repo,
const git_signature *stasher,
const char *message,
git_commit *i_commit,
......@@ -268,7 +306,7 @@ static int commit_untracked(
git_buf msg = GIT_BUF_INIT;
int error;
if ((error = build_untracked_tree(&u_tree, index, i_commit, flags)) < 0)
if ((error = build_untracked_tree(&u_tree, repo, i_commit, flags)) < 0)
goto cleanup;
if ((error = git_buf_printf(&msg, "untracked files on %s\n", message)) < 0)
......@@ -276,7 +314,7 @@ static int commit_untracked(
if ((error = git_commit_create(
&u_commit_oid,
git_index_owner(index),
repo,
NULL,
stasher,
stasher,
......@@ -287,7 +325,7 @@ static int commit_untracked(
NULL)) < 0)
goto cleanup;
error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid);
error = git_commit_lookup(u_commit, repo, &u_commit_oid);
cleanup:
git_tree_free(u_tree);
......@@ -316,10 +354,10 @@ static git_diff_delta *stash_delta_merge(
static int build_workdir_tree(
git_tree **tree_out,
git_index *index,
git_repository *repo,
git_index *i_index,
git_commit *b_commit)
{
git_repository *repo = git_index_owner(index);
git_tree *b_tree = NULL;
git_diff *diff = NULL, *idx_to_wd = NULL;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
......@@ -331,17 +369,17 @@ static int build_workdir_tree(
if ((error = git_commit_tree(&b_tree, b_commit)) < 0)
goto cleanup;
if ((error = git_diff_tree_to_index(&diff, repo, b_tree, index, &opts)) < 0 ||
(error = git_diff_index_to_workdir(&idx_to_wd, repo, index, &opts)) < 0 ||
if ((error = git_diff_tree_to_index(&diff, repo, b_tree, i_index, &opts)) < 0 ||
(error = git_diff_index_to_workdir(&idx_to_wd, repo, i_index, &opts)) < 0 ||
(error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0)
goto cleanup;
data.include_changed = true;
if ((error = stash_update_index_from_diff(index, diff, &data)) < 0)
if ((error = stash_update_index_from_diff(repo, i_index, diff, &data)) < 0)
goto cleanup;
error = build_tree_from_index(tree_out, index);
error = build_tree_from_index(tree_out, repo, i_index);
cleanup:
git_diff_free(idx_to_wd);
......@@ -353,7 +391,7 @@ cleanup:
static int commit_worktree(
git_oid *w_commit_oid,
git_index *index,
git_repository *repo,
const git_signature *stasher,
const char *message,
git_commit *i_commit,
......@@ -362,7 +400,9 @@ static int commit_worktree(
{
int error = 0;
git_tree *w_tree = NULL, *i_tree = NULL;
git_index *i_index = NULL;
const git_commit *parents[] = { NULL, NULL, NULL };
int ignorecase;
parents[0] = b_commit;
parents[1] = i_commit;
......@@ -371,15 +411,21 @@ static int commit_worktree(
if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
goto cleanup;
if ((error = git_index_read_tree(index, i_tree)) < 0)
if ((error = git_index_new(&i_index)) < 0 ||
(error = git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE)) < 0)
goto cleanup;
git_index__set_ignore_case(i_index, ignorecase);
if ((error = git_index_read_tree(i_index, i_tree)) < 0)
goto cleanup;
if ((error = build_workdir_tree(&w_tree, index, b_commit)) < 0)
if ((error = build_workdir_tree(&w_tree, repo, i_index, b_commit)) < 0)
goto cleanup;
error = git_commit_create(
w_commit_oid,
git_index_owner(index),
repo,
NULL,
stasher,
stasher,
......@@ -392,6 +438,7 @@ static int commit_worktree(
cleanup:
git_tree_free(i_tree);
git_tree_free(w_tree);
git_index_free(i_index);
return error;
}
......@@ -534,12 +581,12 @@ int git_stash_save(
goto cleanup;
if ((error = commit_index(
&i_commit, index, stasher, git_buf_cstr(&msg), b_commit)) < 0)
&i_commit, repo, index, stasher, git_buf_cstr(&msg), b_commit)) < 0)
goto cleanup;
if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) &&
(error = commit_untracked(
&u_commit, index, stasher, git_buf_cstr(&msg),
&u_commit, repo, stasher, git_buf_cstr(&msg),
i_commit, flags)) < 0)
goto cleanup;
......@@ -547,7 +594,7 @@ int git_stash_save(
goto cleanup;
if ((error = commit_worktree(
out, index, stasher, git_buf_cstr(&msg),
out, repo, stasher, git_buf_cstr(&msg),
i_commit, b_commit, u_commit)) < 0)
goto cleanup;
......@@ -738,6 +785,8 @@ static void normalize_apply_options(
memcpy(opts, &default_apply_opts, sizeof(git_stash_apply_options));
}
opts->checkout_options.checkout_strategy |= GIT_CHECKOUT_NO_REFRESH;
if (!opts->checkout_options.our_label)
opts->checkout_options.our_label = "Updated upstream";
......
......@@ -294,7 +294,7 @@ int git_status_list_new(
/* refresh index from disk unless prevented */
if ((flags & GIT_STATUS_OPT_NO_REFRESH) == 0 &&
git_index_read(index, false) < 0)
git_index_read_safely(index) < 0)
giterr_clear();
status = git_status_list_alloc(index);
......
......@@ -543,6 +543,7 @@ void assert_conflict(
*/
cl_git_pass(git_object_peel(&hack_tree, g_object, GIT_OBJ_TREE));
cl_git_pass(git_index_read_tree(index, (git_tree *)hack_tree));
cl_git_pass(git_index_write(index));
git_object_free(hack_tree);
git_object_free(g_object);
g_object = NULL;
......@@ -739,7 +740,9 @@ void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void)
cl_git_mkfile("./testrepo/this-is-dir/contained_file", "content\n");
cl_git_pass(git_index_add_bypath(index, "this-is-dir/contained_file"));
git_index_write_tree(&tree_id, index);
cl_git_pass(git_index_write(index));
cl_git_pass(git_index_write_tree(&tree_id, index));
cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
cl_git_pass(p_unlink("./testrepo/this-is-dir/contained_file"));
......@@ -1107,7 +1110,7 @@ void test_checkout_tree__removes_conflicts(void)
git_commit *commit;
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
git_index *index;
cl_git_pass(git_oid_fromstr(&commit_id, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6"));
cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id));
......@@ -1150,7 +1153,7 @@ void test_checkout_tree__removes_conflicts_only_by_pathscope(void)
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
git_index *index;
const char *path = "executable.txt";
cl_git_pass(git_oid_fromstr(&commit_id, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6"));
cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id));
......@@ -1248,7 +1251,7 @@ void test_checkout_tree__case_changing_rename(void)
cl_git_pass(git_checkout_tree(g_repo, (git_object *)master_commit, &opts));
cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master"));
assert_on_branch(g_repo, "master");
cl_assert(git_path_isfile("testrepo/README"));
......@@ -1495,6 +1498,7 @@ void test_checkout_tree__baseline_is_empty_when_no_index(void)
size_t conflicts = 0;
assert_on_branch(g_repo, "master");
cl_git_pass(git_repository_head(&head, g_repo));
cl_git_pass(git_reference_peel(&obj, head, GIT_OBJ_COMMIT));
......@@ -1551,8 +1555,12 @@ void test_checkout_tree__mode_change_is_force_updated(void)
cl_must_pass(p_chmod("testrepo/README", 0755));
cl_must_pass(git_index_add_bypath(index, "README"));
cl_git_pass(git_index_write(index));
assert_status_entrycount(g_repo, 1);
cl_git_pass(git_checkout_tree(g_repo, obj, &g_opts));
cl_git_pass(git_index_write(index));
assert_status_entrycount(g_repo, 0);
git_object_free(obj);
......@@ -1564,3 +1572,67 @@ void test_checkout_tree__nullopts(void)
{
cl_git_pass(git_checkout_tree(g_repo, NULL, NULL));
}
static void modify_index_ondisk(void)
{
git_repository *other_repo;
git_index *other_index;
git_index_entry entry = {{0}};
cl_git_pass(git_repository_open(&other_repo, git_repository_workdir(g_repo)));
cl_git_pass(git_repository_index(&other_index, other_repo));
cl_git_pass(git_oid_fromstr(&entry.id, "1385f264afb75a56a5bec74243be9b367ba4ca08"));
entry.mode = 0100644;
entry.path = "README";
cl_git_pass(git_index_add(other_index, &entry));
cl_git_pass(git_index_write(other_index));
git_index_free(other_index);
git_repository_free(other_repo);
}
static void modify_index_and_checkout_tree(git_checkout_options *opts)
{
git_index *index;
git_reference *head;
git_object *obj;
/* External changes to the index are maintained by default */
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(git_repository_head(&head, g_repo));
cl_git_pass(git_reference_peel(&obj, head, GIT_OBJ_COMMIT));
cl_git_pass(git_reset(g_repo, obj, GIT_RESET_HARD, NULL));
assert_status_entrycount(g_repo, 0);
modify_index_ondisk();
/* The file in the index remains modified */
cl_git_pass(git_checkout_tree(g_repo, obj, opts));
git_object_free(obj);
git_reference_free(head);
git_index_free(index);
}
void test_checkout_tree__retains_external_index_changes(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE;
modify_index_and_checkout_tree(&opts);
assert_status_entrycount(g_repo, 1);
}
void test_checkout_tree__no_index_refresh(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_NO_REFRESH;
modify_index_and_checkout_tree(&opts);
assert_status_entrycount(g_repo, 0);
}
......@@ -173,6 +173,7 @@ void test_index_addall__repo_lifecycle(void)
paths.count = 1;
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_stat_data(index, TEST_DIR "/file.bar", true);
check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0);
......@@ -190,6 +191,7 @@ void test_index_addall__repo_lifecycle(void)
check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0);
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_stat_data(index, TEST_DIR "/file.zzz", true);
check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);
......@@ -212,17 +214,20 @@ void test_index_addall__repo_lifecycle(void)
/* attempt to add an ignored file - does nothing */
strs[0] = "file.foo";
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0);
/* add with check - should generate error */
error = git_index_add_all(
index, &paths, GIT_INDEX_ADD_CHECK_PATHSPEC, NULL, NULL);
cl_assert_equal_i(GIT_EINVALIDSPEC, error);
cl_git_pass(git_index_write(index));
check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0);
/* add with force - should allow */
cl_git_pass(git_index_add_all(
index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL));
cl_git_pass(git_index_write(index));
check_stat_data(index, TEST_DIR "/file.foo", true);
check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0);
......@@ -232,6 +237,7 @@ void test_index_addall__repo_lifecycle(void)
check_status(g_repo, 1, 0, 0, 3, 0, 1, 0, 0);
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_stat_data(index, TEST_DIR "/file.foo", true);
check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0);
......@@ -265,6 +271,7 @@ void test_index_addall__repo_lifecycle(void)
strs[0] = "*";
cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_status(g_repo, 3, 1, 0, 0, 0, 0, 0, 0);
/* must be able to remove at any position while still updating other files */
......@@ -294,6 +301,7 @@ void test_index_addall__files_in_folders(void)
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_stat_data(index, TEST_DIR "/file.bar", true);
check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0);
......@@ -302,6 +310,7 @@ void test_index_addall__files_in_folders(void)
check_status(g_repo, 2, 0, 0, 1, 0, 0, 1, 0);
cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_status(g_repo, 3, 0, 0, 0, 0, 0, 1, 0);
git_index_free(index);
......@@ -319,6 +328,7 @@ void test_index_addall__hidden_files(void)
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_stat_data(index, TEST_DIR "/file.bar", true);
check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0);
......@@ -335,6 +345,7 @@ void test_index_addall__hidden_files(void)
check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);
cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_stat_data(index, TEST_DIR "/file.bar", true);
check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0);
......@@ -373,6 +384,7 @@ void test_index_addall__callback_filtering(void)
cl_git_pass(
git_index_add_all(index, NULL, 0, addall_match_prefix, "file."));
cl_git_pass(git_index_write(index));
check_stat_data(index, TEST_DIR "/file.bar", true);
check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0);
......@@ -386,11 +398,13 @@ void test_index_addall__callback_filtering(void)
cl_git_pass(
git_index_add_all(index, NULL, 0, addall_match_prefix, "other"));
cl_git_pass(git_index_write(index));
check_stat_data(index, TEST_DIR "/other.zzz", true);
check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);
cl_git_pass(
git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz"));
cl_git_pass(git_index_write(index));
check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0);
cl_git_pass(
......@@ -407,6 +421,7 @@ void test_index_addall__callback_filtering(void)
cl_git_pass(
git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz"));
cl_git_pass(git_index_write(index));
check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0);
cl_must_pass(p_unlink(TEST_DIR "/file.zzz"));
......@@ -446,6 +461,7 @@ void test_index_addall__adds_conflicts(void)
check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1);
cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_status(g_repo, 0, 1, 3, 0, 0, 0, 0, 0);
git_annotated_commit_free(annotated);
......@@ -473,6 +489,7 @@ void test_index_addall__removes_deleted_conflicted_files(void)
cl_git_rmfile("merge-resolve/conflicting.txt");
cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
cl_git_pass(git_index_write(index));
check_status(g_repo, 0, 2, 2, 0, 0, 0, 0, 0);
git_annotated_commit_free(annotated);
......
......@@ -25,10 +25,47 @@ void test_index_names__cleanup(void)
cl_git_sandbox_cleanup();
}
static void index_add_conflicts(void)
{
git_index_entry entry = {{0}};
const char *paths[][3] = {
{ "ancestor", "ours", "theirs" },
{ "ancestor2", "ours2", "theirs2" },
{ "ancestor3", "ours3", "theirs3" } };
const char **conflict;
size_t i;
for (i = 0; i < ARRAY_SIZE(paths); i++) {
conflict = paths[i];
/* ancestor */
entry.path = conflict[0];
entry.mode = GIT_FILEMODE_BLOB;
GIT_IDXENTRY_STAGE_SET(&entry, GIT_INDEX_STAGE_ANCESTOR);
git_oid_fromstr(&entry.id, "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81");
cl_git_pass(git_index_add(repo_index, &entry));
/* ours */
entry.path = conflict[1];
entry.mode = GIT_FILEMODE_BLOB;
GIT_IDXENTRY_STAGE_SET(&entry, GIT_INDEX_STAGE_OURS);
git_oid_fromstr(&entry.id, "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81");
cl_git_pass(git_index_add(repo_index, &entry));
/* theirs */
entry.path = conflict[2];
entry.mode = GIT_FILEMODE_BLOB;
GIT_IDXENTRY_STAGE_SET(&entry, GIT_INDEX_STAGE_THEIRS);
git_oid_fromstr(&entry.id, "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81");
cl_git_pass(git_index_add(repo_index, &entry));
}
}
void test_index_names__add(void)
{
const git_index_name_entry *conflict_name;
index_add_conflicts();
cl_git_pass(git_index_name_add(repo_index, "ancestor", "ours", "theirs"));
cl_git_pass(git_index_name_add(repo_index, "ancestor2", "ours2", NULL));
cl_git_pass(git_index_name_add(repo_index, "ancestor3", NULL, "theirs3"));
......@@ -49,6 +86,8 @@ void test_index_names__add(void)
cl_assert(strcmp(conflict_name->ancestor, "ancestor3") == 0);
cl_assert(conflict_name->ours == NULL);
cl_assert(strcmp(conflict_name->theirs, "theirs3") == 0);
cl_git_pass(git_index_write(repo_index));
}
void test_index_names__roundtrip(void)
......@@ -114,12 +153,12 @@ void test_index_names__cleaned_on_checkout_tree(void)
git_object *obj;
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_UPDATE_ONLY;
test_index_names__add();
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_git_pass(git_reference_name_to_id(&oid, repo, "refs/heads/master"));
cl_git_pass(git_object_lookup(&obj, repo, &oid, GIT_OBJ_ANY));
cl_git_pass(git_checkout_tree(repo, obj, &opts));
cl_assert_equal_sz(0, git_index_name_entrycount(repo_index));
git_object_free(obj);
......@@ -129,10 +168,10 @@ void test_index_names__cleaned_on_checkout_head(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_UPDATE_ONLY;
test_index_names__add();
git_checkout_head(repo, &opts);
cl_git_pass(git_checkout_head(repo, &opts));
cl_assert_equal_sz(0, git_index_name_entrycount(repo_index));
}
......@@ -140,9 +179,9 @@ void test_index_names__retained_on_checkout_index(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_UPDATE_ONLY;
test_index_names__add();
git_checkout_index(repo, repo_index, &opts);
cl_git_pass(git_checkout_index(repo, repo_index, &opts));
cl_assert(git_index_name_entrycount(repo_index) > 0);
}
......@@ -56,6 +56,8 @@ void test_index_reuc__add(void)
cl_assert_equal_oid(&reuc->oid[0], &ancestor_oid);
cl_assert_equal_oid(&reuc->oid[1], &our_oid);
cl_assert_equal_oid(&reuc->oid[2], &their_oid);
cl_git_pass(git_index_write(repo_index));
}
void test_index_reuc__add_no_ancestor(void)
......@@ -81,6 +83,8 @@ void test_index_reuc__add_no_ancestor(void)
cl_assert_equal_oid(&reuc->oid[0], &ancestor_oid);
cl_assert_equal_oid(&reuc->oid[1], &our_oid);
cl_assert_equal_oid(&reuc->oid[2], &their_oid);
cl_git_pass(git_index_write(repo_index));
}
void test_index_reuc__read_bypath(void)
......@@ -338,12 +342,12 @@ void test_index_reuc__cleaned_on_checkout_tree(void)
git_object *obj;
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
test_index_reuc__add();
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_git_pass(git_reference_name_to_id(&oid, repo, "refs/heads/master"));
cl_git_pass(git_object_lookup(&obj, repo, &oid, GIT_OBJ_ANY));
cl_git_pass(git_checkout_tree(repo, obj, &opts));
cl_assert(reuc_entry_exists() == false);
git_object_free(obj);
......@@ -353,10 +357,10 @@ void test_index_reuc__cleaned_on_checkout_head(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
test_index_reuc__add();
git_checkout_head(repo, &opts);
cl_git_pass(git_checkout_head(repo, &opts));
cl_assert(reuc_entry_exists() == false);
}
......@@ -364,9 +368,9 @@ void test_index_reuc__retained_on_checkout_index(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
test_index_reuc__add();
git_checkout_index(repo, repo_index, &opts);
cl_git_pass(git_checkout_index(repo, repo_index, &opts));
cl_assert(reuc_entry_exists() == true);
}
......@@ -72,6 +72,11 @@ void test_index_tests__initialize(void)
{
}
void test_index_tests__cleanup(void)
{
cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, 0));
}
void test_index_tests__empty_index(void)
{
git_index *index;
......@@ -331,6 +336,90 @@ void test_index_tests__add_frombuffer(void)
git_repository_free(repo);
}
void test_index_tests__dirty_and_clean(void)
{
git_repository *repo;
git_index *index;
git_index_entry entry = {{0}};
/* Index is not dirty after opening */
cl_git_pass(git_repository_init(&repo, "./myrepo", 0));
cl_git_pass(git_repository_index(&index, repo));
cl_assert(git_index_entrycount(index) == 0);
cl_assert(!git_index_is_dirty(index));
/* Index is dirty after adding an entry */
entry.mode = GIT_FILEMODE_BLOB;
entry.path = "test.txt";
cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4));
cl_assert(git_index_entrycount(index) == 1);
cl_assert(git_index_is_dirty(index));
/* Index is not dirty after write */
cl_git_pass(git_index_write(index));
cl_assert(!git_index_is_dirty(index));
/* Index is dirty after removing an entry */
cl_git_pass(git_index_remove_bypath(index, "test.txt"));
cl_assert(git_index_entrycount(index) == 0);
cl_assert(git_index_is_dirty(index));
/* Index is not dirty after write */
cl_git_pass(git_index_write(index));
cl_assert(!git_index_is_dirty(index));
/* Index remains not dirty after read */
cl_git_pass(git_index_read(index, 0));
cl_assert(!git_index_is_dirty(index));
/* Index is dirty when we do an unforced read with dirty content */
cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4));
cl_assert(git_index_entrycount(index) == 1);
cl_assert(git_index_is_dirty(index));
cl_git_pass(git_index_read(index, 0));
cl_assert(git_index_is_dirty(index));
/* Index is clean when we force a read with dirty content */
cl_git_pass(git_index_read(index, 1));
cl_assert(!git_index_is_dirty(index));
git_index_free(index);
git_repository_free(repo);
}
void test_index_tests__dirty_fails_optionally(void)
{
git_repository *repo;
git_index *index;
git_index_entry entry = {{0}};
/* Index is not dirty after opening */
repo = cl_git_sandbox_init("testrepo");
cl_git_pass(git_repository_index(&index, repo));
/* Index is dirty after adding an entry */
entry.mode = GIT_FILEMODE_BLOB;
entry.path = "test.txt";
cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4));
cl_assert(git_index_is_dirty(index));
cl_git_pass(git_checkout_head(repo, NULL));
/* Index is dirty (again) after adding an entry */
entry.mode = GIT_FILEMODE_BLOB;
entry.path = "test.txt";
cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4));
cl_assert(git_index_is_dirty(index));
cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, 1));
cl_git_fail_with(GIT_EINDEXDIRTY, git_checkout_head(repo, NULL));
git_index_free(index);
cl_git_sandbox_cleanup();
}
void test_index_tests__add_frombuffer_reset_entry(void)
{
git_index *index;
......
......@@ -33,8 +33,9 @@ void test_rebase_submodule__initialize(void)
/* We have to commit the rewritten .gitmodules file */
cl_git_pass(git_repository_index(&index, repo));
cl_git_pass(git_index_add_bypath(index, ".gitmodules"));
cl_git_pass(git_index_write_tree(&tree_oid, index));
cl_git_pass(git_index_write(index));
cl_git_pass(git_index_write_tree(&tree_oid, index));
cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid));
cl_git_pass(git_repository_head(&master_ref, repo));
......
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