@@ -4,15 +4,15 @@ use crate::{blame::Blame, status::GitStatus};
use anyhow::{anyhow, Context, Result};
use askpass::{AskPassResult, AskPassSession};
use collections::{HashMap, HashSet};
-use futures::{select_biased, FutureExt as _};
+use futures::future::BoxFuture;
+use futures::{select_biased, AsyncWriteExt, FutureExt as _};
use git2::BranchType;
-use gpui::SharedString;
+use gpui::{AppContext, AsyncApp, SharedString};
use parking_lot::Mutex;
use rope::Rope;
use schemars::JsonSchema;
use serde::Deserialize;
use std::borrow::Borrow;
-use std::io::Write as _;
use std::process::Stdio;
use std::sync::LazyLock;
use std::{
@@ -21,7 +21,7 @@ use std::{
sync::Arc,
};
use sum_tree::MapSeekTarget;
-use util::command::{new_smol_command, new_std_command};
+use util::command::new_smol_command;
use util::ResultExt;
pub const REMOTE_CANCELLED_BY_USER: &str = "Operation cancelled by user";
@@ -156,19 +156,20 @@ 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) -> Option<String>;
+ fn load_index_text(&self, path: RepoPath, cx: AsyncApp) -> 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) -> Option<String>;
+ fn load_committed_text(&self, path: RepoPath, cx: AsyncApp) -> BoxFuture<Option<String>>;
fn set_index_text(
&self,
- path: &RepoPath,
+ path: RepoPath,
content: Option<String>,
- env: &HashMap<String, String>,
- ) -> anyhow::Result<()>;
+ env: HashMap<String, String>,
+ cx: AsyncApp,
+ ) -> BoxFuture<anyhow::Result<()>>;
/// Returns the URL of the remote with the given name.
fn remote_url(&self, name: &str) -> Option<String>;
@@ -178,25 +179,36 @@ pub trait GitRepository: Send + Sync {
fn merge_head_shas(&self) -> Vec<String>;
- /// Returns the list of git statuses, sorted by path
+ // Note: this method blocks the current thread!
fn status(&self, path_prefixes: &[RepoPath]) -> Result<GitStatus>;
- fn branches(&self) -> Result<Vec<Branch>>;
- fn change_branch(&self, _: &str) -> Result<()>;
- fn create_branch(&self, _: &str) -> Result<()>;
- fn branch_exits(&self, _: &str) -> Result<bool>;
+ fn branches(&self) -> BoxFuture<Result<Vec<Branch>>>;
+
+ fn change_branch(&self, _: String, _: AsyncApp) -> BoxFuture<Result<()>>;
+ fn create_branch(&self, _: String, _: AsyncApp) -> BoxFuture<Result<()>>;
+
+ fn reset(
+ &self,
+ commit: String,
+ mode: ResetMode,
+ env: HashMap<String, String>,
+ ) -> BoxFuture<Result<()>>;
- fn reset(&self, commit: &str, mode: ResetMode, env: &HashMap<String, String>) -> Result<()>;
fn checkout_files(
&self,
- commit: &str,
- paths: &[RepoPath],
- env: &HashMap<String, String>,
- ) -> Result<()>;
+ commit: String,
+ paths: Vec<RepoPath>,
+ env: HashMap<String, String>,
+ ) -> BoxFuture<Result<()>>;
- fn show(&self, commit: &str) -> Result<CommitDetails>;
+ fn show(&self, commit: String, cx: AsyncApp) -> BoxFuture<Result<CommitDetails>>;
- fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame>;
+ fn blame(
+ &self,
+ path: RepoPath,
+ content: Rope,
+ cx: AsyncApp,
+ ) -> 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>`).
@@ -213,48 +225,67 @@ pub trait GitRepository: Send + Sync {
/// Updates the index to match the worktree at the given paths.
///
/// If any of the paths have been deleted from the worktree, they will be removed from the index if found there.
- fn stage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()>;
+ fn stage_paths(
+ &self,
+ paths: Vec<RepoPath>,
+ env: HashMap<String, String>,
+ cx: AsyncApp,
+ ) -> BoxFuture<Result<()>>;
/// Updates the index to match HEAD at the given paths.
///
/// If any of the paths were previously staged but do not exist in HEAD, they will be removed from the index.
- fn unstage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()>;
+ fn unstage_paths(
+ &self,
+ paths: Vec<RepoPath>,
+ env: HashMap<String, String>,
+ cx: AsyncApp,
+ ) -> BoxFuture<Result<()>>;
fn commit(
&self,
- message: &str,
- name_and_email: Option<(&str, &str)>,
- env: &HashMap<String, String>,
- ) -> Result<()>;
+ message: SharedString,
+ name_and_email: Option<(SharedString, SharedString)>,
+ env: HashMap<String, String>,
+ cx: AsyncApp,
+ ) -> BoxFuture<Result<()>>;
fn push(
&self,
- branch_name: &str,
- upstream_name: &str,
+ branch_name: String,
+ upstream_name: String,
options: Option<PushOptions>,
askpass: AskPassSession,
- env: &HashMap<String, String>,
- ) -> Result<RemoteCommandOutput>;
+ env: HashMap<String, String>,
+ cx: AsyncApp,
+ ) -> BoxFuture<Result<RemoteCommandOutput>>;
fn pull(
&self,
- branch_name: &str,
- upstream_name: &str,
+ branch_name: String,
+ upstream_name: String,
askpass: AskPassSession,
- env: &HashMap<String, String>,
- ) -> Result<RemoteCommandOutput>;
+ env: HashMap<String, String>,
+ cx: AsyncApp,
+ ) -> BoxFuture<Result<RemoteCommandOutput>>;
+
fn fetch(
&self,
askpass: AskPassSession,
- env: &HashMap<String, String>,
- ) -> Result<RemoteCommandOutput>;
+ env: HashMap<String, String>,
+ cx: AsyncApp,
+ ) -> BoxFuture<Result<RemoteCommandOutput>>;
- fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>>;
+ fn get_remotes(
+ &self,
+ branch_name: Option<String>,
+ cx: AsyncApp,
+ ) -> BoxFuture<Result<Vec<Remote>>>;
/// returns a list of remote branches that contain HEAD
- fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>>;
+ fn check_for_pushed_commit(&self, cx: AsyncApp) -> BoxFuture<Result<Vec<SharedString>>>;
/// Run git diff
- fn diff(&self, diff: DiffType) -> Result<String>;
+ fn diff(&self, diff: DiffType, cx: AsyncApp) -> BoxFuture<Result<String>>;
}
pub enum DiffType {
@@ -275,14 +306,14 @@ impl std::fmt::Debug for dyn GitRepository {
}
pub struct RealGitRepository {
- pub repository: Mutex<git2::Repository>,
+ pub repository: Arc<Mutex<git2::Repository>>,
pub git_binary_path: PathBuf,
}
impl RealGitRepository {
pub fn new(repository: git2::Repository, git_binary_path: Option<PathBuf>) -> Self {
Self {
- repository: Mutex::new(repository),
+ repository: Arc::new(Mutex::new(repository)),
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
}
}
@@ -316,162 +347,200 @@ impl GitRepository for RealGitRepository {
repo.commondir().into()
}
- fn show(&self, commit: &str) -> Result<CommitDetails> {
- let repo = self.repository.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)
- }
-
- fn reset(&self, commit: &str, mode: ResetMode, env: &HashMap<String, String>) -> Result<()> {
- let working_directory = self.working_directory()?;
-
- let mode_flag = match mode {
- ResetMode::Mixed => "--mixed",
- ResetMode::Soft => "--soft",
- };
-
- let output = new_std_command(&self.git_binary_path)
- .envs(env)
- .current_dir(&working_directory)
- .args(["reset", mode_flag, commit])
- .output()?;
- if !output.status.success() {
- return Err(anyhow!(
- "Failed to reset:\n{}",
- String::from_utf8_lossy(&output.stderr)
- ));
+ fn show(&self, commit: String, cx: AsyncApp) -> 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()
+ }
+
+ fn reset(
+ &self,
+ commit: String,
+ mode: ResetMode,
+ env: HashMap<String, String>,
+ ) -> BoxFuture<Result<()>> {
+ async move {
+ let working_directory = self.working_directory();
+
+ let mode_flag = match mode {
+ ResetMode::Mixed => "--mixed",
+ ResetMode::Soft => "--soft",
+ };
+
+ let output = new_smol_command(&self.git_binary_path)
+ .envs(env)
+ .current_dir(&working_directory?)
+ .args(["reset", mode_flag, &commit])
+ .output()
+ .await?;
+ if !output.status.success() {
+ return Err(anyhow!(
+ "Failed to reset:\n{}",
+ String::from_utf8_lossy(&output.stderr)
+ ));
+ }
+ Ok(())
}
- Ok(())
+ .boxed()
}
fn checkout_files(
&self,
- commit: &str,
- paths: &[RepoPath],
- env: &HashMap<String, String>,
- ) -> Result<()> {
- if paths.is_empty() {
- return Ok(());
- }
- let working_directory = self.working_directory()?;
-
- let output = new_std_command(&self.git_binary_path)
- .current_dir(&working_directory)
- .envs(env)
- .args(["checkout", commit, "--"])
- .args(paths.iter().map(|path| path.as_ref()))
- .output()?;
- if !output.status.success() {
- return Err(anyhow!(
- "Failed to checkout files:\n{}",
- String::from_utf8_lossy(&output.stderr)
- ));
+ commit: String,
+ paths: Vec<RepoPath>,
+ env: HashMap<String, String>,
+ ) -> BoxFuture<Result<()>> {
+ let working_directory = self.working_directory();
+ let git_binary_path = self.git_binary_path.clone();
+ async move {
+ if paths.is_empty() {
+ return Ok(());
+ }
+
+ let output = new_smol_command(&git_binary_path)
+ .current_dir(&working_directory?)
+ .envs(env)
+ .args(["checkout", &commit, "--"])
+ .args(paths.iter().map(|path| path.as_ref()))
+ .output()
+ .await?;
+ if !output.status.success() {
+ return Err(anyhow!(
+ "Failed to checkout files:\n{}",
+ String::from_utf8_lossy(&output.stderr)
+ ));
+ }
+ Ok(())
}
- Ok(())
+ .boxed()
}
- fn load_index_text(&self, path: &RepoPath) -> Option<String> {
- fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
- const STAGE_NORMAL: i32 = 0;
- let index = repo.index()?;
-
- // This check is required because index.get_path() unwraps internally :(
- check_path_to_repo_path_errors(path)?;
+ fn load_index_text(&self, path: RepoPath, cx: AsyncApp) -> BoxFuture<Option<String>> {
+ let repo = self.repository.clone();
+ cx.background_spawn(async move {
+ fn logic(repo: &git2::Repository, path: &RepoPath) -> Result<Option<String>> {
+ const STAGE_NORMAL: i32 = 0;
+ let index = repo.index()?;
- let oid = match index.get_path(path, STAGE_NORMAL) {
- Some(entry) if entry.mode != GIT_MODE_SYMLINK => entry.id,
- _ => return Ok(None),
- };
+ // This check is required because index.get_path() unwraps internally :(
+ check_path_to_repo_path_errors(path)?;
- let content = repo.find_blob(oid)?.content().to_owned();
- Ok(Some(String::from_utf8(content)?))
- }
+ let oid = match index.get_path(path, STAGE_NORMAL) {
+ Some(entry) if entry.mode != GIT_MODE_SYMLINK => entry.id,
+ _ => return Ok(None),
+ };
- match logic(&self.repository.lock(), path) {
- Ok(value) => return value,
- Err(err) => log::error!("Error loading index text: {:?}", err),
- }
- 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) -> Option<String> {
- let repo = self.repository.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)
+ fn load_committed_text(&self, path: RepoPath, cx: AsyncApp) -> 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()
}
fn set_index_text(
&self,
- path: &RepoPath,
+ path: RepoPath,
content: Option<String>,
- env: &HashMap<String, String>,
- ) -> anyhow::Result<()> {
- let working_directory = self.working_directory()?;
- if let Some(content) = content {
- let mut child = new_std_command(&self.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())?;
- let output = child.wait_with_output()?.stdout;
- let sha = String::from_utf8(output)?;
-
- log::debug!("indexing SHA: {sha}, path {path:?}");
+ 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.as_ref())
+ .output()
+ .await?;
- let output = new_std_command(&self.git_binary_path)
- .current_dir(&working_directory)
- .envs(env)
- .args(["update-index", "--add", "--cacheinfo", "100644", &sha])
- .arg(path.as_ref())
- .output()?;
-
- if !output.status.success() {
- return Err(anyhow!(
- "Failed to stage:\n{}",
- String::from_utf8_lossy(&output.stderr)
- ));
- }
- } else {
- let output = new_std_command(&self.git_binary_path)
- .current_dir(&working_directory)
- .envs(env)
- .args(["update-index", "--force-remove"])
- .arg(path.as_ref())
- .output()?;
+ 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.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(())
+ Ok(())
+ })
+ .boxed()
}
fn remote_url(&self, name: &str) -> Option<String> {
@@ -515,415 +584,477 @@ impl GitRepository for RealGitRepository {
GitStatus::new(&self.git_binary_path, &working_directory, path_prefixes)
}
- fn branch_exits(&self, name: &str) -> Result<bool> {
- let repo = self.repository.lock();
- let branch = repo.find_branch(name, BranchType::Local);
- match branch {
- Ok(_) => Ok(true),
- Err(e) => match e.code() {
- git2::ErrorCode::NotFound => Ok(false),
- _ => Err(anyhow!(e)),
- },
- }
- }
-
- fn branches(&self) -> Result<Vec<Branch>> {
- let working_directory = self
- .repository
- .lock()
- .workdir()
- .context("failed to read git work directory")?
- .to_path_buf();
- let fields = [
- "%(HEAD)",
- "%(objectname)",
- "%(parent)",
- "%(refname)",
- "%(upstream)",
- "%(upstream:track)",
- "%(committerdate:unix)",
- "%(contents:subject)",
- ]
- .join("%00");
- let args = vec!["for-each-ref", "refs/heads/**/*", "--format", &fields];
-
- let output = new_std_command(&self.git_binary_path)
- .current_dir(&working_directory)
- .args(args)
- .output()?;
-
- if !output.status.success() {
- return Err(anyhow!(
- "Failed to git git branches:\n{}",
- String::from_utf8_lossy(&output.stderr)
- ));
- }
-
- let input = String::from_utf8_lossy(&output.stdout);
-
- let mut branches = parse_branch_input(&input)?;
- if branches.is_empty() {
- let args = vec!["symbolic-ref", "--quiet", "--short", "HEAD"];
-
- let output = new_std_command(&self.git_binary_path)
+ fn branches(&self) -> BoxFuture<Result<Vec<Branch>>> {
+ let working_directory = self.working_directory();
+ let git_binary_path = self.git_binary_path.clone();
+ async move {
+ let fields = [
+ "%(HEAD)",
+ "%(objectname)",
+ "%(parent)",
+ "%(refname)",
+ "%(upstream)",
+ "%(upstream:track)",
+ "%(committerdate:unix)",
+ "%(contents:subject)",
+ ]
+ .join("%00");
+ let args = vec!["for-each-ref", "refs/heads/**/*", "--format", &fields];
+ let working_directory = working_directory?;
+ let output = new_smol_command(&git_binary_path)
.current_dir(&working_directory)
.args(args)
- .output()?;
+ .output()
+ .await?;
- // git symbolic-ref returns a non-0 exit code if HEAD points
- // to something other than a branch
- if output.status.success() {
- let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
-
- branches.push(Branch {
- name: name.into(),
- is_head: true,
- upstream: None,
- most_recent_commit: None,
- });
+ if !output.status.success() {
+ return Err(anyhow!(
+ "Failed to git git branches:\n{}",
+ String::from_utf8_lossy(&output.stderr)
+ ));
}
- }
- Ok(branches)
+ let input = String::from_utf8_lossy(&output.stdout);
+
+ let mut branches = parse_branch_input(&input)?;
+ if branches.is_empty() {
+ let args = vec!["symbolic-ref", "--quiet", "--short", "HEAD"];
+
+ let output = new_smol_command(&git_binary_path)
+ .current_dir(&working_directory)
+ .args(args)
+ .output()
+ .await?;
+
+ // git symbolic-ref returns a non-0 exit code if HEAD points
+ // to something other than a branch
+ if output.status.success() {
+ let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
+
+ branches.push(Branch {
+ name: name.into(),
+ is_head: true,
+ upstream: None,
+ most_recent_commit: None,
+ });
+ }
+ }
+
+ Ok(branches)
+ }
+ .boxed()
+ }
+
+ fn change_branch(&self, name: String, cx: AsyncApp) -> 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()
}
- fn change_branch(&self, name: &str) -> Result<()> {
- let repo = self.repository.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(())
- }
-
- fn create_branch(&self, name: &str) -> Result<()> {
- let repo = self.repository.lock();
- let current_commit = repo.head()?.peel_to_commit()?;
- repo.branch(name, ¤t_commit, false)?;
- Ok(())
+ fn create_branch(&self, name: String, cx: AsyncApp) -> 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()
}
- fn blame(&self, path: &Path, content: Rope) -> Result<crate::blame::Blame> {
- let working_directory = self
- .repository
- .lock()
- .workdir()
- .with_context(|| format!("failed to get git working directory for file {:?}", path))?
- .to_path_buf();
+ fn blame(
+ &self,
+ path: RepoPath,
+ content: Rope,
+ cx: AsyncApp,
+ ) -> 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);
- crate::blame::Blame::for_path(
- &self.git_binary_path,
- &working_directory,
- path,
- &content,
- remote_url,
- )
- }
-
- fn diff(&self, diff: DiffType) -> Result<String> {
- let working_directory = self.working_directory()?;
- let args = match diff {
- DiffType::HeadToIndex => Some("--staged"),
- DiffType::HeadToWorktree => None,
- };
-
- let output = new_std_command(&self.git_binary_path)
- .current_dir(&working_directory)
- .args(["diff"])
- .args(args)
- .output()?;
-
- 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())
+ cx.background_spawn(async move {
+ crate::blame::Blame::for_path(
+ &git_binary_path,
+ &working_directory?,
+ &path,
+ &content,
+ remote_url,
+ )
+ .await
+ })
+ .boxed()
}
- fn stage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()> {
- let working_directory = self.working_directory()?;
+ fn diff(&self, diff: DiffType, cx: AsyncApp) -> 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,
+ };
- if !paths.is_empty() {
- let output = new_std_command(&self.git_binary_path)
- .current_dir(&working_directory)
- .envs(env)
- .args(["update-index", "--add", "--remove", "--"])
- .args(paths.iter().map(|p| p.as_ref()))
- .output()?;
+ 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 stage paths:\n{}",
+ "Failed to run git diff:\n{}",
String::from_utf8_lossy(&output.stderr)
));
}
- }
- Ok(())
+ Ok(String::from_utf8_lossy(&output.stdout).to_string())
+ })
+ .boxed()
}
- fn unstage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()> {
- let working_directory = self.working_directory()?;
+ 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.as_ref()))
+ .output()
+ .await?;
- if !paths.is_empty() {
- let output = new_std_command(&self.git_binary_path)
- .current_dir(&working_directory)
- .envs(env)
- .args(["reset", "--quiet", "--"])
- .args(paths.iter().map(|p| p.as_ref()))
- .output()?;
+ if !output.status.success() {
+ return Err(anyhow!(
+ "Failed to stage paths:\n{}",
+ String::from_utf8_lossy(&output.stderr)
+ ));
+ }
+ }
+ Ok(())
+ })
+ .boxed()
+ }
- if !output.status.success() {
- return Err(anyhow!(
- "Failed to unstage:\n{}",
- String::from_utf8_lossy(&output.stderr)
- ));
+ 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)
+ ));
+ }
}
- }
- Ok(())
+ Ok(())
+ })
+ .boxed()
}
fn commit(
&self,
- message: &str,
- name_and_email: Option<(&str, &str)>,
- env: &HashMap<String, String>,
- ) -> Result<()> {
- let working_directory = self.working_directory()?;
-
- let mut cmd = new_std_command(&self.git_binary_path);
- cmd.current_dir(&working_directory)
- .envs(env)
- .args(["commit", "--quiet", "-m"])
- .arg(message)
- .arg("--cleanup=strip");
-
- if let Some((name, email)) = name_and_email {
- cmd.arg("--author").arg(&format!("{name} <{email}>"));
- }
+ message: SharedString,
+ name_and_email: Option<(SharedString, SharedString)>,
+ 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 {
+ let mut cmd = new_smol_command(&git_binary_path);
+ cmd.current_dir(&working_directory?)
+ .envs(env)
+ .args(["commit", "--quiet", "-m"])
+ .arg(&message.to_string())
+ .arg("--cleanup=strip");
- let output = cmd.output()?;
+ if let Some((name, email)) = name_and_email {
+ cmd.arg("--author").arg(&format!("{name} <{email}>"));
+ }
- if !output.status.success() {
- return Err(anyhow!(
- "Failed to commit:\n{}",
- String::from_utf8_lossy(&output.stderr)
- ));
- }
- Ok(())
+ let output = cmd.output().await?;
+
+ if !output.status.success() {
+ return Err(anyhow!(
+ "Failed to commit:\n{}",
+ String::from_utf8_lossy(&output.stderr)
+ ));
+ }
+ Ok(())
+ })
+ .boxed()
}
fn push(
&self,
- branch_name: &str,
- remote_name: &str,
+ branch_name: String,
+ remote_name: String,
options: Option<PushOptions>,
ask_pass: AskPassSession,
- env: &HashMap<String, String>,
- ) -> Result<RemoteCommandOutput> {
- let working_directory = self.working_directory()?;
-
- let mut command = new_smol_command("git");
- command
- .envs(env)
- .env("GIT_ASKPASS", ask_pass.script_path())
- .env("SSH_ASKPASS", ask_pass.script_path())
- .env("SSH_ASKPASS_REQUIRE", "force")
- .current_dir(&working_directory)
- .args(["push"])
- .args(options.map(|option| match option {
- PushOptions::SetUpstream => "--set-upstream",
- PushOptions::Force => "--force-with-lease",
- }))
- .arg(remote_name)
- .arg(format!("{}:{}", branch_name, branch_name))
- .stdout(smol::process::Stdio::piped())
- .stderr(smol::process::Stdio::piped());
- let git_process = command.spawn()?;
-
- run_remote_command(ask_pass, git_process)
+ 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();
+ async move {
+ let working_directory = working_directory?;
+
+ let mut command = new_smol_command("git");
+ command
+ .envs(env)
+ .env("GIT_ASKPASS", ask_pass.script_path())
+ .env("SSH_ASKPASS", ask_pass.script_path())
+ .env("SSH_ASKPASS_REQUIRE", "force")
+ .env("GIT_HTTP_USER_AGENT", "Zed")
+ .current_dir(&working_directory)
+ .args(["push"])
+ .args(options.map(|option| match option {
+ PushOptions::SetUpstream => "--set-upstream",
+ PushOptions::Force => "--force-with-lease",
+ }))
+ .arg(remote_name)
+ .arg(format!("{}:{}", branch_name, branch_name))
+ .stdin(smol::process::Stdio::null())
+ .stdout(smol::process::Stdio::piped())
+ .stderr(smol::process::Stdio::piped());
+ let git_process = command.spawn()?;
+
+ run_remote_command(ask_pass, git_process).await
+ }
+ .boxed()
}
fn pull(
&self,
- branch_name: &str,
- remote_name: &str,
+ branch_name: String,
+ remote_name: String,
ask_pass: AskPassSession,
- env: &HashMap<String, String>,
- ) -> Result<RemoteCommandOutput> {
- let working_directory = self.working_directory()?;
-
- let mut command = new_smol_command("git");
- command
- .envs(env)
- .env("GIT_ASKPASS", ask_pass.script_path())
- .env("SSH_ASKPASS", ask_pass.script_path())
- .env("SSH_ASKPASS_REQUIRE", "force")
- .current_dir(&working_directory)
- .args(["pull"])
- .arg(remote_name)
- .arg(branch_name)
- .stdout(smol::process::Stdio::piped())
- .stderr(smol::process::Stdio::piped());
- let git_process = command.spawn()?;
-
- run_remote_command(ask_pass, git_process)
+ env: HashMap<String, String>,
+ _cx: AsyncApp,
+ ) -> BoxFuture<Result<RemoteCommandOutput>> {
+ let working_directory = self.working_directory();
+ async {
+ let mut command = new_smol_command("git");
+ command
+ .envs(env)
+ .env("GIT_ASKPASS", ask_pass.script_path())
+ .env("SSH_ASKPASS", ask_pass.script_path())
+ .env("SSH_ASKPASS_REQUIRE", "force")
+ .current_dir(&working_directory?)
+ .args(["pull"])
+ .arg(remote_name)
+ .arg(branch_name)
+ .stdout(smol::process::Stdio::piped())
+ .stderr(smol::process::Stdio::piped());
+ let git_process = command.spawn()?;
+
+ run_remote_command(ask_pass, git_process).await
+ }
+ .boxed()
}
fn fetch(
&self,
ask_pass: AskPassSession,
- env: &HashMap<String, String>,
- ) -> Result<RemoteCommandOutput> {
- let working_directory = self.working_directory()?;
-
- let mut command = new_smol_command("git");
- command
- .envs(env)
- .env("GIT_ASKPASS", ask_pass.script_path())
- .env("SSH_ASKPASS", ask_pass.script_path())
- .env("SSH_ASKPASS_REQUIRE", "force")
- .current_dir(&working_directory)
- .args(["fetch", "--all"])
- .stdout(smol::process::Stdio::piped())
- .stderr(smol::process::Stdio::piped());
- let git_process = command.spawn()?;
-
- run_remote_command(ask_pass, git_process)
- }
-
- fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>> {
- let working_directory = self.working_directory()?;
-
- if let Some(branch_name) = branch_name {
- let output = new_std_command(&self.git_binary_path)
- .current_dir(&working_directory)
- .args(["config", "--get"])
- .arg(format!("branch.{}.remote", branch_name))
- .output()?;
-
- 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_std_command(&self.git_binary_path)
- .current_dir(&working_directory)
- .args(["remote"])
- .output()?;
-
- 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)
- ));
+ env: HashMap<String, String>,
+ _cx: AsyncApp,
+ ) -> BoxFuture<Result<RemoteCommandOutput>> {
+ let working_directory = self.working_directory();
+ async {
+ let mut command = new_smol_command("git");
+ command
+ .envs(env)
+ .env("GIT_ASKPASS", ask_pass.script_path())
+ .env("SSH_ASKPASS", ask_pass.script_path())
+ .env("SSH_ASKPASS_REQUIRE", "force")
+ .current_dir(&working_directory?)
+ .args(["fetch", "--all"])
+ .stdout(smol::process::Stdio::piped())
+ .stderr(smol::process::Stdio::piped());
+ let git_process = command.spawn()?;
+
+ run_remote_command(ask_pass, git_process).await
}
+ .boxed()
}
- fn check_for_pushed_commit(&self) -> Result<Vec<SharedString>> {
- let working_directory = self.working_directory()?;
- let git_cmd = |args: &[&str]| -> Result<String> {
- let output = new_std_command(&self.git_binary_path)
+ fn get_remotes(
+ &self,
+ branch_name: Option<String>,
+ cx: AsyncApp,
+ ) -> 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 {
+ 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(args)
- .output()?;
+ .args(["remote"])
+ .output()
+ .await?;
+
if output.status.success() {
- Ok(String::from_utf8(output.stdout)?)
+ 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 {
- Err(anyhow!(String::from_utf8_lossy(&output.stderr).to_string()))
+ return Err(anyhow!(
+ "Failed to get remotes:\n{}",
+ String::from_utf8_lossy(&output.stderr)
+ ));
}
- };
-
- let head = git_cmd(&["rev-parse", "HEAD"])
- .context("Failed to get HEAD")?
- .trim()
- .to_owned();
-
- let mut remote_branches = vec![];
- let mut add_if_matching = |remote_head: &str| {
- if let Ok(merge_base) = git_cmd(&["merge-base", &head, remote_head]) {
- if merge_base.trim() == head {
- if let Some(s) = remote_head.strip_prefix("refs/remotes/") {
- remote_branches.push(s.to_owned().into());
+ })
+ .boxed()
+ }
+
+ fn check_for_pushed_commit(&self, cx: AsyncApp) -> 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()))
+ }
+ };
+
+ 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"]).context("Failed to get remotes")?;
- for remote in remotes.lines() {
- if let Ok(remote_head) =
- git_cmd(&["symbolic-ref", &format!("refs/remotes/{remote}/HEAD")])
- {
- add_if_matching(remote_head.trim());
- }
- }
- // ... and the remote branch that the checked-out one is tracking
- if let Ok(remote_head) = git_cmd(&["rev-parse", "--symbolic-full-name", "@{u}"]) {
- add_if_matching(remote_head.trim());
- }
+ // ... 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)
+ Ok(remote_branches)
+ })
+ .boxed()
}
}
-fn run_remote_command(
+async fn run_remote_command(
mut ask_pass: AskPassSession,
git_process: smol::process::Child,
) -> std::result::Result<RemoteCommandOutput, anyhow::Error> {
- smol::block_on(async {
- select_biased! {
- result = ask_pass.run().fuse() => {
- match result {
- AskPassResult::CancelledByUser => {
- Err(anyhow!(REMOTE_CANCELLED_BY_USER))?
- }
- AskPassResult::Timedout => {
- Err(anyhow!("Connecting to host timed out"))?
- }
+ select_biased! {
+ result = ask_pass.run().fuse() => {
+ match result {
+ AskPassResult::CancelledByUser => {
+ Err(anyhow!(REMOTE_CANCELLED_BY_USER))?
}
- }
- output = git_process.output().fuse() => {
- let output = output?;
- if !output.status.success() {
- Err(anyhow!(
- "{}",
- String::from_utf8_lossy(&output.stderr)
- ))
- } else {
- Ok(RemoteCommandOutput {
- stdout: String::from_utf8_lossy(&output.stdout).to_string(),
- stderr: String::from_utf8_lossy(&output.stderr).to_string(),
- })
+ AskPassResult::Timedout => {
+ Err(anyhow!("Connecting to host timed out"))?
}
}
}
- })
+ output = git_process.output().fuse() => {
+ let output = output?;
+ if !output.status.success() {
+ Err(anyhow!(
+ "{}",
+ String::from_utf8_lossy(&output.stderr)
+ ))
+ } else {
+ Ok(RemoteCommandOutput {
+ stdout: String::from_utf8_lossy(&output.stdout).to_string(),
+ stderr: String::from_utf8_lossy(&output.stderr).to_string(),
+ })
+ }
+ }
+ }
}
#[derive(Debug, Clone)]
@@ -401,7 +401,7 @@ impl GitStore {
if let Some((repo, path)) = this.repository_and_path_for_buffer_id(buffer_id, cx) {
let recv = repo.update(cx, |repo, cx| {
repo.set_index_text(
- &path,
+ path,
new_index_text.as_ref().map(|rope| rope.to_string()),
cx,
)
@@ -715,7 +715,7 @@ impl GitStore {
repository_handle
.update(&mut cx, |repository_handle, cx| {
repository_handle.set_index_text(
- &RepoPath::from_str(&envelope.payload.path),
+ RepoPath::from_str(&envelope.payload.path),
envelope.payload.text,
cx,
)
@@ -808,7 +808,7 @@ impl GitStore {
repository_handle
.update(&mut cx, |repository_handle, _| {
- repository_handle.create_branch(&branch_name)
+ repository_handle.create_branch(branch_name)
})?
.await??;
@@ -828,7 +828,7 @@ impl GitStore {
repository_handle
.update(&mut cx, |repository_handle, _| {
- repository_handle.change_branch(&branch_name)
+ repository_handle.change_branch(branch_name)
})?
.await??;
@@ -847,7 +847,7 @@ impl GitStore {
let commit = repository_handle
.update(&mut cx, |repository_handle, _| {
- repository_handle.show(&envelope.payload.commit)
+ repository_handle.show(envelope.payload.commit)
})?
.await??;
Ok(proto::GitCommitDetails {
@@ -876,7 +876,7 @@ impl GitStore {
repository_handle
.update(&mut cx, |repository_handle, cx| {
- repository_handle.reset(&envelope.payload.commit, mode, cx)
+ repository_handle.reset(envelope.payload.commit, mode, cx)
})?
.await??;
Ok(proto::Ack {})
@@ -1081,8 +1081,8 @@ impl Repository {
fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
where
- F: FnOnce(GitRepo) -> Fut + 'static,
- Fut: Future<Output = R> + Send + 'static,
+ F: FnOnce(GitRepo, AsyncApp) -> Fut + 'static,
+ Fut: Future<Output = R> + 'static,
R: Send + 'static,
{
self.send_keyed_job(None, job)
@@ -1090,8 +1090,8 @@ impl Repository {
fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
where
- F: FnOnce(GitRepo) -> Fut + 'static,
- Fut: Future<Output = R> + Send + 'static,
+ F: FnOnce(GitRepo, AsyncApp) -> Fut + 'static,
+ Fut: Future<Output = R> + 'static,
R: Send + 'static,
{
let (result_tx, result_rx) = futures::channel::oneshot::channel();
@@ -1100,8 +1100,8 @@ impl Repository {
.unbounded_send(GitJob {
key,
job: Box::new(|cx: &mut AsyncApp| {
- let job = job(git_repo);
- cx.background_spawn(async move {
+ let job = job(git_repo, cx.clone());
+ cx.spawn(|_| async move {
let result = job.await;
result_tx.send(result).ok();
})
@@ -1292,9 +1292,9 @@ impl Repository {
let commit = commit.to_string();
let env = self.worktree_environment(cx);
- self.send_job(|git_repo| async move {
+ self.send_job(|git_repo, _| async move {
match git_repo {
- GitRepo::Local(repo) => repo.checkout_files(&commit, &paths, &env.await),
+ GitRepo::Local(repo) => repo.checkout_files(commit, paths, env.await).await,
GitRepo::Remote {
project_id,
client,
@@ -1322,17 +1322,17 @@ impl Repository {
pub fn reset(
&self,
- commit: &str,
+ commit: String,
reset_mode: ResetMode,
cx: &mut App,
) -> oneshot::Receiver<Result<()>> {
let commit = commit.to_string();
let env = self.worktree_environment(cx);
- self.send_job(|git_repo| async move {
+ self.send_job(|git_repo, _| async move {
match git_repo {
GitRepo::Local(git_repo) => {
let env = env.await;
- git_repo.reset(&commit, reset_mode, &env)
+ git_repo.reset(commit, reset_mode, env).await
}
GitRepo::Remote {
project_id,
@@ -1359,11 +1359,10 @@ impl Repository {
})
}
- pub fn show(&self, commit: &str) -> oneshot::Receiver<Result<CommitDetails>> {
- let commit = commit.to_string();
- self.send_job(|git_repo| async move {
+ pub fn show(&self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
+ self.send_job(|git_repo, cx| async move {
match git_repo {
- GitRepo::Local(git_repository) => git_repository.show(&commit),
+ GitRepo::Local(git_repository) => git_repository.show(commit, cx).await,
GitRepo::Remote {
project_id,
client,
@@ -1433,9 +1432,9 @@ impl Repository {
let env = env.await;
this.update(&mut cx, |this, _| {
- this.send_job(|git_repo| async move {
+ this.send_job(|git_repo, cx| async move {
match git_repo {
- GitRepo::Local(repo) => repo.stage_paths(&entries, &env),
+ GitRepo::Local(repo) => repo.stage_paths(entries, env, cx).await,
GitRepo::Remote {
project_id,
client,
@@ -1504,9 +1503,9 @@ impl Repository {
let env = env.await;
this.update(&mut cx, |this, _| {
- this.send_job(|git_repo| async move {
+ this.send_job(|git_repo, cx| async move {
match git_repo {
- GitRepo::Local(repo) => repo.unstage_paths(&entries, &env),
+ GitRepo::Local(repo) => repo.unstage_paths(entries, env, cx).await,
GitRepo::Remote {
project_id,
client,
@@ -1587,17 +1586,11 @@ impl Repository {
cx: &mut App,
) -> oneshot::Receiver<Result<()>> {
let env = self.worktree_environment(cx);
- self.send_job(|git_repo| async move {
+ self.send_job(|git_repo, cx| async move {
match git_repo {
GitRepo::Local(repo) => {
let env = env.await;
- repo.commit(
- message.as_ref(),
- name_and_email
- .as_ref()
- .map(|(name, email)| (name.as_ref(), email.as_ref())),
- &env,
- )
+ repo.commit(message, name_and_email, env, cx).await
}
GitRepo::Remote {
project_id,
@@ -1634,12 +1627,12 @@ impl Repository {
let askpass_id = util::post_inc(&mut self.latest_askpass_id);
let env = self.worktree_environment(cx);
- self.send_job(move |git_repo| async move {
+ self.send_job(move |git_repo, cx| async move {
match git_repo {
GitRepo::Local(git_repository) => {
let askpass = AskPassSession::new(&executor, askpass).await?;
let env = env.await;
- git_repository.fetch(askpass, &env)
+ git_repository.fetch(askpass, env, cx).await
}
GitRepo::Remote {
project_id,
@@ -1685,12 +1678,21 @@ impl Repository {
let askpass_id = util::post_inc(&mut self.latest_askpass_id);
let env = self.worktree_environment(cx);
- self.send_job(move |git_repo| async move {
+ self.send_job(move |git_repo, cx| async move {
match git_repo {
GitRepo::Local(git_repository) => {
let env = env.await;
let askpass = AskPassSession::new(&executor, askpass).await?;
- git_repository.push(&branch, &remote, options, askpass, &env)
+ git_repository
+ .push(
+ branch.to_string(),
+ remote.to_string(),
+ options,
+ askpass,
+ env,
+ cx,
+ )
+ .await
}
GitRepo::Remote {
project_id,
@@ -1740,12 +1742,14 @@ impl Repository {
let askpass_id = util::post_inc(&mut self.latest_askpass_id);
let env = self.worktree_environment(cx);
- self.send_job(move |git_repo| async move {
+ self.send_job(move |git_repo, cx| async move {
match git_repo {
GitRepo::Local(git_repository) => {
let askpass = AskPassSession::new(&executor, askpass).await?;
let env = env.await;
- git_repository.pull(&branch, &remote, askpass, &env)
+ git_repository
+ .pull(branch.to_string(), remote.to_string(), askpass, env, cx)
+ .await
}
GitRepo::Remote {
project_id,
@@ -1781,18 +1785,17 @@ impl Repository {
fn set_index_text(
&self,
- path: &RepoPath,
+ path: RepoPath,
content: Option<String>,
cx: &mut App,
) -> oneshot::Receiver<anyhow::Result<()>> {
- let path = path.clone();
let env = self.worktree_environment(cx);
self.send_keyed_job(
Some(GitJobKey::WriteIndex(path.clone())),
- |git_repo| async move {
+ |git_repo, cx| async move {
match git_repo {
- GitRepo::Local(repo) => repo.set_index_text(&path, content, &env.await),
+ GitRepo::Local(repo) => repo.set_index_text(path, content, env.await, cx).await,
GitRepo::Remote {
project_id,
client,
@@ -1819,11 +1822,9 @@ impl Repository {
&self,
branch_name: Option<String>,
) -> oneshot::Receiver<Result<Vec<Remote>>> {
- self.send_job(|repo| async move {
+ self.send_job(|repo, cx| async move {
match repo {
- GitRepo::Local(git_repository) => {
- git_repository.get_remotes(branch_name.as_deref())
- }
+ GitRepo::Local(git_repository) => git_repository.get_remotes(branch_name, cx).await,
GitRepo::Remote {
project_id,
client,
@@ -1854,9 +1855,13 @@ impl Repository {
}
pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
- self.send_job(|repo| async move {
+ self.send_job(|repo, cx| async move {
match repo {
- GitRepo::Local(git_repository) => git_repository.branches(),
+ GitRepo::Local(git_repository) => {
+ let git_repository = git_repository.clone();
+ cx.background_spawn(async move { git_repository.branches().await })
+ .await
+ }
GitRepo::Remote {
project_id,
client,
@@ -1884,9 +1889,9 @@ impl Repository {
}
pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
- self.send_job(|repo| async move {
+ self.send_job(|repo, cx| async move {
match repo {
- GitRepo::Local(git_repository) => git_repository.diff(diff_type),
+ GitRepo::Local(git_repository) => git_repository.diff(diff_type, cx).await,
GitRepo::Remote {
project_id,
client,
@@ -1916,11 +1921,12 @@ impl Repository {
})
}
- pub fn create_branch(&self, branch_name: &str) -> oneshot::Receiver<Result<()>> {
- let branch_name = branch_name.to_owned();
- self.send_job(|repo| async move {
+ pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
+ self.send_job(|repo, cx| async move {
match repo {
- GitRepo::Local(git_repository) => git_repository.create_branch(&branch_name),
+ GitRepo::Local(git_repository) => {
+ git_repository.create_branch(branch_name, cx).await
+ }
GitRepo::Remote {
project_id,
client,
@@ -1942,11 +1948,12 @@ impl Repository {
})
}
- pub fn change_branch(&self, branch_name: &str) -> oneshot::Receiver<Result<()>> {
- let branch_name = branch_name.to_owned();
- self.send_job(|repo| async move {
+ pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
+ self.send_job(|repo, cx| async move {
match repo {
- GitRepo::Local(git_repository) => git_repository.change_branch(&branch_name),
+ GitRepo::Local(git_repository) => {
+ git_repository.change_branch(branch_name, cx).await
+ }
GitRepo::Remote {
project_id,
client,
@@ -1969,9 +1976,9 @@ impl Repository {
}
pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
- self.send_job(|repo| async move {
+ self.send_job(|repo, cx| async move {
match repo {
- GitRepo::Local(git_repository) => git_repository.check_for_pushed_commit(),
+ GitRepo::Local(git_repository) => git_repository.check_for_pushed_commit(cx).await,
GitRepo::Remote {
project_id,
client,