fix(config): report legacy config migration

This commit is contained in:
cyq
2026-06-02 08:46:20 +08:00
committed by Hunter B
parent 537afcf07e
commit 6144d64914
2 changed files with 106 additions and 6 deletions
+100 -4
View File
@@ -2026,18 +2026,34 @@ pub fn default_config_path() -> Result<PathBuf> {
Ok(primary)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfigMigration {
pub legacy_path: PathBuf,
pub primary_path: PathBuf,
}
impl ConfigMigration {
pub fn user_notice(&self) -> String {
format!(
"Migrated legacy config from {} to {}. Use the .codewhale path for future edits; the .deepseek file remains only as a compatibility fallback.",
self.legacy_path.display(),
self.primary_path.display()
)
}
}
/// v0.8.44: one-time migration from `~/.deepseek/config.toml` to
/// `~/.codewhale/config.toml`. Called on first launch after the config
/// is loaded; copies the legacy file if the primary doesn't exist yet.
/// Never overwrites an existing primary config.
pub fn migrate_config_if_needed() -> Result<()> {
pub fn migrate_config_if_needed() -> Result<Option<ConfigMigration>> {
let primary = codewhale_home()?.join(CONFIG_FILE_NAME);
if primary.exists() {
return Ok(());
return Ok(None);
}
let legacy = legacy_deepseek_home()?.join(CONFIG_FILE_NAME);
if !legacy.exists() {
return Ok(());
return Ok(None);
}
// Copy the config to the new home.
if let Some(parent) = primary.parent() {
@@ -2050,7 +2066,10 @@ pub fn migrate_config_if_needed() -> Result<()> {
legacy.display(),
primary.display()
);
Ok(())
Ok(Some(ConfigMigration {
legacy_path: legacy,
primary_path: primary,
}))
}
fn parse_bool(raw: &str) -> Result<bool> {
@@ -3295,6 +3314,83 @@ unix_socket_path = "/tmp/cw-hooks.sock"
);
}
#[test]
fn migrate_config_reports_copied_legacy_path() {
let _lock = env_lock();
struct HomeEnvGuard {
home: Option<OsString>,
userprofile: Option<OsString>,
codewhale_home: Option<OsString>,
}
impl Drop for HomeEnvGuard {
fn drop(&mut self) {
// Safety: test-only environment mutation is serialized by env_lock().
unsafe {
match self.home.take() {
Some(value) => env::set_var("HOME", value),
None => env::remove_var("HOME"),
}
match self.userprofile.take() {
Some(value) => env::set_var("USERPROFILE", value),
None => env::remove_var("USERPROFILE"),
}
match self.codewhale_home.take() {
Some(value) => env::set_var("CODEWHALE_HOME", value),
None => env::remove_var("CODEWHALE_HOME"),
}
}
}
}
let unique = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock")
.as_nanos();
let home = std::env::temp_dir().join(format!(
"codewhale-config-migration-{}-{unique}",
std::process::id()
));
let legacy_dir = home.join(LEGACY_APP_DIR);
let primary_dir = home.join(CODEWHALE_APP_DIR);
fs::create_dir_all(&legacy_dir).expect("legacy dir");
fs::write(
legacy_dir.join(CONFIG_FILE_NAME),
"provider = \"deepseek\"\n",
)
.expect("legacy config");
let _env = HomeEnvGuard {
home: env::var_os("HOME"),
userprofile: env::var_os("USERPROFILE"),
codewhale_home: env::var_os("CODEWHALE_HOME"),
};
// Safety: test-only environment mutation is serialized by env_lock().
unsafe {
env::set_var("HOME", &home);
env::set_var("USERPROFILE", &home);
env::remove_var("CODEWHALE_HOME");
}
let migration = migrate_config_if_needed()
.expect("migration")
.expect("legacy config should be copied");
assert_eq!(migration.legacy_path, legacy_dir.join(CONFIG_FILE_NAME));
assert_eq!(migration.primary_path, primary_dir.join(CONFIG_FILE_NAME));
let notice = migration.user_notice();
assert!(notice.contains(&legacy_dir.join(CONFIG_FILE_NAME).display().to_string()));
assert!(notice.contains(&primary_dir.join(CONFIG_FILE_NAME).display().to_string()));
assert!(notice.contains(".codewhale path for future edits"));
assert!(notice.contains(".deepseek file remains only as a compatibility fallback"));
assert_eq!(
fs::read_to_string(primary_dir.join(CONFIG_FILE_NAME)).expect("primary config"),
"provider = \"deepseek\"\n"
);
let _ = fs::remove_dir_all(home);
}
#[test]
fn normalize_config_file_path_rejects_traversal() {
let err = normalize_config_file_path(PathBuf::from("../config.toml"))
+6 -2
View File
@@ -5068,8 +5068,12 @@ async fn run_interactive(
// v0.8.44: migrate config from ~/.deepseek/ to ~/.codewhale/ on first
// launch. Non-fatal — existing installs keep working either way.
if let Err(err) = codewhale_config::migrate_config_if_needed() {
logging::warn(format!("Config migration skipped: {err}"));
match codewhale_config::migrate_config_if_needed() {
Ok(Some(migration)) => {
eprintln!("{}", migration.user_notice());
}
Ok(None) => {}
Err(err) => logging::warn(format!("Config migration skipped: {err}")),
}
let model = config.default_model();