use crate::cli::{Args, SyncBackend, MergeStrategy}; use crate::config::{Config, Dotfile}; use crate::detect::Detector; use crate::backup::BackupManager; use crate::sync::SyncManager; use crate::merge::Merger; use anyhow::{Context, Result}; use std::path::PathBuf; #[cfg(feature = "dialoguer")] use dialoguer::Confirm; pub fn init(_args: Args, backend: Option) -> Result<()> { println!("Initializing DotMigrate configuration..."); let backend = backend.unwrap_or_else(|| { #[cfg(feature = "dialoguer")] { if Confirm::new() .with_prompt("Use git repository for sync?") .default(true) .interact() .unwrap() { SyncBackend::Git } else { SyncBackend::Direct } } #[cfg(not(feature = "dialoguer"))] { SyncBackend::Git } }); let config = Config::default_for_backend(backend); let config_path = Config::default_path()?; config.save(&config_path) .with_context(|| format!("Failed to save config to {:?}", config_path))?; println!("Configuration created at {:?}", config_path); println!("Run 'dotmigrate detect' to find existing dotfiles."); Ok(()) } pub fn detect(args: Args, output: Option, include_system: bool) -> Result<()> { println!("Detecting dotfiles..."); let config_path = args.config.clone() .or_else(|| Config::default_path().ok()) .unwrap_or_else(|| PathBuf::from(".dotmigrate/config.yml")); let config = if config_path.exists() { Config::load(&config_path)? } else { Config::default() }; let detector = Detector::new(&config); let dotfiles = detector.detect_dotfiles(include_system) .context("Failed to detect dotfiles")?; if args.dry_run { println!("[DRY-RUN] Would detect {} dotfiles:", dotfiles.len()); for dotfile in &dotfiles { println!(" - {:?}", dotfile.path); } } else { println!("Detected {} dotfiles:", dotfiles.len()); for dotfile in &dotfiles { println!(" - {:?}", dotfile.path); } } if let Some(output_path) = output { let content = serde_yaml::to_string(&dotfiles) .context("Failed to serialize dotfiles")?; if args.dry_run { println!("[DRY-RUN] Would save detected dotfiles to {:?}", output_path); } else { std::fs::write(&output_path, content) .context("Failed to write dotfiles to output")?; println!("Detected dotfiles saved to {:?}", output_path); } } Ok(()) } pub fn backup(args: Args, output: Option, backup_dir: Option) -> Result<()> { println!("Creating dotfiles backup..."); let config_path = args.config.clone() .or_else(|| Config::default_path().ok()) .unwrap_or_else(|| PathBuf::from(".dotmigrate/config.yml")); let config = if config_path.exists() { Config::load(&config_path)? } else { Config::default() }; let detector = Detector::new(&config); let dotfiles = detector.detect_dotfiles(false)?; let backup_path = backup_dir .or_else(|| dirs::home_dir().map(|p| p.join(".dotmigrate/backups"))) .unwrap_or_else(|| PathBuf::from("backups")); let backup_manager = BackupManager::new(&backup_path)?; if args.dry_run { println!("[DRY-RUN] Would create backup of {} dotfiles", dotfiles.len()); println!("[DRY-RUN] Backup directory: {:?}", backup_path); } else { let backup = backup_manager.create_backup(&dotfiles) .context("Failed to create backup")?; println!("Backup created: {}", backup.archive_path.display()); if let Some(output_path) = output { let manifest = backup_manager.get_manifest(&backup); let content = serde_yaml::to_string(&manifest) .context("Failed to serialize backup manifest")?; std::fs::write(&output_path, content) .context("Failed to write backup manifest")?; println!("Backup manifest saved to {:?}", output_path); } } Ok(()) } pub fn sync(args: Args, remote: Option, branch: Option, strategy: Option) -> Result<()> { println!("Syncing dotfiles..."); let config_path = args.config.clone() .or_else(|| Config::default_path().ok()) .unwrap_or_else(|| PathBuf::from(".dotmigrate/config.yml")); let mut config = if config_path.exists() { Config::load(&config_path)? } else { Config::default() }; if let Some(r) = remote { config.sync.remote = Some(r); } if let Some(b) = branch { config.sync.branch = b; } let backend = args.backend .or(config.sync.backend) .unwrap_or(SyncBackend::Git); let detector = Detector::new(&config); let local_dotfiles = detector.detect_dotfiles(false)?; let sync_manager = SyncManager::new(&config, backend); if args.dry_run { println!("[DRY-RUN] Would sync with backend: {:?}", backend); if let Some(ref remote_url) = config.sync.remote { println!("[DRY-RUN] Remote: {}", remote_url); } println!("[DRY-RUN] Local dotfiles: {}", local_dotfiles.len()); } else { sync_manager.sync(&local_dotfiles, strategy) .context("Failed to sync")?; println!("Sync completed successfully."); } Ok(()) } pub fn merge(args: Args, base: PathBuf, local: PathBuf, remote: PathBuf, output: PathBuf, strategy: Option) -> Result<()> { println!("Merging files..."); let merger = Merger::new(); if args.dry_run { println!("[DRY-RUN] Would merge files:"); println!(" Base: {:?}", base); println!(" Local: {:?}", local); println!(" Remote: {:?}", remote); println!(" Output: {:?}", output); } else { let result = merger.three_way_merge(&base, &local, &remote, &output, strategy) .context("Failed to merge files")?; match result { merge::MergeResult::Success => { println!("Merge completed successfully. Output: {:?}", output); } merge::MergeResult::Conflict(conflicts) => { println!("Merge completed with {} conflicts:", conflicts.len()); for conflict in &conflicts { println!(" - {}", conflict.path.display()); } } merge::MergeResult::AutoMerged => { println!("Auto-merge completed successfully. Output: {:?}", output); } } } Ok(()) } pub fn status(args: Args, detailed: bool) -> Result<()> { println!("DotMigrate Status"); let config_path = args.config.clone() .or_else(|| Config::default_path().ok()) .unwrap_or_else(|| PathBuf::from(".dotmigrate/config.yml")); if config_path.exists() { println!("Configuration: {:?}", config_path); let config = Config::load(&config_path)?; println!(" Backend: {:?}", config.sync.backend.unwrap_or(SyncBackend::Git)); if let Some(ref remote) = config.sync.remote { println!(" Remote: {}", remote); } println!(" Branch: {}", config.sync.branch); } else { println!("No configuration found. Run 'dotmigrate init' first."); } let detector = Detector::new(&Config::default()); let dotfiles = detector.detect_dotfiles(false)?; println!("Detected {} dotfiles", dotfiles.len()); if detailed { println!("\nDotfiles:"); for dotfile in &dotfiles { println!(" - {:?}", dotfile.path); if let Some(ref hash) = dotfile.content_hash { println!(" Hash: {}", hash); } } } let backup_dir = dirs::home_dir() .map(|p| p.join(".dotmigrate/backups")) .filter(|p| p.exists()); if let Some(ref backup_path) = backup_dir { println!("\nBackup directory: {:?}", backup_path); let entries = std::fs::read_dir(backup_path) .map(|r| r.map(|iter| iter.count()).unwrap_or(0)) .unwrap_or(0); println!(" {} backup archives", entries); } Ok(()) } pub fn diff(args: Args, local: PathBuf, remote: PathBuf) -> Result<()> { println!("Comparing files..."); let merger = Merger::new(); let diff = merger.diff(&local, &remote)?; if diff.is_empty() { println!("Files are identical."); } else { println!("Differences found:"); for line in diff { println!("{}", line); } } Ok(()) } pub fn completions(shell: clap_complete::Shell) -> Result<()> { let mut cmd = crate::cli::Args::command(); clap_complete::generate(shell, &mut cmd, "dotmigrate", &mut std::io::stdout()); Ok(()) }