aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe')
-rw-r--r--crates/shirabe/src/util/filesystem.rs87
1 files changed, 57 insertions, 30 deletions
diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs
index bca18771..05146fd3 100644
--- a/crates/shirabe/src/util/filesystem.rs
+++ b/crates/shirabe/src/util/filesystem.rs
@@ -116,9 +116,9 @@ impl Filesystem {
/// Recursively remove a directory
pub fn remove_directory(&mut self, directory: impl AsRef<Path>) -> anyhow::Result<bool> {
// TODO(bytes):
- // This path is matched against a regex (remove_edge_cases) and passed to an
- // `rm -rf`/`rmdir` subprocess via the String-based ProcessExecutor, so it has to be
- // representable as UTF-8.
+ // This path is matched against a regex (remove_edge_cases) and, on Windows, passed to an
+ // `rmdir` subprocess via the String-based ProcessExecutor, so it has to be representable as
+ // UTF-8.
let directory = directory.as_ref();
let directory = directory.to_str().ok_or_else(|| {
RuntimeException::new(format!(
@@ -131,24 +131,25 @@ impl Filesystem {
return Ok(r);
}
- let cmd: Vec<String> = if Platform::is_windows() {
- vec![
+ let result = if Platform::is_windows() {
+ // TODO(perf,windows): `std::fs::remove_dir_all` is expected to be faster here too,
+ // but how it differs from the `rmdir` command has to be investigated first.
+ let cmd: Vec<String> = vec![
"rmdir".to_string(),
"/S".to_string(),
"/Q".to_string(),
Platform::realpath(directory),
- ]
+ ];
+
+ let mut output = PhpMixed::Null;
+ self.get_process()
+ .execute(&cmd, &mut output, None)
+ .map(|n| n == 0)
+ .unwrap_or(false)
} else {
- vec!["rm".to_string(), "-rf".to_string(), directory.to_string()]
+ remove_directory_recursively(Path::new(directory)).is_ok()
};
- let mut output = PhpMixed::Null;
- let result = self
- .get_process()
- .execute(&cmd, &mut output, None)
- .map(|n| n == 0)
- .unwrap_or(false);
-
// clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache2(false, "");
@@ -164,7 +165,7 @@ impl Filesystem {
this: &std::rc::Rc<std::cell::RefCell<Filesystem>>,
directory: &str,
) -> anyhow::Result<bool> {
- let (process_executor, cmd) = {
+ {
let mut fs = this.borrow_mut();
if let Some(mock) = fs.mock.as_mut()
@@ -179,28 +180,33 @@ impl Filesystem {
if let Some(r) = edge_case_result {
return Ok(r);
}
+ }
- let cmd: Vec<String> = if Platform::is_windows() {
- vec![
- "rmdir".to_string(),
- "/S".to_string(),
- "/Q".to_string(),
- Platform::realpath(directory),
- ]
- } else {
- vec!["rm".to_string(), "-rf".to_string(), directory.to_string()]
- };
+ let result = if Platform::is_windows() {
+ // TODO(perf,windows): `std::fs::remove_dir_all` is expected to be faster here too,
+ // but how it differs from the `rmdir` command has to be investigated first.
+ let cmd: Vec<String> = vec![
+ "rmdir".to_string(),
+ "/S".to_string(),
+ "/Q".to_string(),
+ Platform::realpath(directory),
+ ];
- (fs.get_process_handle(), cmd)
- };
+ let process_executor = this.borrow_mut().get_process_handle();
+ let process_future = process_executor.borrow_mut().execute_async(&cmd, None);
+ let mut process = process_future.await?;
- let process_future = process_executor.borrow_mut().execute_async(&cmd, None);
- let mut process = process_future.await?;
+ process.is_successful()
+ } else {
+ remove_directory_recursively_async(Path::new(directory))
+ .await
+ .is_ok()
+ };
// clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache2(false, "");
- if process.is_successful() && !is_dir(directory) {
+ if result && !is_dir(directory) {
return Ok(true);
}
@@ -1084,3 +1090,24 @@ impl Filesystem {
result
}
}
+
+/// Removes `path` and everything below it, standing in for the `rm -rf` that Composer shells out
+/// to on non-Windows platforms. Symbolic links below `path` are unlinked rather than followed, and
+/// a `path` that is already gone counts as removed.
+///
+/// Unlike `rm -rf`, this stops at the first entry it cannot remove instead of removing the rest of
+/// the tree first. Callers fall back to `Filesystem::remove_directory_php` on failure, which walks
+/// the remainder and surfaces the error.
+fn remove_directory_recursively(path: &Path) -> std::io::Result<()> {
+ match std::fs::remove_dir_all(path) {
+ Err(e) if e.kind() != std::io::ErrorKind::NotFound => Err(e),
+ _ => Ok(()),
+ }
+}
+
+async fn remove_directory_recursively_async(path: &Path) -> std::io::Result<()> {
+ match tokio::fs::remove_dir_all(path).await {
+ Err(e) if e.kind() != std::io::ErrorKind::NotFound => Err(e),
+ _ => Ok(()),
+ }
+}