diff --git a/changelog.d/20261012-rehearsal-path-normalization.md b/changelog.d/20261012-rehearsal-path-normalization.md new file mode 100644 index 0000000..e558f86 --- /dev/null +++ b/changelog.d/20261012-rehearsal-path-normalization.md @@ -0,0 +1,7 @@ +--- +area: manager +type: fixed +audience: admins +title: Normalize rehearsal server roots to prevent Windows path prefix errors +--- +Server update rehearsals now canonicalize the installation root before collecting files, resolving a "prefix not found" error during rehearsal preparation on Windows. diff --git a/crates/coa-core/src/mysql_snapshot.rs b/crates/coa-core/src/mysql_snapshot.rs index 9d445ee..ea14db9 100644 --- a/crates/coa-core/src/mysql_snapshot.rs +++ b/crates/coa-core/src/mysql_snapshot.rs @@ -228,6 +228,8 @@ pub(crate) fn verify(point: &Path, snapshot: &Snapshot) -> Result<()> { pub(crate) fn rehearsal_copy(root: &Path, meta: &Path, point: &Path, snapshot: &Snapshot) -> Result { verify(point, snapshot)?; + let canonical = fsx::canonicalize_lenient(root)?; + let root = canonical.as_path(); let destination = meta.join("rehearsals").join(uuid::Uuid::new_v4().simple().to_string()).join("server"); let mut files = BTreeMap::new(); let mut directories = vec![root.to_path_buf()]; @@ -475,4 +477,17 @@ mod tests { fsx::atomic_write(&root.join("mysql/data/extra.ibd"), b"new data").unwrap(); assert!(unchanged(&root, &snapshot).is_err()); } + + #[test] + fn rehearsal_copy_succeeds_with_nested_directories_and_unnormalized_root() { + let (_temp, root, meta, point, snapshot) = fixture(); + let meta_obj = crate::registry::InstallMeta::new(crate::registry::InstallKind::New, &root); + fsx::atomic_write_json(&meta.join("install.json"), &meta_obj).unwrap(); + fsx::atomic_write(&root.join("Core/worldserver.exe"), b"exe").unwrap(); + fsx::atomic_write(&root.join("Core/configs/worldserver.conf"), b"conf").unwrap(); + fsx::atomic_write(&root.join("Settings/repack.json"), b"{}").unwrap(); + let dest = rehearsal_copy(&root, &meta, &point, &snapshot).unwrap(); + assert_eq!(fs::read(dest.join("Core/configs/worldserver.conf")).unwrap(), b"conf"); + assert_eq!(fs::read(dest.join("mysql/data/characters.ibd")).unwrap(), b"saved"); + } }