Detailed changes
@@ -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<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
- 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<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
- use notify::EventKind;
use parking_lot::Mutex;
let (tx, rx) = smol::channel::unbounded();
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
- let root_path = path.to_path_buf();
-
- // Check if root path is a symlink
- let target_path = self.read_link(&path).await.ok();
+ let watcher = Arc::new(linux_watcher::LinuxWatcher::new(tx, pending_paths.clone()));
- 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::<Vec<_>>();
-
- 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<notify::INotifyWatcher>,
- pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
- }
-
- 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<anyhow::Result<GlobalWatcher, notify::Error>> =
- OnceLock::new();
-
- fn handle_event(event: Result<notify::Event, notify::Error>) {
- 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<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
- 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)),
- }
- }
-}
@@ -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<Mutex<Vec<PathEvent>>>,
+}
+
+impl LinuxWatcher {
+ pub fn new(
+ tx: smol::channel::Sender<()>,
+ pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
+ ) -> 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::<Vec<_>>();
+
+ 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<notify::INotifyWatcher>,
+ pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
+}
+
+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<anyhow::Result<GlobalWatcher, notify::Error>> = OnceLock::new();
+
+fn handle_event(event: Result<notify::Event, notify::Error>) {
+ 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<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
+ 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)),
+ }
+}
@@ -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<Vec<fsevent::Event>>,
+ handles: Weak<Mutex<BTreeMap<PathBuf, fsevent::Handle>>>,
+ latency: Duration,
+}
+
+impl MacWatcher {
+ pub fn new(
+ events_tx: smol::channel::Sender<Vec<fsevent::Event>>,
+ handles: Weak<Mutex<BTreeMap<PathBuf, fsevent::Handle>>>,
+ 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::<Path, _>((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(())
+ }
+}
@@ -45,6 +45,8 @@ pub trait GitRepository: Send + Sync {
fn branch_exits(&self, _: &str) -> Result<bool>;
fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
+
+ 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<String> {
fn logic(repo: &git2::Repository, relative_file_path: &Path) -> Result<Option<String>> {
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<GitStatus> {
let state = self.state.lock();
let mut entries = state
@@ -2024,6 +2024,7 @@ pub fn perform_project_search(
text: impl Into<std::sync::Arc<str>>,
cx: &mut gpui::VisualTestContext,
) {
+ cx.run_until_parked();
search_view.update(cx, |search_view, cx| {
search_view
.query_editor
@@ -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<dyn GitRepository>,
- /// 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<Path>,
+ pub(crate) dot_git_dir_abs_path: Arc<Path>,
+ /// Absolute path to the .git file, if we're in a git worktree.
+ pub(crate) dot_git_worktree_abs_path: Option<Arc<Path>>,
}
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::<HashSet<_>>();
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<Path>,
fs: &dyn Fs,
+ watcher: &dyn Watcher,
) -> Option<(RepositoryWorkDirectory, Arc<dyn GitRepository>)> {
let work_dir_path: Arc<Path> = 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<Path>,
dot_git_path: Arc<Path>,
location_in_repo: Option<Arc<Path>>,
fs: &dyn Fs,
+ watcher: &dyn Watcher,
) -> Option<(RepositoryWorkDirectory, Arc<dyn GitRepository>)> {
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<Path> = 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<Path> = 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<Worktree> {
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,
)
});
@@ -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}"
+ );
}
@@ -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 {}