match at repo root and add test
This commit is contained in:
parent
ed685af557
commit
1d990806df
3 changed files with 141 additions and 57 deletions
|
@ -5,6 +5,7 @@ use std::{ffi::OsStr, path::Path, sync::Arc};
|
||||||
pub enum IgnoreStack {
|
pub enum IgnoreStack {
|
||||||
None,
|
None,
|
||||||
Global {
|
Global {
|
||||||
|
repo_root: Option<Arc<Path>>,
|
||||||
ignore: Arc<Gitignore>,
|
ignore: Arc<Gitignore>,
|
||||||
},
|
},
|
||||||
Some {
|
Some {
|
||||||
|
@ -24,8 +25,8 @@ impl IgnoreStack {
|
||||||
Arc::new(Self::All)
|
Arc::new(Self::All)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn global(ignore: Arc<Gitignore>) -> Arc<Self> {
|
pub fn global(repo_root: Option<Arc<Path>>, ignore: Arc<Gitignore>) -> Arc<Self> {
|
||||||
Arc::new(Self::Global { ignore })
|
Arc::new(Self::Global { repo_root, ignore })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append(self: Arc<Self>, abs_base_path: Arc<Path>, ignore: Arc<Gitignore>) -> Arc<Self> {
|
pub fn append(self: Arc<Self>, abs_base_path: Arc<Path>, ignore: Arc<Gitignore>) -> Arc<Self> {
|
||||||
|
@ -47,11 +48,24 @@ impl IgnoreStack {
|
||||||
match self {
|
match self {
|
||||||
Self::None => false,
|
Self::None => false,
|
||||||
Self::All => true,
|
Self::All => true,
|
||||||
Self::Global { ignore } => match ignore.matched(abs_path, is_dir) {
|
Self::Global { repo_root, ignore } => {
|
||||||
ignore::Match::None => false,
|
let combined_path;
|
||||||
ignore::Match::Ignore(_) => true,
|
let abs_path = if let Some(repo_root) = repo_root {
|
||||||
ignore::Match::Whitelist(_) => false,
|
combined_path = ignore.path().join(
|
||||||
},
|
abs_path
|
||||||
|
.strip_prefix(repo_root)
|
||||||
|
.expect("repo root should be a parent of matched path"),
|
||||||
|
);
|
||||||
|
&combined_path
|
||||||
|
} else {
|
||||||
|
abs_path
|
||||||
|
};
|
||||||
|
match ignore.matched(abs_path, is_dir) {
|
||||||
|
ignore::Match::None => false,
|
||||||
|
ignore::Match::Ignore(_) => true,
|
||||||
|
ignore::Match::Whitelist(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::Some {
|
Self::Some {
|
||||||
abs_base_path,
|
abs_base_path,
|
||||||
ignore,
|
ignore,
|
||||||
|
|
|
@ -3,7 +3,7 @@ mod worktree_settings;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod worktree_tests;
|
mod worktree_tests;
|
||||||
|
|
||||||
use ::ignore::gitignore::{Gitignore, GitignoreBuilder, gitconfig_excludes_path};
|
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{HashMap, HashSet, VecDeque};
|
use collections::{HashMap, HashSet, VecDeque};
|
||||||
|
@ -65,7 +65,7 @@ use std::{
|
||||||
use sum_tree::{Bias, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet, Unit};
|
use sum_tree::{Bias, Edit, KeyedItem, SeekTarget, SumTree, Summary, TreeMap, TreeSet, Unit};
|
||||||
use text::{LineEnding, Rope};
|
use text::{LineEnding, Rope};
|
||||||
use util::{
|
use util::{
|
||||||
ResultExt, path,
|
ResultExt,
|
||||||
paths::{PathMatcher, SanitizedPath, home_dir},
|
paths::{PathMatcher, SanitizedPath, home_dir},
|
||||||
};
|
};
|
||||||
pub use worktree_settings::WorktreeSettings;
|
pub use worktree_settings::WorktreeSettings;
|
||||||
|
@ -336,23 +336,6 @@ impl Default for WorkDirectory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
|
|
||||||
pub struct WorkDirectoryEntry(ProjectEntryId);
|
|
||||||
|
|
||||||
impl Deref for WorkDirectoryEntry {
|
|
||||||
type Target = ProjectEntryId;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ProjectEntryId> for WorkDirectoryEntry {
|
|
||||||
fn from(value: ProjectEntryId) -> Self {
|
|
||||||
WorkDirectoryEntry(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LocalSnapshot {
|
pub struct LocalSnapshot {
|
||||||
snapshot: Snapshot,
|
snapshot: Snapshot,
|
||||||
|
@ -2794,8 +2777,14 @@ impl LocalSnapshot {
|
||||||
inodes
|
inodes
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ignore_stack_for_abs_path(&self, abs_path: &Path, is_dir: bool) -> Arc<IgnoreStack> {
|
fn ignore_stack_for_abs_path(
|
||||||
|
&self,
|
||||||
|
abs_path: &Path,
|
||||||
|
is_dir: bool,
|
||||||
|
fs: &dyn Fs,
|
||||||
|
) -> Arc<IgnoreStack> {
|
||||||
let mut new_ignores = Vec::new();
|
let mut new_ignores = Vec::new();
|
||||||
|
let mut repo_root = None;
|
||||||
for (index, ancestor) in abs_path.ancestors().enumerate() {
|
for (index, ancestor) in abs_path.ancestors().enumerate() {
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
|
if let Some((ignore, _)) = self.ignores_by_parent_abs_path.get(ancestor) {
|
||||||
|
@ -2804,18 +2793,17 @@ impl LocalSnapshot {
|
||||||
new_ignores.push((ancestor, None));
|
new_ignores.push((ancestor, None));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ancestor.join(*DOT_GIT).exists() {
|
let metadata = smol::block_on(fs.metadata(&ancestor.join(*DOT_GIT)))
|
||||||
// FIXME HERE
|
.ok()
|
||||||
|
.flatten();
|
||||||
|
if metadata.is_some() {
|
||||||
|
repo_root = Some(Arc::from(ancestor));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME the plan for global
|
|
||||||
// - the abs_base_path for the ignore is ""
|
|
||||||
// - match relative to dot git parent using existing stripping logic
|
|
||||||
// - global variant??
|
|
||||||
let mut ignore_stack = if let Some(global_gitignore) = self.global_gitignore.clone() {
|
let mut ignore_stack = if let Some(global_gitignore) = self.global_gitignore.clone() {
|
||||||
IgnoreStack::global(global_gitignore)
|
IgnoreStack::global(repo_root, global_gitignore)
|
||||||
} else {
|
} else {
|
||||||
IgnoreStack::none()
|
IgnoreStack::none()
|
||||||
};
|
};
|
||||||
|
@ -2945,9 +2933,15 @@ impl BackgroundScannerState {
|
||||||
.any(|p| entry.path.starts_with(p))
|
.any(|p| entry.path.starts_with(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enqueue_scan_dir(&self, abs_path: Arc<Path>, entry: &Entry, scan_job_tx: &Sender<ScanJob>) {
|
fn enqueue_scan_dir(
|
||||||
|
&self,
|
||||||
|
abs_path: Arc<Path>,
|
||||||
|
entry: &Entry,
|
||||||
|
scan_job_tx: &Sender<ScanJob>,
|
||||||
|
fs: &dyn Fs,
|
||||||
|
) {
|
||||||
let path = entry.path.clone();
|
let path = entry.path.clone();
|
||||||
let ignore_stack = self.snapshot.ignore_stack_for_abs_path(&abs_path, true);
|
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);
|
let mut ancestor_inodes = self.snapshot.ancestor_inodes_for_path(&path);
|
||||||
|
|
||||||
if !ancestor_inodes.contains(&entry.inode) {
|
if !ancestor_inodes.contains(&entry.inode) {
|
||||||
|
@ -3888,14 +3882,21 @@ impl BackgroundScanner {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
state.snapshot.scan_id += 1;
|
state.snapshot.scan_id += 1;
|
||||||
if let Some(mut root_entry) = state.snapshot.root_entry().cloned() {
|
if let Some(mut root_entry) = state.snapshot.root_entry().cloned() {
|
||||||
let ignore_stack = state
|
let ignore_stack = state.snapshot.ignore_stack_for_abs_path(
|
||||||
.snapshot
|
root_abs_path.as_path(),
|
||||||
.ignore_stack_for_abs_path(root_abs_path.as_path(), true);
|
true,
|
||||||
|
self.fs.as_ref(),
|
||||||
|
);
|
||||||
if ignore_stack.is_abs_path_ignored(root_abs_path.as_path(), true) {
|
if ignore_stack.is_abs_path_ignored(root_abs_path.as_path(), true) {
|
||||||
root_entry.is_ignored = true;
|
root_entry.is_ignored = true;
|
||||||
state.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref());
|
state.insert_entry(root_entry.clone(), self.fs.as_ref(), self.watcher.as_ref());
|
||||||
}
|
}
|
||||||
state.enqueue_scan_dir(root_abs_path.into(), &root_entry, &scan_job_tx);
|
state.enqueue_scan_dir(
|
||||||
|
root_abs_path.into(),
|
||||||
|
&root_entry,
|
||||||
|
&scan_job_tx,
|
||||||
|
self.fs.as_ref(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4183,7 +4184,10 @@ impl BackgroundScanner {
|
||||||
state.snapshot.global_gitignore = ignore;
|
state.snapshot.global_gitignore = ignore;
|
||||||
// FIXME is_dir (do we care?)
|
// FIXME is_dir (do we care?)
|
||||||
let abs_path = state.snapshot.abs_path().clone();
|
let abs_path = state.snapshot.abs_path().clone();
|
||||||
let ignore_stack = state.snapshot.ignore_stack_for_abs_path(&abs_path, true);
|
let ignore_stack =
|
||||||
|
state
|
||||||
|
.snapshot
|
||||||
|
.ignore_stack_for_abs_path(&abs_path, true, self.fs.as_ref());
|
||||||
(state.snapshot.clone(), ignore_stack, abs_path)
|
(state.snapshot.clone(), ignore_stack, abs_path)
|
||||||
};
|
};
|
||||||
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
let (scan_job_tx, scan_job_rx) = channel::unbounded();
|
||||||
|
@ -4207,7 +4211,12 @@ impl BackgroundScanner {
|
||||||
if let Some(entry) = state.snapshot.entry_for_path(ancestor) {
|
if let Some(entry) = state.snapshot.entry_for_path(ancestor) {
|
||||||
if entry.kind == EntryKind::UnloadedDir {
|
if entry.kind == EntryKind::UnloadedDir {
|
||||||
let abs_path = root_path.as_path().join(ancestor);
|
let abs_path = root_path.as_path().join(ancestor);
|
||||||
state.enqueue_scan_dir(abs_path.into(), entry, &scan_job_tx);
|
state.enqueue_scan_dir(
|
||||||
|
abs_path.into(),
|
||||||
|
entry,
|
||||||
|
&scan_job_tx,
|
||||||
|
self.fs.as_ref(),
|
||||||
|
);
|
||||||
state.paths_to_scan.insert(path.clone());
|
state.paths_to_scan.insert(path.clone());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -4585,9 +4594,11 @@ impl BackgroundScanner {
|
||||||
let abs_path: Arc<Path> = root_abs_path.as_path().join(path).into();
|
let abs_path: Arc<Path> = root_abs_path.as_path().join(path).into();
|
||||||
match metadata {
|
match metadata {
|
||||||
Ok(Some((metadata, canonical_path))) => {
|
Ok(Some((metadata, canonical_path))) => {
|
||||||
let ignore_stack = state
|
let ignore_stack = state.snapshot.ignore_stack_for_abs_path(
|
||||||
.snapshot
|
&abs_path,
|
||||||
.ignore_stack_for_abs_path(&abs_path, metadata.is_dir);
|
metadata.is_dir,
|
||||||
|
self.fs.as_ref(),
|
||||||
|
);
|
||||||
let is_external = !canonical_path.starts_with(&root_canonical_path);
|
let is_external = !canonical_path.starts_with(&root_canonical_path);
|
||||||
let mut fs_entry = Entry::new(
|
let mut fs_entry = Entry::new(
|
||||||
path.clone(),
|
path.clone(),
|
||||||
|
@ -4612,7 +4623,12 @@ impl BackgroundScanner {
|
||||||
|| (fs_entry.path.as_os_str().is_empty()
|
|| (fs_entry.path.as_os_str().is_empty()
|
||||||
&& abs_path.file_name() == Some(*DOT_GIT))
|
&& abs_path.file_name() == Some(*DOT_GIT))
|
||||||
{
|
{
|
||||||
state.enqueue_scan_dir(abs_path, &fs_entry, scan_queue_tx);
|
state.enqueue_scan_dir(
|
||||||
|
abs_path,
|
||||||
|
&fs_entry,
|
||||||
|
scan_queue_tx,
|
||||||
|
self.fs.as_ref(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
fs_entry.kind = EntryKind::UnloadedDir;
|
fs_entry.kind = EntryKind::UnloadedDir;
|
||||||
}
|
}
|
||||||
|
@ -4756,7 +4772,11 @@ impl BackgroundScanner {
|
||||||
{
|
{
|
||||||
ignores_to_update.next().unwrap();
|
ignores_to_update.next().unwrap();
|
||||||
}
|
}
|
||||||
let ignore_stack = prev_snapshot.ignore_stack_for_abs_path(&parent_abs_path, true);
|
let ignore_stack = prev_snapshot.ignore_stack_for_abs_path(
|
||||||
|
&parent_abs_path,
|
||||||
|
true,
|
||||||
|
self.fs.as_ref(),
|
||||||
|
);
|
||||||
Some((parent_abs_path, ignore_stack))
|
Some((parent_abs_path, ignore_stack))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4796,7 +4816,12 @@ impl BackgroundScanner {
|
||||||
if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() {
|
if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() {
|
||||||
let state = self.state.lock();
|
let state = self.state.lock();
|
||||||
if state.should_scan_directory(&entry) {
|
if state.should_scan_directory(&entry) {
|
||||||
state.enqueue_scan_dir(abs_path.clone(), &entry, &job.scan_queue);
|
state.enqueue_scan_dir(
|
||||||
|
abs_path.clone(),
|
||||||
|
&entry,
|
||||||
|
&job.scan_queue,
|
||||||
|
self.fs.as_ref(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4829,9 +4854,6 @@ impl BackgroundScanner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !entries_by_path_edits.is_empty() {
|
|
||||||
dbg!(&entries_by_path_edits);
|
|
||||||
}
|
|
||||||
state
|
state
|
||||||
.snapshot
|
.snapshot
|
||||||
.entries_by_path
|
.entries_by_path
|
||||||
|
@ -5665,10 +5687,12 @@ fn discover_git_paths(dot_git_abs_path: &Arc<Path>, fs: &dyn Fs) -> (Arc<Path>,
|
||||||
(repository_dir_abs_path, common_dir_abs_path)
|
(repository_dir_abs_path, common_dir_abs_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
fn global_gitignore_path() -> Option<Arc<Path>> {
|
fn global_gitignore_path() -> Option<Arc<Path>> {
|
||||||
if cfg!(test) {
|
Some(Path::new(util::path!("/home/zed/.config/git/ignore")).into())
|
||||||
Some(Path::new(path!("/home/zed/.config/git/ignore")).into())
|
}
|
||||||
} else {
|
|
||||||
gitconfig_excludes_path().map(Into::into)
|
#[cfg(not(test))]
|
||||||
}
|
fn global_gitignore_path() -> Option<Arc<Path>> {
|
||||||
|
::ignore::gitignore::gitconfig_excludes_path().map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2077,7 +2077,6 @@ async fn test_repository_above_root(executor: BackgroundExecutor, cx: &mut TestA
|
||||||
});
|
});
|
||||||
pretty_assertions::assert_eq!(repos, [Path::new(path!("/root")).into()]);
|
pretty_assertions::assert_eq!(repos, [Path::new(path!("/root")).into()]);
|
||||||
|
|
||||||
eprintln!(">>>>>>>>>> touch");
|
|
||||||
fs.touch_path(path!("/root/subproject")).await;
|
fs.touch_path(path!("/root/subproject")).await;
|
||||||
worktree
|
worktree
|
||||||
.update(cx, |worktree, _| {
|
.update(cx, |worktree, _| {
|
||||||
|
@ -2098,6 +2097,53 @@ async fn test_repository_above_root(executor: BackgroundExecutor, cx: &mut TestA
|
||||||
pretty_assertions::assert_eq!(repos, [Path::new(path!("/root")).into()]);
|
pretty_assertions::assert_eq!(repos, [Path::new(path!("/root")).into()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_global_gitignore(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(executor);
|
||||||
|
fs.insert_tree(
|
||||||
|
path!("/home/zed"),
|
||||||
|
json!({
|
||||||
|
".config": {
|
||||||
|
"git": {
|
||||||
|
"ignore": "foo\n/bar\nbaz\n"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
".git": {},
|
||||||
|
".gitignore": "!baz",
|
||||||
|
"foo": "",
|
||||||
|
"bar": "",
|
||||||
|
"sub": {
|
||||||
|
"bar": ""
|
||||||
|
},
|
||||||
|
"baz": ""
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let worktree = Worktree::local(
|
||||||
|
path!("/home/zed/project").as_ref(),
|
||||||
|
true,
|
||||||
|
fs.clone(),
|
||||||
|
Arc::default(),
|
||||||
|
&mut cx.to_async(),
|
||||||
|
)
|
||||||
|
.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, &[], &["foo", "bar"], &["sub/bar", "baz"], &[]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn check_worktree_entries(
|
fn check_worktree_entries(
|
||||||
tree: &Worktree,
|
tree: &Worktree,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue