Skip to content

Commit c43658f

Browse files
authored
Merge pull request libgit2#4536 from libgit2/ethomson/index_dirty
Add a "dirty" state to the index when it has unsaved changes
2 parents 68e7379 + 243d40d commit c43658f

File tree

15 files changed

+431
-73
lines changed

15 files changed

+431
-73
lines changed

docs/changelog.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ v0.27 + 1
1616
* You can now swap out memory allocators via the
1717
`GIT_OPT_SET_ALLOCATOR` option with `git_libgit2_opts()`.
1818

19+
* You can now ensure that functions do not discard unwritten changes to the
20+
index via the `GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY` option to
21+
`git_libgit2_opts()`. This will cause functions that implicitly re-read
22+
the index (eg, `git_checkout`) to fail if you have staged changes to the
23+
index but you have not written the index to disk. (Unless the checkout
24+
has the FORCE flag specified.)
25+
26+
At present, this defaults to off, but we intend to enable this more
27+
broadly in the future, as a warning or error. We encourage you to
28+
examine your code to ensure that you are not relying on the current
29+
behavior that implicitly removes staged changes.
30+
1931
### API removals
2032

2133
### Breaking API changes

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

include/git2/errors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ typedef enum {
5555
GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */
5656
GIT_RETRY = -32, /**< Internal only */
5757
GIT_EMISMATCH = -33, /**< Hashsum mismatch in object */
58+
GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */
5859
} git_error_code;
5960

6061
/**

src/checkout.c

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2397,31 +2397,40 @@ static int checkout_data_init(
23972397
GIT_DIR_MODE, GIT_MKDIR_VERIFY_DIR)) < 0)
23982398
goto cleanup;
23992399

2400+
if ((error = git_repository_index(&data->index, data->repo)) < 0)
2401+
goto cleanup;
2402+
24002403
/* refresh config and index content unless NO_REFRESH is given */
24012404
if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) {
24022405
git_config *cfg;
24032406

24042407
if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
24052408
goto cleanup;
24062409

2407-
/* Get the repository index and reload it (unless we're checking
2408-
* out the index; then it has the changes we're trying to check
2409-
* out and those should not be overwritten.)
2410+
/* Reload the repository index (unless we're checking out the
2411+
* index; then it has the changes we're trying to check out
2412+
* and those should not be overwritten.)
24102413
*/
2411-
if ((error = git_repository_index(&data->index, data->repo)) < 0)
2412-
goto cleanup;
2413-
24142414
if (data->index != git_iterator_index(target)) {
2415-
if ((error = git_index_read(data->index, true)) < 0)
2416-
goto cleanup;
2417-
2418-
/* cannot checkout if unresolved conflicts exist */
2419-
if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) == 0 &&
2420-
git_index_has_conflicts(data->index)) {
2421-
error = GIT_ECONFLICT;
2422-
giterr_set(GITERR_CHECKOUT,
2423-
"unresolved conflicts exist in the index");
2424-
goto cleanup;
2415+
if (data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) {
2416+
/* When forcing, we can blindly re-read the index */
2417+
if ((error = git_index_read(data->index, false)) < 0)
2418+
goto cleanup;
2419+
} else {
2420+
/*
2421+
* When not being forced, we need to check for unresolved
2422+
* conflicts and unsaved changes in the index before
2423+
* proceeding.
2424+
*/
2425+
if (git_index_has_conflicts(data->index)) {
2426+
error = GIT_ECONFLICT;
2427+
giterr_set(GITERR_CHECKOUT,
2428+
"unresolved conflicts exist in the index");
2429+
goto cleanup;
2430+
}
2431+
2432+
if ((error = git_index_read_safely(data->index)) < 0)
2433+
goto cleanup;
24252434
}
24262435

24272436
/* clean conflict data in the current index */

src/index.c

Lines changed: 47 additions & 6 deletions
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);
@@ -516,6 +518,8 @@ static int index_remove_entry(git_index *index, size_t pos)
516518
} else {
517519
index_entry_free(entry);
518520
}
521+
522+
index->dirty = 1;
519523
}
520524

521525
return error;
@@ -527,6 +531,7 @@ int git_index_clear(git_index *index)
527531

528532
assert(index);
529533

534+
index->dirty = 1;
530535
index->tree = NULL;
531536
git_pool_clear(&index->tree_pool);
532537

@@ -637,8 +642,10 @@ int git_index_read(git_index *index, int force)
637642
index->on_disk = git_path_exists(index->index_file_path);
638643

639644
if (!index->on_disk) {
640-
if (force)
641-
return git_index_clear(index);
645+
if (force && (error = git_index_clear(index)) < 0)
646+
return error;
647+
648+
index->dirty = 0;
642649
return 0;
643650
}
644651

@@ -650,6 +657,7 @@ int git_index_read(git_index *index, int force)
650657
index->index_file_path);
651658
return updated;
652659
}
660+
653661
if (!updated && !force)
654662
return 0;
655663

@@ -665,13 +673,26 @@ int git_index_read(git_index *index, int force)
665673
if (!error)
666674
error = parse_index(index, buffer.ptr, buffer.size);
667675

668-
if (!error)
676+
if (!error) {
669677
git_futils_filestamp_set(&index->stamp, &stamp);
678+
index->dirty = 0;
679+
}
670680

671681
git_buf_dispose(&buffer);
672682
return error;
673683
}
674684

685+
int git_index_read_safely(git_index *index)
686+
{
687+
if (git_index__enforce_unsaved_safety && index->dirty) {
688+
giterr_set(GITERR_INDEX,
689+
"the index has unsaved changes that would be overwritten by this operation");
690+
return GIT_EINDEXDIRTY;
691+
}
692+
693+
return git_index_read(index, false);
694+
}
695+
675696
int git_index__changed_relative_to(
676697
git_index *index, const git_oid *checksum)
677698
{
@@ -735,8 +756,10 @@ static int truncate_racily_clean(git_index *index)
735756
/* Ensure that we have a stage 0 for this file (ie, it's not a
736757
* conflict), otherwise smudging it is quite pointless.
737758
*/
738-
if (entry)
759+
if (entry) {
739760
entry->file_size = 0;
761+
index->dirty = 1;
762+
}
740763
}
741764

742765
done:
@@ -774,8 +797,9 @@ int git_index_write(git_index *index)
774797

775798
truncate_racily_clean(index);
776799

777-
if ((error = git_indexwriter_init(&writer, index)) == 0)
778-
error = git_indexwriter_commit(&writer);
800+
if ((error = git_indexwriter_init(&writer, index)) == 0 &&
801+
(error = git_indexwriter_commit(&writer)) == 0)
802+
index->dirty = 0;
779803

780804
git_indexwriter_cleanup(&writer);
781805

@@ -1389,6 +1413,8 @@ static int index_insert(
13891413
if (error < 0) {
13901414
index_entry_free(*entry_ptr);
13911415
*entry_ptr = NULL;
1416+
} else {
1417+
index->dirty = 1;
13921418
}
13931419

13941420
return error;
@@ -1616,6 +1642,8 @@ int git_index__fill(git_index *index, const git_vector *source_entries)
16161642
INSERT_IN_MAP(index, entry, &ret);
16171643
if (ret < 0)
16181644
break;
1645+
1646+
index->dirty = 1;
16191647
}
16201648

16211649
if (!ret)
@@ -2053,6 +2081,7 @@ int git_index_name_add(git_index *index,
20532081
return -1;
20542082
}
20552083

2084+
index->dirty = 1;
20562085
return 0;
20572086
}
20582087

@@ -2067,6 +2096,8 @@ void git_index_name_clear(git_index *index)
20672096
index_name_entry_free(conflict_name);
20682097

20692098
git_vector_clear(&index->names);
2099+
2100+
index->dirty = 1;
20702101
}
20712102

20722103
size_t git_index_reuc_entrycount(git_index *index)
@@ -2092,6 +2123,8 @@ static int index_reuc_insert(
20922123
assert(git_vector_is_sorted(&index->reuc));
20932124

20942125
res = git_vector_insert_sorted(&index->reuc, reuc, &index_reuc_on_dup);
2126+
index->dirty = 1;
2127+
20952128
return res == GIT_EEXISTS ? 0 : res;
20962129
}
20972130

@@ -2157,6 +2190,7 @@ int git_index_reuc_remove(git_index *index, size_t position)
21572190
if (!error)
21582191
index_entry_reuc_free(reuc);
21592192

2193+
index->dirty = 1;
21602194
return error;
21612195
}
21622196

@@ -2170,6 +2204,8 @@ void git_index_reuc_clear(git_index *index)
21702204
index_entry_reuc_free(git__swap(index->reuc.contents[i], NULL));
21712205

21722206
git_vector_clear(&index->reuc);
2207+
2208+
index->dirty = 1;
21732209
}
21742210

21752211
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)
26042640
git_vector_set_sorted(&index->entries, !index->ignore_case);
26052641
git_vector_sort(&index->entries);
26062642

2643+
index->dirty = 0;
26072644
done:
26082645
return error;
26092646
}
@@ -3070,6 +3107,8 @@ int git_index_read_tree(git_index *index, const git_tree *tree)
30703107
entries_map = git__swap(index->entries_map, entries_map);
30713108
}
30723109

3110+
index->dirty = 1;
3111+
30733112
cleanup:
30743113
git_vector_free(&entries);
30753114
git_idxmap_free(entries_map);
@@ -3209,6 +3248,7 @@ static int git_index_read_iterator(
32093248

32103249
clear_uptodate(index);
32113250

3251+
index->dirty = 1;
32123252
error = 0;
32133253

32143254
done:
@@ -3601,6 +3641,7 @@ int git_indexwriter_commit(git_indexwriter *writer)
36013641
return -1;
36023642
}
36033643

3644+
writer->index->dirty = 0;
36043645
writer->index->on_disk = 1;
36053646
git_oid_cpy(&writer->index->checksum, &checksum);
36063647

src/index.h

Lines changed: 10 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

@@ -37,6 +39,7 @@ struct git_index {
3739
unsigned int ignore_case:1;
3840
unsigned int distrust_filemode:1;
3941
unsigned int no_symlinks:1;
42+
unsigned int dirty:1; /* whether we have unsaved changes */
4043

4144
git_tree_cache *tree;
4245
git_pool tree_pool;
@@ -143,6 +146,13 @@ extern int git_index_snapshot_find(
143146
/* Replace an index with a new index */
144147
int git_index_read_index(git_index *index, const git_index *new_index);
145148

149+
GIT_INLINE(int) git_index_is_dirty(git_index *index)
150+
{
151+
return index->dirty;
152+
}
153+
154+
extern int git_index_read_safely(git_index *index);
155+
146156
typedef struct {
147157
git_index *index;
148158
git_filebuf file;

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;

0 commit comments

Comments
 (0)