4520 lines · 165.7 KB · Rust
RawDownload
pub mod git_traversal; use crate::{    ProjectEnvironment, ProjectItem, ProjectPath,    buffer_store::{BufferStore, BufferStoreEvent},    worktree_store::{WorktreeStore, WorktreeStoreEvent},};use anyhow::{Context as _, Result, anyhow, bail};use askpass::AskPassDelegate;use buffer_diff::{BufferDiff, BufferDiffEvent};use client::ProjectId;use collections::HashMap;use fs::Fs;use futures::{    FutureExt as _, StreamExt as _,    channel::{mpsc, oneshot},    future::{self, Shared},};use git::{    BuildPermalinkParams, GitHostingProviderRegistry, WORK_DIRECTORY_REPO_PATH,    blame::Blame,    parse_git_remote_url,    repository::{        Branch, CommitDetails, CommitDiff, CommitFile, CommitOptions, DiffType, GitRepository,        GitRepositoryCheckpoint, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode,        UpstreamTrackingStatus,    },    status::{        FileStatus, GitSummary, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode,    },};use gpui::{    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,    WeakEntity,};use language::{    Buffer, BufferEvent, Language, LanguageRegistry,    proto::{deserialize_version, serialize_version},};use parking_lot::Mutex;use postage::stream::Stream as _;use rpc::{    AnyProtoClient, TypedEnvelope,    proto::{self, FromProto, SSH_PROJECT_ID, ToProto, git_reset, split_repository_update},};use serde::Deserialize;use std::{    cmp::Ordering,    collections::{BTreeSet, VecDeque},    future::Future,    mem,    ops::Range,    path::{Path, PathBuf},    sync::{        Arc,        atomic::{self, AtomicU64},    },    time::Instant,};use sum_tree::{Edit, SumTree, TreeSet};use text::{Bias, BufferId};use util::{ResultExt, debug_panic, post_inc};use worktree::{    File, PathKey, PathProgress, PathSummary, PathTarget, UpdatedGitRepositoriesSet,    UpdatedGitRepository, Worktree,}; pub struct GitStore {    state: GitStoreState,    buffer_store: Entity<BufferStore>,    worktree_store: Entity<WorktreeStore>,    repositories: HashMap<RepositoryId, Entity<Repository>>,    active_repo_id: Option<RepositoryId>,    #[allow(clippy::type_complexity)]    loading_diffs:        HashMap<(BufferId, DiffKind), Shared<Task<Result<Entity<BufferDiff>, Arc<anyhow::Error>>>>>,    diffs: HashMap<BufferId, Entity<BufferDiffState>>,    shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>,    _subscriptions: Vec<Subscription>,} #[derive(Default)]struct SharedDiffs {    unstaged: Option<Entity<BufferDiff>>,    uncommitted: Option<Entity<BufferDiff>>,} 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>>,    recalculating_tx: postage::watch::Sender<bool>,     /// These operation counts are used to ensure that head and index text    /// values read from the git repository are up-to-date with any hunk staging    /// operations that have been performed on the BufferDiff.    ///    /// The operation count is incremented immediately when the user initiates a    /// hunk stage/unstage operation. Then, upon finishing writing the new index    /// text do disk, the `operation count as of write` is updated to reflect    /// the operation count that prompted the write.    hunk_staging_operation_count: usize,    hunk_staging_operation_count_as_of_write: 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 {        next_repository_id: Arc<AtomicU64>,        downstream: Option<LocalDownstreamState>,        project_environment: Entity<ProjectEnvironment>,        fs: Arc<dyn Fs>,    },    Ssh {        upstream_client: AnyProtoClient,        upstream_project_id: ProjectId,        downstream: Option<(AnyProtoClient, ProjectId)>,    },    Remote {        upstream_client: AnyProtoClient,        upstream_project_id: ProjectId,    },} enum DownstreamUpdate {    UpdateRepository(RepositorySnapshot),    RemoveRepository(RepositoryId),} 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<Arc<Path>, GitRepositoryCheckpoint>,} #[derive(Clone, Debug, PartialEq, Eq)]pub struct StatusEntry {    pub repo_path: RepoPath,    pub status: FileStatus,} impl StatusEntry {    fn to_proto(&self) -> proto::StatusEntry {        let simple_status = match self.status {            FileStatus::Ignored | FileStatus::Untracked => proto::GitStatus::Added as i32,            FileStatus::Unmerged { .. } => proto::GitStatus::Conflict as i32,            FileStatus::Tracked(TrackedStatus {                index_status,                worktree_status,            }) => tracked_status_to_proto(if worktree_status != StatusCode::Unmodified {                worktree_status            } else {                index_status            }),        };         proto::StatusEntry {            repo_path: self.repo_path.as_ref().to_proto(),            simple_status,            status: Some(status_to_proto(self.status)),        }    }} impl TryFrom<proto::StatusEntry> for StatusEntry {    type Error = anyhow::Error;     fn try_from(value: proto::StatusEntry) -> Result<Self, Self::Error> {        let repo_path = RepoPath(Arc::<Path>::from_proto(value.repo_path));        let status = status_from_proto(value.simple_status, value.status)?;        Ok(Self { repo_path, status })    }} impl sum_tree::Item for StatusEntry {    type Summary = PathSummary<GitSummary>;     fn summary(&self, _: &<Self::Summary as sum_tree::Summary>::Context) -> Self::Summary {        PathSummary {            max_path: self.repo_path.0.clone(),            item_summary: self.status.summary(),        }    }} impl sum_tree::KeyedItem for StatusEntry {    type Key = PathKey;     fn key(&self) -> Self::Key {        PathKey(self.repo_path.0.clone())    }} #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]pub struct RepositoryId(pub u64); #[derive(Clone, Debug, PartialEq, Eq)]pub struct RepositorySnapshot {    pub id: RepositoryId,    pub merge_message: Option<SharedString>,    pub statuses_by_path: SumTree<StatusEntry>,    pub work_directory_abs_path: Arc<Path>,    pub branch: Option<Branch>,    pub head_commit: Option<CommitDetails>,    pub merge_conflicts: TreeSet<RepoPath>,    pub merge_head_shas: Vec<SharedString>,    pub scan_id: u64,} type JobId = u64; #[derive(Clone, Debug, PartialEq, Eq)]pub struct JobInfo {    pub start: Instant,    pub message: SharedString,} pub struct Repository {    this: WeakEntity<Self>,    snapshot: RepositorySnapshot,    commit_message_buffer: Option<Entity<Buffer>>,    git_store: WeakEntity<GitStore>,    // For a local repository, holds paths that have had worktree events since the last status scan completed,    // and that should be examined during the next status scan.    paths_needing_status_update: BTreeSet<RepoPath>,    job_sender: mpsc::UnboundedSender<GitJob>,    active_jobs: HashMap<JobId, JobInfo>,    job_id: JobId,    askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,    latest_askpass_id: u64,} impl std::ops::Deref for Repository {    type Target = RepositorySnapshot;     fn deref(&self) -> &Self::Target {        &self.snapshot    }} #[derive(Clone)]pub enum RepositoryState {    Local {        backend: Arc<dyn GitRepository>,        environment: Arc<HashMap<String, String>>,    },    Remote {        project_id: ProjectId,        client: AnyProtoClient,    },} #[derive(Clone, Debug)]pub enum RepositoryEvent {    Updated { full_scan: bool },    MergeHeadsChanged,} #[derive(Clone, Debug)]pub struct JobsUpdated; #[derive(Debug)]pub enum GitStoreEvent {    ActiveRepositoryChanged(Option<RepositoryId>),    RepositoryUpdated(RepositoryId, RepositoryEvent, bool),    RepositoryAdded(RepositoryId),    RepositoryRemoved(RepositoryId),    IndexWriteError(anyhow::Error),    JobsUpdated,} impl EventEmitter<RepositoryEvent> for Repository {}impl EventEmitter<JobsUpdated> for Repository {}impl EventEmitter<GitStoreEvent> for GitStore {} pub struct GitJob {    job: Box<dyn FnOnce(RepositoryState, &mut AsyncApp) -> Task<()>>,    key: Option<GitJobKey>,} #[derive(PartialEq, Eq)]enum GitJobKey {    WriteIndex(RepoPath),    ReloadBufferDiffBases,    RefreshStatuses,    ReloadGitState,} 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 {                next_repository_id: Arc::new(AtomicU64::new(1)),                downstream: None,                project_environment: 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,                upstream_project_id: project_id,            },            cx,        )    }     pub fn ssh(        worktree_store: &Entity<WorktreeStore>,        buffer_store: Entity<BufferStore>,        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: None,            },            cx,        )    }     fn new(        worktree_store: Entity<WorktreeStore>,        buffer_store: Entity<BufferStore>,        state: GitStoreState,        cx: &mut Context<Self>,    ) -> Self {        let _subscriptions = vec![            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,            _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_load_commit_diff);        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: downstream_client,                ..            } => {                for repo in self.repositories.values() {                    let update = repo.read(cx).snapshot.initial_update(project_id);                    for update in split_repository_update(update) {                        client.send(update).log_err();                    }                }                *downstream_client = Some((client, ProjectId(project_id)));            }            GitStoreState::Local {                downstream: 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).snapshot.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.id)                                        {                                            let update =                                                snapshot.build_update(old_snapshot, project_id);                                            *old_snapshot = snapshot;                                            for update in split_repository_update(update) {                                                client.send(update)?;                                            }                                        } else {                                            let update = snapshot.initial_update(project_id);                                            for update in split_repository_update(update) {                                                client.send(update)?;                                            }                                            snapshots.insert(snapshot.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: downstream_client,

Lines 1–500 of 4520.