fileops: Make git_futils_mkdir_r() able to skip non-empty directories
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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
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));