summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2018-06-30 13:24:23 +0100
committerGitHub <noreply@github.com>2018-06-30 13:24:23 +0100
commitc43658f62ee268a0b5ee13f5764544882245063c (patch)
tree53e16272b69be5f323ce7caf6cf7ce0302773f96 /src
parent68e73791a9dc955db998e3f4385854aed07cab8b (diff)
parent243d40df2357620b7fb0c4d10f10d5bf7b67e647 (diff)
downloadlibgit2-c43658f62ee268a0b5ee13f5764544882245063c.tar.gz
Merge pull request #4536 from libgit2/ethomson/index_dirty
Add a "dirty" state to the index when it has unsaved changes
Diffstat (limited to 'src')
-rw-r--r--src/checkout.c41
-rw-r--r--src/index.c53
-rw-r--r--src/index.h10
-rw-r--r--src/settings.c5
-rw-r--r--src/stash.c105
-rw-r--r--src/status.c2
6 files changed, 165 insertions, 51 deletions
diff --git a/src/checkout.c b/src/checkout.c
index c6936bf28..2a4e5c4a5 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -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 */
diff --git a/src/index.c b/src/index.c
index 67c844c50..4907c81e1 100644
--- a/src/index.c
+++ b/src/index.c
@@ -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);
diff --git a/src/index.h b/src/index.h
index 0f1c0956c..aa54215dd 100644
--- a/src/index.h
+++ b/src/index.h
@@ -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;
diff --git a/src/settings.c b/src/settings.c
index 14280f8c0..ba2f7158c 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -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;
diff --git a/src/stash.c b/src/stash.c
index b85a5e720..cb542a36c 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -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";
diff --git a/src/status.c b/src/status.c
index f547bd466..a2d389860 100644
--- a/src/status.c
+++ b/src/status.c
@@ -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);