Skip to content

Commit bfa1f02

Browse files
committed
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 parent 787768c commit bfa1f02

File tree

5 files changed

+35
-3
lines changed

5 files changed

+35
-3
lines changed

include/git2/common.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ typedef enum {
194194
GIT_OPT_GET_WINDOWS_SHAREMODE,
195195
GIT_OPT_SET_WINDOWS_SHAREMODE,
196196
GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION,
197-
GIT_OPT_SET_ALLOCATOR
197+
GIT_OPT_SET_ALLOCATOR,
198+
GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY
198199
} git_libgit2_opt_t;
199200

200201
/**
@@ -363,6 +364,14 @@ typedef enum {
363364
* > allocator will then be used to make all memory allocations for
364365
* > libgit2 operations.
365366
*
367+
* opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, int enabled)
368+
*
369+
* > Ensure that there are no unsaved changes in the index before
370+
* > beginning any operation that reloads the index from disk (eg,
371+
* > checkout). If there are unsaved changes, the instruction will
372+
* > fail. (Using the FORCE flag to checkout will still overwrite
373+
* > these changes.)
374+
*
366375
* @param option Option key
367376
* @param ... value to set the option
368377
* @return 0 on success, <0 on failure

src/index.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ struct reuc_entry_internal {
135135
char path[GIT_FLEX_ARRAY];
136136
};
137137

138+
bool git_index__enforce_unsaved_safety = false;
139+
138140
/* local declarations */
139141
static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size);
140142
static int read_header(struct index_header *dest, const void *buffer);
@@ -682,7 +684,7 @@ int git_index_read(git_index *index, int force)
682684

683685
int git_index_read_safely(git_index *index)
684686
{
685-
if (index->dirty) {
687+
if (git_index__enforce_unsaved_safety && index->dirty) {
686688
giterr_set(GITERR_INDEX,
687689
"the index has unsaved changes that would be overwritten by this operation");
688690
return GIT_EINDEXDIRTY;

src/index.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#define GIT_INDEX_FILE "index"
2121
#define GIT_INDEX_FILE_MODE 0666
2222

23+
extern bool git_index__enforce_unsaved_safety;
24+
2325
struct git_index {
2426
git_refcount rc;
2527

src/settings.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "object.h"
2424
#include "odb.h"
2525
#include "refs.h"
26+
#include "index.h"
2627
#include "transports/smart.h"
2728
#include "streams/openssl.h"
2829
#include "streams/mbedtls.h"
@@ -265,6 +266,10 @@ int git_libgit2_opts(int key, ...)
265266
error = git_allocator_setup(va_arg(ap, git_allocator *));
266267
break;
267268

269+
case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY:
270+
git_index__enforce_unsaved_safety = (va_arg(ap, int) != 0);
271+
break;
272+
268273
default:
269274
giterr_set(GITERR_INVALID, "invalid option key");
270275
error = -1;

tests/index/tests.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ void test_index_tests__initialize(void)
7272
{
7373
}
7474

75+
void test_index_tests__cleanup(void)
76+
{
77+
cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, 0));
78+
}
79+
7580
void test_index_tests__empty_index(void)
7681
{
7782
git_index *index;
@@ -384,7 +389,7 @@ void test_index_tests__dirty_and_clean(void)
384389
git_repository_free(repo);
385390
}
386391

387-
void test_index_tests__dirty_fails_with_error(void)
392+
void test_index_tests__dirty_fails_optionally(void)
388393
{
389394
git_repository *repo;
390395
git_index *index;
@@ -400,6 +405,15 @@ void test_index_tests__dirty_fails_with_error(void)
400405
cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4));
401406
cl_assert(git_index_is_dirty(index));
402407

408+
cl_git_pass(git_checkout_head(repo, NULL));
409+
410+
/* Index is dirty (again) after adding an entry */
411+
entry.mode = GIT_FILEMODE_BLOB;
412+
entry.path = "test.txt";
413+
cl_git_pass(git_index_add_frombuffer(index, &entry, "Hi.\n", 4));
414+
cl_assert(git_index_is_dirty(index));
415+
416+
cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, 1));
403417
cl_git_fail_with(GIT_EINDEXDIRTY, git_checkout_head(repo, NULL));
404418

405419
git_index_free(index);

0 commit comments

Comments
 (0)