diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2018-11-05 15:34:59 +0000 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2018-11-05 16:13:37 +0000 |
commit | 78580ad31031ddb850de002a02338f160186e247 (patch) | |
tree | a7e74e1f224b0d8704ac138a8636dc8685591608 | |
parent | 605066eee9deac246db0dcef7737297f8e27b20a (diff) | |
download | libgit2-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.c | 65 | ||||
-rw-r--r-- | tests/apply/apply_helpers.h | 50 | ||||
-rw-r--r-- | tests/apply/both.c | 22 |
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); +} |