From e09d18eed66d0239bea73af51e586f6ae651fe49 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 3 May 2013 18:39:44 -0500 Subject: [PATCH] allow checkout to proceed when a dir to be removed is in use (win32) --- include/git2/checkout.h | 3 ++ src/checkout.c | 3 ++ src/fileops.c | 4 +-- src/win32/posix_w32.c | 13 +++++++- tests-clar/checkout/tree.c | 63 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index d3e971b43..6798bf31c 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -134,6 +134,9 @@ typedef enum { /** Treat pathspec as simple list of exact match file paths */ GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13), + /** Ignore directories in use, they will be left empty */ + GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES = (1u << 18), + /** * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED */ diff --git a/src/checkout.c b/src/checkout.c index 21f32d89a..e9ec2bdab 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -955,6 +955,9 @@ static int checkout_remove_the_old( uint32_t flg = GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS; + if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) + flg |= GIT_RMDIR_SKIP_NONEMPTY; + git_buf_truncate(&data->path, data->workdir_len); git_vector_foreach(&data->diff->deltas, i, delta) { diff --git a/src/fileops.c b/src/fileops.c index d6244711f..36f601706 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -444,7 +444,7 @@ static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path) if (data->error < 0) { if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && - (errno == ENOTEMPTY || errno == EEXIST)) + (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY)) data->error = 0; else futils__error_cannot_rmdir(path->ptr, NULL); @@ -480,7 +480,7 @@ static int futils__rmdir_empty_parent(void *opaque, git_buf *path) if (en == ENOENT || en == ENOTDIR) { giterr_clear(); error = 0; - } else if (en == ENOTEMPTY || en == EEXIST) { + } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) { giterr_clear(); error = GIT_ITEROVER; } else { diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 4d56299f7..a817d4245 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -314,9 +314,20 @@ int p_chmod(const char* path, mode_t mode) int p_rmdir(const char* path) { + int error; wchar_t buf[GIT_WIN_PATH]; git__utf8_to_16(buf, GIT_WIN_PATH, path); - return _wrmdir(buf); + + error = _wrmdir(buf); + + /* _wrmdir() is documented to return EACCES if "A program has an open + * handle to the directory." This sounds like what everybody else calls + * EBUSY. Let's convert appropriate error codes. + */ + if (GetLastError() == ERROR_SHARING_VIOLATION) + errno = EBUSY; + + return error; } int p_hide_directory__w32(const char *path) diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index eb129f34e..67357a942 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -526,3 +526,66 @@ void test_checkout_tree__can_write_to_empty_dirs(void) git_object_free(obj); } + +void test_checkout_tree__fails_when_dir_in_use(void) +{ +#ifdef GIT_WIN32 + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_oid oid; + git_object *obj = NULL; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_assert(git_path_isfile("testrepo/a/b.txt")); + + git_object_free(obj); + + cl_git_pass(p_chdir("testrepo/a")); + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_fail(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass(p_chdir("../..")); + + cl_assert(git_path_is_empty_dir("testrepo/a")); +#endif +} + +void test_checkout_tree__can_continue_when_dir_in_use(void) +{ +#ifdef GIT_WIN32 + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_oid oid; + git_object *obj = NULL; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE | + GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_assert(git_path_isfile("testrepo/a/b.txt")); + + git_object_free(obj); + + cl_git_pass(p_chdir("testrepo/a")); + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass(p_chdir("../..")); + + cl_assert(git_path_is_empty_dir("testrepo/a")); +#endif +}