1282 lines · 46.6 KB · Rust
RawDownload
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, PushOptions, Remote, RemoteCommandOutput, ResetMode};use git::repository::{GitRepository, RepoPath};use gpui::{    App, AppContext, 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::collections::VecDeque;use std::future::Future;use std::path::{Path, PathBuf};use std::sync::Arc;use text::BufferId;use util::{maybe, ResultExt};use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory}; 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<GitJob>,    _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>,    job_sender: mpsc::UnboundedSender<GitJob>,} #[derive(Clone)]pub enum GitRepo {    Local(Arc<dyn GitRepository>),    Remote {        project_id: ProjectId,        client: AnyProtoClient,        worktree_id: WorktreeId,        work_directory_id: ProjectEntryId,    },} pub enum GitEvent {    ActiveRepositoryChanged,    FileSystemUpdated,    GitStateUpdated,} 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>,        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_get_remotes);        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);    }     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,                                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);            }        }    }     pub fn all_repositories(&self) -> Vec<Entity<Repository>> {        self.repositories.clone()    }     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 remote_output = repository_handle            .update(&mut cx, |repository_handle, _cx| repository_handle.fetch())?            .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 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)            })?            .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 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)            })?            .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);        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);        let name = envelope.payload.name.map(SharedString::from);        let email = envelope.payload.email.map(SharedString::from);         repository_handle            .update(&mut cx, |repository_handle, _| {                repository_handle.commit(message, name.zip(email))            })?            .await??;        Ok(proto::Ack {})    }     async fn handle_get_remotes(        this: Entity<Self>,        envelope: TypedEnvelope<proto::GetRemotes>,        mut cx: AsyncApp,    ) -> Result<proto::GetRemotesResponse> {        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 branch_name = envelope.payload.branch_name;         let remotes = repository_handle            .update(&mut cx, |repository_handle, _| {                repository_handle.get_remotes(branch_name)            })?            .await??;         Ok(proto::GetRemotesResponse {            remotes: remotes                .into_iter()                .map(|remotes| proto::get_remotes_response::Remote {                    name: remotes.name.to_string(),                })                .collect::<Vec<_>>(),        })    }     async fn handle_show(        this: Entity<Self>,        envelope: TypedEnvelope<proto::GitShow>,        mut cx: AsyncApp,    ) -> Result<proto::GitCommitDetails> {        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 commit = repository_handle            .update(&mut cx, |repository_handle, _| {                repository_handle.show(&envelope.payload.commit)            })?            .await??;        Ok(proto::GitCommitDetails {            sha: commit.sha.into(),            message: commit.message.into(),            commit_timestamp: commit.commit_timestamp,            committer_email: commit.committer_email.into(),            committer_name: commit.committer_name.into(),        })    }     async fn handle_reset(        this: Entity<Self>,        envelope: TypedEnvelope<proto::GitReset>,        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 mode = match envelope.payload.mode() {            git_reset::ResetMode::Soft => ResetMode::Soft,            git_reset::ResetMode::Mixed => ResetMode::Mixed,        }; 

Lines 1–500 of 1282.