use std::fmt::Write as _;
use std::path::PathBuf;
use anyhow::Context;
use worktrunk::config::{
DeprecationInfo, compute_migrated_content, config_path,
copy_approved_commands_to_approvals_file, format_deprecation_warnings, format_migration_diff,
};
use worktrunk::git::Repository;
use worktrunk::styling::{
eprintln, format_bash_with_gutter, hint_message, info_message, success_message,
suggest_command_in_dir,
};
use crate::output::prompt::{PromptResponse, prompt_yes_no_preview};
struct UpdateCandidate {
config_path: PathBuf,
original: String,
migrated: String,
info: DeprecationInfo,
}
pub fn handle_config_update(yes: bool, print: bool) -> anyhow::Result<()> {
let mut candidates = Vec::new();
if let Some(candidate) = check_user_config()? {
candidates.push(candidate);
}
if let Some(candidate) = check_project_config()? {
candidates.push(candidate);
}
if candidates.is_empty() {
if print {
return Ok(());
}
eprintln!("{}", info_message("No deprecated settings found"));
return Ok(());
}
if print {
let multi = candidates.len() > 1;
for (idx, candidate) in candidates.iter().enumerate() {
eprint!("{}", format_deprecation_warnings(&candidate.info));
if multi {
if idx > 0 {
println!();
}
println!(
"# {} ({})",
candidate.info.label,
candidate.config_path.display()
);
}
print!("{}", candidate.migrated);
}
return Ok(());
}
for candidate in &candidates {
eprint!("{}", format_update_preview(candidate));
}
if !yes {
match prompt_yes_no_preview("Apply updates?", || {})? {
PromptResponse::Accepted => {}
PromptResponse::Declined => {
eprintln!("{}", info_message("Update cancelled"));
return Ok(());
}
}
}
for candidate in &candidates {
if candidate.info.deprecations.approved_commands
&& let Some(approvals_path) =
copy_approved_commands_to_approvals_file(&candidate.config_path)
{
let filename = approvals_path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default();
eprintln!(
"{}",
info_message(format!("Copied approved commands to {filename}"))
);
}
std::fs::write(&candidate.config_path, &candidate.migrated)
.with_context(|| format!("Failed to update {}", candidate.info.label))?;
eprintln!(
"{}",
success_message(format!("Updated {}", candidate.info.label.to_lowercase()))
);
}
Ok(())
}
fn format_update_preview(candidate: &UpdateCandidate) -> String {
let mut out = format_deprecation_warnings(&candidate.info);
let label = candidate
.config_path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "config".to_string());
if let Some(diff) = format_migration_diff(&candidate.original, &candidate.migrated, &label) {
let _ = writeln!(out, "{}", info_message("Proposed diff:"));
let _ = writeln!(out, "{diff}");
}
out
}
fn check_user_config() -> anyhow::Result<Option<UpdateCandidate>> {
let config_path = match config_path() {
Some(path) => path,
None => return Ok(None),
};
if !config_path.exists() {
return Ok(None);
}
let original = std::fs::read_to_string(&config_path).context("Failed to read user config")?;
let result = worktrunk::config::check_and_migrate(
&config_path,
&original,
true, "User config",
None, false, )?;
let Some(info) = result.info.filter(DeprecationInfo::has_deprecations) else {
return Ok(None);
};
let migrated = compute_migrated_content(&original);
Ok(Some(UpdateCandidate {
config_path,
original,
migrated,
info,
}))
}
fn check_project_config() -> anyhow::Result<Option<UpdateCandidate>> {
let repo = match Repository::current() {
Ok(repo) => repo,
Err(_) => return Ok(None),
};
let config_path = match repo.project_config_path() {
Ok(Some(path)) => path,
_ => return Ok(None),
};
if !config_path.exists() {
return Ok(None);
}
let is_linked = repo.current_worktree().is_linked().unwrap_or(true);
let original =
std::fs::read_to_string(&config_path).context("Failed to read project config")?;
let result = worktrunk::config::check_and_migrate(
&config_path,
&original,
!is_linked, "Project config",
Some(&repo),
false,
)?;
let Some(info) = result.info.filter(DeprecationInfo::has_deprecations) else {
return Ok(None);
};
if is_linked {
let cmd = suggest_command_in_dir(repo.repo_path()?, "config", &["update"], &[]);
eprintln!("{}", hint_message("To update project config:"));
eprintln!("{}", format_bash_with_gutter(&cmd));
return Ok(None);
}
let migrated = compute_migrated_content(&original);
Ok(Some(UpdateCandidate {
config_path,
original,
migrated,
info,
}))
}