git: Pass project environment to git binary invocations (#26301)

Closes #26213 

Release Notes:

- Git Beta: pass down environment variables from project to git
operations
This commit is contained in:
Cole Miller 2025-03-10 12:12:46 -04:00 committed by GitHub
parent 013a646799
commit b91e929086
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 194 additions and 55 deletions

View file

@ -149,7 +149,12 @@ pub trait GitRepository: Send + Sync {
/// Also returns `None` for symlinks. /// Also returns `None` for symlinks.
fn load_committed_text(&self, path: &RepoPath) -> Option<String>; fn load_committed_text(&self, path: &RepoPath) -> Option<String>;
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()>; fn set_index_text(
&self,
path: &RepoPath,
content: Option<String>,
env: &HashMap<String, String>,
) -> anyhow::Result<()>;
/// Returns the URL of the remote with the given name. /// Returns the URL of the remote with the given name.
fn remote_url(&self, name: &str) -> Option<String>; fn remote_url(&self, name: &str) -> Option<String>;
@ -167,8 +172,13 @@ pub trait GitRepository: Send + Sync {
fn create_branch(&self, _: &str) -> Result<()>; fn create_branch(&self, _: &str) -> Result<()>;
fn branch_exits(&self, _: &str) -> Result<bool>; fn branch_exits(&self, _: &str) -> Result<bool>;
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()>; fn reset(&self, commit: &str, mode: ResetMode, env: &HashMap<String, String>) -> Result<()>;
fn checkout_files(&self, commit: &str, paths: &[RepoPath]) -> Result<()>; fn checkout_files(
&self,
commit: &str,
paths: &[RepoPath],
env: &HashMap<String, String>,
) -> Result<()>;
fn show(&self, commit: &str) -> Result<CommitDetails>; fn show(&self, commit: &str) -> Result<CommitDetails>;
@ -189,13 +199,18 @@ pub trait GitRepository: Send + Sync {
/// Updates the index to match the worktree at the given paths. /// 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. /// 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]) -> Result<()>; fn stage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()>;
/// Updates the index to match HEAD at the given paths. /// 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. /// 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]) -> Result<()>; fn unstage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()>;
fn commit(&self, message: &str, name_and_email: Option<(&str, &str)>) -> Result<()>; fn commit(
&self,
message: &str,
name_and_email: Option<(&str, &str)>,
env: &HashMap<String, String>,
) -> Result<()>;
fn push( fn push(
&self, &self,
@ -203,6 +218,7 @@ pub trait GitRepository: Send + Sync {
upstream_name: &str, upstream_name: &str,
options: Option<PushOptions>, options: Option<PushOptions>,
askpass: AskPassSession, askpass: AskPassSession,
env: &HashMap<String, String>,
) -> Result<RemoteCommandOutput>; ) -> Result<RemoteCommandOutput>;
fn pull( fn pull(
@ -210,8 +226,13 @@ pub trait GitRepository: Send + Sync {
branch_name: &str, branch_name: &str,
upstream_name: &str, upstream_name: &str,
askpass: AskPassSession, askpass: AskPassSession,
env: &HashMap<String, String>,
) -> Result<RemoteCommandOutput>;
fn fetch(
&self,
askpass: AskPassSession,
env: &HashMap<String, String>,
) -> Result<RemoteCommandOutput>; ) -> Result<RemoteCommandOutput>;
fn fetch(&self, askpass: AskPassSession) -> Result<RemoteCommandOutput>;
fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>>; fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>>;
@ -308,7 +329,7 @@ impl GitRepository for RealGitRepository {
Ok(details) Ok(details)
} }
fn reset(&self, commit: &str, mode: ResetMode) -> Result<()> { fn reset(&self, commit: &str, mode: ResetMode, env: &HashMap<String, String>) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
let mode_flag = match mode { let mode_flag = match mode {
@ -317,6 +338,7 @@ impl GitRepository for RealGitRepository {
}; };
let output = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.envs(env)
.current_dir(&working_directory) .current_dir(&working_directory)
.args(["reset", mode_flag, commit]) .args(["reset", mode_flag, commit])
.output()?; .output()?;
@ -329,7 +351,12 @@ impl GitRepository for RealGitRepository {
Ok(()) Ok(())
} }
fn checkout_files(&self, commit: &str, paths: &[RepoPath]) -> Result<()> { fn checkout_files(
&self,
commit: &str,
paths: &[RepoPath],
env: &HashMap<String, String>,
) -> Result<()> {
if paths.is_empty() { if paths.is_empty() {
return Ok(()); return Ok(());
} }
@ -337,6 +364,7 @@ impl GitRepository for RealGitRepository {
let output = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.envs(env)
.args(["checkout", commit, "--"]) .args(["checkout", commit, "--"])
.args(paths.iter().map(|path| path.as_ref())) .args(paths.iter().map(|path| path.as_ref()))
.output()?; .output()?;
@ -385,11 +413,17 @@ impl GitRepository for RealGitRepository {
Some(content) Some(content)
} }
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()> { fn set_index_text(
&self,
path: &RepoPath,
content: Option<String>,
env: &HashMap<String, String>,
) -> anyhow::Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
if let Some(content) = content { if let Some(content) = content {
let mut child = new_std_command(&self.git_binary_path) let mut child = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.envs(env)
.args(["hash-object", "-w", "--stdin"]) .args(["hash-object", "-w", "--stdin"])
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
@ -402,6 +436,7 @@ impl GitRepository for RealGitRepository {
let output = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.envs(env)
.args(["update-index", "--add", "--cacheinfo", "100644", &sha]) .args(["update-index", "--add", "--cacheinfo", "100644", &sha])
.arg(path.as_ref()) .arg(path.as_ref())
.output()?; .output()?;
@ -415,6 +450,7 @@ impl GitRepository for RealGitRepository {
} else { } else {
let output = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.envs(env)
.args(["update-index", "--force-remove"]) .args(["update-index", "--force-remove"])
.arg(path.as_ref()) .arg(path.as_ref())
.output()?; .output()?;
@ -607,12 +643,13 @@ impl GitRepository for RealGitRepository {
Ok(String::from_utf8_lossy(&output.stdout).to_string()) Ok(String::from_utf8_lossy(&output.stdout).to_string())
} }
fn stage_paths(&self, paths: &[RepoPath]) -> Result<()> { fn stage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
if !paths.is_empty() { if !paths.is_empty() {
let output = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.envs(env)
.args(["update-index", "--add", "--remove", "--"]) .args(["update-index", "--add", "--remove", "--"])
.args(paths.iter().map(|p| p.as_ref())) .args(paths.iter().map(|p| p.as_ref()))
.output()?; .output()?;
@ -627,12 +664,13 @@ impl GitRepository for RealGitRepository {
Ok(()) Ok(())
} }
fn unstage_paths(&self, paths: &[RepoPath]) -> Result<()> { fn unstage_paths(&self, paths: &[RepoPath], env: &HashMap<String, String>) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
if !paths.is_empty() { if !paths.is_empty() {
let output = new_std_command(&self.git_binary_path) let output = new_std_command(&self.git_binary_path)
.current_dir(&working_directory) .current_dir(&working_directory)
.envs(env)
.args(["reset", "--quiet", "--"]) .args(["reset", "--quiet", "--"])
.args(paths.iter().map(|p| p.as_ref())) .args(paths.iter().map(|p| p.as_ref()))
.output()?; .output()?;
@ -647,11 +685,17 @@ impl GitRepository for RealGitRepository {
Ok(()) Ok(())
} }
fn commit(&self, message: &str, name_and_email: Option<(&str, &str)>) -> Result<()> { fn commit(
&self,
message: &str,
name_and_email: Option<(&str, &str)>,
env: &HashMap<String, String>,
) -> Result<()> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
let mut cmd = new_std_command(&self.git_binary_path); let mut cmd = new_std_command(&self.git_binary_path);
cmd.current_dir(&working_directory) cmd.current_dir(&working_directory)
.envs(env)
.args(["commit", "--quiet", "-m"]) .args(["commit", "--quiet", "-m"])
.arg(message) .arg(message)
.arg("--cleanup=strip"); .arg("--cleanup=strip");
@ -677,11 +721,13 @@ impl GitRepository for RealGitRepository {
remote_name: &str, remote_name: &str,
options: Option<PushOptions>, options: Option<PushOptions>,
ask_pass: AskPassSession, ask_pass: AskPassSession,
env: &HashMap<String, String>,
) -> Result<RemoteCommandOutput> { ) -> Result<RemoteCommandOutput> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
let mut command = new_smol_command("git"); let mut command = new_smol_command("git");
command command
.envs(env)
.env("GIT_ASKPASS", ask_pass.script_path()) .env("GIT_ASKPASS", ask_pass.script_path())
.env("SSH_ASKPASS", ask_pass.script_path()) .env("SSH_ASKPASS", ask_pass.script_path())
.env("SSH_ASKPASS_REQUIRE", "force") .env("SSH_ASKPASS_REQUIRE", "force")
@ -705,11 +751,13 @@ impl GitRepository for RealGitRepository {
branch_name: &str, branch_name: &str,
remote_name: &str, remote_name: &str,
ask_pass: AskPassSession, ask_pass: AskPassSession,
env: &HashMap<String, String>,
) -> Result<RemoteCommandOutput> { ) -> Result<RemoteCommandOutput> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
let mut command = new_smol_command("git"); let mut command = new_smol_command("git");
command command
.envs(env)
.env("GIT_ASKPASS", ask_pass.script_path()) .env("GIT_ASKPASS", ask_pass.script_path())
.env("SSH_ASKPASS", ask_pass.script_path()) .env("SSH_ASKPASS", ask_pass.script_path())
.env("SSH_ASKPASS_REQUIRE", "force") .env("SSH_ASKPASS_REQUIRE", "force")
@ -724,11 +772,16 @@ impl GitRepository for RealGitRepository {
run_remote_command(ask_pass, git_process) run_remote_command(ask_pass, git_process)
} }
fn fetch(&self, ask_pass: AskPassSession) -> Result<RemoteCommandOutput> { fn fetch(
&self,
ask_pass: AskPassSession,
env: &HashMap<String, String>,
) -> Result<RemoteCommandOutput> {
let working_directory = self.working_directory()?; let working_directory = self.working_directory()?;
let mut command = new_smol_command("git"); let mut command = new_smol_command("git");
command command
.envs(env)
.env("GIT_ASKPASS", ask_pass.script_path()) .env("GIT_ASKPASS", ask_pass.script_path())
.env("SSH_ASKPASS", ask_pass.script_path()) .env("SSH_ASKPASS", ask_pass.script_path())
.env("SSH_ASKPASS_REQUIRE", "force") .env("SSH_ASKPASS_REQUIRE", "force")
@ -919,7 +972,12 @@ impl GitRepository for FakeGitRepository {
state.head_contents.get(path.as_ref()).cloned() state.head_contents.get(path.as_ref()).cloned()
} }
fn set_index_text(&self, path: &RepoPath, content: Option<String>) -> anyhow::Result<()> { fn set_index_text(
&self,
path: &RepoPath,
content: Option<String>,
_env: &HashMap<String, String>,
) -> anyhow::Result<()> {
let mut state = self.state.lock(); let mut state = self.state.lock();
if let Some(message) = state.simulated_index_write_error_message.clone() { if let Some(message) = state.simulated_index_write_error_message.clone() {
return Err(anyhow::anyhow!(message)); return Err(anyhow::anyhow!(message));
@ -952,11 +1010,11 @@ impl GitRepository for FakeGitRepository {
unimplemented!() unimplemented!()
} }
fn reset(&self, _: &str, _: ResetMode) -> Result<()> { fn reset(&self, _: &str, _: ResetMode, _: &HashMap<String, String>) -> Result<()> {
unimplemented!() unimplemented!()
} }
fn checkout_files(&self, _: &str, _: &[RepoPath]) -> Result<()> { fn checkout_files(&self, _: &str, _: &[RepoPath], _: &HashMap<String, String>) -> Result<()> {
unimplemented!() unimplemented!()
} }
@ -1042,15 +1100,20 @@ impl GitRepository for FakeGitRepository {
.cloned() .cloned()
} }
fn stage_paths(&self, _paths: &[RepoPath]) -> Result<()> { fn stage_paths(&self, _paths: &[RepoPath], _env: &HashMap<String, String>) -> Result<()> {
unimplemented!() unimplemented!()
} }
fn unstage_paths(&self, _paths: &[RepoPath]) -> Result<()> { fn unstage_paths(&self, _paths: &[RepoPath], _env: &HashMap<String, String>) -> Result<()> {
unimplemented!() unimplemented!()
} }
fn commit(&self, _message: &str, _name_and_email: Option<(&str, &str)>) -> Result<()> { fn commit(
&self,
_message: &str,
_name_and_email: Option<(&str, &str)>,
_env: &HashMap<String, String>,
) -> Result<()> {
unimplemented!() unimplemented!()
} }
@ -1060,6 +1123,7 @@ impl GitRepository for FakeGitRepository {
_remote: &str, _remote: &str,
_options: Option<PushOptions>, _options: Option<PushOptions>,
_ask_pass: AskPassSession, _ask_pass: AskPassSession,
_env: &HashMap<String, String>,
) -> Result<RemoteCommandOutput> { ) -> Result<RemoteCommandOutput> {
unimplemented!() unimplemented!()
} }
@ -1069,11 +1133,16 @@ impl GitRepository for FakeGitRepository {
_branch: &str, _branch: &str,
_remote: &str, _remote: &str,
_ask_pass: AskPassSession, _ask_pass: AskPassSession,
_env: &HashMap<String, String>,
) -> Result<RemoteCommandOutput> { ) -> Result<RemoteCommandOutput> {
unimplemented!() unimplemented!()
} }
fn fetch(&self, _ask_pass: AskPassSession) -> Result<RemoteCommandOutput> { fn fetch(
&self,
_ask_pass: AskPassSession,
_env: &HashMap<String, String>,
) -> Result<RemoteCommandOutput> {
unimplemented!() unimplemented!()
} }

View file

@ -901,13 +901,14 @@ impl GitPanel {
let buffers = futures::future::join_all(tasks).await; let buffers = futures::future::join_all(tasks).await;
active_repository active_repository
.update(&mut cx, |repo, _| { .update(&mut cx, |repo, cx| {
repo.checkout_files( repo.checkout_files(
"HEAD", "HEAD",
entries entries
.iter() .iter()
.map(|entries| entries.repo_path.clone()) .map(|entries| entries.repo_path.clone())
.collect(), .collect(),
cx,
) )
})? })?
.await??; .await??;
@ -1289,7 +1290,8 @@ impl GitPanel {
let task = if self.has_staged_changes() { let task = if self.has_staged_changes() {
// Repository serializes all git operations, so we can just send a commit immediately // Repository serializes all git operations, so we can just send a commit immediately
let commit_task = active_repository.read(cx).commit(message.into(), None); let commit_task =
active_repository.update(cx, |repo, cx| repo.commit(message.into(), None, cx));
cx.background_spawn(async move { commit_task.await? }) cx.background_spawn(async move { commit_task.await? })
} else { } else {
let changed_files = self let changed_files = self
@ -1310,7 +1312,7 @@ impl GitPanel {
cx.spawn(|_, mut cx| async move { cx.spawn(|_, mut cx| async move {
stage_task.await?; stage_task.await?;
let commit_task = active_repository let commit_task = active_repository
.update(&mut cx, |repo, _| repo.commit(message.into(), None))?; .update(&mut cx, |repo, cx| repo.commit(message.into(), None, cx))?;
commit_task.await? commit_task.await?
}) })
}; };
@ -1346,7 +1348,7 @@ impl GitPanel {
if let Ok(true) = confirmation.await { if let Ok(true) = confirmation.await {
let prior_head = prior_head.await?; let prior_head = prior_head.await?;
repo.update(&mut cx, |repo, _| repo.reset("HEAD^", ResetMode::Soft))? repo.update(&mut cx, |repo, cx| repo.reset("HEAD^", ResetMode::Soft, cx))?
.await??; .await??;
Ok(Some(prior_head)) Ok(Some(prior_head))

View file

@ -178,6 +178,7 @@ impl EntityMap {
} }
} }
#[track_caller]
fn double_lease_panic<T>(operation: &str) -> ! { fn double_lease_panic<T>(operation: &str) -> ! {
panic!( panic!(
"cannot {operation} {} while it is already being updated", "cannot {operation} {} while it is already being updated",

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
buffer_store::{BufferStore, BufferStoreEvent}, buffer_store::{BufferStore, BufferStoreEvent},
worktree_store::{WorktreeStore, WorktreeStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent},
Project, ProjectItem, ProjectPath, Project, ProjectEnvironment, ProjectItem, ProjectPath,
}; };
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use askpass::{AskPassDelegate, AskPassSession}; use askpass::{AskPassDelegate, AskPassSession};
@ -10,6 +10,7 @@ use client::ProjectId;
use collections::HashMap; use collections::HashMap;
use futures::{ use futures::{
channel::{mpsc, oneshot}, channel::{mpsc, oneshot},
future::OptionFuture,
StreamExt as _, StreamExt as _,
}; };
use git::repository::DiffType; use git::repository::DiffType;
@ -43,6 +44,7 @@ use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory};
pub struct GitStore { pub struct GitStore {
buffer_store: Entity<BufferStore>, buffer_store: Entity<BufferStore>,
environment: Option<Entity<ProjectEnvironment>>,
pub(super) project_id: Option<ProjectId>, pub(super) project_id: Option<ProjectId>,
pub(super) client: AnyProtoClient, pub(super) client: AnyProtoClient,
repositories: Vec<Entity<Repository>>, repositories: Vec<Entity<Repository>>,
@ -54,6 +56,7 @@ pub struct GitStore {
pub struct Repository { pub struct Repository {
commit_message_buffer: Option<Entity<Buffer>>, commit_message_buffer: Option<Entity<Buffer>>,
git_store: WeakEntity<GitStore>, git_store: WeakEntity<GitStore>,
project_environment: Option<WeakEntity<ProjectEnvironment>>,
pub worktree_id: WorktreeId, pub worktree_id: WorktreeId,
pub repository_entry: RepositoryEntry, pub repository_entry: RepositoryEntry,
pub dot_git_abs_path: PathBuf, pub dot_git_abs_path: PathBuf,
@ -101,6 +104,7 @@ impl GitStore {
pub fn new( pub fn new(
worktree_store: &Entity<WorktreeStore>, worktree_store: &Entity<WorktreeStore>,
buffer_store: Entity<BufferStore>, buffer_store: Entity<BufferStore>,
environment: Option<Entity<ProjectEnvironment>>,
client: AnyProtoClient, client: AnyProtoClient,
project_id: Option<ProjectId>, project_id: Option<ProjectId>,
cx: &mut Context<'_, Self>, cx: &mut Context<'_, Self>,
@ -115,6 +119,7 @@ impl GitStore {
project_id, project_id,
client, client,
buffer_store, buffer_store,
environment,
repositories: Vec::new(), repositories: Vec::new(),
active_index: None, active_index: None,
update_sender, update_sender,
@ -225,6 +230,10 @@ impl GitStore {
existing_handle existing_handle
} else { } else {
cx.new(|_| Repository { cx.new(|_| Repository {
project_environment: self
.environment
.as_ref()
.map(|env| env.downgrade()),
git_store: this.clone(), git_store: this.clone(),
worktree_id, worktree_id,
askpass_delegates: Default::default(), askpass_delegates: Default::default(),
@ -282,9 +291,13 @@ impl GitStore {
if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event { if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
let buffer_id = diff.read(cx).buffer_id; let buffer_id = diff.read(cx).buffer_id;
if let Some((repo, path)) = this.repository_and_path_for_buffer_id(buffer_id, cx) { if let Some((repo, path)) = this.repository_and_path_for_buffer_id(buffer_id, cx) {
let recv = repo let recv = repo.update(cx, |repo, cx| {
.read(cx) repo.set_index_text(
.set_index_text(&path, new_index_text.as_ref().map(|rope| rope.to_string())); &path,
new_index_text.as_ref().map(|rope| rope.to_string()),
cx,
)
});
let diff = diff.downgrade(); let diff = diff.downgrade();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
if let Some(result) = cx.background_spawn(async move { recv.await.ok() }).await if let Some(result) = cx.background_spawn(async move { recv.await.ok() }).await
@ -542,10 +555,11 @@ impl GitStore {
Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
repository_handle repository_handle
.update(&mut cx, |repository_handle, _| { .update(&mut cx, |repository_handle, cx| {
repository_handle.set_index_text( repository_handle.set_index_text(
&RepoPath::from_str(&envelope.payload.path), &RepoPath::from_str(&envelope.payload.path),
envelope.payload.text, envelope.payload.text,
cx,
) )
})? })?
.await??; .await??;
@ -567,8 +581,8 @@ impl GitStore {
let email = envelope.payload.email.map(SharedString::from); let email = envelope.payload.email.map(SharedString::from);
repository_handle repository_handle
.update(&mut cx, |repository_handle, _| { .update(&mut cx, |repository_handle, cx| {
repository_handle.commit(message, name.zip(email)) repository_handle.commit(message, name.zip(email), cx)
})? })?
.await??; .await??;
Ok(proto::Ack {}) Ok(proto::Ack {})
@ -703,8 +717,8 @@ impl GitStore {
}; };
repository_handle repository_handle
.update(&mut cx, |repository_handle, _| { .update(&mut cx, |repository_handle, cx| {
repository_handle.reset(&envelope.payload.commit, mode) repository_handle.reset(&envelope.payload.commit, mode, cx)
})? })?
.await??; .await??;
Ok(proto::Ack {}) Ok(proto::Ack {})
@ -727,8 +741,8 @@ impl GitStore {
.collect(); .collect();
repository_handle repository_handle
.update(&mut cx, |repository_handle, _| { .update(&mut cx, |repository_handle, cx| {
repository_handle.checkout_files(&envelope.payload.commit, paths) repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
})? })?
.await??; .await??;
Ok(proto::Ack {}) Ok(proto::Ack {})
@ -1115,11 +1129,14 @@ impl Repository {
&self, &self,
commit: &str, commit: &str,
paths: Vec<RepoPath>, paths: Vec<RepoPath>,
cx: &mut App,
) -> oneshot::Receiver<Result<()>> { ) -> oneshot::Receiver<Result<()>> {
let commit = commit.to_string(); 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 { match git_repo {
GitRepo::Local(repo) => repo.checkout_files(&commit, &paths), GitRepo::Local(repo) => repo.checkout_files(&commit, &paths, &env.await),
GitRepo::Remote { GitRepo::Remote {
project_id, project_id,
client, client,
@ -1145,11 +1162,20 @@ impl Repository {
}) })
} }
pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> { pub fn reset(
&self,
commit: &str,
reset_mode: ResetMode,
cx: &mut App,
) -> oneshot::Receiver<Result<()>> {
let commit = commit.to_string(); 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 { match git_repo {
GitRepo::Local(git_repo) => git_repo.reset(&commit, reset_mode), GitRepo::Local(git_repo) => {
let env = env.await;
git_repo.reset(&commit, reset_mode, &env)
}
GitRepo::Remote { GitRepo::Remote {
project_id, project_id,
client, client,
@ -1219,6 +1245,7 @@ impl Repository {
if entries.is_empty() { if entries.is_empty() {
return Task::ready(Ok(())); return Task::ready(Ok(()));
} }
let env = self.worktree_environment(cx);
let mut save_futures = Vec::new(); let mut save_futures = Vec::new();
if let Some(buffer_store) = self.buffer_store(cx) { if let Some(buffer_store) = self.buffer_store(cx) {
@ -1245,11 +1272,12 @@ impl Repository {
for save_future in save_futures { for save_future in save_futures {
save_future.await?; save_future.await?;
} }
let env = env.await;
this.update(&mut cx, |this, _| { this.update(&mut cx, |this, _| {
this.send_job(|git_repo| async move { this.send_job(|git_repo| async move {
match git_repo { match git_repo {
GitRepo::Local(repo) => repo.stage_paths(&entries), GitRepo::Local(repo) => repo.stage_paths(&entries, &env),
GitRepo::Remote { GitRepo::Remote {
project_id, project_id,
client, client,
@ -1288,6 +1316,7 @@ impl Repository {
if entries.is_empty() { if entries.is_empty() {
return Task::ready(Ok(())); return Task::ready(Ok(()));
} }
let env = self.worktree_environment(cx);
let mut save_futures = Vec::new(); let mut save_futures = Vec::new();
if let Some(buffer_store) = self.buffer_store(cx) { if let Some(buffer_store) = self.buffer_store(cx) {
@ -1314,11 +1343,12 @@ impl Repository {
for save_future in save_futures { for save_future in save_futures {
save_future.await?; save_future.await?;
} }
let env = env.await;
this.update(&mut cx, |this, _| { this.update(&mut cx, |this, _| {
this.send_job(|git_repo| async move { this.send_job(|git_repo| async move {
match git_repo { match git_repo {
GitRepo::Local(repo) => repo.unstage_paths(&entries), GitRepo::Local(repo) => repo.unstage_paths(&entries, &env),
GitRepo::Remote { GitRepo::Remote {
project_id, project_id,
client, client,
@ -1375,19 +1405,42 @@ impl Repository {
self.repository_entry.status_len() self.repository_entry.status_len()
} }
fn worktree_environment(
&self,
cx: &mut App,
) -> impl Future<Output = HashMap<String, String>> + 'static {
let task = self.project_environment.as_ref().and_then(|env| {
env.update(cx, |env, cx| {
env.get_environment(
Some(self.worktree_id),
Some(self.worktree_abs_path.clone()),
cx,
)
})
.ok()
});
async move { OptionFuture::from(task).await.flatten().unwrap_or_default() }
}
pub fn commit( pub fn commit(
&self, &self,
message: SharedString, message: SharedString,
name_and_email: Option<(SharedString, SharedString)>, name_and_email: Option<(SharedString, SharedString)>,
cx: &mut App,
) -> oneshot::Receiver<Result<()>> { ) -> oneshot::Receiver<Result<()>> {
let env = self.worktree_environment(cx);
self.send_job(|git_repo| async move { self.send_job(|git_repo| async move {
match git_repo { match git_repo {
GitRepo::Local(repo) => repo.commit( GitRepo::Local(repo) => {
let env = env.await;
repo.commit(
message.as_ref(), message.as_ref(),
name_and_email name_and_email
.as_ref() .as_ref()
.map(|(name, email)| (name.as_ref(), email.as_ref())), .map(|(name, email)| (name.as_ref(), email.as_ref())),
), &env,
)
}
GitRepo::Remote { GitRepo::Remote {
project_id, project_id,
client, client,
@ -1416,17 +1469,19 @@ impl Repository {
pub fn fetch( pub fn fetch(
&mut self, &mut self,
askpass: AskPassDelegate, askpass: AskPassDelegate,
cx: &App, cx: &mut App,
) -> oneshot::Receiver<Result<RemoteCommandOutput>> { ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
let executor = cx.background_executor().clone(); let executor = cx.background_executor().clone();
let askpass_delegates = self.askpass_delegates.clone(); let askpass_delegates = self.askpass_delegates.clone();
let askpass_id = util::post_inc(&mut self.latest_askpass_id); 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| async move {
match git_repo { match git_repo {
GitRepo::Local(git_repository) => { GitRepo::Local(git_repository) => {
let askpass = AskPassSession::new(&executor, askpass).await?; let askpass = AskPassSession::new(&executor, askpass).await?;
git_repository.fetch(askpass) let env = env.await;
git_repository.fetch(askpass, &env)
} }
GitRepo::Remote { GitRepo::Remote {
project_id, project_id,
@ -1465,17 +1520,19 @@ impl Repository {
remote: SharedString, remote: SharedString,
options: Option<PushOptions>, options: Option<PushOptions>,
askpass: AskPassDelegate, askpass: AskPassDelegate,
cx: &App, cx: &mut App,
) -> oneshot::Receiver<Result<RemoteCommandOutput>> { ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
let executor = cx.background_executor().clone(); let executor = cx.background_executor().clone();
let askpass_delegates = self.askpass_delegates.clone(); let askpass_delegates = self.askpass_delegates.clone();
let askpass_id = util::post_inc(&mut self.latest_askpass_id); 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| async move {
match git_repo { match git_repo {
GitRepo::Local(git_repository) => { GitRepo::Local(git_repository) => {
let env = env.await;
let askpass = AskPassSession::new(&executor, askpass).await?; let askpass = AskPassSession::new(&executor, askpass).await?;
git_repository.push(&branch, &remote, options, askpass) git_repository.push(&branch, &remote, options, askpass, &env)
} }
GitRepo::Remote { GitRepo::Remote {
project_id, project_id,
@ -1518,16 +1575,19 @@ impl Repository {
branch: SharedString, branch: SharedString,
remote: SharedString, remote: SharedString,
askpass: AskPassDelegate, askpass: AskPassDelegate,
cx: &App, cx: &mut App,
) -> oneshot::Receiver<Result<RemoteCommandOutput>> { ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
let executor = cx.background_executor().clone(); let executor = cx.background_executor().clone();
let askpass_delegates = self.askpass_delegates.clone(); let askpass_delegates = self.askpass_delegates.clone();
let askpass_id = util::post_inc(&mut self.latest_askpass_id); 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| async move {
match git_repo { match git_repo {
GitRepo::Local(git_repository) => { GitRepo::Local(git_repository) => {
let askpass = AskPassSession::new(&executor, askpass).await?; let askpass = AskPassSession::new(&executor, askpass).await?;
git_repository.pull(&branch, &remote, askpass) let env = env.await;
git_repository.pull(&branch, &remote, askpass, &env)
} }
GitRepo::Remote { GitRepo::Remote {
project_id, project_id,
@ -1565,13 +1625,16 @@ impl Repository {
&self, &self,
path: &RepoPath, path: &RepoPath,
content: Option<String>, content: Option<String>,
cx: &mut App,
) -> oneshot::Receiver<anyhow::Result<()>> { ) -> oneshot::Receiver<anyhow::Result<()>> {
let path = path.clone(); let path = path.clone();
let env = self.worktree_environment(cx);
self.send_keyed_job( self.send_keyed_job(
Some(GitJobKey::WriteIndex(path.clone())), Some(GitJobKey::WriteIndex(path.clone())),
|git_repo| async move { |git_repo| async move {
match git_repo { match git_repo {
GitRepo::Local(repo) => repo.set_index_text(&path, content), GitRepo::Local(repo) => repo.set_index_text(&path, content, &env.await),
GitRepo::Remote { GitRepo::Remote {
project_id, project_id,
client, client,

View file

@ -844,6 +844,7 @@ impl Project {
GitStore::new( GitStore::new(
&worktree_store, &worktree_store,
buffer_store.clone(), buffer_store.clone(),
Some(environment.clone()),
client.clone().into(), client.clone().into(),
None, None,
cx, cx,
@ -972,6 +973,7 @@ impl Project {
GitStore::new( GitStore::new(
&worktree_store, &worktree_store,
buffer_store.clone(), buffer_store.clone(),
Some(environment.clone()),
ssh_proto.clone(), ssh_proto.clone(),
Some(ProjectId(SSH_PROJECT_ID)), Some(ProjectId(SSH_PROJECT_ID)),
cx, cx,
@ -1179,6 +1181,7 @@ impl Project {
GitStore::new( GitStore::new(
&worktree_store, &worktree_store,
buffer_store.clone(), buffer_store.clone(),
None,
client.clone().into(), client.clone().into(),
Some(ProjectId(remote_id)), Some(ProjectId(remote_id)),
cx, cx,

View file

@ -87,10 +87,12 @@ impl HeadlessProject {
buffer_store buffer_store
}); });
let environment = project::ProjectEnvironment::new(&worktree_store, None, cx);
let git_store = cx.new(|cx| { let git_store = cx.new(|cx| {
GitStore::new( GitStore::new(
&worktree_store, &worktree_store,
buffer_store.clone(), buffer_store.clone(),
Some(environment.clone()),
session.clone().into(), session.clone().into(),
None, None,
cx, cx,
@ -105,7 +107,6 @@ impl HeadlessProject {
cx, cx,
) )
}); });
let environment = project::ProjectEnvironment::new(&worktree_store, None, cx);
let toolchain_store = cx.new(|cx| { let toolchain_store = cx.new(|cx| {
ToolchainStore::local( ToolchainStore::local(
languages.clone(), languages.clone(),