pub mod branch_diff;mod conflict_set;pub mod git_traversal;pub mod pending_op; use crate::{ ProjectEnvironment, ProjectItem, ProjectPath, buffer_store::{BufferStore, BufferStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent},};use anyhow::{Context as _, Result, anyhow, bail};use askpass::{AskPassDelegate, EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs};use buffer_diff::{BufferDiff, BufferDiffEvent};use client::ProjectId;use collections::HashMap;pub use conflict_set::{ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate};use fs::Fs;use futures::{ FutureExt, StreamExt, channel::{ mpsc, oneshot::{self, Canceled}, }, future::{self, Shared}, stream::FuturesOrdered,};use git::{ BuildPermalinkParams, GitHostingProviderRegistry, Oid, RunHook, blame::Blame, parse_git_remote_url, repository::{ Branch, CommitDetails, CommitDiff, CommitFile, CommitOptions, DiffType, FetchOptions, GitRepository, GitRepositoryCheckpoint, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode, UpstreamTrackingStatus, Worktree as GitWorktree, }, stash::{GitStash, StashEntry}, status::{ DiffTreeType, FileStatus, GitSummary, StatusCode, TrackedStatus, TreeDiff, TreeDiffStatus, 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 pending_op::{PendingOp, PendingOpId, PendingOps, PendingOpsSummary};use postage::stream::Stream as _;use rpc::{ AnyProtoClient, TypedEnvelope, proto::{self, git_reset, split_repository_update},};use serde::Deserialize;use settings::WorktreeId;use smol::future::yield_now;use std::{ cmp::Ordering, collections::{BTreeSet, HashSet, VecDeque}, future::Future, mem, ops::Range, path::{Path, PathBuf}, str::FromStr, sync::{ Arc, atomic::{self, AtomicU64}, }, time::Instant,};use sum_tree::{Edit, SumTree, TreeSet};use task::Shell;use text::{Bias, BufferId};use util::{ ResultExt, debug_panic, paths::{PathStyle, SanitizedPath}, post_inc, rel_path::RelPath,};use worktree::{ File, PathChange, PathKey, PathProgress, PathSummary, PathTarget, ProjectEntryId, UpdatedGitRepositoriesSet, UpdatedGitRepository, Worktree,};use zeroize::Zeroize; pub struct GitStore { state: GitStoreState, buffer_store: Entity<BufferStore>, worktree_store: Entity<WorktreeStore>, repositories: HashMap<RepositoryId, Entity<Repository>>, worktree_ids: HashMap<RepositoryId, HashSet<WorktreeId>>, 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<BufferGitState>>, shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>, _subscriptions: Vec<Subscription>,} #[derive(Default)]struct SharedDiffs { unstaged: Option<Entity<BufferDiff>>, uncommitted: Option<Entity<BufferDiff>>,} struct BufferGitState { unstaged_diff: Option<WeakEntity<BufferDiff>>, uncommitted_diff: Option<WeakEntity<BufferDiff>>, conflict_set: Option<WeakEntity<ConflictSet>>, recalculate_diff_task: Option<Task<Result<()>>>, reparse_conflict_markers_task: Option<Task<Result<()>>>, language: Option<Arc<Language>>, language_registry: Option<Arc<LanguageRegistry>>, conflict_updated_futures: Vec<oneshot::Sender<()>>, 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<str>>, index_text: Option<Arc<str>>, 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>, }, Remote { upstream_client: AnyProtoClient, upstream_project_id: u64, downstream: Option<(AnyProtoClient, ProjectId)>, },} enum DownstreamUpdate { UpdateRepository(RepositorySnapshot), RemoveRepository(RepositoryId),} struct LocalDownstreamState { client: AnyProtoClient, project_id: ProjectId, updates_tx: mpsc::UnboundedSender<DownstreamUpdate>, _task: Task<Result<()>>,} #[derive(Clone, Debug)]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.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::from_proto(&value.repo_path).context("invalid 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.as_ref().clone(), item_summary: self.status.summary(), } }} impl sum_tree::KeyedItem for StatusEntry { type Key = PathKey; fn key(&self) -> Self::Key { PathKey(self.repo_path.as_ref().clone()) }} #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]pub struct RepositoryId(pub u64); #[derive(Clone, Debug, Default, PartialEq, Eq)]pub struct MergeDetails { pub conflicted_paths: TreeSet<RepoPath>, pub message: Option<SharedString>, pub heads: Vec<Option<SharedString>>,} #[derive(Clone, Debug, PartialEq, Eq)]pub struct RepositorySnapshot { pub id: RepositoryId, pub statuses_by_path: SumTree<StatusEntry>, pub work_directory_abs_path: Arc<Path>, pub path_style: PathStyle, pub branch: Option<Branch>, pub head_commit: Option<CommitDetails>, pub scan_id: u64, pub merge: MergeDetails, pub remote_origin_url: Option<String>, pub remote_upstream_url: Option<String>, pub stash_entries: GitStash,} 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>, pending_ops: SumTree<PendingOps>, job_id: JobId, askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>, latest_askpass_id: u64, repository_state: Shared<Task<Result<RepositoryState, String>>>,} impl std::ops::Deref for Repository { type Target = RepositorySnapshot; fn deref(&self) -> &Self::Target { &self.snapshot }} #[derive(Clone)]pub struct LocalRepositoryState { pub fs: Arc<dyn Fs>, pub backend: Arc<dyn GitRepository>, pub environment: Arc<HashMap<String, String>>,} impl LocalRepositoryState { async fn new( work_directory_abs_path: Arc<Path>, dot_git_abs_path: Arc<Path>, project_environment: WeakEntity<ProjectEnvironment>, fs: Arc<dyn Fs>, cx: &mut AsyncApp, ) -> anyhow::Result<Self> { let environment = project_environment .update(cx, |project_environment, cx| { project_environment.local_directory_environment(&Shell::System, work_directory_abs_path.clone(), cx) })? .await .unwrap_or_else(|| { log::error!("failed to get working directory environment for repository {work_directory_abs_path:?}"); HashMap::default() }); let search_paths = environment.get("PATH").map(|val| val.to_owned()); let backend = cx .background_spawn({ let fs = fs.clone(); async move { let system_git_binary_path = search_paths .and_then(|search_paths| { which::which_in("git", Some(search_paths), &work_directory_abs_path) .ok() }) .or_else(|| which::which("git").ok()); fs.open_repo(&dot_git_abs_path, system_git_binary_path.as_deref()) .with_context(|| format!("opening repository at {dot_git_abs_path:?}")) } }) .await?; Ok(LocalRepositoryState { backend, environment: Arc::new(environment), fs, }) }} #[derive(Clone)]pub struct RemoteRepositoryState { pub project_id: ProjectId, pub client: AnyProtoClient,} #[derive(Clone)]pub enum RepositoryState { Local(LocalRepositoryState), Remote(RemoteRepositoryState),} #[derive(Clone, Debug, PartialEq, Eq)]pub enum RepositoryEvent { StatusesChanged, MergeHeadsChanged, BranchChanged, StashEntriesChanged, PendingOpsChanged { pending_ops: SumTree<PendingOps> },} #[derive(Clone, Debug)]pub struct JobsUpdated; #[derive(Debug)]pub enum GitStoreEvent { ActiveRepositoryChanged(Option<RepositoryId>), RepositoryUpdated(RepositoryId, RepositoryEvent, bool), RepositoryAdded, RepositoryRemoved(RepositoryId), IndexWriteError(anyhow::Error), JobsUpdated, ConflictsUpdated,} 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(Vec<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: u64, cx: &mut Context<Self>, ) -> Self { Self::new( worktree_store.clone(), buffer_store, GitStoreState::Remote { upstream_client, upstream_project_id: 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(), worktree_ids: 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_get_default_branch); 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_rename_branch); client.add_entity_request_handler(Self::handle_create_remote); client.add_entity_request_handler(Self::handle_remove_remote); client.add_entity_request_handler(Self::handle_delete_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_stash); client.add_entity_request_handler(Self::handle_stash_pop); client.add_entity_request_handler(Self::handle_stash_apply); client.add_entity_request_handler(Self::handle_stash_drop); client.add_entity_request_handler(Self::handle_commit); client.add_entity_request_handler(Self::handle_run_hook); 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_file_history); 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_tree_diff);Lines 1–500 of 6399. Load more
pub mod branch_diff; mod conflict_set; pub mod git_traversal; pub mod pending_op; use crate::{ ProjectEnvironment, ProjectItem, ProjectPath, buffer_store::{BufferStore, BufferStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent}, }; use anyhow::{Context as _, Result, anyhow, bail}; use askpass::{AskPassDelegate, EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs}; use buffer_diff::{BufferDiff, BufferDiffEvent}; use client::ProjectId; use collections::HashMap; pub use conflict_set::{ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate}; use fs::Fs; use futures::{ FutureExt, StreamExt, channel::{ mpsc, oneshot::{self, Canceled}, }, future::{self, Shared}, stream::FuturesOrdered, }; use git::{ BuildPermalinkParams, GitHostingProviderRegistry, Oid, RunHook, blame::Blame, parse_git_remote_url, repository::{ Branch, CommitDetails, CommitDiff, CommitFile, CommitOptions, DiffType, FetchOptions, GitRepository, GitRepositoryCheckpoint, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode, UpstreamTrackingStatus, Worktree as GitWorktree, }, stash::{GitStash, StashEntry}, status::{ DiffTreeType, FileStatus, GitSummary, StatusCode, TrackedStatus, TreeDiff, TreeDiffStatus, 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 pending_op::{PendingOp, PendingOpId, PendingOps, PendingOpsSummary}; use postage::stream::Stream as _; use rpc::{ AnyProtoClient, TypedEnvelope, proto::{self, git_reset, split_repository_update}, }; use serde::Deserialize; use settings::WorktreeId; use smol::future::yield_now; use std::{ cmp::Ordering, collections::{BTreeSet, HashSet, VecDeque}, future::Future, mem, ops::Range, path::{Path, PathBuf}, str::FromStr, sync::{ Arc, atomic::{self, AtomicU64}, }, time::Instant, }; use sum_tree::{Edit, SumTree, TreeSet}; use task::Shell; use text::{Bias, BufferId}; use util::{ ResultExt, debug_panic, paths::{PathStyle, SanitizedPath}, post_inc, rel_path::RelPath, }; use worktree::{ File, PathChange, PathKey, PathProgress, PathSummary, PathTarget, ProjectEntryId, UpdatedGitRepositoriesSet, UpdatedGitRepository, Worktree, }; use zeroize::Zeroize; pub struct GitStore { state: GitStoreState, buffer_store: Entity<BufferStore>, worktree_store: Entity<WorktreeStore>, repositories: HashMap<RepositoryId, Entity<Repository>>, worktree_ids: HashMap<RepositoryId, HashSet<WorktreeId>>, 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<BufferGitState>>, shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>, _subscriptions: Vec<Subscription>, } #[derive(Default)] struct SharedDiffs { unstaged: Option<Entity<BufferDiff>>, uncommitted: Option<Entity<BufferDiff>>, } struct BufferGitState { unstaged_diff: Option<WeakEntity<BufferDiff>>, uncommitted_diff: Option<WeakEntity<BufferDiff>>, conflict_set: Option<WeakEntity<ConflictSet>>, recalculate_diff_task: Option<Task<Result<()>>>, reparse_conflict_markers_task: Option<Task<Result<()>>>, language: Option<Arc<Language>>, language_registry: Option<Arc<LanguageRegistry>>, conflict_updated_futures: Vec<oneshot::Sender<()>>, 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<str>>, index_text: Option<Arc<str>>, 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>, }, Remote { upstream_client: AnyProtoClient, upstream_project_id: u64, downstream: Option<(AnyProtoClient, ProjectId)>, }, } enum DownstreamUpdate { UpdateRepository(RepositorySnapshot), RemoveRepository(RepositoryId), } struct LocalDownstreamState { client: AnyProtoClient, project_id: ProjectId, updates_tx: mpsc::UnboundedSender<DownstreamUpdate>, _task: Task<Result<()>>, } #[derive(Clone, Debug)] 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.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::from_proto(&value.repo_path).context("invalid 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.as_ref().clone(), item_summary: self.status.summary(), } } } impl sum_tree::KeyedItem for StatusEntry { type Key = PathKey; fn key(&self) -> Self::Key { PathKey(self.repo_path.as_ref().clone()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct RepositoryId(pub u64); #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct MergeDetails { pub conflicted_paths: TreeSet<RepoPath>, pub message: Option<SharedString>, pub heads: Vec<Option<SharedString>>, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RepositorySnapshot { pub id: RepositoryId, pub statuses_by_path: SumTree<StatusEntry>, pub work_directory_abs_path: Arc<Path>, pub path_style: PathStyle, pub branch: Option<Branch>, pub head_commit: Option<CommitDetails>, pub scan_id: u64, pub merge: MergeDetails, pub remote_origin_url: Option<String>, pub remote_upstream_url: Option<String>, pub stash_entries: GitStash, } 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>, pending_ops: SumTree<PendingOps>, job_id: JobId, askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>, latest_askpass_id: u64, repository_state: Shared<Task<Result<RepositoryState, String>>>, } impl std::ops::Deref for Repository { type Target = RepositorySnapshot; fn deref(&self) -> &Self::Target { &self.snapshot } } #[derive(Clone)] pub struct LocalRepositoryState { pub fs: Arc<dyn Fs>, pub backend: Arc<dyn GitRepository>, pub environment: Arc<HashMap<String, String>>, } impl LocalRepositoryState { async fn new( work_directory_abs_path: Arc<Path>, dot_git_abs_path: Arc<Path>, project_environment: WeakEntity<ProjectEnvironment>, fs: Arc<dyn Fs>, cx: &mut AsyncApp, ) -> anyhow::Result<Self> { let environment = project_environment .update(cx, |project_environment, cx| { project_environment.local_directory_environment(&Shell::System, work_directory_abs_path.clone(), cx) })? .await .unwrap_or_else(|| { log::error!("failed to get working directory environment for repository {work_directory_abs_path:?}"); HashMap::default() }); let search_paths = environment.get("PATH").map(|val| val.to_owned()); let backend = cx .background_spawn({ let fs = fs.clone(); async move { let system_git_binary_path = search_paths .and_then(|search_paths| { which::which_in("git", Some(search_paths), &work_directory_abs_path) .ok() }) .or_else(|| which::which("git").ok()); fs.open_repo(&dot_git_abs_path, system_git_binary_path.as_deref()) .with_context(|| format!("opening repository at {dot_git_abs_path:?}")) } }) .await?; Ok(LocalRepositoryState { backend, environment: Arc::new(environment), fs, }) } } #[derive(Clone)] pub struct RemoteRepositoryState { pub project_id: ProjectId, pub client: AnyProtoClient, } #[derive(Clone)] pub enum RepositoryState { Local(LocalRepositoryState), Remote(RemoteRepositoryState), } #[derive(Clone, Debug, PartialEq, Eq)] pub enum RepositoryEvent { StatusesChanged, MergeHeadsChanged, BranchChanged, StashEntriesChanged, PendingOpsChanged { pending_ops: SumTree<PendingOps> }, } #[derive(Clone, Debug)] pub struct JobsUpdated; #[derive(Debug)] pub enum GitStoreEvent { ActiveRepositoryChanged(Option<RepositoryId>), RepositoryUpdated(RepositoryId, RepositoryEvent, bool), RepositoryAdded, RepositoryRemoved(RepositoryId), IndexWriteError(anyhow::Error), JobsUpdated, ConflictsUpdated, } 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(Vec<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: u64, cx: &mut Context<Self>, ) -> Self { Self::new( worktree_store.clone(), buffer_store, GitStoreState::Remote { upstream_client, upstream_project_id: 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(), worktree_ids: 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_get_default_branch); 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_rename_branch); client.add_entity_request_handler(Self::handle_create_remote); client.add_entity_request_handler(Self::handle_remove_remote); client.add_entity_request_handler(Self::handle_delete_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_stash); client.add_entity_request_handler(Self::handle_stash_pop); client.add_entity_request_handler(Self::handle_stash_apply); client.add_entity_request_handler(Self::handle_stash_drop); client.add_entity_request_handler(Self::handle_commit); client.add_entity_request_handler(Self::handle_run_hook); 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_file_history); 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_tree_diff);