diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 732a0d4d1e..9013d33c3c 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -149,7 +149,12 @@ pub trait GitRepository: Send + Sync { /// Also returns `None` for symlinks. fn load_committed_text(&self, path: &RepoPath) -> Option; - fn set_index_text(&self, path: &RepoPath, content: Option) -> anyhow::Result<()>; + fn set_index_text( + &self, + path: &RepoPath, + content: Option, + env: &HashMap, + ) -> anyhow::Result<()>; /// Returns the URL of the remote with the given name. fn remote_url(&self, name: &str) -> Option; @@ -167,8 +172,13 @@ pub trait GitRepository: Send + Sync { fn create_branch(&self, _: &str) -> Result<()>; fn branch_exits(&self, _: &str) -> Result; - fn reset(&self, commit: &str, mode: ResetMode) -> Result<()>; - fn checkout_files(&self, commit: &str, paths: &[RepoPath]) -> Result<()>; + fn reset(&self, commit: &str, mode: ResetMode, env: &HashMap) -> Result<()>; + fn checkout_files( + &self, + commit: &str, + paths: &[RepoPath], + env: &HashMap, + ) -> Result<()>; fn show(&self, commit: &str) -> Result; @@ -189,13 +199,18 @@ 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]) -> Result<()>; + fn stage_paths(&self, paths: &[RepoPath], env: &HashMap) -> 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]) -> Result<()>; + fn unstage_paths(&self, paths: &[RepoPath], env: &HashMap) -> 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, + ) -> Result<()>; fn push( &self, @@ -203,6 +218,7 @@ pub trait GitRepository: Send + Sync { upstream_name: &str, options: Option, askpass: AskPassSession, + env: &HashMap, ) -> Result; fn pull( @@ -210,8 +226,13 @@ pub trait GitRepository: Send + Sync { branch_name: &str, upstream_name: &str, askpass: AskPassSession, + env: &HashMap, + ) -> Result; + fn fetch( + &self, + askpass: AskPassSession, + env: &HashMap, ) -> Result; - fn fetch(&self, askpass: AskPassSession) -> Result; fn get_remotes(&self, branch_name: Option<&str>) -> Result>; @@ -308,7 +329,7 @@ impl GitRepository for RealGitRepository { Ok(details) } - fn reset(&self, commit: &str, mode: ResetMode) -> Result<()> { + fn reset(&self, commit: &str, mode: ResetMode, env: &HashMap) -> Result<()> { let working_directory = self.working_directory()?; let mode_flag = match mode { @@ -317,6 +338,7 @@ impl GitRepository for RealGitRepository { }; let output = new_std_command(&self.git_binary_path) + .envs(env) .current_dir(&working_directory) .args(["reset", mode_flag, commit]) .output()?; @@ -329,7 +351,12 @@ impl GitRepository for RealGitRepository { Ok(()) } - fn checkout_files(&self, commit: &str, paths: &[RepoPath]) -> Result<()> { + fn checkout_files( + &self, + commit: &str, + paths: &[RepoPath], + env: &HashMap, + ) -> Result<()> { if paths.is_empty() { return Ok(()); } @@ -337,6 +364,7 @@ impl GitRepository for RealGitRepository { 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()?; @@ -385,11 +413,17 @@ impl GitRepository for RealGitRepository { Some(content) } - fn set_index_text(&self, path: &RepoPath, content: Option) -> anyhow::Result<()> { + fn set_index_text( + &self, + path: &RepoPath, + content: Option, + env: &HashMap, + ) -> 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()) @@ -402,6 +436,7 @@ impl GitRepository for RealGitRepository { 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()?; @@ -415,6 +450,7 @@ impl GitRepository for RealGitRepository { } 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()?; @@ -607,12 +643,13 @@ impl GitRepository for RealGitRepository { Ok(String::from_utf8_lossy(&output.stdout).to_string()) } - fn stage_paths(&self, paths: &[RepoPath]) -> Result<()> { + fn stage_paths(&self, paths: &[RepoPath], env: &HashMap) -> Result<()> { let working_directory = self.working_directory()?; 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()?; @@ -627,12 +664,13 @@ impl GitRepository for RealGitRepository { Ok(()) } - fn unstage_paths(&self, paths: &[RepoPath]) -> Result<()> { + fn unstage_paths(&self, paths: &[RepoPath], env: &HashMap) -> Result<()> { let working_directory = self.working_directory()?; 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()?; @@ -647,11 +685,17 @@ impl GitRepository for RealGitRepository { 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, + ) -> 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"); @@ -677,11 +721,13 @@ impl GitRepository for RealGitRepository { remote_name: &str, options: Option, ask_pass: AskPassSession, + env: &HashMap, ) -> Result { 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") @@ -705,11 +751,13 @@ impl GitRepository for RealGitRepository { branch_name: &str, remote_name: &str, ask_pass: AskPassSession, + env: &HashMap, ) -> Result { 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") @@ -724,11 +772,16 @@ impl GitRepository for RealGitRepository { run_remote_command(ask_pass, git_process) } - fn fetch(&self, ask_pass: AskPassSession) -> Result { + fn fetch( + &self, + ask_pass: AskPassSession, + env: &HashMap, + ) -> Result { 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") @@ -919,7 +972,12 @@ impl GitRepository for FakeGitRepository { state.head_contents.get(path.as_ref()).cloned() } - fn set_index_text(&self, path: &RepoPath, content: Option) -> anyhow::Result<()> { + fn set_index_text( + &self, + path: &RepoPath, + content: Option, + _env: &HashMap, + ) -> anyhow::Result<()> { let mut state = self.state.lock(); if let Some(message) = state.simulated_index_write_error_message.clone() { return Err(anyhow::anyhow!(message)); @@ -952,11 +1010,11 @@ impl GitRepository for FakeGitRepository { unimplemented!() } - fn reset(&self, _: &str, _: ResetMode) -> Result<()> { + fn reset(&self, _: &str, _: ResetMode, _: &HashMap) -> Result<()> { unimplemented!() } - fn checkout_files(&self, _: &str, _: &[RepoPath]) -> Result<()> { + fn checkout_files(&self, _: &str, _: &[RepoPath], _: &HashMap) -> Result<()> { unimplemented!() } @@ -1042,15 +1100,20 @@ impl GitRepository for FakeGitRepository { .cloned() } - fn stage_paths(&self, _paths: &[RepoPath]) -> Result<()> { + fn stage_paths(&self, _paths: &[RepoPath], _env: &HashMap) -> Result<()> { unimplemented!() } - fn unstage_paths(&self, _paths: &[RepoPath]) -> Result<()> { + fn unstage_paths(&self, _paths: &[RepoPath], _env: &HashMap) -> Result<()> { 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, + ) -> Result<()> { unimplemented!() } @@ -1060,6 +1123,7 @@ impl GitRepository for FakeGitRepository { _remote: &str, _options: Option, _ask_pass: AskPassSession, + _env: &HashMap, ) -> Result { unimplemented!() } @@ -1069,11 +1133,16 @@ impl GitRepository for FakeGitRepository { _branch: &str, _remote: &str, _ask_pass: AskPassSession, + _env: &HashMap, ) -> Result { unimplemented!() } - fn fetch(&self, _ask_pass: AskPassSession) -> Result { + fn fetch( + &self, + _ask_pass: AskPassSession, + _env: &HashMap, + ) -> Result { unimplemented!() } diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 4274a17682..e813614a6a 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -901,13 +901,14 @@ impl GitPanel { let buffers = futures::future::join_all(tasks).await; active_repository - .update(&mut cx, |repo, _| { + .update(&mut cx, |repo, cx| { repo.checkout_files( "HEAD", entries .iter() .map(|entries| entries.repo_path.clone()) .collect(), + cx, ) })? .await??; @@ -1289,7 +1290,8 @@ impl GitPanel { let task = if self.has_staged_changes() { // 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? }) } else { let changed_files = self @@ -1310,7 +1312,7 @@ impl GitPanel { cx.spawn(|_, mut cx| async move { stage_task.await?; 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? }) }; @@ -1346,7 +1348,7 @@ impl GitPanel { if let Ok(true) = confirmation.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??; Ok(Some(prior_head)) diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 7b53506ae4..60b59079a9 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -178,6 +178,7 @@ impl EntityMap { } } +#[track_caller] fn double_lease_panic(operation: &str) -> ! { panic!( "cannot {operation} {} while it is already being updated", diff --git a/crates/project/src/git.rs b/crates/project/src/git.rs index 374ec74c81..6e24caa8b5 100644 --- a/crates/project/src/git.rs +++ b/crates/project/src/git.rs @@ -1,7 +1,7 @@ use crate::{ buffer_store::{BufferStore, BufferStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent}, - Project, ProjectItem, ProjectPath, + Project, ProjectEnvironment, ProjectItem, ProjectPath, }; use anyhow::{Context as _, Result}; use askpass::{AskPassDelegate, AskPassSession}; @@ -10,6 +10,7 @@ use client::ProjectId; use collections::HashMap; use futures::{ channel::{mpsc, oneshot}, + future::OptionFuture, StreamExt as _, }; use git::repository::DiffType; @@ -43,6 +44,7 @@ use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory}; pub struct GitStore { buffer_store: Entity, + environment: Option>, pub(super) project_id: Option, pub(super) client: AnyProtoClient, repositories: Vec>, @@ -54,6 +56,7 @@ pub struct GitStore { pub struct Repository { commit_message_buffer: Option>, git_store: WeakEntity, + project_environment: Option>, pub worktree_id: WorktreeId, pub repository_entry: RepositoryEntry, pub dot_git_abs_path: PathBuf, @@ -101,6 +104,7 @@ impl GitStore { pub fn new( worktree_store: &Entity, buffer_store: Entity, + environment: Option>, client: AnyProtoClient, project_id: Option, cx: &mut Context<'_, Self>, @@ -115,6 +119,7 @@ impl GitStore { project_id, client, buffer_store, + environment, repositories: Vec::new(), active_index: None, update_sender, @@ -225,6 +230,10 @@ impl GitStore { existing_handle } else { cx.new(|_| Repository { + project_environment: self + .environment + .as_ref() + .map(|env| env.downgrade()), git_store: this.clone(), worktree_id, askpass_delegates: Default::default(), @@ -282,9 +291,13 @@ impl GitStore { if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event { let buffer_id = diff.read(cx).buffer_id; if let Some((repo, path)) = this.repository_and_path_for_buffer_id(buffer_id, cx) { - let recv = repo - .read(cx) - .set_index_text(&path, new_index_text.as_ref().map(|rope| rope.to_string())); + let recv = repo.update(cx, |repo, cx| { + repo.set_index_text( + &path, + new_index_text.as_ref().map(|rope| rope.to_string()), + cx, + ) + }); let diff = diff.downgrade(); cx.spawn(|this, mut cx| async move { 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)?; repository_handle - .update(&mut cx, |repository_handle, _| { + .update(&mut cx, |repository_handle, cx| { repository_handle.set_index_text( &RepoPath::from_str(&envelope.payload.path), envelope.payload.text, + cx, ) })? .await??; @@ -567,8 +581,8 @@ impl GitStore { let email = envelope.payload.email.map(SharedString::from); repository_handle - .update(&mut cx, |repository_handle, _| { - repository_handle.commit(message, name.zip(email)) + .update(&mut cx, |repository_handle, cx| { + repository_handle.commit(message, name.zip(email), cx) })? .await??; Ok(proto::Ack {}) @@ -703,8 +717,8 @@ impl GitStore { }; repository_handle - .update(&mut cx, |repository_handle, _| { - repository_handle.reset(&envelope.payload.commit, mode) + .update(&mut cx, |repository_handle, cx| { + repository_handle.reset(&envelope.payload.commit, mode, cx) })? .await??; Ok(proto::Ack {}) @@ -727,8 +741,8 @@ impl GitStore { .collect(); repository_handle - .update(&mut cx, |repository_handle, _| { - repository_handle.checkout_files(&envelope.payload.commit, paths) + .update(&mut cx, |repository_handle, cx| { + repository_handle.checkout_files(&envelope.payload.commit, paths, cx) })? .await??; Ok(proto::Ack {}) @@ -1115,11 +1129,14 @@ impl Repository { &self, commit: &str, paths: Vec, + cx: &mut App, ) -> oneshot::Receiver> { let commit = commit.to_string(); + let env = self.worktree_environment(cx); + self.send_job(|git_repo| async move { match git_repo { - GitRepo::Local(repo) => repo.checkout_files(&commit, &paths), + GitRepo::Local(repo) => repo.checkout_files(&commit, &paths, &env.await), GitRepo::Remote { project_id, client, @@ -1145,11 +1162,20 @@ impl Repository { }) } - pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver> { + pub fn reset( + &self, + commit: &str, + reset_mode: ResetMode, + cx: &mut App, + ) -> oneshot::Receiver> { let commit = commit.to_string(); + let env = self.worktree_environment(cx); self.send_job(|git_repo| async move { 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 { project_id, client, @@ -1219,6 +1245,7 @@ impl Repository { if entries.is_empty() { return Task::ready(Ok(())); } + let env = self.worktree_environment(cx); let mut save_futures = Vec::new(); if let Some(buffer_store) = self.buffer_store(cx) { @@ -1245,11 +1272,12 @@ impl Repository { for save_future in save_futures { save_future.await?; } + let env = env.await; this.update(&mut cx, |this, _| { this.send_job(|git_repo| async move { match git_repo { - GitRepo::Local(repo) => repo.stage_paths(&entries), + GitRepo::Local(repo) => repo.stage_paths(&entries, &env), GitRepo::Remote { project_id, client, @@ -1288,6 +1316,7 @@ impl Repository { if entries.is_empty() { return Task::ready(Ok(())); } + let env = self.worktree_environment(cx); let mut save_futures = Vec::new(); if let Some(buffer_store) = self.buffer_store(cx) { @@ -1314,11 +1343,12 @@ impl Repository { for save_future in save_futures { save_future.await?; } + let env = env.await; this.update(&mut cx, |this, _| { this.send_job(|git_repo| async move { match git_repo { - GitRepo::Local(repo) => repo.unstage_paths(&entries), + GitRepo::Local(repo) => repo.unstage_paths(&entries, &env), GitRepo::Remote { project_id, client, @@ -1375,19 +1405,42 @@ impl Repository { self.repository_entry.status_len() } + fn worktree_environment( + &self, + cx: &mut App, + ) -> impl Future> + '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( &self, message: SharedString, name_and_email: Option<(SharedString, SharedString)>, + cx: &mut App, ) -> oneshot::Receiver> { + let env = self.worktree_environment(cx); self.send_job(|git_repo| async move { match git_repo { - GitRepo::Local(repo) => repo.commit( - message.as_ref(), - name_and_email - .as_ref() - .map(|(name, email)| (name.as_ref(), email.as_ref())), - ), + 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, + ) + } GitRepo::Remote { project_id, client, @@ -1416,17 +1469,19 @@ impl Repository { pub fn fetch( &mut self, askpass: AskPassDelegate, - cx: &App, + cx: &mut App, ) -> oneshot::Receiver> { let executor = cx.background_executor().clone(); let askpass_delegates = self.askpass_delegates.clone(); 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 { match git_repo { GitRepo::Local(git_repository) => { let askpass = AskPassSession::new(&executor, askpass).await?; - git_repository.fetch(askpass) + let env = env.await; + git_repository.fetch(askpass, &env) } GitRepo::Remote { project_id, @@ -1465,17 +1520,19 @@ impl Repository { remote: SharedString, options: Option, askpass: AskPassDelegate, - cx: &App, + cx: &mut App, ) -> oneshot::Receiver> { let executor = cx.background_executor().clone(); let askpass_delegates = self.askpass_delegates.clone(); 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 { 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) + git_repository.push(&branch, &remote, options, askpass, &env) } GitRepo::Remote { project_id, @@ -1518,16 +1575,19 @@ impl Repository { branch: SharedString, remote: SharedString, askpass: AskPassDelegate, - cx: &App, + cx: &mut App, ) -> oneshot::Receiver> { let executor = cx.background_executor().clone(); let askpass_delegates = self.askpass_delegates.clone(); 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 { match git_repo { GitRepo::Local(git_repository) => { 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 { project_id, @@ -1565,13 +1625,16 @@ impl Repository { &self, path: &RepoPath, content: Option, + cx: &mut App, ) -> oneshot::Receiver> { let path = path.clone(); + let env = self.worktree_environment(cx); + self.send_keyed_job( Some(GitJobKey::WriteIndex(path.clone())), |git_repo| async move { 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 { project_id, client, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 42d9986349..ec8e66fccc 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -844,6 +844,7 @@ impl Project { GitStore::new( &worktree_store, buffer_store.clone(), + Some(environment.clone()), client.clone().into(), None, cx, @@ -972,6 +973,7 @@ impl Project { GitStore::new( &worktree_store, buffer_store.clone(), + Some(environment.clone()), ssh_proto.clone(), Some(ProjectId(SSH_PROJECT_ID)), cx, @@ -1179,6 +1181,7 @@ impl Project { GitStore::new( &worktree_store, buffer_store.clone(), + None, client.clone().into(), Some(ProjectId(remote_id)), cx, diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index bfd4f52fff..0617c195f6 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -87,10 +87,12 @@ impl HeadlessProject { buffer_store }); + let environment = project::ProjectEnvironment::new(&worktree_store, None, cx); let git_store = cx.new(|cx| { GitStore::new( &worktree_store, buffer_store.clone(), + Some(environment.clone()), session.clone().into(), None, cx, @@ -105,7 +107,6 @@ impl HeadlessProject { cx, ) }); - let environment = project::ProjectEnvironment::new(&worktree_store, None, cx); let toolchain_store = cx.new(|cx| { ToolchainStore::local( languages.clone(),