use crate::buffer_store::BufferStore;use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent};use crate::{Project, ProjectPath};use anyhow::{Context as _, Result};use client::ProjectId;use futures::channel::{mpsc, oneshot};use futures::StreamExt as _;use git::repository::{Branch, CommitDetails, ResetMode};use git::{ repository::{GitRepository, RepoPath}, status::{GitSummary, TrackedSummary},};use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task, WeakEntity,};use language::{Buffer, LanguageRegistry};use rpc::proto::{git_reset, ToProto};use rpc::{proto, AnyProtoClient, TypedEnvelope};use settings::WorktreeId;use std::path::{Path, PathBuf};use std::sync::Arc;use text::BufferId;use util::{maybe, ResultExt};use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry}; pub struct GitStore { buffer_store: Entity<BufferStore>, pub(super) project_id: Option<ProjectId>, pub(super) client: Option<AnyProtoClient>, repositories: Vec<Entity<Repository>>, active_index: Option<usize>, update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>, _subscription: Subscription,} pub struct Repository { commit_message_buffer: Option<Entity<Buffer>>, git_store: WeakEntity<GitStore>, pub worktree_id: WorktreeId, pub repository_entry: RepositoryEntry, pub git_repo: GitRepo, pub merge_message: Option<String>, update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>,} #[derive(Clone)]pub enum GitRepo { Local(Arc<dyn GitRepository>), Remote { project_id: ProjectId, client: AnyProtoClient, worktree_id: WorktreeId, work_directory_id: ProjectEntryId, },} pub enum Message { Commit { git_repo: GitRepo, message: SharedString, name_and_email: Option<(SharedString, SharedString)>, }, Reset { repo: GitRepo, commit: SharedString, reset_mode: ResetMode, }, CheckoutFiles { repo: GitRepo, commit: SharedString, paths: Vec<RepoPath>, }, Stage(GitRepo, Vec<RepoPath>), Unstage(GitRepo, Vec<RepoPath>), SetIndexText(GitRepo, RepoPath, Option<String>),} pub enum GitEvent { ActiveRepositoryChanged, FileSystemUpdated, GitStateUpdated,} impl EventEmitter<GitEvent> for GitStore {} impl GitStore { pub fn new( worktree_store: &Entity<WorktreeStore>, buffer_store: Entity<BufferStore>, client: Option<AnyProtoClient>, project_id: Option<ProjectId>, cx: &mut Context<'_, Self>, ) -> Self { let update_sender = Self::spawn_git_worker(cx); let _subscription = cx.subscribe(worktree_store, Self::on_worktree_store_event); GitStore { project_id, client, buffer_store, repositories: Vec::new(), active_index: None, update_sender, _subscription, } } pub fn init(client: &AnyProtoClient) { 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); } 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 { git_store: this.clone(), worktree_id, repository_entry: repo.clone(), git_repo, update_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); } } } pub fn all_repositories(&self) -> Vec<Entity<Repository>> { self.repositories.clone() } fn spawn_git_worker( cx: &mut Context<'_, GitStore>, ) -> mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)> { let (update_sender, mut update_receiver) = mpsc::unbounded::<(Message, oneshot::Sender<Result<()>>)>(); cx.spawn(|_, cx| async move { while let Some((msg, respond)) = update_receiver.next().await { let result = cx.background_spawn(Self::process_git_msg(msg)).await; respond.send(result).ok(); } }) .detach(); update_sender } async fn process_git_msg(msg: Message) -> Result<()> { match msg { Message::Stage(repo, paths) => { match repo { GitRepo::Local(repo) => repo.stage_paths(&paths)?, GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => { client .request(proto::Stage { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), paths: paths .into_iter() .map(|repo_path| repo_path.as_ref().to_proto()) .collect(), }) .await .context("sending stage request")?; } } Ok(()) } Message::Reset { repo, commit, reset_mode, } => { match repo { GitRepo::Local(repo) => repo.reset(&commit, reset_mode)?, GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => { client .request(proto::GitReset { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), commit: commit.into(), mode: match reset_mode { ResetMode::Soft => git_reset::ResetMode::Soft.into(), ResetMode::Mixed => git_reset::ResetMode::Mixed.into(), }, }) .await?; } } Ok(()) } Message::CheckoutFiles { repo, commit, paths, } => { match repo { GitRepo::Local(repo) => repo.checkout_files(&commit, &paths)?, GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => { client .request(proto::GitCheckoutFiles { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), commit: commit.into(), paths: paths .into_iter() .map(|p| p.to_string_lossy().to_string()) .collect(), }) .await?; } } Ok(()) } Message::Unstage(repo, paths) => { match repo { GitRepo::Local(repo) => repo.unstage_paths(&paths)?, GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => { client .request(proto::Unstage { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), paths: paths .into_iter() .map(|repo_path| repo_path.as_ref().to_proto()) .collect(), }) .await .context("sending unstage request")?; } } Ok(()) } Message::Commit { git_repo, message, name_and_email, } => { 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::Remote { project_id, client, worktree_id, work_directory_id, } => { let (name, email) = name_and_email.unzip(); client .request(proto::Commit { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), message: String::from(message), name: name.map(String::from), email: email.map(String::from), }) .await .context("sending commit request")?; } } Ok(()) } Message::SetIndexText(git_repo, path, text) => match git_repo { GitRepo::Local(repo) => repo.set_index_text(&path, text), GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => client.send(proto::SetIndexText { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), path: path.as_ref().to_proto(), text, }), }, } } 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); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let entries = envelope .payload .paths .into_iter() .map(PathBuf::from) .map(RepoPath::new) .collect(); repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.stage_entries(entries, cx) })? .await?; Ok(proto::Ack {}) } async fn handle_unstage( this: Entity<Self>, envelope: TypedEnvelope<proto::Unstage>, 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); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let entries = envelope .payload .paths .into_iter() .map(PathBuf::from) .map(RepoPath::new) .collect(); repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.unstage_entries(entries, cx) })? .await?; Ok(proto::Ack {}) } async fn handle_set_index_text( this: Entity<Self>, envelope: TypedEnvelope<proto::SetIndexText>, 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); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; repository_handle .update(&mut cx, |repository_handle, _| { repository_handle.set_index_text( &RepoPath::from_str(&envelope.payload.path), envelope.payload.text, ) })? .await??; Ok(proto::Ack {}) } async fn handle_commit( this: Entity<Self>, envelope: TypedEnvelope<proto::Commit>, 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); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let message = SharedString::from(envelope.payload.message);Lines 1–500 of 1018. Load more
use crate::buffer_store::BufferStore; use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent}; use crate::{Project, ProjectPath}; use anyhow::{Context as _, Result}; use client::ProjectId; use futures::channel::{mpsc, oneshot}; use futures::StreamExt as _; use git::repository::{Branch, CommitDetails, ResetMode}; use git::{ repository::{GitRepository, RepoPath}, status::{GitSummary, TrackedSummary}, }; use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task, WeakEntity, }; use language::{Buffer, LanguageRegistry}; use rpc::proto::{git_reset, ToProto}; use rpc::{proto, AnyProtoClient, TypedEnvelope}; use settings::WorktreeId; use std::path::{Path, PathBuf}; use std::sync::Arc; use text::BufferId; use util::{maybe, ResultExt}; use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry}; pub struct GitStore { buffer_store: Entity<BufferStore>, pub(super) project_id: Option<ProjectId>, pub(super) client: Option<AnyProtoClient>, repositories: Vec<Entity<Repository>>, active_index: Option<usize>, update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>, _subscription: Subscription, } pub struct Repository { commit_message_buffer: Option<Entity<Buffer>>, git_store: WeakEntity<GitStore>, pub worktree_id: WorktreeId, pub repository_entry: RepositoryEntry, pub git_repo: GitRepo, pub merge_message: Option<String>, update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>, } #[derive(Clone)] pub enum GitRepo { Local(Arc<dyn GitRepository>), Remote { project_id: ProjectId, client: AnyProtoClient, worktree_id: WorktreeId, work_directory_id: ProjectEntryId, }, } pub enum Message { Commit { git_repo: GitRepo, message: SharedString, name_and_email: Option<(SharedString, SharedString)>, }, Reset { repo: GitRepo, commit: SharedString, reset_mode: ResetMode, }, CheckoutFiles { repo: GitRepo, commit: SharedString, paths: Vec<RepoPath>, }, Stage(GitRepo, Vec<RepoPath>), Unstage(GitRepo, Vec<RepoPath>), SetIndexText(GitRepo, RepoPath, Option<String>), } pub enum GitEvent { ActiveRepositoryChanged, FileSystemUpdated, GitStateUpdated, } impl EventEmitter<GitEvent> for GitStore {} impl GitStore { pub fn new( worktree_store: &Entity<WorktreeStore>, buffer_store: Entity<BufferStore>, client: Option<AnyProtoClient>, project_id: Option<ProjectId>, cx: &mut Context<'_, Self>, ) -> Self { let update_sender = Self::spawn_git_worker(cx); let _subscription = cx.subscribe(worktree_store, Self::on_worktree_store_event); GitStore { project_id, client, buffer_store, repositories: Vec::new(), active_index: None, update_sender, _subscription, } } pub fn init(client: &AnyProtoClient) { 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); } 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 { git_store: this.clone(), worktree_id, repository_entry: repo.clone(), git_repo, update_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); } } } pub fn all_repositories(&self) -> Vec<Entity<Repository>> { self.repositories.clone() } fn spawn_git_worker( cx: &mut Context<'_, GitStore>, ) -> mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)> { let (update_sender, mut update_receiver) = mpsc::unbounded::<(Message, oneshot::Sender<Result<()>>)>(); cx.spawn(|_, cx| async move { while let Some((msg, respond)) = update_receiver.next().await { let result = cx.background_spawn(Self::process_git_msg(msg)).await; respond.send(result).ok(); } }) .detach(); update_sender } async fn process_git_msg(msg: Message) -> Result<()> { match msg { Message::Stage(repo, paths) => { match repo { GitRepo::Local(repo) => repo.stage_paths(&paths)?, GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => { client .request(proto::Stage { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), paths: paths .into_iter() .map(|repo_path| repo_path.as_ref().to_proto()) .collect(), }) .await .context("sending stage request")?; } } Ok(()) } Message::Reset { repo, commit, reset_mode, } => { match repo { GitRepo::Local(repo) => repo.reset(&commit, reset_mode)?, GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => { client .request(proto::GitReset { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), commit: commit.into(), mode: match reset_mode { ResetMode::Soft => git_reset::ResetMode::Soft.into(), ResetMode::Mixed => git_reset::ResetMode::Mixed.into(), }, }) .await?; } } Ok(()) } Message::CheckoutFiles { repo, commit, paths, } => { match repo { GitRepo::Local(repo) => repo.checkout_files(&commit, &paths)?, GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => { client .request(proto::GitCheckoutFiles { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), commit: commit.into(), paths: paths .into_iter() .map(|p| p.to_string_lossy().to_string()) .collect(), }) .await?; } } Ok(()) } Message::Unstage(repo, paths) => { match repo { GitRepo::Local(repo) => repo.unstage_paths(&paths)?, GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => { client .request(proto::Unstage { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), paths: paths .into_iter() .map(|repo_path| repo_path.as_ref().to_proto()) .collect(), }) .await .context("sending unstage request")?; } } Ok(()) } Message::Commit { git_repo, message, name_and_email, } => { 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::Remote { project_id, client, worktree_id, work_directory_id, } => { let (name, email) = name_and_email.unzip(); client .request(proto::Commit { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), message: String::from(message), name: name.map(String::from), email: email.map(String::from), }) .await .context("sending commit request")?; } } Ok(()) } Message::SetIndexText(git_repo, path, text) => match git_repo { GitRepo::Local(repo) => repo.set_index_text(&path, text), GitRepo::Remote { project_id, client, worktree_id, work_directory_id, } => client.send(proto::SetIndexText { project_id: project_id.0, worktree_id: worktree_id.to_proto(), work_directory_id: work_directory_id.to_proto(), path: path.as_ref().to_proto(), text, }), }, } } 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); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let entries = envelope .payload .paths .into_iter() .map(PathBuf::from) .map(RepoPath::new) .collect(); repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.stage_entries(entries, cx) })? .await?; Ok(proto::Ack {}) } async fn handle_unstage( this: Entity<Self>, envelope: TypedEnvelope<proto::Unstage>, 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); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let entries = envelope .payload .paths .into_iter() .map(PathBuf::from) .map(RepoPath::new) .collect(); repository_handle .update(&mut cx, |repository_handle, cx| { repository_handle.unstage_entries(entries, cx) })? .await?; Ok(proto::Ack {}) } async fn handle_set_index_text( this: Entity<Self>, envelope: TypedEnvelope<proto::SetIndexText>, 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); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; repository_handle .update(&mut cx, |repository_handle, _| { repository_handle.set_index_text( &RepoPath::from_str(&envelope.payload.path), envelope.payload.text, ) })? .await??; Ok(proto::Ack {}) } async fn handle_commit( this: Entity<Self>, envelope: TypedEnvelope<proto::Commit>, 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); let repository_handle = Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?; let message = SharedString::from(envelope.payload.message);