Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions crates/repo_metadata/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,27 @@ pub fn repo_watch_filter() -> WatchFilter {
)
}

/// Returns a [`WatchFilter`] that combines git-internal path filtering with
/// gitignore-based directory pruning.
///
/// The descend predicate prunes both `.git/` internals (same as [`repo_watch_filter`])
/// and directories matched by the provided gitignore rules (e.g. `node_modules/`,
/// `target/`). This prevents the recursive walk from registering inotify watches on
/// large ignored directory trees that can consume gigabytes of memory.
///
/// The emit predicate is unchanged: it forwards everything outside `.git/` plus
/// allowlisted files inside `.git/`.
#[cfg(feature = "local_fs")]
pub fn repo_watch_filter_with_gitignores(gitignores: Arc<Vec<Gitignore>>) -> WatchFilter {
WatchFilter::with_filter(
Arc::new(move |path: &Path| {
should_watch_directory_in_git_path(path)
&& !matches_gitignores(path, true, gitignores.as_slice(), true)
}),
Arc::new(|path: &Path| !should_ignore_git_path(path)),
)
}

/// Determines whether a file should be parsed by a treesitter query. For now the main criteria is it shouldn't
/// exceed the given file size limit.
pub fn is_file_parsable(path: &Path) -> Result<bool, io::Error> {
Expand Down
5 changes: 3 additions & 2 deletions crates/repo_metadata/src/local_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::{gitignores_for_directory, matches_gitignores, RepoMetadataError};
cfg_if::cfg_if! {
if #[cfg(feature = "local_fs")] {
use notify_debouncer_full::notify::RecursiveMode;
use crate::entry::repo_watch_filter;
use crate::entry::repo_watch_filter_with_gitignores;
use crate::repositories::{DetectedRepositories, DetectedRepositoriesEvent};
use watcher::{BulkFilesystemWatcher, BulkFilesystemWatcherEvent};
use warpui_core::SingletonEntity as _;
Expand Down Expand Up @@ -431,10 +431,11 @@ impl LocalRepoMetadataModel {
{
if let Some(ref watcher) = self.watcher {
let watch_path = local_path.clone();
let gitignores = Arc::new(crate::entry::gitignores_for_directory(&watch_path));
watcher.update(ctx, |watcher, _ctx| {
std::mem::drop(watcher.register_path(
&watch_path,
repo_watch_filter(),
repo_watch_filter_with_gitignores(gitignores),
RecursiveMode::Recursive,
));
});
Expand Down
6 changes: 4 additions & 2 deletions crates/repo_metadata/src/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,14 +321,16 @@ impl DirectoryWatcher {
let local_path = directory_path.to_local_path();
let registration_future = if let Some(ref watcher) = self.watcher {
if let Some(local_path) = local_path.clone() {
let gitignores =
std::sync::Arc::new(crate::entry::gitignores_for_directory(&local_path));
watcher.update(ctx, |watcher, _ctx| {
use notify_debouncer_full::notify::RecursiveMode;

use crate::entry::repo_watch_filter;
use crate::entry::repo_watch_filter_with_gitignores;

Some(watcher.register_path(
&local_path,
repo_watch_filter(),
repo_watch_filter_with_gitignores(gitignores),
RecursiveMode::Recursive,
))
})
Expand Down
25 changes: 19 additions & 6 deletions crates/warp_files/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,12 +362,19 @@ impl FileModel {
.insert(file_path.to_path_buf(), repo_root);
WatcherType::Repository
} else {
// Fallback to individual file watcher
// Fallback to individual file watcher.
// Watch the parent directory (NonRecursive) instead of the file
// itself so the watch survives editors that use a
// delete+create/rename pattern (vim, sed -i, etc.).
let watch_path = file_path
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| file_path.to_path_buf());
self.watcher.update(ctx, |watcher, _ctx| {
std::mem::drop(watcher.register_path(
file_path,
&watch_path,
WatchFilter::accept_all(),
RecursiveMode::Recursive,
RecursiveMode::NonRecursive,
));
});
WatcherType::Individual
Expand Down Expand Up @@ -1152,12 +1159,18 @@ impl FileModel {
}
}

// Register individual file watcher
// Register individual file watcher on the parent directory
// (NonRecursive) so the watch survives delete+create/rename
// patterns and avoids creating recursive watches on the path.
let watch_path = path
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_else(|| path.clone());
self.watcher.update(ctx, |watcher, _ctx| {
std::mem::drop(watcher.register_path(
&path,
&watch_path,
WatchFilter::accept_all(),
RecursiveMode::Recursive,
RecursiveMode::NonRecursive,
));
});
}
Expand Down