use crate::config::Mount;
use crate::config::RepoConfigManager;
use crate::config::RepoMappingManager;
use crate::config::SyncStrategy;
use crate::config::extract_org_repo_from_url;
use crate::git::clone::CloneOptions;
use crate::git::clone::clone_repository;
use crate::git::ref_key::encode_ref_key;
use crate::git::utils::get_control_repo_root;
use crate::mount::MountOptions;
use crate::mount::MountResolver;
use crate::mount::MountSpace;
use crate::mount::get_mount_manager;
use crate::platform::detect_platform;
use crate::utils::paths::ensure_dir;
use anyhow::Result;
use colored::Colorize;
use std::collections::HashMap;
use std::path::PathBuf;
pub async fn update_active_mounts() -> Result<()> {
let repo_root = get_control_repo_root(&std::env::current_dir()?)?;
let platform_info = detect_platform()?;
let mount_manager = get_mount_manager(&platform_info)?;
let repo_manager = RepoConfigManager::new(repo_root.clone());
let desired = repo_manager.load_desired_state()?.ok_or_else(|| {
anyhow::anyhow!("No repository configuration found. Run 'thoughts init'.")
})?;
let base = repo_root.join(".thoughts-data");
if base.is_symlink() && !base.exists() {
anyhow::bail!(
"Worktree .thoughts-data symlink is broken. \
Re-run 'thoughts init' in the worktree or main repository."
);
}
ensure_dir(&base)?;
let base_canon = std::fs::canonicalize(&base).unwrap_or_else(|_| base.clone());
let thoughts_dir = base.join(&desired.mount_dirs.thoughts);
let context_dir = base.join(&desired.mount_dirs.context);
let references_dir = base.join(&desired.mount_dirs.references);
ensure_dir(&thoughts_dir)?;
ensure_dir(&context_dir)?;
ensure_dir(&references_dir)?;
println!("{} filesystem mounts...", "Synchronizing".cyan());
let mut desired_targets: Vec<(MountSpace, Mount, bool, Option<String>)> = vec![];
if let Some(tm) = &desired.thoughts_mount {
let m = Mount::Git {
url: tm.remote.clone(),
subpath: tm.subpath.clone(),
sync: tm.sync,
};
desired_targets.push((MountSpace::Thoughts, m, false, None));
}
for cm in &desired.context_mounts {
let m = Mount::Git {
url: cm.remote.clone(),
subpath: cm.subpath.clone(),
sync: cm.sync,
};
let space = MountSpace::Context(cm.mount_path.clone());
desired_targets.push((space, m, false, None));
}
for rm in &desired.references {
let url = &rm.remote;
let (org, repo) = match extract_org_repo_from_url(url) {
Ok(x) => x,
Err(e) => {
println!(
" {} Invalid reference in config, skipping: {}\n {}",
"Warning:".yellow(),
url,
e
);
continue;
}
};
let m = Mount::Git {
url: url.clone(),
subpath: None,
sync: SyncStrategy::None,
};
let ref_key = rm.ref_name.as_deref().map(encode_ref_key).transpose()?;
let space = MountSpace::Reference {
org_path: org,
repo,
ref_key,
};
desired_targets.push((space, m, true, rm.ref_name.clone()));
}
let active = mount_manager.list_mounts().await?;
let mut active_map = HashMap::<String, PathBuf>::new();
for mi in active {
let target_canon = std::fs::canonicalize(&mi.target).unwrap_or_else(|_| mi.target.clone());
if target_canon.starts_with(&base_canon)
&& let Ok(rel) = target_canon.strip_prefix(&base_canon)
{
let key = rel.to_string_lossy().to_string();
active_map.insert(key, mi.target.clone());
}
}
for (active_key, target_path) in &active_map {
if !desired_targets
.iter()
.any(|(space, _, _, _)| space.relative_path(&desired.mount_dirs) == *active_key)
{
println!(" {} removed mount: {}", "Unmounting".yellow(), active_key);
mount_manager.unmount(target_path, false).await?;
}
}
let resolver = MountResolver::new()?;
for (space, m, _read_only, ref_name) in &desired_targets {
let key = space.relative_path(&desired.mount_dirs);
if !active_map.contains_key(&key) {
let target = desired.get_mount_target(space, &repo_root);
let parent = target.parent().ok_or_else(|| {
anyhow::anyhow!("mount target has no parent directory: {}", target.display())
})?;
ensure_dir(parent)?;
let src = match (space, m) {
(MountSpace::Reference { .. }, Mount::Git { url, .. }) => {
let repo_mapping = RepoMappingManager::new()?;
if let Some(path) =
repo_mapping.resolve_reference_url(url, ref_name.as_deref())?
{
path
} else {
println!(" {} repository {} ...", "Cloning".yellow(), url);
clone_and_map(url, ref_name.as_deref())?
}
}
_ => match resolver.resolve_mount(m) {
Ok(p) => p,
Err(_) => {
if let Mount::Git { url, .. } = &m {
println!(" {} repository {} ...", "Cloning".yellow(), url);
clone_and_map(url, None)?
} else {
continue;
}
}
},
};
let mut options = MountOptions::default();
if space.is_read_only() {
options.read_only = true;
}
println!(
" {} {}: {}",
"Mounting".green(),
space,
if space.is_read_only() {
"(read-only)"
} else {
""
}
);
match mount_manager.mount(&[src], &target, &options).await {
Ok(()) => println!(" {} Successfully mounted", "✓".green()),
Err(e) => eprintln!(" {} Failed to mount: {}", "✗".red(), e),
}
}
}
println!("{} Mount synchronization complete", "✓".green());
Ok(())
}
fn clone_and_map(url: &str, ref_name: Option<&str>) -> Result<PathBuf> {
let mut repo_mapping = RepoMappingManager::new()?;
let default_path = RepoMappingManager::get_default_reference_clone_path(url, ref_name)?;
let clone_opts = CloneOptions {
url: url.to_string(),
target_path: default_path.clone(),
branch: ref_name.map(str::to_string),
};
clone_repository(&clone_opts)?;
repo_mapping.add_reference_mapping(url, ref_name, default_path.clone(), true)?;
Ok(default_path)
}