summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2018-11-05 15:34:59 +0000
committerEdward Thomson <ethomson@edwardthomson.com>2018-11-05 16:13:37 +0000
commit78580ad31031ddb850de002a02338f160186e247 (patch)
treea7e74e1f224b0d8704ac138a8636dc8685591608
parent605066eee9deac246db0dcef7737297f8e27b20a (diff)
downloadlibgit2-78580ad31031ddb850de002a02338f160186e247.tar.gz
apply: test modifying a file after renaming it
Ensure that we cannot modify a file after it's been renamed out of the way. If multiple deltas exist for a single path, ensure that we do not attempt to modify a file after it's been renamed out of the way. To support this, we must track the paths that have been removed or renamed; add to a string map when we remove a path and remove from the string map if we recreate a path. Validate that we are not applying to a path that is in this map, unless the delta is a rename, since git supports renaming one file to two different places in two different deltas. Further, test that we cannot apply a modification delta to a path that will be created in the future by a rename (a path that does not yet exist.)
-rw-r--r--src/apply.c65
-rw-r--r--tests/apply/apply_helpers.h50
-rw-r--r--tests/apply/both.c22
3 files changed, 125 insertions, 12 deletions
diff --git a/src/apply.c b/src/apply.c
index ca500c123..c3379822d 100644
--- a/src/apply.c
+++ b/src/apply.c
@@ -425,6 +425,7 @@ static int apply_one(
git_reader *postimage_reader,
git_index *postimage,
git_diff *diff,
+ git_strmap *removed_paths,
size_t i,
const git_apply_options *opts)
{
@@ -437,6 +438,7 @@ static int apply_one(
git_filemode_t pre_filemode;
git_index_entry pre_entry, post_entry;
bool skip_preimage = false;
+ size_t pos;
int error;
if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
@@ -458,11 +460,21 @@ static int apply_one(
/*
* 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.)
+ * content from the index or working directory. (Don't do this in
+ * the case of a rename, which must be specified before additional
+ * deltas since we apply deltas to the target filename.)
+ *
+ * Additionally, make sure that the file has not been deleted or renamed
+ * out of the way; again, except in the rename case, since we support
+ * renaming a single file into two target files.
*/
if (delta->status != GIT_DELTA_RENAMED) {
+ pos = git_strmap_lookup_index(removed_paths, delta->old_file.path);
+ if (git_strmap_valid_index(removed_paths, pos)) {
+ error = apply_err("path '%s' has been renamed or deleted", delta->old_file.path);
+ goto done;
+ }
+
if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
postimage_reader, delta->old_file.path)) == 0) {
skip_preimage = true;
@@ -531,6 +543,14 @@ static int apply_one(
goto done;
}
+ if (delta->status == GIT_DELTA_RENAMED ||
+ delta->status == GIT_DELTA_DELETED)
+ git_strmap_insert(removed_paths, delta->old_file.path, (char *)delta->old_file.path, &error);
+
+ if (delta->status == GIT_DELTA_RENAMED ||
+ delta->status == GIT_DELTA_ADDED)
+ git_strmap_delete(removed_paths, delta->new_file.path);
+
done:
git_buf_dispose(&pre_contents);
git_buf_dispose(&post_contents);
@@ -540,6 +560,32 @@ done:
return error;
}
+static int apply_deltas(
+ git_repository *repo,
+ git_reader *pre_reader,
+ git_index *preimage,
+ git_reader *post_reader,
+ git_index *postimage,
+ git_diff *diff,
+ const git_apply_options *opts)
+{
+ git_strmap *removed_paths;
+ size_t i;
+ int error;
+
+ if (git_strmap_alloc(&removed_paths) < 0)
+ return -1;
+
+ for (i = 0; i < git_diff_num_deltas(diff); i++) {
+ if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, removed_paths, i, opts)) < 0)
+ goto done;
+ }
+
+done:
+ git_strmap_free(removed_paths);
+ return error;
+}
+
int git_apply_to_tree(
git_index **out,
git_repository *repo,
@@ -586,10 +632,8 @@ int git_apply_to_tree(
goto done;
}
- for (i = 0; i < git_diff_num_deltas(diff); i++) {
- if ((error = apply_one(repo, pre_reader, NULL, post_reader, postimage, diff, i, &opts)) < 0)
- goto done;
- }
+ if ((error = apply_deltas(repo, pre_reader, NULL, post_reader, postimage, diff, &opts)) < 0)
+ goto done;
*out = postimage;
@@ -725,7 +769,6 @@ int git_apply(
git_index *index = NULL, *preimage = NULL, *postimage = NULL;
git_reader *pre_reader = NULL, *post_reader = NULL;
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
- size_t i;
int error = GIT_EINVALID;
assert(repo && diff);
@@ -774,10 +817,8 @@ int git_apply(
(error = git_indexwriter_init(&indexwriter, index)) < 0)
goto done;
- for (i = 0; i < git_diff_num_deltas(diff); i++) {
- if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, i, &opts)) < 0)
- goto done;
- }
+ if ((error = apply_deltas(repo, pre_reader, preimage, post_reader, postimage, diff, &opts)) < 0)
+ goto done;
switch (location) {
case GIT_APPLY_LOCATION_BOTH:
diff --git a/tests/apply/apply_helpers.h b/tests/apply/apply_helpers.h
index dfd2f7491..a2f4dab6d 100644
--- a/tests/apply/apply_helpers.h
+++ b/tests/apply/apply_helpers.h
@@ -344,6 +344,56 @@
"-but the shin or knuckle is the nicest.\n" \
"+but the shin or knuckle is the nicest!\n"
+#define DIFF_RENAME_AFTER_MODIFY_TARGET_PATH \
+ "diff --git a/beef.txt b/beef.txt\n" \
+ "index 292cb60..61c686b 100644\n" \
+ "--- a/beef.txt\n" \
+ "+++ b/beef.txt\n" \
+ "@@ -1,4 +1,4 @@\n" \
+ "-VEAL SOUP!\n" \
+ "+VEAL SOUP\n" \
+ "\n" \
+ " Put into a pot three quarts of water, three onions cut small, one\n" \
+ " spoonful of black pepper pounded, and two of salt, with two or three\n" \
+ "diff --git a/veal.txt b/beef.txt\n" \
+ "similarity index 96%\n" \
+ "rename from veal.txt\n" \
+ "rename to beef.txt\n" \
+ "index 94d2c01..292cb60 100644\n" \
+ "--- a/veal.txt\n" \
+ "+++ b/beef.txt\n" \
+ "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \
+ " in, first taking off their skins, by letting them stand a few minutes in\n" \
+ " hot water, when they may be easily peeled. When made in this way you\n" \
+ " must thicken it with the flour only. Any part of the veal may be used,\n" \
+ "-but the shin or knuckle is the nicest.\n" \
+ "+but the shin or knuckle is the nicest!\n"
+
+#define DIFF_RENAME_AND_MODIFY_SOURCE_PATH \
+ "diff --git a/veal.txt b/asdf.txt\n" \
+ "similarity index 96%\n" \
+ "rename from veal.txt\n" \
+ "rename to asdf.txt\n" \
+ "index 94d2c01..292cb60 100644\n" \
+ "--- a/veal.txt\n" \
+ "+++ b/asdf.txt\n" \
+ "@@ -15,4 +15,4 @@ will curdle in the soup. For a change you may put a dozen ripe tomatos\n" \
+ " in, first taking off their skins, by letting them stand a few minutes in\n" \
+ " hot water, when they may be easily peeled. When made in this way you\n" \
+ " must thicken it with the flour only. Any part of the veal may be used,\n" \
+ "-but the shin or knuckle is the nicest.\n" \
+ "+but the shin or knuckle is the nicest!\n" \
+ "diff --git a/veal.txt b/veal.txt\n" \
+ "index 292cb60..61c686b 100644\n" \
+ "--- a/veal.txt\n" \
+ "+++ b/veal.txt\n" \
+ "@@ -1,4 +1,4 @@\n" \
+ "-VEAL SOUP!\n" \
+ "+VEAL SOUP\n" \
+ "\n" \
+ " Put into a pot three quarts of water, three onions cut small, one\n" \
+ " spoonful of black pepper pounded, and two of salt, with two or three\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 74fc95c66..f5bb3e4bd 100644
--- a/tests/apply/both.c
+++ b/tests/apply/both.c
@@ -676,3 +676,25 @@ void test_apply_both__rename_delta_after_modify_delta(void)
git_diff_free(diff);
}
+
+void test_apply_both__cant_rename_after_modify_nonexistent_target_path(void)
+{
+ git_diff *diff;
+
+ cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AFTER_MODIFY_TARGET_PATH,
+ strlen(DIFF_RENAME_AFTER_MODIFY_TARGET_PATH)));
+ cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL));
+
+ git_diff_free(diff);
+}
+
+void test_apply_both__cant_modify_source_path_after_rename(void)
+{
+ git_diff *diff;
+
+ cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_SOURCE_PATH,
+ strlen(DIFF_RENAME_AND_MODIFY_SOURCE_PATH)));
+ cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL));
+
+ git_diff_free(diff);
+}