summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/diff.h7
-rw-r--r--src/checkout.c2
-rw-r--r--src/diff.c36
-rw-r--r--src/diff.h2
-rw-r--r--tests/diff/workdir.c87
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);
+}