diff --git a/src/migration.rs b/src/migration.rs index a14068571..75f1bb0fa 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -1,7 +1,6 @@ use crate::config::Config; use crate::memory::{self, Memory, MemoryCategory}; use anyhow::{bail, Context, Result}; -use directories::UserDirs; use rusqlite::{Connection, OpenFlags, OptionalExtension}; use std::collections::HashSet; use std::fs; @@ -48,7 +47,7 @@ async fn migrate_openclaw_memory( source_workspace: Option, dry_run: bool, ) -> Result<()> { - let source_workspace = resolve_openclaw_workspace(source_workspace); + let source_workspace = resolve_openclaw_workspace(config, source_workspace); if !source_workspace.path.exists() { return handle_missing_source_workspace(config, &source_workspace, dry_run); } @@ -362,7 +361,7 @@ fn pick_column_expr(columns: &[String], candidates: &[&str], fallback: &str) -> pick_optional_column_expr(columns, candidates).unwrap_or_else(|| fallback.to_string()) } -fn resolve_openclaw_workspace(source: Option) -> SourceWorkspace { +fn resolve_openclaw_workspace(config: &Config, source: Option) -> SourceWorkspace { if let Some(src) = source { return SourceWorkspace { path: src, @@ -370,10 +369,8 @@ fn resolve_openclaw_workspace(source: Option) -> SourceWorkspace { }; } - let path = UserDirs::new().map_or_else( - || PathBuf::from(".openclaw").join("workspace"), - |dirs| dirs.home_dir().join(".openclaw").join("workspace"), - ); + let path = default_openclaw_workspace_from_env() + .unwrap_or_else(|| config.workspace_dir.join(".openclaw").join("workspace")); SourceWorkspace { path, @@ -381,6 +378,13 @@ fn resolve_openclaw_workspace(source: Option) -> SourceWorkspace { } } +fn default_openclaw_workspace_from_env() -> Option { + std::env::var_os("HOME") + .or_else(|| std::env::var_os("USERPROFILE")) + .filter(|value| !value.is_empty()) + .map(|home| PathBuf::from(home).join(".openclaw").join("workspace")) +} + fn paths_equal(a: &Path, b: &Path) -> bool { match (fs::canonicalize(a), fs::canonicalize(b)) { (Ok(a), Ok(b)) => a == b, @@ -500,6 +504,7 @@ mod tests { use crate::config::{Config, MemoryConfig}; use crate::memory::SqliteMemory; use rusqlite::params; + use std::sync::{Mutex, OnceLock}; use tempfile::TempDir; fn test_config(workspace: &Path) -> Config { @@ -514,6 +519,37 @@ mod tests { } } + struct EnvGuard { + key: &'static str, + previous: Option, + } + + impl EnvGuard { + fn set(key: &'static str, value: Option<&str>) -> Self { + let previous = std::env::var_os(key); + match value { + Some(v) => std::env::set_var(key, v), + None => std::env::remove_var(key), + } + Self { key, previous } + } + } + + impl Drop for EnvGuard { + fn drop(&mut self) { + if let Some(value) = &self.previous { + std::env::set_var(self.key, value); + } else { + std::env::remove_var(self.key); + } + } + } + + fn env_lock() -> std::sync::MutexGuard<'static, ()> { + static LOCK: OnceLock> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())).lock().unwrap() + } + #[test] fn parse_structured_markdown_line() { let line = "**user_pref**: likes Rust"; @@ -665,6 +701,39 @@ mod tests { assert!(err.to_string().contains("OpenClaw workspace not found")); } + #[test] + fn default_source_prefers_home_env_when_available() { + let _env_guard = env_lock(); + let target = TempDir::new().unwrap(); + let fake_home = target.path().join("fake-home"); + std::fs::create_dir_all(&fake_home).unwrap(); + let _home_guard = EnvGuard::set("HOME", Some(fake_home.to_str().unwrap())); + let _userprofile_guard = EnvGuard::set("USERPROFILE", None); + + let config = test_config(target.path()); + let source = resolve_openclaw_workspace(&config, None); + + assert_eq!(source.kind, SourceWorkspaceKind::Default); + assert_eq!(source.path, fake_home.join(".openclaw").join("workspace")); + } + + #[test] + fn default_source_is_workspace_scoped_without_home_env() { + let _env_guard = env_lock(); + let target = TempDir::new().unwrap(); + let _home_guard = EnvGuard::set("HOME", None); + let _userprofile_guard = EnvGuard::set("USERPROFILE", None); + + let config = test_config(target.path()); + let source = resolve_openclaw_workspace(&config, None); + + assert_eq!(source.kind, SourceWorkspaceKind::Default); + assert_eq!( + source.path, + target.path().join(".openclaw").join("workspace") + ); + } + #[test] fn migration_target_rejects_none_backend() { let target = TempDir::new().unwrap();