@@ -21,6 +21,7 @@ use text::LineEnding;
use std::collections::HashSet;
use std::ffi::{OsStr, OsString};
+use std::sync::atomic::AtomicBool;
use std::process::ExitStatus;
use std::str::FromStr;
@@ -933,6 +934,9 @@ pub trait GitRepository: Send + Sync {
) -> BoxFuture<'_, Result<()>>;
fn commit_data_reader(&self) -> Result<CommitDataReader>;
+
+ fn set_trusted(&self, trusted: bool);
+ fn is_trusted(&self) -> bool;
}
pub enum DiffType {
@@ -959,6 +963,7 @@ pub struct RealGitRepository {
pub any_git_binary_path: PathBuf,
any_git_binary_help_output: Arc<Mutex<Option<SharedString>>>,
executor: BackgroundExecutor,
+ is_trusted: Arc<AtomicBool>,
}
impl RealGitRepository {
@@ -977,6 +982,7 @@ impl RealGitRepository {
any_git_binary_path,
executor,
any_git_binary_help_output: Arc::new(Mutex::new(None)),
+ is_trusted: Arc::new(AtomicBool::new(false)),
})
}
@@ -988,20 +994,24 @@ impl RealGitRepository {
.map(Path::to_path_buf)
}
+ fn git_binary(&self) -> Result<GitBinary> {
+ Ok(GitBinary::new(
+ self.any_git_binary_path.clone(),
+ self.working_directory()
+ .with_context(|| "Can't run git commands without a working directory")?,
+ self.executor.clone(),
+ self.is_trusted(),
+ ))
+ }
+
async fn any_git_binary_help_output(&self) -> SharedString {
if let Some(output) = self.any_git_binary_help_output.lock().clone() {
return output;
}
- let git_binary_path = self.any_git_binary_path.clone();
- let executor = self.executor.clone();
- let working_directory = self.working_directory();
+ let git_binary = self.git_binary();
let output: SharedString = self
.executor
- .spawn(async move {
- GitBinary::new(git_binary_path, working_directory?, executor)
- .run(["help", "-a"])
- .await
- })
+ .spawn(async move { git_binary?.run(["help", "-a"]).await })
.await
.unwrap_or_default()
.into();
@@ -1044,6 +1054,7 @@ pub async fn get_git_committer(cx: &AsyncApp) -> GitCommitter {
git_binary_path.unwrap_or(PathBuf::from("git")),
paths::home_dir().clone(),
cx.background_executor().clone(),
+ true,
);
cx.background_spawn(async move {
@@ -1075,14 +1086,12 @@ impl GitRepository for RealGitRepository {
}
fn show(&self, commit: String) -> BoxFuture<'_, Result<CommitDetails>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = self.working_directory();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
- let output = new_command(git_binary_path)
- .current_dir(&working_directory)
- .args([
+ let git = git_binary?;
+ let output = git
+ .build_command([
"--no-optional-locks",
"show",
"--no-patch",
@@ -1113,15 +1122,14 @@ impl GitRepository for RealGitRepository {
}
fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result<CommitDiff>> {
- let Some(working_directory) = self.repository.lock().workdir().map(ToOwned::to_owned)
- else {
+ if self.repository.lock().workdir().is_none() {
return future::ready(Err(anyhow!("no working directory"))).boxed();
- };
- let git_binary_path = self.any_git_binary_path.clone();
+ }
+ let git_binary = self.git_binary();
cx.background_spawn(async move {
- let show_output = util::command::new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args([
+ let git = git_binary?;
+ let show_output = git
+ .build_command([
"--no-optional-locks",
"show",
"--format=",
@@ -1142,9 +1150,8 @@ impl GitRepository for RealGitRepository {
let changes = parse_git_diff_name_status(&show_stdout);
let parent_sha = format!("{}^", commit);
- let mut cat_file_process = util::command::new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(["--no-optional-locks", "cat-file", "--batch=%(objectsize)"])
+ let mut cat_file_process = git
+ .build_command(["--no-optional-locks", "cat-file", "--batch=%(objectsize)"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
@@ -1251,18 +1258,17 @@ impl GitRepository for RealGitRepository {
mode: ResetMode,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
+ let git_binary = self.git_binary();
async move {
- let working_directory = self.working_directory();
-
let mode_flag = match mode {
ResetMode::Mixed => "--mixed",
ResetMode::Soft => "--soft",
};
- let output = new_command(&self.any_git_binary_path)
+ let git = git_binary?;
+ let output = git
+ .build_command(["reset", mode_flag, &commit])
.envs(env.iter())
- .current_dir(&working_directory?)
- .args(["reset", mode_flag, &commit])
.output()
.await?;
anyhow::ensure!(
@@ -1281,17 +1287,16 @@ impl GitRepository for RealGitRepository {
paths: Vec<RepoPath>,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
async move {
if paths.is_empty() {
return Ok(());
}
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory?)
+ let git = git_binary?;
+ let output = git
+ .build_command(["checkout", &commit, "--"])
.envs(env.iter())
- .args(["checkout", &commit, "--"])
.args(paths.iter().map(|path| path.as_unix_str()))
.output()
.await?;
@@ -1378,18 +1383,16 @@ impl GitRepository for RealGitRepository {
env: Arc<HashMap<String, String>>,
is_executable: bool,
) -> BoxFuture<'_, anyhow::Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
+ let git = git_binary?;
let mode = if is_executable { "100755" } else { "100644" };
if let Some(content) = content {
- let mut child = new_command(&git_binary_path)
- .current_dir(&working_directory)
+ let mut child = git
+ .build_command(["hash-object", "-w", "--stdin"])
.envs(env.iter())
- .args(["hash-object", "-w", "--stdin"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
@@ -1402,10 +1405,9 @@ impl GitRepository for RealGitRepository {
log::debug!("indexing SHA: {sha}, path {path:?}");
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory)
+ let output = git
+ .build_command(["update-index", "--add", "--cacheinfo", mode, sha])
.envs(env.iter())
- .args(["update-index", "--add", "--cacheinfo", mode, sha])
.arg(path.as_unix_str())
.output()
.await?;
@@ -1417,10 +1419,9 @@ impl GitRepository for RealGitRepository {
);
} else {
log::debug!("removing path {path:?} from the index");
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory)
+ let output = git
+ .build_command(["update-index", "--force-remove"])
.envs(env.iter())
- .args(["update-index", "--force-remove"])
.arg(path.as_unix_str())
.output()
.await?;
@@ -1449,14 +1450,12 @@ impl GitRepository for RealGitRepository {
}
fn revparse_batch(&self, revs: Vec<String>) -> BoxFuture<'_, Result<Vec<Option<String>>>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
- let mut process = new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args([
+ let git = git_binary?;
+ let mut process = git
+ .build_command([
"--no-optional-locks",
"cat-file",
"--batch-check=%(objectname)",
@@ -1509,19 +1508,14 @@ impl GitRepository for RealGitRepository {
}
fn status(&self, path_prefixes: &[RepoPath]) -> Task<Result<GitStatus>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = match self.working_directory() {
- Ok(working_directory) => working_directory,
+ let git = match self.git_binary() {
+ Ok(git) => git,
Err(e) => return Task::ready(Err(e)),
};
let args = git_status_args(path_prefixes);
log::debug!("Checking for git status in {path_prefixes:?}");
self.executor.spawn(async move {
- let output = new_command(&git_binary_path)
- .current_dir(working_directory)
- .args(args)
- .output()
- .await?;
+ let output = git.build_command(args).output().await?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
stdout.parse()
@@ -1533,9 +1527,8 @@ impl GitRepository for RealGitRepository {
}
fn diff_tree(&self, request: DiffTreeType) -> BoxFuture<'_, Result<TreeDiff>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = match self.working_directory() {
- Ok(working_directory) => working_directory,
+ let git = match self.git_binary() {
+ Ok(git) => git,
Err(e) => return Task::ready(Err(e)).boxed(),
};
@@ -1560,11 +1553,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
- let output = new_command(&git_binary_path)
- .current_dir(working_directory)
- .args(args)
- .output()
- .await?;
+ let output = git.build_command(args).output().await?;
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
stdout.parse()
@@ -1577,13 +1566,12 @@ impl GitRepository for RealGitRepository {
}
fn stash_entries(&self) -> BoxFuture<'_, Result<GitStash>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = self.working_directory();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let output = new_command(&git_binary_path)
- .current_dir(working_directory?)
- .args(&["stash", "list", "--pretty=format:%gd%x00%H%x00%ct%x00%s"])
+ let git = git_binary?;
+ let output = git
+ .build_command(&["stash", "list", "--pretty=format:%gd%x00%H%x00%ct%x00%s"])
.output()
.await?;
if output.status.success() {
@@ -1598,8 +1586,7 @@ impl GitRepository for RealGitRepository {
}
fn branches(&self) -> BoxFuture<'_, Result<Vec<Branch>>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
let fields = [
@@ -1621,12 +1608,8 @@ impl GitRepository for RealGitRepository {
"--format",
&fields,
];
- let working_directory = working_directory?;
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(args)
- .output()
- .await?;
+ let git = git_binary?;
+ let output = git.build_command(args).output().await?;
anyhow::ensure!(
output.status.success(),
@@ -1640,11 +1623,7 @@ impl GitRepository for RealGitRepository {
if branches.is_empty() {
let args = vec!["symbolic-ref", "--quiet", "HEAD"];
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(args)
- .output()
- .await?;
+ let output = git.build_command(args).output().await?;
// git symbolic-ref returns a non-0 exit code if HEAD points
// to something other than a branch
@@ -1666,13 +1645,12 @@ impl GitRepository for RealGitRepository {
}
fn worktrees(&self) -> BoxFuture<'_, Result<Vec<Worktree>>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = self.working_directory();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let output = new_command(&git_binary_path)
- .current_dir(working_directory?)
- .args(&["--no-optional-locks", "worktree", "list", "--porcelain"])
+ let git = git_binary?;
+ let output = git
+ .build_command(&["--no-optional-locks", "worktree", "list", "--porcelain"])
.output()
.await?;
if output.status.success() {
@@ -1692,8 +1670,7 @@ impl GitRepository for RealGitRepository {
directory: PathBuf,
from_commit: Option<String>,
) -> BoxFuture<'_, Result<()>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = self.working_directory();
+ let git_binary = self.git_binary();
let final_path = directory.join(&name);
let mut args = vec![
OsString::from("--no-optional-locks"),
@@ -1713,11 +1690,8 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
std::fs::create_dir_all(final_path.parent().unwrap_or(&final_path))?;
- let output = new_command(&git_binary_path)
- .current_dir(working_directory?)
- .args(args)
- .output()
- .await?;
+ let git = git_binary?;
+ let output = git.build_command(args).output().await?;
if output.status.success() {
Ok(())
} else {
@@ -1729,9 +1703,7 @@ impl GitRepository for RealGitRepository {
}
fn remove_worktree(&self, path: PathBuf, force: bool) -> BoxFuture<'_, Result<()>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = self.working_directory();
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
@@ -1745,18 +1717,14 @@ impl GitRepository for RealGitRepository {
}
args.push("--".into());
args.push(path.as_os_str().into());
- GitBinary::new(git_binary_path, working_directory?, executor)
- .run(args)
- .await?;
+ git_binary?.run(args).await?;
anyhow::Ok(())
})
.boxed()
}
fn rename_worktree(&self, old_path: PathBuf, new_path: PathBuf) -> BoxFuture<'_, Result<()>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = self.working_directory();
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
@@ -1768,9 +1736,7 @@ impl GitRepository for RealGitRepository {
old_path.as_os_str().into(),
new_path.as_os_str().into(),
];
- GitBinary::new(git_binary_path, working_directory?, executor)
- .run(args)
- .await?;
+ git_binary?.run(args).await?;
anyhow::Ok(())
})
.boxed()
@@ -1778,9 +1744,7 @@ impl GitRepository for RealGitRepository {
fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>> {
let repo = self.repository.clone();
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
let branch = self.executor.spawn(async move {
let repo = repo.lock();
let branch = if let Ok(branch) = repo.find_branch(&name, BranchType::Local) {
@@ -1815,9 +1779,7 @@ impl GitRepository for RealGitRepository {
self.executor
.spawn(async move {
let branch = branch.await?;
- GitBinary::new(git_binary_path, working_directory?, executor)
- .run(&["checkout", &branch])
- .await?;
+ git_binary?.run(&["checkout", &branch]).await?;
anyhow::Ok(())
})
.boxed()
@@ -1828,9 +1790,7 @@ impl GitRepository for RealGitRepository {
name: String,
base_branch: Option<String>,
) -> BoxFuture<'_, Result<()>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = self.working_directory();
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
@@ -1841,22 +1801,18 @@ impl GitRepository for RealGitRepository {
args.push(&base_branch_str);
}
- GitBinary::new(git_binary_path, working_directory?, executor)
- .run(&args)
- .await?;
+ git_binary?.run(&args).await?;
anyhow::Ok(())
})
.boxed()
}
fn rename_branch(&self, branch: String, new_name: String) -> BoxFuture<'_, Result<()>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = self.working_directory();
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- GitBinary::new(git_binary_path, working_directory?, executor)
+ git_binary?
.run(&["branch", "-m", &branch, &new_name])
.await?;
anyhow::Ok(())
@@ -1865,15 +1821,11 @@ impl GitRepository for RealGitRepository {
}
fn delete_branch(&self, name: String) -> BoxFuture<'_, Result<()>> {
- let git_binary_path = self.any_git_binary_path.clone();
- let working_directory = self.working_directory();
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- GitBinary::new(git_binary_path, working_directory?, executor)
- .run(&["branch", "-d", &name])
- .await?;
+ git_binary?.run(&["branch", "-d", &name]).await?;
anyhow::Ok(())
})
.boxed()
@@ -1885,20 +1837,11 @@ impl GitRepository for RealGitRepository {
content: Rope,
line_ending: LineEnding,
) -> BoxFuture<'_, Result<crate::blame::Blame>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
- let executor = self.executor.clone();
+ let git = self.git_binary();
- executor
+ self.executor
.spawn(async move {
- crate::blame::Blame::for_path(
- &git_binary_path,
- &working_directory?,
- &path,
- &content,
- line_ending,
- )
- .await
+ crate::blame::Blame::for_path(&git?, &path, &content, line_ending).await
})
.boxed()
}
@@ -1913,11 +1856,10 @@ impl GitRepository for RealGitRepository {
skip: usize,
limit: Option<usize>,
) -> BoxFuture<'_, Result<FileHistory>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
+ let git = git_binary?;
// Use a unique delimiter with a hardcoded UUID to separate commits
// This essentially eliminates any chance of encountering the delimiter in actual commit data
let commit_delimiter =
@@ -1945,9 +1887,8 @@ impl GitRepository for RealGitRepository {
args.push("--");
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(&args)
+ let output = git
+ .build_command(&args)
.arg(path.as_unix_str())
.output()
.await?;
@@ -1992,30 +1933,17 @@ impl GitRepository for RealGitRepository {
}
fn diff(&self, diff: DiffType) -> BoxFuture<'_, Result<String>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
+ let git = git_binary?;
let output = match diff {
DiffType::HeadToIndex => {
- new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(["diff", "--staged"])
- .output()
- .await?
- }
- DiffType::HeadToWorktree => {
- new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(["diff"])
- .output()
- .await?
+ git.build_command(["diff", "--staged"]).output().await?
}
+ DiffType::HeadToWorktree => git.build_command(["diff"]).output().await?,
DiffType::MergeBase { base_ref } => {
- new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(["diff", "--merge-base", base_ref.as_ref()])
+ git.build_command(["diff", "--merge-base", base_ref.as_ref()])
.output()
.await?
}
@@ -2036,15 +1964,14 @@ impl GitRepository for RealGitRepository {
paths: Vec<RepoPath>,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
if !paths.is_empty() {
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory?)
+ let git = git_binary?;
+ let output = git
+ .build_command(["update-index", "--add", "--remove", "--"])
.envs(env.iter())
- .args(["update-index", "--add", "--remove", "--"])
.args(paths.iter().map(|p| p.as_unix_str()))
.output()
.await?;
@@ -2064,16 +1991,15 @@ impl GitRepository for RealGitRepository {
paths: Vec<RepoPath>,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
if !paths.is_empty() {
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory?)
+ let git = git_binary?;
+ let output = git
+ .build_command(["reset", "--quiet", "--"])
.envs(env.iter())
- .args(["reset", "--quiet", "--"])
.args(paths.iter().map(|p| p.as_std_path()))
.output()
.await?;
@@ -2094,19 +2020,16 @@ impl GitRepository for RealGitRepository {
paths: Vec<RepoPath>,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let mut cmd = new_command(&git_binary_path);
- cmd.current_dir(&working_directory?)
+ let git = git_binary?;
+ let output = git
+ .build_command(["stash", "push", "--quiet", "--include-untracked"])
.envs(env.iter())
- .args(["stash", "push", "--quiet"])
- .arg("--include-untracked");
-
- cmd.args(paths.iter().map(|p| p.as_unix_str()));
-
- let output = cmd.output().await?;
+ .args(paths.iter().map(|p| p.as_unix_str()))
+ .output()
+ .await?;
anyhow::ensure!(
output.status.success(),
@@ -2123,20 +2046,15 @@ impl GitRepository for RealGitRepository {
index: Option<usize>,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let mut cmd = new_command(git_binary_path);
+ let git = git_binary?;
let mut args = vec!["stash".to_string(), "pop".to_string()];
if let Some(index) = index {
args.push(format!("stash@{{{}}}", index));
}
- cmd.current_dir(&working_directory?)
- .envs(env.iter())
- .args(args);
-
- let output = cmd.output().await?;
+ let output = git.build_command(&args).envs(env.iter()).output().await?;
anyhow::ensure!(
output.status.success(),
@@ -2153,20 +2071,15 @@ impl GitRepository for RealGitRepository {
index: Option<usize>,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let mut cmd = new_command(git_binary_path);
+ let git = git_binary?;
let mut args = vec!["stash".to_string(), "apply".to_string()];
if let Some(index) = index {
args.push(format!("stash@{{{}}}", index));
}
- cmd.current_dir(&working_directory?)
- .envs(env.iter())
- .args(args);
-
- let output = cmd.output().await?;
+ let output = git.build_command(&args).envs(env.iter()).output().await?;
anyhow::ensure!(
output.status.success(),
@@ -2183,20 +2096,15 @@ impl GitRepository for RealGitRepository {
index: Option<usize>,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let mut cmd = new_command(git_binary_path);
+ let git = git_binary?;
let mut args = vec!["stash".to_string(), "drop".to_string()];
if let Some(index) = index {
args.push(format!("stash@{{{}}}", index));
}
- cmd.current_dir(&working_directory?)
- .envs(env.iter())
- .args(args);
-
- let output = cmd.output().await?;
+ let output = git.build_command(&args).envs(env.iter()).output().await?;
anyhow::ensure!(
output.status.success(),
@@ -2216,16 +2124,14 @@ impl GitRepository for RealGitRepository {
ask_pass: AskPassDelegate,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
let executor = self.executor.clone();
// Note: Do not spawn this command on the background thread, it might pop open the credential helper
// which we want to block on.
async move {
- let mut cmd = new_command(git_binary_path);
- cmd.current_dir(&working_directory?)
- .envs(env.iter())
- .args(["commit", "--quiet", "-m"])
+ let git = git_binary?;
+ let mut cmd = git.build_command(["commit", "--quiet", "-m"]);
+ cmd.envs(env.iter())
.arg(&message.to_string())
.arg("--cleanup=strip")
.arg("--no-verify")
@@ -2264,16 +2170,21 @@ impl GitRepository for RealGitRepository {
let working_directory = self.working_directory();
let executor = cx.background_executor().clone();
let git_binary_path = self.system_git_binary_path.clone();
+ let is_trusted = self.is_trusted();
// Note: Do not spawn this command on the background thread, it might pop open the credential helper
// which we want to block on.
async move {
let git_binary_path = git_binary_path.context("git not found on $PATH, can't push")?;
let working_directory = working_directory?;
- let mut command = new_command(git_binary_path);
+ let git = GitBinary::new(
+ git_binary_path,
+ working_directory,
+ executor.clone(),
+ is_trusted,
+ );
+ let mut command = git.build_command(["push"]);
command
.envs(env.iter())
- .current_dir(&working_directory)
- .args(["push"])
.args(options.map(|option| match option {
PushOptions::SetUpstream => "--set-upstream",
PushOptions::Force => "--force-with-lease",
@@ -2301,15 +2212,20 @@ impl GitRepository for RealGitRepository {
let working_directory = self.working_directory();
let executor = cx.background_executor().clone();
let git_binary_path = self.system_git_binary_path.clone();
+ let is_trusted = self.is_trusted();
// Note: Do not spawn this command on the background thread, it might pop open the credential helper
// which we want to block on.
async move {
let git_binary_path = git_binary_path.context("git not found on $PATH, can't pull")?;
- let mut command = new_command(git_binary_path);
- command
- .envs(env.iter())
- .current_dir(&working_directory?)
- .arg("pull");
+ let working_directory = working_directory?;
+ let git = GitBinary::new(
+ git_binary_path,
+ working_directory,
+ executor.clone(),
+ is_trusted,
+ );
+ let mut command = git.build_command(["pull"]);
+ command.envs(env.iter());
if rebase {
command.arg("--rebase");
@@ -2337,15 +2253,21 @@ impl GitRepository for RealGitRepository {
let remote_name = format!("{}", fetch_options);
let git_binary_path = self.system_git_binary_path.clone();
let executor = cx.background_executor().clone();
+ let is_trusted = self.is_trusted();
// Note: Do not spawn this command on the background thread, it might pop open the credential helper
// which we want to block on.
async move {
let git_binary_path = git_binary_path.context("git not found on $PATH, can't fetch")?;
- let mut command = new_command(git_binary_path);
+ let working_directory = working_directory?;
+ let git = GitBinary::new(
+ git_binary_path,
+ working_directory,
+ executor.clone(),
+ is_trusted,
+ );
+ let mut command = git.build_command(["fetch", &remote_name]);
command
.envs(env.iter())
- .current_dir(&working_directory?)
- .args(["fetch", &remote_name])
.stdout(Stdio::piped())
.stderr(Stdio::piped());
@@ -2355,14 +2277,12 @@ impl GitRepository for RealGitRepository {
}
fn get_push_remote(&self, branch: String) -> BoxFuture<'_, Result<Option<Remote>>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(["rev-parse", "--abbrev-ref"])
+ let git = git_binary?;
+ let output = git
+ .build_command(["rev-parse", "--abbrev-ref"])
.arg(format!("{branch}@{{push}}"))
.output()
.await?;
@@ -2382,14 +2302,12 @@ impl GitRepository for RealGitRepository {
}
fn get_branch_remote(&self, branch: String) -> BoxFuture<'_, Result<Option<Remote>>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(["config", "--get"])
+ let git = git_binary?;
+ let output = git
+ .build_command(["config", "--get"])
.arg(format!("branch.{branch}.remote"))
.output()
.await?;
@@ -2406,16 +2324,11 @@ impl GitRepository for RealGitRepository {
}
fn get_all_remotes(&self) -> BoxFuture<'_, Result<Vec<Remote>>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(["remote", "-v"])
- .output()
- .await?;
+ let git = git_binary?;
+ let output = git.build_command(["remote", "-v"]).output().await?;
anyhow::ensure!(
output.status.success(),
@@ -2464,17 +2377,12 @@ impl GitRepository for RealGitRepository {
}
fn check_for_pushed_commit(&self) -> BoxFuture<'_, Result<Vec<SharedString>>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
+ let git = git_binary?;
let git_cmd = async |args: &[&str]| -> Result<String> {
- let output = new_command(&git_binary_path)
- .current_dir(&working_directory)
- .args(args)
- .output()
- .await?;
+ let output = git.build_command(args).output().await?;
anyhow::ensure!(
output.status.success(),
String::from_utf8_lossy(&output.stderr).to_string()
@@ -2523,14 +2431,10 @@ impl GitRepository for RealGitRepository {
}
fn checkpoint(&self) -> BoxFuture<'static, Result<GitRepositoryCheckpoint>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
- let mut git = GitBinary::new(git_binary_path, working_directory.clone(), executor)
- .envs(checkpoint_author_envs());
+ let mut git = git_binary?.envs(checkpoint_author_envs());
git.with_temp_index(async |git| {
let head_sha = git.run(&["rev-parse", "HEAD"]).await.ok();
let mut excludes = exclude_files(git).await?;
@@ -2556,15 +2460,10 @@ impl GitRepository for RealGitRepository {
}
fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
-
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
-
- let git = GitBinary::new(git_binary_path, working_directory, executor);
+ let git = git_binary?;
git.run(&[
"restore",
"--source",
@@ -2595,14 +2494,10 @@ impl GitRepository for RealGitRepository {
left: GitRepositoryCheckpoint,
right: GitRepositoryCheckpoint,
) -> BoxFuture<'_, Result<bool>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
-
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
- let git = GitBinary::new(git_binary_path, working_directory, executor);
+ let git = git_binary?;
let result = git
.run(&[
"diff-tree",
@@ -2633,14 +2528,10 @@ impl GitRepository for RealGitRepository {
base_checkpoint: GitRepositoryCheckpoint,
target_checkpoint: GitRepositoryCheckpoint,
) -> BoxFuture<'_, Result<String>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
-
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
- let git = GitBinary::new(git_binary_path, working_directory, executor);
+ let git = git_binary?;
git.run(&[
"diff",
"--find-renames",
@@ -2657,14 +2548,10 @@ impl GitRepository for RealGitRepository {
&self,
include_remote_name: bool,
) -> BoxFuture<'_, Result<Option<SharedString>>> {
- let working_directory = self.working_directory();
- let git_binary_path = self.any_git_binary_path.clone();
-
- let executor = self.executor.clone();
+ let git_binary = self.git_binary();
self.executor
.spawn(async move {
- let working_directory = working_directory?;
- let git = GitBinary::new(git_binary_path, working_directory, executor);
+ let git = git_binary?;
let strip_prefix = if include_remote_name {
"refs/remotes/"
@@ -2714,15 +2601,19 @@ impl GitRepository for RealGitRepository {
hook: RunHook,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
- let working_directory = self.working_directory();
+ let git_binary = self.git_binary();
let repository = self.repository.clone();
- let git_binary_path = self.any_git_binary_path.clone();
- let executor = self.executor.clone();
let help_output = self.any_git_binary_help_output();
// Note: Do not spawn these commands on the background thread, as this causes some git hooks to hang.
async move {
- let working_directory = working_directory?;
+ let git = git_binary?;
+
+ if !git.is_trusted {
+ bail!("Can't run git commit hooks in restrictive workspace");
+ }
+
+ let working_directory = git.working_directory.clone();
if !help_output
.await
.lines()
@@ -6,6 +6,9 @@ pub mod pending_op;
use crate::{
ProjectEnvironment, ProjectItem, ProjectPath,
buffer_store::{BufferStore, BufferStoreEvent},
+ trusted_worktrees::{
+ PathTrust, TrustedWorktrees, TrustedWorktreesEvent, TrustedWorktreesStore,
+ },
worktree_store::{WorktreeStore, WorktreeStoreEvent},
};
use anyhow::{Context as _, Result, anyhow, bail};
@@ -343,6 +346,7 @@ impl LocalRepositoryState {
dot_git_abs_path: Arc<Path>,
project_environment: WeakEntity<ProjectEnvironment>,
fs: Arc<dyn Fs>,
+ is_trusted: bool,
cx: &mut AsyncApp,
) -> anyhow::Result<Self> {
let environment = project_environment
@@ -370,6 +374,7 @@ impl LocalRepositoryState {
}
})
.await?;
+ backend.set_trusted(is_trusted);
Ok(LocalRepositoryState {
backend,
environment: Arc::new(environment),
@@ -478,11 +483,15 @@ impl GitStore {
state: GitStoreState,
cx: &mut Context<Self>,
) -> Self {
- let _subscriptions = vec![
+ let mut _subscriptions = vec![
cx.subscribe(&worktree_store, Self::on_worktree_store_event),
cx.subscribe(&buffer_store, Self::on_buffer_store_event),
];
+ if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) {
+ _subscriptions.push(cx.subscribe(&trusted_worktrees, Self::on_trusted_worktrees_event));
+ }
+
GitStore {
state,
buffer_store,
@@ -1497,6 +1506,13 @@ impl GitStore {
} = update
{
let id = RepositoryId(next_repository_id.fetch_add(1, atomic::Ordering::Release));
+ let is_trusted = TrustedWorktrees::try_get_global(cx)
+ .map(|trusted_worktrees| {
+ trusted_worktrees.update(cx, |trusted_worktrees, cx| {
+ trusted_worktrees.can_trust(&self.worktree_store, worktree_id, cx)
+ })
+ })
+ .unwrap_or(false);
let git_store = cx.weak_entity();
let repo = cx.new(|cx| {
let mut repo = Repository::local(
@@ -1505,6 +1521,7 @@ impl GitStore {
dot_git_abs_path.clone(),
project_environment.downgrade(),
fs.clone(),
+ is_trusted,
git_store,
cx,
);
@@ -1545,6 +1562,39 @@ impl GitStore {
}
}
+ fn on_trusted_worktrees_event(
+ &mut self,
+ _: Entity<TrustedWorktreesStore>,
+ event: &TrustedWorktreesEvent,
+ cx: &mut Context<Self>,
+ ) {
+ if !matches!(self.state, GitStoreState::Local { .. }) {
+ return;
+ }
+
+ let (is_trusted, event_paths) = match event {
+ TrustedWorktreesEvent::Trusted(_, trusted_paths) => (true, trusted_paths),
+ TrustedWorktreesEvent::Restricted(_, restricted_paths) => (false, restricted_paths),
+ };
+
+ for (repo_id, worktree_ids) in &self.worktree_ids {
+ if worktree_ids
+ .iter()
+ .any(|worktree_id| event_paths.contains(&PathTrust::Worktree(*worktree_id)))
+ {
+ if let Some(repo) = self.repositories.get(repo_id) {
+ let repository_state = repo.read(cx).repository_state.clone();
+ cx.background_spawn(async move {
+ if let Ok(RepositoryState::Local(state)) = repository_state.await {
+ state.backend.set_trusted(is_trusted);
+ }
+ })
+ .detach();
+ }
+ }
+ }
+ }
+
fn on_buffer_store_event(
&mut self,
_: Entity<BufferStore>,
@@ -3693,6 +3743,13 @@ impl MergeDetails {
}
impl Repository {
+ pub fn is_trusted(&self) -> bool {
+ match self.repository_state.peek() {
+ Some(Ok(RepositoryState::Local(state))) => state.backend.is_trusted(),
+ _ => false,
+ }
+ }
+
pub fn snapshot(&self) -> RepositorySnapshot {
self.snapshot.clone()
}
@@ -3717,6 +3774,7 @@ impl Repository {
dot_git_abs_path: Arc<Path>,
project_environment: WeakEntity<ProjectEnvironment>,
fs: Arc<dyn Fs>,
+ is_trusted: bool,
git_store: WeakEntity<GitStore>,
cx: &mut Context<Self>,
) -> Self {
@@ -3729,6 +3787,7 @@ impl Repository {
dot_git_abs_path,
project_environment,
fs,
+ is_trusted,
cx,
)
.await