Mikayla MakiWIP: Implement pusha94b13d1 year agoHistory
1099 lines · 40.1 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, 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>),    Push(GitRepo, bool),    ForcePush(GitRepo),} 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_push);        client.add_entity_request_handler(Self::handle_force_push);        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 {                if !respond.is_canceled() {                    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::ForcePush(repo) => {                match repo {                    GitRepo::Local(git_repository) => git_repository.force_push()?,                    GitRepo::Remote {                        project_id,                        client,                        worktree_id,                        work_directory_id,                    } => {                        client                            .request(proto::ForcePush {                                project_id: project_id.0,                                worktree_id: worktree_id.to_proto(),                                work_directory_id: work_directory_id.to_proto(),                            })                            .await                            .context("sending force push request")?;                    }                }                Ok(())            }            Message::Push(repo, upstream) => {                match repo {                    GitRepo::Local(git_repository) => git_repository.push(upstream)?,                    GitRepo::Remote {                        project_id,                        client,                        worktree_id,                        work_directory_id,                    } => {                        client                            .request(proto::Push {                                project_id: project_id.0,                                worktree_id: worktree_id.to_proto(),                                work_directory_id: work_directory_id.to_proto(),                                set_upstream: upstream,                            })                            .await                            .context("sending push request")?;                    }                }                Ok(())            }            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_force_push(        this: Entity<Self>,        envelope: TypedEnvelope<proto::ForcePush>,        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, _cx| {                repository_handle.force_push()            })?            .await??;        Ok(proto::Ack {})    }     async fn handle_push(        this: Entity<Self>,        envelope: TypedEnvelope<proto::Push>,        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, _cx| {                if envelope.payload.set_upstream {                    repository_handle.push_upstream()                } else {                    repository_handle.push()                }            })?

Lines 1–500 of 1099.