diff --git a/Cargo.lock b/Cargo.lock index 6f434e8685..6d6a640fb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12871,6 +12871,7 @@ dependencies = [ "prost-build 0.9.0", "serde", "typed-path", + "util", "workspace-hack", ] diff --git a/crates/fs/src/fake_git_repo.rs b/crates/fs/src/fake_git_repo.rs index 04ba656232..1abf7ecadb 100644 --- a/crates/fs/src/fake_git_repo.rs +++ b/crates/fs/src/fake_git_repo.rs @@ -15,6 +15,7 @@ use ignore::gitignore::GitignoreBuilder; use rope::Rope; use smol::future::FutureExt as _; use std::{path::PathBuf, sync::Arc}; +use util::rel_path::RelPath; #[derive(Clone)] pub struct FakeGitRepository { @@ -222,7 +223,10 @@ impl GitRepository for FakeGitRepository { .read_file_sync(path) .ok() .map(|content| String::from_utf8(content).unwrap())?; - Some((repo_path.into(), (content, is_ignored))) + Some(( + RepoPath::from(&RelPath::new(repo_path)), + (content, is_ignored), + )) }) .collect(); diff --git a/crates/git/src/blame.rs b/crates/git/src/blame.rs index 2128fa55c3..7bfd31f02f 100644 --- a/crates/git/src/blame.rs +++ b/crates/git/src/blame.rs @@ -1,4 +1,5 @@ use crate::commit::get_messages; +use crate::repository::RepoPath; use crate::{GitRemote, Oid}; use anyhow::{Context as _, Result}; use collections::{HashMap, HashSet}; @@ -33,7 +34,7 @@ impl Blame { pub async fn for_path( git_binary: &Path, working_directory: &Path, - path: &Path, + path: &RepoPath, content: &Rope, remote_url: Option, ) -> Result { @@ -66,7 +67,7 @@ const GIT_BLAME_NO_PATH: &str = "fatal: no such path"; async fn run_git_blame( git_binary: &Path, working_directory: &Path, - path: &Path, + path: &RepoPath, contents: &Rope, ) -> Result { let mut child = util::command::new_smol_command(git_binary) diff --git a/crates/git/src/commit.rs b/crates/git/src/commit.rs index aaacdc038a..14a025a4ec 100644 Binary files a/crates/git/src/commit.rs and b/crates/git/src/commit.rs differ diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index dc7ab0af65..87f18c4e6a 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -27,6 +27,7 @@ use std::{ use sum_tree::MapSeekTarget; use thiserror::Error; use util::command::{new_smol_command, new_std_command}; +use util::rel_path::RelPath; use util::{ResultExt, paths}; use uuid::Uuid; @@ -662,14 +663,22 @@ impl GitRepository for RealGitRepository { for (path, status_code) in changes { match status_code { StatusCode::Modified => { - writeln!(&mut stdin, "{commit}:{}", path.display())?; - writeln!(&mut stdin, "{parent_sha}:{}", path.display())?; + write!(&mut stdin, "{commit}:")?; + stdin.write_all(path.as_bytes())?; + stdin.write_all(b"\n")?; + write!(&mut stdin, "{parent_sha}:")?; + stdin.write_all(path.as_bytes())?; + stdin.write_all(b"\n")?; } StatusCode::Added => { - writeln!(&mut stdin, "{commit}:{}", path.display())?; + write!(&mut stdin, "{commit}:")?; + stdin.write_all(path.as_bytes())?; + stdin.write_all(b"\n")?; } StatusCode::Deleted => { - writeln!(&mut stdin, "{parent_sha}:{}", path.display())?; + write!(&mut stdin, "{parent_sha}:")?; + stdin.write_all(path.as_bytes())?; + stdin.write_all(b"\n")?; } _ => continue, } @@ -765,7 +774,7 @@ impl GitRepository for RealGitRepository { .current_dir(&working_directory?) .envs(env.iter()) .args(["checkout", &commit, "--"]) - .args(paths.iter().map(|path| path.as_ref())) + .args(paths.iter().map(|path| path.to_unix_style())) .output() .await?; anyhow::ensure!( @@ -787,13 +796,14 @@ impl GitRepository for RealGitRepository { .spawn(async move { fn logic(repo: &git2::Repository, path: &RepoPath) -> Result> { // This check is required because index.get_path() unwraps internally :( - check_path_to_repo_path_errors(path)?; + // TODO: move this function to where we instantiate the repopaths + // check_path_to_repo_path_errors(path)?; let mut index = repo.index()?; index.read(false)?; const STAGE_NORMAL: i32 = 0; - let oid = match index.get_path(path, STAGE_NORMAL) { + let oid = match index.get_path(Path::new(&path.to_unix_style()), STAGE_NORMAL) { Some(entry) if entry.mode != GIT_MODE_SYMLINK => entry.id, _ => return Ok(None), }; @@ -817,7 +827,7 @@ impl GitRepository for RealGitRepository { .spawn(async move { let repo = repo.lock(); let head = repo.head().ok()?.peel_to_tree().log_err()?; - let entry = head.get_path(&path).ok()?; + let entry = head.get_path(Path::new(&path.as_os_str())).ok()?; if entry.filemode() == i32::from(git2::FileMode::Link) { return None; } @@ -1184,7 +1194,7 @@ impl GitRepository for RealGitRepository { .current_dir(&working_directory?) .envs(env.iter()) .args(["reset", "--quiet", "--"]) - .args(paths.iter().map(|p| p.as_ref())) + .args(paths.iter().map(|p| p.to_unix_style())) .output() .await?; @@ -1213,7 +1223,7 @@ impl GitRepository for RealGitRepository { .args(["stash", "push", "--quiet"]) .arg("--include-untracked"); - cmd.args(paths.iter().map(|p| p.as_ref())); + cmd.args(paths.iter().map(|p| p.to_unix_style())); let output = cmd.output().await?; @@ -1652,7 +1662,7 @@ fn git_status_args(path_prefixes: &[RepoPath]) -> Vec { OsString::from("-z"), ]; args.extend(path_prefixes.iter().map(|path_prefix| { - if path_prefix.0.as_ref() == Path::new("") { + if path_prefix.0.as_ref() == RelPath::new("") { Path::new(".").into() } else { path_prefix.as_os_str().into() @@ -1905,64 +1915,33 @@ async fn run_askpass_command( } pub static WORK_DIRECTORY_REPO_PATH: LazyLock = - LazyLock::new(|| RepoPath(Path::new("").into())); + LazyLock::new(|| RepoPath(RelPath::new("").into())); #[derive(Clone, Debug, Ord, Hash, PartialOrd, Eq, PartialEq)] -pub struct RepoPath(pub Arc); +pub struct RepoPath(pub Arc); impl RepoPath { - pub fn new(path: PathBuf) -> Self { - debug_assert!(path.is_relative(), "Repo paths must be relative"); - - RepoPath(path.into()) - } - pub fn from_str(path: &str) -> Self { - let path = Path::new(path); - debug_assert!(path.is_relative(), "Repo paths must be relative"); - - RepoPath(path.into()) + RepoPath(RelPath::new(path).into()) } pub fn to_unix_style(&self) -> Cow<'_, OsStr> { - #[cfg(target_os = "windows")] - { - use std::ffi::OsString; - - let path = self.0.as_os_str().to_string_lossy().replace("\\", "/"); - Cow::Owned(OsString::from(path)) - } - #[cfg(not(target_os = "windows"))] - { - Cow::Borrowed(self.0.as_os_str()) - } + self.0.as_os_str() } } -impl std::fmt::Display for RepoPath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.to_string_lossy().fmt(f) +impl From<&RelPath> for RepoPath { + fn from(value: &RelPath) -> Self { + RepoPath(value.into()) } } -impl From<&Path> for RepoPath { - fn from(value: &Path) -> Self { - RepoPath::new(value.into()) - } -} - -impl From> for RepoPath { - fn from(value: Arc) -> Self { +impl From> for RepoPath { + fn from(value: Arc) -> Self { RepoPath(value) } } -impl From for RepoPath { - fn from(value: PathBuf) -> Self { - RepoPath::new(value) - } -} - impl From<&str> for RepoPath { fn from(value: &str) -> Self { Self::from_str(value) @@ -1971,32 +1950,32 @@ impl From<&str> for RepoPath { impl Default for RepoPath { fn default() -> Self { - RepoPath(Path::new("").into()) + RepoPath(RelPath::new("").into()) } } -impl AsRef for RepoPath { - fn as_ref(&self) -> &Path { +impl AsRef for RepoPath { + fn as_ref(&self) -> &RelPath { self.0.as_ref() } } impl std::ops::Deref for RepoPath { - type Target = Path; + type Target = RelPath; fn deref(&self) -> &Self::Target { &self.0 } } -impl Borrow for RepoPath { - fn borrow(&self) -> &Path { +impl Borrow for RepoPath { + fn borrow(&self) -> &RelPath { self.0.as_ref() } } #[derive(Debug)] -pub struct RepoPathDescendants<'a>(pub &'a Path); +pub struct RepoPathDescendants<'a>(pub &'a RelPath); impl MapSeekTarget for RepoPathDescendants<'_> { fn cmp_cursor(&self, key: &RepoPath) -> Ordering { @@ -2080,35 +2059,6 @@ fn parse_upstream_track(upstream_track: &str) -> Result { })) } -fn check_path_to_repo_path_errors(relative_file_path: &Path) -> Result<()> { - match relative_file_path.components().next() { - None => anyhow::bail!("repo path should not be empty"), - Some(Component::Prefix(_)) => anyhow::bail!( - "repo path `{}` should be relative, not a windows prefix", - relative_file_path.to_string_lossy() - ), - Some(Component::RootDir) => { - anyhow::bail!( - "repo path `{}` should be relative", - relative_file_path.to_string_lossy() - ) - } - Some(Component::CurDir) => { - anyhow::bail!( - "repo path `{}` should not start with `.`", - relative_file_path.to_string_lossy() - ) - } - Some(Component::ParentDir) => { - anyhow::bail!( - "repo path `{}` should not start with `..`", - relative_file_path.to_string_lossy() - ) - } - _ => Ok(()), - } -} - fn checkpoint_author_envs() -> HashMap { HashMap::from_iter([ ("GIT_AUTHOR_NAME".to_string(), "Zed".to_string()), diff --git a/crates/git/src/status.rs b/crates/git/src/status.rs index 6158b51798..b126d50f46 100644 --- a/crates/git/src/status.rs +++ b/crates/git/src/status.rs @@ -1,8 +1,8 @@ use crate::repository::RepoPath; use anyhow::Result; use serde::{Deserialize, Serialize}; -use std::{path::Path, str::FromStr, sync::Arc}; -use util::ResultExt; +use std::{str::FromStr, sync::Arc}; +use util::{ResultExt, rel_path::RelPath}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum FileStatus { @@ -464,7 +464,7 @@ impl FromStr for GitStatus { } let status = entry.as_bytes()[0..2].try_into().unwrap(); let status = FileStatus::from_bytes(status).log_err()?; - let path = RepoPath(Path::new(path).into()); + let path = RepoPath(RelPath::new(path.as_bytes()).into()); Some((path, status)) }) .collect::>(); diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 01fc987816..87e844292e 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -1663,7 +1663,7 @@ impl GitStore { .payload .paths .into_iter() - .map(PathBuf::from) + .map(RelPath::new) .map(RepoPath::new) .collect(); diff --git a/crates/proto/Cargo.toml b/crates/proto/Cargo.toml index 6cae4394bd..2f2f79b5c5 100644 --- a/crates/proto/Cargo.toml +++ b/crates/proto/Cargo.toml @@ -20,6 +20,7 @@ doctest = false anyhow.workspace = true prost.workspace = true serde.workspace = true +util.workspace = true workspace-hack.workspace = true [build-dependencies] diff --git a/crates/proto/src/typed_envelope.rs b/crates/proto/src/typed_envelope.rs index 381a6379dc..e6fdf7ecc0 100644 --- a/crates/proto/src/typed_envelope.rs +++ b/crates/proto/src/typed_envelope.rs @@ -9,6 +9,7 @@ use std::{ sync::Arc, }; use std::{marker::PhantomData, time::Instant}; +use util::rel_path::RelPath; pub trait EnvelopedMessage: Clone + Debug + Serialize + Sized + Send + Sync + 'static { const NAME: &'static str; @@ -158,6 +159,12 @@ impl FromProto for Arc { } } +impl FromProto for Arc { + fn from_proto(proto: String) -> Self { + RelPath::new(proto.as_bytes()).into() + } +} + impl ToProto for PathBuf { fn to_proto(self) -> String { to_proto_path(&self) diff --git a/crates/util/src/rel_path.rs b/crates/util/src/rel_path.rs new file mode 100644 index 0000000000..ae628cabdb --- /dev/null +++ b/crates/util/src/rel_path.rs @@ -0,0 +1,206 @@ +use std::{ + borrow::Cow, + ffi::OsStr, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, + sync::Arc, +}; + +use anyhow::{Result, bail}; + +#[repr(transparent)] +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RelPath([u8]); + +impl RelPath { + pub fn new + ?Sized>(s: &S) -> &Self { + unsafe { &*(s.as_ref() as *const [u8] as *const Self) } + } + + pub fn components(&self) -> RelPathComponents { + RelPathComponents(&self.0) + } + + pub fn file_name(&self) -> Option<&[u8]> { + self.components().next_back() + } + + pub fn parent(&self) -> Option<&Self> { + let mut components = self.components(); + components.next_back()?; + Some(Self::new(components.0)) + } + + pub fn starts_with(&self, other: &Self) -> bool { + let mut components = self.components(); + other.components().all(|other_component| { + components + .next() + .map_or(false, |component| component == other_component) + }) + } + + pub fn strip_prefix(&self, other: &Self) -> Result<&Self, ()> { + let mut components = self.components(); + other + .components() + .all(|other_component| { + components + .next() + .map_or(false, |component| component == other_component) + }) + .then(|| Self::new(components.0)) + .ok_or_else(|| ()) + } + + pub fn from_path(relative_path: &Path) -> Result<&Self> { + use std::path::Component; + match relative_path.components().next() { + Some(Component::Prefix(_)) => bail!( + "path `{}` should be relative, not a windows prefix", + relative_path.to_string_lossy() + ), + Some(Component::RootDir) => { + bail!( + "path `{}` should be relative", + relative_path.to_string_lossy() + ) + } + Some(Component::CurDir) => { + bail!( + "path `{}` should not start with `.`", + relative_path.to_string_lossy() + ) + } + Some(Component::ParentDir) => { + bail!( + "path `{}` should not start with `..`", + relative_path.to_string_lossy() + ) + } + None => bail!("relative path should not be empty"), + _ => Ok(Self::new(relative_path.as_os_str().as_bytes())), + } + } + + pub fn append_to_abs_path(&self, abs_path: &Path) -> PathBuf { + // TODO: implement this differently + let mut result = abs_path.to_path_buf(); + for component in self.components() { + result.push(String::from_utf8_lossy(component).as_ref()); + } + result + } + + pub fn to_proto(&self) -> String { + String::from_utf8_lossy(&self.0).to_string() + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn as_os_str(&self) -> Cow<'_, OsStr> { + #[cfg(target_os = "windows")] + { + use std::ffi::OsString; + let path = String::from_utf8_lossy(&self.0); + match path { + Cow::Borrowed(s) => Cow::Borrowed(OsStr::new(s)), + Cow::Owned(s) => Cow::Owned(OsString::from(s)), + } + } + #[cfg(not(target_os = "windows"))] + { + use std::os::unix::ffi::OsStrExt; + + Cow::Borrowed(OsStr::from_bytes(&self.0)) + } + } +} + +impl From<&RelPath> for Arc { + fn from(rel_path: &RelPath) -> Self { + let bytes: Arc<[u8]> = Arc::from(&rel_path.0); + unsafe { Arc::from_raw(Arc::into_raw(bytes) as *const RelPath) } + } +} + +impl AsRef for &str { + fn as_ref(&self) -> &RelPath { + RelPath::new(self) + } +} + +impl std::fmt::Debug for RelPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Ok(str) = std::str::from_utf8(&self.0) { + write!(f, "RelPath({})", str) + } else { + write!(f, "RelPath({:?})", &self.0) + } + } +} + +pub struct RelPathComponents<'a>(&'a [u8]); + +const SEPARATOR: u8 = b'/'; + +impl<'a> Iterator for RelPathComponents<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + if let Some(sep_ix) = self.0.iter().position(|&byte| byte == SEPARATOR) { + let (head, tail) = self.0.split_at(sep_ix); + self.0 = &tail[1..]; + Some(head) + } else if self.0.is_empty() { + None + } else { + let result = self.0; + self.0 = &[]; + Some(result) + } + } +} + +impl<'a> DoubleEndedIterator for RelPathComponents<'a> { + fn next_back(&mut self) -> Option { + if let Some(sep_ix) = self.0.iter().rposition(|&byte| byte == SEPARATOR) { + let (head, tail) = self.0.split_at(sep_ix); + self.0 = head; + Some(&tail[1..]) + } else if self.0.is_empty() { + None + } else { + let result = self.0; + self.0 = &[]; + Some(result) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rel_path_components() { + let path = RelPath::new("foo/bar/baz"); + let mut components = path.components(); + assert_eq!(components.next(), Some("foo".as_bytes())); + assert_eq!(components.next(), Some("bar".as_bytes())); + assert_eq!(components.next(), Some("baz".as_bytes())); + assert_eq!(components.next(), None); + } + + #[test] + fn test_rel_path_parent() { + assert_eq!( + RelPath::new("foo/bar/baz").parent().unwrap(), + RelPath::new("foo/bar") + ); + assert_eq!(RelPath::new("foo").parent().unwrap(), RelPath::new("")); + assert_eq!(RelPath::new("").parent(), None); + } +} diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 932b519b18..cecbc982cf 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -5,6 +5,7 @@ pub mod fs; pub mod markdown; pub mod paths; pub mod redact; +pub mod rel_path; pub mod schemars; pub mod serde; pub mod shell_env; diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index b5a0f71e81..685f8644d9 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -67,6 +67,7 @@ use text::{LineEnding, Rope}; use util::{ ResultExt, paths::{PathMatcher, SanitizedPath, home_dir}, + rel_path::RelPath, }; pub use worktree_settings::WorktreeSettings; @@ -132,12 +133,12 @@ pub struct LocalWorktree { } pub struct PathPrefixScanRequest { - path: Arc, + path: Arc, done: SmallVec<[barrier::Sender; 1]>, } struct ScanRequest { - relative_paths: Vec>, + relative_paths: Vec>, done: SmallVec<[barrier::Sender; 1]>, } @@ -186,7 +187,7 @@ pub struct Snapshot { #[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum WorkDirectory { InProject { - relative_path: Arc, + relative_path: Arc, }, AboveProject { absolute_path: Arc, @@ -197,7 +198,7 @@ pub enum WorkDirectory { impl WorkDirectory { #[cfg(test)] fn in_project(path: &str) -> Self { - let path = Path::new(path); + let path = RelPath::new(path); Self::InProject { relative_path: path.into(), } @@ -232,9 +233,8 @@ impl WorkDirectory { /// is a repository in a directory between these two paths /// external .git folder in a parent folder of the project root. #[track_caller] - pub fn directory_contains(&self, path: impl AsRef) -> bool { + pub fn directory_contains(&self, path: impl AsRef) -> bool { let path = path.as_ref(); - debug_assert!(path.is_relative()); match self { WorkDirectory::InProject { relative_path } => path.starts_with(relative_path), WorkDirectory::AboveProject { .. } => true, @@ -246,9 +246,8 @@ impl WorkDirectory { /// If the root of the repository (and its .git folder) are located in a parent folder /// of the project root folder, then the returned RepoPath is relative to the root /// of the repository and not a valid path inside the project. - pub fn relativize(&self, path: &Path) -> Result { + pub fn relativize(&self, path: &RelPath) -> Result { // path is assumed to be relative to worktree root. - debug_assert!(path.is_relative()); match self { WorkDirectory::InProject { relative_path } => Ok(path .strip_prefix(relative_path) @@ -842,12 +841,12 @@ impl Worktree { pub fn create_entry( &mut self, - path: impl Into>, + path: impl Into>, is_directory: bool, content: Option>, cx: &Context, ) -> Task> { - let path: Arc = path.into(); + let path: Arc = path.into(); let worktree_id = self.id(); match self { Worktree::Local(this) => this.create_entry(path, is_directory, content, cx), @@ -914,7 +913,7 @@ impl Worktree { Some(task) } - fn get_children_ids_recursive(&self, path: &Path, ids: &mut Vec) { + fn get_children_ids_recursive(&self, path: &RelPath, ids: &mut Vec) { let children_iter = self.child_entries(path); for child in children_iter { ids.push(child.id); @@ -1575,7 +1574,7 @@ impl LocalWorktree { fn create_entry( &self, - path: impl Into>, + path: impl Into>, is_dir: bool, content: Option>, cx: &Context, @@ -1975,7 +1974,7 @@ impl LocalWorktree { })) } - fn refresh_entries_for_paths(&self, paths: Vec>) -> barrier::Receiver { + fn refresh_entries_for_paths(&self, paths: Vec>) -> barrier::Receiver { let (tx, rx) = barrier::channel(); self.scan_requests_tx .try_send(ScanRequest { @@ -1987,11 +1986,14 @@ impl LocalWorktree { } #[cfg(feature = "test-support")] - pub fn manually_refresh_entries_for_paths(&self, paths: Vec>) -> barrier::Receiver { + pub fn manually_refresh_entries_for_paths( + &self, + paths: Vec>, + ) -> barrier::Receiver { self.refresh_entries_for_paths(paths) } - pub fn add_path_prefix_to_scan(&self, path_prefix: Arc) -> barrier::Receiver { + pub fn add_path_prefix_to_scan(&self, path_prefix: Arc) -> barrier::Receiver { let (tx, rx) = barrier::channel(); self.path_prefixes_to_scan_tx .try_send(PathPrefixScanRequest { @@ -2004,8 +2006,8 @@ impl LocalWorktree { fn refresh_entry( &self, - path: Arc, - old_path: Option>, + path: Arc, + old_path: Option>, cx: &Context, ) -> Task>> { if self.settings.is_path_excluded(&path) { @@ -2403,18 +2405,8 @@ impl Snapshot { } } - pub fn absolutize(&self, path: &Path) -> Result { - if path - .components() - .any(|component| !matches!(component, std::path::Component::Normal(_))) - { - anyhow::bail!("invalid path"); - } - if path.file_name().is_some() { - Ok(self.abs_path.as_path().join(path)) - } else { - Ok(self.abs_path.as_path().to_path_buf()) - } + pub fn absolutize(&self, path: &RelPath) -> Result { + Ok(path.append_to_abs_path(&self.abs_path.0)) } pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool { @@ -2585,7 +2577,7 @@ impl Snapshot { include_files: bool, include_dirs: bool, include_ignored: bool, - path: &Path, + path: &RelPath, ) -> Traversal<'_> { Traversal::new(self, include_files, include_dirs, include_ignored, path) } @@ -2602,15 +2594,15 @@ impl Snapshot { self.traverse_from_offset(true, true, include_ignored, start) } - pub fn paths(&self) -> impl Iterator> { - let empty_path = Path::new(""); + pub fn paths(&self) -> impl Iterator> { + let empty_path = RelPath::new(""); self.entries_by_path .cursor::<()>(&()) .filter(move |entry| entry.path.as_ref() != empty_path) .map(|entry| &entry.path) } - pub fn child_entries<'a>(&'a self, parent_path: &'a Path) -> ChildEntriesIter<'a> { + pub fn child_entries<'a>(&'a self, parent_path: &'a RelPath) -> ChildEntriesIter<'a> { let options = ChildEntriesOptions { include_files: true, include_dirs: true, @@ -2621,7 +2613,7 @@ impl Snapshot { pub fn child_entries_with_options<'a>( &'a self, - parent_path: &'a Path, + parent_path: &'a RelPath, options: ChildEntriesOptions, ) -> ChildEntriesIter<'a> { let mut cursor = self.entries_by_path.cursor(&()); @@ -2659,9 +2651,8 @@ impl Snapshot { self.scan_id } - pub fn entry_for_path(&self, path: impl AsRef) -> Option<&Entry> { + pub fn entry_for_path(&self, path: impl AsRef) -> Option<&Entry> { let path = path.as_ref(); - debug_assert!(path.is_relative()); self.traverse_from_path(true, true, true, path) .entry() .and_then(|entry| { @@ -3436,7 +3427,7 @@ impl File { pub struct Entry { pub id: ProjectEntryId, pub kind: EntryKind, - pub path: Arc, + pub path: Arc, pub inode: u64, pub mtime: Option, @@ -3510,7 +3501,7 @@ pub struct UpdatedGitRepository { pub common_dir_abs_path: Option>, } -pub type UpdatedEntriesSet = Arc<[(Arc, ProjectEntryId, PathChange)]>; +pub type UpdatedEntriesSet = Arc<[(Arc, ProjectEntryId, PathChange)]>; pub type UpdatedGitRepositoriesSet = Arc<[UpdatedGitRepository]>; #[derive(Clone, Debug)] @@ -3786,7 +3777,7 @@ impl<'a> sum_tree::Dimension<'a, PathEntrySummary> for ProjectEntryId { } #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct PathKey(pub Arc); +pub struct PathKey(pub Arc); impl Default for PathKey { fn default() -> Self { @@ -5188,7 +5179,7 @@ impl WorktreeModelHandle for Entity { #[derive(Clone, Debug)] struct TraversalProgress<'a> { - max_path: &'a Path, + max_path: &'a RelPath, count: usize, non_ignored_count: usize, file_count: usize, @@ -5226,7 +5217,7 @@ impl<'a> sum_tree::Dimension<'a, EntrySummary> for TraversalProgress<'a> { impl Default for TraversalProgress<'_> { fn default() -> Self { Self { - max_path: Path::new(""), + max_path: RelPath::new(""), count: 0, non_ignored_count: 0, file_count: 0, @@ -5250,7 +5241,7 @@ impl<'a> Traversal<'a> { include_files: bool, include_dirs: bool, include_ignored: bool, - start_path: &Path, + start_path: &RelPath, ) -> Self { let mut cursor = snapshot.entries_by_path.cursor(&()); cursor.seek(&TraversalTarget::path(start_path), Bias::Left); @@ -5343,12 +5334,12 @@ impl<'a> Iterator for Traversal<'a> { #[derive(Debug, Clone, Copy)] pub enum PathTarget<'a> { - Path(&'a Path), - Successor(&'a Path), + Path(&'a RelPath), + Successor(&'a RelPath), } impl PathTarget<'_> { - fn cmp_path(&self, other: &Path) -> Ordering { + fn cmp_path(&self, other: &RelPath) -> Ordering { match self { PathTarget::Path(path) => path.cmp(&other), PathTarget::Successor(path) => { @@ -5386,11 +5377,11 @@ enum TraversalTarget<'a> { } impl<'a> TraversalTarget<'a> { - fn path(path: &'a Path) -> Self { + fn path(path: &'a RelPath) -> Self { Self::Path(PathTarget::Path(path)) } - fn successor(path: &'a Path) -> Self { + fn successor(path: &'a RelPath) -> Self { Self::Path(PathTarget::Successor(path)) } diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index d4c309e5bc..9bc72e017c 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -20,7 +20,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::{ResultExt, path, test::TempTree}; +use util::{ResultExt, path, rel_path::RelPath, test::TempTree}; #[gpui::test] async fn test_traversal(cx: &mut TestAppContext) { @@ -56,10 +56,10 @@ async fn test_traversal(cx: &mut TestAppContext) { .map(|entry| entry.path.as_ref()) .collect::>(), vec![ - Path::new(""), - Path::new(".gitignore"), - Path::new("a"), - Path::new("a/c"), + RelPath::new(""), + RelPath::new(".gitignore"), + RelPath::new("a"), + RelPath::new("a/c"), ] ); assert_eq!( @@ -67,11 +67,11 @@ async fn test_traversal(cx: &mut TestAppContext) { .map(|entry| entry.path.as_ref()) .collect::>(), vec![ - Path::new(""), - Path::new(".gitignore"), - Path::new("a"), - Path::new("a/b"), - Path::new("a/c"), + RelPath::new(""), + RelPath::new(".gitignore"), + RelPath::new("a"), + RelPath::new("a/b"), + RelPath::new("a/c"), ] ); }) @@ -121,14 +121,14 @@ async fn test_circular_symlinks(cx: &mut TestAppContext) { .map(|entry| entry.path.as_ref()) .collect::>(), vec![ - Path::new(""), - Path::new("lib"), - Path::new("lib/a"), - Path::new("lib/a/a.txt"), - Path::new("lib/a/lib"), - Path::new("lib/b"), - Path::new("lib/b/b.txt"), - Path::new("lib/b/lib"), + RelPath::new(""), + RelPath::new("lib"), + RelPath::new("lib/a"), + RelPath::new("lib/a/a.txt"), + RelPath::new("lib/a/lib"), + RelPath::new("lib/b"), + RelPath::new("lib/b/b.txt"), + RelPath::new("lib/b/lib"), ] ); }); @@ -147,14 +147,14 @@ async fn test_circular_symlinks(cx: &mut TestAppContext) { .map(|entry| entry.path.as_ref()) .collect::>(), vec![ - Path::new(""), - Path::new("lib"), - Path::new("lib/a"), - Path::new("lib/a/a.txt"), - Path::new("lib/a/lib-2"), - Path::new("lib/b"), - Path::new("lib/b/b.txt"), - Path::new("lib/b/lib"), + RelPath::new(""), + RelPath::new("lib"), + RelPath::new("lib/a"), + RelPath::new("lib/a/a.txt"), + RelPath::new("lib/a/lib-2"), + RelPath::new("lib/b"), + RelPath::new("lib/b/b.txt"), + RelPath::new("lib/b/lib"), ] ); }); @@ -236,18 +236,20 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::>(), vec![ - (Path::new(""), false), - (Path::new("deps"), false), - (Path::new("deps/dep-dir2"), true), - (Path::new("deps/dep-dir3"), true), - (Path::new("src"), false), - (Path::new("src/a.rs"), false), - (Path::new("src/b.rs"), false), + (RelPath::new(""), false), + (RelPath::new("deps"), false), + (RelPath::new("deps/dep-dir2"), true), + (RelPath::new("deps/dep-dir3"), true), + (RelPath::new("src"), false), + (RelPath::new("src/a.rs"), false), + (RelPath::new("src/b.rs"), false), ] ); assert_eq!( - tree.entry_for_path("deps/dep-dir2").unwrap().kind, + tree.entry_for_path(RelPath::new("deps/dep-dir2")) + .unwrap() + .kind, EntryKind::UnloadedDir ); }); @@ -256,7 +258,7 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { tree.read_with(cx, |tree, _| { tree.as_local() .unwrap() - .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()]) + .refresh_entries_for_paths(vec![RelPath::new("deps/dep-dir3").into()]) }) .recv() .await; @@ -269,24 +271,27 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::>(), vec![ - (Path::new(""), false), - (Path::new("deps"), false), - (Path::new("deps/dep-dir2"), true), - (Path::new("deps/dep-dir3"), true), - (Path::new("deps/dep-dir3/deps"), true), - (Path::new("deps/dep-dir3/src"), true), - (Path::new("src"), false), - (Path::new("src/a.rs"), false), - (Path::new("src/b.rs"), false), + (RelPath::new(""), false), + (RelPath::new("deps"), false), + (RelPath::new("deps/dep-dir2"), true), + (RelPath::new("deps/dep-dir3"), true), + (RelPath::new("deps/dep-dir3/deps"), true), + (RelPath::new("deps/dep-dir3/src"), true), + (RelPath::new("src"), false), + (RelPath::new("src/a.rs"), false), + (RelPath::new("src/b.rs"), false), ] ); }); assert_eq!( mem::take(&mut *tree_updates.lock()), &[ - (Path::new("deps/dep-dir3").into(), PathChange::Loaded), - (Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded), - (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded) + (RelPath::new("deps/dep-dir3").into(), PathChange::Loaded), + ( + RelPath::new("deps/dep-dir3/deps").into(), + PathChange::Loaded + ), + (RelPath::new("deps/dep-dir3/src").into(), PathChange::Loaded) ] ); @@ -294,7 +299,7 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { tree.read_with(cx, |tree, _| { tree.as_local() .unwrap() - .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()]) + .refresh_entries_for_paths(vec![RelPath::new("deps/dep-dir3/src").into()]) }) .recv() .await; @@ -306,17 +311,17 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::>(), vec![ - (Path::new(""), false), - (Path::new("deps"), false), - (Path::new("deps/dep-dir2"), true), - (Path::new("deps/dep-dir3"), true), - (Path::new("deps/dep-dir3/deps"), true), - (Path::new("deps/dep-dir3/src"), true), - (Path::new("deps/dep-dir3/src/e.rs"), true), - (Path::new("deps/dep-dir3/src/f.rs"), true), - (Path::new("src"), false), - (Path::new("src/a.rs"), false), - (Path::new("src/b.rs"), false), + (RelPath::new(""), false), + (RelPath::new("deps"), false), + (RelPath::new("deps/dep-dir2"), true), + (RelPath::new("deps/dep-dir3"), true), + (RelPath::new("deps/dep-dir3/deps"), true), + (RelPath::new("deps/dep-dir3/src"), true), + (RelPath::new("deps/dep-dir3/src/e.rs"), true), + (RelPath::new("deps/dep-dir3/src/f.rs"), true), + (RelPath::new("src"), false), + (RelPath::new("src/a.rs"), false), + (RelPath::new("src/b.rs"), false), ] ); }); @@ -324,13 +329,13 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { assert_eq!( mem::take(&mut *tree_updates.lock()), &[ - (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded), + (RelPath::new("deps/dep-dir3/src").into(), PathChange::Loaded), ( - Path::new("deps/dep-dir3/src/e.rs").into(), + RelPath::new("deps/dep-dir3/src/e.rs").into(), PathChange::Loaded ), ( - Path::new("deps/dep-dir3/src/f.rs").into(), + RelPath::new("deps/dep-dir3/src/f.rs").into(), PathChange::Loaded ) ] @@ -368,7 +373,7 @@ async fn test_renaming_case_only(cx: &mut TestAppContext) { tree.entries(true, 0) .map(|entry| entry.path.as_ref()) .collect::>(), - vec![Path::new(""), Path::new(OLD_NAME)] + vec![RelPath::new(""), RelPath::new(OLD_NAME)] ); }); @@ -390,7 +395,7 @@ async fn test_renaming_case_only(cx: &mut TestAppContext) { tree.entries(true, 0) .map(|entry| entry.path.as_ref()) .collect::>(), - vec![Path::new(""), Path::new(NEW_NAME)] + vec![RelPath::new(""), RelPath::new(NEW_NAME)] ); }); } @@ -446,13 +451,13 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { .map(|entry| (entry.path.as_ref(), entry.is_ignored)) .collect::>(), vec![ - (Path::new(""), false), - (Path::new(".gitignore"), false), - (Path::new("one"), false), - (Path::new("one/node_modules"), true), - (Path::new("two"), false), - (Path::new("two/x.js"), false), - (Path::new("two/y.js"), false), + (RelPath::new(""), false), + (RelPath::new(".gitignore"), false), + (RelPath::new("one"), false), + (RelPath::new("one/node_modules"), true), + (RelPath::new("two"), false), + (RelPath::new("two/x.js"), false), + (RelPath::new("two/y.js"), false), ] ); }); @@ -473,24 +478,24 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { .map(|entry| (entry.path.as_ref(), entry.is_ignored)) .collect::>(), vec![ - (Path::new(""), false), - (Path::new(".gitignore"), false), - (Path::new("one"), false), - (Path::new("one/node_modules"), true), - (Path::new("one/node_modules/a"), true), - (Path::new("one/node_modules/b"), true), - (Path::new("one/node_modules/b/b1.js"), true), - (Path::new("one/node_modules/b/b2.js"), true), - (Path::new("one/node_modules/c"), true), - (Path::new("two"), false), - (Path::new("two/x.js"), false), - (Path::new("two/y.js"), false), + (RelPath::new(""), false), + (RelPath::new(".gitignore"), false), + (RelPath::new("one"), false), + (RelPath::new("one/node_modules"), true), + (RelPath::new("one/node_modules/a"), true), + (RelPath::new("one/node_modules/b"), true), + (RelPath::new("one/node_modules/b/b1.js"), true), + (RelPath::new("one/node_modules/b/b2.js"), true), + (RelPath::new("one/node_modules/c"), true), + (RelPath::new("two"), false), + (RelPath::new("two/x.js"), false), + (RelPath::new("two/y.js"), false), ] ); assert_eq!( loaded.file.path.as_ref(), - Path::new("one/node_modules/b/b1.js") + RelPath::new("one/node_modules/b/b1.js") ); // Only the newly-expanded directories are scanned. @@ -513,26 +518,26 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { .map(|entry| (entry.path.as_ref(), entry.is_ignored)) .collect::>(), vec![ - (Path::new(""), false), - (Path::new(".gitignore"), false), - (Path::new("one"), false), - (Path::new("one/node_modules"), true), - (Path::new("one/node_modules/a"), true), - (Path::new("one/node_modules/a/a1.js"), true), - (Path::new("one/node_modules/a/a2.js"), true), - (Path::new("one/node_modules/b"), true), - (Path::new("one/node_modules/b/b1.js"), true), - (Path::new("one/node_modules/b/b2.js"), true), - (Path::new("one/node_modules/c"), true), - (Path::new("two"), false), - (Path::new("two/x.js"), false), - (Path::new("two/y.js"), false), + (RelPath::new(""), false), + (RelPath::new(".gitignore"), false), + (RelPath::new("one"), false), + (RelPath::new("one/node_modules"), true), + (RelPath::new("one/node_modules/a"), true), + (RelPath::new("one/node_modules/a/a1.js"), true), + (RelPath::new("one/node_modules/a/a2.js"), true), + (RelPath::new("one/node_modules/b"), true), + (RelPath::new("one/node_modules/b/b1.js"), true), + (RelPath::new("one/node_modules/b/b2.js"), true), + (RelPath::new("one/node_modules/c"), true), + (RelPath::new("two"), false), + (RelPath::new("two/x.js"), false), + (RelPath::new("two/y.js"), false), ] ); assert_eq!( loaded.file.path.as_ref(), - Path::new("one/node_modules/a/a2.js") + RelPath::new("one/node_modules/a/a2.js") ); // Only the newly-expanded directory is scanned. @@ -592,7 +597,7 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) { .await; let tree = Worktree::local( - Path::new("/root"), + RelPath::new("/root"), true, fs.clone(), Default::default(), @@ -610,7 +615,7 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) { tree.read_with(cx, |tree, _| { tree.as_local() .unwrap() - .refresh_entries_for_paths(vec![Path::new("node_modules/d/d.js").into()]) + .refresh_entries_for_paths(vec![RelPath::new("node_modules/d/d.js").into()]) }) .recv() .await; @@ -622,18 +627,18 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) { .map(|e| (e.path.as_ref(), e.is_ignored)) .collect::>(), &[ - (Path::new(""), false), - (Path::new(".gitignore"), false), - (Path::new("a"), false), - (Path::new("a/a.js"), false), - (Path::new("b"), false), - (Path::new("b/b.js"), false), - (Path::new("node_modules"), true), - (Path::new("node_modules/c"), true), - (Path::new("node_modules/d"), true), - (Path::new("node_modules/d/d.js"), true), - (Path::new("node_modules/d/e"), true), - (Path::new("node_modules/d/f"), true), + (RelPath::new(""), false), + (RelPath::new(".gitignore"), false), + (RelPath::new("a"), false), + (RelPath::new("a/a.js"), false), + (RelPath::new("b"), false), + (RelPath::new("b/b.js"), false), + (RelPath::new("node_modules"), true), + (RelPath::new("node_modules/c"), true), + (RelPath::new("node_modules/d"), true), + (RelPath::new("node_modules/d/d.js"), true), + (RelPath::new("node_modules/d/e"), true), + (RelPath::new("node_modules/d/f"), true), ] ); }); @@ -654,23 +659,23 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) { .map(|e| (e.path.as_ref(), e.is_ignored)) .collect::>(), &[ - (Path::new(""), false), - (Path::new(".gitignore"), false), - (Path::new("a"), false), - (Path::new("a/a.js"), false), - (Path::new("b"), false), - (Path::new("b/b.js"), false), + (RelPath::new(""), false), + (RelPath::new(".gitignore"), false), + (RelPath::new("a"), false), + (RelPath::new("a/a.js"), false), + (RelPath::new("b"), false), + (RelPath::new("b/b.js"), false), // This directory is no longer ignored - (Path::new("node_modules"), false), - (Path::new("node_modules/c"), false), - (Path::new("node_modules/c/c.js"), false), - (Path::new("node_modules/d"), false), - (Path::new("node_modules/d/d.js"), false), + (RelPath::new("node_modules"), false), + (RelPath::new("node_modules/c"), false), + (RelPath::new("node_modules/c/c.js"), false), + (RelPath::new("node_modules/d"), false), + (RelPath::new("node_modules/d/d.js"), false), // This subdirectory is now ignored - (Path::new("node_modules/d/e"), true), - (Path::new("node_modules/d/f"), false), - (Path::new("node_modules/d/f/f1.js"), false), - (Path::new("node_modules/d/f/f2.js"), false), + (RelPath::new("node_modules/d/e"), true), + (RelPath::new("node_modules/d/f"), false), + (RelPath::new("node_modules/d/f/f1.js"), false), + (RelPath::new("node_modules/d/f/f2.js"), false), ] ); }); @@ -711,7 +716,7 @@ async fn test_write_file(cx: &mut TestAppContext) { worktree .update(cx, |tree, cx| { tree.write_file( - Path::new("tracked-dir/file.txt"), + RelPath::new("tracked-dir/file.txt"), "hello".into(), Default::default(), cx, @@ -722,7 +727,7 @@ async fn test_write_file(cx: &mut TestAppContext) { worktree .update(cx, |tree, cx| { tree.write_file( - Path::new("ignored-dir/file.txt"), + RelPath::new("ignored-dir/file.txt"), "world".into(), Default::default(), cx, @@ -1421,7 +1426,7 @@ async fn test_random_worktree_operations_during_initial_scan( .map(|o| o.parse().unwrap()) .unwrap_or(20); - let root_dir = Path::new(path!("/test")); + let root_dir = RelPath::new(path!("/test")); let fs = FakeFs::new(cx.background_executor.clone()) as Arc; fs.as_fake().insert_tree(root_dir, json!({})).await; for _ in 0..initial_entries { @@ -1512,7 +1517,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) .map(|o| o.parse().unwrap()) .unwrap_or(20); - let root_dir = Path::new(path!("/test")); + let root_dir = RelPath::new(path!("/test")); let fs = FakeFs::new(cx.background_executor.clone()) as Arc; fs.as_fake().insert_tree(root_dir, json!({})).await; for _ in 0..initial_entries { @@ -1702,11 +1707,11 @@ fn randomly_mutate_worktree( let entry = snapshot.entries(false, 0).choose(rng).unwrap(); match rng.gen_range(0_u32..100) { - 0..=33 if entry.path.as_ref() != Path::new("") => { + 0..=33 if entry.path.as_ref() != RelPath::new("") => { log::info!("deleting entry {:?} ({})", entry.path, entry.id.0); worktree.delete_entry(entry.id, false, cx).unwrap() } - ..=66 if entry.path.as_ref() != Path::new("") => { + ..=66 if entry.path.as_ref() != RelPath::new("") => { let other_entry = snapshot.entries(false, 0).choose(rng).unwrap(); let new_parent_path = if other_entry.is_dir() { other_entry.path.clone() @@ -1759,7 +1764,7 @@ fn randomly_mutate_worktree( async fn randomly_mutate_fs( fs: &Arc, - root_path: &Path, + root_path: &RelPath, insertion_probability: f64, rng: &mut impl Rng, ) { @@ -1931,7 +1936,7 @@ async fn test_private_single_file_worktree(cx: &mut TestAppContext) { fs.insert_tree("/", json!({".env": "PRIVATE=secret\n"})) .await; let tree = Worktree::local( - Path::new("/.env"), + RelPath::new("/.env"), true, fs.clone(), Default::default(), @@ -1952,18 +1957,18 @@ fn test_unrelativize() { let work_directory = WorkDirectory::in_project(""); pretty_assertions::assert_eq!( work_directory.try_unrelativize(&"crates/gpui/gpui.rs".into()), - Some(Path::new("crates/gpui/gpui.rs").into()) + Some(RelPath::new("crates/gpui/gpui.rs").into()) ); let work_directory = WorkDirectory::in_project("vendor/some-submodule"); pretty_assertions::assert_eq!( work_directory.try_unrelativize(&"src/thing.c".into()), - Some(Path::new("vendor/some-submodule/src/thing.c").into()) + Some(RelPath::new("vendor/some-submodule/src/thing.c").into()) ); let work_directory = WorkDirectory::AboveProject { - absolute_path: Path::new("/projects/zed").into(), - location_in_repo: Path::new("crates/gpui").into(), + absolute_path: RelPath::new("/projects/zed").into(), + location_in_repo: RelPath::new("crates/gpui").into(), }; pretty_assertions::assert_eq!( @@ -1973,14 +1978,14 @@ fn test_unrelativize() { pretty_assertions::assert_eq!( work_directory.unrelativize(&"crates/util/util.rs".into()), - Path::new("../util/util.rs").into() + RelPath::new("../util/util.rs").into() ); pretty_assertions::assert_eq!(work_directory.try_unrelativize(&"README.md".into()), None,); pretty_assertions::assert_eq!( work_directory.unrelativize(&"README.md".into()), - Path::new("../../README.md").into() + RelPath::new("../../README.md").into() ); } @@ -2023,7 +2028,7 @@ async fn test_repository_above_root(executor: BackgroundExecutor, cx: &mut TestA .map(|entry| entry.work_directory_abs_path.clone()) .collect::>() }); - pretty_assertions::assert_eq!(repos, [Path::new(path!("/root")).into()]); + pretty_assertions::assert_eq!(repos, [RelPath::new(path!("/root")).into()]); eprintln!(">>>>>>>>>> touch"); fs.touch_path(path!("/root/subproject")).await; @@ -2043,7 +2048,7 @@ async fn test_repository_above_root(executor: BackgroundExecutor, cx: &mut TestA .map(|entry| entry.work_directory_abs_path.clone()) .collect::>() }); - pretty_assertions::assert_eq!(repos, [Path::new(path!("/root")).into()]); + pretty_assertions::assert_eq!(repos, [RelPath::new(path!("/root")).into()]); } #[track_caller]