mod conflict_set;pub mod diff_buffer_list;pub mod git_traversal;pub mod job_debug_queue;pub mod pending_op; use crate::{ ProjectEnvironment, ProjectItem, ProjectPath, buffer_store::{BufferStore, BufferStoreEvent}, project_settings::ProjectSettings, trusted_worktrees::{ PathTrust, TrustedWorktrees, TrustedWorktreesEvent, TrustedWorktreesStore, }, worktree_store::{WorktreeStore, WorktreeStoreEvent},};use anyhow::{Context as _, Result, anyhow, bail};use askpass::{AskPassDelegate, EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs};use buffer_diff::{BufferDiff, DiffHunk, DiffHunkSecondaryStatus, PendingHunk, PendingSense};use client::ProjectId;use collections::HashMap;pub use conflict_set::{ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate};use fs::{Fs, RemoveOptions};use futures::{ FutureExt, SinkExt, Stream, StreamExt, channel::{ mpsc, oneshot::{self, Canceled}, }, future::{self, BoxFuture, Shared}, stream::{FuturesOrdered, FuturesUnordered},};use git::{ BuildPermalinkParams, GitHostingProviderRegistry, Oid, RunHook, blame::Blame, parse_git_remote_url, repository::{ Branch, BranchesScanResult, CommitData, CommitDetails, CommitDiff, CommitFile, CommitOptions, CreateWorktreeTarget, DiffStatType, DiffType, FetchOptions, FileHistoryChangedFileSets, GitCommitTemplate, GitRepository, GitRepositoryCheckpoint, InitialGraphCommitData, LogOrder, LogSource, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode, SearchCommitArgs, UpstreamTrackingStatus, Worktree as GitWorktree, delete_branch_flag, }, stash::{GitStash, StashEntry}, status::{ self, DiffStat, DiffTreeType, FileStatus, GitSummary, StatusCode, TrackedStatus, TreeDiff, TreeDiffStatus, UnmergedStatus, UnmergedStatusCode, },};use gpui::{ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString, Subscription, Task, TaskExt, WeakEntity,};use language::{ Anchor, Buffer, BufferEvent, Capability, Language, LanguageRegistry, proto::{deserialize_version, serialize_version},};use parking_lot::Mutex;use paths::{config_dir, home_dir};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::{Settings, WorktreeId};use smallvec::SmallVec;use smol::future::yield_now;use std::{ cmp::Ordering, collections::{BTreeSet, HashSet, VecDeque, hash_map::Entry}, future::Future, mem, ops::Range, path::{Path, PathBuf}, str::FromStr, sync::{ Arc, atomic::{self, AtomicU64}, }, time::{Duration, Instant, SystemTime},};use sum_tree::{Edit, SumTree, TreeMap};use task::Shell;use text::{Bias, BufferId, OffsetRangeExt, Rope, ToOffset};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>>, buffer_ids_by_index_text_buffer_id: HashMap<BufferId, BufferId>, 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>>, staged_diff: Option<(WeakEntity<BufferDiff>, Entity<Buffer>)>, uncommitted_diff: Option<WeakEntity<BufferDiff>>, oid_diffs: HashMap<Option<git::Oid>, 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>>, /// The optimistic, in-flight index state: the sole input to the index write, /// expressed relative to the currently-loaded index text (`I0`). Never shown /// to any view (views use per-diff pending hunks). Cleared when a /// recalculation settles. /// /// `I0` is immutable for the lifetime of any pending edit (the index text /// buffer is only fast-forwarded when a recalculation settles, which clears /// this state in the same update), so byte offsets stay valid for the whole /// window. pending_index_edits: Option<Vec<(Range<usize>, Arc<str>)>>, oid_texts: HashMap<git::Oid, Arc<str>>, head_text_buffer: WeakEntity<Buffer>, index_text_buffer: WeakEntity<Buffer>, index_text_buffer_language_enabled: bool, head_changed: bool, index_changed: bool, language_changed: bool,} fn pending_hunks( hunks: &[DiffHunk], version: &clock::Global, sense: PendingSense,) -> Vec<PendingHunk> { hunks .iter() .map(|hunk| { PendingHunk::new( hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone(), version.clone(), sense, ) }) .collect()} #[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, Staged, Uncommitted, SinceOid(Option<git::Oid>),} struct IndexTextFile { path: Arc<RelPath>, full_path: PathBuf, path_style: PathStyle, file_name: String, worktree_id: WorktreeId, is_private: bool,} impl IndexTextFile { fn new(file: &dyn language::File, cx: &App) -> Self { Self { path: file.path().clone(), full_path: file.full_path(cx), path_style: file.path_style(cx), file_name: file.file_name(cx).to_string(), worktree_id: file.worktree_id(cx), is_private: file.is_private(), } }} impl language::File for IndexTextFile { fn as_local(&self) -> Option<&dyn language::LocalFile> { None } fn disk_state(&self) -> language::DiskState { language::DiskState::Historic { was_deleted: false } } fn path(&self) -> &Arc<RelPath> { &self.path } fn full_path(&self, _: &App) -> PathBuf { self.full_path.clone() } fn path_style(&self, _: &App) -> PathStyle { self.path_style } fn file_name<'a>(&'a self, _: &'a App) -> &'a str { &self.file_name } fn worktree_id(&self, _: &App) -> WorktreeId { self.worktree_id } fn to_proto(&self, _: &App) -> rpc::proto::File { rpc::proto::File { worktree_id: self.worktree_id.to_proto(), entry_id: None, path: self.path.as_ref().as_unix_str().to_owned(), mtime: None, is_deleted: false, is_historic: true, } } fn is_private(&self) -> bool { self.is_private }} #[derive(Debug, Clone, Copy)]pub enum GitAccess { /// Either: /// - the user owns `.git` /// - the user doesn't own `.git`, but has both of: /// - OS-level read permissions /// - the directory is marked as safe (git config safe.directory) Yes, /// The user is not the owner of `.git`, and one of the following is true: /// - the directory is not marked as safe (git config safe.directory) /// - the user does not have OS-level read permissions to `.git` No,} enum GitStoreState { Local { next_repository_id: Arc<AtomicU64>, downstream: Option<LocalDownstreamState>, project_environment: Entity<ProjectEnvironment>, fs: Arc<dyn Fs>, _fs_watches: Box<[Task<()>]>, }, 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, pub diff_stat: Option<DiffStat>, pub staged_diff_stat: Option<DiffStat>, pub unstaged_diff_stat: Option<DiffStat>,} 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_unix_str().to_owned(), simple_status, status: Some(status_to_proto(self.status)), diff_stat_added: self.diff_stat.map(|ds| ds.added), diff_stat_deleted: self.diff_stat.map(|ds| ds.deleted), staged_diff_stat_added: self.staged_diff_stat.map(|ds| ds.added), staged_diff_stat_deleted: self.staged_diff_stat.map(|ds| ds.deleted), unstaged_diff_stat_added: self.unstaged_diff_stat.map(|ds| ds.added), unstaged_diff_stat_deleted: self.unstaged_diff_stat.map(|ds| ds.deleted), } }} 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)?; let diff_stat = match (value.diff_stat_added, value.diff_stat_deleted) { (Some(added), Some(deleted)) => Some(DiffStat { added, deleted }), _ => None, }; let staged_diff_stat = match (value.staged_diff_stat_added, value.staged_diff_stat_deleted) { (Some(added), Some(deleted)) => Some(DiffStat { added, deleted }), _ => None, }; let unstaged_diff_stat = match ( value.unstaged_diff_stat_added, value.unstaged_diff_stat_deleted, ) { (Some(added), Some(deleted)) => Some(DiffStat { added, deleted }), _ => None, }; Ok(Self { repo_path, status, diff_stat, staged_diff_stat, unstaged_diff_stat, }) }} 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 merge_heads_by_conflicted_path: TreeMap<RepoPath, Vec<Option<SharedString>>>, pub message: Option<SharedString>,} #[derive(Clone)]pub enum CommitDataState { Loading(Option<Shared<oneshot::Receiver<Arc<CommitData>>>>), Loaded(Arc<CommitData>),} #[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 dot_git_abs_path: Arc<Path>, /// Absolute path to the directory holding this worktree's Git state. /// /// For a linked worktree this is the worktree-specific directory under the /// common Git directory, such as `<main>/.git/worktrees/<name>`. pub repository_dir_abs_path: Arc<Path>, /// Absolute path to the repository's common Git directory. /// /// For a normal checkout this is `<work_directory>/.git`. For a linked /// worktree this is the common Git directory shared by all worktrees. If /// that common directory is a bare repository, there may be no main /// worktree path to derive from it. pub common_dir_abs_path: Arc<Path>, pub path_style: PathStyle, pub branch: Option<Branch>, pub branch_list: Arc<[Branch]>, pub branch_list_error: Option<SharedString>, 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, pub linked_worktrees: Arc<[GitWorktree]>,} type JobId = u64; #[derive(Clone, Debug, PartialEq, Eq)]pub struct JobInfo { pub start: Instant, pub message: SharedString,} struct CommitDataHandler { _task: Task<()>, commit_data_request: async_channel::Sender<Oid>, completion_senders: HashMap<Oid, oneshot::Sender<Arc<CommitData>>>, pending_requests: HashSet<Oid>,} /// Represents the handler of a git cat-file --batch process within Zed/// It's used to lazily fetch commit data as needed (whatever a user is viewing)enum CommitDataHandlerState { /// The handler is open and processing requests Open(CommitDataHandler), /// The handler closed because it didn't receive any requests in the last 10s /// or hasn't been open before Closed,} enum NextCommitDataRequest { Request(BoxFuture<'static, Result<proto::GetCommitDataResponse>>), Idle, Closed,} pub struct InitialGitGraphData { fetch_task: Task<()>, pub error: Option<SharedString>, pub commit_data: Vec<Arc<InitialGraphCommitData>>, pub commit_oid_to_index: HashMap<Oid, usize>, subscribers: Vec<async_channel::Sender<Result<Vec<Arc<InitialGraphCommitData>>, SharedString>>>,} pub struct GraphDataResponse<'a> { pub commits: &'a [Arc<InitialGraphCommitData>], pub is_loading: bool, pub error: Option<SharedString>,} pub struct Repository { this: WeakEntity<Self>, snapshot: RepositorySnapshot, commit_message_buffer: Option<Entity<Buffer>>,Lines 1–500 of 10872. Load more
mod conflict_set; pub mod diff_buffer_list; pub mod git_traversal; pub mod job_debug_queue; pub mod pending_op; use crate::{ ProjectEnvironment, ProjectItem, ProjectPath, buffer_store::{BufferStore, BufferStoreEvent}, project_settings::ProjectSettings, trusted_worktrees::{ PathTrust, TrustedWorktrees, TrustedWorktreesEvent, TrustedWorktreesStore, }, worktree_store::{WorktreeStore, WorktreeStoreEvent}, }; use anyhow::{Context as _, Result, anyhow, bail}; use askpass::{AskPassDelegate, EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs}; use buffer_diff::{BufferDiff, DiffHunk, DiffHunkSecondaryStatus, PendingHunk, PendingSense}; use client::ProjectId; use collections::HashMap; pub use conflict_set::{ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate}; use fs::{Fs, RemoveOptions}; use futures::{ FutureExt, SinkExt, Stream, StreamExt, channel::{ mpsc, oneshot::{self, Canceled}, }, future::{self, BoxFuture, Shared}, stream::{FuturesOrdered, FuturesUnordered}, }; use git::{ BuildPermalinkParams, GitHostingProviderRegistry, Oid, RunHook, blame::Blame, parse_git_remote_url, repository::{ Branch, BranchesScanResult, CommitData, CommitDetails, CommitDiff, CommitFile, CommitOptions, CreateWorktreeTarget, DiffStatType, DiffType, FetchOptions, FileHistoryChangedFileSets, GitCommitTemplate, GitRepository, GitRepositoryCheckpoint, InitialGraphCommitData, LogOrder, LogSource, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode, SearchCommitArgs, UpstreamTrackingStatus, Worktree as GitWorktree, delete_branch_flag, }, stash::{GitStash, StashEntry}, status::{ self, DiffStat, DiffTreeType, FileStatus, GitSummary, StatusCode, TrackedStatus, TreeDiff, TreeDiffStatus, UnmergedStatus, UnmergedStatusCode, }, }; use gpui::{ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString, Subscription, Task, TaskExt, WeakEntity, }; use language::{ Anchor, Buffer, BufferEvent, Capability, Language, LanguageRegistry, proto::{deserialize_version, serialize_version}, }; use parking_lot::Mutex; use paths::{config_dir, home_dir}; 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::{Settings, WorktreeId}; use smallvec::SmallVec; use smol::future::yield_now; use std::{ cmp::Ordering, collections::{BTreeSet, HashSet, VecDeque, hash_map::Entry}, future::Future, mem, ops::Range, path::{Path, PathBuf}, str::FromStr, sync::{ Arc, atomic::{self, AtomicU64}, }, time::{Duration, Instant, SystemTime}, }; use sum_tree::{Edit, SumTree, TreeMap}; use task::Shell; use text::{Bias, BufferId, OffsetRangeExt, Rope, ToOffset}; 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>>, buffer_ids_by_index_text_buffer_id: HashMap<BufferId, BufferId>, 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>>, staged_diff: Option<(WeakEntity<BufferDiff>, Entity<Buffer>)>, uncommitted_diff: Option<WeakEntity<BufferDiff>>, oid_diffs: HashMap<Option<git::Oid>, 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>>, /// The optimistic, in-flight index state: the sole input to the index write, /// expressed relative to the currently-loaded index text (`I0`). Never shown /// to any view (views use per-diff pending hunks). Cleared when a /// recalculation settles. /// /// `I0` is immutable for the lifetime of any pending edit (the index text /// buffer is only fast-forwarded when a recalculation settles, which clears /// this state in the same update), so byte offsets stay valid for the whole /// window. pending_index_edits: Option<Vec<(Range<usize>, Arc<str>)>>, oid_texts: HashMap<git::Oid, Arc<str>>, head_text_buffer: WeakEntity<Buffer>, index_text_buffer: WeakEntity<Buffer>, index_text_buffer_language_enabled: bool, head_changed: bool, index_changed: bool, language_changed: bool, } fn pending_hunks( hunks: &[DiffHunk], version: &clock::Global, sense: PendingSense, ) -> Vec<PendingHunk> { hunks .iter() .map(|hunk| { PendingHunk::new( hunk.buffer_range.clone(), hunk.diff_base_byte_range.clone(), version.clone(), sense, ) }) .collect() } #[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, Staged, Uncommitted, SinceOid(Option<git::Oid>), } struct IndexTextFile { path: Arc<RelPath>, full_path: PathBuf, path_style: PathStyle, file_name: String, worktree_id: WorktreeId, is_private: bool, } impl IndexTextFile { fn new(file: &dyn language::File, cx: &App) -> Self { Self { path: file.path().clone(), full_path: file.full_path(cx), path_style: file.path_style(cx), file_name: file.file_name(cx).to_string(), worktree_id: file.worktree_id(cx), is_private: file.is_private(), } } } impl language::File for IndexTextFile { fn as_local(&self) -> Option<&dyn language::LocalFile> { None } fn disk_state(&self) -> language::DiskState { language::DiskState::Historic { was_deleted: false } } fn path(&self) -> &Arc<RelPath> { &self.path } fn full_path(&self, _: &App) -> PathBuf { self.full_path.clone() } fn path_style(&self, _: &App) -> PathStyle { self.path_style } fn file_name<'a>(&'a self, _: &'a App) -> &'a str { &self.file_name } fn worktree_id(&self, _: &App) -> WorktreeId { self.worktree_id } fn to_proto(&self, _: &App) -> rpc::proto::File { rpc::proto::File { worktree_id: self.worktree_id.to_proto(), entry_id: None, path: self.path.as_ref().as_unix_str().to_owned(), mtime: None, is_deleted: false, is_historic: true, } } fn is_private(&self) -> bool { self.is_private } } #[derive(Debug, Clone, Copy)] pub enum GitAccess { /// Either: /// - the user owns `.git` /// - the user doesn't own `.git`, but has both of: /// - OS-level read permissions /// - the directory is marked as safe (git config safe.directory) Yes, /// The user is not the owner of `.git`, and one of the following is true: /// - the directory is not marked as safe (git config safe.directory) /// - the user does not have OS-level read permissions to `.git` No, } enum GitStoreState { Local { next_repository_id: Arc<AtomicU64>, downstream: Option<LocalDownstreamState>, project_environment: Entity<ProjectEnvironment>, fs: Arc<dyn Fs>, _fs_watches: Box<[Task<()>]>, }, 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, pub diff_stat: Option<DiffStat>, pub staged_diff_stat: Option<DiffStat>, pub unstaged_diff_stat: Option<DiffStat>, } 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_unix_str().to_owned(), simple_status, status: Some(status_to_proto(self.status)), diff_stat_added: self.diff_stat.map(|ds| ds.added), diff_stat_deleted: self.diff_stat.map(|ds| ds.deleted), staged_diff_stat_added: self.staged_diff_stat.map(|ds| ds.added), staged_diff_stat_deleted: self.staged_diff_stat.map(|ds| ds.deleted), unstaged_diff_stat_added: self.unstaged_diff_stat.map(|ds| ds.added), unstaged_diff_stat_deleted: self.unstaged_diff_stat.map(|ds| ds.deleted), } } } 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)?; let diff_stat = match (value.diff_stat_added, value.diff_stat_deleted) { (Some(added), Some(deleted)) => Some(DiffStat { added, deleted }), _ => None, }; let staged_diff_stat = match (value.staged_diff_stat_added, value.staged_diff_stat_deleted) { (Some(added), Some(deleted)) => Some(DiffStat { added, deleted }), _ => None, }; let unstaged_diff_stat = match ( value.unstaged_diff_stat_added, value.unstaged_diff_stat_deleted, ) { (Some(added), Some(deleted)) => Some(DiffStat { added, deleted }), _ => None, }; Ok(Self { repo_path, status, diff_stat, staged_diff_stat, unstaged_diff_stat, }) } } 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 merge_heads_by_conflicted_path: TreeMap<RepoPath, Vec<Option<SharedString>>>, pub message: Option<SharedString>, } #[derive(Clone)] pub enum CommitDataState { Loading(Option<Shared<oneshot::Receiver<Arc<CommitData>>>>), Loaded(Arc<CommitData>), } #[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 dot_git_abs_path: Arc<Path>, /// Absolute path to the directory holding this worktree's Git state. /// /// For a linked worktree this is the worktree-specific directory under the /// common Git directory, such as `<main>/.git/worktrees/<name>`. pub repository_dir_abs_path: Arc<Path>, /// Absolute path to the repository's common Git directory. /// /// For a normal checkout this is `<work_directory>/.git`. For a linked /// worktree this is the common Git directory shared by all worktrees. If /// that common directory is a bare repository, there may be no main /// worktree path to derive from it. pub common_dir_abs_path: Arc<Path>, pub path_style: PathStyle, pub branch: Option<Branch>, pub branch_list: Arc<[Branch]>, pub branch_list_error: Option<SharedString>, 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, pub linked_worktrees: Arc<[GitWorktree]>, } type JobId = u64; #[derive(Clone, Debug, PartialEq, Eq)] pub struct JobInfo { pub start: Instant, pub message: SharedString, } struct CommitDataHandler { _task: Task<()>, commit_data_request: async_channel::Sender<Oid>, completion_senders: HashMap<Oid, oneshot::Sender<Arc<CommitData>>>, pending_requests: HashSet<Oid>, } /// Represents the handler of a git cat-file --batch process within Zed /// It's used to lazily fetch commit data as needed (whatever a user is viewing) enum CommitDataHandlerState { /// The handler is open and processing requests Open(CommitDataHandler), /// The handler closed because it didn't receive any requests in the last 10s /// or hasn't been open before Closed, } enum NextCommitDataRequest { Request(BoxFuture<'static, Result<proto::GetCommitDataResponse>>), Idle, Closed, } pub struct InitialGitGraphData { fetch_task: Task<()>, pub error: Option<SharedString>, pub commit_data: Vec<Arc<InitialGraphCommitData>>, pub commit_oid_to_index: HashMap<Oid, usize>, subscribers: Vec<async_channel::Sender<Result<Vec<Arc<InitialGraphCommitData>>, SharedString>>>, } pub struct GraphDataResponse<'a> { pub commits: &'a [Arc<InitialGraphCommitData>], pub is_loading: bool, pub error: Option<SharedString>, } pub struct Repository { this: WeakEntity<Self>, snapshot: RepositorySnapshot, commit_message_buffer: Option<Entity<Buffer>>,