Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Nathan Sobo 2021-04-23 12:47:23 -06:00
parent c9d7249305
commit dced9469f5
4 changed files with 113 additions and 80 deletions

View file

@ -199,6 +199,9 @@ where
}
pub fn next(&mut self) {
if !self.did_seek {
self.descend_to_first_item(self.tree, |_| true)
}
self.next_internal(|_| true)
}

View file

@ -114,7 +114,7 @@ impl Workspace {
pub fn contains_path(&self, path: &Path, app: &AppContext) -> bool {
self.worktrees
.iter()
.any(|worktree| worktree.read(app).contains_path(path))
.any(|worktree| worktree.read(app).contains_abs_path(path))
}
pub fn open_paths(&mut self, paths: &[PathBuf], ctx: &mut ModelContext<Self>) {
@ -125,7 +125,7 @@ impl Workspace {
pub fn open_path<'a>(&'a mut self, path: PathBuf, ctx: &mut ModelContext<Self>) {
for tree in self.worktrees.iter() {
if tree.read(ctx).contains_path(&path) {
if tree.read(ctx).contains_abs_path(&path) {
return;
}
}

View file

@ -64,12 +64,12 @@ impl Worktree {
let snapshot = Snapshot {
id,
scan_id: 0,
path: path.into(),
abs_path: path.into(),
ignores: Default::default(),
entries: Default::default(),
};
let (event_stream, event_stream_handle) =
fsevent::EventStream::new(&[snapshot.path.as_ref()], Duration::from_millis(100));
fsevent::EventStream::new(&[snapshot.abs_path.as_ref()], Duration::from_millis(100));
let background_snapshot = Arc::new(Mutex::new(snapshot.clone()));
@ -148,18 +148,18 @@ impl Worktree {
self.snapshot.clone()
}
pub fn contains_path(&self, path: &Path) -> bool {
path.starts_with(&self.snapshot.path)
pub fn contains_abs_path(&self, path: &Path) -> bool {
path.starts_with(&self.snapshot.abs_path)
}
pub fn load_history(
&self,
relative_path: &Path,
path: &Path,
ctx: &AppContext,
) -> impl Future<Output = Result<History>> {
let path = self.snapshot.path.join(relative_path);
let abs_path = self.snapshot.abs_path.join(path);
ctx.background_executor().spawn(async move {
let mut file = std::fs::File::open(&path)?;
let mut file = std::fs::File::open(&abs_path)?;
let mut base_text = String::new();
file.read_to_string(&mut base_text)?;
Ok(History::new(Arc::from(base_text)))
@ -168,14 +168,14 @@ impl Worktree {
pub fn save<'a>(
&self,
relative_path: &Path,
path: &Path,
content: BufferSnapshot,
ctx: &AppContext,
) -> Task<Result<()>> {
let path = self.snapshot.path.join(relative_path);
let abs_path = self.snapshot.abs_path.join(path);
ctx.background_executor().spawn(async move {
let buffer_size = content.text_summary().bytes.min(10 * 1024);
let file = std::fs::File::create(&path)?;
let file = std::fs::File::create(&abs_path)?;
let mut writer = std::io::BufWriter::with_capacity(buffer_size, file);
for chunk in content.fragments() {
writer.write(chunk.as_bytes())?;
@ -208,7 +208,7 @@ impl fmt::Debug for Worktree {
pub struct Snapshot {
id: usize,
scan_id: usize,
path: Arc<Path>,
abs_path: Arc<Path>,
ignores: BTreeMap<Arc<Path>, (Arc<Gitignore>, usize)>,
entries: SumTree<Entry>,
}
@ -226,16 +226,23 @@ impl Snapshot {
FileIter::all(self, start)
}
#[cfg(test)]
pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
let mut cursor = self.entries.cursor::<(), ()>();
cursor.next();
cursor.map(|entry| entry.path())
}
pub fn visible_files(&self, start: usize) -> FileIter {
FileIter::visible(self, start)
}
pub fn root_entry(&self) -> Entry {
self.entry_for_path(&self.path).unwrap()
self.entry_for_path(&self.abs_path).unwrap()
}
pub fn root_name(&self) -> Option<&OsStr> {
self.path.file_name()
self.abs_path.file_name()
}
fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<Entry> {
@ -252,6 +259,8 @@ impl Snapshot {
}
fn is_path_ignored(&self, path: &Path) -> Result<bool> {
dbg!(path);
let mut entry = self
.entry_for_path(path)
.ok_or_else(|| anyhow!("entry does not exist in worktree"))?;
@ -263,6 +272,7 @@ impl Snapshot {
entry.path().parent().and_then(|p| self.entry_for_path(p))
{
let parent_path = parent_entry.path();
dbg!(parent_path);
if let Some((ignore, _)) = self.ignores.get(parent_path) {
let relative_path = path.strip_prefix(parent_path).unwrap();
match ignore.matched_path_or_any_parents(relative_path, entry.is_dir()) {
@ -322,8 +332,7 @@ impl Snapshot {
}
fn insert_ignore_file(&mut self, path: &Path) {
let root_path = self.path.parent().unwrap_or(Path::new(""));
let (ignore, err) = Gitignore::new(root_path.join(path));
let (ignore, err) = Gitignore::new(self.abs_path.join(path));
if let Some(err) = err {
log::error!("error in ignore file {:?} - {:?}", path, err);
}
@ -573,7 +582,7 @@ impl BackgroundScanner {
}
fn update_other_mount_paths(&mut self) {
let path = self.snapshot.lock().path.clone();
let path = self.snapshot.lock().abs_path.clone();
self.other_mount_paths.clear();
self.other_mount_paths.extend(
mounted_volume_paths()
@ -582,8 +591,8 @@ impl BackgroundScanner {
);
}
fn path(&self) -> Arc<Path> {
self.snapshot.lock().path.clone()
fn abs_path(&self) -> Arc<Path> {
self.snapshot.lock().abs_path.clone()
}
fn snapshot(&self) -> Snapshot {
@ -625,16 +634,15 @@ impl BackgroundScanner {
fn scan_dirs(&self) -> io::Result<()> {
self.snapshot.lock().scan_id += 1;
let path = self.path();
let metadata = fs::metadata(&path)?;
let path: Arc<Path> = Arc::from(Path::new(""));
let abs_path = self.abs_path();
let metadata = fs::metadata(&abs_path)?;
let inode = metadata.ino();
let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink();
let name: Arc<OsStr> = path.file_name().unwrap_or(OsStr::new("/")).into();
let relative_path: Arc<Path> = Arc::from((*name).as_ref());
let is_symlink = fs::symlink_metadata(&abs_path)?.file_type().is_symlink();
if metadata.file_type().is_dir() {
let dir_entry = Entry::Dir {
path: relative_path.clone(),
path: path.clone(),
inode,
is_symlink,
pending: true,
@ -645,8 +653,8 @@ impl BackgroundScanner {
let (tx, rx) = crossbeam_channel::unbounded();
tx.send(ScanJob {
path: path.to_path_buf(),
relative_path,
abs_path: abs_path.to_path_buf(),
path,
scan_queue: tx.clone(),
})
.unwrap();
@ -657,7 +665,7 @@ impl BackgroundScanner {
pool.execute(|| {
while let Ok(job) = rx.recv() {
if let Err(err) = self.scan_dir(&job) {
log::error!("error scanning {:?}: {}", job.path, err);
log::error!("error scanning {:?}: {}", job.abs_path, err);
}
}
});
@ -665,8 +673,8 @@ impl BackgroundScanner {
});
} else {
self.snapshot.lock().insert_entry(Entry::File {
path_entry: PathEntry::new(inode, relative_path.clone()),
path: relative_path,
path_entry: PathEntry::new(inode, path.clone()),
path,
inode,
is_symlink,
is_ignored: None,
@ -682,37 +690,37 @@ impl BackgroundScanner {
let mut new_entries = Vec::new();
let mut new_jobs = Vec::new();
for child_entry in fs::read_dir(&job.path)? {
for child_entry in fs::read_dir(&job.abs_path)? {
let child_entry = child_entry?;
let child_name: Arc<OsStr> = child_entry.file_name().into();
let child_relative_path: Arc<Path> = job.relative_path.join(child_name.as_ref()).into();
let child_name = child_entry.file_name();
let child_abs_path = job.abs_path.join(&child_name);
let child_path: Arc<Path> = job.path.join(&child_name).into();
let child_metadata = child_entry.metadata()?;
let child_inode = child_metadata.ino();
let child_is_symlink = child_metadata.file_type().is_symlink();
let child_path = job.path.join(child_name.as_ref());
// Disallow mount points outside the file system containing the root of this worktree
if self.other_mount_paths.contains(&child_path) {
if self.other_mount_paths.contains(&child_abs_path) {
continue;
}
if child_metadata.is_dir() {
new_entries.push(Entry::Dir {
path: child_relative_path.clone(),
path: child_path.clone(),
inode: child_inode,
is_symlink: child_is_symlink,
pending: true,
is_ignored: None,
});
new_jobs.push(ScanJob {
abs_path: child_abs_path,
path: child_path,
relative_path: child_relative_path,
scan_queue: job.scan_queue.clone(),
});
} else {
new_entries.push(Entry::File {
path_entry: PathEntry::new(child_inode, child_relative_path.clone()),
path: child_relative_path,
path_entry: PathEntry::new(child_inode, child_path.clone()),
path: child_path,
inode: child_inode,
is_symlink: child_is_symlink,
is_ignored: None,
@ -722,7 +730,7 @@ impl BackgroundScanner {
self.snapshot
.lock()
.populate_dir(job.relative_path.clone(), new_entries);
.populate_dir(job.path.clone(), new_entries);
for new_job in new_jobs {
job.scan_queue.send(new_job).unwrap();
}
@ -736,40 +744,44 @@ impl BackgroundScanner {
let mut snapshot = self.snapshot();
snapshot.scan_id += 1;
let root_path = if let Ok(path) = snapshot.path.canonicalize() {
path
let root_abs_path = if let Ok(abs_path) = snapshot.abs_path.canonicalize() {
abs_path
} else {
return false;
};
events.sort_unstable_by(|a, b| a.path.cmp(&b.path));
let mut paths = events.into_iter().map(|e| e.path).peekable();
let mut abs_paths = events.into_iter().map(|e| e.path).peekable();
let (scan_queue_tx, scan_queue_rx) = crossbeam_channel::unbounded();
while let Some(path) = paths.next() {
let relative_path =
match path.strip_prefix(&root_path.parent().unwrap_or(Path::new(""))) {
Ok(relative_path) => relative_path.to_path_buf(),
Err(_) => {
log::error!("unexpected event {:?} for root path {:?}", path, root_path);
continue;
}
};
while paths.peek().map_or(false, |p| p.starts_with(&path)) {
paths.next();
while let Some(abs_path) = abs_paths.next() {
let path = match abs_path.strip_prefix(&root_abs_path) {
Ok(path) => Arc::from(path.to_path_buf()),
Err(_) => {
log::error!(
"unexpected event {:?} for root path {:?}",
abs_path,
root_abs_path
);
continue;
}
};
while abs_paths.peek().map_or(false, |p| p.starts_with(&abs_path)) {
abs_paths.next();
}
snapshot.remove_path(&relative_path);
snapshot.remove_path(&path);
match self.fs_entry_for_path(&root_path, &path) {
match self.fs_entry_for_path(path.clone(), &abs_path) {
Ok(Some(fs_entry)) => {
let is_dir = fs_entry.is_dir();
snapshot.insert_entry(fs_entry);
if is_dir {
scan_queue_tx
.send(ScanJob {
abs_path,
path,
relative_path: relative_path.into(),
scan_queue: scan_queue_tx.clone(),
})
.unwrap();
@ -792,7 +804,7 @@ impl BackgroundScanner {
pool.execute(|| {
while let Ok(job) = scan_queue_rx.recv() {
if let Err(err) = self.scan_dir(&job) {
log::error!("error scanning {:?}: {}", job.path, err);
log::error!("error scanning {:?}: {}", job.abs_path, err);
}
}
});
@ -919,8 +931,8 @@ impl BackgroundScanner {
});
}
fn fs_entry_for_path(&self, root_path: &Path, path: &Path) -> Result<Option<Entry>> {
let metadata = match fs::metadata(&path) {
fn fs_entry_for_path(&self, path: Arc<Path>, abs_path: &Path) -> Result<Option<Entry>> {
let metadata = match fs::metadata(&abs_path) {
Err(err) => {
return match (err.kind(), err.raw_os_error()) {
(io::ErrorKind::NotFound, _) => Ok(None),
@ -930,20 +942,15 @@ impl BackgroundScanner {
}
Ok(metadata) => metadata,
};
let inode = metadata.ino();
let is_symlink = fs::symlink_metadata(&path)
let is_symlink = fs::symlink_metadata(&abs_path)
.context("failed to read symlink metadata")?
.file_type()
.is_symlink();
let relative_path_with_root = root_path
.parent()
.map_or(path, |parent| path.strip_prefix(parent).unwrap())
.into();
let entry = if metadata.file_type().is_dir() {
Entry::Dir {
path: relative_path_with_root,
path,
inode,
is_symlink,
pending: true,
@ -951,8 +958,8 @@ impl BackgroundScanner {
}
} else {
Entry::File {
path_entry: PathEntry::new(inode, relative_path_with_root.clone()),
path: relative_path_with_root,
path_entry: PathEntry::new(inode, path.clone()),
path,
inode,
is_symlink,
is_ignored: None,
@ -964,8 +971,8 @@ impl BackgroundScanner {
}
struct ScanJob {
path: PathBuf,
relative_path: Arc<Path>,
abs_path: PathBuf,
path: Arc<Path>,
scan_queue: crossbeam_channel::Sender<ScanJob>,
}
@ -1149,7 +1156,7 @@ mod tests {
}
#[test]
fn test_rescan() {
fn test_rescan_simple() {
App::test_async((), |mut app| async move {
let dir = temp_tree(json!({
"a": {
@ -1173,10 +1180,23 @@ mod tests {
});
std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
tree.condition(&app, move |_, _| {
file2.path().as_ref() == Path::new("d/file2")
})
.await;
app.read(|ctx| tree.read(ctx).next_scan_complete()).await;
app.read(|ctx| {
assert_eq!(
tree.read(ctx)
.paths()
.map(|p| p.to_str().unwrap())
.collect::<Vec<_>>(),
vec!["a", "a/file1", "b", "d", "d/file2"]
)
});
// tree.condition(&app, move |_, _| {
// file2.path().as_ref() == Path::new("d/file2")
// })
// .await;
});
}
@ -1196,6 +1216,16 @@ mod tests {
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
app.read(|ctx| tree.read(ctx).scan_complete()).await;
app.read(|ctx| {
let paths = tree
.read(ctx)
.paths()
.map(|p| p.to_str().unwrap())
.collect::<Vec<_>>();
println!("paths {:?}", paths);
});
app.read(|ctx| {
let tree = tree.read(ctx);
let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap();
@ -1255,7 +1285,7 @@ mod tests {
Arc::new(Mutex::new(Snapshot {
id: 0,
scan_id: 0,
path: root_dir.path().into(),
abs_path: root_dir.path().into(),
entries: Default::default(),
ignores: Default::default(),
})),
@ -1288,7 +1318,7 @@ mod tests {
Arc::new(Mutex::new(Snapshot {
id: 0,
scan_id: 0,
path: root_dir.path().into(),
abs_path: root_dir.path().into(),
entries: Default::default(),
ignores: Default::default(),
})),

View file

@ -523,7 +523,7 @@ mod tests {
&Snapshot {
id: 0,
scan_id: 0,
path: PathBuf::new().into(),
abs_path: PathBuf::new().into(),
ignores: Default::default(),
entries: Default::default(),
},