apply: validate workdir contents match index for BOTH When applying to both the index and the working directory, ensure that the index contents match the working directory. This mirrors the requirement in `git apply --index`. This also means that - along with the prior commit that uses the working directory contents as the checkout baseline - we no longer expect conflicts during checkout. So remove the special-case error handling for checkout conflicts. (Any checkout conflict now would be because the file was actually modified between the start of patch application and the checkout.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
diff --git a/src/apply.c b/src/apply.c
index d4c7dd0..df1d0d8 100644
--- a/src/apply.c
+++ b/src/apply.c
@@ -406,15 +406,19 @@ static int apply_one(
delta = git_patch_get_delta(patch);
if (delta->status != GIT_DELTA_ADDED) {
- if ((error = git_reader_read(&pre_contents, &pre_id,
- preimage_reader, delta->old_file.path)) < 0) {
+ error = git_reader_read(&pre_contents, &pre_id,
+ preimage_reader, delta->old_file.path);
- /* ENOTFOUND is really an application error */
- if (error == GIT_ENOTFOUND)
- error = GIT_EAPPLYFAIL;
+ /* ENOTFOUND means the preimage was not found; apply failed. */
+ if (error == GIT_ENOTFOUND)
+ error = GIT_EAPPLYFAIL;
+ /* When applying to BOTH, the index did not match the workdir. */
+ if (error == GIT_READER_MISMATCH)
+ error = apply_err("%s: does not match index", delta->old_file.path);
+
+ if (error < 0)
goto done;
- }
/*
* We need to populate the preimage data structure with the
@@ -563,13 +567,6 @@ static int git_apply__to_workdir(
error = git_checkout_index(repo, postimage, &checkout_opts);
- /*
- * When there's a checkout conflict, the file in the working directory
- * has been modified. Upgrade this error to an application error.
- */
- if (error == GIT_ECONFLICT)
- error = GIT_EAPPLYFAIL;
-
done:
git_vector_free(&paths);
return error;
@@ -645,7 +642,7 @@ int git_apply(
git_reader *pre_reader = NULL;
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
size_t i;
- int error;
+ int error = GIT_EINVALID;
assert(repo && diff);
@@ -660,10 +657,19 @@ int git_apply(
* in `--cached` or `--index` mode, we apply to the contents already
* in the index.
*/
- if (opts.location == GIT_APPLY_LOCATION_WORKDIR)
- error = git_reader_for_workdir(&pre_reader, repo, false);
- else
+ switch (opts.location) {
+ case GIT_APPLY_LOCATION_BOTH:
+ error = git_reader_for_workdir(&pre_reader, repo, true);
+ break;
+ case GIT_APPLY_LOCATION_INDEX:
error = git_reader_for_index(&pre_reader, repo, NULL);
+ break;
+ case GIT_APPLY_LOCATION_WORKDIR:
+ error = git_reader_for_workdir(&pre_reader, repo, false);
+ break;
+ default:
+ assert(false);
+ }
if (error < 0)
goto done;
diff --git a/tests/apply/both.c b/tests/apply/both.c
index 1500e2c..2691af9 100644
--- a/tests/apply/both.c
+++ b/tests/apply/both.c
@@ -185,6 +185,43 @@ void test_apply_both__application_failure_leaves_index_unmodified(void)
git_diff_free(diff);
}
+void test_apply_both__index_must_match_workdir(void)
+{
+ git_diff *diff;
+ git_index *index;
+ git_index_entry idx_entry;
+ git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
+
+ const char *diff_file = DIFF_MODIFY_TWO_FILES;
+
+ /*
+ * Append a line to the end of the file in both the index and the
+ * working directory. Although the appended line would allow for
+ * patch application in each, the line appended is different in
+ * each, so the application should not be allowed.
+ */
+ cl_git_append2file("merge-recursive/asparagus.txt",
+ "This is a modification.\n");
+
+ cl_git_pass(git_repository_index(&index, repo));
+
+ memset(&idx_entry, 0, sizeof(git_index_entry));
+ idx_entry.mode = 0100644;
+ idx_entry.path = "asparagus.txt";
+ cl_git_pass(git_oid_fromstr(&idx_entry.id, "06d3fefb8726ab1099acc76e02dfb85e034b2538"));
+ cl_git_pass(git_index_add(index, &idx_entry));
+
+ cl_git_pass(git_index_write(index));
+ git_index_free(index);
+
+ cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file)));
+
+ opts.location = GIT_APPLY_LOCATION_BOTH;
+ cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, &opts));
+
+ git_diff_free(diff);
+}
+
void test_apply_both__application_failure_leaves_workdir_unmodified(void)
{
git_diff *diff;