thoughts_tool/mount/
resolver.rs

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