Initial upload: DotMigrate dotfiles migration tool with CI/CD
This commit is contained in:
285
src/cli/commands.rs
Normal file
285
src/cli/commands.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
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<SyncBackend>) -> 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<PathBuf>, 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<PathBuf>, backup_dir: Option<PathBuf>) -> 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<String>, branch: Option<String>, strategy: Option<MergeStrategy>) -> 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<MergeStrategy>) -> 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user