Skip to main content

thoughts_tool/mount/
resolver.rs

1use crate::config::Mount;
2use crate::config::RepoMappingManager;
3use crate::mount::utils::normalize_mount_path;
4use anyhow::Result;
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8pub struct MountResolver {
9    repo_mapping: RepoMappingManager,
10}
11
12impl MountResolver {
13    pub fn new() -> Result<Self> {
14        Ok(Self {
15            repo_mapping: RepoMappingManager::new()?,
16        })
17    }
18
19    /// Resolve a mount to its local filesystem path
20    pub fn resolve_mount(&self, mount: &Mount) -> Result<PathBuf> {
21        match mount {
22            Mount::Directory { path, .. } => {
23                // Directory mounts need path normalization
24                Ok(normalize_mount_path(path)?)
25            }
26            Mount::Git { url, subpath, .. } => {
27                // Build full URL with subpath if present
28                let full_url = if let Some(sub) = subpath {
29                    format!("{url}:{sub}")
30                } else {
31                    url.clone()
32                };
33
34                // Resolve through mapping
35                self.repo_mapping.resolve_url(&full_url)?.ok_or_else(|| {
36                    use colored::Colorize;
37                    anyhow::anyhow!(
38                        "Repository not cloned: {}\n\n\
39                             To fix this, run one of:\n  \
40                             • {} (auto-managed)\n  \
41                             • {} (custom location)",
42                        url,
43                        format!("thoughts mount clone {url}").cyan(),
44                        format!("thoughts mount clone {url} /your/path").cyan()
45                    )
46                })
47            }
48        }
49    }
50
51    /// Check if a mount needs cloning
52    pub fn needs_clone(&self, mount: &Mount) -> Result<bool> {
53        match mount {
54            Mount::Directory { .. } => Ok(false),
55            Mount::Git { url, .. } => Ok(self.repo_mapping.resolve_url(url)?.is_none()),
56        }
57    }
58
59    /// Get clone URL and suggested path for a mount
60    #[allow(dead_code)]
61    // TODO(2): Integrate into clone command for consistency
62    pub fn get_clone_info(&self, mount: &Mount) -> Result<Option<(String, PathBuf)>> {
63        match mount {
64            Mount::Directory { .. } => Ok(None),
65            Mount::Git { url, .. } => {
66                if self.needs_clone(mount)? {
67                    let clone_path = RepoMappingManager::get_default_clone_path(url)?;
68                    Ok(Some((url.clone(), clone_path)))
69                } else {
70                    Ok(None)
71                }
72            }
73        }
74    }
75
76    /// Resolve all mounts in a config
77    #[allow(dead_code)]
78    // TODO(2): Keep for future batch operations and diagnostics
79    pub fn resolve_all(&self, mounts: &HashMap<String, Mount>) -> Vec<(String, Result<PathBuf>)> {
80        mounts
81            .iter()
82            .map(|(name, mount)| (name.clone(), self.resolve_mount(mount)))
83            .collect()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::config::SyncStrategy;
91
92    #[test]
93    fn test_directory_resolution() {
94        let resolver = MountResolver::new().unwrap();
95        let mount = Mount::Directory {
96            path: PathBuf::from("/home/user/docs"),
97            sync: SyncStrategy::None,
98        };
99
100        let resolved = resolver.resolve_mount(&mount).unwrap();
101        assert_eq!(resolved, PathBuf::from("/home/user/docs"));
102    }
103
104    #[test]
105    fn test_git_mount_detection() {
106        let mount = Mount::Git {
107            url: "git@github.com:test/repo.git".to_string(),
108            sync: SyncStrategy::Auto,
109            subpath: None,
110        };
111
112        // Test that we can detect git mounts
113        assert!(mount.is_git());
114    }
115}