diff options
-rw-r--r-- | include/git2/diff.h | 7 | ||||
-rw-r--r-- | src/checkout.c | 2 | ||||
-rw-r--r-- | src/diff.c | 36 | ||||
-rw-r--r-- | src/diff.h | 2 | ||||
-rw-r--r-- | tests/diff/workdir.c | 87 |
5 files changed, 128 insertions, 6 deletions
diff --git a/include/git2/diff.h b/include/git2/diff.h index 273f471b6..afdb2c981 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -145,6 +145,13 @@ typedef enum { */ GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS = (1u << 14), + /** When diff finds a file in the working directory with stat + * information different from the index, but the OID ends up being the + * same, write the correct stat information into the index. Note: + * without this flag, diff will always leave the index untouched. + */ + GIT_DIFF_UPDATE_INDEX = (1u << 15), + /* * Options controlling how output will be generated */ diff --git a/src/checkout.c b/src/checkout.c index d94cb0c7d..727911694 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -184,7 +184,7 @@ static bool checkout_is_workdir_modified( if (baseitem->size && wditem->file_size != baseitem->size) return true; - if (git_diff__oid_for_entry(&oid, data->diff, wditem) < 0) + if (git_diff__oid_for_entry(&oid, data->diff, wditem, NULL) < 0) return false; return (git_oid__cmp(&baseitem->id, &oid) != 0); diff --git a/src/diff.c b/src/diff.c index eae4543fc..caed8bf40 100644 --- a/src/diff.c +++ b/src/diff.c @@ -442,6 +442,14 @@ static int diff_list_apply_options( diff->new_src = tmp_src; } + /* Unset UPDATE_INDEX unless diffing workdir and index */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && + (!(diff->old_src == GIT_ITERATOR_TYPE_WORKDIR || + diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) || + !(diff->old_src == GIT_ITERATOR_TYPE_INDEX || + diff->new_src == GIT_ITERATOR_TYPE_INDEX))) + diff->opts.flags &= ~GIT_DIFF_UPDATE_INDEX; + /* if ignore_submodules not explicitly set, check diff config */ if (diff->opts.ignore_submodules <= 0) { const git_config_entry *entry; @@ -523,11 +531,14 @@ int git_diff__oid_for_file( entry.file_size = size; entry.path = (char *)path; - return git_diff__oid_for_entry(out, diff, &entry); + return git_diff__oid_for_entry(out, diff, &entry, NULL); } int git_diff__oid_for_entry( - git_oid *out, git_diff *diff, const git_index_entry *src) + git_oid *out, + git_diff *diff, + const git_index_entry *src, + const git_oid *update_match) { int error = 0; git_buf full_path = GIT_BUF_INIT; @@ -595,7 +606,16 @@ int git_diff__oid_for_entry( git_filter_list_free(fl); } - /* TODO: update index for entry if requested */ + /* update index for entry if requested */ + if (!error && update_match && git_oid_equal(out, update_match)) { + git_index *idx; + + if (!(error = git_repository_index(&idx, diff->repo))) { + memcpy(&entry.id, out, sizeof(entry.id)); + error = git_index_add(idx, &entry); + git_index_free(idx); + } + } git_buf_free(&full_path); return error; @@ -776,7 +796,12 @@ static int maybe_modified( */ if (modified_uncertain && git_oid_iszero(&nitem->id)) { if (git_oid_iszero(&noid)) { - if ((error = git_diff__oid_for_entry(&noid, diff, nitem)) < 0) + const git_oid *update_check = + DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) ? + &oitem->id : NULL; + + if ((error = git_diff__oid_for_entry( + &noid, diff, nitem, update_check)) < 0) return error; } @@ -1208,6 +1233,9 @@ int git_diff_index_to_workdir( &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx) ); + if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX)) + error = git_index_write(index); + return error; } diff --git a/src/diff.h b/src/diff.h index 8fa3c9b7b..b2b7dba70 100644 --- a/src/diff.h +++ b/src/diff.h @@ -97,7 +97,7 @@ extern int git_diff_delta__format_file_header( extern int git_diff__oid_for_file( git_oid *out, git_diff *, const char *, uint16_t, git_off_t); extern int git_diff__oid_for_entry( - git_oid *out, git_diff *, const git_index_entry *entry); + git_oid *out, git_diff *, const git_index_entry *, const git_oid *update); extern int git_diff__from_iterators( git_diff **diff_ptr, diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index 84c8866e0..9e4608e9d 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -1502,3 +1502,90 @@ void test_diff_workdir__with_stale_index(void) git_index_free(idx); } + +static int touch_file(void *payload, git_buf *path) +{ + int fd; + char b; + + GIT_UNUSED(payload); + if (git_path_isdir(path->ptr)) + return 0; + + cl_assert((fd = p_open(path->ptr, O_RDWR)) >= 0); + cl_assert_equal_i(1, p_read(fd, &b, 1)); + cl_must_pass(p_lseek(fd, 0, SEEK_SET)); + cl_must_pass(p_write(fd, &b, 1)); + cl_must_pass(p_close(fd)); + + return 0; +} + +static void basic_diff_status(git_diff **out, const git_diff_options *opts) +{ + diff_expects exp; + + cl_git_pass(git_diff_index_to_workdir(out, g_repo, NULL, opts)); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + *out, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); +} + +void test_diff_workdir__can_update_index(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + + g_repo = cl_git_sandbox_init("status"); + + /* touch all the files so stat times are different */ + { + git_buf path = GIT_BUF_INIT; + cl_git_pass(git_buf_sets(&path, "status")); + cl_git_pass(git_path_direach(&path, 0, touch_file, NULL)); + git_buf_free(&path); + } + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + basic_diff_status(&diff, &opts); +#ifdef GIT_PERF + cl_assert_equal_sz(diff->stat_calls, 13 + 3); + cl_assert_equal_sz(diff->oid_calculations, 5); + cl_assert_equal_sz(diff->submodule_lookups, 1); +#endif + + git_diff_free(diff); + + /* now allow diff to update stat cache */ + opts.flags |= GIT_DIFF_UPDATE_INDEX; + + basic_diff_status(&diff, &opts); +#ifdef GIT_PERF + cl_assert_equal_sz(diff->stat_calls, 13 + 3); + cl_assert_equal_sz(diff->oid_calculations, 5); + cl_assert_equal_sz(diff->submodule_lookups, 1); +#endif + + git_diff_free(diff); + + /* now if we do it again, we should see fewer OID calculations */ + + basic_diff_status(&diff, &opts); +#ifdef GIT_PERF + cl_assert_equal_sz(diff->stat_calls, 13 + 3); + cl_assert_equal_sz(diff->oid_calculations, 0); /* Yay */ + cl_assert_equal_sz(diff->submodule_lookups, 1); +#endif + + git_diff_free(diff); +} |