fix(store): no root keeps the \\?\ prefix; the Windows root test compares canonical homes (P11c)
main's canonical default made Root::setup take std::fs::canonicalize(home), which on Windows is \\?\C:\... with the long name. root.rs now uses root::canonical (canonicalize, then the prefix off) at every site, so no root, record or message keeps the prefix; Unix is unchanged (the identity), and macOS still agrees on /tmp and /private/tmp. windows_root.rs compares against default_root(canonical(home)), so TEMP's 8.3 short name is not in the expectation, and asserts the root has no prefix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
1 parent
4a1bd05b7c
commit
f2d1d4e892
2 files changed
+20
-10
No files matched your search
@@ -290,7 +290,7 @@ pub fn inside_a_root(path: &Path) -> bool {
|
||||
let Some(existing) = path.ancestors().find(|dir| dir.exists()) else {
|
||||
return false;
|
||||
};
|
||||
let real = std::fs::canonicalize(existing).unwrap_or_else(|_| existing.to_path_buf());
|
||||
let real = canonical(existing).unwrap_or_else(|_| existing.to_path_buf());
|
||||
real.ancestors().any(|dir| dir.join(MARKER_FILE).is_file())
|
||||
}
|
||||
|
||||
@@ -323,6 +323,13 @@ pub fn without_extended_prefix(text: &str) -> String {
|
||||
text.replace(r"\\?\UNC\", r"\\").replace(r"\\?\", "")
|
||||
}
|
||||
|
||||
/// `std::fs::canonicalize` with Windows' extended-length prefix taken off
|
||||
/// (`\\?\C:\dir` is `C:\dir`): no root, record, message or git argument
|
||||
/// ever keeps the prefix.
|
||||
pub fn canonical(path: &Path) -> std::io::Result<PathBuf> {
|
||||
std::fs::canonicalize(path).map(|real| plain_path(&real))
|
||||
}
|
||||
|
||||
/// [`without_extended_prefix`] for a path (the same path on Unix).
|
||||
pub fn plain_path(path: &Path) -> PathBuf {
|
||||
match path.to_str() {
|
||||
@@ -388,7 +395,7 @@ pub fn short_link_for(dir: &Path) -> std::io::Result<PathBuf> {
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
let dir = std::fs::canonicalize(dir)?;
|
||||
let dir = canonical(dir)?;
|
||||
let link = base.join(&hex::encode(Sha256::digest(dir.as_os_str().as_encoded_bytes()))[..16]);
|
||||
match std::fs::symlink_metadata(&link) {
|
||||
Ok(meta) => {
|
||||
@@ -533,8 +540,7 @@ impl Root {
|
||||
// resolved that way too: on macOS `/tmp` is `/private/tmp`, and a
|
||||
// root chosen at the default must read as the default (so the
|
||||
// record goes), not as somewhere else.
|
||||
let default =
|
||||
default_root(&std::fs::canonicalize(home).unwrap_or_else(|_| home.to_path_buf()));
|
||||
let default = default_root(&canonical(home).unwrap_or_else(|_| home.to_path_buf()));
|
||||
let recorded = recorded_root(home)?;
|
||||
let current = recorded.clone().unwrap_or_else(|| default.clone());
|
||||
let path = match (chosen, &recorded, xdg_data_home) {
|
||||
@@ -1209,9 +1215,9 @@ impl Root {
|
||||
/// The source of an adoption, made absolute: books outside the root
|
||||
/// (and not holding it) with a journal.
|
||||
fn adoptable(&self, source: &Path) -> Result<PathBuf, RootError> {
|
||||
let source = std::fs::canonicalize(source)
|
||||
let source = canonical(source)
|
||||
.map_err(|e| RootError::Adopt(format!("can't read the books given: {}", e.kind())))?;
|
||||
let root = std::fs::canonicalize(&self.path)?;
|
||||
let root = canonical(&self.path)?;
|
||||
if source.starts_with(&root) || root.starts_with(&source) {
|
||||
return Err(RootError::Adopt(format!(
|
||||
"those books are inside {THE_ROOT} (or hold it): adopt books from outside it"
|
||||
@@ -1275,7 +1281,7 @@ impl Root {
|
||||
confirm: bool,
|
||||
) -> Result<PutAside, RootError> {
|
||||
(|| {
|
||||
let root = std::fs::canonicalize(&self.path)?;
|
||||
let root = canonical(&self.path)?;
|
||||
let book = self.book_dir(id);
|
||||
if !book.join(JOURNAL_FILE).is_file() {
|
||||
return Err(RootError::Adopt(format!(
|
||||
@@ -1298,7 +1304,7 @@ impl Root {
|
||||
let (Some(parent), Some(name)) = (given.parent(), given.file_name()) else {
|
||||
return Err(RootError::Adopt(format!("{shown} can't be put aside")));
|
||||
};
|
||||
let path = std::fs::canonicalize(parent)
|
||||
let path = canonical(parent)
|
||||
.map_err(|e| RootError::Adopt(format!("{shown}: {}", e.kind())))?
|
||||
.join(name);
|
||||
let meta = std::fs::symlink_metadata(&path)
|
||||
@@ -1932,7 +1938,7 @@ fn resolve_new(path: &Path) -> Result<PathBuf, RootError> {
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
let mut out = std::fs::canonicalize(existing)?;
|
||||
let mut out = canonical(existing)?;
|
||||
for n in rest.into_iter().rev() {
|
||||
out.push(n);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,11 @@ fn the_default_root_sits_under_local_app_data() {
|
||||
fn setup_makes_a_private_root_and_its_folders() {
|
||||
let home = tmp("setup");
|
||||
let (root, report) = Root::setup(home.path(), None, None).unwrap();
|
||||
assert_eq!(root.path(), default_root(home.path()));
|
||||
// The default root of the home as the system names it (its long name,
|
||||
// no `\\?\` prefix; TEMP may hold an 8.3 short name).
|
||||
let real_home = ledger_store::root::canonical(home.path()).unwrap();
|
||||
assert_eq!(root.path(), default_root(&real_home));
|
||||
assert!(!root.path().to_string_lossy().starts_with(r"\\?\"));
|
||||
assert!(report.created && !report.tightened && !report.recorded);
|
||||
assert!(winsec::is_private(root.path()).unwrap());
|
||||
for sub in ["books", "run", "legacy"] {
|
||||
|
||||
Reference in new issue
Block a user