Cargo.lock 🔗
@@ -4749,6 +4749,7 @@ dependencies = [
"env_logger 0.11.7",
"extension",
"fs",
+ "gpui",
"language",
"log",
"reqwest_client",
Antonio Scandurra created
This lays the groundwork for using `status` as part of the new agent
panel.
Release Notes:
- N/A
Cargo.lock | 1
crates/assistant2/src/thread.rs | 2
crates/assistant_eval/src/headless_assistant.rs | 5
crates/evals/src/eval.rs | 2
crates/extension_cli/Cargo.toml | 1
crates/extension_cli/src/main.rs | 2
crates/extension_host/src/extension_store_test.rs | 2
crates/fs/src/fake_git_repo.rs | 62
crates/fs/src/fs.rs | 11
crates/git/src/repository.rs | 871 ++++++++--------
crates/git/src/status.rs | 46
crates/gpui/src/platform.rs | 5
crates/project/src/git_store.rs | 67
crates/project/src/project.rs | 2
crates/project/src/project_tests.rs | 9
crates/remote_server/src/unix.rs | 2
crates/worktree/src/worktree.rs | 18
crates/worktree/src/worktree_tests.rs | 30
crates/zed/src/main.rs | 2
19 files changed, 549 insertions(+), 591 deletions(-)
@@ -4749,6 +4749,7 @@ dependencies = [
"env_logger 0.11.7",
"extension",
"fs",
+ "gpui",
"language",
"log",
"reqwest_client",
@@ -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();
@@ -149,7 +149,10 @@ pub fn init(cx: &mut App) -> Arc<HeadlessAppState> {
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()));
@@ -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<dyn Fs>;
+ let fs = Arc::new(RealFs::new(None, cx.background_executor().clone())) as Arc<dyn Fs>;
let clock = Arc::new(RealSystemClock);
let client = cx
.update(|cx| {
@@ -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
@@ -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)?;
@@ -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": {}
@@ -62,7 +62,7 @@ impl FakeGitRepository {
.unwrap()
}
- fn with_state_async<F, T>(&self, write: bool, f: F) -> BoxFuture<Result<T>>
+ fn with_state_async<F, T>(&self, write: bool, f: F) -> BoxFuture<'static, Result<T>>
where
F: 'static + Send + FnOnce(&mut FakeGitRepositoryState) -> Result<T>,
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<Option<String>> {
+ fn load_index_text(&self, path: RepoPath) -> BoxFuture<Option<String>> {
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<Option<String>> {
+ fn load_committed_text(&self, path: RepoPath) -> BoxFuture<Option<String>> {
async {
self.with_state_async(false, move |state| {
state
@@ -116,7 +116,6 @@ impl GitRepository for FakeGitRepository {
path: RepoPath,
content: Option<String>,
_env: HashMap<String, String>,
- _cx: AsyncApp,
) -> BoxFuture<anyhow::Result<()>> {
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<Result<CommitDetails>> {
+ fn show(&self, _commit: String) -> BoxFuture<Result<CommitDetails>> {
unimplemented!()
}
@@ -172,7 +171,12 @@ impl GitRepository for FakeGitRepository {
self.path()
}
- fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
+ fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'static, Result<GitStatus>> {
+ let status = self.status_blocking(path_prefixes);
+ async move { status }.boxed()
+ }
+
+ fn status_blocking(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
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<Result<()>> {
+ fn change_branch(&self, name: String) -> BoxFuture<Result<()>> {
self.with_state_async(true, |state| {
state.current_branch_name = Some(name);
Ok(())
})
}
- fn create_branch(&self, name: String, _: AsyncApp) -> BoxFuture<Result<()>> {
+ fn create_branch(&self, name: String) -> BoxFuture<Result<()>> {
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<Result<git::blame::Blame>> {
+ fn blame(&self, path: RepoPath, _content: Rope) -> BoxFuture<Result<git::blame::Blame>> {
self.with_state_async(false, move |state| {
state
.blames
@@ -350,7 +349,6 @@ impl GitRepository for FakeGitRepository {
&self,
_paths: Vec<RepoPath>,
_env: HashMap<String, String>,
- _cx: AsyncApp,
) -> BoxFuture<Result<()>> {
unimplemented!()
}
@@ -359,7 +357,6 @@ impl GitRepository for FakeGitRepository {
&self,
_paths: Vec<RepoPath>,
_env: HashMap<String, String>,
- _cx: AsyncApp,
) -> BoxFuture<Result<()>> {
unimplemented!()
}
@@ -369,7 +366,6 @@ impl GitRepository for FakeGitRepository {
_message: gpui::SharedString,
_name_and_email: Option<(gpui::SharedString, gpui::SharedString)>,
_env: HashMap<String, String>,
- _cx: AsyncApp,
) -> BoxFuture<Result<()>> {
unimplemented!()
}
@@ -406,38 +402,23 @@ impl GitRepository for FakeGitRepository {
unimplemented!()
}
- fn get_remotes(
- &self,
- _branch: Option<String>,
- _cx: AsyncApp,
- ) -> BoxFuture<Result<Vec<Remote>>> {
+ fn get_remotes(&self, _branch: Option<String>) -> BoxFuture<Result<Vec<Remote>>> {
unimplemented!()
}
- fn check_for_pushed_commit(
- &self,
- _cx: gpui::AsyncApp,
- ) -> BoxFuture<Result<Vec<gpui::SharedString>>> {
+ fn check_for_pushed_commit(&self) -> BoxFuture<Result<Vec<gpui::SharedString>>> {
future::ready(Ok(Vec::new())).boxed()
}
- fn diff(
- &self,
- _diff: git::repository::DiffType,
- _cx: gpui::AsyncApp,
- ) -> BoxFuture<Result<String>> {
+ fn diff(&self, _diff: git::repository::DiffType) -> BoxFuture<Result<String>> {
unimplemented!()
}
- fn checkpoint(&self, _cx: AsyncApp) -> BoxFuture<Result<GitRepositoryCheckpoint>> {
+ fn checkpoint(&self) -> BoxFuture<Result<GitRepositoryCheckpoint>> {
unimplemented!()
}
- fn restore_checkpoint(
- &self,
- _checkpoint: GitRepositoryCheckpoint,
- _cx: AsyncApp,
- ) -> BoxFuture<Result<()>> {
+ fn restore_checkpoint(&self, _checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>> {
unimplemented!()
}
@@ -445,16 +426,11 @@ impl GitRepository for FakeGitRepository {
&self,
_left: GitRepositoryCheckpoint,
_right: GitRepositoryCheckpoint,
- _cx: AsyncApp,
) -> BoxFuture<Result<bool>> {
unimplemented!()
}
- fn delete_checkpoint(
- &self,
- _checkpoint: GitRepositoryCheckpoint,
- _cx: AsyncApp,
- ) -> BoxFuture<Result<()>> {
+ fn delete_checkpoint(&self, _checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>> {
unimplemented!()
}
}
@@ -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<MTime> for proto::Timestamp {
}
}
-#[derive(Default)]
pub struct RealFs {
git_binary_path: Option<PathBuf>,
+ 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<PathBuf>) -> Self {
- Self { git_binary_path }
+ pub fn new(git_binary_path: Option<PathBuf>, 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(),
)?))
}
@@ -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<Option<String>>;
+ fn load_index_text(&self, path: RepoPath) -> BoxFuture<Option<String>>;
/// 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<Option<String>>;
+ fn load_committed_text(&self, path: RepoPath) -> BoxFuture<Option<String>>;
fn set_index_text(
&self,
path: RepoPath,
content: Option<String>,
env: HashMap<String, String>,
- cx: AsyncApp,
) -> BoxFuture<anyhow::Result<()>>;
/// 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<String>;
- // Note: this method blocks the current thread!
- fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus>;
+ fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'static, Result<GitStatus>>;
+ fn status_blocking(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus>;
fn branches(&self) -> BoxFuture<Result<Vec<Branch>>>;
- fn change_branch(&self, _: String, _: AsyncApp) -> BoxFuture<Result<()>>;
- fn create_branch(&self, _: String, _: AsyncApp) -> BoxFuture<Result<()>>;
+ fn change_branch(&self, name: String) -> BoxFuture<Result<()>>;
+ fn create_branch(&self, name: String) -> BoxFuture<Result<()>>;
fn reset(
&self,
@@ -206,14 +205,9 @@ pub trait GitRepository: Send + Sync {
env: HashMap<String, String>,
) -> BoxFuture<Result<()>>;
- fn show(&self, commit: String, cx: AsyncApp) -> BoxFuture<Result<CommitDetails>>;
+ fn show(&self, commit: String) -> BoxFuture<Result<CommitDetails>>;
- fn blame(
- &self,
- path: RepoPath,
- content: Rope,
- cx: &mut AsyncApp,
- ) -> BoxFuture<Result<crate::blame::Blame>>;
+ fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<Result<crate::blame::Blame>>;
/// 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/<name>`).
@@ -234,7 +228,6 @@ pub trait GitRepository: Send + Sync {
&self,
paths: Vec<RepoPath>,
env: HashMap<String, String>,
- cx: AsyncApp,
) -> BoxFuture<Result<()>>;
/// Updates the index to match HEAD at the given paths.
///
@@ -243,7 +236,6 @@ pub trait GitRepository: Send + Sync {
&self,
paths: Vec<RepoPath>,
env: HashMap<String, String>,
- cx: AsyncApp,
) -> BoxFuture<Result<()>>;
fn commit(
@@ -251,7 +243,6 @@ pub trait GitRepository: Send + Sync {
message: SharedString,
name_and_email: Option<(SharedString, SharedString)>,
env: HashMap<String, String>,
- cx: AsyncApp,
) -> BoxFuture<Result<()>>;
fn push(
@@ -261,6 +252,8 @@ pub trait GitRepository: Send + Sync {
options: Option<PushOptions>,
askpass: AskPassSession,
env: HashMap<String, String>,
+ // This method takes an AsyncApp to ensure it's invoked on the main thread,
+ // otherwise git-credentials-manager won't work.
cx: AsyncApp,
) -> BoxFuture<Result<RemoteCommandOutput>>;
@@ -270,6 +263,8 @@ pub trait GitRepository: Send + Sync {
upstream_name: String,
askpass: AskPassSession,
env: HashMap<String, String>,
+ // This method takes an AsyncApp to ensure it's invoked on the main thread,
+ // otherwise git-credentials-manager won't work.
cx: AsyncApp,
) -> BoxFuture<Result<RemoteCommandOutput>>;
@@ -277,45 +272,34 @@ pub trait GitRepository: Send + Sync {
&self,
askpass: AskPassSession,
env: HashMap<String, String>,
+ // This method takes an AsyncApp to ensure it's invoked on the main thread,
+ // otherwise git-credentials-manager won't work.
cx: AsyncApp,
) -> BoxFuture<Result<RemoteCommandOutput>>;
- fn get_remotes(
- &self,
- branch_name: Option<String>,
- cx: AsyncApp,
- ) -> BoxFuture<Result<Vec<Remote>>>;
+ fn get_remotes(&self, branch_name: Option<String>) -> BoxFuture<Result<Vec<Remote>>>;
/// returns a list of remote branches that contain HEAD
- fn check_for_pushed_commit(&self, cx: AsyncApp) -> BoxFuture<Result<Vec<SharedString>>>;
+ fn check_for_pushed_commit(&self) -> BoxFuture<Result<Vec<SharedString>>>;
/// Run git diff
- fn diff(&self, diff: DiffType, cx: AsyncApp) -> BoxFuture<Result<String>>;
+ fn diff(&self, diff: DiffType) -> BoxFuture<Result<String>>;
/// Creates a checkpoint for the repository.
- fn checkpoint(&self, cx: AsyncApp) -> BoxFuture<Result<GitRepositoryCheckpoint>>;
+ fn checkpoint(&self) -> BoxFuture<Result<GitRepositoryCheckpoint>>;
/// Resets to a previously-created checkpoint.
- fn restore_checkpoint(
- &self,
- checkpoint: GitRepositoryCheckpoint,
- cx: AsyncApp,
- ) -> BoxFuture<Result<()>>;
+ fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>>;
/// Compares two checkpoints, returning true if they are equal
fn compare_checkpoints(
&self,
left: GitRepositoryCheckpoint,
right: GitRepositoryCheckpoint,
- cx: AsyncApp,
) -> BoxFuture<Result<bool>>;
/// Deletes a previously-created checkpoint.
- fn delete_checkpoint(
- &self,
- checkpoint: GitRepositoryCheckpoint,
- cx: AsyncApp,
- ) -> BoxFuture<Result<()>>;
+ fn delete_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>>;
}
pub enum DiffType {
@@ -338,15 +322,21 @@ impl std::fmt::Debug for dyn GitRepository {
pub struct RealGitRepository {
pub repository: Arc<Mutex<git2::Repository>>,
pub git_binary_path: PathBuf,
+ executor: BackgroundExecutor,
}
impl RealGitRepository {
- pub fn new(dotgit_path: &Path, git_binary_path: Option<PathBuf>) -> Option<Self> {
+ pub fn new(
+ dotgit_path: &Path,
+ git_binary_path: Option<PathBuf>,
+ executor: BackgroundExecutor,
+ ) -> Option<Self> {
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<Result<CommitDetails>> {
+ fn show(&self, commit: String) -> BoxFuture<Result<CommitDetails>> {
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<Option<String>> {
+ fn load_index_text(&self, path: RepoPath) -> BoxFuture<Option<String>> {
let repo = self.repository.clone();
- cx.background_spawn(async move {
- fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
- // 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)?;
-
- 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()
+ self.executor
+ .spawn(async move {
+ fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
+ // 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)?;
+
+ 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()
}
- fn load_committed_text(&self, path: RepoPath, cx: AsyncApp) -> BoxFuture<Option<String>> {
+ fn load_committed_text(&self, path: RepoPath) -> BoxFuture<Option<String>> {
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<String>,
env: HashMap<String, String>,
- cx: AsyncApp,
) -> BoxFuture<anyhow::Result<()>> {
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)?;
-
- 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?;
-
- 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)
- ));
+ 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:?}");
+
+ 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)
+ ));
+ }
+ } 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<String> {
@@ -614,14 +607,32 @@ impl GitRepository for RealGitRepository {
shas
}
- fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus> {
- 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<GitStatus>> {
+ 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<GitStatus> {
+ 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<Result<Vec<Branch>>> {
@@ -685,146 +696,145 @@ impl GitRepository for RealGitRepository {
.boxed()
}
- fn change_branch(&self, name: String, cx: AsyncApp) -> BoxFuture<Result<()>> {
+ fn change_branch(&self, name: String) -> BoxFuture<Result<()>> {
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<Result<()>> {
+ fn create_branch(&self, name: String) -> BoxFuture<Result<()>> {
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<Result<crate::blame::Blame>> {
+ fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<Result<crate::blame::Blame>> {
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<Result<String>> {
+ fn diff(&self, diff: DiffType) -> BoxFuture<Result<String>> {
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<RepoPath>,
env: HashMap<String, String>,
- cx: AsyncApp,
) -> BoxFuture<Result<()>> {
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?;
-
- if !output.status.success() {
- return Err(anyhow!(
- "Failed to stage paths:\n{}",
- String::from_utf8_lossy(&output.stderr)
- ));
+ 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)
+ ));
+ }
}
- }
- Ok(())
- })
- .boxed()
+ Ok(())
+ })
+ .boxed()
}
fn unstage_paths(
&self,
paths: Vec<RepoPath>,
env: HashMap<String, String>,
- cx: AsyncApp,
) -> BoxFuture<Result<()>> {
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?;
-
- if !output.status.success() {
- return Err(anyhow!(
- "Failed to unstage:\n{}",
- String::from_utf8_lossy(&output.stderr)
- ));
+ 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)
+ ));
+ }
}
- }
- Ok(())
- })
- .boxed()
+ Ok(())
+ })
+ .boxed()
}
fn commit(
@@ -832,32 +842,32 @@ impl GitRepository for RealGitRepository {
message: SharedString,
name_and_email: Option<(SharedString, SharedString)>,
env: HashMap<String, String>,
- cx: AsyncApp,
) -> BoxFuture<Result<()>> {
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<PushOptions>,
ask_pass: AskPassSession,
env: HashMap<String, String>,
- // note: git push *must* be started on the main thread for
- // git-credentials manager to work (hence taking an AsyncApp)
_cx: AsyncApp,
) -> BoxFuture<Result<RemoteCommandOutput>> {
let working_directory = self.working_directory();
@@ -954,197 +962,194 @@ impl GitRepository for RealGitRepository {
.boxed()
}
- fn get_remotes(
- &self,
- branch_name: Option<String>,
- cx: AsyncApp,
- ) -> BoxFuture<Result<Vec<Remote>>> {
+ fn get_remotes(&self, branch_name: Option<String>) -> BoxFuture<Result<Vec<Remote>>> {
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 {
+ 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);
+
+ return Ok(vec![Remote {
+ name: remote_name.trim().to_string().into(),
+ }]);
+ }
+ }
+
let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
- .args(["config", "--get"])
- .arg(format!("branch.{}.remote", branch_name))
+ .args(["remote"])
.output()
.await?;
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 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)
+ ));
}
- }
-
- 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()
+ })
+ .boxed()
}
- fn check_for_pushed_commit(&self, cx: AsyncApp) -> BoxFuture<Result<Vec<SharedString>>> {
+ fn check_for_pushed_commit(&self) -> BoxFuture<Result<Vec<SharedString>>> {
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<String> {
- 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()))
- }
- };
+ self.executor
+ .spawn(async move {
+ let working_directory = working_directory?;
+ let git_cmd = async |args: &[&str]| -> Result<String> {
+ 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());
+ 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;
+ }
}
- };
- // check the main branch of each remote
- let remotes = git_cmd(&["remote"])
- .await
- .context("Failed to get remotes")?;
- for remote in remotes.lines() {
+ // ... 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<Result<GitRepositoryCheckpoint>> {
+ fn checkpoint(&self) -> BoxFuture<Result<GitRepositoryCheckpoint>> {
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<Result<()>> {
+ fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<Result<()>> {
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?;
-
- git.with_temp_index(async move |git| {
- git.run(&["read-tree", &checkpoint.commit_sha.to_string()])
- .await?;
- git.run(&["clean", "-d", "--force"]).await
- })
- .await?;
+ let mut git = GitBinary::new(git_binary_path, working_directory, executor);
+ git.run(&[
+ "restore",
+ "--source",
+ &checkpoint.commit_sha.to_string(),
+ "--worktree",
+ ".",
+ ])
+ .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?;
- }
+ git.with_temp_index(async move |git| {
+ git.run(&["read-tree", &checkpoint.commit_sha.to_string()])
+ .await?;
+ git.run(&["clean", "-d", "--force"]).await
+ })
+ .await?;
- Ok(())
- })
- .boxed()
+ 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()
}
fn compare_checkpoints(
&self,
left: GitRepositoryCheckpoint,
right: GitRepositoryCheckpoint,
- cx: AsyncApp,
) -> BoxFuture<Result<bool>> {
if left.head_sha != right.head_sha {
return future::ready(Ok(false)).boxed();
@@ -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<Self> {
- 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}"))?;
-
- 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
+impl FromStr for GitStatus {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self> {
+ let mut entries = s
.split('\0')
.filter_map(|entry| {
let sep = entry.get(2..3)?;
@@ -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<dyn Platform> {
Rc::new(MacPlatform::new(headless))
@@ -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<Result<CommitDetails>> {
- 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<Result<()>> {
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<String>,
) -> oneshot::Receiver<Result<Vec<Remote>>> {
- 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<Result<String>> {
- 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<Result<()>> {
- 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<Result<()>> {
- 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<Result<Vec<SharedString>>> {
- 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<Result<GitRepositoryCheckpoint>> {
- 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<Result<()>> {
- 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<Result<bool>> {
- 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<Result<()>> {
- 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")),
}
@@ -1476,7 +1476,7 @@ impl Project {
) -> Entity<Project> {
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();
@@ -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));
@@ -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);
@@ -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::<PathProgress>(&());
@@ -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;
@@ -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(),
)
@@ -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(),