From 2270fcfed56fb86d2f57a786ea72fcfb9f0d1aa9 Mon Sep 17 00:00:00 2001 From: Mossa Date: Thu, 14 May 2026 09:21:33 +0200 Subject: [PATCH] fix(paths): make validate_for_add / validate_for_get handle cwd-relative input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the user is in a subdirectory of the repo, they type paths relative to their cwd (e.g. \`file.bin\` from \`/repo/sub/\`). The \`validate_for_*\` checks today do \`self.repo_root.join(path)\`, which anchors at the wrong directory and only works correctly when the caller has already converted to a repo-relative form. That conversion currently lives inside \`resolve_paths_for_add\` / \`resolve_paths_for_get\`, which means callers that want per-row \`AddDetail::Error\` / \`GetDetail::Error\` rows (rather than the bail on canonicalize failure that \`resolve_paths_*\` does) can't bypass those helpers — they need the conversion side-effect. Move the cwd→repo-relative conversion into the validators (private \`DvsPaths::user_path_to_repo_relative\`). Now callers can hand user input straight to \`add_files\` / \`get_files\` and the per-row error machinery in \`validate_for_*\` does the right thing. The returned tuple carries the repo-relative form so downstream \`file_path\` / \`metadata_path\` calls remain correct. CLI behavior unchanged: it still calls \`resolve_paths_for_*\` which still bails on missing paths. --- dvs/src/paths.rs | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/dvs/src/paths.rs b/dvs/src/paths.rs index 1c2cbcd0..1975f733 100644 --- a/dvs/src/paths.rs +++ b/dvs/src/paths.rs @@ -137,8 +137,8 @@ impl DvsPaths { pub fn validate_for_add(&self, paths: &[PathBuf]) -> Vec<(PathBuf, AddPathStatus)> { let mut found = Vec::new(); for path in paths { - let file_path = self.file_path(path); - let status = match file_path.canonicalize() { + let repo_rel = self.user_path_to_repo_relative(path); + let status = match self.file_path(&repo_rel).canonicalize() { Ok(canonical) => { if !canonical.starts_with(&self.repo_root) { AddPathStatus::OutsideProject @@ -152,7 +152,7 @@ impl DvsPaths { } Err(_) => AddPathStatus::NotFound, }; - found.push((path.clone(), status)); + found.push((repo_rel, status)); } found } @@ -160,18 +160,35 @@ impl DvsPaths { pub fn validate_for_get(&self, paths: &[PathBuf]) -> Vec<(PathBuf, GetPathStatus)> { let mut found = Vec::new(); for path in paths { - let metadata_path = self.metadata_path(path); - let validation = if metadata_path.is_file() { + let repo_rel = self.user_path_to_repo_relative(path); + let validation = if self.metadata_path(&repo_rel).is_file() { GetPathStatus::Tracked - } else if self.file_path(path).is_file() { + } else if self.file_path(&repo_rel).is_file() { GetPathStatus::NotTracked } else { GetPathStatus::NotFound }; - found.push((path.clone(), validation)); + found.push((repo_rel, validation)); } found } + + /// Convert a user-supplied path (cwd-relative or absolute) to the + /// repo-relative form used internally by `file_path` / `metadata_path`. + /// Absolute paths outside the repo are returned unchanged so the + /// canonicalize-then-starts_with check in `validate_for_add` can flag + /// them as `OutsideProject`. + fn user_path_to_repo_relative(&self, path: &Path) -> PathBuf { + if path.is_absolute() { + path.strip_prefix(&self.repo_root) + .map(Path::to_path_buf) + .unwrap_or_else(|_| path.to_path_buf()) + } else if let Some(prefix) = self.cwd_relative_to_root() { + prefix.join(path) + } else { + path.to_path_buf() + } + } } #[cfg(test)]