diff --git a/Cargo.lock b/Cargo.lock index f84b759aab..b32ad635fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4749,6 +4749,7 @@ dependencies = [ "env_logger 0.11.7", "extension", "fs", + "gpui", "language", "log", "reqwest_client", diff --git a/crates/assistant2/src/thread.rs b/crates/assistant2/src/thread.rs index 47ad1907f5..5facf752b4 100644 --- a/crates/assistant2/src/thread.rs +++ b/crates/assistant2/src/thread.rs @@ -1429,7 +1429,7 @@ impl Thread { // Get diff asynchronously let diff = repo - .diff(git::repository::DiffType::HeadToWorktree, cx.clone()) + .diff(git::repository::DiffType::HeadToWorktree) .await .ok(); diff --git a/crates/assistant_eval/src/headless_assistant.rs b/crates/assistant_eval/src/headless_assistant.rs index 31bbc69f31..041d1a8308 100644 --- a/crates/assistant_eval/src/headless_assistant.rs +++ b/crates/assistant_eval/src/headless_assistant.rs @@ -149,7 +149,10 @@ pub fn init(cx: &mut App) -> Arc { cx.set_http_client(client.http_client().clone()); let git_binary_path = None; - let fs = Arc::new(RealFs::new(git_binary_path)); + let fs = Arc::new(RealFs::new( + git_binary_path, + cx.background_executor().clone(), + )); let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone())); diff --git a/crates/evals/src/eval.rs b/crates/evals/src/eval.rs index eba827ff79..8bcced25be 100644 --- a/crates/evals/src/eval.rs +++ b/crates/evals/src/eval.rs @@ -273,7 +273,7 @@ async fn run_evaluation( let repos_dir = Path::new(EVAL_REPOS_DIR); let db_path = Path::new(EVAL_DB_PATH); let api_key = std::env::var("OPENAI_API_KEY").unwrap(); - let fs = Arc::new(RealFs::new(None)) as Arc; + let fs = Arc::new(RealFs::new(None, cx.background_executor().clone())) as Arc; let clock = Arc::new(RealSystemClock); let client = cx .update(|cx| { diff --git a/crates/extension_cli/Cargo.toml b/crates/extension_cli/Cargo.toml index 55bad74d7e..b2562a8e82 100644 --- a/crates/extension_cli/Cargo.toml +++ b/crates/extension_cli/Cargo.toml @@ -18,6 +18,7 @@ clap = { workspace = true, features = ["derive"] } env_logger.workspace = true extension.workspace = true fs.workspace = true +gpui.workspace = true language.workspace = true log.workspace = true reqwest_client.workspace = true diff --git a/crates/extension_cli/src/main.rs b/crates/extension_cli/src/main.rs index ac96cd311d..c753138007 100644 --- a/crates/extension_cli/src/main.rs +++ b/crates/extension_cli/src/main.rs @@ -34,7 +34,7 @@ async fn main() -> Result<()> { env_logger::init(); let args = Args::parse(); - let fs = Arc::new(RealFs::default()); + let fs = Arc::new(RealFs::new(None, gpui::background_executor())); let engine = wasmtime::Engine::default(); let mut wasm_store = WasmStore::new(&engine)?; diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs index 84b504be13..def82110f2 100644 --- a/crates/extension_host/src/extension_store_test.rs +++ b/crates/extension_host/src/extension_store_test.rs @@ -477,7 +477,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { let test_extension_id = "test-extension"; let test_extension_dir = root_dir.join("extensions").join(test_extension_id); - let fs = Arc::new(RealFs::default()); + let fs = Arc::new(RealFs::new(None, cx.executor())); let extensions_dir = TempTree::new(json!({ "installed": {}, "work": {} diff --git a/crates/fs/src/fake_git_repo.rs b/crates/fs/src/fake_git_repo.rs index 272944be46..8e90f8cc07 100644 --- a/crates/fs/src/fake_git_repo.rs +++ b/crates/fs/src/fake_git_repo.rs @@ -62,7 +62,7 @@ impl FakeGitRepository { .unwrap() } - fn with_state_async(&self, write: bool, f: F) -> BoxFuture> + fn with_state_async(&self, write: bool, f: F) -> BoxFuture<'static, Result> where F: 'static + Send + FnOnce(&mut FakeGitRepositoryState) -> Result, T: Send, @@ -81,7 +81,7 @@ impl FakeGitRepository { impl GitRepository for FakeGitRepository { fn reload_index(&self) {} - fn load_index_text(&self, path: RepoPath, _cx: AsyncApp) -> BoxFuture> { + fn load_index_text(&self, path: RepoPath) -> BoxFuture> { async { self.with_state_async(false, move |state| { state @@ -96,7 +96,7 @@ impl GitRepository for FakeGitRepository { .boxed() } - fn load_committed_text(&self, path: RepoPath, _cx: AsyncApp) -> BoxFuture> { + fn load_committed_text(&self, path: RepoPath) -> BoxFuture> { async { self.with_state_async(false, move |state| { state @@ -116,7 +116,6 @@ impl GitRepository for FakeGitRepository { path: RepoPath, content: Option, _env: HashMap, - _cx: AsyncApp, ) -> BoxFuture> { self.with_state_async(true, move |state| { if let Some(message) = state.simulated_index_write_error_message.clone() { @@ -142,7 +141,7 @@ impl GitRepository for FakeGitRepository { vec![] } - fn show(&self, _commit: String, _cx: AsyncApp) -> BoxFuture> { + fn show(&self, _commit: String) -> BoxFuture> { unimplemented!() } @@ -172,7 +171,12 @@ impl GitRepository for FakeGitRepository { self.path() } - fn status(&self, path_prefixes: &[RepoPath]) -> Result { + fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'static, Result> { + let status = self.status_blocking(path_prefixes); + async move { status }.boxed() + } + + fn status_blocking(&self, path_prefixes: &[RepoPath]) -> Result { let workdir_path = self.dot_git_path.parent().unwrap(); // Load gitignores @@ -317,26 +321,21 @@ impl GitRepository for FakeGitRepository { }) } - fn change_branch(&self, name: String, _cx: AsyncApp) -> BoxFuture> { + fn change_branch(&self, name: String) -> BoxFuture> { self.with_state_async(true, |state| { state.current_branch_name = Some(name); Ok(()) }) } - fn create_branch(&self, name: String, _: AsyncApp) -> BoxFuture> { + fn create_branch(&self, name: String) -> BoxFuture> { self.with_state_async(true, move |state| { state.branches.insert(name.to_owned()); Ok(()) }) } - fn blame( - &self, - path: RepoPath, - _content: Rope, - _cx: &mut AsyncApp, - ) -> BoxFuture> { + fn blame(&self, path: RepoPath, _content: Rope) -> BoxFuture> { self.with_state_async(false, move |state| { state .blames @@ -350,7 +349,6 @@ impl GitRepository for FakeGitRepository { &self, _paths: Vec, _env: HashMap, - _cx: AsyncApp, ) -> BoxFuture> { unimplemented!() } @@ -359,7 +357,6 @@ impl GitRepository for FakeGitRepository { &self, _paths: Vec, _env: HashMap, - _cx: AsyncApp, ) -> BoxFuture> { unimplemented!() } @@ -369,7 +366,6 @@ impl GitRepository for FakeGitRepository { _message: gpui::SharedString, _name_and_email: Option<(gpui::SharedString, gpui::SharedString)>, _env: HashMap, - _cx: AsyncApp, ) -> BoxFuture> { unimplemented!() } @@ -406,38 +402,23 @@ impl GitRepository for FakeGitRepository { unimplemented!() } - fn get_remotes( - &self, - _branch: Option, - _cx: AsyncApp, - ) -> BoxFuture>> { + fn get_remotes(&self, _branch: Option) -> BoxFuture>> { unimplemented!() } - fn check_for_pushed_commit( - &self, - _cx: gpui::AsyncApp, - ) -> BoxFuture>> { + fn check_for_pushed_commit(&self) -> BoxFuture>> { future::ready(Ok(Vec::new())).boxed() } - fn diff( - &self, - _diff: git::repository::DiffType, - _cx: gpui::AsyncApp, - ) -> BoxFuture> { + fn diff(&self, _diff: git::repository::DiffType) -> BoxFuture> { unimplemented!() } - fn checkpoint(&self, _cx: AsyncApp) -> BoxFuture> { + fn checkpoint(&self) -> BoxFuture> { unimplemented!() } - fn restore_checkpoint( - &self, - _checkpoint: GitRepositoryCheckpoint, - _cx: AsyncApp, - ) -> BoxFuture> { + fn restore_checkpoint(&self, _checkpoint: GitRepositoryCheckpoint) -> BoxFuture> { unimplemented!() } @@ -445,16 +426,11 @@ impl GitRepository for FakeGitRepository { &self, _left: GitRepositoryCheckpoint, _right: GitRepositoryCheckpoint, - _cx: AsyncApp, ) -> BoxFuture> { unimplemented!() } - fn delete_checkpoint( - &self, - _checkpoint: GitRepositoryCheckpoint, - _cx: AsyncApp, - ) -> BoxFuture> { + fn delete_checkpoint(&self, _checkpoint: GitRepositoryCheckpoint) -> BoxFuture> { unimplemented!() } } diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 963b6877a9..75368978d7 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -8,6 +8,7 @@ use anyhow::{anyhow, Context as _, Result}; #[cfg(any(target_os = "linux", target_os = "freebsd"))] use ashpd::desktop::trash; use gpui::App; +use gpui::BackgroundExecutor; use gpui::Global; use gpui::ReadGlobal as _; use std::borrow::Cow; @@ -240,9 +241,9 @@ impl From for proto::Timestamp { } } -#[derive(Default)] pub struct RealFs { git_binary_path: Option, + executor: BackgroundExecutor, } pub trait FileHandle: Send + Sync + std::fmt::Debug { @@ -294,8 +295,11 @@ impl FileHandle for std::fs::File { pub struct RealWatcher {} impl RealFs { - pub fn new(git_binary_path: Option) -> Self { - Self { git_binary_path } + pub fn new(git_binary_path: Option, executor: BackgroundExecutor) -> Self { + Self { + git_binary_path, + executor, + } } } @@ -754,6 +758,7 @@ impl Fs for RealFs { Some(Arc::new(RealGitRepository::new( dotgit_path, self.git_binary_path.clone(), + self.executor.clone(), )?)) } diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index e8c0dcabe7..f45cc53dcc 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -5,13 +5,13 @@ use collections::HashMap; use futures::future::BoxFuture; use futures::{select_biased, AsyncWriteExt, FutureExt as _}; use git2::BranchType; -use gpui::{AppContext, AsyncApp, BackgroundExecutor, SharedString}; +use gpui::{AsyncApp, BackgroundExecutor, SharedString}; use parking_lot::Mutex; use rope::Rope; use schemars::JsonSchema; use serde::Deserialize; use std::borrow::{Borrow, Cow}; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::future; use std::path::Component; use std::process::{ExitStatus, Stdio}; @@ -23,7 +23,7 @@ use std::{ }; use sum_tree::MapSeekTarget; use thiserror::Error; -use util::command::new_smol_command; +use util::command::{new_smol_command, new_std_command}; use util::ResultExt; use uuid::Uuid; @@ -161,19 +161,18 @@ pub trait GitRepository: Send + Sync { /// Returns the contents of an entry in the repository's index, or None if there is no entry for the given path. /// /// Also returns `None` for symlinks. - fn load_index_text(&self, path: RepoPath, cx: AsyncApp) -> BoxFuture>; + fn load_index_text(&self, path: RepoPath) -> BoxFuture>; /// Returns the contents of an entry in the repository's HEAD, or None if HEAD does not exist or has no entry for the given path. /// /// Also returns `None` for symlinks. - fn load_committed_text(&self, path: RepoPath, cx: AsyncApp) -> BoxFuture>; + fn load_committed_text(&self, path: RepoPath) -> BoxFuture>; fn set_index_text( &self, path: RepoPath, content: Option, env: HashMap, - cx: AsyncApp, ) -> BoxFuture>; /// Returns the URL of the remote with the given name. @@ -184,13 +183,13 @@ pub trait GitRepository: Send + Sync { fn merge_head_shas(&self) -> Vec; - // Note: this method blocks the current thread! - fn status(&self, path_prefixes: &[RepoPath]) -> Result; + fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'static, Result>; + fn status_blocking(&self, path_prefixes: &[RepoPath]) -> Result; fn branches(&self) -> BoxFuture>>; - fn change_branch(&self, _: String, _: AsyncApp) -> BoxFuture>; - fn create_branch(&self, _: String, _: AsyncApp) -> BoxFuture>; + fn change_branch(&self, name: String) -> BoxFuture>; + fn create_branch(&self, name: String) -> BoxFuture>; fn reset( &self, @@ -206,14 +205,9 @@ pub trait GitRepository: Send + Sync { env: HashMap, ) -> BoxFuture>; - fn show(&self, commit: String, cx: AsyncApp) -> BoxFuture>; + fn show(&self, commit: String) -> BoxFuture>; - fn blame( - &self, - path: RepoPath, - content: Rope, - cx: &mut AsyncApp, - ) -> BoxFuture>; + fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture>; /// Returns the absolute path to the repository. For worktrees, this will be the path to the /// worktree's gitdir within the main repository (typically `.git/worktrees/`). @@ -234,7 +228,6 @@ pub trait GitRepository: Send + Sync { &self, paths: Vec, env: HashMap, - cx: AsyncApp, ) -> BoxFuture>; /// Updates the index to match HEAD at the given paths. /// @@ -243,7 +236,6 @@ pub trait GitRepository: Send + Sync { &self, paths: Vec, env: HashMap, - cx: AsyncApp, ) -> BoxFuture>; fn commit( @@ -251,7 +243,6 @@ pub trait GitRepository: Send + Sync { message: SharedString, name_and_email: Option<(SharedString, SharedString)>, env: HashMap, - cx: AsyncApp, ) -> BoxFuture>; fn push( @@ -261,6 +252,8 @@ pub trait GitRepository: Send + Sync { options: Option, askpass: AskPassSession, env: HashMap, + // This method takes an AsyncApp to ensure it's invoked on the main thread, + // otherwise git-credentials-manager won't work. cx: AsyncApp, ) -> BoxFuture>; @@ -270,6 +263,8 @@ pub trait GitRepository: Send + Sync { upstream_name: String, askpass: AskPassSession, env: HashMap, + // This method takes an AsyncApp to ensure it's invoked on the main thread, + // otherwise git-credentials-manager won't work. cx: AsyncApp, ) -> BoxFuture>; @@ -277,45 +272,34 @@ pub trait GitRepository: Send + Sync { &self, askpass: AskPassSession, env: HashMap, + // This method takes an AsyncApp to ensure it's invoked on the main thread, + // otherwise git-credentials-manager won't work. cx: AsyncApp, ) -> BoxFuture>; - fn get_remotes( - &self, - branch_name: Option, - cx: AsyncApp, - ) -> BoxFuture>>; + fn get_remotes(&self, branch_name: Option) -> BoxFuture>>; /// returns a list of remote branches that contain HEAD - fn check_for_pushed_commit(&self, cx: AsyncApp) -> BoxFuture>>; + fn check_for_pushed_commit(&self) -> BoxFuture>>; /// Run git diff - fn diff(&self, diff: DiffType, cx: AsyncApp) -> BoxFuture>; + fn diff(&self, diff: DiffType) -> BoxFuture>; /// Creates a checkpoint for the repository. - fn checkpoint(&self, cx: AsyncApp) -> BoxFuture>; + fn checkpoint(&self) -> BoxFuture>; /// Resets to a previously-created checkpoint. - fn restore_checkpoint( - &self, - checkpoint: GitRepositoryCheckpoint, - cx: AsyncApp, - ) -> BoxFuture>; + fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture>; /// Compares two checkpoints, returning true if they are equal fn compare_checkpoints( &self, left: GitRepositoryCheckpoint, right: GitRepositoryCheckpoint, - cx: AsyncApp, ) -> BoxFuture>; /// Deletes a previously-created checkpoint. - fn delete_checkpoint( - &self, - checkpoint: GitRepositoryCheckpoint, - cx: AsyncApp, - ) -> BoxFuture>; + fn delete_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture>; } pub enum DiffType { @@ -338,15 +322,21 @@ impl std::fmt::Debug for dyn GitRepository { pub struct RealGitRepository { pub repository: Arc>, pub git_binary_path: PathBuf, + executor: BackgroundExecutor, } impl RealGitRepository { - pub fn new(dotgit_path: &Path, git_binary_path: Option) -> Option { + pub fn new( + dotgit_path: &Path, + git_binary_path: Option, + executor: BackgroundExecutor, + ) -> Option { let workdir_root = dotgit_path.parent()?; let repository = git2::Repository::open(workdir_root).log_err()?; Some(Self { repository: Arc::new(Mutex::new(repository)), git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")), + executor, }) } @@ -386,29 +376,30 @@ impl GitRepository for RealGitRepository { repo.commondir().into() } - fn show(&self, commit: String, cx: AsyncApp) -> BoxFuture> { + fn show(&self, commit: String) -> BoxFuture> { let repo = self.repository.clone(); - cx.background_spawn(async move { - let repo = repo.lock(); - let Ok(commit) = repo.revparse_single(&commit)?.into_commit() else { - anyhow::bail!("{} is not a commit", commit); - }; - let details = CommitDetails { - sha: commit.id().to_string().into(), - message: String::from_utf8_lossy(commit.message_raw_bytes()) - .to_string() - .into(), - commit_timestamp: commit.time().seconds(), - committer_email: String::from_utf8_lossy(commit.committer().email_bytes()) - .to_string() - .into(), - committer_name: String::from_utf8_lossy(commit.committer().name_bytes()) - .to_string() - .into(), - }; - Ok(details) - }) - .boxed() + self.executor + .spawn(async move { + let repo = repo.lock(); + let Ok(commit) = repo.revparse_single(&commit)?.into_commit() else { + anyhow::bail!("{} is not a commit", commit); + }; + let details = CommitDetails { + sha: commit.id().to_string().into(), + message: String::from_utf8_lossy(commit.message_raw_bytes()) + .to_string() + .into(), + commit_timestamp: commit.time().seconds(), + committer_email: String::from_utf8_lossy(commit.committer().email_bytes()) + .to_string() + .into(), + committer_name: String::from_utf8_lossy(commit.committer().name_bytes()) + .to_string() + .into(), + }; + Ok(details) + }) + .boxed() } fn reset( @@ -473,48 +464,50 @@ impl GitRepository for RealGitRepository { .boxed() } - fn load_index_text(&self, path: RepoPath, cx: AsyncApp) -> BoxFuture> { + fn load_index_text(&self, path: RepoPath) -> BoxFuture> { let repo = self.repository.clone(); - cx.background_spawn(async move { - fn logic(repo: &git2::Repository, path: &RepoPath) -> Result> { - // This check is required because index.get_path() unwraps internally :( - check_path_to_repo_path_errors(path)?; + self.executor + .spawn(async move { + fn logic(repo: &git2::Repository, path: &RepoPath) -> Result> { + // This check is required because index.get_path() unwraps internally :( + check_path_to_repo_path_errors(path)?; - let mut index = repo.index()?; - index.read(false)?; + let mut index = repo.index()?; + index.read(false)?; - const STAGE_NORMAL: i32 = 0; - let oid = match index.get_path(path, STAGE_NORMAL) { - Some(entry) if entry.mode != GIT_MODE_SYMLINK => entry.id, - _ => return Ok(None), - }; + const STAGE_NORMAL: i32 = 0; + let oid = match index.get_path(path, STAGE_NORMAL) { + Some(entry) if entry.mode != GIT_MODE_SYMLINK => entry.id, + _ => return Ok(None), + }; - let content = repo.find_blob(oid)?.content().to_owned(); - Ok(Some(String::from_utf8(content)?)) - } - match logic(&repo.lock(), &path) { - Ok(value) => return value, - Err(err) => log::error!("Error loading index text: {:?}", err), - } - None - }) - .boxed() + let content = repo.find_blob(oid)?.content().to_owned(); + Ok(Some(String::from_utf8(content)?)) + } + match logic(&repo.lock(), &path) { + Ok(value) => return value, + Err(err) => log::error!("Error loading index text: {:?}", err), + } + None + }) + .boxed() } - fn load_committed_text(&self, path: RepoPath, cx: AsyncApp) -> BoxFuture> { + fn load_committed_text(&self, path: RepoPath) -> BoxFuture> { let repo = self.repository.clone(); - cx.background_spawn(async move { - let repo = repo.lock(); - let head = repo.head().ok()?.peel_to_tree().log_err()?; - let entry = head.get_path(&path).ok()?; - if entry.filemode() == i32::from(git2::FileMode::Link) { - return None; - } - let content = repo.find_blob(entry.id()).log_err()?.content().to_owned(); - let content = String::from_utf8(content).log_err()?; - Some(content) - }) - .boxed() + self.executor + .spawn(async move { + let repo = repo.lock(); + let head = repo.head().ok()?.peel_to_tree().log_err()?; + let entry = head.get_path(&path).ok()?; + if entry.filemode() == i32::from(git2::FileMode::Link) { + return None; + } + let content = repo.find_blob(entry.id()).log_err()?.content().to_owned(); + let content = String::from_utf8(content).log_err()?; + Some(content) + }) + .boxed() } fn set_index_text( @@ -522,65 +515,65 @@ impl GitRepository for RealGitRepository { path: RepoPath, content: Option, env: HashMap, - cx: AsyncApp, ) -> BoxFuture> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - cx.background_spawn(async move { - let working_directory = working_directory?; - if let Some(content) = content { - let mut child = new_smol_command(&git_binary_path) - .current_dir(&working_directory) - .envs(&env) - .args(["hash-object", "-w", "--stdin"]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - child - .stdin - .take() - .unwrap() - .write_all(content.as_bytes()) - .await?; - let output = child.output().await?.stdout; - let sha = String::from_utf8(output)?; + self.executor + .spawn(async move { + let working_directory = working_directory?; + if let Some(content) = content { + let mut child = new_smol_command(&git_binary_path) + .current_dir(&working_directory) + .envs(&env) + .args(["hash-object", "-w", "--stdin"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn()?; + child + .stdin + .take() + .unwrap() + .write_all(content.as_bytes()) + .await?; + let output = child.output().await?.stdout; + let sha = String::from_utf8(output)?; - log::debug!("indexing SHA: {sha}, path {path:?}"); + log::debug!("indexing SHA: {sha}, path {path:?}"); - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory) - .envs(env) - .args(["update-index", "--add", "--cacheinfo", "100644", &sha]) - .arg(path.to_unix_style()) - .output() - .await?; + let output = new_smol_command(&git_binary_path) + .current_dir(&working_directory) + .envs(env) + .args(["update-index", "--add", "--cacheinfo", "100644", &sha]) + .arg(path.to_unix_style()) + .output() + .await?; - if !output.status.success() { - return Err(anyhow!( - "Failed to stage:\n{}", - String::from_utf8_lossy(&output.stderr) - )); + if !output.status.success() { + return Err(anyhow!( + "Failed to stage:\n{}", + String::from_utf8_lossy(&output.stderr) + )); + } + } else { + let output = new_smol_command(&git_binary_path) + .current_dir(&working_directory) + .envs(env) + .args(["update-index", "--force-remove"]) + .arg(path.to_unix_style()) + .output() + .await?; + + if !output.status.success() { + return Err(anyhow!( + "Failed to unstage:\n{}", + String::from_utf8_lossy(&output.stderr) + )); + } } - } else { - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory) - .envs(env) - .args(["update-index", "--force-remove"]) - .arg(path.to_unix_style()) - .output() - .await?; - if !output.status.success() { - return Err(anyhow!( - "Failed to unstage:\n{}", - String::from_utf8_lossy(&output.stderr) - )); - } - } - - Ok(()) - }) - .boxed() + Ok(()) + }) + .boxed() } fn remote_url(&self, name: &str) -> Option { @@ -614,14 +607,32 @@ impl GitRepository for RealGitRepository { shas } - fn status(&self, path_prefixes: &[RepoPath]) -> Result { - let working_directory = self - .repository - .lock() - .workdir() - .context("failed to read git work directory")? - .to_path_buf(); - GitStatus::new(&self.git_binary_path, &working_directory, path_prefixes) + fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'static, Result> { + let working_directory = self.working_directory(); + let git_binary_path = self.git_binary_path.clone(); + let executor = self.executor.clone(); + let args = git_status_args(path_prefixes); + self.executor + .spawn(async move { + let working_directory = working_directory?; + let git = GitBinary::new(git_binary_path, working_directory, executor); + git.run(&args).await?.parse() + }) + .boxed() + } + + fn status_blocking(&self, path_prefixes: &[RepoPath]) -> Result { + let output = new_std_command(&self.git_binary_path) + .current_dir(self.working_directory()?) + .args(git_status_args(path_prefixes)) + .output()?; + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + stdout.parse() + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Err(anyhow!("git status failed: {}", stderr)) + } } fn branches(&self) -> BoxFuture>> { @@ -685,146 +696,145 @@ impl GitRepository for RealGitRepository { .boxed() } - fn change_branch(&self, name: String, cx: AsyncApp) -> BoxFuture> { + fn change_branch(&self, name: String) -> BoxFuture> { let repo = self.repository.clone(); - cx.background_spawn(async move { - let repo = repo.lock(); - let revision = repo.find_branch(&name, BranchType::Local)?; - let revision = revision.get(); - let as_tree = revision.peel_to_tree()?; - repo.checkout_tree(as_tree.as_object(), None)?; - repo.set_head( - revision - .name() - .ok_or_else(|| anyhow!("Branch name could not be retrieved"))?, - )?; - Ok(()) - }) - .boxed() + self.executor + .spawn(async move { + let repo = repo.lock(); + let revision = repo.find_branch(&name, BranchType::Local)?; + let revision = revision.get(); + let as_tree = revision.peel_to_tree()?; + repo.checkout_tree(as_tree.as_object(), None)?; + repo.set_head( + revision + .name() + .ok_or_else(|| anyhow!("Branch name could not be retrieved"))?, + )?; + Ok(()) + }) + .boxed() } - fn create_branch(&self, name: String, cx: AsyncApp) -> BoxFuture> { + fn create_branch(&self, name: String) -> BoxFuture> { let repo = self.repository.clone(); - cx.background_spawn(async move { - let repo = repo.lock(); - let current_commit = repo.head()?.peel_to_commit()?; - repo.branch(&name, ¤t_commit, false)?; - Ok(()) - }) - .boxed() + self.executor + .spawn(async move { + let repo = repo.lock(); + let current_commit = repo.head()?.peel_to_commit()?; + repo.branch(&name, ¤t_commit, false)?; + Ok(()) + }) + .boxed() } - fn blame( - &self, - path: RepoPath, - content: Rope, - cx: &mut AsyncApp, - ) -> BoxFuture> { + fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); const REMOTE_NAME: &str = "origin"; let remote_url = self.remote_url(REMOTE_NAME); - cx.background_spawn(async move { - crate::blame::Blame::for_path( - &git_binary_path, - &working_directory?, - &path, - &content, - remote_url, - ) - .await - }) - .boxed() + self.executor + .spawn(async move { + crate::blame::Blame::for_path( + &git_binary_path, + &working_directory?, + &path, + &content, + remote_url, + ) + .await + }) + .boxed() } - fn diff(&self, diff: DiffType, cx: AsyncApp) -> BoxFuture> { + fn diff(&self, diff: DiffType) -> BoxFuture> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - cx.background_spawn(async move { - let args = match diff { - DiffType::HeadToIndex => Some("--staged"), - DiffType::HeadToWorktree => None, - }; + self.executor + .spawn(async move { + let args = match diff { + DiffType::HeadToIndex => Some("--staged"), + DiffType::HeadToWorktree => None, + }; - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory?) - .args(["diff"]) - .args(args) - .output() - .await?; + let output = new_smol_command(&git_binary_path) + .current_dir(&working_directory?) + .args(["diff"]) + .args(args) + .output() + .await?; - if !output.status.success() { - return Err(anyhow!( - "Failed to run git diff:\n{}", - String::from_utf8_lossy(&output.stderr) - )); - } - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - }) - .boxed() + if !output.status.success() { + return Err(anyhow!( + "Failed to run git diff:\n{}", + String::from_utf8_lossy(&output.stderr) + )); + } + Ok(String::from_utf8_lossy(&output.stdout).to_string()) + }) + .boxed() } fn stage_paths( &self, paths: Vec, env: HashMap, - cx: AsyncApp, ) -> BoxFuture> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - cx.background_spawn(async move { - if !paths.is_empty() { - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory?) - .envs(env) - .args(["update-index", "--add", "--remove", "--"]) - .args(paths.iter().map(|p| p.to_unix_style())) - .output() - .await?; + self.executor + .spawn(async move { + if !paths.is_empty() { + let output = new_smol_command(&git_binary_path) + .current_dir(&working_directory?) + .envs(env) + .args(["update-index", "--add", "--remove", "--"]) + .args(paths.iter().map(|p| p.to_unix_style())) + .output() + .await?; - if !output.status.success() { - return Err(anyhow!( - "Failed to stage paths:\n{}", - String::from_utf8_lossy(&output.stderr) - )); + if !output.status.success() { + return Err(anyhow!( + "Failed to stage paths:\n{}", + String::from_utf8_lossy(&output.stderr) + )); + } } - } - Ok(()) - }) - .boxed() + Ok(()) + }) + .boxed() } fn unstage_paths( &self, paths: Vec, env: HashMap, - cx: AsyncApp, ) -> BoxFuture> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - cx.background_spawn(async move { - if !paths.is_empty() { - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory?) - .envs(env) - .args(["reset", "--quiet", "--"]) - .args(paths.iter().map(|p| p.as_ref())) - .output() - .await?; + self.executor + .spawn(async move { + if !paths.is_empty() { + let output = new_smol_command(&git_binary_path) + .current_dir(&working_directory?) + .envs(env) + .args(["reset", "--quiet", "--"]) + .args(paths.iter().map(|p| p.as_ref())) + .output() + .await?; - if !output.status.success() { - return Err(anyhow!( - "Failed to unstage:\n{}", - String::from_utf8_lossy(&output.stderr) - )); + if !output.status.success() { + return Err(anyhow!( + "Failed to unstage:\n{}", + String::from_utf8_lossy(&output.stderr) + )); + } } - } - Ok(()) - }) - .boxed() + Ok(()) + }) + .boxed() } fn commit( @@ -832,32 +842,32 @@ impl GitRepository for RealGitRepository { message: SharedString, name_and_email: Option<(SharedString, SharedString)>, env: HashMap, - cx: AsyncApp, ) -> BoxFuture> { let working_directory = self.working_directory(); - cx.background_spawn(async move { - let mut cmd = new_smol_command("git"); - cmd.current_dir(&working_directory?) - .envs(env) - .args(["commit", "--quiet", "-m"]) - .arg(&message.to_string()) - .arg("--cleanup=strip"); + self.executor + .spawn(async move { + let mut cmd = new_smol_command("git"); + cmd.current_dir(&working_directory?) + .envs(env) + .args(["commit", "--quiet", "-m"]) + .arg(&message.to_string()) + .arg("--cleanup=strip"); - if let Some((name, email)) = name_and_email { - cmd.arg("--author").arg(&format!("{name} <{email}>")); - } + if let Some((name, email)) = name_and_email { + cmd.arg("--author").arg(&format!("{name} <{email}>")); + } - let output = cmd.output().await?; + let output = cmd.output().await?; - if !output.status.success() { - return Err(anyhow!( - "Failed to commit:\n{}", - String::from_utf8_lossy(&output.stderr) - )); - } - Ok(()) - }) - .boxed() + if !output.status.success() { + return Err(anyhow!( + "Failed to commit:\n{}", + String::from_utf8_lossy(&output.stderr) + )); + } + Ok(()) + }) + .boxed() } fn push( @@ -867,8 +877,6 @@ impl GitRepository for RealGitRepository { options: Option, ask_pass: AskPassSession, env: HashMap, - // note: git push *must* be started on the main thread for - // git-credentials manager to work (hence taking an AsyncApp) _cx: AsyncApp, ) -> BoxFuture> { let working_directory = self.working_directory(); @@ -954,197 +962,194 @@ impl GitRepository for RealGitRepository { .boxed() } - fn get_remotes( - &self, - branch_name: Option, - cx: AsyncApp, - ) -> BoxFuture>> { + fn get_remotes(&self, branch_name: Option) -> BoxFuture>> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - cx.background_spawn(async move { - let working_directory = working_directory?; - if let Some(branch_name) = branch_name { - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory) - .args(["config", "--get"]) - .arg(format!("branch.{}.remote", branch_name)) - .output() - .await?; + self.executor + .spawn(async move { + let working_directory = working_directory?; + if let Some(branch_name) = branch_name { + let output = new_smol_command(&git_binary_path) + .current_dir(&working_directory) + .args(["config", "--get"]) + .arg(format!("branch.{}.remote", branch_name)) + .output() + .await?; - if output.status.success() { - let remote_name = String::from_utf8_lossy(&output.stdout); + if output.status.success() { + let remote_name = String::from_utf8_lossy(&output.stdout); - return Ok(vec![Remote { - name: remote_name.trim().to_string().into(), - }]); - } - } - - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory) - .args(["remote"]) - .output() - .await?; - - if output.status.success() { - let remote_names = String::from_utf8_lossy(&output.stdout) - .split('\n') - .filter(|name| !name.is_empty()) - .map(|name| Remote { - name: name.trim().to_string().into(), - }) - .collect(); - - return Ok(remote_names); - } else { - return Err(anyhow!( - "Failed to get remotes:\n{}", - String::from_utf8_lossy(&output.stderr) - )); - } - }) - .boxed() - } - - fn check_for_pushed_commit(&self, cx: AsyncApp) -> BoxFuture>> { - let working_directory = self.working_directory(); - let git_binary_path = self.git_binary_path.clone(); - cx.background_spawn(async move { - let working_directory = working_directory?; - let git_cmd = async |args: &[&str]| -> Result { - let output = new_smol_command(&git_binary_path) - .current_dir(&working_directory) - .args(args) - .output() - .await?; - if output.status.success() { - Ok(String::from_utf8(output.stdout)?) - } else { - Err(anyhow!(String::from_utf8_lossy(&output.stderr).to_string())) - } - }; - - let head = git_cmd(&["rev-parse", "HEAD"]) - .await - .context("Failed to get HEAD")? - .trim() - .to_owned(); - - let mut remote_branches = vec![]; - let mut add_if_matching = async |remote_head: &str| { - if let Ok(merge_base) = git_cmd(&["merge-base", &head, remote_head]).await { - if merge_base.trim() == head { - if let Some(s) = remote_head.strip_prefix("refs/remotes/") { - remote_branches.push(s.to_owned().into()); - } + return Ok(vec![Remote { + name: remote_name.trim().to_string().into(), + }]); } } - }; - // check the main branch of each remote - let remotes = git_cmd(&["remote"]) - .await - .context("Failed to get remotes")?; - for remote in remotes.lines() { + let output = new_smol_command(&git_binary_path) + .current_dir(&working_directory) + .args(["remote"]) + .output() + .await?; + + if output.status.success() { + let remote_names = String::from_utf8_lossy(&output.stdout) + .split('\n') + .filter(|name| !name.is_empty()) + .map(|name| Remote { + name: name.trim().to_string().into(), + }) + .collect(); + + return Ok(remote_names); + } else { + return Err(anyhow!( + "Failed to get remotes:\n{}", + String::from_utf8_lossy(&output.stderr) + )); + } + }) + .boxed() + } + + fn check_for_pushed_commit(&self) -> BoxFuture>> { + let working_directory = self.working_directory(); + let git_binary_path = self.git_binary_path.clone(); + self.executor + .spawn(async move { + let working_directory = working_directory?; + let git_cmd = async |args: &[&str]| -> Result { + let output = new_smol_command(&git_binary_path) + .current_dir(&working_directory) + .args(args) + .output() + .await?; + if output.status.success() { + Ok(String::from_utf8(output.stdout)?) + } else { + Err(anyhow!(String::from_utf8_lossy(&output.stderr).to_string())) + } + }; + + let head = git_cmd(&["rev-parse", "HEAD"]) + .await + .context("Failed to get HEAD")? + .trim() + .to_owned(); + + let mut remote_branches = vec![]; + let mut add_if_matching = async |remote_head: &str| { + if let Ok(merge_base) = git_cmd(&["merge-base", &head, remote_head]).await { + if merge_base.trim() == head { + if let Some(s) = remote_head.strip_prefix("refs/remotes/") { + remote_branches.push(s.to_owned().into()); + } + } + } + }; + + // check the main branch of each remote + let remotes = git_cmd(&["remote"]) + .await + .context("Failed to get remotes")?; + for remote in remotes.lines() { + if let Ok(remote_head) = + git_cmd(&["symbolic-ref", &format!("refs/remotes/{remote}/HEAD")]).await + { + add_if_matching(remote_head.trim()).await; + } + } + + // ... and the remote branch that the checked-out one is tracking if let Ok(remote_head) = - git_cmd(&["symbolic-ref", &format!("refs/remotes/{remote}/HEAD")]).await + git_cmd(&["rev-parse", "--symbolic-full-name", "@{u}"]).await { add_if_matching(remote_head.trim()).await; } - } - // ... and the remote branch that the checked-out one is tracking - if let Ok(remote_head) = git_cmd(&["rev-parse", "--symbolic-full-name", "@{u}"]).await { - add_if_matching(remote_head.trim()).await; - } - - Ok(remote_branches) - }) - .boxed() + Ok(remote_branches) + }) + .boxed() } - fn checkpoint(&self, cx: AsyncApp) -> BoxFuture> { + fn checkpoint(&self) -> BoxFuture> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - let executor = cx.background_executor().clone(); - cx.background_spawn(async move { - let working_directory = working_directory?; - let mut git = GitBinary::new(git_binary_path, working_directory, executor) - .envs(checkpoint_author_envs()); - git.with_temp_index(async |git| { - let head_sha = git.run(&["rev-parse", "HEAD"]).await.ok(); - git.run(&["add", "--all"]).await?; - let tree = git.run(&["write-tree"]).await?; - let checkpoint_sha = if let Some(head_sha) = head_sha.as_deref() { - git.run(&["commit-tree", &tree, "-p", head_sha, "-m", "Checkpoint"]) - .await? - } else { - git.run(&["commit-tree", &tree, "-m", "Checkpoint"]).await? - }; - let ref_name = format!("refs/zed/{}", Uuid::new_v4()); - git.run(&["update-ref", &ref_name, &checkpoint_sha]).await?; - - Ok(GitRepositoryCheckpoint { - ref_name, - head_sha: if let Some(head_sha) = head_sha { - Some(head_sha.parse()?) + let executor = self.executor.clone(); + self.executor + .spawn(async move { + let working_directory = working_directory?; + let mut git = GitBinary::new(git_binary_path, working_directory, executor) + .envs(checkpoint_author_envs()); + git.with_temp_index(async |git| { + let head_sha = git.run(&["rev-parse", "HEAD"]).await.ok(); + git.run(&["add", "--all"]).await?; + let tree = git.run(&["write-tree"]).await?; + let checkpoint_sha = if let Some(head_sha) = head_sha.as_deref() { + git.run(&["commit-tree", &tree, "-p", head_sha, "-m", "Checkpoint"]) + .await? } else { - None - }, - commit_sha: checkpoint_sha.parse()?, + git.run(&["commit-tree", &tree, "-m", "Checkpoint"]).await? + }; + let ref_name = format!("refs/zed/{}", Uuid::new_v4()); + git.run(&["update-ref", &ref_name, &checkpoint_sha]).await?; + + Ok(GitRepositoryCheckpoint { + ref_name, + head_sha: if let Some(head_sha) = head_sha { + Some(head_sha.parse()?) + } else { + None + }, + commit_sha: checkpoint_sha.parse()?, + }) }) + .await }) - .await - }) - .boxed() + .boxed() } - fn restore_checkpoint( - &self, - checkpoint: GitRepositoryCheckpoint, - cx: AsyncApp, - ) -> BoxFuture> { + fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - let executor = cx.background_executor().clone(); - cx.background_spawn(async move { - let working_directory = working_directory?; + let executor = self.executor.clone(); + self.executor + .spawn(async move { + let working_directory = working_directory?; - let mut git = GitBinary::new(git_binary_path, working_directory, executor); - git.run(&[ - "restore", - "--source", - &checkpoint.commit_sha.to_string(), - "--worktree", - ".", - ]) - .await?; + let mut git = GitBinary::new(git_binary_path, working_directory, executor); + git.run(&[ + "restore", + "--source", + &checkpoint.commit_sha.to_string(), + "--worktree", + ".", + ]) + .await?; - git.with_temp_index(async move |git| { - git.run(&["read-tree", &checkpoint.commit_sha.to_string()]) - .await?; - git.run(&["clean", "-d", "--force"]).await + git.with_temp_index(async move |git| { + git.run(&["read-tree", &checkpoint.commit_sha.to_string()]) + .await?; + git.run(&["clean", "-d", "--force"]).await + }) + .await?; + + if let Some(head_sha) = checkpoint.head_sha { + git.run(&["reset", "--mixed", &head_sha.to_string()]) + .await?; + } else { + git.run(&["update-ref", "-d", "HEAD"]).await?; + } + + Ok(()) }) - .await?; - - if let Some(head_sha) = checkpoint.head_sha { - git.run(&["reset", "--mixed", &head_sha.to_string()]) - .await?; - } else { - git.run(&["update-ref", "-d", "HEAD"]).await?; - } - - Ok(()) - }) - .boxed() + .boxed() } fn compare_checkpoints( &self, left: GitRepositoryCheckpoint, right: GitRepositoryCheckpoint, - cx: AsyncApp, ) -> BoxFuture> { if left.head_sha != right.head_sha { return future::ready(Ok(false)).boxed(); @@ -1153,55 +1158,72 @@ impl GitRepository for RealGitRepository { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - let executor = cx.background_executor().clone(); - cx.background_spawn(async move { - let working_directory = working_directory?; - let git = GitBinary::new(git_binary_path, working_directory, executor); - let result = git - .run(&[ - "diff-tree", - "--quiet", - &left.commit_sha.to_string(), - &right.commit_sha.to_string(), - ]) - .await; - match result { - Ok(_) => Ok(true), - Err(error) => { - if let Some(GitBinaryCommandError { status, .. }) = - error.downcast_ref::() - { - if status.code() == Some(1) { - return Ok(false); + let executor = self.executor.clone(); + self.executor + .spawn(async move { + let working_directory = working_directory?; + let git = GitBinary::new(git_binary_path, working_directory, executor); + let result = git + .run(&[ + "diff-tree", + "--quiet", + &left.commit_sha.to_string(), + &right.commit_sha.to_string(), + ]) + .await; + match result { + Ok(_) => Ok(true), + Err(error) => { + if let Some(GitBinaryCommandError { status, .. }) = + error.downcast_ref::() + { + if status.code() == Some(1) { + return Ok(false); + } } - } - Err(error) + Err(error) + } } - } - }) - .boxed() + }) + .boxed() } - fn delete_checkpoint( - &self, - checkpoint: GitRepositoryCheckpoint, - cx: AsyncApp, - ) -> BoxFuture> { + fn delete_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - let executor = cx.background_executor().clone(); - cx.background_spawn(async move { - let working_directory = working_directory?; - let git = GitBinary::new(git_binary_path, working_directory, executor); - git.run(&["update-ref", "-d", &checkpoint.ref_name]).await?; - Ok(()) - }) - .boxed() + let executor = self.executor.clone(); + self.executor + .spawn(async move { + let working_directory = working_directory?; + let git = GitBinary::new(git_binary_path, working_directory, executor); + git.run(&["update-ref", "-d", &checkpoint.ref_name]).await?; + Ok(()) + }) + .boxed() } } +fn git_status_args(path_prefixes: &[RepoPath]) -> Vec { + let mut args = vec![ + OsString::from("--no-optional-locks"), + OsString::from("status"), + OsString::from("--porcelain=v1"), + OsString::from("--untracked-files=all"), + OsString::from("--no-renames"), + OsString::from("-z"), + ]; + args.extend(path_prefixes.iter().map(|path_prefix| { + if path_prefix.0.as_ref() == Path::new("") { + Path::new(".").into() + } else { + path_prefix.as_os_str().into() + } + })); + args +} + struct GitBinary { git_binary_path: PathBuf, working_directory: PathBuf, @@ -1259,7 +1281,10 @@ impl GitBinary { Ok(result) } - pub async fn run(&self, args: &[&str]) -> Result { + pub async fn run(&self, args: impl IntoIterator) -> Result + where + S: AsRef, + { let mut command = new_smol_command(&self.git_binary_path); command.current_dir(&self.working_directory); command.args(args); @@ -1558,22 +1583,14 @@ mod tests { let file_path = repo_dir.path().join("file"); smol::fs::write(&file_path, "initial").await.unwrap(); - let repo = RealGitRepository::new(&repo_dir.path().join(".git"), None).unwrap(); - repo.stage_paths( - vec![RepoPath::from_str("file")], - HashMap::default(), - cx.to_async(), - ) - .await - .unwrap(); - repo.commit( - "Initial commit".into(), - None, - checkpoint_author_envs(), - cx.to_async(), - ) - .await - .unwrap(); + let repo = + RealGitRepository::new(&repo_dir.path().join(".git"), None, cx.executor()).unwrap(); + repo.stage_paths(vec![RepoPath::from_str("file")], HashMap::default()) + .await + .unwrap(); + repo.commit("Initial commit".into(), None, checkpoint_author_envs()) + .await + .unwrap(); smol::fs::write(&file_path, "modified before checkpoint") .await @@ -1582,7 +1599,7 @@ mod tests { .await .unwrap(); let sha_before_checkpoint = repo.head_sha().unwrap(); - let checkpoint = repo.checkpoint(cx.to_async()).await.unwrap(); + let checkpoint = repo.checkpoint().await.unwrap(); // Ensure the user can't see any branches after creating a checkpoint. assert_eq!(repo.branches().await.unwrap().len(), 1); @@ -1590,18 +1607,13 @@ mod tests { smol::fs::write(&file_path, "modified after checkpoint") .await .unwrap(); - repo.stage_paths( - vec![RepoPath::from_str("file")], - HashMap::default(), - cx.to_async(), - ) - .await - .unwrap(); + repo.stage_paths(vec![RepoPath::from_str("file")], HashMap::default()) + .await + .unwrap(); repo.commit( "Commit after checkpoint".into(), None, checkpoint_author_envs(), - cx.to_async(), ) .await .unwrap(); @@ -1614,10 +1626,8 @@ mod tests { .unwrap(); // Ensure checkpoint stays alive even after a Git GC. - repo.gc(cx.to_async()).await.unwrap(); - repo.restore_checkpoint(checkpoint.clone(), cx.to_async()) - .await - .unwrap(); + repo.gc().await.unwrap(); + repo.restore_checkpoint(checkpoint.clone()).await.unwrap(); assert_eq!(repo.head_sha().unwrap(), sha_before_checkpoint); assert_eq!( @@ -1638,11 +1648,9 @@ mod tests { ); // Garbage collecting after deleting a checkpoint makes it unreachable. - repo.delete_checkpoint(checkpoint.clone(), cx.to_async()) - .await - .unwrap(); - repo.gc(cx.to_async()).await.unwrap(); - repo.restore_checkpoint(checkpoint.clone(), cx.to_async()) + repo.delete_checkpoint(checkpoint.clone()).await.unwrap(); + repo.gc().await.unwrap(); + repo.restore_checkpoint(checkpoint.clone()) .await .unwrap_err(); } @@ -1653,12 +1661,13 @@ mod tests { let repo_dir = tempfile::tempdir().unwrap(); git2::Repository::init(repo_dir.path()).unwrap(); - let repo = RealGitRepository::new(&repo_dir.path().join(".git"), None).unwrap(); + let repo = + RealGitRepository::new(&repo_dir.path().join(".git"), None, cx.executor()).unwrap(); smol::fs::write(repo_dir.path().join("foo"), "foo") .await .unwrap(); - let checkpoint_sha = repo.checkpoint(cx.to_async()).await.unwrap(); + let checkpoint_sha = repo.checkpoint().await.unwrap(); // Ensure the user can't see any branches after creating a checkpoint. assert_eq!(repo.branches().await.unwrap().len(), 1); @@ -1669,9 +1678,7 @@ mod tests { smol::fs::write(repo_dir.path().join("baz"), "qux") .await .unwrap(); - repo.restore_checkpoint(checkpoint_sha, cx.to_async()) - .await - .unwrap(); + repo.restore_checkpoint(checkpoint_sha).await.unwrap(); assert_eq!( smol::fs::read_to_string(repo_dir.path().join("foo")) .await @@ -1696,22 +1703,14 @@ mod tests { let file_path = repo_dir.path().join("file"); smol::fs::write(&file_path, "initial").await.unwrap(); - let repo = RealGitRepository::new(&repo_dir.path().join(".git"), None).unwrap(); - repo.stage_paths( - vec![RepoPath::from_str("file")], - HashMap::default(), - cx.to_async(), - ) - .await - .unwrap(); - repo.commit( - "Initial commit".into(), - None, - checkpoint_author_envs(), - cx.to_async(), - ) - .await - .unwrap(); + let repo = + RealGitRepository::new(&repo_dir.path().join(".git"), None, cx.executor()).unwrap(); + repo.stage_paths(vec![RepoPath::from_str("file")], HashMap::default()) + .await + .unwrap(); + repo.commit("Initial commit".into(), None, checkpoint_author_envs()) + .await + .unwrap(); let initial_commit_sha = repo.head_sha().unwrap(); @@ -1722,7 +1721,7 @@ mod tests { .await .unwrap(); - let checkpoint = repo.checkpoint(cx.to_async()).await.unwrap(); + let checkpoint = repo.checkpoint().await.unwrap(); repo.stage_paths( vec![ @@ -1730,22 +1729,14 @@ mod tests { RepoPath::from_str("new_file2"), ], HashMap::default(), - cx.to_async(), ) .await .unwrap(); - repo.commit( - "Commit new files".into(), - None, - checkpoint_author_envs(), - cx.to_async(), - ) - .await - .unwrap(); - - repo.restore_checkpoint(checkpoint, cx.to_async()) + repo.commit("Commit new files".into(), None, checkpoint_author_envs()) .await .unwrap(); + + repo.restore_checkpoint(checkpoint).await.unwrap(); assert_eq!(repo.head_sha().unwrap(), initial_commit_sha); assert_eq!( smol::fs::read_to_string(repo_dir.path().join("new_file1")) @@ -1760,7 +1751,7 @@ mod tests { "content2" ); assert_eq!( - repo.status(&[]).unwrap().entries.as_ref(), + repo.status(&[]).await.unwrap().entries.as_ref(), &[ (RepoPath::from_str("new_file1"), FileStatus::Untracked), (RepoPath::from_str("new_file2"), FileStatus::Untracked) @@ -1774,26 +1765,27 @@ mod tests { let repo_dir = tempfile::tempdir().unwrap(); git2::Repository::init(repo_dir.path()).unwrap(); - let repo = RealGitRepository::new(&repo_dir.path().join(".git"), None).unwrap(); + let repo = + RealGitRepository::new(&repo_dir.path().join(".git"), None, cx.executor()).unwrap(); smol::fs::write(repo_dir.path().join("file1"), "content1") .await .unwrap(); - let checkpoint1 = repo.checkpoint(cx.to_async()).await.unwrap(); + let checkpoint1 = repo.checkpoint().await.unwrap(); smol::fs::write(repo_dir.path().join("file2"), "content2") .await .unwrap(); - let checkpoint2 = repo.checkpoint(cx.to_async()).await.unwrap(); + let checkpoint2 = repo.checkpoint().await.unwrap(); assert!(!repo - .compare_checkpoints(checkpoint1, checkpoint2.clone(), cx.to_async()) + .compare_checkpoints(checkpoint1, checkpoint2.clone()) .await .unwrap()); - let checkpoint3 = repo.checkpoint(cx.to_async()).await.unwrap(); + let checkpoint3 = repo.checkpoint().await.unwrap(); assert!(repo - .compare_checkpoints(checkpoint2, checkpoint3, cx.to_async()) + .compare_checkpoints(checkpoint2, checkpoint3) .await .unwrap()); } @@ -1827,18 +1819,19 @@ mod tests { impl RealGitRepository { /// Force a Git garbage collection on the repository. - fn gc(&self, cx: AsyncApp) -> BoxFuture> { + fn gc(&self) -> BoxFuture> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); - let executor = cx.background_executor().clone(); - cx.background_spawn(async move { - let git_binary_path = git_binary_path.clone(); - let working_directory = working_directory?; - let git = GitBinary::new(git_binary_path, working_directory, executor); - git.run(&["gc", "--prune=now"]).await?; - Ok(()) - }) - .boxed() + let executor = self.executor.clone(); + self.executor + .spawn(async move { + let git_binary_path = git_binary_path.clone(); + let working_directory = working_directory?; + let git = GitBinary::new(git_binary_path, working_directory, executor); + git.run(&["gc", "--prune=now"]).await?; + Ok(()) + }) + .boxed() } } } diff --git a/crates/git/src/status.rs b/crates/git/src/status.rs index efefcdfd48..528bcec01f 100644 --- a/crates/git/src/status.rs +++ b/crates/git/src/status.rs @@ -1,7 +1,7 @@ use crate::repository::RepoPath; use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; -use std::{path::Path, process::Stdio, sync::Arc}; +use std::{path::Path, str::FromStr, sync::Arc}; use util::ResultExt; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -443,45 +443,11 @@ pub struct GitStatus { pub entries: Arc<[(RepoPath, FileStatus)]>, } -impl GitStatus { - pub(crate) fn new( - git_binary: &Path, - working_directory: &Path, - path_prefixes: &[RepoPath], - ) -> Result { - let child = util::command::new_std_command(git_binary) - .current_dir(working_directory) - .args([ - "--no-optional-locks", - "status", - "--porcelain=v1", - "--untracked-files=all", - "--no-renames", - "-z", - ]) - .args(path_prefixes.iter().map(|path_prefix| { - if path_prefix.0.as_ref() == Path::new("") { - Path::new(".") - } else { - path_prefix - } - })) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .map_err(|e| anyhow!("Failed to start git status process: {e}"))?; +impl FromStr for GitStatus { + type Err = anyhow::Error; - let output = child - .wait_with_output() - .map_err(|e| anyhow!("Failed to read git status output: {e}"))?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(anyhow!("git status process failed: {stderr}")); - } - let stdout = String::from_utf8_lossy(&output.stdout); - let mut entries = stdout + fn from_str(s: &str) -> Result { + let mut entries = s .split('\0') .filter_map(|entry| { let sep = entry.get(2..3)?; diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 985d43b4d7..534a0e659e 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -74,6 +74,11 @@ pub(crate) use windows::*; #[cfg(any(test, feature = "test-support"))] pub use test::TestScreenCaptureSource; +/// Returns a background executor for the current platform. +pub fn background_executor() -> BackgroundExecutor { + current_platform(true).background_executor() +} + #[cfg(target_os = "macos")] pub(crate) fn current_platform(headless: bool) -> Rc { Rc::new(MacPlatform::new(headless)) diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 0787127032..ebf37e0af2 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -773,11 +773,11 @@ impl GitStore { anyhow::Ok(Some((repo, relative_path, content))) }); - cx.spawn(async move |cx| { + cx.spawn(async move |_cx| { let Some((repo, relative_path, content)) = blame_params? else { return Ok(None); }; - repo.blame(relative_path.clone(), content, cx) + repo.blame(relative_path.clone(), content) .await .with_context(|| format!("Failed to blame {:?}", relative_path.0)) .map(Some) @@ -1282,16 +1282,13 @@ impl GitStore { let index_text = if current_index_text.is_some() { local_repo .repo() - .load_index_text(relative_path.clone(), cx.clone()) + .load_index_text(relative_path.clone()) .await } else { None }; let head_text = if current_head_text.is_some() { - local_repo - .repo() - .load_committed_text(relative_path, cx.clone()) - .await + local_repo.repo().load_committed_text(relative_path).await } else { None }; @@ -2832,9 +2829,9 @@ impl Repository { } pub fn show(&self, commit: String) -> oneshot::Receiver> { - self.send_job(|git_repo, cx| async move { + self.send_job(|git_repo, _cx| async move { match git_repo { - RepositoryState::Local(git_repository) => git_repository.show(commit, cx).await, + RepositoryState::Local(git_repository) => git_repository.show(commit).await, RepositoryState::Remote { project_id, client, @@ -2901,9 +2898,9 @@ impl Repository { let env = env.await; this.update(cx, |this, _| { - this.send_job(|git_repo, cx| async move { + this.send_job(|git_repo, _cx| async move { match git_repo { - RepositoryState::Local(repo) => repo.stage_paths(entries, env, cx).await, + RepositoryState::Local(repo) => repo.stage_paths(entries, env).await, RepositoryState::Remote { project_id, client, @@ -2969,9 +2966,9 @@ impl Repository { let env = env.await; this.update(cx, |this, _| { - this.send_job(|git_repo, cx| async move { + this.send_job(|git_repo, _cx| async move { match git_repo { - RepositoryState::Local(repo) => repo.unstage_paths(entries, env, cx).await, + RepositoryState::Local(repo) => repo.unstage_paths(entries, env).await, RepositoryState::Remote { project_id, client, @@ -3055,11 +3052,11 @@ impl Repository { cx: &mut App, ) -> oneshot::Receiver> { let env = self.worktree_environment(cx); - self.send_job(|git_repo, cx| async move { + self.send_job(|git_repo, _cx| async move { match git_repo { RepositoryState::Local(repo) => { let env = env.await; - repo.commit(message, name_and_email, env, cx).await + repo.commit(message, name_and_email, env).await } RepositoryState::Remote { project_id, @@ -3254,10 +3251,10 @@ impl Repository { self.send_keyed_job( Some(GitJobKey::WriteIndex(path.clone())), - |git_repo, cx| async { + |git_repo, _cx| async { match git_repo { RepositoryState::Local(repo) => { - repo.set_index_text(path, content, env.await, cx).await + repo.set_index_text(path, content, env.await).await } RepositoryState::Remote { project_id, @@ -3283,10 +3280,10 @@ impl Repository { &self, branch_name: Option, ) -> oneshot::Receiver>> { - self.send_job(|repo, cx| async move { + self.send_job(|repo, _cx| async move { match repo { RepositoryState::Local(git_repository) => { - git_repository.get_remotes(branch_name, cx).await + git_repository.get_remotes(branch_name).await } RepositoryState::Remote { project_id, @@ -3352,9 +3349,9 @@ impl Repository { } pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver> { - self.send_job(|repo, cx| async move { + self.send_job(|repo, _cx| async move { match repo { - RepositoryState::Local(git_repository) => git_repository.diff(diff_type, cx).await, + RepositoryState::Local(git_repository) => git_repository.diff(diff_type).await, RepositoryState::Remote { project_id, client, @@ -3383,10 +3380,10 @@ impl Repository { } pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver> { - self.send_job(|repo, cx| async move { + self.send_job(|repo, _cx| async move { match repo { RepositoryState::Local(git_repository) => { - git_repository.create_branch(branch_name, cx).await + git_repository.create_branch(branch_name).await } RepositoryState::Remote { project_id, @@ -3408,10 +3405,10 @@ impl Repository { } pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver> { - self.send_job(|repo, cx| async move { + self.send_job(|repo, _cx| async move { match repo { RepositoryState::Local(git_repository) => { - git_repository.change_branch(branch_name, cx).await + git_repository.change_branch(branch_name).await } RepositoryState::Remote { project_id, @@ -3433,10 +3430,10 @@ impl Repository { } pub fn check_for_pushed_commits(&self) -> oneshot::Receiver>> { - self.send_job(|repo, cx| async move { + self.send_job(|repo, _cx| async move { match repo { RepositoryState::Local(git_repository) => { - git_repository.check_for_pushed_commit(cx).await + git_repository.check_for_pushed_commit().await } RepositoryState::Remote { project_id, @@ -3459,9 +3456,9 @@ impl Repository { } pub fn checkpoint(&self) -> oneshot::Receiver> { - self.send_job(|repo, cx| async move { + self.send_job(|repo, _cx| async move { match repo { - RepositoryState::Local(git_repository) => git_repository.checkpoint(cx).await, + RepositoryState::Local(git_repository) => git_repository.checkpoint().await, RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")), } }) @@ -3471,10 +3468,10 @@ impl Repository { &self, checkpoint: GitRepositoryCheckpoint, ) -> oneshot::Receiver> { - self.send_job(move |repo, cx| async move { + self.send_job(move |repo, _cx| async move { match repo { RepositoryState::Local(git_repository) => { - git_repository.restore_checkpoint(checkpoint, cx).await + git_repository.restore_checkpoint(checkpoint).await } RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")), } @@ -3513,10 +3510,10 @@ impl Repository { left: GitRepositoryCheckpoint, right: GitRepositoryCheckpoint, ) -> oneshot::Receiver> { - self.send_job(move |repo, cx| async move { + self.send_job(move |repo, _cx| async move { match repo { RepositoryState::Local(git_repository) => { - git_repository.compare_checkpoints(left, right, cx).await + git_repository.compare_checkpoints(left, right).await } RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")), } @@ -3527,10 +3524,10 @@ impl Repository { &self, checkpoint: GitRepositoryCheckpoint, ) -> oneshot::Receiver> { - self.send_job(move |repo, cx| async move { + self.send_job(move |repo, _cx| async move { match repo { RepositoryState::Local(git_repository) => { - git_repository.delete_checkpoint(checkpoint, cx).await + git_repository.delete_checkpoint(checkpoint).await } RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")), } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 04eb54752f..62aae83e07 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1476,7 +1476,7 @@ impl Project { ) -> Entity { use clock::FakeSystemClock; - let fs = Arc::new(RealFs::default()); + let fs = Arc::new(RealFs::new(None, cx.background_executor().clone())); let languages = LanguageRegistry::test(cx.background_executor().clone()); let clock = Arc::new(FakeSystemClock::new()); let http_client = http_client::FakeHttpClient::with_404_response(); diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index e833a40332..d5abc60002 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -99,7 +99,12 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) { ) .unwrap(); - let project = Project::test(Arc::new(RealFs::default()), [root_link_path.as_ref()], cx).await; + let project = Project::test( + Arc::new(RealFs::new(None, cx.executor())), + [root_link_path.as_ref()], + cx, + ) + .await; project.update(cx, |project, cx| { let tree = project.worktrees(cx).next().unwrap().read(cx); @@ -3332,7 +3337,7 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) { } })); - let project = Project::test(Arc::new(RealFs::default()), [dir.path()], cx).await; + let project = Project::test(Arc::new(RealFs::new(None, cx.executor())), [dir.path()], cx).await; let buffer_for_path = |path: &'static str, cx: &mut gpui::TestAppContext| { let buffer = project.update(cx, |p, cx| p.open_local_buffer(dir.path().join(path), cx)); diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index 3e3126c649..5a585cee69 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -445,7 +445,7 @@ pub fn execute_run( let extension_host_proxy = ExtensionHostProxy::global(cx); let project = cx.new(|cx| { - let fs = Arc::new(RealFs::new(None)); + let fs = Arc::new(RealFs::new(None, cx.background_executor().clone())); let node_settings_rx = initialize_settings(session.clone(), fs.clone(), cx); let proxy_url = read_proxy_settings(cx); diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 9c7952cc8c..0d2b6e00c7 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -1035,16 +1035,13 @@ impl Worktree { Worktree::Local(this) => { let path = Arc::from(path); let snapshot = this.snapshot(); - cx.spawn(async move |cx| { + cx.spawn(async move |_cx| { if let Some(repo) = snapshot.local_repo_containing_path(&path) { if let Some(repo_path) = repo.relativize(&path).log_err() { if let Some(git_repo) = snapshot.git_repositories.get(&repo.work_directory_id) { - return Ok(git_repo - .repo_ptr - .load_index_text(repo_path, cx.clone()) - .await); + return Ok(git_repo.repo_ptr.load_index_text(repo_path).await); } } } @@ -1062,16 +1059,13 @@ impl Worktree { Worktree::Local(this) => { let path = Arc::from(path); let snapshot = this.snapshot(); - cx.spawn(async move |cx| { + cx.spawn(async move |_cx| { if let Some(repo) = snapshot.local_repo_containing_path(&path) { if let Some(repo_path) = repo.relativize(&path).log_err() { if let Some(git_repo) = snapshot.git_repositories.get(&repo.work_directory_id) { - return Ok(git_repo - .repo_ptr - .load_committed_text(repo_path, cx.clone()) - .await); + return Ok(git_repo.repo_ptr.load_committed_text(repo_path).await); } } } @@ -5027,7 +5021,7 @@ impl BackgroundScanner { } for (_work_directory, mut paths) in paths_by_git_repo { - if let Ok(status) = paths.repo.status(&paths.repo_paths) { + if let Ok(status) = paths.repo.status_blocking(&paths.repo_paths) { let mut changed_path_statuses = Vec::new(); let statuses = paths.entry.statuses_by_path.clone(); let mut cursor = statuses.cursor::(&()); @@ -5531,7 +5525,7 @@ async fn do_git_status_update( log::trace!("updating git statuses for repo {repository_name}"); let Some(statuses) = local_repository .repo() - .status(&[git::WORK_DIRECTORY_REPO_PATH.clone()]) + .status_blocking(&[git::WORK_DIRECTORY_REPO_PATH.clone()]) .log_err() else { return; diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 9c7006fe64..2662103b32 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -351,7 +351,7 @@ async fn test_renaming_case_only(cx: &mut TestAppContext) { const OLD_NAME: &str = "aaa.rs"; const NEW_NAME: &str = "AAA.rs"; - let fs = Arc::new(RealFs::default()); + let fs = Arc::new(RealFs::new(None, cx.executor())); let temp_root = TempTree::new(json!({ OLD_NAME: "", })); @@ -876,7 +876,7 @@ async fn test_write_file(cx: &mut TestAppContext) { let worktree = Worktree::local( dir.path(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -965,7 +965,7 @@ async fn test_file_scan_inclusions(cx: &mut TestAppContext) { let tree = Worktree::local( dir.path(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -1028,7 +1028,7 @@ async fn test_file_scan_exclusions_overrules_inclusions(cx: &mut TestAppContext) let tree = Worktree::local( dir.path(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -1085,7 +1085,7 @@ async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppC let tree = Worktree::local( dir.path(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -1166,7 +1166,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { let tree = Worktree::local( dir.path(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -1271,7 +1271,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) { let tree = Worktree::local( dir.path(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -1382,7 +1382,7 @@ async fn test_fs_events_in_dot_git_worktree(cx: &mut TestAppContext) { let tree = Worktree::local( dot_git_worktree_dir.clone(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -1512,7 +1512,7 @@ async fn test_create_dir_all_on_create_entry(cx: &mut TestAppContext) { assert!(tree.entry_for_path("a/b/").unwrap().is_dir()); }); - let fs_real = Arc::new(RealFs::default()); + let fs_real = Arc::new(RealFs::new(None, cx.executor())); let temp_root = TempTree::new(json!({ "a": {} })); @@ -2126,7 +2126,7 @@ async fn test_rename_work_directory(cx: &mut TestAppContext) { let tree = Worktree::local( root_path, true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -2233,7 +2233,7 @@ async fn test_file_status(cx: &mut TestAppContext) { let tree = Worktree::local( root.path(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -2422,7 +2422,7 @@ async fn test_git_repository_status(cx: &mut TestAppContext) { let tree = Worktree::local( root.path(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -2551,7 +2551,7 @@ async fn test_git_status_postprocessing(cx: &mut TestAppContext) { let tree = Worktree::local( root.path(), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -2619,7 +2619,7 @@ async fn test_repository_subfolder_git_status(cx: &mut TestAppContext) { let tree = Worktree::local( root.path().join(project_root), true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) @@ -2685,7 +2685,7 @@ async fn test_conflicted_cherry_pick(cx: &mut TestAppContext) { let tree = Worktree::local( root_path, true, - Arc::new(RealFs::default()), + Arc::new(RealFs::new(None, cx.executor())), Default::default(), &mut cx.to_async(), ) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 88fafd1abd..f07d8817f2 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -264,7 +264,7 @@ fn main() { }; log::info!("Using git binary path: {:?}", git_binary_path); - let fs = Arc::new(RealFs::new(git_binary_path)); + let fs = Arc::new(RealFs::new(git_binary_path, app.background_executor())); let user_settings_file_rx = watch_config_file( &app.background_executor(), fs.clone(),