@@ -19,7 +19,7 @@ use postage::{
use smol::{channel::Sender, Timer};
use std::{
cmp,
- collections::{BTreeMap, HashSet},
+ collections::{HashMap, HashSet},
ffi::{CStr, OsStr},
fmt, fs,
future::Future,
@@ -216,7 +216,7 @@ pub struct Snapshot {
scan_id: usize,
abs_path: Arc<Path>,
root_name_chars: Vec<char>,
- ignores: BTreeMap<u64, Arc<Gitignore>>,
+ ignores: HashMap<Arc<Path>, (Arc<Gitignore>, usize)>,
entries: SumTree<Entry>,
}
@@ -244,6 +244,10 @@ impl Snapshot {
FileIter::visible(self, start)
}
+ fn child_entries<'a>(&'a self, path: &'a Path) -> ChildEntriesIter<'a> {
+ ChildEntriesIter::new(path, self)
+ }
+
pub fn root_entry(&self) -> &Entry {
self.entry_for_path("").unwrap()
}
@@ -269,37 +273,16 @@ impl Snapshot {
self.entry_for_path(path.as_ref()).map(|e| e.inode())
}
- fn is_path_ignored(&self, path: &Path) -> Result<bool> {
- todo!();
- Ok(false)
- // let mut entry = self
- // .entry_for_path(path)
- // .ok_or_else(|| anyhow!("entry does not exist in worktree"))?;
-
- // if path.starts_with(".git") {
- // Ok(true)
- // } else {
- // while let Some(parent_entry) =
- // entry.path().parent().and_then(|p| self.entry_for_path(p))
- // {
- // let parent_path = parent_entry.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()) {
- // ::ignore::Match::Whitelist(_) => return Ok(false),
- // ::ignore::Match::Ignore(_) => return Ok(true),
- // ::ignore::Match::None => {}
- // }
- // }
- // entry = parent_entry;
- // }
- // Ok(false)
- // }
- }
-
fn insert_entry(&mut self, entry: Entry) {
if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
- self.insert_ignore_file(entry.path());
+ let (ignore, err) = Gitignore::new(self.abs_path.join(entry.path()));
+ if let Some(err) = err {
+ log::error!("error in ignore file {:?} - {:?}", entry.path(), err);
+ }
+
+ let ignore_dir_path = entry.path().parent().unwrap();
+ self.ignores
+ .insert(ignore_dir_path.into(), (Arc::new(ignore), self.scan_id));
}
self.entries.insert(entry);
}
@@ -312,9 +295,13 @@ impl Snapshot {
) {
let mut edits = Vec::new();
- let mut parent_entry = self.entries.get(&PathKey(parent_path)).unwrap().clone();
+ let mut parent_entry = self
+ .entries
+ .get(&PathKey(parent_path.clone()))
+ .unwrap()
+ .clone();
if let Some(ignore) = ignore {
- self.ignores.insert(parent_entry.inode, ignore);
+ self.ignores.insert(parent_path, (ignore, self.scan_id));
}
if matches!(parent_entry.kind, EntryKind::PendingDir) {
parent_entry.kind = EntryKind::Dir;
@@ -324,9 +311,6 @@ impl Snapshot {
edits.push(Edit::Insert(parent_entry));
for entry in entries {
- if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
- self.insert_ignore_file(entry.path());
- }
edits.push(Edit::Insert(entry));
}
self.entries.edit(edits);
@@ -342,22 +326,38 @@ impl Snapshot {
};
self.entries = new_entries;
- // if path.file_name() == Some(&GITIGNORE) {
- // if let Some((_, scan_id)) = self.ignores.get_mut(path.parent().unwrap()) {
- // *scan_id = self.scan_id;
- // }
- // }
+ if path.file_name() == Some(&GITIGNORE) {
+ if let Some((_, scan_id)) = self.ignores.get_mut(path.parent().unwrap()) {
+ *scan_id = self.scan_id;
+ }
+ }
}
- fn insert_ignore_file(&mut self, path: &Path) -> Arc<Gitignore> {
- let (ignore, err) = Gitignore::new(self.abs_path.join(path));
- if let Some(err) = err {
- log::error!("error in ignore file {:?} - {:?}", path, err);
+ fn ignore_stack_for_path(&self, path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
+ let mut new_ignores = Vec::new();
+ for ancestor in path.ancestors().skip(1) {
+ if let Some((ignore, _)) = self.ignores.get(ancestor) {
+ new_ignores.push((ancestor, Some(ignore.clone())));
+ } else {
+ new_ignores.push((ancestor, None));
+ }
+ }
+
+ let mut ignore_stack = IgnoreStack::none();
+ for (parent_path, ignore) in new_ignores.into_iter().rev() {
+ if ignore_stack.is_path_ignored(&parent_path, true) {
+ ignore_stack = IgnoreStack::all();
+ break;
+ } else if let Some(ignore) = ignore {
+ ignore_stack = ignore_stack.append(Arc::from(parent_path), ignore);
+ }
+ }
+
+ if ignore_stack.is_path_ignored(path, is_dir) {
+ ignore_stack = IgnoreStack::all();
}
- let ignore = Arc::new(ignore);
- let ignore_parent_inode = self.entry_for_path(path.parent().unwrap()).unwrap().inode;
- self.ignores.insert(ignore_parent_inode, ignore.clone());
- ignore
+
+ ignore_stack
}
}
@@ -417,14 +417,10 @@ impl Entry {
self.inode
}
- fn is_ignored(&self) -> bool {
+ pub fn is_ignored(&self) -> bool {
self.is_ignored
}
- fn set_ignored(&mut self, ignored: bool) {
- self.is_ignored = ignored;
- }
-
fn is_dir(&self) -> bool {
matches!(self.kind, EntryKind::Dir | EntryKind::PendingDir)
}
@@ -617,8 +613,6 @@ impl BackgroundScanner {
return;
}
- return;
-
event_stream.run(move |events| {
if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
return false;
@@ -687,8 +681,6 @@ impl BackgroundScanner {
});
}
- self.recompute_ignore_statuses();
-
Ok(())
}
@@ -816,15 +808,17 @@ impl BackgroundScanner {
snapshot.remove_path(&path);
match self.fs_entry_for_path(path.clone(), &abs_path) {
- Ok(Some(fs_entry)) => {
+ Ok(Some(mut fs_entry)) => {
let is_dir = fs_entry.is_dir();
+ let ignore_stack = snapshot.ignore_stack_for_path(&path, is_dir);
+ fs_entry.is_ignored = ignore_stack.is_all();
snapshot.insert_entry(fs_entry);
if is_dir {
scan_queue_tx
.send(ScanJob {
abs_path,
path,
- ignore_stack: todo!(),
+ ignore_stack,
scan_queue: scan_queue_tx.clone(),
})
.unwrap();
@@ -854,124 +848,95 @@ impl BackgroundScanner {
}
});
- self.recompute_ignore_statuses();
+ self.update_ignore_statuses();
true
}
- fn recompute_ignore_statuses(&self) {
- self.compute_ignore_status_for_new_ignores();
- self.compute_ignore_status_for_new_entries();
- }
-
- fn compute_ignore_status_for_new_ignores(&self) {
- // let mut snapshot = self.snapshot();
-
- // let mut ignores_to_delete = Vec::new();
- // let mut changed_ignore_parents = Vec::new();
- // for (parent_path, (_, scan_id)) in &snapshot.ignores {
- // let prev_ignore_parent = changed_ignore_parents.last();
- // if *scan_id == snapshot.scan_id
- // && prev_ignore_parent.map_or(true, |l| !parent_path.starts_with(l))
- // {
- // changed_ignore_parents.push(parent_path.clone());
- // }
-
- // let ignore_parent_exists = snapshot.entry_for_path(parent_path).is_some();
- // let ignore_exists = snapshot
- // .entry_for_path(parent_path.join(&*GITIGNORE))
- // .is_some();
- // if !ignore_parent_exists || !ignore_exists {
- // ignores_to_delete.push(parent_path.clone());
- // }
- // }
-
- // for parent_path in ignores_to_delete {
- // snapshot.ignores.remove(&parent_path);
- // self.snapshot.lock().ignores.remove(&parent_path);
- // }
-
- // let (entries_tx, entries_rx) = crossbeam_channel::unbounded();
- // self.thread_pool.scoped(|scope| {
- // let (edits_tx, edits_rx) = crossbeam_channel::unbounded();
- // scope.execute(move || {
- // let mut edits = Vec::new();
- // while let Ok(edit) = edits_rx.recv() {
- // edits.push(edit);
- // while let Ok(edit) = edits_rx.try_recv() {
- // edits.push(edit);
- // }
- // self.snapshot.lock().entries.edit(mem::take(&mut edits));
- // }
- // });
-
- // scope.execute(|| {
- // let entries_tx = entries_tx;
- // let mut cursor = snapshot.entries.cursor::<_, ()>();
- // for ignore_parent_path in &changed_ignore_parents {
- // cursor.seek(&PathSearch::Exact(ignore_parent_path), SeekBias::Right);
- // while let Some(entry) = cursor.item() {
- // if entry.path().starts_with(ignore_parent_path) {
- // entries_tx.send(entry.clone()).unwrap();
- // cursor.next();
- // } else {
- // break;
- // }
- // }
- // }
- // });
-
- // for _ in 0..self.thread_pool.thread_count() - 2 {
- // let edits_tx = edits_tx.clone();
- // scope.execute(|| {
- // let edits_tx = edits_tx;
- // while let Ok(mut entry) = entries_rx.recv() {
- // entry.set_ignored(snapshot.is_path_ignored(entry.path()).unwrap());
- // edits_tx.send(Edit::Insert(entry)).unwrap();
- // }
- // });
- // }
- // });
- }
-
- fn compute_ignore_status_for_new_entries(&self) {
- // let snapshot = self.snapshot.lock().clone();
-
- // let (entries_tx, entries_rx) = crossbeam_channel::unbounded();
- // self.thread_pool.scoped(|scope| {
- // let (edits_tx, edits_rx) = crossbeam_channel::unbounded();
- // scope.execute(move || {
- // let mut edits = Vec::new();
- // while let Ok(edit) = edits_rx.recv() {
- // edits.push(edit);
- // while let Ok(edit) = edits_rx.try_recv() {
- // edits.push(edit);
- // }
- // self.snapshot.lock().entries.edit(mem::take(&mut edits));
- // }
- // });
-
- // scope.execute(|| {
- // let entries_tx = entries_tx;
- // for entry in snapshot
- // .entries
- // .filter::<_, ()>(|e| e.recompute_ignore_status)
- // {
- // entries_tx.send(entry.clone()).unwrap();
- // }
- // });
-
- // for _ in 0..self.thread_pool.thread_count() - 2 {
- // let edits_tx = edits_tx.clone();
- // scope.execute(|| {
- // let edits_tx = edits_tx;
- // while let Ok(mut entry) = entries_rx.recv() {
- // entry.set_ignored(snapshot.is_path_ignored(entry.path()).unwrap());
- // edits_tx.send(Edit::Insert(entry)).unwrap();
- // }
- // });
- // }
- // });
+ fn update_ignore_statuses(&self) {
+ let mut snapshot = self.snapshot();
+
+ let mut ignores_to_update = Vec::new();
+ let mut ignores_to_delete = Vec::new();
+ for (parent_path, (_, scan_id)) in &snapshot.ignores {
+ if *scan_id == snapshot.scan_id && snapshot.entry_for_path(parent_path).is_some() {
+ ignores_to_update.push(parent_path.clone());
+ }
+
+ let ignore_path = parent_path.join(&*GITIGNORE);
+ if snapshot.entry_for_path(ignore_path).is_none() {
+ ignores_to_delete.push(parent_path.clone());
+ }
+ }
+
+ for parent_path in ignores_to_delete {
+ snapshot.ignores.remove(&parent_path);
+ self.snapshot.lock().ignores.remove(&parent_path);
+ }
+
+ let (ignore_queue_tx, ignore_queue_rx) = crossbeam_channel::unbounded();
+ ignores_to_update.sort_unstable();
+ let mut ignores_to_update = ignores_to_update.into_iter().peekable();
+ while let Some(parent_path) = ignores_to_update.next() {
+ while ignores_to_update
+ .peek()
+ .map_or(false, |p| p.starts_with(&parent_path))
+ {
+ ignores_to_update.next().unwrap();
+ }
+
+ let ignore_stack = snapshot.ignore_stack_for_path(&parent_path, true);
+ ignore_queue_tx
+ .send(UpdateIgnoreStatusJob {
+ path: parent_path,
+ ignore_stack,
+ ignore_queue: ignore_queue_tx.clone(),
+ })
+ .unwrap();
+ }
+ drop(ignore_queue_tx);
+
+ self.thread_pool.scoped(|scope| {
+ for _ in 0..self.thread_pool.thread_count() {
+ scope.execute(|| {
+ while let Ok(job) = ignore_queue_rx.recv() {
+ self.update_ignore_status(job, &snapshot);
+ }
+ });
+ }
+ });
+ }
+
+ fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &Snapshot) {
+ let mut ignore_stack = job.ignore_stack;
+ if let Some((ignore, _)) = snapshot.ignores.get(&job.path) {
+ ignore_stack = ignore_stack.append(job.path.clone(), ignore.clone());
+ }
+
+ let mut edits = Vec::new();
+ for mut entry in snapshot.child_entries(&job.path).cloned() {
+ let was_ignored = entry.is_ignored;
+ entry.is_ignored = ignore_stack.is_path_ignored(entry.path(), entry.is_dir());
+ if entry.is_dir() {
+ let child_ignore_stack = if entry.is_ignored {
+ IgnoreStack::all()
+ } else {
+ ignore_stack.clone()
+ };
+ job.ignore_queue
+ .send(UpdateIgnoreStatusJob {
+ path: entry.path().clone(),
+ ignore_stack: child_ignore_stack,
+ ignore_queue: job.ignore_queue.clone(),
+ })
+ .unwrap();
+ }
+
+ if entry.is_ignored != was_ignored {
+ edits.push(Edit::Insert(entry));
+ }
+ }
+ self.snapshot.lock().entries.edit(edits);
}
fn fs_entry_for_path(&self, path: Arc<Path>, abs_path: &Path) -> Result<Option<Entry>> {
@@ -1020,6 +985,12 @@ struct ScanJob {
scan_queue: crossbeam_channel::Sender<ScanJob>,
}
+struct UpdateIgnoreStatusJob {
+ path: Arc<Path>,
+ ignore_stack: Arc<IgnoreStack>,
+ ignore_queue: crossbeam_channel::Sender<UpdateIgnoreStatusJob>,
+}
+
pub trait WorktreeHandle {
fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Result<FileHandle>;
}
@@ -1088,6 +1059,40 @@ impl<'a> Iterator for FileIter<'a> {
}
}
+struct ChildEntriesIter<'a> {
+ parent_path: &'a Path,
+ cursor: Cursor<'a, Entry, PathSearch<'a>, ()>,
+}
+
+impl<'a> ChildEntriesIter<'a> {
+ fn new(parent_path: &'a Path, snapshot: &'a Snapshot) -> Self {
+ let mut cursor = snapshot.entries.cursor();
+ cursor.seek(&PathSearch::Exact(parent_path), SeekBias::Right);
+ Self {
+ parent_path,
+ cursor,
+ }
+ }
+}
+
+impl<'a> Iterator for ChildEntriesIter<'a> {
+ type Item = &'a Entry;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(item) = self.cursor.item() {
+ if item.path().starts_with(self.parent_path) {
+ self.cursor
+ .seek_forward(&PathSearch::Successor(item.path()), SeekBias::Left);
+ Some(item)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+}
+
fn mounted_volume_paths() -> Vec<PathBuf> {
unsafe {
let mut stat_ptr: *mut libc::statfs = std::ptr::null_mut();
@@ -1260,16 +1265,6 @@ 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();
@@ -1278,8 +1273,6 @@ mod tests {
assert_eq!(ignored.is_ignored(), true);
});
- return;
-
fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap();
fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap();
app.read(|ctx| tree.read(ctx).next_scan_complete()).await;
@@ -1540,12 +1533,29 @@ mod tests {
assert!(files.next().is_none());
assert!(visible_files.next().is_none());
- // for (ignore_parent_path, _) in &self.ignores {
- // assert!(self.entry_for_path(ignore_parent_path).is_some());
- // assert!(self
- // .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
- // .is_some());
- // }
+ let mut bfs_paths = Vec::new();
+ let mut stack = vec![Path::new("")];
+ while let Some(path) = stack.pop() {
+ bfs_paths.push(path);
+ let ix = stack.len();
+ for child_entry in self.child_entries(path) {
+ stack.insert(ix, child_entry.path());
+ }
+ }
+
+ let dfs_paths = self
+ .entries
+ .cursor::<(), ()>()
+ .map(|e| e.path().as_ref())
+ .collect::<Vec<_>>();
+ assert_eq!(bfs_paths, dfs_paths);
+
+ for (ignore_parent_path, _) in &self.ignores {
+ assert!(self.entry_for_path(ignore_parent_path).is_some());
+ assert!(self
+ .entry_for_path(ignore_parent_path.join(&*GITIGNORE))
+ .is_some());
+ }
}
fn to_vec(&self) -> Vec<(&Path, u64, bool)> {
@@ -21,8 +21,11 @@ impl IgnoreStack {
Arc::new(Self::All)
}
+ pub fn is_all(&self) -> bool {
+ matches!(self, IgnoreStack::All)
+ }
+
pub fn append(self: Arc<Self>, base: Arc<Path>, ignore: Arc<Gitignore>) -> Arc<Self> {
- log::info!("appending ignore {:?}", base);
match self.as_ref() {
IgnoreStack::All => self,
_ => Arc::new(Self::Some {
@@ -34,33 +37,18 @@ impl IgnoreStack {
}
pub fn is_path_ignored(&self, path: &Path, is_dir: bool) -> bool {
- println!("is_path_ignored? {:?} {}", path, is_dir);
match self {
- Self::None => {
- println!("none case");
- false
- }
- Self::All => {
- println!("all case");
- true
- }
+ Self::None => false,
+ Self::All => true,
Self::Some {
base,
ignore,
parent: prev,
- } => {
- println!(
- "some case {:?} {:?}",
- base,
- path.strip_prefix(base).unwrap()
- );
-
- match ignore.matched(path.strip_prefix(base).unwrap(), is_dir) {
- ignore::Match::None => prev.is_path_ignored(path, is_dir),
- ignore::Match::Ignore(_) => true,
- ignore::Match::Whitelist(_) => false,
- }
- }
+ } => match ignore.matched(path.strip_prefix(base).unwrap(), is_dir) {
+ ignore::Match::None => prev.is_path_ignored(path, is_dir),
+ ignore::Match::Ignore(_) => true,
+ ignore::Match::Whitelist(_) => false,
+ },
}
}
}