Merge branch 'main' into fix-broken-lsp-installations
This commit is contained in:
commit
48bed2ee03
516 changed files with 12747 additions and 3368 deletions
|
@ -64,7 +64,7 @@ use std::{
|
|||
mem,
|
||||
num::NonZeroU32,
|
||||
ops::Range,
|
||||
path::{Component, Path, PathBuf},
|
||||
path::{self, Component, Path, PathBuf},
|
||||
process::Stdio,
|
||||
rc::Rc,
|
||||
str,
|
||||
|
@ -481,6 +481,7 @@ impl Project {
|
|||
client.add_model_request_handler(Self::handle_rename_project_entry);
|
||||
client.add_model_request_handler(Self::handle_copy_project_entry);
|
||||
client.add_model_request_handler(Self::handle_delete_project_entry);
|
||||
client.add_model_request_handler(Self::handle_expand_project_entry);
|
||||
client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
|
||||
client.add_model_request_handler(Self::handle_apply_code_action);
|
||||
client.add_model_request_handler(Self::handle_on_type_formatting);
|
||||
|
@ -1075,6 +1076,40 @@ impl Project {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn expand_entry(
|
||||
&mut self,
|
||||
worktree_id: WorktreeId,
|
||||
entry_id: ProjectEntryId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let worktree = self.worktree_for_id(worktree_id, cx)?;
|
||||
if self.is_local() {
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
worktree.as_local_mut().unwrap().expand_entry(entry_id, cx)
|
||||
})
|
||||
} else {
|
||||
let worktree = worktree.downgrade();
|
||||
let request = self.client.request(proto::ExpandProjectEntry {
|
||||
project_id: self.remote_id().unwrap(),
|
||||
entry_id: entry_id.to_proto(),
|
||||
});
|
||||
Some(cx.spawn_weak(|_, mut cx| async move {
|
||||
let response = request.await?;
|
||||
if let Some(worktree) = worktree.upgrade(&cx) {
|
||||
worktree
|
||||
.update(&mut cx, |worktree, _| {
|
||||
worktree
|
||||
.as_remote_mut()
|
||||
.unwrap()
|
||||
.wait_for_snapshot(response.worktree_scan_id as usize)
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
if self.client_state.is_some() {
|
||||
return Err(anyhow!("project was already shared"));
|
||||
|
@ -3305,23 +3340,44 @@ impl Project {
|
|||
for watcher in params.watchers {
|
||||
for worktree in &self.worktrees {
|
||||
if let Some(worktree) = worktree.upgrade(cx) {
|
||||
let worktree = worktree.read(cx);
|
||||
if let Some(abs_path) = worktree.abs_path().to_str() {
|
||||
if let Some(suffix) = match &watcher.glob_pattern {
|
||||
lsp::GlobPattern::String(s) => s,
|
||||
lsp::GlobPattern::Relative(rp) => &rp.pattern,
|
||||
}
|
||||
.strip_prefix(abs_path)
|
||||
.and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR))
|
||||
{
|
||||
if let Some(glob) = Glob::new(suffix).log_err() {
|
||||
builders
|
||||
.entry(worktree.id())
|
||||
.or_insert_with(|| GlobSetBuilder::new())
|
||||
.add(glob);
|
||||
let glob_is_inside_worktree = worktree.update(cx, |tree, _| {
|
||||
if let Some(abs_path) = tree.abs_path().to_str() {
|
||||
let relative_glob_pattern = match &watcher.glob_pattern {
|
||||
lsp::GlobPattern::String(s) => s
|
||||
.strip_prefix(abs_path)
|
||||
.and_then(|s| s.strip_prefix(std::path::MAIN_SEPARATOR)),
|
||||
lsp::GlobPattern::Relative(rp) => {
|
||||
let base_uri = match &rp.base_uri {
|
||||
lsp::OneOf::Left(workspace_folder) => {
|
||||
&workspace_folder.uri
|
||||
}
|
||||
lsp::OneOf::Right(base_uri) => base_uri,
|
||||
};
|
||||
base_uri.to_file_path().ok().and_then(|file_path| {
|
||||
(file_path.to_str() == Some(abs_path))
|
||||
.then_some(rp.pattern.as_str())
|
||||
})
|
||||
}
|
||||
};
|
||||
if let Some(relative_glob_pattern) = relative_glob_pattern {
|
||||
let literal_prefix =
|
||||
glob_literal_prefix(&relative_glob_pattern);
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.add_path_prefix_to_scan(Path::new(literal_prefix).into());
|
||||
if let Some(glob) = Glob::new(relative_glob_pattern).log_err() {
|
||||
builders
|
||||
.entry(tree.id())
|
||||
.or_insert_with(|| GlobSetBuilder::new())
|
||||
.add(glob);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
false
|
||||
});
|
||||
if glob_is_inside_worktree {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5947,6 +6003,29 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
async fn handle_expand_project_entry(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::ExpandProjectEntry>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::ExpandProjectEntryResponse> {
|
||||
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
|
||||
let worktree = this
|
||||
.read_with(&cx, |this, cx| this.worktree_for_entry(entry_id, cx))
|
||||
.ok_or_else(|| anyhow!("invalid request"))?;
|
||||
worktree
|
||||
.update(&mut cx, |worktree, cx| {
|
||||
worktree
|
||||
.as_local_mut()
|
||||
.unwrap()
|
||||
.expand_entry(entry_id, cx)
|
||||
.ok_or_else(|| anyhow!("invalid entry"))
|
||||
})?
|
||||
.await?;
|
||||
let worktree_scan_id = worktree.read_with(&cx, |worktree, _| worktree.scan_id()) as u64;
|
||||
Ok(proto::ExpandProjectEntryResponse { worktree_scan_id })
|
||||
}
|
||||
|
||||
async fn handle_update_diagnostic_summary(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
|
||||
|
@ -7289,6 +7368,22 @@ impl Project {
|
|||
}
|
||||
}
|
||||
|
||||
fn glob_literal_prefix<'a>(glob: &'a str) -> &'a str {
|
||||
let mut literal_end = 0;
|
||||
for (i, part) in glob.split(path::MAIN_SEPARATOR).enumerate() {
|
||||
if part.contains(&['*', '?', '{', '}']) {
|
||||
break;
|
||||
} else {
|
||||
if i > 0 {
|
||||
// Acount for separator prior to this part
|
||||
literal_end += path::MAIN_SEPARATOR.len_utf8();
|
||||
}
|
||||
literal_end += part.len();
|
||||
}
|
||||
}
|
||||
&glob[..literal_end]
|
||||
}
|
||||
|
||||
impl WorktreeHandle {
|
||||
pub fn upgrade(&self, cx: &AppContext) -> Option<ModelHandle<Worktree>> {
|
||||
match self {
|
||||
|
|
|
@ -535,8 +535,28 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
|||
fs.insert_tree(
|
||||
"/the-root",
|
||||
json!({
|
||||
"a.rs": "",
|
||||
"b.rs": "",
|
||||
".gitignore": "target\n",
|
||||
"src": {
|
||||
"a.rs": "",
|
||||
"b.rs": "",
|
||||
},
|
||||
"target": {
|
||||
"x": {
|
||||
"out": {
|
||||
"x.rs": ""
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"out": {
|
||||
"y.rs": "",
|
||||
}
|
||||
},
|
||||
"z": {
|
||||
"out": {
|
||||
"z.rs": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
@ -550,11 +570,32 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
|||
// Start the language server by opening a buffer with a compatible file extension.
|
||||
let _buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/the-root/a.rs", cx)
|
||||
project.open_local_buffer("/the-root/src/a.rs", cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Initially, we don't load ignored files because the language server has not explicitly asked us to watch them.
|
||||
project.read_with(cx, |project, cx| {
|
||||
let worktree = project.worktrees(cx).next().unwrap();
|
||||
assert_eq!(
|
||||
worktree
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.entries(true)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_ignored))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new(""), false),
|
||||
(Path::new(".gitignore"), false),
|
||||
(Path::new("src"), false),
|
||||
(Path::new("src/a.rs"), false),
|
||||
(Path::new("src/b.rs"), false),
|
||||
(Path::new("target"), true),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// Keep track of the FS events reported to the language server.
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
let file_changes = Arc::new(Mutex::new(Vec::new()));
|
||||
|
@ -565,12 +606,20 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
|||
method: "workspace/didChangeWatchedFiles".to_string(),
|
||||
register_options: serde_json::to_value(
|
||||
lsp::DidChangeWatchedFilesRegistrationOptions {
|
||||
watchers: vec![lsp::FileSystemWatcher {
|
||||
glob_pattern: lsp::GlobPattern::String(
|
||||
"/the-root/*.{rs,c}".to_string(),
|
||||
),
|
||||
kind: None,
|
||||
}],
|
||||
watchers: vec![
|
||||
lsp::FileSystemWatcher {
|
||||
glob_pattern: lsp::GlobPattern::String(
|
||||
"/the-root/src/*.{rs,c}".to_string(),
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
lsp::FileSystemWatcher {
|
||||
glob_pattern: lsp::GlobPattern::String(
|
||||
"/the-root/target/y/**/*.rs".to_string(),
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
.ok(),
|
||||
|
@ -588,17 +637,50 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
|||
});
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
assert_eq!(file_changes.lock().len(), 0);
|
||||
assert_eq!(mem::take(&mut *file_changes.lock()), &[]);
|
||||
|
||||
// Now the language server has asked us to watch an ignored directory path,
|
||||
// so we recursively load it.
|
||||
project.read_with(cx, |project, cx| {
|
||||
let worktree = project.worktrees(cx).next().unwrap();
|
||||
assert_eq!(
|
||||
worktree
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.entries(true)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_ignored))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new(""), false),
|
||||
(Path::new(".gitignore"), false),
|
||||
(Path::new("src"), false),
|
||||
(Path::new("src/a.rs"), false),
|
||||
(Path::new("src/b.rs"), false),
|
||||
(Path::new("target"), true),
|
||||
(Path::new("target/x"), true),
|
||||
(Path::new("target/y"), true),
|
||||
(Path::new("target/y/out"), true),
|
||||
(Path::new("target/y/out/y.rs"), true),
|
||||
(Path::new("target/z"), true),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// Perform some file system mutations, two of which match the watched patterns,
|
||||
// and one of which does not.
|
||||
fs.create_file("/the-root/c.rs".as_ref(), Default::default())
|
||||
fs.create_file("/the-root/src/c.rs".as_ref(), Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
fs.create_file("/the-root/d.txt".as_ref(), Default::default())
|
||||
fs.create_file("/the-root/src/d.txt".as_ref(), Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
fs.remove_file("/the-root/b.rs".as_ref(), Default::default())
|
||||
fs.remove_file("/the-root/src/b.rs".as_ref(), Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
fs.create_file("/the-root/target/x/out/x2.rs".as_ref(), Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
fs.create_file("/the-root/target/y/out/y2.rs".as_ref(), Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -608,11 +690,15 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
|||
&*file_changes.lock(),
|
||||
&[
|
||||
lsp::FileEvent {
|
||||
uri: lsp::Url::from_file_path("/the-root/b.rs").unwrap(),
|
||||
uri: lsp::Url::from_file_path("/the-root/src/b.rs").unwrap(),
|
||||
typ: lsp::FileChangeType::DELETED,
|
||||
},
|
||||
lsp::FileEvent {
|
||||
uri: lsp::Url::from_file_path("/the-root/c.rs").unwrap(),
|
||||
uri: lsp::Url::from_file_path("/the-root/src/c.rs").unwrap(),
|
||||
typ: lsp::FileChangeType::CREATED,
|
||||
},
|
||||
lsp::FileEvent {
|
||||
uri: lsp::Url::from_file_path("/the-root/target/y/out/y2.rs").unwrap(),
|
||||
typ: lsp::FileChangeType::CREATED,
|
||||
},
|
||||
]
|
||||
|
@ -3846,6 +3932,14 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glob_literal_prefix() {
|
||||
assert_eq!(glob_literal_prefix("**/*.js"), "");
|
||||
assert_eq!(glob_literal_prefix("node_modules/**/*.js"), "node_modules");
|
||||
assert_eq!(glob_literal_prefix("foo/{bar,baz}.js"), "foo");
|
||||
assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js");
|
||||
}
|
||||
|
||||
async fn search(
|
||||
project: &ModelHandle<Project>,
|
||||
query: SearchQuery,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
worktree::{Event, Snapshot, WorktreeHandle},
|
||||
EntryKind, PathChange, Worktree,
|
||||
Entry, EntryKind, PathChange, Worktree,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use client::Client;
|
||||
|
@ -8,12 +8,14 @@ use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};
|
|||
use git::GITIGNORE;
|
||||
use gpui::{executor::Deterministic, ModelContext, Task, TestAppContext};
|
||||
use parking_lot::Mutex;
|
||||
use postage::stream::Stream;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rand::prelude::*;
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
env,
|
||||
fmt::Write,
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
@ -34,11 +36,8 @@ async fn test_traversal(cx: &mut TestAppContext) {
|
|||
)
|
||||
.await;
|
||||
|
||||
let http_client = FakeHttpClient::with_404_response();
|
||||
let client = cx.read(|cx| Client::new(http_client, cx));
|
||||
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
build_client(cx),
|
||||
Path::new("/root"),
|
||||
true,
|
||||
fs,
|
||||
|
@ -107,11 +106,8 @@ async fn test_descendent_entries(cx: &mut TestAppContext) {
|
|||
)
|
||||
.await;
|
||||
|
||||
let http_client = FakeHttpClient::with_404_response();
|
||||
let client = cx.read(|cx| Client::new(http_client, cx));
|
||||
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
build_client(cx),
|
||||
Path::new("/root"),
|
||||
true,
|
||||
fs,
|
||||
|
@ -154,7 +150,18 @@ async fn test_descendent_entries(cx: &mut TestAppContext) {
|
|||
.collect::<Vec<_>>(),
|
||||
vec![Path::new("g"), Path::new("g/h"),]
|
||||
);
|
||||
});
|
||||
|
||||
// Expand gitignored directory.
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![Path::new("i/j").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.descendent_entries(false, false, Path::new("i"))
|
||||
.map(|entry| entry.path.as_ref())
|
||||
|
@ -196,9 +203,8 @@ async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppCo
|
|||
fs.insert_symlink("/root/lib/a/lib", "..".into()).await;
|
||||
fs.insert_symlink("/root/lib/b/lib", "..".into()).await;
|
||||
|
||||
let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
build_client(cx),
|
||||
Path::new("/root"),
|
||||
true,
|
||||
fs.clone(),
|
||||
|
@ -257,32 +263,473 @@ async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppCo
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
||||
// .gitignores are handled explicitly by Zed and do not use the git
|
||||
// machinery that the git_tests module checks
|
||||
let parent_dir = temp_tree(json!({
|
||||
".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
|
||||
"tree": {
|
||||
".git": {},
|
||||
".gitignore": "ignored-dir\n",
|
||||
"tracked-dir": {
|
||||
"tracked-file1": "",
|
||||
"ancestor-ignored-file1": "",
|
||||
async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"dir1": {
|
||||
"deps": {
|
||||
// symlinks here
|
||||
},
|
||||
"src": {
|
||||
"a.rs": "",
|
||||
"b.rs": "",
|
||||
},
|
||||
},
|
||||
"ignored-dir": {
|
||||
"ignored-file1": ""
|
||||
"dir2": {
|
||||
"src": {
|
||||
"c.rs": "",
|
||||
"d.rs": "",
|
||||
}
|
||||
},
|
||||
"dir3": {
|
||||
"deps": {},
|
||||
"src": {
|
||||
"e.rs": "",
|
||||
"f.rs": "",
|
||||
},
|
||||
}
|
||||
}
|
||||
}));
|
||||
let dir = parent_dir.path().join("tree");
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
// These symlinks point to directories outside of the worktree's root, dir1.
|
||||
fs.insert_symlink("/root/dir1/deps/dep-dir2", "../../dir2".into())
|
||||
.await;
|
||||
fs.insert_symlink("/root/dir1/deps/dep-dir3", "../../dir3".into())
|
||||
.await;
|
||||
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
dir.as_path(),
|
||||
build_client(cx),
|
||||
Path::new("/root/dir1"),
|
||||
true,
|
||||
Arc::new(RealFs),
|
||||
fs.clone(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
|
||||
let tree_updates = Arc::new(Mutex::new(Vec::new()));
|
||||
tree.update(cx, |_, cx| {
|
||||
let tree_updates = tree_updates.clone();
|
||||
cx.subscribe(&tree, move |_, _, event, _| {
|
||||
if let Event::UpdatedEntries(update) = event {
|
||||
tree_updates.lock().extend(
|
||||
update
|
||||
.iter()
|
||||
.map(|(path, _, change)| (path.clone(), *change)),
|
||||
);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
// The symlinked directories are not scanned by default.
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_external))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(Path::new(""), false),
|
||||
(Path::new("deps"), false),
|
||||
(Path::new("deps/dep-dir2"), true),
|
||||
(Path::new("deps/dep-dir3"), true),
|
||||
(Path::new("src"), false),
|
||||
(Path::new("src/a.rs"), false),
|
||||
(Path::new("src/b.rs"), false),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
tree.entry_for_path("deps/dep-dir2").unwrap().kind,
|
||||
EntryKind::UnloadedDir
|
||||
);
|
||||
});
|
||||
|
||||
// Expand one of the symlinked directories.
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
|
||||
// The expanded directory's contents are loaded. Subdirectories are
|
||||
// not scanned yet.
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_external))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(Path::new(""), false),
|
||||
(Path::new("deps"), false),
|
||||
(Path::new("deps/dep-dir2"), true),
|
||||
(Path::new("deps/dep-dir3"), true),
|
||||
(Path::new("deps/dep-dir3/deps"), true),
|
||||
(Path::new("deps/dep-dir3/src"), true),
|
||||
(Path::new("src"), false),
|
||||
(Path::new("src/a.rs"), false),
|
||||
(Path::new("src/b.rs"), false),
|
||||
]
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
mem::take(&mut *tree_updates.lock()),
|
||||
&[
|
||||
(Path::new("deps/dep-dir3").into(), PathChange::Loaded),
|
||||
(Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded),
|
||||
(Path::new("deps/dep-dir3/src").into(), PathChange::Loaded)
|
||||
]
|
||||
);
|
||||
|
||||
// Expand a subdirectory of one of the symlinked directories.
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
|
||||
// The expanded subdirectory's contents are loaded.
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_external))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(Path::new(""), false),
|
||||
(Path::new("deps"), false),
|
||||
(Path::new("deps/dep-dir2"), true),
|
||||
(Path::new("deps/dep-dir3"), true),
|
||||
(Path::new("deps/dep-dir3/deps"), true),
|
||||
(Path::new("deps/dep-dir3/src"), true),
|
||||
(Path::new("deps/dep-dir3/src/e.rs"), true),
|
||||
(Path::new("deps/dep-dir3/src/f.rs"), true),
|
||||
(Path::new("src"), false),
|
||||
(Path::new("src/a.rs"), false),
|
||||
(Path::new("src/b.rs"), false),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
mem::take(&mut *tree_updates.lock()),
|
||||
&[
|
||||
(Path::new("deps/dep-dir3/src").into(), PathChange::Loaded),
|
||||
(
|
||||
Path::new("deps/dep-dir3/src/e.rs").into(),
|
||||
PathChange::Loaded
|
||||
),
|
||||
(
|
||||
Path::new("deps/dep-dir3/src/f.rs").into(),
|
||||
PathChange::Loaded
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_open_gitignored_files(cx: &mut TestAppContext) {
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
".gitignore": "node_modules\n",
|
||||
"one": {
|
||||
"node_modules": {
|
||||
"a": {
|
||||
"a1.js": "a1",
|
||||
"a2.js": "a2",
|
||||
},
|
||||
"b": {
|
||||
"b1.js": "b1",
|
||||
"b2.js": "b2",
|
||||
},
|
||||
},
|
||||
},
|
||||
"two": {
|
||||
"x.js": "",
|
||||
"y.js": "",
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let tree = Worktree::local(
|
||||
build_client(cx),
|
||||
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, _| {
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_ignored))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(Path::new(""), false),
|
||||
(Path::new(".gitignore"), false),
|
||||
(Path::new("one"), false),
|
||||
(Path::new("one/node_modules"), true),
|
||||
(Path::new("two"), false),
|
||||
(Path::new("two/x.js"), false),
|
||||
(Path::new("two/y.js"), false),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// Open a file that is nested inside of a gitignored directory that
|
||||
// has not yet been expanded.
|
||||
let prev_read_dir_count = fs.read_dir_call_count();
|
||||
let buffer = tree
|
||||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.load_buffer(0, "one/node_modules/b/b1.js".as_ref(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tree.read_with(cx, |tree, cx| {
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_ignored))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(Path::new(""), false),
|
||||
(Path::new(".gitignore"), false),
|
||||
(Path::new("one"), false),
|
||||
(Path::new("one/node_modules"), true),
|
||||
(Path::new("one/node_modules/a"), true),
|
||||
(Path::new("one/node_modules/b"), true),
|
||||
(Path::new("one/node_modules/b/b1.js"), true),
|
||||
(Path::new("one/node_modules/b/b2.js"), true),
|
||||
(Path::new("two"), false),
|
||||
(Path::new("two/x.js"), false),
|
||||
(Path::new("two/y.js"), false),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
buffer.read(cx).file().unwrap().path().as_ref(),
|
||||
Path::new("one/node_modules/b/b1.js")
|
||||
);
|
||||
|
||||
// Only the newly-expanded directories are scanned.
|
||||
assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 2);
|
||||
});
|
||||
|
||||
// Open another file in a different subdirectory of the same
|
||||
// gitignored directory.
|
||||
let prev_read_dir_count = fs.read_dir_call_count();
|
||||
let buffer = tree
|
||||
.update(cx, |tree, cx| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.load_buffer(0, "one/node_modules/a/a2.js".as_ref(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tree.read_with(cx, |tree, cx| {
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.map(|entry| (entry.path.as_ref(), entry.is_ignored))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
(Path::new(""), false),
|
||||
(Path::new(".gitignore"), false),
|
||||
(Path::new("one"), false),
|
||||
(Path::new("one/node_modules"), true),
|
||||
(Path::new("one/node_modules/a"), true),
|
||||
(Path::new("one/node_modules/a/a1.js"), true),
|
||||
(Path::new("one/node_modules/a/a2.js"), true),
|
||||
(Path::new("one/node_modules/b"), true),
|
||||
(Path::new("one/node_modules/b/b1.js"), true),
|
||||
(Path::new("one/node_modules/b/b2.js"), true),
|
||||
(Path::new("two"), false),
|
||||
(Path::new("two/x.js"), false),
|
||||
(Path::new("two/y.js"), false),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
buffer.read(cx).file().unwrap().path().as_ref(),
|
||||
Path::new("one/node_modules/a/a2.js")
|
||||
);
|
||||
|
||||
// Only the newly-expanded directory is scanned.
|
||||
assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) {
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
".gitignore": "node_modules\n",
|
||||
"a": {
|
||||
"a.js": "",
|
||||
},
|
||||
"b": {
|
||||
"b.js": "",
|
||||
},
|
||||
"node_modules": {
|
||||
"c": {
|
||||
"c.js": "",
|
||||
},
|
||||
"d": {
|
||||
"d.js": "",
|
||||
"e": {
|
||||
"e1.js": "",
|
||||
"e2.js": "",
|
||||
},
|
||||
"f": {
|
||||
"f1.js": "",
|
||||
"f2.js": "",
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let tree = Worktree::local(
|
||||
build_client(cx),
|
||||
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;
|
||||
|
||||
// Open a file within the gitignored directory, forcing some of its
|
||||
// subdirectories to be read, but not all.
|
||||
let read_dir_count_1 = fs.read_dir_call_count();
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![Path::new("node_modules/d/d.js").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
|
||||
// Those subdirectories are now loaded.
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.map(|e| (e.path.as_ref(), e.is_ignored))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new(""), false),
|
||||
(Path::new(".gitignore"), false),
|
||||
(Path::new("a"), false),
|
||||
(Path::new("a/a.js"), false),
|
||||
(Path::new("b"), false),
|
||||
(Path::new("b/b.js"), false),
|
||||
(Path::new("node_modules"), true),
|
||||
(Path::new("node_modules/c"), true),
|
||||
(Path::new("node_modules/d"), true),
|
||||
(Path::new("node_modules/d/d.js"), true),
|
||||
(Path::new("node_modules/d/e"), true),
|
||||
(Path::new("node_modules/d/f"), true),
|
||||
]
|
||||
);
|
||||
});
|
||||
let read_dir_count_2 = fs.read_dir_call_count();
|
||||
assert_eq!(read_dir_count_2 - read_dir_count_1, 2);
|
||||
|
||||
// Update the gitignore so that node_modules is no longer ignored,
|
||||
// but a subdirectory is ignored
|
||||
fs.save("/root/.gitignore".as_ref(), &"e".into(), Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
// All of the directories that are no longer ignored are now loaded.
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert_eq!(
|
||||
tree.entries(true)
|
||||
.map(|e| (e.path.as_ref(), e.is_ignored))
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new(""), false),
|
||||
(Path::new(".gitignore"), false),
|
||||
(Path::new("a"), false),
|
||||
(Path::new("a/a.js"), false),
|
||||
(Path::new("b"), false),
|
||||
(Path::new("b/b.js"), false),
|
||||
// This directory is no longer ignored
|
||||
(Path::new("node_modules"), false),
|
||||
(Path::new("node_modules/c"), false),
|
||||
(Path::new("node_modules/c/c.js"), false),
|
||||
(Path::new("node_modules/d"), false),
|
||||
(Path::new("node_modules/d/d.js"), false),
|
||||
// This subdirectory is now ignored
|
||||
(Path::new("node_modules/d/e"), true),
|
||||
(Path::new("node_modules/d/f"), false),
|
||||
(Path::new("node_modules/d/f/f1.js"), false),
|
||||
(Path::new("node_modules/d/f/f2.js"), false),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// Each of the newly-loaded directories is scanned only once.
|
||||
let read_dir_count_3 = fs.read_dir_call_count();
|
||||
assert_eq!(read_dir_count_3 - read_dir_count_2, 2);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
".gitignore": "ancestor-ignored-file1\nancestor-ignored-file2\n",
|
||||
"tree": {
|
||||
".git": {},
|
||||
".gitignore": "ignored-dir\n",
|
||||
"tracked-dir": {
|
||||
"tracked-file1": "",
|
||||
"ancestor-ignored-file1": "",
|
||||
},
|
||||
"ignored-dir": {
|
||||
"ignored-file1": ""
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let tree = Worktree::local(
|
||||
build_client(cx),
|
||||
"/root/tree".as_ref(),
|
||||
true,
|
||||
fs.clone(),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
|
@ -290,7 +737,15 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
|||
.unwrap();
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
tree.flush_fs_events(cx).await;
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
tree.as_local()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(vec![Path::new("ignored-dir").into()])
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
|
||||
cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
assert!(
|
||||
|
@ -311,10 +766,26 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
|
|||
);
|
||||
});
|
||||
|
||||
std::fs::write(dir.join("tracked-dir/tracked-file2"), "").unwrap();
|
||||
std::fs::write(dir.join("tracked-dir/ancestor-ignored-file2"), "").unwrap();
|
||||
std::fs::write(dir.join("ignored-dir/ignored-file2"), "").unwrap();
|
||||
tree.flush_fs_events(cx).await;
|
||||
fs.create_file(
|
||||
"/root/tree/tracked-dir/tracked-file2".as_ref(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
fs.create_file(
|
||||
"/root/tree/tracked-dir/ancestor-ignored-file2".as_ref(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
fs.create_file(
|
||||
"/root/tree/ignored-dir/ignored-file2".as_ref(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.foreground().run_until_parked();
|
||||
cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
assert!(
|
||||
|
@ -346,10 +817,8 @@ async fn test_write_file(cx: &mut TestAppContext) {
|
|||
"ignored-dir": {}
|
||||
}));
|
||||
|
||||
let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
build_client(cx),
|
||||
dir.path(),
|
||||
true,
|
||||
Arc::new(RealFs),
|
||||
|
@ -393,8 +862,6 @@ async fn test_write_file(cx: &mut TestAppContext) {
|
|||
|
||||
#[gpui::test(iterations = 30)]
|
||||
async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
||||
let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
|
@ -407,7 +874,7 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|||
.await;
|
||||
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
build_client(cx),
|
||||
"/root".as_ref(),
|
||||
true,
|
||||
fs,
|
||||
|
@ -472,9 +939,8 @@ async fn test_random_worktree_operations_during_initial_scan(
|
|||
}
|
||||
log::info!("generated initial tree");
|
||||
|
||||
let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let worktree = Worktree::local(
|
||||
client.clone(),
|
||||
build_client(cx),
|
||||
root_dir,
|
||||
true,
|
||||
fs.clone(),
|
||||
|
@ -506,7 +972,7 @@ async fn test_random_worktree_operations_during_initial_scan(
|
|||
.await
|
||||
.log_err();
|
||||
worktree.read_with(cx, |tree, _| {
|
||||
tree.as_local().unwrap().snapshot().check_invariants()
|
||||
tree.as_local().unwrap().snapshot().check_invariants(true)
|
||||
});
|
||||
|
||||
if rng.gen_bool(0.6) {
|
||||
|
@ -523,7 +989,7 @@ async fn test_random_worktree_operations_during_initial_scan(
|
|||
let final_snapshot = worktree.read_with(cx, |tree, _| {
|
||||
let tree = tree.as_local().unwrap();
|
||||
let snapshot = tree.snapshot();
|
||||
snapshot.check_invariants();
|
||||
snapshot.check_invariants(true);
|
||||
snapshot
|
||||
});
|
||||
|
||||
|
@ -562,9 +1028,8 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
|
|||
}
|
||||
log::info!("generated initial tree");
|
||||
|
||||
let client = cx.read(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let worktree = Worktree::local(
|
||||
client.clone(),
|
||||
build_client(cx),
|
||||
root_dir,
|
||||
true,
|
||||
fs.clone(),
|
||||
|
@ -627,12 +1092,17 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
|
|||
log::info!("quiescing");
|
||||
fs.as_fake().flush_events(usize::MAX);
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
let snapshot = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
|
||||
snapshot.check_invariants();
|
||||
snapshot.check_invariants(true);
|
||||
let expanded_paths = snapshot
|
||||
.expanded_entries()
|
||||
.map(|e| e.path.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
{
|
||||
let new_worktree = Worktree::local(
|
||||
client.clone(),
|
||||
build_client(cx),
|
||||
root_dir,
|
||||
true,
|
||||
fs.clone(),
|
||||
|
@ -644,6 +1114,14 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
|
|||
new_worktree
|
||||
.update(cx, |tree, _| tree.as_local_mut().unwrap().scan_complete())
|
||||
.await;
|
||||
new_worktree
|
||||
.update(cx, |tree, _| {
|
||||
tree.as_local_mut()
|
||||
.unwrap()
|
||||
.refresh_entries_for_paths(expanded_paths)
|
||||
})
|
||||
.recv()
|
||||
.await;
|
||||
let new_snapshot =
|
||||
new_worktree.read_with(cx, |tree, _| tree.as_local().unwrap().snapshot());
|
||||
assert_eq!(
|
||||
|
@ -660,11 +1138,25 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
|
|||
}
|
||||
|
||||
assert_eq!(
|
||||
prev_snapshot.entries(true).collect::<Vec<_>>(),
|
||||
snapshot.entries(true).collect::<Vec<_>>(),
|
||||
prev_snapshot
|
||||
.entries(true)
|
||||
.map(ignore_pending_dir)
|
||||
.collect::<Vec<_>>(),
|
||||
snapshot
|
||||
.entries(true)
|
||||
.map(ignore_pending_dir)
|
||||
.collect::<Vec<_>>(),
|
||||
"wrong updates after snapshot {i}: {updates:#?}",
|
||||
);
|
||||
}
|
||||
|
||||
fn ignore_pending_dir(entry: &Entry) -> Entry {
|
||||
let mut entry = entry.clone();
|
||||
if entry.kind.is_dir() {
|
||||
entry.kind = EntryKind::Dir
|
||||
}
|
||||
entry
|
||||
}
|
||||
}
|
||||
|
||||
// The worktree's `UpdatedEntries` event can be used to follow along with
|
||||
|
@ -679,7 +1171,6 @@ fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Workt
|
|||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
match change_type {
|
||||
PathChange::Loaded => entries.insert(ix, entry.unwrap()),
|
||||
PathChange::Added => entries.insert(ix, entry.unwrap()),
|
||||
PathChange::Removed => drop(entries.remove(ix)),
|
||||
PathChange::Updated => {
|
||||
|
@ -688,7 +1179,7 @@ fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext<Workt
|
|||
assert_eq!(existing_entry.path, entry.path);
|
||||
*existing_entry = entry;
|
||||
}
|
||||
PathChange::AddedOrUpdated => {
|
||||
PathChange::AddedOrUpdated | PathChange::Loaded => {
|
||||
let entry = entry.unwrap();
|
||||
if entries.get(ix).map(|e| &e.path) == Some(&entry.path) {
|
||||
*entries.get_mut(ix).unwrap() = entry;
|
||||
|
@ -947,10 +1438,8 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) {
|
|||
}));
|
||||
let root_path = root.path();
|
||||
|
||||
let http_client = FakeHttpClient::with_404_response();
|
||||
let client = cx.read(|cx| Client::new(http_client, cx));
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
build_client(cx),
|
||||
root_path,
|
||||
true,
|
||||
Arc::new(RealFs),
|
||||
|
@ -1026,10 +1515,8 @@ async fn test_git_repository_for_path(cx: &mut TestAppContext) {
|
|||
},
|
||||
}));
|
||||
|
||||
let http_client = FakeHttpClient::with_404_response();
|
||||
let client = cx.read(|cx| Client::new(http_client, cx));
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
build_client(cx),
|
||||
root.path(),
|
||||
true,
|
||||
Arc::new(RealFs),
|
||||
|
@ -1150,10 +1637,8 @@ async fn test_git_status(deterministic: Arc<Deterministic>, cx: &mut TestAppCont
|
|||
|
||||
}));
|
||||
|
||||
let http_client = FakeHttpClient::with_404_response();
|
||||
let client = cx.read(|cx| Client::new(http_client, cx));
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
build_client(cx),
|
||||
root.path(),
|
||||
true,
|
||||
Arc::new(RealFs),
|
||||
|
@ -1357,10 +1842,8 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
|
|||
],
|
||||
);
|
||||
|
||||
let http_client = FakeHttpClient::with_404_response();
|
||||
let client = cx.read(|cx| Client::new(http_client, cx));
|
||||
let tree = Worktree::local(
|
||||
client,
|
||||
build_client(cx),
|
||||
Path::new("/root"),
|
||||
true,
|
||||
fs.clone(),
|
||||
|
@ -1439,6 +1922,11 @@ async fn test_propagate_git_statuses(cx: &mut TestAppContext) {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_client(cx: &mut TestAppContext) -> Arc<Client> {
|
||||
let http_client = FakeHttpClient::with_404_response();
|
||||
cx.read(|cx| Client::new(http_client, cx))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn git_init(path: &Path) -> git2::Repository {
|
||||
git2::Repository::init(path).expect("Failed to initialize git repository")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue