pass down the repo root, and handle .git events
This commit is contained in:
parent
17822762b7
commit
ca458cc529
3 changed files with 119 additions and 102 deletions
|
@ -3,6 +3,8 @@ use std::{ffi::OsStr, path::Path, sync::Arc};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct IgnoreStack {
|
pub struct IgnoreStack {
|
||||||
|
/// Rooted globs (like /foo or foo/bar) in the global core.excludesFile are matched against the nearest containing repository root,
|
||||||
|
/// so we
|
||||||
pub repo_root: Option<Arc<Path>>,
|
pub repo_root: Option<Arc<Path>>,
|
||||||
pub top: Arc<IgnoreStackEntry>,
|
pub top: Arc<IgnoreStackEntry>,
|
||||||
}
|
}
|
||||||
|
@ -10,6 +12,7 @@ pub struct IgnoreStack {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum IgnoreStackEntry {
|
pub enum IgnoreStackEntry {
|
||||||
None,
|
None,
|
||||||
|
/// core.excludesFile
|
||||||
Global {
|
Global {
|
||||||
ignore: Arc<Gitignore>,
|
ignore: Arc<Gitignore>,
|
||||||
},
|
},
|
||||||
|
|
|
@ -65,7 +65,7 @@ use std::{
|
||||||
use sum_tree::{Bias, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet};
|
use sum_tree::{Bias, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet};
|
||||||
use text::{LineEnding, Rope};
|
use text::{LineEnding, Rope};
|
||||||
use util::{
|
use util::{
|
||||||
ResultExt,
|
ResultExt, debug_panic,
|
||||||
paths::{PathMatcher, SanitizedPath, home_dir},
|
paths::{PathMatcher, SanitizedPath, home_dir},
|
||||||
};
|
};
|
||||||
pub use worktree_settings::WorktreeSettings;
|
pub use worktree_settings::WorktreeSettings;
|
||||||
|
@ -2795,11 +2795,9 @@ impl LocalSnapshot {
|
||||||
} else {
|
} else {
|
||||||
IgnoreStack::none()
|
IgnoreStack::none()
|
||||||
};
|
};
|
||||||
dbg!(&abs_path, &repo_root);
|
|
||||||
ignore_stack.repo_root = repo_root;
|
ignore_stack.repo_root = repo_root;
|
||||||
for (parent_abs_path, ignore) in new_ignores.into_iter().rev() {
|
for (parent_abs_path, ignore) in new_ignores.into_iter().rev() {
|
||||||
if ignore_stack.is_abs_path_ignored(parent_abs_path, true) {
|
if ignore_stack.is_abs_path_ignored(parent_abs_path, true) {
|
||||||
dbg!("ALL");
|
|
||||||
ignore_stack = IgnoreStack::all();
|
ignore_stack = IgnoreStack::all();
|
||||||
break;
|
break;
|
||||||
} else if let Some(ignore) = ignore {
|
} else if let Some(ignore) = ignore {
|
||||||
|
@ -2808,7 +2806,6 @@ impl LocalSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
|
if ignore_stack.is_abs_path_ignored(abs_path, is_dir) {
|
||||||
dbg!("ALL");
|
|
||||||
ignore_stack = IgnoreStack::all();
|
ignore_stack = IgnoreStack::all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4149,11 +4146,20 @@ impl BackgroundScanner {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
self.update_ignore_statuses(scan_job_tx).await;
|
let affected_repo_roots = if !dot_git_abs_paths.is_empty() {
|
||||||
self.scan_dirs(false, scan_job_rx).await;
|
self.update_git_repositories(dot_git_abs_paths)
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
if !dot_git_abs_paths.is_empty() {
|
{
|
||||||
self.update_git_repositories(dot_git_abs_paths);
|
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);
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -4362,17 +4368,12 @@ impl BackgroundScanner {
|
||||||
swap_to_front(&mut child_paths, *DOT_GIT);
|
swap_to_front(&mut child_paths, *DOT_GIT);
|
||||||
|
|
||||||
if let Some(path) = child_paths.first()
|
if let Some(path) = child_paths.first()
|
||||||
&& path == *DOT_GIT
|
&& path.ends_with(*DOT_GIT)
|
||||||
{
|
{
|
||||||
ignore_stack.repo_root = Some(job.abs_path.clone());
|
ignore_stack.repo_root = Some(job.abs_path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since we check for the presence of .git first, we can register this
|
|
||||||
// directory as a repo root on the ignore stack before we call is_abs_path_ignored below.
|
|
||||||
let mut repo_root = None;
|
|
||||||
dbg!("------");
|
|
||||||
for child_abs_path in child_paths {
|
for child_abs_path in child_paths {
|
||||||
dbg!(&child_abs_path);
|
|
||||||
let child_abs_path: Arc<Path> = child_abs_path.into();
|
let child_abs_path: Arc<Path> = child_abs_path.into();
|
||||||
let child_name = child_abs_path.file_name().unwrap();
|
let child_name = child_abs_path.file_name().unwrap();
|
||||||
let child_path: Arc<Path> = job.path.join(child_name).into();
|
let child_path: Arc<Path> = job.path.join(child_name).into();
|
||||||
|
@ -4471,7 +4472,6 @@ impl BackgroundScanner {
|
||||||
path: child_path,
|
path: child_path,
|
||||||
is_external: child_entry.is_external,
|
is_external: child_entry.is_external,
|
||||||
ignore_stack: if child_entry.is_ignored {
|
ignore_stack: if child_entry.is_ignored {
|
||||||
dbg!("ALL");
|
|
||||||
IgnoreStack::all()
|
IgnoreStack::all()
|
||||||
} else {
|
} else {
|
||||||
ignore_stack.clone()
|
ignore_stack.clone()
|
||||||
|
@ -4737,9 +4737,10 @@ impl BackgroundScanner {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_ignore_statuses(&self, scan_job_tx: Sender<ScanJob>) {
|
fn ignores_needing_update(&self) -> Vec<Arc<Path>> {
|
||||||
let mut ignores_to_update = Vec::new();
|
let mut ignores_to_update = Vec::new();
|
||||||
let prev_snapshot = {
|
|
||||||
|
{
|
||||||
let snapshot = &mut self.state.lock().snapshot;
|
let snapshot = &mut self.state.lock().snapshot;
|
||||||
let abs_path = snapshot.abs_path.clone();
|
let abs_path = snapshot.abs_path.clone();
|
||||||
snapshot
|
snapshot
|
||||||
|
@ -4760,33 +4761,31 @@ impl BackgroundScanner {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
snapshot.clone()
|
ignores_to_update
|
||||||
};
|
}
|
||||||
|
|
||||||
ignores_to_update.sort_unstable();
|
fn order_ignores(
|
||||||
let mut ignores_to_update = ignores_to_update.into_iter().peekable();
|
&self,
|
||||||
let ignores_to_update = std::iter::from_fn({
|
mut ignores: Vec<Arc<Path>>,
|
||||||
let prev_snapshot = prev_snapshot.clone();
|
) -> impl use<> + Iterator<Item = (Arc<Path>, IgnoreStack)> {
|
||||||
move || {
|
let fs = self.fs.clone();
|
||||||
let parent_abs_path = ignores_to_update.next()?;
|
let snapshot = self.state.lock().snapshot.clone();
|
||||||
while ignores_to_update
|
ignores.sort_unstable();
|
||||||
.peek()
|
let mut ignores_to_update = ignores.into_iter().peekable();
|
||||||
.map_or(false, |p| p.starts_with(&parent_abs_path))
|
std::iter::from_fn(move || {
|
||||||
{
|
let parent_abs_path = ignores_to_update.next()?;
|
||||||
ignores_to_update.next().unwrap();
|
while ignores_to_update
|
||||||
}
|
.peek()
|
||||||
let ignore_stack = prev_snapshot.ignore_stack_for_abs_path(
|
.map_or(false, |p| p.starts_with(&parent_abs_path))
|
||||||
&parent_abs_path,
|
{
|
||||||
true,
|
ignores_to_update.next().unwrap();
|
||||||
self.fs.as_ref(),
|
|
||||||
);
|
|
||||||
Some((parent_abs_path, ignore_stack))
|
|
||||||
}
|
}
|
||||||
});
|
let ignore_stack =
|
||||||
|
snapshot.ignore_stack_for_abs_path(&parent_abs_path, true, fs.as_ref());
|
||||||
self.update_ignore_statuses_for_paths(scan_job_tx, prev_snapshot, ignores_to_update)
|
Some((parent_abs_path, ignore_stack))
|
||||||
.await;
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
|
async fn update_ignore_status(&self, job: UpdateIgnoreStatusJob, snapshot: &LocalSnapshot) {
|
||||||
|
@ -4804,20 +4803,20 @@ impl BackgroundScanner {
|
||||||
.strip_prefix(snapshot.abs_path.as_path())
|
.strip_prefix(snapshot.abs_path.as_path())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if snapshot.entry_for_path(path.join(*DOT_GIT)).is_some() {
|
// FIXME understand the bug that causes .git to not have a snapshot entry here in the test
|
||||||
dbg!("HERE");
|
if let Ok(Some(metadata)) = smol::block_on(self.fs.metadata(&job.abs_path.join(*DOT_GIT)))
|
||||||
ignore_stack.repo_root = Some(job.abs_path.join(*DOT_GIT).into());
|
&& metadata.is_dir
|
||||||
|
{
|
||||||
|
ignore_stack.repo_root = Some(job.abs_path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
for mut entry in snapshot.child_entries(path).cloned() {
|
for mut entry in snapshot.child_entries(path).cloned() {
|
||||||
dbg!(&path);
|
|
||||||
let was_ignored = entry.is_ignored;
|
let was_ignored = entry.is_ignored;
|
||||||
let abs_path: Arc<Path> = snapshot.abs_path().join(&entry.path).into();
|
let abs_path: Arc<Path> = snapshot.abs_path().join(&entry.path).into();
|
||||||
entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
|
entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, entry.is_dir());
|
||||||
|
|
||||||
if entry.is_dir() {
|
if entry.is_dir() {
|
||||||
let child_ignore_stack = if entry.is_ignored {
|
let child_ignore_stack = if entry.is_ignored {
|
||||||
dbg!("ALL");
|
|
||||||
IgnoreStack::all()
|
IgnoreStack::all()
|
||||||
} else {
|
} else {
|
||||||
ignore_stack.clone()
|
ignore_stack.clone()
|
||||||
|
@ -4872,10 +4871,11 @@ impl BackgroundScanner {
|
||||||
state.snapshot.entries_by_id.edit(entries_by_id_edits, &());
|
state.snapshot.entries_by_id.edit(entries_by_id_edits, &());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_git_repositories(&self, dot_git_paths: Vec<PathBuf>) {
|
fn update_git_repositories(&self, dot_git_paths: Vec<PathBuf>) -> Vec<Arc<Path>> {
|
||||||
log::trace!("reloading repositories: {dot_git_paths:?}");
|
log::trace!("reloading repositories: {dot_git_paths:?}");
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
let scan_id = state.snapshot.scan_id;
|
let scan_id = state.snapshot.scan_id;
|
||||||
|
let mut affected_repo_roots = Vec::new();
|
||||||
for dot_git_dir in dot_git_paths {
|
for dot_git_dir in dot_git_paths {
|
||||||
let existing_repository_entry =
|
let existing_repository_entry =
|
||||||
state
|
state
|
||||||
|
@ -4895,8 +4895,12 @@ impl BackgroundScanner {
|
||||||
match existing_repository_entry {
|
match existing_repository_entry {
|
||||||
None => {
|
None => {
|
||||||
let Ok(relative) = dot_git_dir.strip_prefix(state.snapshot.abs_path()) else {
|
let Ok(relative) = dot_git_dir.strip_prefix(state.snapshot.abs_path()) else {
|
||||||
return;
|
debug_panic!(
|
||||||
|
"update_git_repositories called with .git directory outside the worktree root"
|
||||||
|
);
|
||||||
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
affected_repo_roots.push(dot_git_dir.parent().unwrap().into());
|
||||||
state.insert_git_repository(
|
state.insert_git_repository(
|
||||||
relative.into(),
|
relative.into(),
|
||||||
self.fs.as_ref(),
|
self.fs.as_ref(),
|
||||||
|
@ -4936,7 +4940,15 @@ impl BackgroundScanner {
|
||||||
|
|
||||||
snapshot
|
snapshot
|
||||||
.git_repositories
|
.git_repositories
|
||||||
.retain(|work_directory_id, _| ids_to_preserve.contains(work_directory_id));
|
.retain(|work_directory_id, entry| {
|
||||||
|
let preserve = ids_to_preserve.contains(work_directory_id);
|
||||||
|
if !preserve {
|
||||||
|
affected_repo_roots.push(entry.dot_git_abs_path.parent().unwrap().into());
|
||||||
|
}
|
||||||
|
preserve
|
||||||
|
});
|
||||||
|
|
||||||
|
affected_repo_roots
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn progress_timer(&self, running: bool) {
|
async fn progress_timer(&self, running: bool) {
|
||||||
|
|
|
@ -1984,58 +1984,6 @@ fn test_unrelativize() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_gitignore_with_git_info_exclude(cx: &mut TestAppContext) {
|
|
||||||
init_test(cx);
|
|
||||||
|
|
||||||
// Test that .git/info/exclude entries are properly recognized
|
|
||||||
let fs = FakeFs::new(cx.background_executor.clone());
|
|
||||||
fs.insert_tree(
|
|
||||||
"/root",
|
|
||||||
json!({
|
|
||||||
".git": {
|
|
||||||
"info": {
|
|
||||||
"exclude": "excluded_file.txt\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
".gitignore": "local_ignored.txt\n",
|
|
||||||
"normal_file.txt": "normal file content",
|
|
||||||
"local_ignored.txt": "locally ignored content",
|
|
||||||
"excluded_file.txt": "excluded content",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let tree = Worktree::local(
|
|
||||||
Path::new("/root"),
|
|
||||||
true,
|
|
||||||
fs.clone(),
|
|
||||||
Default::default(),
|
|
||||||
&mut cx.to_async(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
tree.read_with(cx, |tree, _| {
|
|
||||||
check_worktree_entries(
|
|
||||||
tree,
|
|
||||||
&[],
|
|
||||||
&[
|
|
||||||
"local_ignored.txt", // Ignored by .gitignore
|
|
||||||
"excluded_file.txt", // Ignored by .git/info/exclude
|
|
||||||
],
|
|
||||||
&[
|
|
||||||
"normal_file.txt", // Not ignored
|
|
||||||
".gitignore", // Not ignored
|
|
||||||
],
|
|
||||||
&[],
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_repository_above_root(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
async fn test_repository_above_root(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
@ -2143,6 +2091,8 @@ async fn test_global_gitignore(executor: BackgroundExecutor, cx: &mut TestAppCon
|
||||||
.await;
|
.await;
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
// .gitignore overrides excludesFile, and anchored paths in excludesFile are resolved
|
||||||
|
// relative to the nearest containing repository
|
||||||
worktree.update(cx, |worktree, _cx| {
|
worktree.update(cx, |worktree, _cx| {
|
||||||
check_worktree_entries(
|
check_worktree_entries(
|
||||||
worktree,
|
worktree,
|
||||||
|
@ -2151,7 +2101,59 @@ async fn test_global_gitignore(executor: BackgroundExecutor, cx: &mut TestAppCon
|
||||||
&["sub/bar", "baz"],
|
&["sub/bar", "baz"],
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
// Ignore statuses are updated when excludesFile changes
|
||||||
|
fs.write(
|
||||||
|
Path::new(path!("/home/zed/.config/git/ignore")),
|
||||||
|
"/bar\nbaz\n".as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
worktree
|
||||||
|
.update(cx, |worktree, _| {
|
||||||
|
worktree.as_local().unwrap().scan_complete()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
worktree.update(cx, |worktree, _cx| {
|
||||||
|
check_worktree_entries(
|
||||||
|
worktree,
|
||||||
|
&[],
|
||||||
|
&["bar", "subrepo/bar"],
|
||||||
|
&["foo", "sub/bar", "baz"],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME statuses are updated when .git added/removed
|
||||||
|
|
||||||
|
fs.remove_dir(
|
||||||
|
Path::new(path!("/home/zed/project/subrepo/.git")),
|
||||||
|
RemoveOptions {
|
||||||
|
recursive: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
worktree
|
||||||
|
.update(cx, |worktree, _| {
|
||||||
|
worktree.as_local().unwrap().scan_complete()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
worktree.update(cx, |worktree, _cx| {
|
||||||
|
check_worktree_entries(
|
||||||
|
worktree,
|
||||||
|
&[],
|
||||||
|
&["bar"],
|
||||||
|
&["foo", "sub/bar", "baz", "subrepo/bar"],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue