diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 4a84c27dfd..6cfaf82a29 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -1,3 +1,9 @@ +#[cfg(target_os = "macos")] +mod mac_watcher; + +#[cfg(target_os = "linux")] +pub mod linux_watcher; + use anyhow::{anyhow, Result}; use git::GitHostingProviderRegistry; @@ -530,14 +536,21 @@ impl Fs for RealFs { Pin>>>, Arc, ) { - use fsevent::{EventStream, StreamFlags}; + use fsevent::StreamFlags; - let (tx, rx) = smol::channel::unbounded(); - let (stream, handle) = EventStream::new(&[path], latency); - std::thread::spawn(move || { - stream.run(move |events| { - smol::block_on( - tx.send( + let (events_tx, events_rx) = smol::channel::unbounded(); + let handles = Arc::new(parking_lot::Mutex::new(collections::BTreeMap::default())); + let watcher = Arc::new(mac_watcher::MacWatcher::new( + events_tx, + Arc::downgrade(&handles), + latency, + )); + watcher.add(path).expect("handles can't be dropped"); + + ( + Box::pin( + events_rx + .map(|events| { events .into_iter() .map(|event| { @@ -555,19 +568,14 @@ impl Fs for RealFs { kind, } }) - .collect(), - ), - ) - .is_ok() - }); - }); - - ( - Box::pin(rx.chain(futures::stream::once(async move { - drop(handle); - vec![] - }))), - Arc::new(RealWatcher {}), + .collect() + }) + .chain(futures::stream::once(async move { + drop(handles); + vec![] + })), + ), + watcher, ) } @@ -580,81 +588,26 @@ impl Fs for RealFs { Pin>>>, Arc, ) { - use notify::EventKind; use parking_lot::Mutex; let (tx, rx) = smol::channel::unbounded(); let pending_paths: Arc>> = Default::default(); - let root_path = path.to_path_buf(); + let watcher = Arc::new(linux_watcher::LinuxWatcher::new(tx, pending_paths.clone())); - // Check if root path is a symlink - let target_path = self.read_link(&path).await.ok(); - - watcher::global({ - let target_path = target_path.clone(); - |g| { - let tx = tx.clone(); - let pending_paths = pending_paths.clone(); - g.add(move |event: ¬ify::Event| { - let kind = match event.kind { - EventKind::Create(_) => Some(PathEventKind::Created), - EventKind::Modify(_) => Some(PathEventKind::Changed), - EventKind::Remove(_) => Some(PathEventKind::Removed), - _ => None, - }; - let mut paths = event - .paths - .iter() - .filter_map(|path| { - if let Some(target) = target_path.clone() { - if path.starts_with(target) { - return Some(PathEvent { - path: path.clone(), - kind, - }); - } - } else if path.starts_with(&root_path) { - return Some(PathEvent { - path: path.clone(), - kind, - }); - } - None - }) - .collect::>(); - - if !paths.is_empty() { - paths.sort(); - let mut pending_paths = pending_paths.lock(); - if pending_paths.is_empty() { - tx.try_send(()).ok(); - } - util::extend_sorted(&mut *pending_paths, paths, usize::MAX, |a, b| { - a.path.cmp(&b.path) - }); - } - }) - } - }) - .log_err(); - - let watcher = Arc::new(RealWatcher {}); - - watcher.add(path).ok(); // Ignore "file doesn't exist error" and rely on parent watcher. + watcher.add(&path).ok(); // Ignore "file doesn't exist error" and rely on parent watcher. + if let Some(parent) = path.parent() { + // watch the parent dir so we can tell when settings.json is created + watcher.add(parent).log_err(); + } // Check if path is a symlink and follow the target parent - if let Some(target) = target_path { + if let Some(target) = self.read_link(&path).await.ok() { watcher.add(&target).ok(); if let Some(parent) = target.parent() { watcher.add(parent).log_err(); } } - // watch the parent dir so we can tell when settings.json is created - if let Some(parent) = path.parent() { - watcher.add(parent).log_err(); - } - ( Box::pin(rx.filter_map({ let watcher = watcher.clone(); @@ -784,23 +737,6 @@ impl Watcher for RealWatcher { } } -#[cfg(target_os = "linux")] -impl Watcher for RealWatcher { - fn add(&self, path: &Path) -> Result<()> { - use notify::Watcher; - Ok(watcher::global(|w| { - w.inotify - .lock() - .watch(path, notify::RecursiveMode::NonRecursive) - })??) - } - - fn remove(&self, path: &Path) -> Result<()> { - use notify::Watcher; - Ok(watcher::global(|w| w.inotify.lock().unwatch(path))??) - } -} - #[cfg(any(test, feature = "test-support"))] pub struct FakeFs { // Use an unfair lock to ensure tests are deterministic. @@ -2084,49 +2020,3 @@ mod tests { ); } } - -#[cfg(target_os = "linux")] -pub mod watcher { - use std::sync::OnceLock; - - use parking_lot::Mutex; - use util::ResultExt; - - pub struct GlobalWatcher { - // two mutexes because calling inotify.add triggers an inotify.event, which needs watchers. - pub(super) inotify: Mutex, - pub(super) watchers: Mutex>>, - } - - impl GlobalWatcher { - pub(super) fn add(&self, cb: impl Fn(¬ify::Event) + Send + Sync + 'static) { - self.watchers.lock().push(Box::new(cb)) - } - } - - static INOTIFY_INSTANCE: OnceLock> = - OnceLock::new(); - - fn handle_event(event: Result) { - let Some(event) = event.log_err() else { return }; - global::<()>(move |watcher| { - for f in watcher.watchers.lock().iter() { - f(&event) - } - }) - .log_err(); - } - - pub fn global(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result { - let result = INOTIFY_INSTANCE.get_or_init(|| { - notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher { - inotify: Mutex::new(file_watcher), - watchers: Default::default(), - }) - }); - match result { - Ok(g) => Ok(f(g)), - Err(e) => Err(anyhow::anyhow!("{}", e)), - } - } -} diff --git a/crates/fs/src/linux_watcher.rs b/crates/fs/src/linux_watcher.rs new file mode 100644 index 0000000000..04244c6956 --- /dev/null +++ b/crates/fs/src/linux_watcher.rs @@ -0,0 +1,121 @@ +use notify::EventKind; +use parking_lot::Mutex; +use std::sync::{Arc, OnceLock}; +use util::ResultExt; + +use crate::{PathEvent, PathEventKind, Watcher}; + +pub struct LinuxWatcher { + tx: smol::channel::Sender<()>, + pending_path_events: Arc>>, +} + +impl LinuxWatcher { + pub fn new( + tx: smol::channel::Sender<()>, + pending_path_events: Arc>>, + ) -> Self { + Self { + tx, + pending_path_events, + } + } +} + +impl Watcher for LinuxWatcher { + fn add(&self, path: &std::path::Path) -> gpui::Result<()> { + let root_path = path.to_path_buf(); + + let tx = self.tx.clone(); + let pending_paths = self.pending_path_events.clone(); + + use notify::Watcher; + + global({ + |g| { + g.add(move |event: ¬ify::Event| { + let kind = match event.kind { + EventKind::Create(_) => Some(PathEventKind::Created), + EventKind::Modify(_) => Some(PathEventKind::Changed), + EventKind::Remove(_) => Some(PathEventKind::Removed), + _ => None, + }; + let mut path_events = event + .paths + .iter() + .filter_map(|event_path| { + event_path.starts_with(&root_path).then(|| PathEvent { + path: event_path.clone(), + kind, + }) + }) + .collect::>(); + + if !path_events.is_empty() { + path_events.sort(); + let mut pending_paths = pending_paths.lock(); + if pending_paths.is_empty() { + tx.try_send(()).ok(); + } + util::extend_sorted( + &mut *pending_paths, + path_events, + usize::MAX, + |a, b| a.path.cmp(&b.path), + ); + } + }) + } + })?; + + global(|g| { + g.inotify + .lock() + .watch(path, notify::RecursiveMode::NonRecursive) + })??; + + Ok(()) + } + + fn remove(&self, path: &std::path::Path) -> gpui::Result<()> { + use notify::Watcher; + Ok(global(|w| w.inotify.lock().unwatch(path))??) + } +} + +pub struct GlobalWatcher { + // two mutexes because calling inotify.add triggers an inotify.event, which needs watchers. + pub(super) inotify: Mutex, + pub(super) watchers: Mutex>>, +} + +impl GlobalWatcher { + pub(super) fn add(&self, cb: impl Fn(¬ify::Event) + Send + Sync + 'static) { + self.watchers.lock().push(Box::new(cb)) + } +} + +static INOTIFY_INSTANCE: OnceLock> = OnceLock::new(); + +fn handle_event(event: Result) { + let Some(event) = event.log_err() else { return }; + global::<()>(move |watcher| { + for f in watcher.watchers.lock().iter() { + f(&event) + } + }) + .log_err(); +} + +pub fn global(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result { + let result = INOTIFY_INSTANCE.get_or_init(|| { + notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher { + inotify: Mutex::new(file_watcher), + watchers: Default::default(), + }) + }); + match result { + Ok(g) => Ok(f(g)), + Err(e) => Err(anyhow::anyhow!("{}", e)), + } +} diff --git a/crates/fs/src/mac_watcher.rs b/crates/fs/src/mac_watcher.rs new file mode 100644 index 0000000000..0caff5e7a1 --- /dev/null +++ b/crates/fs/src/mac_watcher.rs @@ -0,0 +1,70 @@ +use crate::Watcher; +use anyhow::{Context as _, Result}; +use collections::{BTreeMap, Bound}; +use fsevent::EventStream; +use parking_lot::Mutex; +use std::{ + path::{Path, PathBuf}, + sync::Weak, + time::Duration, +}; + +pub struct MacWatcher { + events_tx: smol::channel::Sender>, + handles: Weak>>, + latency: Duration, +} + +impl MacWatcher { + pub fn new( + events_tx: smol::channel::Sender>, + handles: Weak>>, + latency: Duration, + ) -> Self { + Self { + events_tx, + handles, + latency, + } + } +} + +impl Watcher for MacWatcher { + fn add(&self, path: &Path) -> Result<()> { + let handles = self + .handles + .upgrade() + .context("unable to watch path, receiver dropped")?; + let mut handles = handles.lock(); + + // Return early if an ancestor of this path was already being watched. + if let Some((watched_path, _)) = handles + .range::((Bound::Unbounded, Bound::Included(path))) + .next_back() + { + if path.starts_with(watched_path) { + return Ok(()); + } + } + + let (stream, handle) = EventStream::new(&[path], self.latency); + let tx = self.events_tx.clone(); + std::thread::spawn(move || { + stream.run(move |events| smol::block_on(tx.send(events)).is_ok()); + }); + handles.insert(path.into(), handle); + + Ok(()) + } + + fn remove(&self, path: &Path) -> gpui::Result<()> { + let handles = self + .handles + .upgrade() + .context("unable to remove path, receiver dropped")?; + + let mut handles = handles.lock(); + handles.remove(path); + Ok(()) + } +} diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index fe65816cc5..78f6ece508 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -45,6 +45,8 @@ pub trait GitRepository: Send + Sync { fn branch_exits(&self, _: &str) -> Result; fn blame(&self, path: &Path, content: Rope) -> Result; + + fn path(&self) -> PathBuf; } impl std::fmt::Debug for dyn GitRepository { @@ -83,6 +85,11 @@ impl GitRepository for RealGitRepository { } } + fn path(&self) -> PathBuf { + let repo = self.repository.lock(); + repo.path().into() + } + fn load_index_text(&self, relative_file_path: &Path) -> Option { fn logic(repo: &git2::Repository, relative_file_path: &Path) -> Result> { const STAGE_NORMAL: i32 = 0; @@ -276,6 +283,11 @@ impl GitRepository for FakeGitRepository { None } + fn path(&self) -> PathBuf { + let state = self.state.lock(); + state.path.clone() + } + fn status(&self, path_prefixes: &[PathBuf]) -> Result { let state = self.state.lock(); let mut entries = state diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 625113ab6e..48f53b45f9 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -2024,6 +2024,7 @@ pub fn perform_project_search( text: impl Into>, cx: &mut gpui::VisualTestContext, ) { + cx.run_until_parked(); search_view.update(cx, |search_view, cx| { search_view .query_editor diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 8114f2dd7b..0d9f359b3b 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -14,7 +14,6 @@ use futures::{ oneshot, }, select_biased, - stream::select, task::Poll, FutureExt as _, Stream, StreamExt, }; @@ -307,9 +306,11 @@ struct BackgroundScannerState { pub struct LocalRepositoryEntry { pub(crate) git_dir_scan_id: usize, pub(crate) repo_ptr: Arc, - /// Path to the actual .git folder. + /// Absolute path to the actual .git folder. /// Note: if .git is a file, this points to the folder indicated by the .git file - pub(crate) git_dir_path: Arc, + pub(crate) dot_git_dir_abs_path: Arc, + /// Absolute path to the .git file, if we're in a git worktree. + pub(crate) dot_git_worktree_abs_path: Option>, } impl LocalRepositoryEntry { @@ -2559,7 +2560,7 @@ impl LocalSnapshot { new_ignores.push((ancestor, None)); } } - if ancestor.join(*DOT_GIT).is_dir() { + if ancestor.join(*DOT_GIT).exists() { break; } } @@ -2664,7 +2665,7 @@ impl LocalSnapshot { let dotgit_paths = self .git_repositories .iter() - .map(|repo| repo.1.git_dir_path.clone()) + .map(|repo| repo.1.dot_git_dir_abs_path.clone()) .collect::>(); let work_dir_paths = self .repository_entries @@ -2764,11 +2765,11 @@ impl BackgroundScannerState { } } - fn insert_entry(&mut self, mut entry: Entry, fs: &dyn Fs) -> 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.build_git_repository(entry.path.clone(), fs); + self.insert_git_repository(entry.path.clone(), fs, watcher); } #[cfg(test)] @@ -2897,10 +2898,11 @@ impl BackgroundScannerState { self.snapshot.check_invariants(false); } - fn build_git_repository( + fn insert_git_repository( &mut self, dot_git_path: Arc, fs: &dyn Fs, + watcher: &dyn Watcher, ) -> Option<(RepositoryWorkDirectory, Arc)> { let work_dir_path: Arc = match dot_git_path.parent() { Some(parent_dir) => { @@ -2927,15 +2929,16 @@ impl BackgroundScannerState { } }; - self.build_git_repository_for_path(work_dir_path, dot_git_path, None, fs) + self.insert_git_repository_for_path(work_dir_path, dot_git_path, None, fs, watcher) } - fn build_git_repository_for_path( + fn insert_git_repository_for_path( &mut self, work_dir_path: Arc, dot_git_path: Arc, location_in_repo: Option>, fs: &dyn Fs, + watcher: &dyn Watcher, ) -> Option<(RepositoryWorkDirectory, Arc)> { let work_dir_id = self .snapshot @@ -2946,9 +2949,31 @@ impl BackgroundScannerState { return None; } - let abs_path = self.snapshot.abs_path.join(&dot_git_path); + let dot_git_abs_path = self.snapshot.abs_path.join(&dot_git_path); + let t0 = Instant::now(); - let repository = fs.open_repo(&abs_path)?; + let repository = fs.open_repo(&dot_git_abs_path)?; + + let actual_repo_path = repository.path(); + let actual_dot_git_dir_abs_path: Arc = Arc::from( + actual_repo_path + .ancestors() + .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))?, + ); + + watcher.add(&actual_repo_path).log_err()?; + + let dot_git_worktree_abs_path = if actual_dot_git_dir_abs_path.as_ref() == dot_git_abs_path + { + None + } else { + // The two paths could be different because we opened a git worktree. + // When that happens, the .git path in the worktree (`dot_git_abs_path`) is a file that + // points to the worktree-subdirectory in the actual .git directory (`git_dir_path`) + watcher.add(&dot_git_abs_path).log_err()?; + Some(Arc::from(dot_git_abs_path)) + }; + log::trace!("constructed libgit2 repo in {:?}", t0.elapsed()); let work_directory = RepositoryWorkDirectory(work_dir_path.clone()); @@ -2972,7 +2997,8 @@ impl BackgroundScannerState { LocalRepositoryEntry { git_dir_scan_id: 0, repo_ptr: repository.clone(), - git_dir_path: dot_git_path.clone(), + dot_git_dir_abs_path: actual_dot_git_dir_abs_path, + dot_git_worktree_abs_path, }, ); @@ -3542,23 +3568,27 @@ impl BackgroundScanner { } let ancestor_dot_git = ancestor.join(*DOT_GIT); - if ancestor_dot_git.is_dir() { + // Check whether the directory or file called `.git` exists (in the + // case of worktrees it's a file.) + if self + .fs + .metadata(&ancestor_dot_git) + .await + .is_ok_and(|metadata| metadata.is_some()) + { if index != 0 { // We canonicalize, since the FS events use the canonicalized path. if let Some(ancestor_dot_git) = self.fs.canonicalize(&ancestor_dot_git).await.log_err() { - let (ancestor_git_events, _) = - self.fs.watch(&ancestor_dot_git, FS_WATCH_LATENCY).await; - fs_events_rx = select(fs_events_rx, ancestor_git_events).boxed(); - // We associate the external git repo with our root folder and // also mark where in the git repo the root folder is located. - self.state.lock().build_git_repository_for_path( + self.state.lock().insert_git_repository_for_path( Path::new("").into(), ancestor_dot_git.into(), Some(root_abs_path.strip_prefix(ancestor).unwrap().into()), self.fs.as_ref(), + self.watcher.as_ref(), ); }; } @@ -3578,7 +3608,7 @@ impl BackgroundScanner { .ignore_stack_for_abs_path(&root_abs_path, true); if ignore_stack.is_abs_path_ignored(&root_abs_path, true) { root_entry.is_ignored = true; - state.insert_entry(root_entry.clone(), self.fs.as_ref()); + state.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref()); } state.enqueue_scan_dir(root_abs_path, &root_entry, &scan_job_tx); } @@ -3708,7 +3738,7 @@ impl BackgroundScanner { }; let mut relative_paths = Vec::with_capacity(abs_paths.len()); - let mut dot_git_paths = Vec::new(); + let mut dot_git_abs_paths = Vec::new(); abs_paths.sort_unstable(); abs_paths.dedup_by(|a, b| a.starts_with(b)); abs_paths.retain(|abs_path| { @@ -3723,7 +3753,7 @@ impl BackgroundScanner { FsMonitor } let mut fsmonitor_parse_state = None; - if let Some(dot_git_dir) = abs_path + if let Some(dot_git_abs_path) = abs_path .ancestors() .find(|ancestor| { let file_name = ancestor.file_name(); @@ -3742,12 +3772,9 @@ impl BackgroundScanner { }) { - let dot_git_path = dot_git_dir - .strip_prefix(&root_canonical_path) - .unwrap_or(dot_git_dir) - .to_path_buf(); - if !dot_git_paths.contains(&dot_git_path) { - dot_git_paths.push(dot_git_path); + let dot_git_abs_path = dot_git_abs_path.to_path_buf(); + if !dot_git_abs_paths.contains(&dot_git_abs_path) { + dot_git_abs_paths.push(dot_git_abs_path); } is_git_related = true; } @@ -3790,7 +3817,7 @@ impl BackgroundScanner { } }); - if relative_paths.is_empty() && dot_git_paths.is_empty() { + if relative_paths.is_empty() && dot_git_abs_paths.is_empty() { return; } @@ -3810,8 +3837,8 @@ impl BackgroundScanner { self.update_ignore_statuses(scan_job_tx).await; self.scan_dirs(false, scan_job_rx).await; - if !dot_git_paths.is_empty() { - self.update_git_repositories(dot_git_paths).await; + if !dot_git_abs_paths.is_empty() { + self.update_git_repositories(dot_git_abs_paths).await; } { @@ -3995,10 +4022,12 @@ impl BackgroundScanner { let child_path: Arc = job.path.join(child_name).into(); if child_name == *DOT_GIT { - let repo = self - .state - .lock() - .build_git_repository(child_path.clone(), self.fs.as_ref()); + let repo = self.state.lock().insert_git_repository( + child_path.clone(), + self.fs.as_ref(), + self.watcher.as_ref(), + ); + if let Some((work_directory, repository)) = repo { let t0 = Instant::now(); let statuses = repository @@ -4011,7 +4040,6 @@ impl BackgroundScanner { statuses, }); } - self.watcher.add(child_abs_path.as_ref()).log_err(); } else if child_name == *GITIGNORE { match build_gitignore(&child_abs_path, self.fs.as_ref()).await { Ok(ignore) => { @@ -4221,7 +4249,7 @@ impl BackgroundScanner { if let Some((repo_entry, repo)) = state.snapshot.repo_for_path(relative_path) { if let Ok(repo_path) = repo_entry.relativize(&state.snapshot, relative_path) { paths_by_git_repo - .entry(repo.git_dir_path.clone()) + .entry(repo.dot_git_dir_abs_path.clone()) .or_insert_with(|| RepoPaths { repo: repo.repo_ptr.clone(), repo_paths: Vec::new(), @@ -4281,7 +4309,7 @@ impl BackgroundScanner { fs_entry.git_status = git_statuses_by_relative_path.remove(path); } - state.insert_entry(fs_entry.clone(), self.fs.as_ref()); + state.insert_entry(fs_entry.clone(), self.fs.as_ref(), self.watcher.as_ref()); } Ok(None) => { self.remove_repo_path(path, &mut state.snapshot); @@ -4494,13 +4522,22 @@ impl BackgroundScanner { .git_repositories .iter() .find_map(|(entry_id, repo)| { - (repo.git_dir_path.as_ref() == dot_git_dir) - .then(|| (*entry_id, repo.clone())) + if repo.dot_git_dir_abs_path.as_ref() == &dot_git_dir + || repo.dot_git_worktree_abs_path.as_deref() == Some(&dot_git_dir) + { + Some((*entry_id, repo.clone())) + } else { + None + } }); let (work_directory, repository) = match existing_repository_entry { None => { - match state.build_git_repository(dot_git_dir.into(), self.fs.as_ref()) { + match state.insert_git_repository( + dot_git_dir.into(), + self.fs.as_ref(), + self.watcher.as_ref(), + ) { Some(output) => output, None => continue, } @@ -4555,19 +4592,14 @@ impl BackgroundScanner { .map_or(false, |entry| { snapshot.entry_for_path(entry.path.join(*DOT_GIT)).is_some() }); - if exists_in_snapshot { + + if exists_in_snapshot + || matches!( + smol::block_on(self.fs.metadata(&entry.dot_git_dir_abs_path)), + Ok(Some(_)) + ) + { ids_to_preserve.insert(work_directory_id); - } else { - let git_dir_abs_path = snapshot.abs_path().join(&entry.git_dir_path); - let git_dir_excluded = self.settings.is_path_excluded(&entry.git_dir_path); - if git_dir_excluded - && !matches!( - smol::block_on(self.fs.metadata(&git_dir_abs_path)), - Ok(None) - ) - { - ids_to_preserve.insert(work_directory_id); - } } } @@ -4960,7 +4992,7 @@ impl WorktreeModelHandle for Model { let local_repo_entry = tree.get_local_repo(&root_entry).unwrap(); ( tree.fs.clone(), - local_repo_entry.git_dir_path.clone(), + local_repo_entry.dot_git_dir_abs_path.clone(), local_repo_entry.git_dir_scan_id, ) }); diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 929dc01c6d..bdda66c2e0 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -720,7 +720,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) { cx.read(|cx| { let tree = tree.read(cx); assert_entry_git_state(tree, "tracked-dir/tracked-file1", None, false); - assert_entry_git_state(tree, "tracked-dir/ancestor-ignored-file1", None, true); + assert_entry_git_state(tree, "tracked-dir/ancestor-ignored-file1", None, false); assert_entry_git_state(tree, "ignored-dir/ignored-file1", None, true); }); @@ -757,7 +757,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) { Some(GitFileStatus::Added), false, ); - assert_entry_git_state(tree, "tracked-dir/ancestor-ignored-file2", None, true); + assert_entry_git_state(tree, "tracked-dir/ancestor-ignored-file2", None, false); assert_entry_git_state(tree, "ignored-dir/ignored-file2", None, true); assert!(tree.entry_for_path(".git").unwrap().is_ignored); }); @@ -843,7 +843,7 @@ async fn test_write_file(cx: &mut TestAppContext) { .unwrap(); #[cfg(target_os = "linux")] - fs::watcher::global(|_| {}).unwrap(); + fs::linux_watcher::global(|_| {}).unwrap(); cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) .await; @@ -2635,6 +2635,12 @@ fn assert_entry_git_state( is_ignored: bool, ) { let entry = tree.entry_for_path(path).expect("entry {path} not found"); - assert_eq!(entry.git_status, git_status); - assert_eq!(entry.is_ignored, is_ignored); + assert_eq!( + entry.git_status, git_status, + "expected {path} to have git status: {git_status:?}" + ); + assert_eq!( + entry.is_ignored, is_ignored, + "expected {path} to have is_ignored: {is_ignored}" + ); } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 41a5731be0..3855639ffd 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -154,7 +154,7 @@ pub fn initialize_workspace( .detach(); #[cfg(target_os = "linux")] - if let Err(e) = fs::watcher::global(|_| {}) { + if let Err(e) = fs::linux_watcher::global(|_| {}) { let message = format!(db::indoc!{r#" inotify_init returned {}