diff options
| author | Edward Thomson <ethomson@edwardthomson.com> | 2018-06-30 13:24:23 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-06-30 13:24:23 +0100 |
| commit | c43658f62ee268a0b5ee13f5764544882245063c (patch) | |
| tree | 53e16272b69be5f323ce7caf6cf7ce0302773f96 /src | |
| parent | 68e73791a9dc955db998e3f4385854aed07cab8b (diff) | |
| parent | 243d40df2357620b7fb0c4d10f10d5bf7b67e647 (diff) | |
| download | libgit2-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.c | 41 | ||||
| -rw-r--r-- | src/index.c | 53 | ||||
| -rw-r--r-- | src/index.h | 10 | ||||
| -rw-r--r-- | src/settings.c | 5 | ||||
| -rw-r--r-- | src/stash.c | 105 | ||||
| -rw-r--r-- | src/status.c | 2 |
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); |
