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 {
|
let Some(existing) = path.ancestors().find(|dir| dir.exists()) else {
|
||||||
return false;
|
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())
|
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"\\?\", "")
|
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).
|
/// [`without_extended_prefix`] for a path (the same path on Unix).
|
||||||
pub fn plain_path(path: &Path) -> PathBuf {
|
pub fn plain_path(path: &Path) -> PathBuf {
|
||||||
match path.to_str() {
|
match path.to_str() {
|
||||||
@@ -388,7 +395,7 @@ pub fn short_link_for(dir: &Path) -> std::io::Result<PathBuf> {
|
|||||||
}
|
}
|
||||||
Err(e) => return Err(e),
|
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]);
|
let link = base.join(&hex::encode(Sha256::digest(dir.as_os_str().as_encoded_bytes()))[..16]);
|
||||||
match std::fs::symlink_metadata(&link) {
|
match std::fs::symlink_metadata(&link) {
|
||||||
Ok(meta) => {
|
Ok(meta) => {
|
||||||
@@ -533,8 +540,7 @@ impl Root {
|
|||||||
// resolved that way too: on macOS `/tmp` is `/private/tmp`, and a
|
// resolved that way too: on macOS `/tmp` is `/private/tmp`, and a
|
||||||
// root chosen at the default must read as the default (so the
|
// root chosen at the default must read as the default (so the
|
||||||
// record goes), not as somewhere else.
|
// record goes), not as somewhere else.
|
||||||
let default =
|
let default = default_root(&canonical(home).unwrap_or_else(|_| home.to_path_buf()));
|
||||||
default_root(&std::fs::canonicalize(home).unwrap_or_else(|_| home.to_path_buf()));
|
|
||||||
let recorded = recorded_root(home)?;
|
let recorded = recorded_root(home)?;
|
||||||
let current = recorded.clone().unwrap_or_else(|| default.clone());
|
let current = recorded.clone().unwrap_or_else(|| default.clone());
|
||||||
let path = match (chosen, &recorded, xdg_data_home) {
|
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
|
/// The source of an adoption, made absolute: books outside the root
|
||||||
/// (and not holding it) with a journal.
|
/// (and not holding it) with a journal.
|
||||||
fn adoptable(&self, source: &Path) -> Result<PathBuf, RootError> {
|
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())))?;
|
.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) {
|
if source.starts_with(&root) || root.starts_with(&source) {
|
||||||
return Err(RootError::Adopt(format!(
|
return Err(RootError::Adopt(format!(
|
||||||
"those books are inside {THE_ROOT} (or hold it): adopt books from outside it"
|
"those books are inside {THE_ROOT} (or hold it): adopt books from outside it"
|
||||||
@@ -1275,7 +1281,7 @@ impl Root {
|
|||||||
confirm: bool,
|
confirm: bool,
|
||||||
) -> Result<PutAside, RootError> {
|
) -> Result<PutAside, RootError> {
|
||||||
(|| {
|
(|| {
|
||||||
let root = std::fs::canonicalize(&self.path)?;
|
let root = canonical(&self.path)?;
|
||||||
let book = self.book_dir(id);
|
let book = self.book_dir(id);
|
||||||
if !book.join(JOURNAL_FILE).is_file() {
|
if !book.join(JOURNAL_FILE).is_file() {
|
||||||
return Err(RootError::Adopt(format!(
|
return Err(RootError::Adopt(format!(
|
||||||
@@ -1298,7 +1304,7 @@ impl Root {
|
|||||||
let (Some(parent), Some(name)) = (given.parent(), given.file_name()) else {
|
let (Some(parent), Some(name)) = (given.parent(), given.file_name()) else {
|
||||||
return Err(RootError::Adopt(format!("{shown} can't be put aside")));
|
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())))?
|
.map_err(|e| RootError::Adopt(format!("{shown}: {}", e.kind())))?
|
||||||
.join(name);
|
.join(name);
|
||||||
let meta = std::fs::symlink_metadata(&path)
|
let meta = std::fs::symlink_metadata(&path)
|
||||||
@@ -1932,7 +1938,7 @@ fn resolve_new(path: &Path) -> Result<PathBuf, RootError> {
|
|||||||
_ => break,
|
_ => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut out = std::fs::canonicalize(existing)?;
|
let mut out = canonical(existing)?;
|
||||||
for n in rest.into_iter().rev() {
|
for n in rest.into_iter().rev() {
|
||||||
out.push(n);
|
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() {
|
fn setup_makes_a_private_root_and_its_folders() {
|
||||||
let home = tmp("setup");
|
let home = tmp("setup");
|
||||||
let (root, report) = Root::setup(home.path(), None, None).unwrap();
|
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!(report.created && !report.tightened && !report.recorded);
|
||||||
assert!(winsec::is_private(root.path()).unwrap());
|
assert!(winsec::is_private(root.path()).unwrap());
|
||||||
for sub in ["books", "run", "legacy"] {
|
for sub in ["books", "run", "legacy"] {
|
||||||
|
|||||||
Reference in new issue
Block a user