| /* This test exercises the problem described in |
| ** https://github.com/libgit2/libgit2/pull/3568 |
| ** where deleting a directory during a diff/status |
| ** operation can cause an access violation. |
| ** |
| ** The "test_diff_racediffiter__basic() test confirms |
| ** the normal operation of diff on the given repo. |
| ** |
| ** The "test_diff_racediffiter__racy_rmdir() test |
| ** uses the new diff progress callback to delete |
| ** a directory (after the initial readdir() and |
| ** before the directory itself is visited) causing |
| ** the recursion and iteration to fail. |
| */ |
| |
| #include "clar_libgit2.h" |
| #include "diff_helpers.h" |
| |
| #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) |
| |
| void test_diff_racediffiter__initialize(void) |
| { |
| } |
| |
| void test_diff_racediffiter__cleanup(void) |
| { |
| cl_git_sandbox_cleanup(); |
| } |
| |
| typedef struct |
| { |
| const char *path; |
| git_delta_t t; |
| |
| } basic_payload; |
| |
| static int notify_cb__basic( |
| const git_diff *diff_so_far, |
| const git_diff_delta *delta_to_add, |
| const char *matched_pathspec, |
| void *payload) |
| { |
| basic_payload *exp = (basic_payload *)payload; |
| basic_payload *e; |
| |
| GIT_UNUSED(diff_so_far); |
| GIT_UNUSED(matched_pathspec); |
| |
| for (e = exp; e->path; e++) { |
| if (strcmp(e->path, delta_to_add->new_file.path) == 0) { |
| cl_assert_equal_i(e->t, delta_to_add->status); |
| return 0; |
| } |
| } |
| cl_assert(0); |
| return GIT_ENOTFOUND; |
| } |
| |
| void test_diff_racediffiter__basic(void) |
| { |
| git_diff_options opts = GIT_DIFF_OPTIONS_INIT; |
| git_repository *repo = cl_git_sandbox_init("diff"); |
| git_diff *diff; |
| |
| basic_payload exp_a[] = { |
| { "another.txt", GIT_DELTA_MODIFIED }, |
| { "readme.txt", GIT_DELTA_MODIFIED }, |
| { "zzzzz/", GIT_DELTA_IGNORED }, |
| { NULL, 0 } |
| }; |
| |
| cl_must_pass(p_mkdir("diff/zzzzz", 0777)); |
| |
| opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; |
| opts.notify_cb = notify_cb__basic; |
| opts.payload = exp_a; |
| |
| cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); |
| |
| git_diff_free(diff); |
| } |
| |
| |
| typedef struct { |
| bool first_time; |
| const char *dir; |
| basic_payload *basic_payload; |
| } racy_payload; |
| |
| static int notify_cb__racy_rmdir( |
| const git_diff *diff_so_far, |
| const git_diff_delta *delta_to_add, |
| const char *matched_pathspec, |
| void *payload) |
| { |
| racy_payload *pay = (racy_payload *)payload; |
| |
| if (pay->first_time) { |
| cl_must_pass(p_rmdir(pay->dir)); |
| pay->first_time = false; |
| } |
| |
| return notify_cb__basic(diff_so_far, delta_to_add, matched_pathspec, pay->basic_payload); |
| } |
| |
| void test_diff_racediffiter__racy(void) |
| { |
| git_diff_options opts = GIT_DIFF_OPTIONS_INIT; |
| git_repository *repo = cl_git_sandbox_init("diff"); |
| git_diff *diff; |
| |
| basic_payload exp_a[] = { |
| { "another.txt", GIT_DELTA_MODIFIED }, |
| { "readme.txt", GIT_DELTA_MODIFIED }, |
| { NULL, 0 } |
| }; |
| |
| racy_payload pay = { true, "diff/zzzzz", exp_a }; |
| |
| cl_must_pass(p_mkdir("diff/zzzzz", 0777)); |
| |
| opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; |
| opts.notify_cb = notify_cb__racy_rmdir; |
| opts.payload = &pay; |
| |
| cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); |
| |
| git_diff_free(diff); |
| } |