Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Julia Ryan
cee726f86b
wip 2025-08-13 20:58:59 -07:00
Max Brunsfeld
d8b791d3a6 Start work on introducing RelPath type 2025-08-11 19:25:24 -07:00
13 changed files with 452 additions and 285 deletions

1
Cargo.lock generated
View file

@ -12871,6 +12871,7 @@ dependencies = [
"prost-build 0.9.0",
"serde",
"typed-path",
"util",
"workspace-hack",
]

View file

@ -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();

View file

@ -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<String>,
) -> Result<Self> {
@ -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<String> {
let mut child = util::command::new_smol_command(git_binary)

Binary file not shown.

View file

@ -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<Option<String>> {
// 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> {
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<RepoPath> =
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<Path>);
pub struct RepoPath(pub Arc<RelPath>);
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<Arc<Path>> for RepoPath {
fn from(value: Arc<Path>) -> Self {
impl From<Arc<RelPath>> for RepoPath {
fn from(value: Arc<RelPath>) -> Self {
RepoPath(value)
}
}
impl From<PathBuf> 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<Path> for RepoPath {
fn as_ref(&self) -> &Path {
impl AsRef<RelPath> 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<Path> for RepoPath {
fn borrow(&self) -> &Path {
impl Borrow<RelPath> 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<RepoPath> for RepoPathDescendants<'_> {
fn cmp_cursor(&self, key: &RepoPath) -> Ordering {
@ -2080,35 +2059,6 @@ fn parse_upstream_track(upstream_track: &str) -> Result<UpstreamTracking> {
}))
}
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<String, String> {
HashMap::from_iter([
("GIT_AUTHOR_NAME".to_string(), "Zed".to_string()),

View file

@ -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::<Vec<_>>();

View file

@ -1663,7 +1663,7 @@ impl GitStore {
.payload
.paths
.into_iter()
.map(PathBuf::from)
.map(RelPath::new)
.map(RepoPath::new)
.collect();

View file

@ -20,6 +20,7 @@ doctest = false
anyhow.workspace = true
prost.workspace = true
serde.workspace = true
util.workspace = true
workspace-hack.workspace = true
[build-dependencies]

View file

@ -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<Path> {
}
}
impl FromProto for Arc<RelPath> {
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)

206
crates/util/src/rel_path.rs Normal file
View file

@ -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<S: AsRef<[u8]> + ?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<RelPath> {
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<RelPath> 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<Self::Item> {
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<Self::Item> {
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);
}
}

View file

@ -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;

View file

@ -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>,
path: Arc<RelPath>,
done: SmallVec<[barrier::Sender; 1]>,
}
struct ScanRequest {
relative_paths: Vec<Arc<Path>>,
relative_paths: Vec<Arc<RelPath>>,
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<Path>,
relative_path: Arc<RelPath>,
},
AboveProject {
absolute_path: Arc<Path>,
@ -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<Path>) -> bool {
pub fn directory_contains(&self, path: impl AsRef<RelPath>) -> 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<RepoPath> {
pub fn relativize(&self, path: &RelPath) -> Result<RepoPath> {
// 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<Arc<Path>>,
path: impl Into<Arc<RelPath>>,
is_directory: bool,
content: Option<Vec<u8>>,
cx: &Context<Worktree>,
) -> Task<Result<CreatedEntry>> {
let path: Arc<Path> = path.into();
let path: Arc<RelPath> = 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<ProjectEntryId>) {
fn get_children_ids_recursive(&self, path: &RelPath, ids: &mut Vec<ProjectEntryId>) {
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<Arc<Path>>,
path: impl Into<Arc<RelPath>>,
is_dir: bool,
content: Option<Vec<u8>>,
cx: &Context<Worktree>,
@ -1975,7 +1974,7 @@ impl LocalWorktree {
}))
}
fn refresh_entries_for_paths(&self, paths: Vec<Arc<Path>>) -> barrier::Receiver {
fn refresh_entries_for_paths(&self, paths: Vec<Arc<RelPath>>) -> 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<Arc<Path>>) -> barrier::Receiver {
pub fn manually_refresh_entries_for_paths(
&self,
paths: Vec<Arc<RelPath>>,
) -> barrier::Receiver {
self.refresh_entries_for_paths(paths)
}
pub fn add_path_prefix_to_scan(&self, path_prefix: Arc<Path>) -> barrier::Receiver {
pub fn add_path_prefix_to_scan(&self, path_prefix: Arc<RelPath>) -> 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<Path>,
old_path: Option<Arc<Path>>,
path: Arc<RelPath>,
old_path: Option<Arc<RelPath>>,
cx: &Context<Worktree>,
) -> Task<Result<Option<Entry>>> {
if self.settings.is_path_excluded(&path) {
@ -2403,18 +2405,8 @@ impl Snapshot {
}
}
pub fn absolutize(&self, path: &Path) -> Result<PathBuf> {
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<PathBuf> {
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<Item = &Arc<Path>> {
let empty_path = Path::new("");
pub fn paths(&self) -> impl Iterator<Item = &Arc<RelPath>> {
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<Path>) -> Option<&Entry> {
pub fn entry_for_path(&self, path: impl AsRef<RelPath>) -> 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<Path>,
pub path: Arc<RelPath>,
pub inode: u64,
pub mtime: Option<MTime>,
@ -3510,7 +3501,7 @@ pub struct UpdatedGitRepository {
pub common_dir_abs_path: Option<Arc<Path>>,
}
pub type UpdatedEntriesSet = Arc<[(Arc<Path>, ProjectEntryId, PathChange)]>;
pub type UpdatedEntriesSet = Arc<[(Arc<RelPath>, 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<Path>);
pub struct PathKey(pub Arc<RelPath>);
impl Default for PathKey {
fn default() -> Self {
@ -5188,7 +5179,7 @@ impl WorktreeModelHandle for Entity<Worktree> {
#[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))
}

View file

@ -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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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<_>>(),
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::<Vec<_>>(),
&[
(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::<Vec<_>>(),
&[
(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<dyn Fs>;
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<dyn Fs>;
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<dyn Fs>,
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::<Vec<_>>()
});
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::<Vec<_>>()
});
pretty_assertions::assert_eq!(repos, [Path::new(path!("/root")).into()]);
pretty_assertions::assert_eq!(repos, [RelPath::new(path!("/root")).into()]);
}
#[track_caller]