diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2021-11-16 23:29:22 -0500 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2022-02-22 22:07:45 -0500 |
commit | 3344fddc97bbdea9c1b6ebb6f7fb6dbd70b41dfb (patch) | |
tree | fd6368a72944571c51627b40c592e7d58e0036e1 /tests/libgit2/apply | |
parent | 91ba089663f5efc3bd4ba14a5099372cf5ce57a6 (diff) | |
download | libgit2-3344fddc97bbdea9c1b6ebb6f7fb6dbd70b41dfb.tar.gz |
refactor: `tests` is now `tests/libgit2`
Like we want to separate libgit2 and utility source code, we want to
separate libgit2 and utility tests. Start by moving all the tests into
libgit2.
Diffstat (limited to 'tests/libgit2/apply')
-rw-r--r-- | tests/libgit2/apply/apply_helpers.c | 135 | ||||
-rw-r--r-- | tests/libgit2/apply/apply_helpers.h | 497 | ||||
-rw-r--r-- | tests/libgit2/apply/both.c | 747 | ||||
-rw-r--r-- | tests/libgit2/apply/callbacks.c | 128 | ||||
-rw-r--r-- | tests/libgit2/apply/check.c | 121 | ||||
-rw-r--r-- | tests/libgit2/apply/fromdiff.c | 378 | ||||
-rw-r--r-- | tests/libgit2/apply/fromfile.c | 449 | ||||
-rw-r--r-- | tests/libgit2/apply/index.c | 321 | ||||
-rw-r--r-- | tests/libgit2/apply/partial.c | 231 | ||||
-rw-r--r-- | tests/libgit2/apply/tree.c | 94 | ||||
-rw-r--r-- | tests/libgit2/apply/workdir.c | 358 |
11 files changed, 3459 insertions, 0 deletions
diff --git a/tests/libgit2/apply/apply_helpers.c b/tests/libgit2/apply/apply_helpers.c new file mode 100644 index 000000000..91cc51a71 --- /dev/null +++ b/tests/libgit2/apply/apply_helpers.c @@ -0,0 +1,135 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +struct iterator_compare_data { + struct merge_index_entry *expected; + size_t cnt; + size_t idx; +}; + +static int iterator_compare(const git_index_entry *entry, void *_data) +{ + git_oid expected_id; + + struct iterator_compare_data *data = (struct iterator_compare_data *)_data; + + cl_assert_equal_i(GIT_INDEX_ENTRY_STAGE(entry), data->expected[data->idx].stage); + cl_git_pass(git_oid_fromstr(&expected_id, data->expected[data->idx].oid_str)); + cl_assert_equal_oid(&entry->id, &expected_id); + cl_assert_equal_i(entry->mode, data->expected[data->idx].mode); + cl_assert_equal_s(entry->path, data->expected[data->idx].path); + + if (data->idx >= data->cnt) + return -1; + + data->idx++; + + return 0; +} + +void validate_apply_workdir( + git_repository *repo, + struct merge_index_entry *workdir_entries, + size_t workdir_cnt) +{ + git_index *index; + git_iterator *iterator; + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + struct iterator_compare_data data = { workdir_entries, workdir_cnt }; + + opts.flags |= GIT_ITERATOR_INCLUDE_HASH; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_iterator_for_workdir(&iterator, repo, index, NULL, &opts)); + + cl_git_pass(git_iterator_foreach(iterator, iterator_compare, &data)); + cl_assert_equal_i(data.idx, data.cnt); + + git_iterator_free(iterator); + git_index_free(index); +} + +void validate_apply_index( + git_repository *repo, + struct merge_index_entry *index_entries, + size_t index_cnt) +{ + git_index *index; + git_iterator *iterator; + struct iterator_compare_data data = { index_entries, index_cnt }; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_iterator_for_index(&iterator, repo, index, NULL)); + + cl_git_pass(git_iterator_foreach(iterator, iterator_compare, &data)); + cl_assert_equal_i(data.idx, data.cnt); + + git_iterator_free(iterator); + git_index_free(index); +} + +static int iterator_eq(const git_index_entry **entry, void *_data) +{ + GIT_UNUSED(_data); + + if (!entry[0] || !entry[1]) + return -1; + + cl_assert_equal_i(GIT_INDEX_ENTRY_STAGE(entry[0]), GIT_INDEX_ENTRY_STAGE(entry[1])); + cl_assert_equal_oid(&entry[0]->id, &entry[1]->id); + cl_assert_equal_i(entry[0]->mode, entry[1]->mode); + cl_assert_equal_s(entry[0]->path, entry[1]->path); + + return 0; +} + +void validate_index_unchanged(git_repository *repo) +{ + git_tree *head; + git_index *index; + git_iterator *head_iterator, *index_iterator, *iterators[2]; + + cl_git_pass(git_repository_head_tree(&head, repo)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_pass(git_iterator_for_tree(&head_iterator, head, NULL)); + cl_git_pass(git_iterator_for_index(&index_iterator, repo, index, NULL)); + + iterators[0] = head_iterator; + iterators[1] = index_iterator; + + cl_git_pass(git_iterator_walk(iterators, 2, iterator_eq, NULL)); + + git_iterator_free(head_iterator); + git_iterator_free(index_iterator); + + git_tree_free(head); + git_index_free(index); +} + +void validate_workdir_unchanged(git_repository *repo) +{ + git_tree *head; + git_index *index; + git_iterator *head_iterator, *workdir_iterator, *iterators[2]; + git_iterator_options workdir_opts = GIT_ITERATOR_OPTIONS_INIT; + + cl_git_pass(git_repository_head_tree(&head, repo)); + cl_git_pass(git_repository_index(&index, repo)); + + workdir_opts.flags |= GIT_ITERATOR_INCLUDE_HASH; + + cl_git_pass(git_iterator_for_tree(&head_iterator, head, NULL)); + cl_git_pass(git_iterator_for_workdir(&workdir_iterator, repo, index, NULL, &workdir_opts)); + + iterators[0] = head_iterator; + iterators[1] = workdir_iterator; + + cl_git_pass(git_iterator_walk(iterators, 2, iterator_eq, NULL)); + + git_iterator_free(head_iterator); + git_iterator_free(workdir_iterator); + + git_tree_free(head); + git_index_free(index); +} diff --git a/tests/libgit2/apply/apply_helpers.h b/tests/libgit2/apply/apply_helpers.h new file mode 100644 index 000000000..82094773e --- /dev/null +++ b/tests/libgit2/apply/apply_helpers.h @@ -0,0 +1,497 @@ +#include "../merge/merge_helpers.h" + +#define TEST_REPO_PATH "merge-recursive" + +#define DIFF_MODIFY_TWO_FILES \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "index f516580..ffb36e5 100644\n" \ + "--- a/asparagus.txt\n" \ + "+++ b/asparagus.txt\n" \ + "@@ -1 +1 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "+ASPARAGUS SOUP.\n" \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 94d2c01..a7b0665 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/veal.txt\n" \ + "@@ -1 +1 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP.\n" \ + "@@ -7 +7 @@ occasionally, then put into it a shin of veal, let it boil two hours\n" \ + "-longer. take out the slices of ham, and skim off the grease if any\n" \ + "+longer; take out the slices of ham, and skim off the grease if any\n" + +/* This is the binary equivalent of DIFF_MODIFY_TWO_FILES */ +#define DIFF_MODIFY_TWO_FILES_BINARY \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "index f51658077d85f2264fa179b4d0848268cb3475c3..ffb36e513f5fdf8a6ba850a20142676a2ac4807d 100644\n" \ + "GIT binary patch\n" \ + "delta 24\n" \ + "fcmX@ja+-zTF*v|6$k9DCSRvRyG(c}7zYP-rT_OhP\n" \ + "\n" \ + "delta 24\n" \ + "fcmX@ja+-zTF*v|6$k9DCSRvRyG(d49zYP-rT;T@W\n" \ + "\n" \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 94d2c01087f48213bd157222d54edfefd77c9bba..a7b066537e6be7109abfe4ff97b675d4e077da20 100644\n" \ + "GIT binary patch\n" \ + "delta 26\n" \ + "hcmX@kah!uI%+=9HA=p1OKyM?L03)OIW@$zpW&mXg25bNT\n" \ + "\n" \ + "delta 26\n" \ + "hcmX@kah!uI%+=9HA=p1OKyf3N03)N`W@$zpW&mU#22ub3\n" \ + "\n" + +#define DIFF_DELETE_FILE \ + "diff --git a/gravy.txt b/gravy.txt\n" \ + "deleted file mode 100644\n" \ + "index c4e6cca..0000000\n" \ + "--- a/gravy.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,8 +0,0 @@\n" \ + "-GRAVY SOUP.\n" \ + "-\n" \ + "-Get eight pounds of coarse lean beef--wash it clean and lay it in your\n" \ + "-pot, put in the same ingredients as for the shin soup, with the same\n" \ + "-quantity of water, and follow the process directed for that. Strain the\n" \ + "-soup through a sieve, and serve it up clear, with nothing more than\n" \ + "-toasted bread in it; two table-spoonsful of mushroom catsup will add a\n" \ + "-fine flavour to the soup.\n" + +#define DIFF_ADD_FILE \ + "diff --git a/newfile.txt b/newfile.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..6370543\n" \ + "--- /dev/null\n" \ + "+++ b/newfile.txt\n" \ + "@@ -0,0 +1,2 @@\n" \ + "+This is a new file!\n" \ + "+Added by a patch.\n" + +#define DIFF_EXECUTABLE_FILE \ + "diff --git a/beef.txt b/beef.txt\n" \ + "old mode 100644\n" \ + "new mode 100755\n" + +#define DIFF_MANY_CHANGES_ONE \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 94d2c01..c9d7d5d 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/veal.txt\n" \ + "@@ -1,2 +1,2 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP\n" \ + " \n" \ + "@@ -4,3 +4,2 @@\n" \ + " spoonful of black pepper pounded, and two of salt, with two or three\n" \ + "-slices of lean ham; let it boil steadily two hours; skim it\n" \ + " occasionally, then put into it a shin of veal, let it boil two hours\n" \ + "@@ -8,3 +7,3 @@\n" \ + " should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ + "-of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ + "+OF FLOUR very nicely, and the yelks of two eggs beaten well, strain this\n" \ + " mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ + "@@ -12,2 +11,3 @@\n" \ + " boiled two or three minutes to take off the raw taste of the eggs. If\n" \ + "+Inserted line.\n" \ + " the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ + "@@ -15,3 +15,3 @@\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" \ + "+Changed line.\n" \ + " must thicken it with the flour only. Any part of the veal may be used,\n" + +#define DIFF_MANY_CHANGES_TWO \ + "diff --git a/veal.txt b/veal.txt\n" \ + "index 94d2c01..6b943d6 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/veal.txt\n" \ + "@@ -1,2 +1,2 @@\n" \ + "-VEAL SOUP!\n" \ + "+VEAL SOUP!!!\n" \ + " \n" \ + "@@ -4,3 +4,2 @@\n" \ + " spoonful of black pepper pounded, and two of salt, with two or three\n" \ + "-slices of lean ham; let it boil steadily two hours; skim it\n" \ + " occasionally, then put into it a shin of veal, let it boil two hours\n" \ + "@@ -8,3 +7,3 @@\n" \ + " should rise, take a gill of good cream, mix with it two table-spoonsful\n" \ + "-of flour very nicely, and the yelks of two eggs beaten well, strain this\n" \ + "+of flour very nicely, AND the yelks of two eggs beaten well, strain this\n" \ + " mixture, and add some chopped parsley; pour some soup on by degrees,\n" \ + "@@ -12,2 +11,3 @@\n" \ + " boiled two or three minutes to take off the raw taste of the eggs. If\n" \ + "+New line.\n" \ + " the cream be not perfectly sweet, and the eggs quite new, the thickening\n" \ + "@@ -15,4 +15,5 @@\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" \ + "+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" \ + "+Another new line.\n" \ + +#define DIFF_RENAME_FILE \ + "diff --git a/beef.txt b/notbeef.txt\n" \ + "similarity index 100%\n" \ + "rename from beef.txt\n" \ + "rename to notbeef.txt\n" + +#define DIFF_RENAME_AND_MODIFY_FILE \ + "diff --git a/beef.txt b/notbeef.txt\n" \ + "similarity index 97%\n" \ + "rename from beef.txt\n" \ + "rename to notbeef.txt\n" \ + "index 68f6182..6fa1014 100644\n" \ + "--- a/beef.txt\n" \ + "+++ b/notbeef.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-BEEF SOUP.\n" \ + "+THIS IS NOT BEEF SOUP, IT HAS A NEW NAME.\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" + +#define DIFF_RENAME_A_TO_B_TO_C \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "deleted file mode 100644\n" \ + "index f516580..0000000\n" \ + "--- a/asparagus.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,10 +0,0 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "-\n" \ + "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "-pulp them through a sieve, and strain the water to it, which must be put\n" \ + "-back in the pot; put into it a chicken cut up, with the tops of\n" \ + "-asparagus which had been laid by, boil it until these last articles are\n" \ + "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ + "diff --git a/beef.txt b/beef.txt\n" \ + "index 68f6182..f516580 100644\n" \ + "--- a/beef.txt\n" \ + "+++ b/beef.txt\n" \ + "@@ -1,22 +1,10 @@\n" \ + "-BEEF SOUP.\n" \ + "+ASPARAGUS 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" \ + "-meat clean and lay it in a pot, sprinkle over it one small\n" \ + "-table-spoonful of pounded black pepper, and two of salt; three onions\n" \ + "-the size of a hen's egg, cut small, six small carrots scraped and cut\n" \ + "-up, two small turnips pared and cut into dice; pour on three quarts of\n" \ + "-water, cover the pot close, and keep it gently and steadily boiling five\n" \ + "-hours, which will leave about three pints of clear soup; do not let the\n" \ + "-pot boil over, but take off the scum carefully, as it rises. When it has\n" \ + "-boiled four hours, put in a small bundle of thyme and parsley, and a\n" \ + "-pint of celery cut small, or a tea-spoonful of celery seed pounded.\n" \ + "-These latter ingredients would lose their delicate flavour if boiled too\n" \ + "-much. Just before you take it up, brown it in the following manner: put\n" \ + "-a small table-spoonful of nice brown sugar into an iron skillet, set it\n" \ + "-on the fire and stir it till it melts and looks very dark, pour into it\n" \ + "-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" \ + "+Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "+of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "+fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "+add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "+pulp them through a sieve, and strain the water to it, which must be put\n" \ + "+back in the pot; put into it a chicken cut up, with the tops of\n" \ + "+asparagus which had been laid by, boil it until these last articles are\n" \ + "+sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ + "diff --git a/notbeef.txt b/notbeef.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..68f6182\n" \ + "--- /dev/null\n" \ + "+++ b/notbeef.txt\n" \ + "@@ -0,0 +1,22 @@\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" \ + "+meat clean and lay it in a pot, sprinkle over it one small\n" \ + "+table-spoonful of pounded black pepper, and two of salt; three onions\n" \ + "+the size of a hen's egg, cut small, six small carrots scraped and cut\n" \ + "+up, two small turnips pared and cut into dice; pour on three quarts of\n" \ + "+water, cover the pot close, and keep it gently and steadily boiling five\n" \ + "+hours, which will leave about three pints of clear soup; do not let the\n" \ + "+pot boil over, but take off the scum carefully, as it rises. When it has\n" \ + "+boiled four hours, put in a small bundle of thyme and parsley, and a\n" \ + "+pint of celery cut small, or a tea-spoonful of celery seed pounded.\n" \ + "+These latter ingredients would lose their delicate flavour if boiled too\n" \ + "+much. Just before you take it up, brown it in the following manner: put\n" \ + "+a small table-spoonful of nice brown sugar into an iron skillet, set it\n" \ + "+on the fire and stir it till it melts and looks very dark, pour into it\n" \ + "+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" + +#define DIFF_RENAME_A_TO_B_TO_C_EXACT \ + "diff --git a/asparagus.txt b/beef.txt\n" \ + "similarity index 100%\n" \ + "rename from asparagus.txt\n" \ + "rename to beef.txt\n" \ + "diff --git a/beef.txt b/notbeef.txt\n" \ + "similarity index 100%\n" \ + "rename from beef.txt\n" \ + "rename to notbeef.txt\n" + +#define DIFF_RENAME_CIRCULAR \ + "diff --git a/asparagus.txt b/beef.txt\n" \ + "similarity index 100%\n" \ + "rename from asparagus.txt\n" \ + "rename to beef.txt\n" \ + "diff --git a/beef.txt b/notbeef.txt\n" \ + "similarity index 100%\n" \ + "rename from beef.txt\n" \ + "rename to asparagus.txt\n" + +#define DIFF_RENAME_2_TO_1 \ + "diff --git a/asparagus.txt b/2.txt\n" \ + "similarity index 100%\n" \ + "rename from asparagus.txt\n" \ + "rename to 2.txt\n" \ + "diff --git a/beef.txt b/2.txt\n" \ + "similarity index 100%\n" \ + "rename from beef.txt\n" \ + "rename to 2.txt\n" + +#define DIFF_RENAME_1_TO_2 \ + "diff --git a/asparagus.txt b/2.txt\n" \ + "similarity index 100%\n" \ + "rename from asparagus.txt\n" \ + "rename to 1.txt\n" \ + "diff --git a/asparagus.txt b/2.txt\n" \ + "similarity index 100%\n" \ + "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" + +#define DIFF_TWO_DELTAS_ONE_NEW_FILE \ + "diff --git a/newfile.txt b/newfile.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..6434b13\n" \ + "--- /dev/null\n" \ + "+++ b/newfile.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+This is a new file.\n" \ + "diff --git a/newfile.txt b/newfile.txt\n" \ + "index 6434b13..08d4c44 100644\n" \ + "--- a/newfile.txt\n" \ + "+++ b/newfile.txt\n" \ + "@@ -1 +1,3 @@\n" \ + " This is a new file.\n" \ + "+\n" \ + "+This is another change to a new file.\n" + +#define DIFF_RENAME_AND_MODIFY_DELTAS \ + "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/asdf.txt b/asdf.txt\n" \ + "index 292cb60..61c686b 100644\n" \ + "--- a/asdf.txt\n" \ + "+++ b/asdf.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" + +#define DIFF_RENAME_AFTER_MODIFY \ + "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" \ + "diff --git a/veal.txt b/other.txt\n" \ + "similarity index 96%\n" \ + "rename from veal.txt\n" \ + "rename to other.txt\n" \ + "index 94d2c01..292cb60 100644\n" \ + "--- a/veal.txt\n" \ + "+++ b/other.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_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" + +#define DIFF_DELETE_AND_READD_FILE \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "deleted file mode 100644\n" \ + "index f516580..0000000\n" \ + "--- a/asparagus.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,10 +0,0 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "-\n" \ + "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "-pulp them through a sieve, and strain the water to it, which must be put\n" \ + "-back in the pot; put into it a chicken cut up, with the tops of\n" \ + "-asparagus which had been laid by, boil it until these last articles are\n" \ + "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..2dc7f8b\n" \ + "--- /dev/null\n" \ + "+++ b/asparagus.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+New file.\n" \ + +#define DIFF_REMOVE_FILE_TWICE \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "deleted file mode 100644\n" \ + "index f516580..0000000\n" \ + "--- a/asparagus.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,10 +0,0 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "-\n" \ + "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "-pulp them through a sieve, and strain the water to it, which must be put\n" \ + "-back in the pot; put into it a chicken cut up, with the tops of\n" \ + "-asparagus which had been laid by, boil it until these last articles are\n" \ + "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" \ + "diff --git a/asparagus.txt b/asparagus.txt\n" \ + "deleted file mode 100644\n" \ + "index f516580..0000000\n" \ + "--- a/asparagus.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,10 +0,0 @@\n" \ + "-ASPARAGUS SOUP!\n" \ + "-\n" \ + "-Take four large bunches of asparagus, scrape it nicely, cut off one inch\n" \ + "-of the tops, and lay them in water, chop the stalks and put them on the\n" \ + "-fire with a piece of bacon, a large onion cut up, and pepper and salt;\n" \ + "-add two quarts of water, boil them till the stalks are quite soft, then\n" \ + "-pulp them through a sieve, and strain the water to it, which must be put\n" \ + "-back in the pot; put into it a chicken cut up, with the tops of\n" \ + "-asparagus which had been laid by, boil it until these last articles are\n" \ + "-sufficiently done, thicken with flour, butter and milk, and serve it up.\n" + +#define DIFF_ADD_INVALID_FILENAME \ + "diff --git a/.git/hello_world.txt b/.git/hello_world.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..f75ba05\n" \ + "--- /dev/null\n" \ + "+++ b/.git/hello_world.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+Hello, world.\n" + +void validate_apply_workdir( + git_repository *repo, + struct merge_index_entry *workdir_entries, + size_t workdir_cnt); + +void validate_apply_index( + git_repository *repo, + struct merge_index_entry *index_entries, + size_t index_cnt); + +void validate_index_unchanged(git_repository *repo); +void validate_workdir_unchanged(git_repository *repo); diff --git a/tests/libgit2/apply/both.c b/tests/libgit2/apply/both.c new file mode 100644 index 000000000..108963270 --- /dev/null +++ b/tests/libgit2/apply/both.c @@ -0,0 +1,747 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_both__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_both__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_both__generated_diff(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + + struct merge_index_entry both_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); + 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); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_both__parsed_diff(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 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_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + 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); +} + +void test_apply_both__removes_file(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.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_DELETE_FILE, + strlen(DIFF_DELETE_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); +} + +void test_apply_both__adds_file(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.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_ADD_FILE, strlen(DIFF_ADD_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); +} + +void test_apply_both__application_failure_leaves_index_unmodified(void) +{ + git_diff *diff; + git_index *index; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_remove(index, "veal.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_both__index_must_match_workdir(void) +{ + git_diff *diff; + git_index *index; + git_index_entry idx_entry; + + 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))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} + +void test_apply_both__index_mode_must_match_workdir(void) +{ + git_diff *diff; + + if (!cl_is_chmod_supported()) + clar__skip(); + + /* Set a file in the working directory executable. */ + cl_must_pass(p_chmod("merge-recursive/asparagus.txt", 0755)); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_MODIFY_TWO_FILES, + strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} + +void test_apply_both__application_failure_leaves_workdir_unmodified(void) +{ + git_diff *diff; + git_index *index; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "8684724651336001c5dbce74bed6736d2443958d", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the workdir */ + cl_git_rewritefile("merge-recursive/veal.txt", + "This is a modification.\n"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "veal.txt")); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__keeps_nonconflicting_changes(void) +{ + git_diff *diff; + git_index *index; + git_index_entry idx_entry; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "beef.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "gravy.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + + memset(&idx_entry, 0, sizeof(git_index_entry)); + idx_entry.mode = 0100644; + idx_entry.path = "beef.txt"; + cl_git_pass(git_oid_fromstr(&idx_entry.id, "898d12687fb35be271c27c795a6b32c8b51da79e")); + cl_git_pass(git_index_add(index, &idx_entry)); + + cl_git_pass(git_index_remove(index, "bouilli.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + /* and mutate the working directory */ + cl_git_rmfile("merge-recursive/oyster.txt"); + cl_git_rewritefile("merge-recursive/gravy.txt", "Hello, world.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__can_apply_nonconflicting_file_changes(void) +{ + git_diff *diff; + git_index *index; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry both_expected[] = { + { 0100644, "f8a701c8a1a22c1729ee50faff1111f2d64f96fc", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + /* + * Replace the workdir file with a version that is different than + * HEAD but such that the patch still applies cleanly. This item + * has a new line appended. + */ + cl_git_append2file("merge-recursive/asparagus.txt", + "This line is added in the index and the workdir.\n"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, "asparagus.txt")); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_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); +} + +void test_apply_both__honors_crlf_attributes(void) +{ + git_diff *diff; + git_oid oid; + git_commit *commit; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + struct merge_index_entry workdir_expected[] = { + { 0100644, "176a458f94e0ea5272ce67c36bf30b6be9caf623", 0, ".gitattributes" }, + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_mkfile("merge-recursive/.gitattributes", "* text=auto\n"); + + cl_git_rmfile("merge-recursive/asparagus.txt"); + cl_git_rmfile("merge-recursive/veal.txt"); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_both__rename(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.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_RENAME_FILE, + strlen(DIFF_RENAME_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); +} + +void test_apply_both__rename_and_modify(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6fa10147f00fe1fab1d5e835529a9dad53db8552", 0, "notbeef.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_RENAME_AND_MODIFY_FILE, + strlen(DIFF_RENAME_AND_MODIFY_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); +} + +void test_apply_both__rename_a_to_b_to_c(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.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_RENAME_A_TO_B_TO_C, + strlen(DIFF_RENAME_A_TO_B_TO_C))); + 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); +} + +void test_apply_both__rename_a_to_b_to_c_exact(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "notbeef.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_RENAME_A_TO_B_TO_C_EXACT, + strlen(DIFF_RENAME_A_TO_B_TO_C_EXACT))); + 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); +} + +void test_apply_both__rename_circular(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "asparagus.txt" }, + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 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_RENAME_CIRCULAR, + strlen(DIFF_RENAME_CIRCULAR))); + 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); +} + +void test_apply_both__rename_2_to_1(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "2.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_RENAME_2_TO_1, + strlen(DIFF_RENAME_2_TO_1))); + 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); +} + +void test_apply_both__rename_1_to_2(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "1.txt" }, + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "2.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 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_RENAME_1_TO_2, + strlen(DIFF_RENAME_1_TO_2))); + 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); +} + +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); +} + +void test_apply_both__two_deltas_one_new_file(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "08d4c445cf0078f3d9b604b82f32f4d87e083325", 0, "newfile.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_NEW_FILE, + strlen(DIFF_TWO_DELTAS_ONE_NEW_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); +} + +void test_apply_both__rename_and_modify_deltas(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "61c686bed39684eee8a2757ceb1291004a21333f", 0, "asdf.txt" }, + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + }; + size_t both_expected_cnt = sizeof(both_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_RENAME_AND_MODIFY_DELTAS, + strlen(DIFF_RENAME_AND_MODIFY_DELTAS))); + 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); +} + +void test_apply_both__rename_delta_after_modify_delta(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "292cb60ce5e25c337c5b6e12957bbbfe1be4bf49", 0, "other.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "c8c120f466591bbe3b8867361d5ec3cdd9fda756", 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_RENAME_AFTER_MODIFY, + strlen(DIFF_RENAME_AFTER_MODIFY))); + 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); +} + +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); +} + +void test_apply_both__readd_deleted_file(void) +{ + git_diff *diff; + + struct merge_index_entry both_expected[] = { + { 0100644, "2dc7f8b24ba27f3888368bd180df03ff4c6c6fab", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 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_DELETE_AND_READD_FILE, + strlen(DIFF_DELETE_AND_READD_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); +} + +void test_apply_both__cant_remove_file_twice(void) +{ + git_diff *diff; + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_REMOVE_FILE_TWICE, + strlen(DIFF_REMOVE_FILE_TWICE))); + cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} + +void test_apply_both__cant_add_invalid_filename(void) +{ + git_diff *diff; + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_ADD_INVALID_FILENAME, + strlen(DIFF_ADD_INVALID_FILENAME))); + cl_git_fail(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); + + git_diff_free(diff); +} diff --git a/tests/libgit2/apply/callbacks.c b/tests/libgit2/apply/callbacks.c new file mode 100644 index 000000000..1b759dc9b --- /dev/null +++ b/tests/libgit2/apply/callbacks.c @@ -0,0 +1,128 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_callbacks__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_callbacks__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int delta_abort_cb(const git_diff_delta *delta, void *payload) +{ + GIT_UNUSED(payload); + + if (!strcmp(delta->old_file.path, "veal.txt")) + return -99; + + return 0; +} + +void test_apply_callbacks__delta_aborts(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + opts.delta_cb = delta_abort_cb; + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_fail_with(-99, + git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +static int delta_skip_cb(const git_diff_delta *delta, void *payload) +{ + GIT_UNUSED(payload); + + if (!strcmp(delta->old_file.path, "asparagus.txt")) + return 1; + + return 0; +} + +void test_apply_callbacks__delta_can_skip(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + opts.delta_cb = delta_skip_cb; + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +static int hunk_skip_odds_cb(const git_diff_hunk *hunk, void *payload) +{ + int *count = (int *)payload; + GIT_UNUSED(hunk); + + return ((*count)++ % 2 == 1); +} + +void test_apply_callbacks__hunk_can_skip(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + int count = 0; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "06f751b6ba4f017ddbf4248015768300268e092a", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + opts.hunk_cb = hunk_skip_odds_cb; + opts.payload = &count; + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MANY_CHANGES_ONE, strlen(DIFF_MANY_CHANGES_ONE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} diff --git a/tests/libgit2/apply/check.c b/tests/libgit2/apply/check.c new file mode 100644 index 000000000..9e42365ed --- /dev/null +++ b/tests/libgit2/apply/check.c @@ -0,0 +1,121 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_check__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_check__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_check__generate_diff(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + cl_git_pass(git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707")); + cl_git_pass(git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f")); + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_check__parsed_diff(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_check__binary(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES_BINARY, + strlen(DIFF_MODIFY_TWO_FILES_BINARY))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_index_unchanged(repo); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_check__does_not_apply(void) +{ + git_diff *diff; + git_index *index; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_remove(index, "veal.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + opts.flags |= GIT_APPLY_CHECK; + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + + git_diff_free(diff); +} diff --git a/tests/libgit2/apply/fromdiff.c b/tests/libgit2/apply/fromdiff.c new file mode 100644 index 000000000..9da6ac923 --- /dev/null +++ b/tests/libgit2/apply/fromdiff.c @@ -0,0 +1,378 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "apply.h" +#include "repository.h" + +#include "../patch/patch_common.h" + +static git_repository *repo = NULL; +static git_diff_options binary_opts = GIT_DIFF_OPTIONS_INIT; + +void test_apply_fromdiff__initialize(void) +{ + repo = cl_git_sandbox_init("renames"); + + binary_opts.flags |= GIT_DIFF_SHOW_BINARY; +} + +void test_apply_fromdiff__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int apply_gitbuf( + const git_str *old, + const char *oldname, + const git_str *new, + const char *newname, + const char *patch_expected, + const git_diff_options *diff_opts) +{ + git_patch *patch; + git_str result = GIT_STR_INIT; + git_buf patchbuf = GIT_BUF_INIT; + char *filename; + unsigned int mode; + int error; + + cl_git_pass(git_patch_from_buffers(&patch, + old ? old->ptr : NULL, old ? old->size : 0, + oldname, + new ? new->ptr : NULL, new ? new->size : 0, + newname, + diff_opts)); + + if (patch_expected) { + cl_git_pass(git_patch_to_buf(&patchbuf, patch)); + cl_assert_equal_s(patch_expected, patchbuf.ptr); + } + + error = git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch, NULL); + + if (error == 0 && new == NULL) { + cl_assert_equal_i(0, result.size); + cl_assert_equal_p(NULL, filename); + cl_assert_equal_i(0, mode); + } + else if (error == 0) { + cl_assert_equal_s(new->ptr, result.ptr); + cl_assert_equal_s(newname ? newname : oldname, filename); + cl_assert_equal_i(0100644, mode); + } + + git__free(filename); + git_str_dispose(&result); + git_buf_dispose(&patchbuf); + git_patch_free(patch); + + return error; +} + +static int apply_buf( + const char *old, + const char *oldname, + const char *new, + const char *newname, + const char *patch_expected, + const git_diff_options *diff_opts) +{ + git_str o = GIT_STR_INIT, n = GIT_STR_INIT, + *optr = NULL, *nptr = NULL; + + if (old) { + o.ptr = (char *)old; + o.size = strlen(old); + optr = &o; + } + + if (new) { + n.ptr = (char *)new; + n.size = strlen(new); + nptr = &n; + } + + return apply_gitbuf(optr, oldname, nptr, newname, patch_expected, diff_opts); +} + +void test_apply_fromdiff__change_middle(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_MIDDLE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL)); +} + +void test_apply_fromdiff__change_middle_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_MIDDLE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__change_firstline(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_FIRSTLINE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL)); +} + +void test_apply_fromdiff__lastline(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_LASTLINE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL)); +} + +void test_apply_fromdiff__change_middle_and_lastline_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_MIDDLE_AND_LASTLINE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_AND_LASTLINE_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__prepend(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND, "file.txt", + PATCH_ORIGINAL_TO_PREPEND, NULL)); +} + +void test_apply_fromdiff__prepend_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__prepend_and_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE, NULL)); +} + +void test_apply_fromdiff__prepend_and_change_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__delete_and_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + PATCH_ORIGINAL_TO_DELETE_AND_CHANGE, NULL)); +} + +void test_apply_fromdiff__delete_and_change_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__delete_firstline(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_FIRSTLINE, "file.txt", + PATCH_ORIGINAL_TO_DELETE_FIRSTLINE, NULL)); +} + +void test_apply_fromdiff__append(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_APPEND, "file.txt", + PATCH_ORIGINAL_TO_APPEND, NULL)); +} + +void test_apply_fromdiff__append_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_APPEND, "file.txt", + PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__prepend_and_append(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_APPEND, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL)); +} + +void test_apply_fromdiff__to_empty_file(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + "", NULL, + PATCH_ORIGINAL_TO_EMPTY_FILE, NULL)); +} + +void test_apply_fromdiff__from_empty_file(void) +{ + cl_git_pass(apply_buf( + "", NULL, + FILE_ORIGINAL, "file.txt", + PATCH_EMPTY_FILE_TO_ORIGINAL, NULL)); +} + +void test_apply_fromdiff__add(void) +{ + cl_git_pass(apply_buf( + NULL, NULL, + FILE_ORIGINAL, "file.txt", + PATCH_ADD_ORIGINAL, NULL)); +} + +void test_apply_fromdiff__delete(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + NULL, NULL, + PATCH_DELETE_ORIGINAL, NULL)); +} + +void test_apply_fromdiff__no_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_ORIGINAL, "file.txt", + "", NULL)); +} + +void test_apply_fromdiff__binary_add(void) +{ + git_str newfile = GIT_STR_INIT; + + newfile.ptr = FILE_BINARY_DELTA_MODIFIED; + newfile.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + NULL, NULL, + &newfile, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_no_change(void) +{ + git_str original = GIT_STR_INIT; + + original.ptr = FILE_BINARY_DELTA_ORIGINAL; + original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &original, "binary.bin", + "", &binary_opts)); +} + +void test_apply_fromdiff__binary_change_delta(void) +{ + git_str original = GIT_STR_INIT, modified = GIT_STR_INIT; + + original.ptr = FILE_BINARY_DELTA_ORIGINAL; + original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; + + modified.ptr = FILE_BINARY_DELTA_MODIFIED; + modified.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &modified, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_change_literal(void) +{ + git_str original = GIT_STR_INIT, modified = GIT_STR_INIT; + + original.ptr = FILE_BINARY_LITERAL_ORIGINAL; + original.size = FILE_BINARY_LITERAL_ORIGINAL_LEN; + + modified.ptr = FILE_BINARY_LITERAL_MODIFIED; + modified.size = FILE_BINARY_LITERAL_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &modified, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_delete(void) +{ + git_str original = GIT_STR_INIT; + + original.ptr = FILE_BINARY_DELTA_MODIFIED; + original.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + NULL, NULL, + NULL, &binary_opts)); +} + +void test_apply_fromdiff__patching_correctly_truncates_source(void) +{ + git_str original = GIT_STR_INIT, patched = GIT_STR_INIT; + git_patch *patch; + unsigned int mode; + char *path; + + cl_git_pass(git_patch_from_buffers(&patch, + "foo\nbar", 7, "file.txt", + "foo\nfoo", 7, "file.txt", NULL)); + + /* + * Previously, we would fail to correctly truncate the source buffer if + * the source has more than one line and ends with a non-newline + * character. In the following call, we thus truncate the source string + * in the middle of the second line. Without the bug fixed, we would + * successfully apply the patch to the source and return success. With + * the overflow being fixed, we should return an error. + */ + cl_git_fail_with(GIT_EAPPLYFAIL, + git_apply__patch(&patched, &path, &mode, + "foo\nbar\n", 5, patch, NULL)); + + /* Verify that the patch succeeds if we do not truncate */ + cl_git_pass(git_apply__patch(&patched, &path, &mode, + "foo\nbar\n", 7, patch, NULL)); + + git_str_dispose(&original); + git_str_dispose(&patched); + git_patch_free(patch); + git__free(path); +} diff --git a/tests/libgit2/apply/fromfile.c b/tests/libgit2/apply/fromfile.c new file mode 100644 index 000000000..8cde62392 --- /dev/null +++ b/tests/libgit2/apply/fromfile.c @@ -0,0 +1,449 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "apply.h" +#include "patch.h" +#include "patch_parse.h" +#include "repository.h" + +#include "../patch/patch_common.h" + +static git_repository *repo = NULL; + +void test_apply_fromfile__initialize(void) +{ + repo = cl_git_sandbox_init("renames"); +} + +void test_apply_fromfile__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int apply_patchfile( + const char *old, + size_t old_len, + const char *new, + size_t new_len, + const char *patchfile, + const char *filename_expected, + unsigned int mode_expected) +{ + git_patch *patch; + git_str result = GIT_STR_INIT; + git_str patchbuf = GIT_STR_INIT; + char *filename; + unsigned int mode; + int error; + + cl_git_pass(git_patch_from_buffer(&patch, patchfile, strlen(patchfile), NULL)); + + error = git_apply__patch(&result, &filename, &mode, old, old_len, patch, NULL); + + if (error == 0) { + cl_assert_equal_i(new_len, result.size); + if (new_len) + cl_assert(memcmp(new, result.ptr, new_len) == 0); + + cl_assert_equal_s(filename_expected, filename); + cl_assert_equal_i(mode_expected, mode); + } + + git__free(filename); + git_str_dispose(&result); + git_str_dispose(&patchbuf); + git_patch_free(patch); + + return error; +} + +static int validate_and_apply_patchfile( + const char *old, + size_t old_len, + const char *new, + size_t new_len, + const char *patchfile, + const git_diff_options *diff_opts, + const char *filename_expected, + unsigned int mode_expected) +{ + git_patch *patch_fromdiff; + git_buf validated = GIT_BUF_INIT; + int error; + + cl_git_pass(git_patch_from_buffers(&patch_fromdiff, + old, old_len, "file.txt", + new, new_len, "file.txt", + diff_opts)); + cl_git_pass(git_patch_to_buf(&validated, patch_fromdiff)); + + cl_assert_equal_s(patchfile, validated.ptr); + + error = apply_patchfile(old, old_len, new, new_len, patchfile, filename_expected, mode_expected); + + git_buf_dispose(&validated); + git_patch_free(patch_fromdiff); + + return error; +} + +void test_apply_fromfile__change_middle(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, + &diff_opts, "file.txt", 0100644)); +} + + +void test_apply_fromfile__change_firstline(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_FIRSTLINE, strlen(FILE_CHANGE_FIRSTLINE), + PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__lastline(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_LASTLINE, strlen(FILE_CHANGE_LASTLINE), + PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_shrink(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_shrink_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), + PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_grow(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_grow_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), + PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__prepend(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_PREPEND, strlen(FILE_PREPEND), + PATCH_ORIGINAL_TO_PREPEND, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__prepend_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_PREPEND, strlen(FILE_PREPEND), + PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__append(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_APPEND, strlen(FILE_APPEND), + PATCH_ORIGINAL_TO_APPEND, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__append_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_APPEND, strlen(FILE_APPEND), + PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__prepend_and_append(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_PREPEND_AND_APPEND, strlen(FILE_PREPEND_AND_APPEND), + PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__to_empty_file(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + "", 0, + PATCH_ORIGINAL_TO_EMPTY_FILE, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__from_empty_file(void) +{ + cl_git_pass(validate_and_apply_patchfile( + "", 0, + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_EMPTY_FILE_TO_ORIGINAL, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__add(void) +{ + cl_git_pass(validate_and_apply_patchfile( + NULL, 0, + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_ADD_ORIGINAL, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__delete(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + NULL, 0, + PATCH_DELETE_ORIGINAL, NULL, NULL, 0)); +} + + +void test_apply_fromfile__rename_exact(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_RENAME_EXACT, "newfile.txt", 0100644)); +} + +void test_apply_fromfile__rename_similar(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_RENAME_SIMILAR, "newfile.txt", 0100644)); +} + +void test_apply_fromfile__rename_similar_quotedname(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_RENAME_SIMILAR_QUOTEDNAME, "foo\"bar.txt", 0100644)); +} + +void test_apply_fromfile__modechange(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_MODECHANGE_UNCHANGED, "file.txt", 0100755)); +} + +void test_apply_fromfile__modechange_with_modification(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_MODECHANGE_MODIFIED, "file.txt", 0100755)); +} + +void test_apply_fromfile__noisy(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_NOISY, "file.txt", 0100644)); +} + +void test_apply_fromfile__noisy_nocontext(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_NOISY_NOCONTEXT, "file.txt", 0100644)); +} + +void test_apply_fromfile__fail_truncated_1(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_1, + strlen(PATCH_TRUNCATED_1), NULL)); +} + +void test_apply_fromfile__fail_truncated_2(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_2, + strlen(PATCH_TRUNCATED_2), NULL)); +} + +void test_apply_fromfile__fail_truncated_3(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_3, + strlen(PATCH_TRUNCATED_3), NULL)); +} + +void test_apply_fromfile__fail_corrupt_githeader(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER, + strlen(PATCH_CORRUPT_GIT_HEADER), NULL)); +} + +void test_apply_fromfile__empty_context(void) +{ + cl_git_pass(apply_patchfile( + FILE_EMPTY_CONTEXT_ORIGINAL, strlen(FILE_EMPTY_CONTEXT_ORIGINAL), + FILE_EMPTY_CONTEXT_MODIFIED, strlen(FILE_EMPTY_CONTEXT_MODIFIED), + PATCH_EMPTY_CONTEXT, + "file.txt", 0100644)); +} + +void test_apply_fromfile__append_no_nl(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_APPEND_NO_NL, strlen(FILE_APPEND_NO_NL), + PATCH_APPEND_NO_NL, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__fail_missing_new_file(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_NEW_FILE, + strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL)); +} + +void test_apply_fromfile__fail_missing_old_file(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_OLD_FILE, + strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL)); +} + +void test_apply_fromfile__fail_no_changes(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_NO_CHANGES, + strlen(PATCH_CORRUPT_NO_CHANGES), NULL)); +} + +void test_apply_fromfile__fail_missing_hunk_header(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_HUNK_HEADER, + strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); +} + +void test_apply_fromfile__fail_not_a_patch(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH, + strlen(PATCH_NOT_A_PATCH), NULL)); +} + +void test_apply_fromfile__binary_add(void) +{ + cl_git_pass(apply_patchfile( + NULL, 0, + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + PATCH_BINARY_ADD, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_change_delta(void) +{ + cl_git_pass(apply_patchfile( + FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + PATCH_BINARY_DELTA, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_change_literal(void) +{ + cl_git_pass(apply_patchfile( + FILE_BINARY_LITERAL_ORIGINAL, FILE_BINARY_LITERAL_ORIGINAL_LEN, + FILE_BINARY_LITERAL_MODIFIED, FILE_BINARY_LITERAL_MODIFIED_LEN, + PATCH_BINARY_LITERAL, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_delete(void) +{ + cl_git_pass(apply_patchfile( + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + NULL, 0, + PATCH_BINARY_DELETE, NULL, 0)); +} + +void test_apply_fromfile__binary_change_does_not_apply(void) +{ + /* try to apply patch backwards, ensure it does not apply */ + cl_git_fail(apply_patchfile( + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, + PATCH_BINARY_DELTA, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_change_must_be_reversible(void) +{ + cl_git_fail(apply_patchfile( + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + NULL, 0, + PATCH_BINARY_NOT_REVERSIBLE, NULL, 0)); +} + +void test_apply_fromfile__empty_file_not_allowed(void) +{ + git_patch *patch; + + cl_git_fail(git_patch_from_buffer(&patch, "", 0, NULL)); + cl_git_fail(git_patch_from_buffer(&patch, NULL, 0, NULL)); +} diff --git a/tests/libgit2/apply/index.c b/tests/libgit2/apply/index.c new file mode 100644 index 000000000..9c9094cce --- /dev/null +++ b/tests/libgit2/apply/index.c @@ -0,0 +1,321 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_index__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_index__generate_diff(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &diff_opts)); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_index__parsed_diff(void) +{ + git_diff *diff; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__removes_file(void) +{ + git_diff *diff; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, + strlen(DIFF_DELETE_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__adds_file(void) +{ + git_diff *diff; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__modified_workdir_with_unmodified_index_is_ok(void) +{ + git_diff *diff; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + struct merge_index_entry workdir_expected[] = { + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "veal.txt" } + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the workdir and leave the index matching HEAD */ + cl_git_rmfile("merge-recursive/asparagus.txt"); + cl_git_rewritefile("merge-recursive/veal.txt", "Hello, world.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_index__application_failure_leaves_index_unmodified(void) +{ + git_diff *diff; + git_index *index; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_remove(index, "veal.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_index__keeps_nonconflicting_changes(void) +{ + git_diff *diff; + git_index *index; + git_index_entry idx_entry; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "beef.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index */ + cl_git_pass(git_repository_index(&index, repo)); + + memset(&idx_entry, 0, sizeof(git_index_entry)); + idx_entry.mode = 0100644; + idx_entry.path = "beef.txt"; + cl_git_pass(git_oid_fromstr(&idx_entry.id, "898d12687fb35be271c27c795a6b32c8b51da79e")); + cl_git_pass(git_index_add(index, &idx_entry)); + + cl_git_pass(git_index_remove(index, "bouilli.txt", 0)); + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__can_apply_nonconflicting_file_changes(void) +{ + git_diff *diff; + git_index *index; + git_index_entry idx_entry; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "4f2d1645dee99ced096877911de540c65ade2ef8", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + /* + * Replace the index entry with a version that is different than + * HEAD but such that the patch still applies cleanly. This item + * has a new line appended. + */ + + 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))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} + +void test_apply_index__change_mode(void) +{ + git_diff *diff; + + const char *diff_file = DIFF_EXECUTABLE_FILE; + + struct merge_index_entry index_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100755, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 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 index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_workdir_unchanged(repo); + + git_diff_free(diff); +} diff --git a/tests/libgit2/apply/partial.c b/tests/libgit2/apply/partial.c new file mode 100644 index 000000000..fd4908d4b --- /dev/null +++ b/tests/libgit2/apply/partial.c @@ -0,0 +1,231 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "apply.h" +#include "repository.h" + +#include "../patch/patch_common.h" + +static git_repository *repo = NULL; + +void test_apply_partial__initialize(void) +{ + repo = cl_git_sandbox_init("renames"); +} + +void test_apply_partial__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int skip_addition( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines > hunk->old_lines) ? 1 : 0; +} + +static int skip_deletion( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines < hunk->old_lines) ? 1 : 0; +} + +static int skip_change( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines == hunk->old_lines) ? 1 : 0; +} + +static int abort_addition( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines > hunk->old_lines) ? GIT_EUSER : 0; +} + +static int abort_deletion( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines < hunk->old_lines) ? GIT_EUSER : 0; +} + +static int abort_change( + const git_diff_hunk *hunk, + void *payload) +{ + GIT_UNUSED(payload); + + return (hunk->new_lines == hunk->old_lines) ? GIT_EUSER : 0; +} + +static int apply_buf( + const char *old, + const char *oldname, + const char *new, + const char *newname, + const char *expected, + const git_diff_options *diff_opts, + git_apply_hunk_cb hunk_cb, + void *payload) +{ + git_patch *patch; + git_str result = GIT_STR_INIT; + git_str patchbuf = GIT_STR_INIT; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + char *filename; + unsigned int mode; + int error; + size_t oldsize = strlen(old); + size_t newsize = strlen(new); + + opts.hunk_cb = hunk_cb; + opts.payload = payload; + + cl_git_pass(git_patch_from_buffers(&patch, old, oldsize, oldname, new, newsize, newname, diff_opts)); + if ((error = git_apply__patch(&result, &filename, &mode, old, oldsize, patch, &opts)) == 0) { + cl_assert_equal_s(expected, result.ptr); + cl_assert_equal_s(newname, filename); + cl_assert_equal_i(0100644, mode); + } + + git__free(filename); + git_str_dispose(&result); + git_str_dispose(&patchbuf); + git_patch_free(patch); + + return error; +} + +void test_apply_partial__prepend_and_change_skip_addition(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_ORIGINAL, NULL, skip_addition, NULL)); +} + +void test_apply_partial__prepend_and_change_nocontext_skip_addition(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_CHANGE_MIDDLE, &diff_opts, skip_addition, NULL)); +} + +void test_apply_partial__prepend_and_change_nocontext_abort_addition(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_fail(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_ORIGINAL, &diff_opts, abort_addition, NULL)); +} + +void test_apply_partial__prepend_and_change_skip_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_PREPEND_AND_CHANGE, NULL, skip_change, NULL)); +} + +void test_apply_partial__prepend_and_change_nocontext_skip_change(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_PREPEND, &diff_opts, skip_change, NULL)); +} + +void test_apply_partial__prepend_and_change_nocontext_abort_change(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_fail(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_CHANGE, "file.txt", + FILE_PREPEND, &diff_opts, abort_change, NULL)); +} + +void test_apply_partial__delete_and_change_skip_deletion(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_ORIGINAL, NULL, skip_deletion, NULL)); +} + +void test_apply_partial__delete_and_change_nocontext_skip_deletion(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_CHANGE_MIDDLE, &diff_opts, skip_deletion, NULL)); +} + +void test_apply_partial__delete_and_change_nocontext_abort_deletion(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_fail(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_ORIGINAL, &diff_opts, abort_deletion, NULL)); +} + +void test_apply_partial__delete_and_change_skip_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_DELETE_AND_CHANGE, NULL, skip_change, NULL)); +} + +void test_apply_partial__delete_and_change_nocontext_skip_change(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_DELETE_FIRSTLINE, &diff_opts, skip_change, NULL)); +} + +void test_apply_partial__delete_and_change_nocontext_abort_change(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_fail(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_DELETE_AND_CHANGE, "file.txt", + FILE_DELETE_FIRSTLINE, &diff_opts, abort_change, NULL)); +} diff --git a/tests/libgit2/apply/tree.c b/tests/libgit2/apply/tree.c new file mode 100644 index 000000000..5154f134f --- /dev/null +++ b/tests/libgit2/apply/tree.c @@ -0,0 +1,94 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" +#include "../merge/merge_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + + +void test_apply_tree__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_apply_tree__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_tree__one(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_index *index = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + + struct merge_index_entry expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); + + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &opts)); + + cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff, NULL)); + merge_test_index(index, expected, 6); + + git_index_free(index); + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_tree__adds_file(void) +{ + git_oid a_oid; + git_commit *a_commit; + git_tree *a_tree; + git_diff *diff; + git_index *index = NULL; + + struct merge_index_entry expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); + + cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff, NULL)); + merge_test_index(index, expected, 7); + + git_index_free(index); + git_diff_free(diff); + git_tree_free(a_tree); + git_commit_free(a_commit); +} diff --git a/tests/libgit2/apply/workdir.c b/tests/libgit2/apply/workdir.c new file mode 100644 index 000000000..d0d9c1aba --- /dev/null +++ b/tests/libgit2/apply/workdir.c @@ -0,0 +1,358 @@ +#include "clar_libgit2.h" +#include "apply_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + +void test_apply_workdir__initialize(void) +{ + git_oid oid; + git_commit *commit; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL)); + git_commit_free(commit); +} + +void test_apply_workdir__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_workdir__generated_diff(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &opts)); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + +void test_apply_workdir__parsed_diff(void) +{ + git_diff *diff; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__removes_file(void) +{ + git_diff *diff; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, DIFF_DELETE_FILE, + strlen(DIFF_DELETE_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__adds_file(void) +{ + git_diff *diff; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "6370543fcfedb3e6516ec53b06158f3687dc1447", 0, "newfile.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "94d2c01087f48213bd157222d54edfefd77c9bba", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_ADD_FILE, strlen(DIFF_ADD_FILE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__modified_index_with_unmodified_workdir_is_ok(void) +{ + git_index *index; + git_index_entry idx_entry = {{0}}; + git_diff *diff; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry index_expected[] = { + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "veal.txt" } + }; + size_t index_expected_cnt = sizeof(index_expected) / + sizeof(struct merge_index_entry); + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the index and leave the workdir matching HEAD */ + cl_git_pass(git_repository_index(&index, repo)); + + idx_entry.mode = 0100644; + idx_entry.path = "veal.txt"; + cl_git_pass(git_oid_fromstr(&idx_entry.id, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d")); + + cl_git_pass(git_index_add(index, &idx_entry)); + cl_git_pass(git_index_remove(index, "asparagus.txt", 0)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_apply_index(repo, index_expected, index_expected_cnt); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_index_free(index); + git_diff_free(diff); +} + +void test_apply_workdir__application_failure_leaves_workdir_unmodified(void) +{ + git_diff *diff; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "8684724651336001c5dbce74bed6736d2443958d", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* mutate the workdir */ + cl_git_rewritefile("merge-recursive/veal.txt", + "This is a modification.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__keeps_nonconflicting_changes(void) +{ + git_diff *diff; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "f75ba05f340c51065cbea2e1fdbfe5fe13144c97", 0, "gravy.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_rmfile("merge-recursive/oyster.txt"); + cl_git_rewritefile("merge-recursive/gravy.txt", "Hello, world.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__can_apply_nonconflicting_file_changes(void) +{ + git_diff *diff; + + const char *diff_file = DIFF_MODIFY_TWO_FILES; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "5db1a0fef164cb66cc0c00d35cc5af979ddc1a64", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + /* + * Replace the workdir file with a version that is different than + * HEAD but such that the patch still applies cleanly. This item + * has a new line appended. + */ + cl_git_append2file("merge-recursive/asparagus.txt", + "This line is added in the workdir.\n"); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__change_mode(void) +{ +#ifndef GIT_WIN32 + git_diff *diff; + + const char *diff_file = DIFF_EXECUTABLE_FILE; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100755, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 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 workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, diff_file, strlen(diff_file))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, NULL)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +#endif +} + +void test_apply_workdir__apply_many_changes_one(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "c9d7d5d58088bc91f6e06f17ca3a205091568d3a", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MANY_CHANGES_ONE, strlen(DIFF_MANY_CHANGES_ONE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} + +void test_apply_workdir__apply_many_changes_two(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "6b943d65af6d8db74d747284fa4ca7d716ad5bbb", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MANY_CHANGES_TWO, strlen(DIFF_MANY_CHANGES_TWO))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} |