Revert "fs: Replace a bunch of uses of smol::fs with manual impls" (#40170)

Reverts zed-industries/zed#39906

This PR should not have landed prior to Wednesday.
This commit is contained in:
Piotr Osiewicz 2025-10-14 13:04:40 +02:00 committed by GitHub
parent 9ad6196150
commit da583e5943
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 184 additions and 336 deletions

30
Cargo.lock generated
View file

@ -1137,7 +1137,7 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50"
dependencies = [
"async-lock 3.4.1",
"async-lock",
"blocking",
"futures-lite 2.6.0",
]
@ -1151,7 +1151,7 @@ dependencies = [
"async-channel 2.3.1",
"async-executor",
"async-io",
"async-lock 3.4.1",
"async-lock",
"blocking",
"futures-lite 2.6.0",
"once_cell",
@ -1163,7 +1163,7 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
dependencies = [
"async-lock 3.4.1",
"async-lock",
"cfg-if",
"concurrent-queue",
"futures-io",
@ -1175,15 +1175,6 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "async-lock"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener 2.5.3",
]
[[package]]
name = "async-lock"
version = "3.4.1"
@ -1223,7 +1214,7 @@ checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
dependencies = [
"async-channel 2.3.1",
"async-io",
"async-lock 3.4.1",
"async-lock",
"async-signal",
"async-task",
"blocking",
@ -1252,7 +1243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
dependencies = [
"async-io",
"async-lock 3.4.1",
"async-lock",
"atomic-waker",
"cfg-if",
"futures-core",
@ -1273,7 +1264,7 @@ dependencies = [
"async-channel 1.9.0",
"async-global-executor",
"async-io",
"async-lock 3.4.1",
"async-lock",
"async-process",
"crossbeam-utils",
"futures-channel",
@ -9045,7 +9036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]
@ -10745,7 +10736,7 @@ dependencies = [
"ashpd 0.12.0",
"async-fs",
"async-io",
"async-lock 3.4.1",
"async-lock",
"blocking",
"cbc",
"cipher",
@ -14832,7 +14823,7 @@ dependencies = [
"async-executor",
"async-fs",
"async-io",
"async-lock 3.4.1",
"async-lock",
"async-net",
"async-process",
"blocking",
@ -19877,7 +19868,6 @@ name = "worktree"
version = "0.1.0"
dependencies = [
"anyhow",
"async-lock 2.8.0",
"clock",
"collections",
"fs",
@ -20173,7 +20163,7 @@ dependencies = [
"async-broadcast",
"async-executor",
"async-io",
"async-lock 3.4.1",
"async-lock",
"async-process",
"async-recursion",
"async-task",

View file

@ -455,7 +455,6 @@ async-compat = "0.2.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = "0.1"
async-fs = "2.1"
async-lock = "2.1"
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
async-recursion = "1.0.0"
async-tar = "0.5.0"

View file

@ -12509,7 +12509,6 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
)
.await;
cx.run_until_parked();
// Set up a buffer white some trailing whitespace and no trailing newline.
cx.set_state(
&[

View file

@ -7,7 +7,6 @@ pub mod fs_watcher;
use anyhow::{Context as _, Result, anyhow};
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
use ashpd::desktop::trash;
use futures::stream::iter;
use gpui::App;
use gpui::BackgroundExecutor;
use gpui::Global;
@ -563,17 +562,12 @@ impl Fs for RealFs {
async fn load(&self, path: &Path) -> Result<String> {
let path = path.to_path_buf();
self.executor
.spawn(async move { Ok(std::fs::read_to_string(path)?) })
.await
let text = smol::unblock(|| std::fs::read_to_string(path)).await?;
Ok(text)
}
async fn load_bytes(&self, path: &Path) -> Result<Vec<u8>> {
let path = path.to_path_buf();
let bytes = self
.executor
.spawn(async move { std::fs::read(path) })
.await?;
let bytes = smol::unblock(|| std::fs::read(path)).await?;
Ok(bytes)
}
@ -641,46 +635,30 @@ impl Fs for RealFs {
if let Some(path) = path.parent() {
self.create_dir(path).await?;
}
let path = path.to_owned();
let contents = content.to_owned();
self.executor
.spawn(async move {
std::fs::write(path, contents)?;
Ok(())
})
.await
smol::fs::write(path, content).await?;
Ok(())
}
async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
let path = path.to_owned();
self.executor
.spawn(async move {
std::fs::canonicalize(&path).with_context(|| format!("canonicalizing {path:?}"))
})
Ok(smol::fs::canonicalize(path)
.await
.with_context(|| format!("canonicalizing {path:?}"))?)
}
async fn is_file(&self, path: &Path) -> bool {
let path = path.to_owned();
self.executor
.spawn(async move { std::fs::metadata(path).is_ok_and(|metadata| metadata.is_file()) })
smol::fs::metadata(path)
.await
.is_ok_and(|metadata| metadata.is_file())
}
async fn is_dir(&self, path: &Path) -> bool {
let path = path.to_owned();
self.executor
.spawn(async move { std::fs::metadata(path).is_ok_and(|metadata| metadata.is_dir()) })
smol::fs::metadata(path)
.await
.is_ok_and(|metadata| metadata.is_dir())
}
async fn metadata(&self, path: &Path) -> Result<Option<Metadata>> {
let path_buf = path.to_owned();
let symlink_metadata = match self
.executor
.spawn(async move { std::fs::symlink_metadata(&path_buf) })
.await
{
let symlink_metadata = match smol::fs::symlink_metadata(path).await {
Ok(metadata) => metadata,
Err(err) => {
return match (err.kind(), err.raw_os_error()) {
@ -692,23 +670,17 @@ impl Fs for RealFs {
};
let path_buf = path.to_path_buf();
let path_exists = self
.executor
.spawn(async move {
path_buf
.try_exists()
.with_context(|| format!("checking existence for path {path_buf:?}"))
})
.await?;
let path_exists = smol::unblock(move || {
path_buf
.try_exists()
.with_context(|| format!("checking existence for path {path_buf:?}"))
})
.await?;
let is_symlink = symlink_metadata.file_type().is_symlink();
let metadata = match (is_symlink, path_exists) {
(true, true) => {
let path_buf = path.to_path_buf();
self.executor
.spawn(async move { std::fs::metadata(path_buf) })
.await
.with_context(|| "accessing symlink for path {path}")?
}
(true, true) => smol::fs::metadata(path)
.await
.with_context(|| "accessing symlink for path {path}")?,
_ => symlink_metadata,
};
@ -743,13 +715,7 @@ impl Fs for RealFs {
&self,
path: &Path,
) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>> {
let path = path.to_owned();
let result = iter(
self.executor
.spawn(async move { std::fs::read_dir(path) })
.await?,
)
.map(|entry| match entry {
let result = smol::fs::read_dir(path).await?.map(|entry| match entry {
Ok(entry) => Ok(entry.path()),
Err(error) => Err(anyhow!("failed to read dir entry {error:?}")),
});

View file

@ -9,10 +9,6 @@ license = "GPL-3.0-or-later"
path = "src/worktree.rs"
doctest = false
[[bin]]
name = "bench_background_scan"
path = "bin/bench_background_scan.rs"
[lints]
workspace = true
@ -28,7 +24,6 @@ test-support = [
[dependencies]
anyhow.workspace = true
async-lock.workspace = true
clock.workspace = true
collections.workspace = true
fs.workspace = true

View file

@ -1,47 +0,0 @@
use std::{
path::Path,
sync::{Arc, atomic::AtomicUsize},
};
use fs::RealFs;
use gpui::Application;
use settings::Settings;
use worktree::{Worktree, WorktreeSettings};
fn main() {
let Some(worktree_root_path) = std::env::args().nth(1) else {
println!(
"Missing path to worktree root\nUsage: bench_background_scan PATH_TO_WORKTREE_ROOT"
);
return;
};
let app = Application::headless();
app.run(|cx| {
settings::init(cx);
WorktreeSettings::register(cx);
let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
cx.spawn(async move |cx| {
let worktree = Worktree::local(
Path::new(&worktree_root_path),
true,
fs,
Arc::new(AtomicUsize::new(0)),
cx,
)
.await
.expect("Worktree initialization to succeed");
let did_finish_scan = worktree
.update(cx, |this, _| this.as_local().unwrap().scan_complete())
.unwrap();
let start = std::time::Instant::now();
did_finish_scan.await;
println!("{:?}", start.elapsed());
cx.update(|cx| {
cx.quit();
})
})
.detach();
})
}

View file

@ -64,7 +64,7 @@ use std::{
use sum_tree::{Bias, Dimensions, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet};
use text::{LineEnding, Rope};
use util::{
ResultExt, debug_panic, maybe,
ResultExt, debug_panic,
paths::{PathMatcher, PathStyle, SanitizedPath, home_dir},
rel_path::RelPath,
};
@ -226,7 +226,7 @@ impl Default for WorkDirectory {
}
}
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct LocalSnapshot {
snapshot: Snapshot,
global_gitignore: Option<Arc<Gitignore>>,
@ -239,7 +239,6 @@ pub struct LocalSnapshot {
/// The file handle of the worktree root. `None` if the worktree is a directory.
/// (so we can find it after it's been moved)
root_file_handle: Option<Arc<dyn fs::FileHandle>>,
executor: BackgroundExecutor,
}
struct BackgroundScannerState {
@ -322,6 +321,7 @@ impl DerefMut for LocalSnapshot {
}
}
#[derive(Debug)]
enum ScanState {
Started,
Updated {
@ -402,7 +402,6 @@ impl Worktree {
PathStyle::local(),
),
root_file_handle,
executor: cx.background_executor().clone(),
};
let worktree_id = snapshot.id();
@ -1070,7 +1069,7 @@ impl LocalWorktree {
scan_requests_rx,
path_prefixes_to_scan_rx,
next_entry_id,
state: async_lock::Mutex::new(BackgroundScannerState {
state: Mutex::new(BackgroundScannerState {
prev_snapshot: snapshot.snapshot.clone(),
snapshot,
scanned_dirs: Default::default(),
@ -2442,7 +2441,7 @@ impl LocalSnapshot {
fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> Entry {
if entry.is_file() && entry.path.file_name() == Some(&GITIGNORE) {
let abs_path = self.absolutize(&entry.path);
match self.executor.block(build_gitignore(&abs_path, fs)) {
match smol::block_on(build_gitignore(&abs_path, fs)) {
Ok(ignore) => {
self.ignores_by_parent_abs_path
.insert(abs_path.parent().unwrap().into(), (Arc::new(ignore), true));
@ -2493,12 +2492,7 @@ impl LocalSnapshot {
inodes
}
async fn ignore_stack_for_abs_path(
&self,
abs_path: &Path,
is_dir: bool,
fs: &dyn Fs,
) -> IgnoreStack {
fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool, fs: &dyn Fs) -> IgnoreStack {
let mut new_ignores = Vec::new();
let mut repo_root = None;
for (index, ancestor) in abs_path.ancestors().enumerate() {
@ -2509,8 +2503,9 @@ impl LocalSnapshot {
new_ignores.push((ancestor, None));
}
}
let metadata = fs.metadata(&ancestor.join(DOT_GIT)).await.ok().flatten();
let metadata = smol::block_on(fs.metadata(&ancestor.join(DOT_GIT)))
.ok()
.flatten();
if metadata.is_some() {
repo_root = Some(Arc::from(ancestor));
break;
@ -2656,7 +2651,7 @@ impl BackgroundScannerState {
.any(|p| entry.path.starts_with(p))
}
async fn enqueue_scan_dir(
fn enqueue_scan_dir(
&self,
abs_path: Arc<Path>,
entry: &Entry,
@ -2664,10 +2659,7 @@ impl BackgroundScannerState {
fs: &dyn Fs,
) {
let path = entry.path.clone();
let ignore_stack = self
.snapshot
.ignore_stack_for_abs_path(&abs_path, true, fs)
.await;
let ignore_stack = self.snapshot.ignore_stack_for_abs_path(&abs_path, true, fs);
let mut ancestor_inodes = self.snapshot.ancestor_inodes_for_path(&path);
if !ancestor_inodes.contains(&entry.inode) {
@ -2705,17 +2697,11 @@ impl BackgroundScannerState {
}
}
async fn insert_entry(
&mut self,
mut entry: Entry,
fs: &dyn Fs,
watcher: &dyn Watcher,
) -> Entry {
fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs, watcher: &dyn Watcher) -> Entry {
self.reuse_entry_id(&mut entry);
let entry = self.snapshot.insert_entry(entry, fs);
if entry.path.file_name() == Some(&DOT_GIT) {
self.insert_git_repository(entry.path.clone(), fs, watcher)
.await;
self.insert_git_repository(entry.path.clone(), fs, watcher);
}
#[cfg(test)]
@ -2846,7 +2832,7 @@ impl BackgroundScannerState {
self.snapshot.check_invariants(false);
}
async fn insert_git_repository(
fn insert_git_repository(
&mut self,
dot_git_path: Arc<RelPath>,
fs: &dyn Fs,
@ -2887,11 +2873,10 @@ impl BackgroundScannerState {
fs,
watcher,
)
.await
.log_err();
}
async fn insert_git_repository_for_path(
fn insert_git_repository_for_path(
&mut self,
work_directory: WorkDirectory,
dot_git_abs_path: Arc<Path>,
@ -2913,7 +2898,7 @@ impl BackgroundScannerState {
let work_directory_abs_path = self.snapshot.work_directory_abs_path(&work_directory);
let (repository_dir_abs_path, common_dir_abs_path) =
discover_git_paths(&dot_git_abs_path, fs).await;
discover_git_paths(&dot_git_abs_path, fs);
watcher
.add(&common_dir_abs_path)
.context("failed to add common directory to watcher")
@ -3557,7 +3542,7 @@ impl<'a> sum_tree::Dimension<'a, EntrySummary> for PathKey {
}
struct BackgroundScanner {
state: async_lock::Mutex<BackgroundScannerState>,
state: Mutex<BackgroundScannerState>,
fs: Arc<dyn Fs>,
fs_case_sensitive: bool,
status_updates_tx: UnboundedSender<ScanState>,
@ -3583,39 +3568,31 @@ impl BackgroundScanner {
// If the worktree root does not contain a git repository, then find
// the git repository in an ancestor directory. Find any gitignore files
// in ancestor directories.
let root_abs_path = self.state.lock().await.snapshot.abs_path.clone();
let root_abs_path = self.state.lock().snapshot.abs_path.clone();
let (ignores, repo) = discover_ancestor_git_repo(self.fs.clone(), &root_abs_path).await;
self.state
.lock()
.await
.snapshot
.ignores_by_parent_abs_path
.extend(ignores);
let containing_git_repository = if let Some((ancestor_dot_git, work_directory)) = repo {
maybe!(async {
self.state
.lock()
.await
.insert_git_repository_for_path(
work_directory,
ancestor_dot_git.clone().into(),
self.fs.as_ref(),
self.watcher.as_ref(),
)
.await
.log_err()?;
Some(ancestor_dot_git)
})
.await
} else {
None
};
let containing_git_repository = repo.and_then(|(ancestor_dot_git, work_directory)| {
self.state
.lock()
.insert_git_repository_for_path(
work_directory,
ancestor_dot_git.clone().into(),
self.fs.as_ref(),
self.watcher.as_ref(),
)
.log_err()?;
Some(ancestor_dot_git)
});
log::trace!("containing git repository: {containing_git_repository:?}");
let mut global_gitignore_events =
if let Some(global_gitignore_path) = &paths::global_gitignore_path() {
self.state.lock().await.snapshot.global_gitignore =
self.state.lock().snapshot.global_gitignore =
if self.fs.is_file(&global_gitignore_path).await {
build_gitignore(global_gitignore_path, self.fs.as_ref())
.await
@ -3629,34 +3606,31 @@ impl BackgroundScanner {
.await
.0
} else {
self.state.lock().await.snapshot.global_gitignore = None;
self.state.lock().snapshot.global_gitignore = None;
Box::pin(futures::stream::empty())
};
let (scan_job_tx, scan_job_rx) = channel::unbounded();
{
let mut state = self.state.lock().await;
let mut state = self.state.lock();
state.snapshot.scan_id += 1;
if let Some(mut root_entry) = state.snapshot.root_entry().cloned() {
let ignore_stack = state
.snapshot
.ignore_stack_for_abs_path(root_abs_path.as_path(), true, self.fs.as_ref())
.await;
let ignore_stack = state.snapshot.ignore_stack_for_abs_path(
root_abs_path.as_path(),
true,
self.fs.as_ref(),
);
if ignore_stack.is_abs_path_ignored(root_abs_path.as_path(), true) {
root_entry.is_ignored = true;
state
.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref())
.await;
state.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref());
}
if root_entry.is_dir() {
state
.enqueue_scan_dir(
root_abs_path.as_path().into(),
&root_entry,
&scan_job_tx,
self.fs.as_ref(),
)
.await;
state.enqueue_scan_dir(
root_abs_path.as_path().into(),
&root_entry,
&scan_job_tx,
self.fs.as_ref(),
);
}
}
};
@ -3665,11 +3639,11 @@ impl BackgroundScanner {
drop(scan_job_tx);
self.scan_dirs(true, scan_job_rx).await;
{
let mut state = self.state.lock().await;
let mut state = self.state.lock();
state.snapshot.completed_scan_id = state.snapshot.scan_id;
}
self.send_status_update(false, SmallVec::new()).await;
self.send_status_update(false, SmallVec::new());
// Process any any FS events that occurred while performing the initial scan.
// For these events, update events cannot be as precise, because we didn't
@ -3714,7 +3688,7 @@ impl BackgroundScanner {
if did_scan {
let abs_path =
{
let mut state = self.state.lock().await;
let mut state = self.state.lock();
state.path_prefixes_to_scan.insert(request.path.clone());
state.snapshot.absolutize(&request.path)
};
@ -3723,7 +3697,7 @@ impl BackgroundScanner {
self.process_events(vec![abs_path]).await;
}
}
self.send_status_update(false, request.done).await;
self.send_status_update(false, request.done);
}
paths = fs_events_rx.next().fuse() => {
@ -3752,7 +3726,7 @@ impl BackgroundScanner {
request.relative_paths.sort_unstable();
self.forcibly_load_paths(&request.relative_paths).await;
let root_path = self.state.lock().await.snapshot.abs_path.clone();
let root_path = self.state.lock().snapshot.abs_path.clone();
let root_canonical_path = self.fs.canonicalize(root_path.as_path()).await;
let root_canonical_path = match &root_canonical_path {
Ok(path) => SanitizedPath::new(path),
@ -3774,7 +3748,7 @@ impl BackgroundScanner {
.collect::<Vec<_>>();
{
let mut state = self.state.lock().await;
let mut state = self.state.lock();
let is_idle = state.snapshot.completed_scan_id == state.snapshot.scan_id;
state.snapshot.scan_id += 1;
if is_idle {
@ -3791,11 +3765,11 @@ impl BackgroundScanner {
)
.await;
self.send_status_update(scanning, request.done).await
self.send_status_update(scanning, request.done)
}
async fn process_events(&self, mut abs_paths: Vec<PathBuf>) {
let root_path = self.state.lock().await.snapshot.abs_path.clone();
let root_path = self.state.lock().snapshot.abs_path.clone();
let root_canonical_path = self.fs.canonicalize(root_path.as_path()).await;
let root_canonical_path = match &root_canonical_path {
Ok(path) => SanitizedPath::new(path),
@ -3803,7 +3777,6 @@ impl BackgroundScanner {
let new_path = self
.state
.lock()
.await
.snapshot
.root_file_handle
.clone()
@ -3836,31 +3809,24 @@ impl BackgroundScanner {
let mut dot_git_abs_paths = Vec::new();
abs_paths.sort_unstable();
abs_paths.dedup_by(|a, b| a.starts_with(b));
{
let snapshot = &self.state.lock().await.snapshot;
abs_paths.retain(|abs_path| {
abs_paths.retain(|abs_path| {
let abs_path = &SanitizedPath::new(abs_path);
let snapshot = &self.state.lock().snapshot;
{
let mut is_git_related = false;
let dot_git_paths = self.executor.block(maybe!(async {
let mut path = None;
for ancestor in abs_path.as_path().ancestors() {
if is_git_dir(ancestor, self.fs.as_ref()).await {
let dot_git_paths = abs_path.as_path().ancestors().find_map(|ancestor| {
if smol::block_on(is_git_dir(ancestor, self.fs.as_ref())) {
let path_in_git_dir = abs_path
.as_path()
.strip_prefix(ancestor)
.expect("stripping off the ancestor");
path = Some((ancestor.to_owned(), path_in_git_dir.to_owned()));
break;
Some((ancestor.to_owned(), path_in_git_dir.to_owned()))
} else {
None
}
}
path
}));
});
if let Some((dot_git_abs_path, path_in_git_dir)) = dot_git_paths {
if skipped_files_in_dot_git
@ -3933,12 +3899,12 @@ impl BackgroundScanner {
true
}
});
}
if relative_paths.is_empty() && dot_git_abs_paths.is_empty() {
return;
}
self.state.lock().await.snapshot.scan_id += 1;
self.state.lock().snapshot.scan_id += 1;
let (scan_job_tx, scan_job_rx) = channel::unbounded();
log::debug!("received fs events {:?}", relative_paths);
@ -3952,29 +3918,29 @@ impl BackgroundScanner {
.await;
let affected_repo_roots = if !dot_git_abs_paths.is_empty() {
self.update_git_repositories(dot_git_abs_paths).await
self.update_git_repositories(dot_git_abs_paths)
} else {
Vec::new()
};
{
let mut ignores_to_update = self.ignores_needing_update().await;
let mut ignores_to_update = self.ignores_needing_update();
ignores_to_update.extend(affected_repo_roots);
let ignores_to_update = self.order_ignores(ignores_to_update).await;
let snapshot = self.state.lock().await.snapshot.clone();
let ignores_to_update = self.order_ignores(ignores_to_update);
let snapshot = self.state.lock().snapshot.clone();
self.update_ignore_statuses_for_paths(scan_job_tx, snapshot, ignores_to_update)
.await;
self.scan_dirs(false, scan_job_rx).await;
}
{
let mut state = self.state.lock().await;
let mut state = self.state.lock();
state.snapshot.completed_scan_id = state.snapshot.scan_id;
for (_, entry) in mem::take(&mut state.removed_entries) {
state.scanned_dirs.remove(&entry.id);
}
}
self.send_status_update(false, SmallVec::new()).await;
self.send_status_update(false, SmallVec::new());
}
async fn update_global_gitignore(&self, abs_path: &Path) {
@ -3983,30 +3949,30 @@ impl BackgroundScanner {
.log_err()
.map(Arc::new);
let (prev_snapshot, ignore_stack, abs_path) = {
let mut state = self.state.lock().await;
let mut state = self.state.lock();
state.snapshot.global_gitignore = ignore;
let abs_path = state.snapshot.abs_path().clone();
let ignore_stack = state
.snapshot
.ignore_stack_for_abs_path(&abs_path, true, self.fs.as_ref())
.await;
let ignore_stack =
state
.snapshot
.ignore_stack_for_abs_path(&abs_path, true, self.fs.as_ref());
(state.snapshot.clone(), ignore_stack, abs_path)
};
let (scan_job_tx, scan_job_rx) = channel::unbounded();
self.update_ignore_statuses_for_paths(
scan_job_tx,
prev_snapshot,
vec![(abs_path, ignore_stack)],
vec![(abs_path, ignore_stack)].into_iter(),
)
.await;
self.scan_dirs(false, scan_job_rx).await;
self.send_status_update(false, SmallVec::new()).await;
self.send_status_update(false, SmallVec::new());
}
async fn forcibly_load_paths(&self, paths: &[Arc<RelPath>]) -> bool {
let (scan_job_tx, scan_job_rx) = channel::unbounded();
{
let mut state = self.state.lock().await;
let mut state = self.state.lock();
let root_path = state.snapshot.abs_path.clone();
for path in paths {
for ancestor in path.ancestors() {
@ -4014,14 +3980,12 @@ impl BackgroundScanner {
&& entry.kind == EntryKind::UnloadedDir
{
let abs_path = root_path.join(ancestor.as_std_path());
state
.enqueue_scan_dir(
abs_path.into(),
entry,
&scan_job_tx,
self.fs.as_ref(),
)
.await;
state.enqueue_scan_dir(
abs_path.into(),
entry,
&scan_job_tx,
self.fs.as_ref(),
);
state.paths_to_scan.insert(path.clone());
break;
}
@ -4033,7 +3997,7 @@ impl BackgroundScanner {
self.scan_dir(&job).await.log_err();
}
!mem::take(&mut self.state.lock().await.paths_to_scan).is_empty()
!mem::take(&mut self.state.lock().paths_to_scan).is_empty()
}
async fn scan_dirs(
@ -4081,7 +4045,7 @@ impl BackgroundScanner {
) {
Ok(_) => {
last_progress_update_count += 1;
self.send_status_update(true, SmallVec::new()).await;
self.send_status_update(true, SmallVec::new());
}
Err(count) => {
last_progress_update_count = count;
@ -4106,12 +4070,8 @@ impl BackgroundScanner {
.await;
}
async fn send_status_update(
&self,
scanning: bool,
barrier: SmallVec<[barrier::Sender; 1]>,
) -> bool {
let mut state = self.state.lock().await;
fn send_status_update(&self, scanning: bool, barrier: SmallVec<[barrier::Sender; 1]>) -> bool {
let mut state = self.state.lock();
if state.changed_paths.is_empty() && scanning {
return true;
}
@ -4140,7 +4100,7 @@ impl BackgroundScanner {
let root_abs_path;
let root_char_bag;
{
let snapshot = &self.state.lock().await.snapshot;
let snapshot = &self.state.lock().snapshot;
if self.settings.is_path_excluded(&job.path) {
log::error!("skipping excluded directory {:?}", job.path);
return Ok(());
@ -4193,14 +4153,12 @@ impl BackgroundScanner {
};
if child_name == DOT_GIT {
let mut state = self.state.lock().await;
state
.insert_git_repository(
child_path.clone(),
self.fs.as_ref(),
self.watcher.as_ref(),
)
.await;
let mut state = self.state.lock();
state.insert_git_repository(
child_path.clone(),
self.fs.as_ref(),
self.watcher.as_ref(),
);
} else if child_name == GITIGNORE {
match build_gitignore(&child_abs_path, self.fs.as_ref()).await {
Ok(ignore) => {
@ -4220,7 +4178,7 @@ impl BackgroundScanner {
if self.settings.is_path_excluded(&child_path) {
log::debug!("skipping excluded child entry {child_path:?}");
self.state.lock().await.remove_path(&child_path);
self.state.lock().remove_path(&child_path);
continue;
}
@ -4320,7 +4278,7 @@ impl BackgroundScanner {
new_entries.push(child_entry);
}
let mut state = self.state.lock().await;
let mut state = self.state.lock();
// Identify any subdirectories that should not be scanned.
let mut job_ix = 0;
@ -4402,7 +4360,7 @@ impl BackgroundScanner {
None
};
let mut state = self.state.lock().await;
let mut state = self.state.lock();
let doing_recursive_update = scan_queue_tx.is_some();
// Remove any entries for paths that no longer exist or are being recursively
@ -4419,10 +4377,11 @@ impl BackgroundScanner {
let abs_path: Arc<Path> = root_abs_path.join(path.as_std_path()).into();
match metadata {
Ok(Some((metadata, canonical_path))) => {
let ignore_stack = state
.snapshot
.ignore_stack_for_abs_path(&abs_path, metadata.is_dir, self.fs.as_ref())
.await;
let ignore_stack = state.snapshot.ignore_stack_for_abs_path(
&abs_path,
metadata.is_dir,
self.fs.as_ref(),
);
let is_external = !canonical_path.starts_with(&root_canonical_path);
let mut fs_entry = Entry::new(
path.clone(),
@ -4454,22 +4413,18 @@ impl BackgroundScanner {
|| (fs_entry.path.is_empty()
&& abs_path.file_name() == Some(OsStr::new(DOT_GIT)))
{
state
.enqueue_scan_dir(
abs_path,
&fs_entry,
scan_queue_tx,
self.fs.as_ref(),
)
.await;
state.enqueue_scan_dir(
abs_path,
&fs_entry,
scan_queue_tx,
self.fs.as_ref(),
);
} else {
fs_entry.kind = EntryKind::UnloadedDir;
}
}
state
.insert_entry(fs_entry.clone(), self.fs.as_ref(), self.watcher.as_ref())
.await;
state.insert_entry(fs_entry.clone(), self.fs.as_ref(), self.watcher.as_ref());
if path.is_empty()
&& let Some((ignores, repo)) = new_ancestor_repo.take()
@ -4484,7 +4439,6 @@ impl BackgroundScanner {
self.fs.as_ref(),
self.watcher.as_ref(),
)
.await
.log_err();
}
}
@ -4523,11 +4477,11 @@ impl BackgroundScanner {
&self,
scan_job_tx: Sender<ScanJob>,
prev_snapshot: LocalSnapshot,
ignores_to_update: Vec<(Arc<Path>, IgnoreStack)>,
mut ignores_to_update: impl Iterator<Item = (Arc<Path>, IgnoreStack)>,
) {
let (ignore_queue_tx, ignore_queue_rx) = channel::unbounded();
{
for (parent_abs_path, ignore_stack) in ignores_to_update {
while let Some((parent_abs_path, ignore_stack)) = ignores_to_update.next() {
ignore_queue_tx
.send_blocking(UpdateIgnoreStatusJob {
abs_path: parent_abs_path,
@ -4568,11 +4522,11 @@ impl BackgroundScanner {
.await;
}
async fn ignores_needing_update(&self) -> Vec<Arc<Path>> {
fn ignores_needing_update(&self) -> Vec<Arc<Path>> {
let mut ignores_to_update = Vec::new();
{
let snapshot = &mut self.state.lock().await.snapshot;
let snapshot = &mut self.state.lock().snapshot;
let abs_path = snapshot.abs_path.clone();
snapshot
.ignores_by_parent_abs_path
@ -4600,27 +4554,26 @@ impl BackgroundScanner {
ignores_to_update
}
async fn order_ignores(&self, mut ignores: Vec<Arc<Path>>) -> Vec<(Arc<Path>, IgnoreStack)> {
fn order_ignores(
&self,
mut ignores: Vec<Arc<Path>>,
) -> impl use<> + Iterator<Item = (Arc<Path>, IgnoreStack)> {
let fs = self.fs.clone();
let snapshot = self.state.lock().await.snapshot.clone();
let snapshot = self.state.lock().snapshot.clone();
ignores.sort_unstable();
let mut ignores_to_update = ignores.into_iter().peekable();
let mut result = vec![];
while let Some(parent_abs_path) = ignores_to_update.next() {
std::iter::from_fn(move || {
let parent_abs_path = ignores_to_update.next()?;
while ignores_to_update
.peek()
.map_or(false, |p| p.starts_with(&parent_abs_path))
{
ignores_to_update.next().unwrap();
}
let ignore_stack = snapshot
.ignore_stack_for_abs_path(&parent_abs_path, true, fs.as_ref())
.await;
result.push((parent_abs_path, ignore_stack));
}
result
let ignore_stack =
snapshot.ignore_stack_for_abs_path(&parent_abs_path, true, fs.as_ref());
Some((parent_abs_path, ignore_stack))
})
}
async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
@ -4652,7 +4605,7 @@ impl BackgroundScanner {
return;
};
if let Ok(Some(metadata)) = self.fs.metadata(&job.abs_path.join(DOT_GIT)).await
if let Ok(Some(metadata)) = smol::block_on(self.fs.metadata(&job.abs_path.join(DOT_GIT)))
&& metadata.is_dir
{
ignore_stack.repo_root = Some(job.abs_path.clone());
@ -4672,16 +4625,14 @@ impl BackgroundScanner {
// Scan any directories that were previously ignored and weren't previously scanned.
if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() {
let state = self.state.lock().await;
let state = self.state.lock();
if state.should_scan_directory(&entry) {
state
.enqueue_scan_dir(
abs_path.clone(),
&entry,
&job.scan_queue,
self.fs.as_ref(),
)
.await;
state.enqueue_scan_dir(
abs_path.clone(),
&entry,
&job.scan_queue,
self.fs.as_ref(),
);
}
}
@ -4705,7 +4656,7 @@ impl BackgroundScanner {
}
}
let state = &mut self.state.lock().await;
let state = &mut self.state.lock();
for edit in &entries_by_path_edits {
if let Edit::Insert(entry) = edit
&& let Err(ix) = state.changed_paths.binary_search(&entry.path)
@ -4721,9 +4672,9 @@ impl BackgroundScanner {
state.snapshot.entries_by_id.edit(entries_by_id_edits, ());
}
async fn update_git_repositories(&self, dot_git_paths: Vec<PathBuf>) -> Vec<Arc<Path>> {
fn update_git_repositories(&self, dot_git_paths: Vec<PathBuf>) -> Vec<Arc<Path>> {
log::trace!("reloading repositories: {dot_git_paths:?}");
let mut state = self.state.lock().await;
let mut state = self.state.lock();
let scan_id = state.snapshot.scan_id;
let mut affected_repo_roots = Vec::new();
for dot_git_dir in dot_git_paths {
@ -4753,15 +4704,13 @@ impl BackgroundScanner {
return Vec::new();
};
affected_repo_roots.push(dot_git_dir.parent().unwrap().into());
state
.insert_git_repository(
RelPath::new(relative, PathStyle::local())
.unwrap()
.into_arc(),
self.fs.as_ref(),
self.watcher.as_ref(),
)
.await;
state.insert_git_repository(
RelPath::new(relative, PathStyle::local())
.unwrap()
.into_arc(),
self.fs.as_ref(),
self.watcher.as_ref(),
);
}
Some(local_repository) => {
state.snapshot.git_repositories.update(
@ -4789,7 +4738,7 @@ impl BackgroundScanner {
if exists_in_snapshot
|| matches!(
self.fs.metadata(&entry.common_dir_abs_path).await,
smol::block_on(self.fs.metadata(&entry.common_dir_abs_path)),
Ok(Some(_))
)
{
@ -5548,13 +5497,11 @@ fn parse_gitfile(content: &str) -> anyhow::Result<&Path> {
Ok(Path::new(path.trim()))
}
async fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<Path>, Arc<Path>) {
fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<Path>, Arc<Path>) {
let mut repository_dir_abs_path = dot_git_abs_path.clone();
let mut common_dir_abs_path = dot_git_abs_path.clone();
if let Some(path) = fs
.load(dot_git_abs_path)
.await
if let Some(path) = smol::block_on(fs.load(dot_git_abs_path))
.ok()
.as_ref()
.and_then(|contents| parse_gitfile(contents).log_err())
@ -5563,19 +5510,17 @@ async fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<P
.parent()
.unwrap_or(Path::new(""))
.join(path);
if let Some(path) = fs.canonicalize(&path).await.log_err() {
if let Some(path) = smol::block_on(fs.canonicalize(&path)).log_err() {
repository_dir_abs_path = Path::new(&path).into();
common_dir_abs_path = repository_dir_abs_path.clone();
if let Some(commondir_contents) = fs.load(&path.join("commondir")).await.ok()
&& let Some(commondir_path) = fs
.canonicalize(&path.join(commondir_contents.trim()))
.await
.log_err()
if let Some(commondir_contents) = smol::block_on(fs.load(&path.join("commondir"))).ok()
&& let Some(commondir_path) =
smol::block_on(fs.canonicalize(&path.join(commondir_contents.trim()))).log_err()
{
common_dir_abs_path = commondir_path.as_path().into();
}
}
};
(repository_dir_abs_path, common_dir_abs_path)
}

View file

@ -734,6 +734,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
})
.await
.unwrap();
worktree.read_with(cx, |tree, _| {
let tracked = tree
.entry_for_path(rel_path("tracked-dir/file.txt"))
@ -1536,7 +1537,7 @@ async fn test_random_worktree_operations_during_initial_scan(
assert_eq!(
updated_snapshot.entries(true, 0).collect::<Vec<_>>(),
final_snapshot.entries(true, 0).collect::<Vec<_>>(),
"wrong updates after snapshot {i}: {updates:#?}",
"wrong updates after snapshot {i}: {snapshot:#?} {updates:#?}",
);
}
}