Commit 555aa453baefec98dbd026592b68214048bedac3

nulltoken 2012-04-09T02:28:31

fileops: Make git_futils_mkdir_r() able to skip non-empty directories

diff --git a/src/fileops.c b/src/fileops.c
index b3bb389..9da1bf7 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -298,13 +298,20 @@ int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
 
 static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
 {
-	int force = *(int *)opaque;
+	enum git_directory_removal_type removal_type = *(enum git_directory_removal_type *)opaque;
+
+	assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
+		|| removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
+		|| removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
 
 	if (git_path_isdir(path->ptr) == true) {
 		if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
 			return -1;
 
 		if (p_rmdir(path->ptr) < 0) {
+			if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && errno == ENOTEMPTY)
+				return 0;
+
 			giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr);
 			return -1;
 		}
@@ -312,7 +319,7 @@ static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
 		return 0;
 	}
 
-	if (force) {
+	if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) {
 		if (p_unlink(path->ptr) < 0) {
 			giterr_set(GITERR_OS, "Could not remove directory.  File '%s' cannot be removed", path->ptr);
 			return -1;
@@ -321,18 +328,22 @@ static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
 		return 0;
 	}
 
-	giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
-	return -1;
+	if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) {
+		giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
+		return -1;
+	}
+
+	return 0;
 }
 
-int git_futils_rmdir_r(const char *path, int force)
+int git_futils_rmdir_r(const char *path, enum git_directory_removal_type removal_type)
 {
 	int error;
 	git_buf p = GIT_BUF_INIT;
 
 	error = git_buf_sets(&p, path);
 	if (!error)
-		error = _rmdir_recurs_foreach(&force, &p);
+		error = _rmdir_recurs_foreach(&removal_type, &p);
 	git_buf_free(&p);
 	return error;
 }
diff --git a/src/fileops.h b/src/fileops.h
index 865b3c9..9cc2d16 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -58,10 +58,25 @@ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t m
  */
 extern int git_futils_mkpath2file(const char *path, const mode_t mode);
 
+typedef enum {
+	GIT_DIRREMOVAL_EMPTY_HIERARCHY = 0,
+	GIT_DIRREMOVAL_FILES_AND_DIRS = 1,
+	GIT_DIRREMOVAL_ONLY_EMPTY_DIRS = 2,
+} git_directory_removal_type;
+
 /**
  * Remove path and any files and directories beneath it.
+ *
+ * @param path Path to to top level directory to process.
+ *
+ * @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy
+ * of empty directories (will fail if any file is found), GIT_DIRREMOVAL_FILES_AND_DIRS
+ * to remove a hierarchy of files and folders, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove
+ * empty directories (no failure on file encounter).
+ *
+ * @return 0 on success; -1 on error.
  */
-extern int git_futils_rmdir_r(const char *path, int force);
+extern int git_futils_rmdir_r(const char *path, enum git_directory_removal_type removal_type);
 
 /**
  * Create and open a temporary file with a `_git2_` suffix.
diff --git a/tests-clar/core/rmdir.c b/tests-clar/core/rmdir.c
index 66b6475..530f1f9 100644
--- a/tests-clar/core/rmdir.c
+++ b/tests-clar/core/rmdir.c
@@ -30,25 +30,39 @@ void test_core_rmdir__initialize(void)
 /* make sure empty dir can be deleted recusively */
 void test_core_rmdir__delete_recursive(void)
 {
-	cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, 0));
+	cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
 }
 
 /* make sure non-empty dir cannot be deleted recusively */
 void test_core_rmdir__fail_to_delete_non_empty_dir(void)
 {
 	git_buf file = GIT_BUF_INIT;
-	int fd;
 
 	cl_git_pass(git_buf_joinpath(&file, empty_tmp_dir, "/two/file.txt"));
 
-	fd = p_creat(file.ptr, 0666);
-	cl_assert(fd >= 0);
+	cl_git_mkfile(git_buf_cstr(&file), "dummy");
 
-	cl_must_pass(p_close(fd));
-	cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, 0));
+	cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
 
 	cl_must_pass(p_unlink(file.ptr));
-	cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, 0));
+	cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
+
+	git_buf_free(&file);
+}
+
+void test_core_rmdir__can_skip__non_empty_dir(void)
+{
+	git_buf file = GIT_BUF_INIT;
+
+	cl_git_pass(git_buf_joinpath(&file, empty_tmp_dir, "/two/file.txt"));
+
+	cl_git_mkfile(git_buf_cstr(&file), "dummy");
+
+	cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS));
+	cl_assert(git_path_exists(git_buf_cstr(&file)) == true);
+
+	cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_FILES_AND_DIRS));
+	cl_assert(git_path_exists(empty_tmp_dir) == false);
 
 	git_buf_free(&file);
 }
diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c
index 7a0494e..708df2c 100644
--- a/tests-clar/status/worktree.c
+++ b/tests-clar/status/worktree.c
@@ -110,7 +110,7 @@ static int remove_file_cb(void *data, git_buf *file)
 		return 0;
 
 	if (git_path_isdir(filename))
-		cl_git_pass(git_futils_rmdir_r(filename, 1));
+		cl_git_pass(git_futils_rmdir_r(filename, GIT_DIRREMOVAL_FILES_AND_DIRS));
 	else
 		cl_git_pass(p_unlink(git_buf_cstr(file)));
 
@@ -346,7 +346,7 @@ void test_status_worktree__issue_592_3(void)
 	repo = cl_git_sandbox_init("issue_592");
 
 	cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c"));
-	cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), 1));
+	cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
 
 	cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
 
@@ -376,7 +376,7 @@ void test_status_worktree__issue_592_5(void)
 	repo = cl_git_sandbox_init("issue_592");
 
 	cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t"));
-	cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), 1));
+	cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
 	cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777));
 
 	cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL));