1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
/* 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);
}