apply: handle multiple deltas to the same file git allows a patch file to contain multiple deltas to the same file: although it does not produce files in this format itself, this could be the result of concatenating two different patch files that affected the same file. git apply behaves by applying this next delta to the existing postimage of the file. We should do the same. If we have previously seen a file, and produced a postimage for it, we will load that postimage and apply the current delta to that. If we have not, get the file from the preimage.
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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
diff --git a/src/apply.c b/src/apply.c
index a8ca106..ca500c1 100644
--- a/src/apply.c
+++ b/src/apply.c
@@ -422,6 +422,7 @@ static int apply_one(
git_repository *repo,
git_reader *preimage_reader,
git_index *preimage,
+ git_reader *postimage_reader,
git_index *postimage,
git_diff *diff,
size_t i,
@@ -435,6 +436,7 @@ static int apply_one(
git_oid pre_id, post_id;
git_filemode_t pre_filemode;
git_index_entry pre_entry, post_entry;
+ bool skip_preimage = false;
int error;
if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
@@ -453,7 +455,26 @@ static int apply_one(
}
}
- if (delta->status != GIT_DELTA_ADDED) {
+ /*
+ * We may be applying a second delta to an already seen file. If so,
+ * use the already modified data in the postimage instead of the
+ * content from the index or working directory. (Renames must be
+ * specified before additional deltas since we are applying deltas
+ * to the _target_ filename.)
+ */
+ if (delta->status != GIT_DELTA_RENAMED) {
+ if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
+ postimage_reader, delta->old_file.path)) == 0) {
+ skip_preimage = true;
+ } else if (error == GIT_ENOTFOUND) {
+ giterr_clear();
+ error = 0;
+ } else {
+ goto done;
+ }
+ }
+
+ if (!skip_preimage && delta->status != GIT_DELTA_ADDED) {
error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
preimage_reader, delta->old_file.path);
@@ -527,7 +548,7 @@ int git_apply_to_tree(
const git_apply_options *given_opts)
{
git_index *postimage = NULL;
- git_reader *pre_reader = NULL;
+ git_reader *pre_reader = NULL, *post_reader = NULL;
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
const git_diff_delta *delta;
size_t i;
@@ -548,7 +569,8 @@ int git_apply_to_tree(
* replace any entries contained therein
*/
if ((error = git_index_new(&postimage)) < 0 ||
- (error = git_index_read_tree(postimage, preimage)) < 0)
+ (error = git_index_read_tree(postimage, preimage)) < 0 ||
+ (error = git_reader_for_index(&post_reader, repo, postimage)) < 0)
goto done;
/*
@@ -565,7 +587,7 @@ int git_apply_to_tree(
}
for (i = 0; i < git_diff_num_deltas(diff); i++) {
- if ((error = apply_one(repo, pre_reader, NULL, postimage, diff, i, &opts)) < 0)
+ if ((error = apply_one(repo, pre_reader, NULL, post_reader, postimage, diff, i, &opts)) < 0)
goto done;
}
@@ -576,6 +598,7 @@ done:
git_index_free(postimage);
git_reader_free(pre_reader);
+ git_reader_free(post_reader);
return error;
}
@@ -700,7 +723,7 @@ int git_apply(
{
git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
git_index *index = NULL, *preimage = NULL, *postimage = NULL;
- git_reader *pre_reader = NULL;
+ git_reader *pre_reader = NULL, *post_reader = NULL;
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
size_t i;
int error = GIT_EINVALID;
@@ -743,7 +766,8 @@ int git_apply(
* to only write these files that were affected by the diff.
*/
if ((error = git_index_new(&preimage)) < 0 ||
- (error = git_index_new(&postimage)) < 0)
+ (error = git_index_new(&postimage)) < 0 ||
+ (error = git_reader_for_index(&post_reader, repo, postimage)) < 0)
goto done;
if ((error = git_repository_index(&index, repo)) < 0 ||
@@ -751,7 +775,7 @@ int git_apply(
goto done;
for (i = 0; i < git_diff_num_deltas(diff); i++) {
- if ((error = apply_one(repo, pre_reader, preimage, postimage, diff, i, &opts)) < 0)
+ if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, i, &opts)) < 0)
goto done;
}
@@ -780,6 +804,7 @@ done:
git_index_free(preimage);
git_index_free(index);
git_reader_free(pre_reader);
+ git_reader_free(post_reader);
return error;
}
diff --git a/tests/apply/apply_helpers.h b/tests/apply/apply_helpers.h
index f6342fb..964f167 100644
--- a/tests/apply/apply_helpers.h
+++ b/tests/apply/apply_helpers.h
@@ -255,6 +255,28 @@
"rename from asparagus.txt\n" \
"rename to 2.txt\n"
+#define DIFF_TWO_DELTAS_ONE_FILE \
+ "diff --git a/beef.txt b/beef.txt\n" \
+ "index 68f6182..235069d 100644\n" \
+ "--- a/beef.txt\n" \
+ "+++ b/beef.txt\n" \
+ "@@ -1,4 +1,4 @@\n" \
+ "-BEEF SOUP.\n" \
+ "+BEEF SOUP!\n" \
+ "\n" \
+ " Take the hind shin of beef, cut off all the flesh off the leg-bone,\n" \
+ " which must be taken away entirely, or the soup will be greasy. Wash the\n" \
+ "diff --git a/beef.txt b/beef.txt\n" \
+ "index 68f6182..e059eb5 100644\n" \
+ "--- a/beef.txt\n" \
+ "+++ b/beef.txt\n" \
+ "@@ -19,4 +19,4 @@ a ladle full of the soup, a little at a time; stirring it all the while.\n" \
+ " Strain this browning and mix it well with the soup; take out the bundle\n" \
+ " of thyme and parsley, put the nicest pieces of meat in your tureen, and\n" \
+ " pour on the soup and vegetables; put in some toasted bread cut in dice,\n" \
+ "-and serve it up.\n" \
+ "+and serve it up!\n"
+
struct iterator_compare_data {
struct merge_index_entry *expected;
size_t cnt;
diff --git a/tests/apply/both.c b/tests/apply/both.c
index 0a41772..faafc9a 100644
--- a/tests/apply/both.c
+++ b/tests/apply/both.c
@@ -574,3 +574,28 @@ void test_apply_both__rename_1_to_2(void)
git_diff_free(diff);
}
+
+void test_apply_both__two_deltas_one_file(void)
+{
+ git_diff *diff;
+
+ struct merge_index_entry both_expected[] = {
+ { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" },
+ { 0100644, "0a9fd4415635e72573f0f6b5e68084cfe18f5075", 0, "beef.txt" },
+ { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" },
+ { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" },
+ { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" },
+ { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }
+ };
+ size_t both_expected_cnt = sizeof(both_expected) /
+ sizeof(struct merge_index_entry);
+
+ cl_git_pass(git_diff_from_buffer(&diff, DIFF_TWO_DELTAS_ONE_FILE,
+ strlen(DIFF_TWO_DELTAS_ONE_FILE)));
+ cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL));
+
+ validate_apply_index(repo, both_expected, both_expected_cnt);
+ validate_apply_workdir(repo, both_expected, both_expected_cnt);
+
+ git_diff_free(diff);
+}