outpost_core/ops/
remove.rs1use std::path::{Path, PathBuf};
2
3use crate::{safety, OutpostError, OutpostResult, SourceRepo};
4
5pub struct RemoveOptions {
6 pub path: PathBuf,
7 pub force: bool,
8}
9
10pub fn run(source: &SourceRepo, opts: RemoveOptions) -> OutpostResult<()> {
11 let mut registry = source.registry_mut()?;
12 let entry = registry_entry(registry.entries(), &opts.path)?.clone();
13
14 if entry.locked && !opts.force {
15 return Err(OutpostError::OutpostLocked {
16 path: entry.path,
17 reason: lock_reason(&entry.lock_reason),
18 });
19 }
20
21 if !entry.path.exists() {
22 registry.remove_by_path(&entry.path)?;
23 return registry.save();
24 }
25
26 let outpost = safety::check_path_is_managed_outpost_of(source, &entry.path)?;
27 if !opts.force {
28 safety::check_clean(outpost.work_tree(), outpost.git())?;
29 safety::check_no_unpushed(&outpost, source)?;
30 }
31
32 registry.remove_by_path(&entry.path)?;
33 registry.save()?;
34 std::fs::remove_dir_all(&entry.path).map_err(|source| OutpostError::IoAt {
35 path: entry.path,
36 source,
37 })
38}
39
40fn registry_entry<'a>(
41 entries: &'a [crate::RegistryEntry],
42 path: &Path,
43) -> OutpostResult<&'a crate::RegistryEntry> {
44 let lookup = canonicalize_existing_or_missing(path);
45 entries
46 .iter()
47 .find(|entry| entry.path == lookup)
48 .ok_or(OutpostError::RegistryEntryNotFound(lookup))
49}
50
51fn canonicalize_existing_or_missing(path: &Path) -> PathBuf {
52 match std::fs::canonicalize(path) {
53 Ok(canonical) => canonical,
54 Err(_) => match (path.parent(), path.file_name()) {
55 (Some(parent), Some(name)) => std::fs::canonicalize(parent)
56 .map(|parent| parent.join(Path::new(name)))
57 .unwrap_or_else(|_| path.to_path_buf()),
58 _ => path.to_path_buf(),
59 },
60 }
61}
62
63fn lock_reason(reason: &Option<String>) -> String {
64 reason
65 .as_ref()
66 .map(|reason| format!(": {reason}"))
67 .unwrap_or_default()
68}