use anyhow::{Result, bail};
use async_trait::async_trait;
use clap::Args;
use crate::{
cli::atomic::should_dry_run,
commands::{Runnable, RunnableInvokeRules},
config::remote::RemoteConfigManager,
context::AppContext,
log_cute, log_dry, log_warn,
util::{
io::confirm,
logging::{BOLD, RESET},
},
};
#[derive(Debug, Args)]
pub struct FetchCmd {
#[arg(short, long)]
force: bool,
}
#[async_trait]
impl Runnable for FetchCmd {
fn get_invoke_rules(&self) -> RunnableInvokeRules {
RunnableInvokeRules {
do_config_autosync: false,
require_sudo: false,
respect_lock: true,
}
}
async fn run(&self, ctx: &AppContext) -> Result<()> {
let dry_run = should_dry_run();
let local_config = ctx.config.load().await?;
let remote_mgr = if let Some(ref remote) = local_config.remote {
RemoteConfigManager::new(remote.clone().url)
} else {
bail!("No URL found in [remote] of config. Add one to use remote sync.")
};
remote_mgr.fetch().await?;
if !self.force {
let remote_config = remote_mgr.get_parsed()?;
let mut changes = Vec::new();
if local_config.brew.as_ref() != remote_config.brew.as_ref() {
changes.push(format!("{BOLD}brew{RESET}: (changed)"));
}
if local_config.remote.as_ref() != remote_config.remote.as_ref() {
changes.push(format!("{BOLD}remote{RESET}: (changed)"));
}
if local_config.vars.as_ref() != remote_config.vars.as_ref() {
changes.push(format!("{BOLD}vars{RESET}: (changed)"));
}
if changes.is_empty() {
log_cute!("No changes found so skipping. Use -f to fetch forcefully.",);
return Ok(());
}
log_warn!("Differences between local and remote config:",);
for line in &changes {
log_warn!(" {line}");
}
if !dry_run && !confirm("Apply remote config (overwrite local config)?") {
log_warn!("Sync aborted by user.");
return Ok(());
}
}
if dry_run {
log_dry!(
"Would overwrite {:?} with remote config.",
local_config.path
);
} else {
remote_mgr.save().await?;
log_cute!("Local config updated from remote!");
}
Ok(())
}
}