settings: optional unsaved index safety Add the `GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY` option, which will cause commands that reload the on-disk index to fail if the current `git_index` has changed that have not been saved. This will prevent users from - for example - adding a file to the index then calling a function like `git_checkout` and having that file be silently removed from the index since it was re-read from disk. Now calls that would re-read the index will fail if the index is "dirty", meaning changes have been made to it but have not been written. Users can either `git_index_read` to discard those changes explicitly, or `git_index_write` to write them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
diff --git a/include/git2/common.h b/include/git2/common.h
index fc820ca..8c93474 100644
--- a/include/git2/common.h
+++ b/include/git2/common.h
@@ -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
diff --git a/src/index.c b/src/index.c
index b8aa516..4907c81 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);
@@ -682,7 +684,7 @@ int git_index_read(git_index *index, int force)
int git_index_read_safely(git_index *index)
{
- if (index->dirty) {
+ 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;
diff --git a/src/index.h b/src/index.h
index a518274..aa54215 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;
diff --git a/src/settings.c b/src/settings.c
index 14280f8..ba2f715 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/tests/index/tests.c b/tests/index/tests.c
index d7a1cc5..3605ac9 100644
--- a/tests/index/tests.c
+++ b/tests/index/tests.c
@@ -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;
@@ -384,7 +389,7 @@ void test_index_tests__dirty_and_clean(void)
git_repository_free(repo);
}
-void test_index_tests__dirty_fails_with_error(void)
+void test_index_tests__dirty_fails_optionally(void)
{
git_repository *repo;
git_index *index;
@@ -400,6 +405,15 @@ void test_index_tests__dirty_fails_with_error(void)
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);