Fix expansion of ancestor directories when refreshing a path
This commit is contained in:
+8
-1
@@ -388,6 +388,7 @@ struct FakeFsState {
|
||||
event_txs: Vec<smol::channel::Sender<Vec<fsevent::Event>>>,
|
||||
events_paused: bool,
|
||||
buffered_events: Vec<fsevent::Event>,
|
||||
read_dir_call_count: usize,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -536,6 +537,7 @@ impl FakeFs {
|
||||
event_txs: Default::default(),
|
||||
buffered_events: Vec::new(),
|
||||
events_paused: false,
|
||||
read_dir_call_count: 0,
|
||||
}),
|
||||
})
|
||||
}
|
||||
@@ -772,6 +774,10 @@ impl FakeFs {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn read_dir_call_count(&self) -> usize {
|
||||
self.state.lock().read_dir_call_count
|
||||
}
|
||||
|
||||
async fn simulate_random_delay(&self) {
|
||||
self.executor
|
||||
.upgrade()
|
||||
@@ -1146,7 +1152,8 @@ impl Fs for FakeFs {
|
||||
) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
|
||||
self.simulate_random_delay().await;
|
||||
let path = normalize_path(path);
|
||||
let state = self.state.lock();
|
||||
let mut state = self.state.lock();
|
||||
state.read_dir_call_count += 1;
|
||||
let entry = state.read_path(&path)?;
|
||||
let mut entry = entry.lock();
|
||||
let children = entry.dir_entries(&path)?;
|
||||
|
||||
+129
-134
@@ -84,15 +84,9 @@ pub struct LocalWorktree {
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
enum ScanRequest {
|
||||
RescanPaths {
|
||||
relative_paths: Vec<Arc<Path>>,
|
||||
done: barrier::Sender,
|
||||
},
|
||||
ExpandPath {
|
||||
path: Arc<Path>,
|
||||
done: barrier::Sender,
|
||||
},
|
||||
struct ScanRequest {
|
||||
relative_paths: Vec<Arc<Path>>,
|
||||
done: barrier::Sender,
|
||||
}
|
||||
|
||||
pub struct RemoteWorktree {
|
||||
@@ -226,6 +220,7 @@ pub struct LocalSnapshot {
|
||||
struct BackgroundScannerState {
|
||||
snapshot: LocalSnapshot,
|
||||
expanded_dirs: HashSet<ProjectEntryId>,
|
||||
expanded_paths: HashSet<Arc<Path>>,
|
||||
/// The ids of all of the entries that were removed from the snapshot
|
||||
/// as part of the current update. These entry ids may be re-used
|
||||
/// if the same inode is discovered at a new path, or if the given
|
||||
@@ -882,14 +877,11 @@ impl LocalWorktree {
|
||||
let path = Arc::from(path);
|
||||
let abs_path = self.absolutize(&path);
|
||||
let fs = self.fs.clone();
|
||||
let expand = path
|
||||
.parent()
|
||||
.map(|path| self.expand_entry_for_path(path, cx));
|
||||
let entry = self.refresh_entry(path.clone(), None, cx);
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Some(mut expand) = expand {
|
||||
expand.recv().await;
|
||||
}
|
||||
cx.spawn(|this, cx| async move {
|
||||
let text = fs.load(&abs_path).await?;
|
||||
let entry = entry.await?;
|
||||
|
||||
let mut index_task = None;
|
||||
let snapshot = this.read_with(&cx, |this, _| this.as_local().unwrap().snapshot());
|
||||
@@ -904,21 +896,12 @@ impl LocalWorktree {
|
||||
}
|
||||
}
|
||||
|
||||
let text = fs.load(&abs_path).await?;
|
||||
|
||||
let diff_base = if let Some(index_task) = index_task {
|
||||
index_task.await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Eagerly populate the snapshot with an updated entry for the loaded file
|
||||
let entry = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.as_local().unwrap().refresh_entry(path, None, cx)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok((
|
||||
File {
|
||||
entry_id: entry.id,
|
||||
@@ -1082,7 +1065,7 @@ impl LocalWorktree {
|
||||
this.as_local_mut()
|
||||
.unwrap()
|
||||
.scan_requests_tx
|
||||
.try_send(ScanRequest::RescanPaths {
|
||||
.try_send(ScanRequest {
|
||||
relative_paths: vec![path],
|
||||
done: tx,
|
||||
})
|
||||
@@ -1159,8 +1142,8 @@ impl LocalWorktree {
|
||||
let (tx, rx) = barrier::channel();
|
||||
if let Some(entry) = self.entry_for_id(entry_id) {
|
||||
self.scan_requests_tx
|
||||
.try_send(ScanRequest::ExpandPath {
|
||||
path: entry.path.clone(),
|
||||
.try_send(ScanRequest {
|
||||
relative_paths: vec![entry.path.clone()],
|
||||
done: tx,
|
||||
})
|
||||
.ok();
|
||||
@@ -1168,15 +1151,11 @@ impl LocalWorktree {
|
||||
rx
|
||||
}
|
||||
|
||||
pub fn expand_entry_for_path(
|
||||
&self,
|
||||
path: impl Into<Arc<Path>>,
|
||||
_cx: &mut ModelContext<Worktree>,
|
||||
) -> barrier::Receiver {
|
||||
pub fn refresh_entries_for_paths(&self, paths: Vec<Arc<Path>>) -> barrier::Receiver {
|
||||
let (tx, rx) = barrier::channel();
|
||||
self.scan_requests_tx
|
||||
.try_send(ScanRequest::ExpandPath {
|
||||
path: path.into(),
|
||||
.try_send(ScanRequest {
|
||||
relative_paths: paths,
|
||||
done: tx,
|
||||
})
|
||||
.ok();
|
||||
@@ -1189,20 +1168,14 @@ impl LocalWorktree {
|
||||
old_path: Option<Arc<Path>>,
|
||||
cx: &mut ModelContext<Worktree>,
|
||||
) -> Task<Result<Entry>> {
|
||||
let path_changes_tx = self.scan_requests_tx.clone();
|
||||
let paths = if let Some(old_path) = old_path.as_ref() {
|
||||
vec![old_path.clone(), path.clone()]
|
||||
} else {
|
||||
vec![path.clone()]
|
||||
};
|
||||
let mut refresh = self.refresh_entries_for_paths(paths);
|
||||
cx.spawn_weak(move |this, mut cx| async move {
|
||||
let relative_paths = if let Some(old_path) = old_path.as_ref() {
|
||||
vec![old_path.clone(), path.clone()]
|
||||
} else {
|
||||
vec![path.clone()]
|
||||
};
|
||||
|
||||
let (tx, mut rx) = barrier::channel();
|
||||
path_changes_tx.try_send(ScanRequest::RescanPaths {
|
||||
relative_paths,
|
||||
done: tx,
|
||||
})?;
|
||||
rx.recv().await;
|
||||
refresh.recv().await;
|
||||
this.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("worktree was dropped"))?
|
||||
.update(&mut cx, |this, _| {
|
||||
@@ -2138,9 +2111,14 @@ impl LocalSnapshot {
|
||||
|
||||
ignore_stack
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalSnapshot {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn expanded_entries(&self) -> impl Iterator<Item = &Entry> {
|
||||
self.entries_by_path
|
||||
.cursor::<()>()
|
||||
.filter(|entry| entry.kind == EntryKind::Dir && (entry.is_external || entry.is_ignored))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn check_invariants(&self) {
|
||||
assert_eq!(
|
||||
@@ -2217,6 +2195,14 @@ impl LocalSnapshot {
|
||||
}
|
||||
|
||||
impl BackgroundScannerState {
|
||||
fn is_path_expanded(&self, path: &Path) -> bool {
|
||||
self.expanded_paths.iter().any(|p| p.starts_with(path))
|
||||
|| self
|
||||
.snapshot
|
||||
.entry_for_path(&path)
|
||||
.map_or(true, |entry| self.expanded_dirs.contains(&entry.id))
|
||||
}
|
||||
|
||||
fn reuse_entry_id(&mut self, entry: &mut Entry) {
|
||||
if let Some(removed_entry_id) = self.removed_entry_ids.remove(&entry.inode) {
|
||||
entry.id = removed_entry_id;
|
||||
@@ -2271,6 +2257,7 @@ impl BackgroundScannerState {
|
||||
.insert(abs_parent_path, (ignore, false));
|
||||
}
|
||||
|
||||
self.expanded_dirs.insert(parent_entry.id);
|
||||
let mut entries_by_path_edits = vec![Edit::Insert(parent_entry)];
|
||||
let mut entries_by_id_edits = Vec::new();
|
||||
let mut dotgit_path = None;
|
||||
@@ -2883,6 +2870,7 @@ impl BackgroundScanner {
|
||||
expanded_dirs: Default::default(),
|
||||
removed_entry_ids: Default::default(),
|
||||
changed_paths: Default::default(),
|
||||
expanded_paths: Default::default(),
|
||||
}),
|
||||
phase: BackgroundScannerPhase::InitialScan,
|
||||
}
|
||||
@@ -2986,7 +2974,7 @@ impl BackgroundScanner {
|
||||
}
|
||||
|
||||
async fn process_scan_request(&self, request: ScanRequest) -> bool {
|
||||
let root_path = self.state.lock().snapshot.abs_path.clone();
|
||||
let root_path = self.expand_paths(&request.relative_paths).await;
|
||||
let root_canonical_path = match self.fs.canonicalize(&root_path).await {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
@@ -2995,57 +2983,20 @@ impl BackgroundScanner {
|
||||
}
|
||||
};
|
||||
|
||||
match request {
|
||||
ScanRequest::RescanPaths {
|
||||
relative_paths,
|
||||
done,
|
||||
} => {
|
||||
let abs_paths = relative_paths
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
if path.file_name().is_some() {
|
||||
root_canonical_path.join(path)
|
||||
} else {
|
||||
root_canonical_path.clone()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.reload_entries_for_paths(
|
||||
root_path,
|
||||
root_canonical_path,
|
||||
abs_paths,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
self.send_status_update(false, Some(done))
|
||||
}
|
||||
ScanRequest::ExpandPath { path, done } => {
|
||||
let (scan_job_tx, mut scan_job_rx) = channel::unbounded();
|
||||
let mut abs_path = root_canonical_path.clone();
|
||||
for component in path.iter() {
|
||||
abs_path.push(component);
|
||||
self.reload_entries_for_paths(
|
||||
root_path.clone(),
|
||||
root_canonical_path.clone(),
|
||||
vec![abs_path.clone()],
|
||||
true,
|
||||
Some(scan_job_tx.clone()),
|
||||
)
|
||||
.await;
|
||||
let abs_paths = request
|
||||
.relative_paths
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
if path.file_name().is_some() {
|
||||
root_canonical_path.join(path)
|
||||
} else {
|
||||
root_canonical_path.clone()
|
||||
}
|
||||
drop(scan_job_tx);
|
||||
|
||||
// Scan the expanded directories serially. This is ok, because only
|
||||
// the direct ancestors of the expanded path need to be scanned.
|
||||
while let Some(job) = scan_job_rx.next().await {
|
||||
self.scan_dir(&job).await.log_err();
|
||||
}
|
||||
|
||||
self.send_status_update(false, Some(done));
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.reload_entries_for_paths(root_path, root_canonical_path, abs_paths, None)
|
||||
.await;
|
||||
self.send_status_update(false, Some(request.done))
|
||||
}
|
||||
|
||||
async fn process_events(&mut self, abs_paths: Vec<PathBuf>) {
|
||||
@@ -3060,15 +3011,8 @@ impl BackgroundScanner {
|
||||
|
||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||
let paths = self
|
||||
.reload_entries_for_paths(
|
||||
root_path,
|
||||
root_canonical_path,
|
||||
abs_paths,
|
||||
false,
|
||||
Some(scan_job_tx.clone()),
|
||||
)
|
||||
.reload_entries_for_paths(root_path, root_canonical_path, abs_paths, Some(scan_job_tx))
|
||||
.await;
|
||||
drop(scan_job_tx);
|
||||
self.scan_dirs(false, scan_job_rx).await;
|
||||
|
||||
self.update_ignore_statuses().await;
|
||||
@@ -3108,6 +3052,46 @@ impl BackgroundScanner {
|
||||
self.send_status_update(false, None);
|
||||
}
|
||||
|
||||
async fn expand_paths(&self, paths: &[Arc<Path>]) -> Arc<Path> {
|
||||
let root_path;
|
||||
let (scan_job_tx, mut scan_job_rx) = channel::unbounded();
|
||||
{
|
||||
let mut state = self.state.lock();
|
||||
root_path = state.snapshot.abs_path.clone();
|
||||
for path in paths {
|
||||
for ancestor in path.ancestors() {
|
||||
if let Some(entry) = state.snapshot.entry_for_path(ancestor) {
|
||||
if entry.kind == EntryKind::PendingDir {
|
||||
let abs_path = root_path.join(ancestor);
|
||||
let ignore_stack =
|
||||
state.snapshot.ignore_stack_for_abs_path(&abs_path, true);
|
||||
let ancestor_inodes =
|
||||
state.snapshot.ancestor_inodes_for_path(&ancestor);
|
||||
scan_job_tx
|
||||
.try_send(ScanJob {
|
||||
abs_path: abs_path.into(),
|
||||
path: ancestor.into(),
|
||||
ignore_stack,
|
||||
scan_queue: scan_job_tx.clone(),
|
||||
ancestor_inodes,
|
||||
is_external: entry.is_external,
|
||||
})
|
||||
.unwrap();
|
||||
state.expanded_paths.insert(path.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(scan_job_tx);
|
||||
}
|
||||
while let Some(job) = scan_job_rx.next().await {
|
||||
self.scan_dir(&job).await.log_err();
|
||||
}
|
||||
self.state.lock().expanded_paths.clear();
|
||||
root_path
|
||||
}
|
||||
|
||||
async fn scan_dirs(
|
||||
&self,
|
||||
enable_progress_updates: bool,
|
||||
@@ -3376,10 +3360,7 @@ impl BackgroundScanner {
|
||||
// If a subdirectory is ignored, or is external to the worktree, don't scan
|
||||
// it unless it is marked as expanded.
|
||||
if (new_job.is_external || new_job.ignore_stack.is_all())
|
||||
&& !state
|
||||
.snapshot
|
||||
.entry_for_path(&new_job.path)
|
||||
.map_or(true, |entry| state.expanded_dirs.contains(&entry.id))
|
||||
&& !state.is_path_expanded(&new_job.path)
|
||||
{
|
||||
log::debug!("defer scanning directory {:?}", new_job.path);
|
||||
continue;
|
||||
@@ -3399,12 +3380,40 @@ impl BackgroundScanner {
|
||||
root_abs_path: Arc<Path>,
|
||||
root_canonical_path: PathBuf,
|
||||
mut abs_paths: Vec<PathBuf>,
|
||||
expand: bool,
|
||||
scan_queue_tx: Option<Sender<ScanJob>>,
|
||||
) -> Option<Vec<Arc<Path>>> {
|
||||
let doing_recursive_update = scan_queue_tx.is_some();
|
||||
let mut event_paths = Vec::<Arc<Path>>::with_capacity(abs_paths.len());
|
||||
abs_paths.sort_unstable();
|
||||
abs_paths.dedup_by(|a, b| a.starts_with(&b));
|
||||
{
|
||||
let state = self.state.lock();
|
||||
abs_paths.retain(|abs_path| {
|
||||
let path = if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
|
||||
path
|
||||
} else {
|
||||
log::error!(
|
||||
"unexpected event {:?} for root path {:?}",
|
||||
abs_path,
|
||||
root_canonical_path
|
||||
);
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
if state
|
||||
.snapshot
|
||||
.entry_for_path(parent)
|
||||
.map_or(true, |entry| entry.kind != EntryKind::Dir)
|
||||
{
|
||||
log::info!("ignoring event within unloaded directory {:?}", parent);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
event_paths.push(path.into());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
let metadata = futures::future::join_all(
|
||||
abs_paths
|
||||
@@ -3424,6 +3433,7 @@ impl BackgroundScanner {
|
||||
|
||||
let mut state = self.state.lock();
|
||||
let snapshot = &mut state.snapshot;
|
||||
let doing_recursive_update = scan_queue_tx.is_some();
|
||||
let is_idle = snapshot.completed_scan_id == snapshot.scan_id;
|
||||
snapshot.scan_id += 1;
|
||||
if is_idle && !doing_recursive_update {
|
||||
@@ -3433,25 +3443,13 @@ impl BackgroundScanner {
|
||||
// Remove any entries for paths that no longer exist or are being recursively
|
||||
// refreshed. Do this before adding any new entries, so that renames can be
|
||||
// detected regardless of the order of the paths.
|
||||
let mut event_paths = Vec::<Arc<Path>>::with_capacity(abs_paths.len());
|
||||
let mut event_metadata = Vec::<_>::with_capacity(abs_paths.len());
|
||||
for (abs_path, metadata) in abs_paths.iter().zip(metadata.iter()) {
|
||||
if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) {
|
||||
if matches!(metadata, Ok(None)) || doing_recursive_update {
|
||||
state.remove_path(path);
|
||||
}
|
||||
event_paths.push(path.into());
|
||||
event_metadata.push(metadata);
|
||||
} else {
|
||||
log::error!(
|
||||
"unexpected event {:?} for root path {:?}",
|
||||
abs_path,
|
||||
root_canonical_path
|
||||
);
|
||||
for (path, metadata) in event_paths.iter().zip(metadata.iter()) {
|
||||
if matches!(metadata, Ok(None)) || doing_recursive_update {
|
||||
state.remove_path(path);
|
||||
}
|
||||
}
|
||||
|
||||
for (path, metadata) in event_paths.iter().cloned().zip(event_metadata.into_iter()) {
|
||||
for (path, metadata) in event_paths.iter().cloned().zip(metadata.into_iter()) {
|
||||
let abs_path: Arc<Path> = root_abs_path.join(&path).into();
|
||||
|
||||
match metadata {
|
||||
@@ -3487,9 +3485,6 @@ impl BackgroundScanner {
|
||||
}
|
||||
|
||||
let fs_entry = state.insert_entry(fs_entry, self.fs.as_ref());
|
||||
if expand {
|
||||
state.expanded_dirs.insert(fs_entry.id);
|
||||
}
|
||||
|
||||
if let Some(scan_queue_tx) = &scan_queue_tx {
|
||||
let mut ancestor_inodes = state.snapshot.ancestor_inodes_for_path(&path);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
worktree::{Event, Snapshot, WorktreeHandle},
|
||||
EntryKind, PathChange, Worktree,
|
||||
Entry, EntryKind, PathChange, Worktree,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use client::Client;
|
||||
@@ -158,9 +158,10 @@ async fn test_descendent_entries(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
// Expand gitignored directory.
|
||||
tree.update(cx, |tree, cx| {
|
||||
let tree = tree.as_local_mut().unwrap();
|
||||
tree.expand_entry_for_path("i/j".as_ref(), cx)
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![Path::new("i/j").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
@@ -336,12 +337,18 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
|
||||
(Path::new("src/b.rs"), false),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
tree.entry_for_path("deps/dep-dir2").unwrap().kind,
|
||||
EntryKind::PendingDir
|
||||
);
|
||||
});
|
||||
|
||||
// Expand one of the symlinked directories.
|
||||
tree.update(cx, |tree, cx| {
|
||||
let tree = tree.as_local_mut().unwrap();
|
||||
tree.expand_entry_for_path("deps/dep-dir3".as_ref(), cx)
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
@@ -368,9 +375,10 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
|
||||
});
|
||||
|
||||
// Expand a subdirectory of one of the symlinked directories.
|
||||
tree.update(cx, |tree, cx| {
|
||||
let tree = tree.as_local_mut().unwrap();
|
||||
tree.expand_entry_for_path("deps/dep-dir3/src".as_ref(), cx)
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
@@ -405,17 +413,19 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
|
||||
"/root",
|
||||
json!({
|
||||
".gitignore": "node_modules\n",
|
||||
"node_modules": {
|
||||
"a": {
|
||||
"a1.js": "a1",
|
||||
"a2.js": "a2",
|
||||
},
|
||||
"b": {
|
||||
"b1.js": "b1",
|
||||
"b2.js": "b2",
|
||||
"one": {
|
||||
"node_modules": {
|
||||
"a": {
|
||||
"a1.js": "a1",
|
||||
"a2.js": "a2",
|
||||
},
|
||||
"b": {
|
||||
"b1.js": "b1",
|
||||
"b2.js": "b2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"src": {
|
||||
"two": {
|
||||
"x.js": "",
|
||||
"y.js": "",
|
||||
},
|
||||
@@ -446,19 +456,23 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
|
||||
vec![
|
||||
(Path::new(""), false),
|
||||
(Path::new(".gitignore"), false),
|
||||
(Path::new("node_modules"), true),
|
||||
(Path::new("src"), false),
|
||||
(Path::new("src/x.js"), false),
|
||||
(Path::new("src/y.js"), 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),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// Open a file that is nested inside of a gitignored directory that
|
||||
// has not yet been expanded.
|
||||
let prev_read_dir_count = fs.read_dir_call_count();
|
||||
let buffer = tree
|
||||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.load_buffer(0, "node_modules/b/b1.js".as_ref(), cx)
|
||||
.load_buffer(0, "one/node_modules/b/b1.js".as_ref(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -471,22 +485,68 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) {
|
||||
vec![
|
||||
(Path::new(""), false),
|
||||
(Path::new(".gitignore"), false),
|
||||
(Path::new("node_modules"), true),
|
||||
(Path::new("node_modules/a"), true),
|
||||
(Path::new("node_modules/b"), true),
|
||||
(Path::new("node_modules/b/b1.js"), true),
|
||||
(Path::new("node_modules/b/b2.js"), true),
|
||||
(Path::new("src"), false),
|
||||
(Path::new("src/x.js"), false),
|
||||
(Path::new("src/y.js"), 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("two"), false),
|
||||
(Path::new("two/x.js"), false),
|
||||
(Path::new("two/y.js"), false),
|
||||
]
|
||||
);
|
||||
|
||||
let buffer = buffer.read(cx);
|
||||
assert_eq!(
|
||||
buffer.file().unwrap().path().as_ref(),
|
||||
Path::new("node_modules/b/b1.js")
|
||||
buffer.read(cx).file().unwrap().path().as_ref(),
|
||||
Path::new("one/node_modules/b/b1.js")
|
||||
);
|
||||
|
||||
// Only the newly-expanded directories are scanned.
|
||||
assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 2);
|
||||
});
|
||||
|
||||
// Open another file in a different subdirectory of the same
|
||||
// gitignored directory.
|
||||
let prev_read_dir_count = fs.read_dir_call_count();
|
||||
let buffer = tree
|
||||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.load_buffer(0, "one/node_modules/a/a2.js".as_ref(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tree.read_with(cx, |tree, cx| {
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.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("two"), false),
|
||||
(Path::new("two/x.js"), false),
|
||||
(Path::new("two/y.js"), false),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
buffer.read(cx).file().unwrap().path().as_ref(),
|
||||
Path::new("one/node_modules/a/a2.js")
|
||||
);
|
||||
|
||||
// Only the newly-expanded directory is scanned.
|
||||
assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -525,9 +585,10 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
|
||||
tree.update(cx, |tree, cx| {
|
||||
let tree = tree.as_local_mut().unwrap();
|
||||
tree.expand_entry_for_path("ignored-dir".as_ref(), cx)
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![Path::new("ignored-dir").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
@@ -868,8 +929,13 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
|
||||
log::info!("quiescing");
|
||||
fs.as_fake().flush_events(usize::MAX);
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
|
||||
snapshot.check_invariants();
|
||||
let expanded_paths = snapshot
|
||||
.expanded_entries()
|
||||
.map(|e| e.path.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
{
|
||||
let new_worktree = Worktree::local(
|
||||
@@ -885,6 +951,14 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
|
||||
new_worktree
|
||||
.update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
|
||||
.await;
|
||||
new_worktree
|
||||
.update(cx, |tree, _| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(expanded_paths)
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
let new_snapshot =
|
||||
new_worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
|
||||
assert_eq!(
|
||||
@@ -901,11 +975,25 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
prev_snapshot.entries(true).collect::<Vec<_>>(),
|
||||
snapshot.entries(true).collect::<Vec<_>>(),
|
||||
prev_snapshot
|
||||
.entries(true)
|
||||
.map(ignore_pending_dir)
|
||||
.collect::<Vec<_>>(),
|
||||
snapshot
|
||||
.entries(true)
|
||||
.map(ignore_pending_dir)
|
||||
.collect::<Vec<_>>(),
|
||||
"wrong updates after snapshot {i}: {updates:#?}",
|
||||
);
|
||||
}
|
||||
|
||||
fn ignore_pending_dir(entry: &Entry) -> Entry {
|
||||
let mut entry = entry.clone();
|
||||
if entry.kind == EntryKind::PendingDir {
|
||||
entry.kind = EntryKind::Dir
|
||||
}
|
||||
entry
|
||||
}
|
||||
}
|
||||
|
||||
// The worktree's `UpdatedEntries` event can be used to follow along with
|
||||
|
||||
Reference in New Issue
Block a user