use crate::{ buffer_store::{BufferStore, BufferStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent}, Project, ProjectEnvironment, ProjectItem, ProjectPath,};use anyhow::{Context as _, Result};use askpass::{AskPassDelegate, AskPassSession};use buffer_diff::BufferDiffEvent;use client::ProjectId;use collections::HashMap;use futures::{ channel::{mpsc, oneshot}, future::OptionFuture, StreamExt as _,};use git::repository::DiffType;use git::{ repository::{ Branch, CommitDetails, GitRepository, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode, }, status::FileStatus,};use gpui::{ App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task, WeakEntity,};use language::{Buffer, LanguageRegistry};use parking_lot::Mutex;use rpc::{ proto::{self, git_reset, ToProto}, AnyProtoClient, TypedEnvelope,};use settings::WorktreeId;use std::{ collections::VecDeque, future::Future, path::{Path, PathBuf}, sync::Arc,};use text::BufferId;use util::{debug_panic, maybe, ResultExt};use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory}; pub struct GitStore { buffer_store: Entity<BufferStore>, environment: Option<Entity<ProjectEnvironment>>, pub(super) project_id: Option<ProjectId>, pub(super) client: AnyProtoClient, repositories: Vec<Entity<Repository>>, active_index: Option<usize>, update_sender: mpsc::UnboundedSender<GitJob>, _subscriptions: [Subscription; 2],} pub struct Repository { commit_message_buffer: Option<Entity<Buffer>>, git_store: WeakEntity<GitStore>, project_environment: Option<WeakEntity<ProjectEnvironment>>, pub worktree_id: WorktreeId, pub repository_entry: RepositoryEntry, pub dot_git_abs_path: PathBuf, pub worktree_abs_path: Arc<Path>, pub is_from_single_file_worktree: bool, pub git_repo: GitRepo, pub merge_message: Option<String>, job_sender: mpsc::UnboundedSender<GitJob>, askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>, latest_askpass_id: u64,} #[derive(Clone)]pub enum GitRepo { Local(Arc<dyn GitRepository>), Remote { project_id: ProjectId, client: AnyProtoClient, worktree_id: WorktreeId, work_directory_id: ProjectEntryId, },} #[derive(Debug)]pub enum GitEvent { ActiveRepositoryChanged, FileSystemUpdated, GitStateUpdated, IndexWriteError(anyhow::Error),} struct GitJob { job: Box<dyn FnOnce(&mut AsyncApp) -> Task<()>>, key: Option<GitJobKey>,} #[derive(PartialEq, Eq)]enum GitJobKey { WriteIndex(RepoPath),} impl EventEmitter<GitEvent> for GitStore {} impl GitStore { pub fn new( worktree_store: &Entity<WorktreeStore>, buffer_store: Entity<BufferStore>, environment: Option<Entity<ProjectEnvironment>>, client: AnyProtoClient, project_id: Option<ProjectId>, cx: &mut Context<'_, Self>, ) -> Self { let update_sender = Self::spawn_git_worker(cx); let _subscriptions = [ cx.subscribe(worktree_store, Self::on_worktree_store_event), cx.subscribe(&buffer_store, Self::on_buffer_store_event), ]; GitStore { project_id, client, buffer_store, environment, repositories: Vec::new(), active_index: None, update_sender, _subscriptions, } } pub fn init(client: &AnyProtoClient) { client.add_entity_request_handler(Self::handle_get_remotes); client.add_entity_request_handler(Self::handle_get_branches); client.add_entity_request_handler(Self::handle_change_branch); client.add_entity_request_handler(Self::handle_create_branch); client.add_entity_request_handler(Self::handle_push); client.add_entity_request_handler(Self::handle_pull); client.add_entity_request_handler(Self::handle_fetch); client.add_entity_request_handler(Self::handle_stage); client.add_entity_request_handler(Self::handle_unstage); client.add_entity_request_handler(Self::handle_commit); client.add_entity_request_handler(Self::handle_reset); client.add_entity_request_handler(Self::handle_show); client.add_entity_request_handler(Self::handle_checkout_files); client.add_entity_request_handler(Self::handle_open_commit_message_buffer); client.add_entity_request_handler(Self::handle_set_index_text); client.add_entity_request_handler(Self::handle_askpass); client.add_entity_request_handler(Self::handle_check_for_pushed_commits); client.add_entity_request_handler(Self::handle_git_diff); } pub fn active_repository(&self) -> Option<Entity<Repository>> { self.active_index .map(|index| self.repositories[index].clone()) } fn on_worktree_store_event( &mut self, worktree_store: Entity<WorktreeStore>, event: &WorktreeStoreEvent, cx: &mut Context<'_, Self>, ) { let mut new_repositories = Vec::new(); let mut new_active_index = None; let this = cx.weak_entity(); let client = self.client.clone(); let project_id = self.project_id; worktree_store.update(cx, |worktree_store, cx| { for worktree in worktree_store.worktrees() { worktree.update(cx, |worktree, cx| { let snapshot = worktree.snapshot(); for repo in snapshot.repositories().iter() { let git_data = worktree .as_local() .and_then(|local_worktree| local_worktree.get_local_repo(repo)) .map(|local_repo| { ( GitRepo::Local(local_repo.repo().clone()), local_repo.merge_message.clone(), ) }) .or_else(|| { let client = client.clone(); let project_id = project_id?; Some(( GitRepo::Remote { project_id, client, worktree_id: worktree.id(), work_directory_id: repo.work_directory_id(), }, None, )) }); let Some((git_repo, merge_message)) = git_data else { continue; }; let worktree_id = worktree.id(); let existing = self.repositories .iter() .enumerate() .find(|(_, existing_handle)| { existing_handle.read(cx).id() == (worktree_id, repo.work_directory_id()) }); let handle = if let Some((index, handle)) = existing { if self.active_index == Some(index) { new_active_index = Some(new_repositories.len()); } // Update the statuses and merge message but keep everything else. let existing_handle = handle.clone(); existing_handle.update(cx, |existing_handle, cx| { existing_handle.repository_entry = repo.clone(); if matches!(git_repo, GitRepo::Local { .. }) && existing_handle.merge_message != merge_message { if let (Some(merge_message), Some(buffer)) = (&merge_message, &existing_handle.commit_message_buffer) { buffer.update(cx, |buffer, cx| { if buffer.is_empty() { buffer.set_text(merge_message.as_str(), cx); } }) } existing_handle.merge_message = merge_message; } }); 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(), latest_askpass_id: 0, repository_entry: repo.clone(), dot_git_abs_path: worktree.dot_git_abs_path(&repo.work_directory), worktree_abs_path: worktree.abs_path(), is_from_single_file_worktree: worktree.is_single_file(), git_repo, job_sender: self.update_sender.clone(), merge_message, commit_message_buffer: None, }) }; new_repositories.push(handle); } }) } }); if new_active_index == None && new_repositories.len() > 0 { new_active_index = Some(0); } self.repositories = new_repositories; self.active_index = new_active_index; match event { WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => { cx.emit(GitEvent::GitStateUpdated); } _ => { cx.emit(GitEvent::FileSystemUpdated); } } } fn on_buffer_store_event( &mut self, _: Entity<BufferStore>, event: &BufferStoreEvent, cx: &mut Context<'_, Self>, ) { if let BufferStoreEvent::BufferDiffAdded(diff) = event { cx.subscribe(diff, Self::on_buffer_diff_event).detach(); } } fn on_buffer_diff_event( this: &mut GitStore, diff: Entity<buffer_diff::BufferDiff>, event: &BufferDiffEvent, cx: &mut Context<'_, 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.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 { if let Err(error) = result { diff.update(&mut cx, |diff, cx| { diff.clear_pending_hunks(cx); }) .ok(); this.update(&mut cx, |_, cx| cx.emit(GitEvent::IndexWriteError(error))) .ok(); } } }) .detach(); } } } pub fn all_repositories(&self) -> Vec<Entity<Repository>> { self.repositories.clone() } pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> { let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?; let status = repo.read(cx).repository_entry.status_for_path(&path)?; Some(status.status) } fn repository_and_path_for_buffer_id( &self, buffer_id: BufferId, cx: &App, ) -> Option<(Entity<Repository>, RepoPath)> { let buffer = self.buffer_store.read(cx).get(buffer_id)?; let path = buffer.read(cx).project_path(cx)?; let mut result: Option<(Entity<Repository>, RepoPath)> = None; for repo_handle in &self.repositories { let repo = repo_handle.read(cx); if repo.worktree_id == path.worktree_id { if let Ok(relative_path) = repo.repository_entry.relativize(&path.path) { if result .as_ref() .is_none_or(|(result, _)| !repo.contains_sub_repo(result, cx)) { result = Some((repo_handle.clone(), relative_path)) } } } } result } fn spawn_git_worker(cx: &mut Context<'_, GitStore>) -> mpsc::UnboundedSender<GitJob> { let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>(); cx.spawn(|_, mut cx| async move { let mut jobs = VecDeque::new(); loop { while let Ok(Some(next_job)) = job_rx.try_next() { jobs.push_back(next_job); } if let Some(job) = jobs.pop_front() { if let Some(current_key) = &job.key { if jobs .iter() .any(|other_job| other_job.key.as_ref() == Some(current_key)) { continue; } } (job.job)(&mut cx).await; } else if let Some(job) = job_rx.next().await { jobs.push_back(job); } else { break; } } }) .detach(); job_tx } async fn handle_fetch( this: Entity<Self>, envelope: TypedEnvelope<proto::Fetch>, mut cx: AsyncApp, ) -> Result<proto::RemoteMessageResponse> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let askpass_id = envelope.payload.askpass_id; let askpass = make_remote_delegate( this, envelope.payload.project_id, worktree_id, work_directory_id, askpass_id, &mut cx, ); let remote_output = repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.fetch(askpass, cx) })? .await??; Ok(proto::RemoteMessageResponse { stdout: remote_output.stdout, stderr: remote_output.stderr, }) } async fn handle_push( this: Entity<Self>, envelope: TypedEnvelope<proto::Push>, mut cx: AsyncApp, ) -> Result<proto::RemoteMessageResponse> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let askpass_id = envelope.payload.askpass_id; let askpass = make_remote_delegate( this, envelope.payload.project_id, worktree_id, work_directory_id, askpass_id, &mut cx, ); let options = envelope .payload .options .as_ref() .map(|_| match envelope.payload.options() { proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream, proto::push::PushOptions::Force => git::repository::PushOptions::Force, }); let branch_name = envelope.payload.branch_name.into(); let remote_name = envelope.payload.remote_name.into(); let remote_output = repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.push(branch_name, remote_name, options, askpass, cx) })? .await??; Ok(proto::RemoteMessageResponse { stdout: remote_output.stdout, stderr: remote_output.stderr, }) } async fn handle_pull( this: Entity<Self>, envelope: TypedEnvelope<proto::Pull>, mut cx: AsyncApp, ) -> Result<proto::RemoteMessageResponse> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let askpass_id = envelope.payload.askpass_id; let askpass = make_remote_delegate( this, envelope.payload.project_id, worktree_id, work_directory_id, askpass_id, &mut cx, ); let branch_name = envelope.payload.branch_name.into(); let remote_name = envelope.payload.remote_name.into(); let remote_message = repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.pull(branch_name, remote_name, askpass, cx) })? .await??; Ok(proto::RemoteMessageResponse { stdout: remote_message.stdout, stderr: remote_message.stderr, }) } async fn handle_stage( this: Entity<Self>, envelope: TypedEnvelope<proto::Stage>, mut cx: AsyncApp, ) -> Result<proto::Ack> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);Lines 1–500 of 1838. Load more
use crate::{ buffer_store::{BufferStore, BufferStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent}, Project, ProjectEnvironment, ProjectItem, ProjectPath, }; use anyhow::{Context as _, Result}; use askpass::{AskPassDelegate, AskPassSession}; use buffer_diff::BufferDiffEvent; use client::ProjectId; use collections::HashMap; use futures::{ channel::{mpsc, oneshot}, future::OptionFuture, StreamExt as _, }; use git::repository::DiffType; use git::{ repository::{ Branch, CommitDetails, GitRepository, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode, }, status::FileStatus, }; use gpui::{ App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task, WeakEntity, }; use language::{Buffer, LanguageRegistry}; use parking_lot::Mutex; use rpc::{ proto::{self, git_reset, ToProto}, AnyProtoClient, TypedEnvelope, }; use settings::WorktreeId; use std::{ collections::VecDeque, future::Future, path::{Path, PathBuf}, sync::Arc, }; use text::BufferId; use util::{debug_panic, maybe, ResultExt}; use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory}; pub struct GitStore { buffer_store: Entity<BufferStore>, environment: Option<Entity<ProjectEnvironment>>, pub(super) project_id: Option<ProjectId>, pub(super) client: AnyProtoClient, repositories: Vec<Entity<Repository>>, active_index: Option<usize>, update_sender: mpsc::UnboundedSender<GitJob>, _subscriptions: [Subscription; 2], } pub struct Repository { commit_message_buffer: Option<Entity<Buffer>>, git_store: WeakEntity<GitStore>, project_environment: Option<WeakEntity<ProjectEnvironment>>, pub worktree_id: WorktreeId, pub repository_entry: RepositoryEntry, pub dot_git_abs_path: PathBuf, pub worktree_abs_path: Arc<Path>, pub is_from_single_file_worktree: bool, pub git_repo: GitRepo, pub merge_message: Option<String>, job_sender: mpsc::UnboundedSender<GitJob>, askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>, latest_askpass_id: u64, } #[derive(Clone)] pub enum GitRepo { Local(Arc<dyn GitRepository>), Remote { project_id: ProjectId, client: AnyProtoClient, worktree_id: WorktreeId, work_directory_id: ProjectEntryId, }, } #[derive(Debug)] pub enum GitEvent { ActiveRepositoryChanged, FileSystemUpdated, GitStateUpdated, IndexWriteError(anyhow::Error), } struct GitJob { job: Box<dyn FnOnce(&mut AsyncApp) -> Task<()>>, key: Option<GitJobKey>, } #[derive(PartialEq, Eq)] enum GitJobKey { WriteIndex(RepoPath), } impl EventEmitter<GitEvent> for GitStore {} impl GitStore { pub fn new( worktree_store: &Entity<WorktreeStore>, buffer_store: Entity<BufferStore>, environment: Option<Entity<ProjectEnvironment>>, client: AnyProtoClient, project_id: Option<ProjectId>, cx: &mut Context<'_, Self>, ) -> Self { let update_sender = Self::spawn_git_worker(cx); let _subscriptions = [ cx.subscribe(worktree_store, Self::on_worktree_store_event), cx.subscribe(&buffer_store, Self::on_buffer_store_event), ]; GitStore { project_id, client, buffer_store, environment, repositories: Vec::new(), active_index: None, update_sender, _subscriptions, } } pub fn init(client: &AnyProtoClient) { client.add_entity_request_handler(Self::handle_get_remotes); client.add_entity_request_handler(Self::handle_get_branches); client.add_entity_request_handler(Self::handle_change_branch); client.add_entity_request_handler(Self::handle_create_branch); client.add_entity_request_handler(Self::handle_push); client.add_entity_request_handler(Self::handle_pull); client.add_entity_request_handler(Self::handle_fetch); client.add_entity_request_handler(Self::handle_stage); client.add_entity_request_handler(Self::handle_unstage); client.add_entity_request_handler(Self::handle_commit); client.add_entity_request_handler(Self::handle_reset); client.add_entity_request_handler(Self::handle_show); client.add_entity_request_handler(Self::handle_checkout_files); client.add_entity_request_handler(Self::handle_open_commit_message_buffer); client.add_entity_request_handler(Self::handle_set_index_text); client.add_entity_request_handler(Self::handle_askpass); client.add_entity_request_handler(Self::handle_check_for_pushed_commits); client.add_entity_request_handler(Self::handle_git_diff); } pub fn active_repository(&self) -> Option<Entity<Repository>> { self.active_index .map(|index| self.repositories[index].clone()) } fn on_worktree_store_event( &mut self, worktree_store: Entity<WorktreeStore>, event: &WorktreeStoreEvent, cx: &mut Context<'_, Self>, ) { let mut new_repositories = Vec::new(); let mut new_active_index = None; let this = cx.weak_entity(); let client = self.client.clone(); let project_id = self.project_id; worktree_store.update(cx, |worktree_store, cx| { for worktree in worktree_store.worktrees() { worktree.update(cx, |worktree, cx| { let snapshot = worktree.snapshot(); for repo in snapshot.repositories().iter() { let git_data = worktree .as_local() .and_then(|local_worktree| local_worktree.get_local_repo(repo)) .map(|local_repo| { ( GitRepo::Local(local_repo.repo().clone()), local_repo.merge_message.clone(), ) }) .or_else(|| { let client = client.clone(); let project_id = project_id?; Some(( GitRepo::Remote { project_id, client, worktree_id: worktree.id(), work_directory_id: repo.work_directory_id(), }, None, )) }); let Some((git_repo, merge_message)) = git_data else { continue; }; let worktree_id = worktree.id(); let existing = self.repositories .iter() .enumerate() .find(|(_, existing_handle)| { existing_handle.read(cx).id() == (worktree_id, repo.work_directory_id()) }); let handle = if let Some((index, handle)) = existing { if self.active_index == Some(index) { new_active_index = Some(new_repositories.len()); } // Update the statuses and merge message but keep everything else. let existing_handle = handle.clone(); existing_handle.update(cx, |existing_handle, cx| { existing_handle.repository_entry = repo.clone(); if matches!(git_repo, GitRepo::Local { .. }) && existing_handle.merge_message != merge_message { if let (Some(merge_message), Some(buffer)) = (&merge_message, &existing_handle.commit_message_buffer) { buffer.update(cx, |buffer, cx| { if buffer.is_empty() { buffer.set_text(merge_message.as_str(), cx); } }) } existing_handle.merge_message = merge_message; } }); 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(), latest_askpass_id: 0, repository_entry: repo.clone(), dot_git_abs_path: worktree.dot_git_abs_path(&repo.work_directory), worktree_abs_path: worktree.abs_path(), is_from_single_file_worktree: worktree.is_single_file(), git_repo, job_sender: self.update_sender.clone(), merge_message, commit_message_buffer: None, }) }; new_repositories.push(handle); } }) } }); if new_active_index == None && new_repositories.len() > 0 { new_active_index = Some(0); } self.repositories = new_repositories; self.active_index = new_active_index; match event { WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => { cx.emit(GitEvent::GitStateUpdated); } _ => { cx.emit(GitEvent::FileSystemUpdated); } } } fn on_buffer_store_event( &mut self, _: Entity<BufferStore>, event: &BufferStoreEvent, cx: &mut Context<'_, Self>, ) { if let BufferStoreEvent::BufferDiffAdded(diff) = event { cx.subscribe(diff, Self::on_buffer_diff_event).detach(); } } fn on_buffer_diff_event( this: &mut GitStore, diff: Entity<buffer_diff::BufferDiff>, event: &BufferDiffEvent, cx: &mut Context<'_, 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.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 { if let Err(error) = result { diff.update(&mut cx, |diff, cx| { diff.clear_pending_hunks(cx); }) .ok(); this.update(&mut cx, |_, cx| cx.emit(GitEvent::IndexWriteError(error))) .ok(); } } }) .detach(); } } } pub fn all_repositories(&self) -> Vec<Entity<Repository>> { self.repositories.clone() } pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> { let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?; let status = repo.read(cx).repository_entry.status_for_path(&path)?; Some(status.status) } fn repository_and_path_for_buffer_id( &self, buffer_id: BufferId, cx: &App, ) -> Option<(Entity<Repository>, RepoPath)> { let buffer = self.buffer_store.read(cx).get(buffer_id)?; let path = buffer.read(cx).project_path(cx)?; let mut result: Option<(Entity<Repository>, RepoPath)> = None; for repo_handle in &self.repositories { let repo = repo_handle.read(cx); if repo.worktree_id == path.worktree_id { if let Ok(relative_path) = repo.repository_entry.relativize(&path.path) { if result .as_ref() .is_none_or(|(result, _)| !repo.contains_sub_repo(result, cx)) { result = Some((repo_handle.clone(), relative_path)) } } } } result } fn spawn_git_worker(cx: &mut Context<'_, GitStore>) -> mpsc::UnboundedSender<GitJob> { let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>(); cx.spawn(|_, mut cx| async move { let mut jobs = VecDeque::new(); loop { while let Ok(Some(next_job)) = job_rx.try_next() { jobs.push_back(next_job); } if let Some(job) = jobs.pop_front() { if let Some(current_key) = &job.key { if jobs .iter() .any(|other_job| other_job.key.as_ref() == Some(current_key)) { continue; } } (job.job)(&mut cx).await; } else if let Some(job) = job_rx.next().await { jobs.push_back(job); } else { break; } } }) .detach(); job_tx } async fn handle_fetch( this: Entity<Self>, envelope: TypedEnvelope<proto::Fetch>, mut cx: AsyncApp, ) -> Result<proto::RemoteMessageResponse> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let askpass_id = envelope.payload.askpass_id; let askpass = make_remote_delegate( this, envelope.payload.project_id, worktree_id, work_directory_id, askpass_id, &mut cx, ); let remote_output = repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.fetch(askpass, cx) })? .await??; Ok(proto::RemoteMessageResponse { stdout: remote_output.stdout, stderr: remote_output.stderr, }) } async fn handle_push( this: Entity<Self>, envelope: TypedEnvelope<proto::Push>, mut cx: AsyncApp, ) -> Result<proto::RemoteMessageResponse> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let askpass_id = envelope.payload.askpass_id; let askpass = make_remote_delegate( this, envelope.payload.project_id, worktree_id, work_directory_id, askpass_id, &mut cx, ); let options = envelope .payload .options .as_ref() .map(|_| match envelope.payload.options() { proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream, proto::push::PushOptions::Force => git::repository::PushOptions::Force, }); let branch_name = envelope.payload.branch_name.into(); let remote_name = envelope.payload.remote_name.into(); let remote_output = repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.push(branch_name, remote_name, options, askpass, cx) })? .await??; Ok(proto::RemoteMessageResponse { stdout: remote_output.stdout, stderr: remote_output.stderr, }) } async fn handle_pull( this: Entity<Self>, envelope: TypedEnvelope<proto::Pull>, mut cx: AsyncApp, ) -> Result<proto::RemoteMessageResponse> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let askpass_id = envelope.payload.askpass_id; let askpass = make_remote_delegate( this, envelope.payload.project_id, worktree_id, work_directory_id, askpass_id, &mut cx, ); let branch_name = envelope.payload.branch_name.into(); let remote_name = envelope.payload.remote_name.into(); let remote_message = repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.pull(branch_name, remote_name, askpass, cx) })? .await??; Ok(proto::RemoteMessageResponse { stdout: remote_message.stdout, stderr: remote_message.stderr, }) } async fn handle_stage( this: Entity<Self>, envelope: TypedEnvelope<proto::Stage>, mut cx: AsyncApp, ) -> Result<proto::Ack> { let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);