Commit e09d18eed66d0239bea73af51e586f6ae651fe49

Edward Thomson 2013-05-03T18:39:44

allow checkout to proceed when a dir to be removed is in use (win32)

diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index d3e971b..6798bf3 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 21f32d8..e9ec2bd 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 d624471..36f6017 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 4d56299..a817d42 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 eb129f3..67357a9 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
+}