10872 lines · 412.5 KB · Rust
RawDownload
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.