3938 lines · 142.8 KB · Rust
RawDownload
pub mod git_traversal; use crate::{    buffer_store::{BufferStore, BufferStoreEvent},    worktree_store::{WorktreeStore, WorktreeStoreEvent},    ProjectEnvironment, ProjectItem, ProjectPath,};use anyhow::{anyhow, bail, Context as _, Result};use askpass::{AskPassDelegate, AskPassSession};use buffer_diff::{BufferDiff, BufferDiffEvent};use client::ProjectId;use collections::HashMap;use fs::Fs;use futures::{    channel::{mpsc, oneshot},    future::{self, OptionFuture, Shared},    FutureExt as _, StreamExt as _,};use git::{    blame::Blame,    parse_git_remote_url,    repository::{        Branch, CommitDetails, DiffType, GitIndex, GitRepository, GitRepositoryCheckpoint,        PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode,    },    status::{FileStatus, GitStatus},    BuildPermalinkParams, GitHostingProviderRegistry,};use gpui::{    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,    WeakEntity,};use language::{    proto::{deserialize_version, serialize_version},    Buffer, BufferEvent, Language, LanguageRegistry,};use parking_lot::Mutex;use rpc::{    proto::{self, git_reset, FromProto, ToProto, SSH_PROJECT_ID},    AnyProtoClient, TypedEnvelope,};use serde::Deserialize;use settings::WorktreeId;use std::{    collections::{hash_map, VecDeque},    future::Future,    ops::Range,    path::{Path, PathBuf},    sync::Arc,};use sum_tree::TreeSet;use text::BufferId;use util::{debug_panic, maybe, ResultExt};use worktree::{    proto_to_branch, File, PathKey, ProjectEntryId, RepositoryEntry, StatusEntry,    UpdatedGitRepositoriesSet, Worktree,}; pub struct GitStore {    state: GitStoreState,    buffer_store: Entity<BufferStore>,    worktree_store: Entity<WorktreeStore>,    repositories: HashMap<ProjectEntryId, Entity<Repository>>,    active_repo_id: Option<ProjectEntryId>,    #[allow(clippy::type_complexity)]    loading_diffs:        HashMap<(BufferId, DiffKind), Shared<Task<Result<Entity<BufferDiff>, Arc<anyhow::Error>>>>>,    diffs: HashMap<BufferId, Entity<BufferDiffState>>,    update_sender: mpsc::UnboundedSender<GitJob>,    shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>,    _subscriptions: [Subscription; 2],} #[derive(Default)]struct SharedDiffs {    unstaged: Option<Entity<BufferDiff>>,    uncommitted: Option<Entity<BufferDiff>>,} #[derive(Default)]struct BufferDiffState {    unstaged_diff: Option<WeakEntity<BufferDiff>>,    uncommitted_diff: Option<WeakEntity<BufferDiff>>,    recalculate_diff_task: Option<Task<Result<()>>>,    language: Option<Arc<Language>>,    language_registry: Option<Arc<LanguageRegistry>>,    diff_updated_futures: Vec<oneshot::Sender<()>>,    hunk_staging_operation_count: usize,     head_text: Option<Arc<String>>,    index_text: Option<Arc<String>>,    head_changed: bool,    index_changed: bool,    language_changed: bool,} #[derive(Clone, Debug)]enum DiffBasesChange {    SetIndex(Option<String>),    SetHead(Option<String>),    SetEach {        index: Option<String>,        head: Option<String>,    },    SetBoth(Option<String>),} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]enum DiffKind {    Unstaged,    Uncommitted,} enum GitStoreState {    Local {        downstream_client: Option<LocalDownstreamState>,        environment: Entity<ProjectEnvironment>,        fs: Arc<dyn Fs>,    },    Ssh {        upstream_client: AnyProtoClient,        upstream_project_id: ProjectId,        downstream_client: Option<(AnyProtoClient, ProjectId)>,        environment: Entity<ProjectEnvironment>,    },    Remote {        upstream_client: AnyProtoClient,        project_id: ProjectId,    },} enum DownstreamUpdate {    UpdateRepository(RepositoryEntry),    RemoveRepository(ProjectEntryId),} struct LocalDownstreamState {    client: AnyProtoClient,    project_id: ProjectId,    updates_tx: mpsc::UnboundedSender<DownstreamUpdate>,    _task: Task<Result<()>>,} #[derive(Clone)]pub struct GitStoreCheckpoint {    checkpoints_by_work_dir_abs_path: HashMap<PathBuf, GitRepositoryCheckpoint>,} #[derive(Clone, Debug)]pub struct GitStoreDiff {    diffs_by_work_dir_abs_path: HashMap<PathBuf, String>,} #[derive(Clone, Debug)]pub struct GitStoreIndex {    indices_by_work_dir_abs_path: HashMap<PathBuf, GitIndex>,} #[derive(Default)]pub struct GitStoreStatus {    #[allow(dead_code)]    statuses_by_work_dir_abs_path: HashMap<PathBuf, GitStatus>,} pub struct Repository {    pub repository_entry: RepositoryEntry,    pub merge_message: Option<String>,    pub completed_scan_id: usize,    commit_message_buffer: Option<Entity<Buffer>>,    git_store: WeakEntity<GitStore>,    project_environment: Option<WeakEntity<ProjectEnvironment>>,    pub worktree_id: Option<WorktreeId>,    state: RepositoryState,    job_sender: mpsc::UnboundedSender<GitJob>,    askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,    latest_askpass_id: u64,} #[derive(Clone)]enum RepositoryState {    Local(Arc<dyn GitRepository>),    Remote {        project_id: ProjectId,        client: AnyProtoClient,        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),    BatchReadIndex(ProjectEntryId),} impl EventEmitter<GitEvent> for GitStore {} impl GitStore {    pub fn local(        worktree_store: &Entity<WorktreeStore>,        buffer_store: Entity<BufferStore>,        environment: Entity<ProjectEnvironment>,        fs: Arc<dyn Fs>,        cx: &mut Context<Self>,    ) -> Self {        Self::new(            worktree_store.clone(),            buffer_store,            GitStoreState::Local {                downstream_client: None,                environment,                fs,            },            cx,        )    }     pub fn remote(        worktree_store: &Entity<WorktreeStore>,        buffer_store: Entity<BufferStore>,        upstream_client: AnyProtoClient,        project_id: ProjectId,        cx: &mut Context<Self>,    ) -> Self {        Self::new(            worktree_store.clone(),            buffer_store,            GitStoreState::Remote {                upstream_client,                project_id,            },            cx,        )    }     pub fn ssh(        worktree_store: &Entity<WorktreeStore>,        buffer_store: Entity<BufferStore>,        environment: Entity<ProjectEnvironment>,        upstream_client: AnyProtoClient,        cx: &mut Context<Self>,    ) -> Self {        Self::new(            worktree_store.clone(),            buffer_store,            GitStoreState::Ssh {                upstream_client,                upstream_project_id: ProjectId(SSH_PROJECT_ID),                downstream_client: None,                environment,            },            cx,        )    }     fn new(        worktree_store: Entity<WorktreeStore>,        buffer_store: Entity<BufferStore>,        state: GitStoreState,        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 {            state,            buffer_store,            worktree_store,            repositories: HashMap::default(),            active_repo_id: None,            update_sender,            _subscriptions,            loading_diffs: HashMap::default(),            shared_diffs: HashMap::default(),            diffs: HashMap::default(),        }    }     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_git_init);        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);        client.add_entity_request_handler(Self::handle_open_unstaged_diff);        client.add_entity_request_handler(Self::handle_open_uncommitted_diff);        client.add_entity_message_handler(Self::handle_update_diff_bases);        client.add_entity_request_handler(Self::handle_get_permalink_to_line);        client.add_entity_request_handler(Self::handle_blame_buffer);        client.add_entity_message_handler(Self::handle_update_repository);        client.add_entity_message_handler(Self::handle_remove_repository);    }     pub fn is_local(&self) -> bool {        matches!(self.state, GitStoreState::Local { .. })    }     pub fn shared(&mut self, project_id: u64, client: AnyProtoClient, cx: &mut Context<Self>) {        match &mut self.state {            GitStoreState::Ssh {                downstream_client, ..            } => {                for repo in self.repositories.values() {                    client                        .send(repo.read(cx).repository_entry.initial_update(project_id))                        .log_err();                }                *downstream_client = Some((client, ProjectId(project_id)));            }            GitStoreState::Local {                downstream_client, ..            } => {                let mut snapshots = HashMap::default();                let (updates_tx, mut updates_rx) = mpsc::unbounded();                for repo in self.repositories.values() {                    updates_tx                        .unbounded_send(DownstreamUpdate::UpdateRepository(                            repo.read(cx).repository_entry.clone(),                        ))                        .ok();                }                *downstream_client = Some(LocalDownstreamState {                    client: client.clone(),                    project_id: ProjectId(project_id),                    updates_tx,                    _task: cx.spawn(async move |this, cx| {                        cx.background_spawn(async move {                            while let Some(update) = updates_rx.next().await {                                match update {                                    DownstreamUpdate::UpdateRepository(snapshot) => {                                        if let Some(old_snapshot) =                                            snapshots.get_mut(&snapshot.work_directory_id)                                        {                                            let update =                                                snapshot.build_update(old_snapshot, project_id);                                            *old_snapshot = snapshot;                                            client.send(update)?;                                        } else {                                            let update = snapshot.initial_update(project_id);                                            client.send(update)?;                                            snapshots.insert(snapshot.work_directory_id, snapshot);                                        }                                    }                                    DownstreamUpdate::RemoveRepository(id) => {                                        client.send(proto::RemoveRepository {                                            project_id,                                            id: id.to_proto(),                                        })?;                                    }                                }                            }                            anyhow::Ok(())                        })                        .await                        .ok();                        this.update(cx, |this, _| {                            if let GitStoreState::Local {                                downstream_client, ..                            } = &mut this.state                            {                                downstream_client.take();                            } else {                                unreachable!("unshared called on remote store");                            }                        })                    }),                });            }            GitStoreState::Remote { .. } => {                debug_panic!("shared called on remote store");            }        }    }     pub fn unshared(&mut self, _cx: &mut Context<Self>) {        match &mut self.state {            GitStoreState::Local {                downstream_client, ..            } => {                downstream_client.take();            }            GitStoreState::Ssh {                downstream_client, ..            } => {                downstream_client.take();            }            GitStoreState::Remote { .. } => {                debug_panic!("unshared called on remote store");            }        }        self.shared_diffs.clear();    }     pub(crate) fn forget_shared_diffs_for(&mut self, peer_id: &proto::PeerId) {        self.shared_diffs.remove(peer_id);    }     pub fn active_repository(&self) -> Option<Entity<Repository>> {        self.active_repo_id            .as_ref()            .map(|id| self.repositories[&id].clone())    }     pub fn open_unstaged_diff(        &mut self,        buffer: Entity<Buffer>,        cx: &mut Context<Self>,    ) -> Task<Result<Entity<BufferDiff>>> {        let buffer_id = buffer.read(cx).remote_id();        if let Some(diff_state) = self.diffs.get(&buffer_id) {            if let Some(unstaged_diff) = diff_state                .read(cx)                .unstaged_diff                .as_ref()                .and_then(|weak| weak.upgrade())            {                if let Some(task) =                    diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())                {                    return cx.background_executor().spawn(async move {                        task.await?;                        Ok(unstaged_diff)                    });                }                return Task::ready(Ok(unstaged_diff));            }        }         let task = match self.loading_diffs.entry((buffer_id, DiffKind::Unstaged)) {            hash_map::Entry::Occupied(e) => e.get().clone(),            hash_map::Entry::Vacant(entry) => {                let staged_text = self.state.load_staged_text(&buffer, &self.buffer_store, cx);                entry                    .insert(                        cx.spawn(async move |this, cx| {                            Self::open_diff_internal(                                this,                                DiffKind::Unstaged,                                staged_text.await.map(DiffBasesChange::SetIndex),                                buffer,                                cx,                            )                            .await                            .map_err(Arc::new)                        })                        .shared(),                    )                    .clone()            }        };         cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })    }     pub fn open_uncommitted_diff(        &mut self,        buffer: Entity<Buffer>,        cx: &mut Context<Self>,    ) -> Task<Result<Entity<BufferDiff>>> {        let buffer_id = buffer.read(cx).remote_id();         if let Some(diff_state) = self.diffs.get(&buffer_id) {            if let Some(uncommitted_diff) = diff_state                .read(cx)                .uncommitted_diff                .as_ref()                .and_then(|weak| weak.upgrade())            {                if let Some(task) =                    diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())                {

Lines 1–500 of 3938.