use std::fmt::Write as _;
use std::path::PathBuf;
use anyhow::Context;
use worktrunk::config::{
DeprecationInfo, config_path, 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,
new_path: PathBuf,
info: DeprecationInfo,
}
pub fn handle_config_update(yes: 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() {
eprintln!("{}", info_message("No deprecated settings found"));
return Ok(());
}
for candidate in &candidates {
eprint!("{}", format_update_preview(&candidate.info));
}
if !yes {
let prompt_text = "Apply updates?".to_string();
match prompt_yes_no_preview(&prompt_text, || {})? {
PromptResponse::Accepted => {}
PromptResponse::Declined => {
eprintln!("{}", info_message("Update cancelled"));
return Ok(());
}
}
}
for candidate in &candidates {
std::fs::rename(&candidate.new_path, &candidate.config_path)
.with_context(|| format!("Failed to update {}", candidate.info.label))?;
eprintln!(
"{}",
success_message(format!("Updated {}", candidate.info.label.to_lowercase()))
);
}
if let Ok(repo) = Repository::current() {
let _ = repo.clear_hint("deprecated-config");
}
Ok(())
}
fn format_update_preview(info: &DeprecationInfo) -> String {
let mut out = format_deprecation_warnings(info);
if let Some(new_path) = &info.migration_path
&& let Some(diff) = format_migration_diff(&info.config_path, new_path)
{
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 content = std::fs::read_to_string(&config_path).context("Failed to read user config")?;
let info = match worktrunk::config::check_and_migrate(
&config_path,
&content,
true, "User config",
None, false, )? {
result
if result
.info
.as_ref()
.is_some_and(DeprecationInfo::has_deprecations) =>
{
result.info.unwrap()
}
_ => return Ok(None),
};
let new_path = match &info.migration_path {
Some(path) => path.clone(),
None => anyhow::bail!("Failed to write migration file for user config"),
};
Ok(Some(UpdateCandidate {
config_path,
new_path,
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 content = std::fs::read_to_string(&config_path).context("Failed to read project config")?;
let info = match worktrunk::config::check_and_migrate(
&config_path,
&content,
!is_linked, "Project config",
Some(&repo),
false, )? {
result
if result
.info
.as_ref()
.is_some_and(DeprecationInfo::has_deprecations) =>
{
result.info.unwrap()
}
_ => 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 new_path = match &info.migration_path {
Some(path) => path.clone(),
None => anyhow::bail!("Failed to write migration file for project config"),
};
Ok(Some(UpdateCandidate {
config_path,
new_path,
info,
}))
}