git_store.rs

   1pub mod branch_diff;
   2mod conflict_set;
   3pub mod git_traversal;
   4pub mod pending_op;
   5
   6use crate::{
   7    ProjectEnvironment, ProjectItem, ProjectPath,
   8    buffer_store::{BufferStore, BufferStoreEvent},
   9    worktree_store::{WorktreeStore, WorktreeStoreEvent},
  10};
  11use anyhow::{Context as _, Result, anyhow, bail};
  12use askpass::{AskPassDelegate, EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs};
  13use buffer_diff::{BufferDiff, BufferDiffEvent};
  14use client::ProjectId;
  15use collections::HashMap;
  16pub use conflict_set::{ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate};
  17use fs::Fs;
  18use futures::{
  19    FutureExt, StreamExt,
  20    channel::{
  21        mpsc,
  22        oneshot::{self, Canceled},
  23    },
  24    future::{self, Shared},
  25    stream::FuturesOrdered,
  26};
  27use git::{
  28    BuildPermalinkParams, GitHostingProviderRegistry, Oid, RunHook,
  29    blame::Blame,
  30    parse_git_remote_url,
  31    repository::{
  32        Branch, CommitDetails, CommitDiff, CommitFile, CommitOptions, DiffType, FetchOptions,
  33        GitRepository, GitRepositoryCheckpoint, GraphCommitData, InitialGraphCommitData, LogOrder,
  34        LogSource, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode,
  35        UpstreamTrackingStatus, Worktree as GitWorktree,
  36    },
  37    stash::{GitStash, StashEntry},
  38    status::{
  39        DiffTreeType, FileStatus, GitSummary, StatusCode, TrackedStatus, TreeDiff, TreeDiffStatus,
  40        UnmergedStatus, UnmergedStatusCode,
  41    },
  42};
  43use gpui::{
  44    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
  45    WeakEntity,
  46};
  47use language::{
  48    Buffer, BufferEvent, Language, LanguageRegistry,
  49    proto::{deserialize_version, serialize_version},
  50};
  51use parking_lot::Mutex;
  52use pending_op::{PendingOp, PendingOpId, PendingOps, PendingOpsSummary};
  53use postage::stream::Stream as _;
  54use rpc::{
  55    AnyProtoClient, TypedEnvelope,
  56    proto::{self, git_reset, split_repository_update},
  57};
  58use serde::Deserialize;
  59use settings::WorktreeId;
  60use smol::future::yield_now;
  61use std::{
  62    cmp::Ordering,
  63    collections::{BTreeSet, HashSet, VecDeque},
  64    future::Future,
  65    mem,
  66    ops::Range,
  67    path::{Path, PathBuf},
  68    str::FromStr,
  69    sync::{
  70        Arc,
  71        atomic::{self, AtomicU64},
  72    },
  73    time::Instant,
  74};
  75use sum_tree::{Edit, SumTree, TreeSet};
  76use task::Shell;
  77use text::{Bias, BufferId};
  78use util::{
  79    ResultExt, debug_panic,
  80    paths::{PathStyle, SanitizedPath},
  81    post_inc,
  82    rel_path::RelPath,
  83};
  84use worktree::{
  85    File, PathChange, PathKey, PathProgress, PathSummary, PathTarget, ProjectEntryId,
  86    UpdatedGitRepositoriesSet, UpdatedGitRepository, Worktree,
  87};
  88use zeroize::Zeroize;
  89
  90pub struct GitStore {
  91    state: GitStoreState,
  92    buffer_store: Entity<BufferStore>,
  93    worktree_store: Entity<WorktreeStore>,
  94    repositories: HashMap<RepositoryId, Entity<Repository>>,
  95    worktree_ids: HashMap<RepositoryId, HashSet<WorktreeId>>,
  96    active_repo_id: Option<RepositoryId>,
  97    #[allow(clippy::type_complexity)]
  98    loading_diffs:
  99        HashMap<(BufferId, DiffKind), Shared<Task<Result<Entity<BufferDiff>, Arc<anyhow::Error>>>>>,
 100    diffs: HashMap<BufferId, Entity<BufferGitState>>,
 101    shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>,
 102    _subscriptions: Vec<Subscription>,
 103}
 104
 105#[derive(Default)]
 106struct SharedDiffs {
 107    unstaged: Option<Entity<BufferDiff>>,
 108    uncommitted: Option<Entity<BufferDiff>>,
 109}
 110
 111struct BufferGitState {
 112    unstaged_diff: Option<WeakEntity<BufferDiff>>,
 113    uncommitted_diff: Option<WeakEntity<BufferDiff>>,
 114    oid_diffs: HashMap<Option<git::Oid>, WeakEntity<BufferDiff>>,
 115    conflict_set: Option<WeakEntity<ConflictSet>>,
 116    recalculate_diff_task: Option<Task<Result<()>>>,
 117    reparse_conflict_markers_task: Option<Task<Result<()>>>,
 118    language: Option<Arc<Language>>,
 119    language_registry: Option<Arc<LanguageRegistry>>,
 120    conflict_updated_futures: Vec<oneshot::Sender<()>>,
 121    recalculating_tx: postage::watch::Sender<bool>,
 122
 123    /// These operation counts are used to ensure that head and index text
 124    /// values read from the git repository are up-to-date with any hunk staging
 125    /// operations that have been performed on the BufferDiff.
 126    ///
 127    /// The operation count is incremented immediately when the user initiates a
 128    /// hunk stage/unstage operation. Then, upon finishing writing the new index
 129    /// text do disk, the `operation count as of write` is updated to reflect
 130    /// the operation count that prompted the write.
 131    hunk_staging_operation_count: usize,
 132    hunk_staging_operation_count_as_of_write: usize,
 133
 134    head_text: Option<Arc<str>>,
 135    index_text: Option<Arc<str>>,
 136    oid_texts: HashMap<git::Oid, Arc<str>>,
 137    head_changed: bool,
 138    index_changed: bool,
 139    language_changed: bool,
 140}
 141
 142#[derive(Clone, Debug)]
 143enum DiffBasesChange {
 144    SetIndex(Option<String>),
 145    SetHead(Option<String>),
 146    SetEach {
 147        index: Option<String>,
 148        head: Option<String>,
 149    },
 150    SetBoth(Option<String>),
 151}
 152
 153#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 154enum DiffKind {
 155    Unstaged,
 156    Uncommitted,
 157    SinceOid(Option<git::Oid>),
 158}
 159
 160enum GitStoreState {
 161    Local {
 162        next_repository_id: Arc<AtomicU64>,
 163        downstream: Option<LocalDownstreamState>,
 164        project_environment: Entity<ProjectEnvironment>,
 165        fs: Arc<dyn Fs>,
 166    },
 167    Remote {
 168        upstream_client: AnyProtoClient,
 169        upstream_project_id: u64,
 170        downstream: Option<(AnyProtoClient, ProjectId)>,
 171    },
 172}
 173
 174enum DownstreamUpdate {
 175    UpdateRepository(RepositorySnapshot),
 176    RemoveRepository(RepositoryId),
 177}
 178
 179struct LocalDownstreamState {
 180    client: AnyProtoClient,
 181    project_id: ProjectId,
 182    updates_tx: mpsc::UnboundedSender<DownstreamUpdate>,
 183    _task: Task<Result<()>>,
 184}
 185
 186#[derive(Clone, Debug)]
 187pub struct GitStoreCheckpoint {
 188    checkpoints_by_work_dir_abs_path: HashMap<Arc<Path>, GitRepositoryCheckpoint>,
 189}
 190
 191#[derive(Clone, Debug, PartialEq, Eq)]
 192pub struct StatusEntry {
 193    pub repo_path: RepoPath,
 194    pub status: FileStatus,
 195}
 196
 197impl StatusEntry {
 198    fn to_proto(&self) -> proto::StatusEntry {
 199        let simple_status = match self.status {
 200            FileStatus::Ignored | FileStatus::Untracked => proto::GitStatus::Added as i32,
 201            FileStatus::Unmerged { .. } => proto::GitStatus::Conflict as i32,
 202            FileStatus::Tracked(TrackedStatus {
 203                index_status,
 204                worktree_status,
 205            }) => tracked_status_to_proto(if worktree_status != StatusCode::Unmodified {
 206                worktree_status
 207            } else {
 208                index_status
 209            }),
 210        };
 211
 212        proto::StatusEntry {
 213            repo_path: self.repo_path.to_proto(),
 214            simple_status,
 215            status: Some(status_to_proto(self.status)),
 216        }
 217    }
 218}
 219
 220impl TryFrom<proto::StatusEntry> for StatusEntry {
 221    type Error = anyhow::Error;
 222
 223    fn try_from(value: proto::StatusEntry) -> Result<Self, Self::Error> {
 224        let repo_path = RepoPath::from_proto(&value.repo_path).context("invalid repo path")?;
 225        let status = status_from_proto(value.simple_status, value.status)?;
 226        Ok(Self { repo_path, status })
 227    }
 228}
 229
 230impl sum_tree::Item for StatusEntry {
 231    type Summary = PathSummary<GitSummary>;
 232
 233    fn summary(&self, _: <Self::Summary as sum_tree::Summary>::Context<'_>) -> Self::Summary {
 234        PathSummary {
 235            max_path: self.repo_path.as_ref().clone(),
 236            item_summary: self.status.summary(),
 237        }
 238    }
 239}
 240
 241impl sum_tree::KeyedItem for StatusEntry {
 242    type Key = PathKey;
 243
 244    fn key(&self) -> Self::Key {
 245        PathKey(self.repo_path.as_ref().clone())
 246    }
 247}
 248
 249#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 250pub struct RepositoryId(pub u64);
 251
 252#[derive(Clone, Debug, Default, PartialEq, Eq)]
 253pub struct MergeDetails {
 254    pub conflicted_paths: TreeSet<RepoPath>,
 255    pub message: Option<SharedString>,
 256    pub heads: Vec<Option<SharedString>>,
 257}
 258
 259#[derive(Clone)]
 260pub enum CommitDataState {
 261    Loading,
 262    Loaded(Arc<GraphCommitData>),
 263}
 264
 265#[derive(Clone, Debug, PartialEq, Eq)]
 266pub struct RepositorySnapshot {
 267    pub id: RepositoryId,
 268    pub statuses_by_path: SumTree<StatusEntry>,
 269    pub work_directory_abs_path: Arc<Path>,
 270    pub path_style: PathStyle,
 271    pub branch: Option<Branch>,
 272    pub head_commit: Option<CommitDetails>,
 273    pub scan_id: u64,
 274    pub merge: MergeDetails,
 275    pub remote_origin_url: Option<String>,
 276    pub remote_upstream_url: Option<String>,
 277    pub stash_entries: GitStash,
 278}
 279
 280type JobId = u64;
 281
 282#[derive(Clone, Debug, PartialEq, Eq)]
 283pub struct JobInfo {
 284    pub start: Instant,
 285    pub message: SharedString,
 286}
 287
 288struct GraphCommitDataHandler {
 289    _task: Task<()>,
 290    commit_data_request: smol::channel::Sender<Oid>,
 291}
 292
 293enum GraphCommitHandlerState {
 294    Starting,
 295    Open(GraphCommitDataHandler),
 296    Closed,
 297}
 298
 299pub struct Repository {
 300    this: WeakEntity<Self>,
 301    snapshot: RepositorySnapshot,
 302    commit_message_buffer: Option<Entity<Buffer>>,
 303    git_store: WeakEntity<GitStore>,
 304    // For a local repository, holds paths that have had worktree events since the last status scan completed,
 305    // and that should be examined during the next status scan.
 306    paths_needing_status_update: Vec<Vec<RepoPath>>,
 307    job_sender: mpsc::UnboundedSender<GitJob>,
 308    active_jobs: HashMap<JobId, JobInfo>,
 309    pending_ops: SumTree<PendingOps>,
 310    job_id: JobId,
 311    askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,
 312    latest_askpass_id: u64,
 313    repository_state: Shared<Task<Result<RepositoryState, String>>>,
 314    pub initial_graph_data: HashMap<
 315        (LogOrder, LogSource),
 316        (
 317            Task<Result<(), SharedString>>,
 318            Vec<Arc<InitialGraphCommitData>>,
 319        ),
 320    >,
 321    graph_commit_data_handler: GraphCommitHandlerState,
 322    commit_data: HashMap<Oid, CommitDataState>,
 323}
 324
 325impl std::ops::Deref for Repository {
 326    type Target = RepositorySnapshot;
 327
 328    fn deref(&self) -> &Self::Target {
 329        &self.snapshot
 330    }
 331}
 332
 333#[derive(Clone)]
 334pub struct LocalRepositoryState {
 335    pub fs: Arc<dyn Fs>,
 336    pub backend: Arc<dyn GitRepository>,
 337    pub environment: Arc<HashMap<String, String>>,
 338}
 339
 340impl LocalRepositoryState {
 341    async fn new(
 342        work_directory_abs_path: Arc<Path>,
 343        dot_git_abs_path: Arc<Path>,
 344        project_environment: WeakEntity<ProjectEnvironment>,
 345        fs: Arc<dyn Fs>,
 346        cx: &mut AsyncApp,
 347    ) -> anyhow::Result<Self> {
 348        let environment = project_environment
 349                .update(cx, |project_environment, cx| {
 350                    project_environment.local_directory_environment(&Shell::System, work_directory_abs_path.clone(), cx)
 351                })?
 352                .await
 353                .unwrap_or_else(|| {
 354                    log::error!("failed to get working directory environment for repository {work_directory_abs_path:?}");
 355                    HashMap::default()
 356                });
 357        let search_paths = environment.get("PATH").map(|val| val.to_owned());
 358        let backend = cx
 359            .background_spawn({
 360                let fs = fs.clone();
 361                async move {
 362                    let system_git_binary_path = search_paths
 363                        .and_then(|search_paths| {
 364                            which::which_in("git", Some(search_paths), &work_directory_abs_path)
 365                                .ok()
 366                        })
 367                        .or_else(|| which::which("git").ok());
 368                    fs.open_repo(&dot_git_abs_path, system_git_binary_path.as_deref())
 369                        .with_context(|| format!("opening repository at {dot_git_abs_path:?}"))
 370                }
 371            })
 372            .await?;
 373        Ok(LocalRepositoryState {
 374            backend,
 375            environment: Arc::new(environment),
 376            fs,
 377        })
 378    }
 379}
 380
 381#[derive(Clone)]
 382pub struct RemoteRepositoryState {
 383    pub project_id: ProjectId,
 384    pub client: AnyProtoClient,
 385}
 386
 387#[derive(Clone)]
 388pub enum RepositoryState {
 389    Local(LocalRepositoryState),
 390    Remote(RemoteRepositoryState),
 391}
 392
 393#[derive(Clone, Debug, PartialEq, Eq)]
 394pub enum RepositoryEvent {
 395    StatusesChanged,
 396    MergeHeadsChanged,
 397    BranchChanged,
 398    StashEntriesChanged,
 399    PendingOpsChanged { pending_ops: SumTree<PendingOps> },
 400    GitGraphCountUpdated((LogOrder, LogSource), usize),
 401}
 402
 403#[derive(Clone, Debug)]
 404pub struct JobsUpdated;
 405
 406#[derive(Debug)]
 407pub enum GitStoreEvent {
 408    ActiveRepositoryChanged(Option<RepositoryId>),
 409    /// Bool is true when the repository that's updated is the active repository
 410    RepositoryUpdated(RepositoryId, RepositoryEvent, bool),
 411    RepositoryAdded,
 412    RepositoryRemoved(RepositoryId),
 413    IndexWriteError(anyhow::Error),
 414    JobsUpdated,
 415    ConflictsUpdated,
 416}
 417
 418impl EventEmitter<RepositoryEvent> for Repository {}
 419impl EventEmitter<JobsUpdated> for Repository {}
 420impl EventEmitter<GitStoreEvent> for GitStore {}
 421
 422pub struct GitJob {
 423    job: Box<dyn FnOnce(RepositoryState, &mut AsyncApp) -> Task<()>>,
 424    key: Option<GitJobKey>,
 425}
 426
 427#[derive(PartialEq, Eq)]
 428enum GitJobKey {
 429    WriteIndex(Vec<RepoPath>),
 430    ReloadBufferDiffBases,
 431    RefreshStatuses,
 432    ReloadGitState,
 433}
 434
 435impl GitStore {
 436    pub fn local(
 437        worktree_store: &Entity<WorktreeStore>,
 438        buffer_store: Entity<BufferStore>,
 439        environment: Entity<ProjectEnvironment>,
 440        fs: Arc<dyn Fs>,
 441        cx: &mut Context<Self>,
 442    ) -> Self {
 443        Self::new(
 444            worktree_store.clone(),
 445            buffer_store,
 446            GitStoreState::Local {
 447                next_repository_id: Arc::new(AtomicU64::new(1)),
 448                downstream: None,
 449                project_environment: environment,
 450                fs,
 451            },
 452            cx,
 453        )
 454    }
 455
 456    pub fn remote(
 457        worktree_store: &Entity<WorktreeStore>,
 458        buffer_store: Entity<BufferStore>,
 459        upstream_client: AnyProtoClient,
 460        project_id: u64,
 461        cx: &mut Context<Self>,
 462    ) -> Self {
 463        Self::new(
 464            worktree_store.clone(),
 465            buffer_store,
 466            GitStoreState::Remote {
 467                upstream_client,
 468                upstream_project_id: project_id,
 469                downstream: None,
 470            },
 471            cx,
 472        )
 473    }
 474
 475    fn new(
 476        worktree_store: Entity<WorktreeStore>,
 477        buffer_store: Entity<BufferStore>,
 478        state: GitStoreState,
 479        cx: &mut Context<Self>,
 480    ) -> Self {
 481        let _subscriptions = vec![
 482            cx.subscribe(&worktree_store, Self::on_worktree_store_event),
 483            cx.subscribe(&buffer_store, Self::on_buffer_store_event),
 484        ];
 485
 486        GitStore {
 487            state,
 488            buffer_store,
 489            worktree_store,
 490            repositories: HashMap::default(),
 491            worktree_ids: HashMap::default(),
 492            active_repo_id: None,
 493            _subscriptions,
 494            loading_diffs: HashMap::default(),
 495            shared_diffs: HashMap::default(),
 496            diffs: HashMap::default(),
 497        }
 498    }
 499
 500    pub fn init(client: &AnyProtoClient) {
 501        client.add_entity_request_handler(Self::handle_get_remotes);
 502        client.add_entity_request_handler(Self::handle_get_branches);
 503        client.add_entity_request_handler(Self::handle_get_default_branch);
 504        client.add_entity_request_handler(Self::handle_change_branch);
 505        client.add_entity_request_handler(Self::handle_create_branch);
 506        client.add_entity_request_handler(Self::handle_rename_branch);
 507        client.add_entity_request_handler(Self::handle_create_remote);
 508        client.add_entity_request_handler(Self::handle_remove_remote);
 509        client.add_entity_request_handler(Self::handle_delete_branch);
 510        client.add_entity_request_handler(Self::handle_git_init);
 511        client.add_entity_request_handler(Self::handle_push);
 512        client.add_entity_request_handler(Self::handle_pull);
 513        client.add_entity_request_handler(Self::handle_fetch);
 514        client.add_entity_request_handler(Self::handle_stage);
 515        client.add_entity_request_handler(Self::handle_unstage);
 516        client.add_entity_request_handler(Self::handle_stash);
 517        client.add_entity_request_handler(Self::handle_stash_pop);
 518        client.add_entity_request_handler(Self::handle_stash_apply);
 519        client.add_entity_request_handler(Self::handle_stash_drop);
 520        client.add_entity_request_handler(Self::handle_commit);
 521        client.add_entity_request_handler(Self::handle_run_hook);
 522        client.add_entity_request_handler(Self::handle_reset);
 523        client.add_entity_request_handler(Self::handle_show);
 524        client.add_entity_request_handler(Self::handle_load_commit_diff);
 525        client.add_entity_request_handler(Self::handle_file_history);
 526        client.add_entity_request_handler(Self::handle_checkout_files);
 527        client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
 528        client.add_entity_request_handler(Self::handle_set_index_text);
 529        client.add_entity_request_handler(Self::handle_askpass);
 530        client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
 531        client.add_entity_request_handler(Self::handle_git_diff);
 532        client.add_entity_request_handler(Self::handle_tree_diff);
 533        client.add_entity_request_handler(Self::handle_get_blob_content);
 534        client.add_entity_request_handler(Self::handle_open_unstaged_diff);
 535        client.add_entity_request_handler(Self::handle_open_uncommitted_diff);
 536        client.add_entity_message_handler(Self::handle_update_diff_bases);
 537        client.add_entity_request_handler(Self::handle_get_permalink_to_line);
 538        client.add_entity_request_handler(Self::handle_blame_buffer);
 539        client.add_entity_message_handler(Self::handle_update_repository);
 540        client.add_entity_message_handler(Self::handle_remove_repository);
 541        client.add_entity_request_handler(Self::handle_git_clone);
 542        client.add_entity_request_handler(Self::handle_get_worktrees);
 543        client.add_entity_request_handler(Self::handle_create_worktree);
 544    }
 545
 546    pub fn is_local(&self) -> bool {
 547        matches!(self.state, GitStoreState::Local { .. })
 548    }
 549    pub fn set_active_repo_for_path(&mut self, project_path: &ProjectPath, cx: &mut Context<Self>) {
 550        if let Some((repo, _)) = self.repository_and_path_for_project_path(project_path, cx) {
 551            let id = repo.read(cx).id;
 552            if self.active_repo_id != Some(id) {
 553                self.active_repo_id = Some(id);
 554                cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
 555            }
 556        }
 557    }
 558
 559    pub fn shared(&mut self, project_id: u64, client: AnyProtoClient, cx: &mut Context<Self>) {
 560        match &mut self.state {
 561            GitStoreState::Remote {
 562                downstream: downstream_client,
 563                ..
 564            } => {
 565                for repo in self.repositories.values() {
 566                    let update = repo.read(cx).snapshot.initial_update(project_id);
 567                    for update in split_repository_update(update) {
 568                        client.send(update).log_err();
 569                    }
 570                }
 571                *downstream_client = Some((client, ProjectId(project_id)));
 572            }
 573            GitStoreState::Local {
 574                downstream: downstream_client,
 575                ..
 576            } => {
 577                let mut snapshots = HashMap::default();
 578                let (updates_tx, mut updates_rx) = mpsc::unbounded();
 579                for repo in self.repositories.values() {
 580                    updates_tx
 581                        .unbounded_send(DownstreamUpdate::UpdateRepository(
 582                            repo.read(cx).snapshot.clone(),
 583                        ))
 584                        .ok();
 585                }
 586                *downstream_client = Some(LocalDownstreamState {
 587                    client: client.clone(),
 588                    project_id: ProjectId(project_id),
 589                    updates_tx,
 590                    _task: cx.spawn(async move |this, cx| {
 591                        cx.background_spawn(async move {
 592                            while let Some(update) = updates_rx.next().await {
 593                                match update {
 594                                    DownstreamUpdate::UpdateRepository(snapshot) => {
 595                                        if let Some(old_snapshot) = snapshots.get_mut(&snapshot.id)
 596                                        {
 597                                            let update =
 598                                                snapshot.build_update(old_snapshot, project_id);
 599                                            *old_snapshot = snapshot;
 600                                            for update in split_repository_update(update) {
 601                                                client.send(update)?;
 602                                            }
 603                                        } else {
 604                                            let update = snapshot.initial_update(project_id);
 605                                            for update in split_repository_update(update) {
 606                                                client.send(update)?;
 607                                            }
 608                                            snapshots.insert(snapshot.id, snapshot);
 609                                        }
 610                                    }
 611                                    DownstreamUpdate::RemoveRepository(id) => {
 612                                        client.send(proto::RemoveRepository {
 613                                            project_id,
 614                                            id: id.to_proto(),
 615                                        })?;
 616                                    }
 617                                }
 618                            }
 619                            anyhow::Ok(())
 620                        })
 621                        .await
 622                        .ok();
 623                        this.update(cx, |this, _| {
 624                            if let GitStoreState::Local {
 625                                downstream: downstream_client,
 626                                ..
 627                            } = &mut this.state
 628                            {
 629                                downstream_client.take();
 630                            } else {
 631                                unreachable!("unshared called on remote store");
 632                            }
 633                        })
 634                    }),
 635                });
 636            }
 637        }
 638    }
 639
 640    pub fn unshared(&mut self, _cx: &mut Context<Self>) {
 641        match &mut self.state {
 642            GitStoreState::Local {
 643                downstream: downstream_client,
 644                ..
 645            } => {
 646                downstream_client.take();
 647            }
 648            GitStoreState::Remote {
 649                downstream: downstream_client,
 650                ..
 651            } => {
 652                downstream_client.take();
 653            }
 654        }
 655        self.shared_diffs.clear();
 656    }
 657
 658    pub(crate) fn forget_shared_diffs_for(&mut self, peer_id: &proto::PeerId) {
 659        self.shared_diffs.remove(peer_id);
 660    }
 661
 662    pub fn active_repository(&self) -> Option<Entity<Repository>> {
 663        self.active_repo_id
 664            .as_ref()
 665            .map(|id| self.repositories[id].clone())
 666    }
 667
 668    pub fn open_unstaged_diff(
 669        &mut self,
 670        buffer: Entity<Buffer>,
 671        cx: &mut Context<Self>,
 672    ) -> Task<Result<Entity<BufferDiff>>> {
 673        let buffer_id = buffer.read(cx).remote_id();
 674        if let Some(diff_state) = self.diffs.get(&buffer_id)
 675            && let Some(unstaged_diff) = diff_state
 676                .read(cx)
 677                .unstaged_diff
 678                .as_ref()
 679                .and_then(|weak| weak.upgrade())
 680        {
 681            if let Some(task) =
 682                diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
 683            {
 684                return cx.background_executor().spawn(async move {
 685                    task.await;
 686                    Ok(unstaged_diff)
 687                });
 688            }
 689            return Task::ready(Ok(unstaged_diff));
 690        }
 691
 692        let Some((repo, repo_path)) =
 693            self.repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
 694        else {
 695            return Task::ready(Err(anyhow!("failed to find git repository for buffer")));
 696        };
 697
 698        let task = self
 699            .loading_diffs
 700            .entry((buffer_id, DiffKind::Unstaged))
 701            .or_insert_with(|| {
 702                let staged_text = repo.update(cx, |repo, cx| {
 703                    repo.load_staged_text(buffer_id, repo_path, cx)
 704                });
 705                cx.spawn(async move |this, cx| {
 706                    Self::open_diff_internal(
 707                        this,
 708                        DiffKind::Unstaged,
 709                        staged_text.await.map(DiffBasesChange::SetIndex),
 710                        buffer,
 711                        cx,
 712                    )
 713                    .await
 714                    .map_err(Arc::new)
 715                })
 716                .shared()
 717            })
 718            .clone();
 719
 720        cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
 721    }
 722
 723    pub fn open_diff_since(
 724        &mut self,
 725        oid: Option<git::Oid>,
 726        buffer: Entity<Buffer>,
 727        repo: Entity<Repository>,
 728        cx: &mut Context<Self>,
 729    ) -> Task<Result<Entity<BufferDiff>>> {
 730        let buffer_id = buffer.read(cx).remote_id();
 731
 732        if let Some(diff_state) = self.diffs.get(&buffer_id)
 733            && let Some(oid_diff) = diff_state.read(cx).oid_diff(oid)
 734        {
 735            if let Some(task) =
 736                diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
 737            {
 738                return cx.background_executor().spawn(async move {
 739                    task.await;
 740                    Ok(oid_diff)
 741                });
 742            }
 743            return Task::ready(Ok(oid_diff));
 744        }
 745
 746        let diff_kind = DiffKind::SinceOid(oid);
 747        if let Some(task) = self.loading_diffs.get(&(buffer_id, diff_kind)) {
 748            let task = task.clone();
 749            return cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) });
 750        }
 751
 752        let task = cx
 753            .spawn(async move |this, cx| {
 754                let result: Result<Entity<BufferDiff>> = async {
 755                    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
 756                    let language_registry =
 757                        buffer.update(cx, |buffer, _| buffer.language_registry());
 758                    let content: Option<Arc<str>> = match oid {
 759                        None => None,
 760                        Some(oid) => Some(
 761                            repo.update(cx, |repo, cx| repo.load_blob_content(oid, cx))
 762                                .await?
 763                                .into(),
 764                        ),
 765                    };
 766                    let buffer_diff = cx.new(|cx| BufferDiff::new(&buffer_snapshot, cx));
 767
 768                    buffer_diff
 769                        .update(cx, |buffer_diff, cx| {
 770                            buffer_diff.language_changed(
 771                                buffer_snapshot.language().cloned(),
 772                                language_registry,
 773                                cx,
 774                            );
 775                            buffer_diff.set_base_text(
 776                                content.clone(),
 777                                buffer_snapshot.language().cloned(),
 778                                buffer_snapshot.text,
 779                                cx,
 780                            )
 781                        })
 782                        .await?;
 783                    let unstaged_diff = this
 784                        .update(cx, |this, cx| this.open_unstaged_diff(buffer.clone(), cx))?
 785                        .await?;
 786                    buffer_diff.update(cx, |buffer_diff, _| {
 787                        buffer_diff.set_secondary_diff(unstaged_diff);
 788                    });
 789
 790                    this.update(cx, |this, cx| {
 791                        cx.subscribe(&buffer_diff, Self::on_buffer_diff_event)
 792                            .detach();
 793
 794                        this.loading_diffs.remove(&(buffer_id, diff_kind));
 795
 796                        let git_store = cx.weak_entity();
 797                        let diff_state = this
 798                            .diffs
 799                            .entry(buffer_id)
 800                            .or_insert_with(|| cx.new(|_| BufferGitState::new(git_store)));
 801
 802                        diff_state.update(cx, |state, _| {
 803                            if let Some(oid) = oid {
 804                                if let Some(content) = content {
 805                                    state.oid_texts.insert(oid, content);
 806                                }
 807                            }
 808                            state.oid_diffs.insert(oid, buffer_diff.downgrade());
 809                        });
 810                    })?;
 811
 812                    Ok(buffer_diff)
 813                }
 814                .await;
 815                result.map_err(Arc::new)
 816            })
 817            .shared();
 818
 819        self.loading_diffs
 820            .insert((buffer_id, diff_kind), task.clone());
 821        cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
 822    }
 823
 824    #[ztracing::instrument(skip_all)]
 825    pub fn open_uncommitted_diff(
 826        &mut self,
 827        buffer: Entity<Buffer>,
 828        cx: &mut Context<Self>,
 829    ) -> Task<Result<Entity<BufferDiff>>> {
 830        let buffer_id = buffer.read(cx).remote_id();
 831
 832        if let Some(diff_state) = self.diffs.get(&buffer_id)
 833            && let Some(uncommitted_diff) = diff_state
 834                .read(cx)
 835                .uncommitted_diff
 836                .as_ref()
 837                .and_then(|weak| weak.upgrade())
 838        {
 839            if let Some(task) =
 840                diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
 841            {
 842                return cx.background_executor().spawn(async move {
 843                    task.await;
 844                    Ok(uncommitted_diff)
 845                });
 846            }
 847            return Task::ready(Ok(uncommitted_diff));
 848        }
 849
 850        let Some((repo, repo_path)) =
 851            self.repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
 852        else {
 853            return Task::ready(Err(anyhow!("failed to find git repository for buffer")));
 854        };
 855
 856        let task = self
 857            .loading_diffs
 858            .entry((buffer_id, DiffKind::Uncommitted))
 859            .or_insert_with(|| {
 860                let changes = repo.update(cx, |repo, cx| {
 861                    repo.load_committed_text(buffer_id, repo_path, cx)
 862                });
 863
 864                // todo(lw): hot foreground spawn
 865                cx.spawn(async move |this, cx| {
 866                    Self::open_diff_internal(this, DiffKind::Uncommitted, changes.await, buffer, cx)
 867                        .await
 868                        .map_err(Arc::new)
 869                })
 870                .shared()
 871            })
 872            .clone();
 873
 874        cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
 875    }
 876
 877    #[ztracing::instrument(skip_all)]
 878    async fn open_diff_internal(
 879        this: WeakEntity<Self>,
 880        kind: DiffKind,
 881        texts: Result<DiffBasesChange>,
 882        buffer_entity: Entity<Buffer>,
 883        cx: &mut AsyncApp,
 884    ) -> Result<Entity<BufferDiff>> {
 885        let diff_bases_change = match texts {
 886            Err(e) => {
 887                this.update(cx, |this, cx| {
 888                    let buffer = buffer_entity.read(cx);
 889                    let buffer_id = buffer.remote_id();
 890                    this.loading_diffs.remove(&(buffer_id, kind));
 891                })?;
 892                return Err(e);
 893            }
 894            Ok(change) => change,
 895        };
 896
 897        this.update(cx, |this, cx| {
 898            let buffer = buffer_entity.read(cx);
 899            let buffer_id = buffer.remote_id();
 900            let language = buffer.language().cloned();
 901            let language_registry = buffer.language_registry();
 902            let text_snapshot = buffer.text_snapshot();
 903            this.loading_diffs.remove(&(buffer_id, kind));
 904
 905            let git_store = cx.weak_entity();
 906            let diff_state = this
 907                .diffs
 908                .entry(buffer_id)
 909                .or_insert_with(|| cx.new(|_| BufferGitState::new(git_store)));
 910
 911            let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
 912
 913            cx.subscribe(&diff, Self::on_buffer_diff_event).detach();
 914            diff_state.update(cx, |diff_state, cx| {
 915                diff_state.language_changed = true;
 916                diff_state.language = language;
 917                diff_state.language_registry = language_registry;
 918
 919                match kind {
 920                    DiffKind::Unstaged => diff_state.unstaged_diff = Some(diff.downgrade()),
 921                    DiffKind::Uncommitted => {
 922                        let unstaged_diff = if let Some(diff) = diff_state.unstaged_diff() {
 923                            diff
 924                        } else {
 925                            let unstaged_diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
 926                            diff_state.unstaged_diff = Some(unstaged_diff.downgrade());
 927                            unstaged_diff
 928                        };
 929
 930                        diff.update(cx, |diff, _| diff.set_secondary_diff(unstaged_diff));
 931                        diff_state.uncommitted_diff = Some(diff.downgrade())
 932                    }
 933                    DiffKind::SinceOid(_) => {
 934                        unreachable!("open_diff_internal is not used for OID diffs")
 935                    }
 936                }
 937
 938                diff_state.diff_bases_changed(text_snapshot, Some(diff_bases_change), cx);
 939                let rx = diff_state.wait_for_recalculation();
 940
 941                anyhow::Ok(async move {
 942                    if let Some(rx) = rx {
 943                        rx.await;
 944                    }
 945                    Ok(diff)
 946                })
 947            })
 948        })??
 949        .await
 950    }
 951
 952    pub fn get_unstaged_diff(&self, buffer_id: BufferId, cx: &App) -> Option<Entity<BufferDiff>> {
 953        let diff_state = self.diffs.get(&buffer_id)?;
 954        diff_state.read(cx).unstaged_diff.as_ref()?.upgrade()
 955    }
 956
 957    pub fn get_uncommitted_diff(
 958        &self,
 959        buffer_id: BufferId,
 960        cx: &App,
 961    ) -> Option<Entity<BufferDiff>> {
 962        let diff_state = self.diffs.get(&buffer_id)?;
 963        diff_state.read(cx).uncommitted_diff.as_ref()?.upgrade()
 964    }
 965
 966    pub fn get_diff_since_oid(
 967        &self,
 968        buffer_id: BufferId,
 969        oid: Option<git::Oid>,
 970        cx: &App,
 971    ) -> Option<Entity<BufferDiff>> {
 972        let diff_state = self.diffs.get(&buffer_id)?;
 973        diff_state.read(cx).oid_diff(oid)
 974    }
 975
 976    pub fn open_conflict_set(
 977        &mut self,
 978        buffer: Entity<Buffer>,
 979        cx: &mut Context<Self>,
 980    ) -> Entity<ConflictSet> {
 981        log::debug!("open conflict set");
 982        let buffer_id = buffer.read(cx).remote_id();
 983
 984        if let Some(git_state) = self.diffs.get(&buffer_id)
 985            && let Some(conflict_set) = git_state
 986                .read(cx)
 987                .conflict_set
 988                .as_ref()
 989                .and_then(|weak| weak.upgrade())
 990        {
 991            let conflict_set = conflict_set;
 992            let buffer_snapshot = buffer.read(cx).text_snapshot();
 993
 994            git_state.update(cx, |state, cx| {
 995                let _ = state.reparse_conflict_markers(buffer_snapshot, cx);
 996            });
 997
 998            return conflict_set;
 999        }
1000
1001        let is_unmerged = self
1002            .repository_and_path_for_buffer_id(buffer_id, cx)
1003            .is_some_and(|(repo, path)| repo.read(cx).snapshot.has_conflict(&path));
1004        let git_store = cx.weak_entity();
1005        let buffer_git_state = self
1006            .diffs
1007            .entry(buffer_id)
1008            .or_insert_with(|| cx.new(|_| BufferGitState::new(git_store)));
1009        let conflict_set = cx.new(|cx| ConflictSet::new(buffer_id, is_unmerged, cx));
1010
1011        self._subscriptions
1012            .push(cx.subscribe(&conflict_set, |_, _, _, cx| {
1013                cx.emit(GitStoreEvent::ConflictsUpdated);
1014            }));
1015
1016        buffer_git_state.update(cx, |state, cx| {
1017            state.conflict_set = Some(conflict_set.downgrade());
1018            let buffer_snapshot = buffer.read(cx).text_snapshot();
1019            let _ = state.reparse_conflict_markers(buffer_snapshot, cx);
1020        });
1021
1022        conflict_set
1023    }
1024
1025    pub fn project_path_git_status(
1026        &self,
1027        project_path: &ProjectPath,
1028        cx: &App,
1029    ) -> Option<FileStatus> {
1030        let (repo, repo_path) = self.repository_and_path_for_project_path(project_path, cx)?;
1031        Some(repo.read(cx).status_for_path(&repo_path)?.status)
1032    }
1033
1034    pub fn checkpoint(&self, cx: &mut App) -> Task<Result<GitStoreCheckpoint>> {
1035        let mut work_directory_abs_paths = Vec::new();
1036        let mut checkpoints = Vec::new();
1037        for repository in self.repositories.values() {
1038            repository.update(cx, |repository, _| {
1039                work_directory_abs_paths.push(repository.snapshot.work_directory_abs_path.clone());
1040                checkpoints.push(repository.checkpoint().map(|checkpoint| checkpoint?));
1041            });
1042        }
1043
1044        cx.background_executor().spawn(async move {
1045            let checkpoints = future::try_join_all(checkpoints).await?;
1046            Ok(GitStoreCheckpoint {
1047                checkpoints_by_work_dir_abs_path: work_directory_abs_paths
1048                    .into_iter()
1049                    .zip(checkpoints)
1050                    .collect(),
1051            })
1052        })
1053    }
1054
1055    pub fn restore_checkpoint(
1056        &self,
1057        checkpoint: GitStoreCheckpoint,
1058        cx: &mut App,
1059    ) -> Task<Result<()>> {
1060        let repositories_by_work_dir_abs_path = self
1061            .repositories
1062            .values()
1063            .map(|repo| (repo.read(cx).snapshot.work_directory_abs_path.clone(), repo))
1064            .collect::<HashMap<_, _>>();
1065
1066        let mut tasks = Vec::new();
1067        for (work_dir_abs_path, checkpoint) in checkpoint.checkpoints_by_work_dir_abs_path {
1068            if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path) {
1069                let restore = repository.update(cx, |repository, _| {
1070                    repository.restore_checkpoint(checkpoint)
1071                });
1072                tasks.push(async move { restore.await? });
1073            }
1074        }
1075        cx.background_spawn(async move {
1076            future::try_join_all(tasks).await?;
1077            Ok(())
1078        })
1079    }
1080
1081    /// Compares two checkpoints, returning true if they are equal.
1082    pub fn compare_checkpoints(
1083        &self,
1084        left: GitStoreCheckpoint,
1085        mut right: GitStoreCheckpoint,
1086        cx: &mut App,
1087    ) -> Task<Result<bool>> {
1088        let repositories_by_work_dir_abs_path = self
1089            .repositories
1090            .values()
1091            .map(|repo| (repo.read(cx).snapshot.work_directory_abs_path.clone(), repo))
1092            .collect::<HashMap<_, _>>();
1093
1094        let mut tasks = Vec::new();
1095        for (work_dir_abs_path, left_checkpoint) in left.checkpoints_by_work_dir_abs_path {
1096            if let Some(right_checkpoint) = right
1097                .checkpoints_by_work_dir_abs_path
1098                .remove(&work_dir_abs_path)
1099            {
1100                if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path)
1101                {
1102                    let compare = repository.update(cx, |repository, _| {
1103                        repository.compare_checkpoints(left_checkpoint, right_checkpoint)
1104                    });
1105
1106                    tasks.push(async move { compare.await? });
1107                }
1108            } else {
1109                return Task::ready(Ok(false));
1110            }
1111        }
1112        cx.background_spawn(async move {
1113            Ok(future::try_join_all(tasks)
1114                .await?
1115                .into_iter()
1116                .all(|result| result))
1117        })
1118    }
1119
1120    /// Blames a buffer.
1121    pub fn blame_buffer(
1122        &self,
1123        buffer: &Entity<Buffer>,
1124        version: Option<clock::Global>,
1125        cx: &mut Context<Self>,
1126    ) -> Task<Result<Option<Blame>>> {
1127        let buffer = buffer.read(cx);
1128        let Some((repo, repo_path)) =
1129            self.repository_and_path_for_buffer_id(buffer.remote_id(), cx)
1130        else {
1131            return Task::ready(Err(anyhow!("failed to find a git repository for buffer")));
1132        };
1133        let content = match &version {
1134            Some(version) => buffer.rope_for_version(version),
1135            None => buffer.as_rope().clone(),
1136        };
1137        let line_ending = buffer.line_ending();
1138        let version = version.unwrap_or(buffer.version());
1139        let buffer_id = buffer.remote_id();
1140
1141        let repo = repo.downgrade();
1142        cx.spawn(async move |_, cx| {
1143            let repository_state = repo
1144                .update(cx, |repo, _| repo.repository_state.clone())?
1145                .await
1146                .map_err(|err| anyhow::anyhow!(err))?;
1147            match repository_state {
1148                RepositoryState::Local(LocalRepositoryState { backend, .. }) => backend
1149                    .blame(repo_path.clone(), content, line_ending)
1150                    .await
1151                    .with_context(|| format!("Failed to blame {:?}", repo_path.as_ref()))
1152                    .map(Some),
1153                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
1154                    let response = client
1155                        .request(proto::BlameBuffer {
1156                            project_id: project_id.to_proto(),
1157                            buffer_id: buffer_id.into(),
1158                            version: serialize_version(&version),
1159                        })
1160                        .await?;
1161                    Ok(deserialize_blame_buffer_response(response))
1162                }
1163            }
1164        })
1165    }
1166
1167    pub fn file_history(
1168        &self,
1169        repo: &Entity<Repository>,
1170        path: RepoPath,
1171        cx: &mut App,
1172    ) -> Task<Result<git::repository::FileHistory>> {
1173        let rx = repo.update(cx, |repo, _| repo.file_history(path));
1174
1175        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
1176    }
1177
1178    pub fn file_history_paginated(
1179        &self,
1180        repo: &Entity<Repository>,
1181        path: RepoPath,
1182        skip: usize,
1183        limit: Option<usize>,
1184        cx: &mut App,
1185    ) -> Task<Result<git::repository::FileHistory>> {
1186        let rx = repo.update(cx, |repo, _| repo.file_history_paginated(path, skip, limit));
1187
1188        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
1189    }
1190
1191    pub fn get_permalink_to_line(
1192        &self,
1193        buffer: &Entity<Buffer>,
1194        selection: Range<u32>,
1195        cx: &mut App,
1196    ) -> Task<Result<url::Url>> {
1197        let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
1198            return Task::ready(Err(anyhow!("buffer has no file")));
1199        };
1200
1201        let Some((repo, repo_path)) = self.repository_and_path_for_project_path(
1202            &(file.worktree.read(cx).id(), file.path.clone()).into(),
1203            cx,
1204        ) else {
1205            // If we're not in a Git repo, check whether this is a Rust source
1206            // file in the Cargo registry (presumably opened with go-to-definition
1207            // from a normal Rust file). If so, we can put together a permalink
1208            // using crate metadata.
1209            if buffer
1210                .read(cx)
1211                .language()
1212                .is_none_or(|lang| lang.name() != "Rust".into())
1213            {
1214                return Task::ready(Err(anyhow!("no permalink available")));
1215            }
1216            let file_path = file.worktree.read(cx).absolutize(&file.path);
1217            return cx.spawn(async move |cx| {
1218                let provider_registry = cx.update(GitHostingProviderRegistry::default_global);
1219                get_permalink_in_rust_registry_src(provider_registry, file_path, selection)
1220                    .context("no permalink available")
1221            });
1222        };
1223
1224        let buffer_id = buffer.read(cx).remote_id();
1225        let branch = repo.read(cx).branch.clone();
1226        let remote = branch
1227            .as_ref()
1228            .and_then(|b| b.upstream.as_ref())
1229            .and_then(|b| b.remote_name())
1230            .unwrap_or("origin")
1231            .to_string();
1232
1233        let rx = repo.update(cx, |repo, _| {
1234            repo.send_job(None, move |state, cx| async move {
1235                match state {
1236                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
1237                        let origin_url = backend
1238                            .remote_url(&remote)
1239                            .await
1240                            .with_context(|| format!("remote \"{remote}\" not found"))?;
1241
1242                        let sha = backend.head_sha().await.context("reading HEAD SHA")?;
1243
1244                        let provider_registry =
1245                            cx.update(GitHostingProviderRegistry::default_global);
1246
1247                        let (provider, remote) =
1248                            parse_git_remote_url(provider_registry, &origin_url)
1249                                .context("parsing Git remote URL")?;
1250
1251                        Ok(provider.build_permalink(
1252                            remote,
1253                            BuildPermalinkParams::new(&sha, &repo_path, Some(selection)),
1254                        ))
1255                    }
1256                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
1257                        let response = client
1258                            .request(proto::GetPermalinkToLine {
1259                                project_id: project_id.to_proto(),
1260                                buffer_id: buffer_id.into(),
1261                                selection: Some(proto::Range {
1262                                    start: selection.start as u64,
1263                                    end: selection.end as u64,
1264                                }),
1265                            })
1266                            .await?;
1267
1268                        url::Url::parse(&response.permalink).context("failed to parse permalink")
1269                    }
1270                }
1271            })
1272        });
1273        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
1274    }
1275
1276    fn downstream_client(&self) -> Option<(AnyProtoClient, ProjectId)> {
1277        match &self.state {
1278            GitStoreState::Local {
1279                downstream: downstream_client,
1280                ..
1281            } => downstream_client
1282                .as_ref()
1283                .map(|state| (state.client.clone(), state.project_id)),
1284            GitStoreState::Remote {
1285                downstream: downstream_client,
1286                ..
1287            } => downstream_client.clone(),
1288        }
1289    }
1290
1291    fn upstream_client(&self) -> Option<AnyProtoClient> {
1292        match &self.state {
1293            GitStoreState::Local { .. } => None,
1294            GitStoreState::Remote {
1295                upstream_client, ..
1296            } => Some(upstream_client.clone()),
1297        }
1298    }
1299
1300    fn on_worktree_store_event(
1301        &mut self,
1302        worktree_store: Entity<WorktreeStore>,
1303        event: &WorktreeStoreEvent,
1304        cx: &mut Context<Self>,
1305    ) {
1306        let GitStoreState::Local {
1307            project_environment,
1308            downstream,
1309            next_repository_id,
1310            fs,
1311        } = &self.state
1312        else {
1313            return;
1314        };
1315
1316        match event {
1317            WorktreeStoreEvent::WorktreeUpdatedEntries(worktree_id, updated_entries) => {
1318                if let Some(worktree) = self
1319                    .worktree_store
1320                    .read(cx)
1321                    .worktree_for_id(*worktree_id, cx)
1322                {
1323                    let paths_by_git_repo =
1324                        self.process_updated_entries(&worktree, updated_entries, cx);
1325                    let downstream = downstream
1326                        .as_ref()
1327                        .map(|downstream| downstream.updates_tx.clone());
1328                    cx.spawn(async move |_, cx| {
1329                        let paths_by_git_repo = paths_by_git_repo.await;
1330                        for (repo, paths) in paths_by_git_repo {
1331                            repo.update(cx, |repo, cx| {
1332                                repo.paths_changed(paths, downstream.clone(), cx);
1333                            });
1334                        }
1335                    })
1336                    .detach();
1337                }
1338            }
1339            WorktreeStoreEvent::WorktreeUpdatedGitRepositories(worktree_id, changed_repos) => {
1340                let Some(worktree) = worktree_store.read(cx).worktree_for_id(*worktree_id, cx)
1341                else {
1342                    return;
1343                };
1344                if !worktree.read(cx).is_visible() {
1345                    log::debug!(
1346                        "not adding repositories for local worktree {:?} because it's not visible",
1347                        worktree.read(cx).abs_path()
1348                    );
1349                    return;
1350                }
1351                self.update_repositories_from_worktree(
1352                    *worktree_id,
1353                    project_environment.clone(),
1354                    next_repository_id.clone(),
1355                    downstream
1356                        .as_ref()
1357                        .map(|downstream| downstream.updates_tx.clone()),
1358                    changed_repos.clone(),
1359                    fs.clone(),
1360                    cx,
1361                );
1362                self.local_worktree_git_repos_changed(worktree, changed_repos, cx);
1363            }
1364            WorktreeStoreEvent::WorktreeRemoved(_entity_id, worktree_id) => {
1365                let repos_without_worktree: Vec<RepositoryId> = self
1366                    .worktree_ids
1367                    .iter_mut()
1368                    .filter_map(|(repo_id, worktree_ids)| {
1369                        worktree_ids.remove(worktree_id);
1370                        if worktree_ids.is_empty() {
1371                            Some(*repo_id)
1372                        } else {
1373                            None
1374                        }
1375                    })
1376                    .collect();
1377                let is_active_repo_removed = repos_without_worktree
1378                    .iter()
1379                    .any(|repo_id| self.active_repo_id == Some(*repo_id));
1380
1381                for repo_id in repos_without_worktree {
1382                    self.repositories.remove(&repo_id);
1383                    self.worktree_ids.remove(&repo_id);
1384                    if let Some(updates_tx) =
1385                        downstream.as_ref().map(|downstream| &downstream.updates_tx)
1386                    {
1387                        updates_tx
1388                            .unbounded_send(DownstreamUpdate::RemoveRepository(repo_id))
1389                            .ok();
1390                    }
1391                }
1392
1393                if is_active_repo_removed {
1394                    if let Some((&repo_id, _)) = self.repositories.iter().next() {
1395                        self.active_repo_id = Some(repo_id);
1396                        cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(repo_id)));
1397                    } else {
1398                        self.active_repo_id = None;
1399                        cx.emit(GitStoreEvent::ActiveRepositoryChanged(None));
1400                    }
1401                }
1402            }
1403            _ => {}
1404        }
1405    }
1406    fn on_repository_event(
1407        &mut self,
1408        repo: Entity<Repository>,
1409        event: &RepositoryEvent,
1410        cx: &mut Context<Self>,
1411    ) {
1412        let id = repo.read(cx).id;
1413        let repo_snapshot = repo.read(cx).snapshot.clone();
1414        for (buffer_id, diff) in self.diffs.iter() {
1415            if let Some((buffer_repo, repo_path)) =
1416                self.repository_and_path_for_buffer_id(*buffer_id, cx)
1417                && buffer_repo == repo
1418            {
1419                diff.update(cx, |diff, cx| {
1420                    if let Some(conflict_set) = &diff.conflict_set {
1421                        let conflict_status_changed =
1422                            conflict_set.update(cx, |conflict_set, cx| {
1423                                let has_conflict = repo_snapshot.has_conflict(&repo_path);
1424                                conflict_set.set_has_conflict(has_conflict, cx)
1425                            })?;
1426                        if conflict_status_changed {
1427                            let buffer_store = self.buffer_store.read(cx);
1428                            if let Some(buffer) = buffer_store.get(*buffer_id) {
1429                                let _ = diff
1430                                    .reparse_conflict_markers(buffer.read(cx).text_snapshot(), cx);
1431                            }
1432                        }
1433                    }
1434                    anyhow::Ok(())
1435                })
1436                .ok();
1437            }
1438        }
1439        cx.emit(GitStoreEvent::RepositoryUpdated(
1440            id,
1441            event.clone(),
1442            self.active_repo_id == Some(id),
1443        ))
1444    }
1445
1446    fn on_jobs_updated(&mut self, _: Entity<Repository>, _: &JobsUpdated, cx: &mut Context<Self>) {
1447        cx.emit(GitStoreEvent::JobsUpdated)
1448    }
1449
1450    /// Update our list of repositories and schedule git scans in response to a notification from a worktree,
1451    fn update_repositories_from_worktree(
1452        &mut self,
1453        worktree_id: WorktreeId,
1454        project_environment: Entity<ProjectEnvironment>,
1455        next_repository_id: Arc<AtomicU64>,
1456        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
1457        updated_git_repositories: UpdatedGitRepositoriesSet,
1458        fs: Arc<dyn Fs>,
1459        cx: &mut Context<Self>,
1460    ) {
1461        let mut removed_ids = Vec::new();
1462        for update in updated_git_repositories.iter() {
1463            if let Some((id, existing)) = self.repositories.iter().find(|(_, repo)| {
1464                let existing_work_directory_abs_path =
1465                    repo.read(cx).work_directory_abs_path.clone();
1466                Some(&existing_work_directory_abs_path)
1467                    == update.old_work_directory_abs_path.as_ref()
1468                    || Some(&existing_work_directory_abs_path)
1469                        == update.new_work_directory_abs_path.as_ref()
1470            }) {
1471                let repo_id = *id;
1472                if let Some(new_work_directory_abs_path) =
1473                    update.new_work_directory_abs_path.clone()
1474                {
1475                    self.worktree_ids
1476                        .entry(repo_id)
1477                        .or_insert_with(HashSet::new)
1478                        .insert(worktree_id);
1479                    existing.update(cx, |existing, cx| {
1480                        existing.snapshot.work_directory_abs_path = new_work_directory_abs_path;
1481                        existing.schedule_scan(updates_tx.clone(), cx);
1482                    });
1483                } else {
1484                    if let Some(worktree_ids) = self.worktree_ids.get_mut(&repo_id) {
1485                        worktree_ids.remove(&worktree_id);
1486                        if worktree_ids.is_empty() {
1487                            removed_ids.push(repo_id);
1488                        }
1489                    }
1490                }
1491            } else if let UpdatedGitRepository {
1492                new_work_directory_abs_path: Some(work_directory_abs_path),
1493                dot_git_abs_path: Some(dot_git_abs_path),
1494                repository_dir_abs_path: Some(_repository_dir_abs_path),
1495                common_dir_abs_path: Some(_common_dir_abs_path),
1496                ..
1497            } = update
1498            {
1499                let id = RepositoryId(next_repository_id.fetch_add(1, atomic::Ordering::Release));
1500                let git_store = cx.weak_entity();
1501                let repo = cx.new(|cx| {
1502                    let mut repo = Repository::local(
1503                        id,
1504                        work_directory_abs_path.clone(),
1505                        dot_git_abs_path.clone(),
1506                        project_environment.downgrade(),
1507                        fs.clone(),
1508                        git_store,
1509                        cx,
1510                    );
1511                    if let Some(updates_tx) = updates_tx.as_ref() {
1512                        // trigger an empty `UpdateRepository` to ensure remote active_repo_id is set correctly
1513                        updates_tx
1514                            .unbounded_send(DownstreamUpdate::UpdateRepository(repo.snapshot()))
1515                            .ok();
1516                    }
1517                    repo.schedule_scan(updates_tx.clone(), cx);
1518                    repo
1519                });
1520                self._subscriptions
1521                    .push(cx.subscribe(&repo, Self::on_repository_event));
1522                self._subscriptions
1523                    .push(cx.subscribe(&repo, Self::on_jobs_updated));
1524                self.repositories.insert(id, repo);
1525                self.worktree_ids.insert(id, HashSet::from([worktree_id]));
1526                cx.emit(GitStoreEvent::RepositoryAdded);
1527                self.active_repo_id.get_or_insert_with(|| {
1528                    cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
1529                    id
1530                });
1531            }
1532        }
1533
1534        for id in removed_ids {
1535            if self.active_repo_id == Some(id) {
1536                self.active_repo_id = None;
1537                cx.emit(GitStoreEvent::ActiveRepositoryChanged(None));
1538            }
1539            self.repositories.remove(&id);
1540            if let Some(updates_tx) = updates_tx.as_ref() {
1541                updates_tx
1542                    .unbounded_send(DownstreamUpdate::RemoveRepository(id))
1543                    .ok();
1544            }
1545        }
1546    }
1547
1548    fn on_buffer_store_event(
1549        &mut self,
1550        _: Entity<BufferStore>,
1551        event: &BufferStoreEvent,
1552        cx: &mut Context<Self>,
1553    ) {
1554        match event {
1555            BufferStoreEvent::BufferAdded(buffer) => {
1556                cx.subscribe(buffer, |this, buffer, event, cx| {
1557                    if let BufferEvent::LanguageChanged(_) = event {
1558                        let buffer_id = buffer.read(cx).remote_id();
1559                        if let Some(diff_state) = this.diffs.get(&buffer_id) {
1560                            diff_state.update(cx, |diff_state, cx| {
1561                                diff_state.buffer_language_changed(buffer, cx);
1562                            });
1563                        }
1564                    }
1565                })
1566                .detach();
1567            }
1568            BufferStoreEvent::SharedBufferClosed(peer_id, buffer_id) => {
1569                if let Some(diffs) = self.shared_diffs.get_mut(peer_id) {
1570                    diffs.remove(buffer_id);
1571                }
1572            }
1573            BufferStoreEvent::BufferDropped(buffer_id) => {
1574                self.diffs.remove(buffer_id);
1575                for diffs in self.shared_diffs.values_mut() {
1576                    diffs.remove(buffer_id);
1577                }
1578            }
1579            BufferStoreEvent::BufferChangedFilePath { buffer, .. } => {
1580                // Whenever a buffer's file path changes, it's possible that the
1581                // new path is actually a path that is being tracked by a git
1582                // repository. In that case, we'll want to update the buffer's
1583                // `BufferDiffState`, in case it already has one.
1584                let buffer_id = buffer.read(cx).remote_id();
1585                let diff_state = self.diffs.get(&buffer_id);
1586                let repo = self.repository_and_path_for_buffer_id(buffer_id, cx);
1587
1588                if let Some(diff_state) = diff_state
1589                    && let Some((repo, repo_path)) = repo
1590                {
1591                    let buffer = buffer.clone();
1592                    let diff_state = diff_state.clone();
1593
1594                    cx.spawn(async move |_git_store, cx| {
1595                        async {
1596                            let diff_bases_change = repo
1597                                .update(cx, |repo, cx| {
1598                                    repo.load_committed_text(buffer_id, repo_path, cx)
1599                                })
1600                                .await?;
1601
1602                            diff_state.update(cx, |diff_state, cx| {
1603                                let buffer_snapshot = buffer.read(cx).text_snapshot();
1604                                diff_state.diff_bases_changed(
1605                                    buffer_snapshot,
1606                                    Some(diff_bases_change),
1607                                    cx,
1608                                );
1609                            });
1610                            anyhow::Ok(())
1611                        }
1612                        .await
1613                        .log_err();
1614                    })
1615                    .detach();
1616                }
1617            }
1618            _ => {}
1619        }
1620    }
1621
1622    pub fn recalculate_buffer_diffs(
1623        &mut self,
1624        buffers: Vec<Entity<Buffer>>,
1625        cx: &mut Context<Self>,
1626    ) -> impl Future<Output = ()> + use<> {
1627        let mut futures = Vec::new();
1628        for buffer in buffers {
1629            if let Some(diff_state) = self.diffs.get_mut(&buffer.read(cx).remote_id()) {
1630                let buffer = buffer.read(cx).text_snapshot();
1631                diff_state.update(cx, |diff_state, cx| {
1632                    diff_state.recalculate_diffs(buffer.clone(), cx);
1633                    futures.extend(diff_state.wait_for_recalculation().map(FutureExt::boxed));
1634                });
1635                futures.push(diff_state.update(cx, |diff_state, cx| {
1636                    diff_state
1637                        .reparse_conflict_markers(buffer, cx)
1638                        .map(|_| {})
1639                        .boxed()
1640                }));
1641            }
1642        }
1643        async move {
1644            futures::future::join_all(futures).await;
1645        }
1646    }
1647
1648    fn on_buffer_diff_event(
1649        &mut self,
1650        diff: Entity<buffer_diff::BufferDiff>,
1651        event: &BufferDiffEvent,
1652        cx: &mut Context<Self>,
1653    ) {
1654        if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
1655            let buffer_id = diff.read(cx).buffer_id;
1656            if let Some(diff_state) = self.diffs.get(&buffer_id) {
1657                let hunk_staging_operation_count = diff_state.update(cx, |diff_state, _| {
1658                    diff_state.hunk_staging_operation_count += 1;
1659                    diff_state.hunk_staging_operation_count
1660                });
1661                if let Some((repo, path)) = self.repository_and_path_for_buffer_id(buffer_id, cx) {
1662                    let recv = repo.update(cx, |repo, cx| {
1663                        log::debug!("hunks changed for {}", path.as_unix_str());
1664                        repo.spawn_set_index_text_job(
1665                            path,
1666                            new_index_text.as_ref().map(|rope| rope.to_string()),
1667                            Some(hunk_staging_operation_count),
1668                            cx,
1669                        )
1670                    });
1671                    let diff = diff.downgrade();
1672                    cx.spawn(async move |this, cx| {
1673                        if let Ok(Err(error)) = cx.background_spawn(recv).await {
1674                            diff.update(cx, |diff, cx| {
1675                                diff.clear_pending_hunks(cx);
1676                            })
1677                            .ok();
1678                            this.update(cx, |_, cx| cx.emit(GitStoreEvent::IndexWriteError(error)))
1679                                .ok();
1680                        }
1681                    })
1682                    .detach();
1683                }
1684            }
1685        }
1686    }
1687
1688    fn local_worktree_git_repos_changed(
1689        &mut self,
1690        worktree: Entity<Worktree>,
1691        changed_repos: &UpdatedGitRepositoriesSet,
1692        cx: &mut Context<Self>,
1693    ) {
1694        log::debug!("local worktree repos changed");
1695        debug_assert!(worktree.read(cx).is_local());
1696
1697        for repository in self.repositories.values() {
1698            repository.update(cx, |repository, cx| {
1699                let repo_abs_path = &repository.work_directory_abs_path;
1700                if changed_repos.iter().any(|update| {
1701                    update.old_work_directory_abs_path.as_ref() == Some(repo_abs_path)
1702                        || update.new_work_directory_abs_path.as_ref() == Some(repo_abs_path)
1703                }) {
1704                    repository.reload_buffer_diff_bases(cx);
1705                }
1706            });
1707        }
1708    }
1709
1710    pub fn repositories(&self) -> &HashMap<RepositoryId, Entity<Repository>> {
1711        &self.repositories
1712    }
1713
1714    pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
1715        let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
1716        let status = repo.read(cx).snapshot.status_for_path(&path)?;
1717        Some(status.status)
1718    }
1719
1720    pub fn repository_and_path_for_buffer_id(
1721        &self,
1722        buffer_id: BufferId,
1723        cx: &App,
1724    ) -> Option<(Entity<Repository>, RepoPath)> {
1725        let buffer = self.buffer_store.read(cx).get(buffer_id)?;
1726        let project_path = buffer.read(cx).project_path(cx)?;
1727        self.repository_and_path_for_project_path(&project_path, cx)
1728    }
1729
1730    pub fn repository_and_path_for_project_path(
1731        &self,
1732        path: &ProjectPath,
1733        cx: &App,
1734    ) -> Option<(Entity<Repository>, RepoPath)> {
1735        let abs_path = self.worktree_store.read(cx).absolutize(path, cx)?;
1736        self.repositories
1737            .values()
1738            .filter_map(|repo| {
1739                let repo_path = repo.read(cx).abs_path_to_repo_path(&abs_path)?;
1740                Some((repo.clone(), repo_path))
1741            })
1742            .max_by_key(|(repo, _)| repo.read(cx).work_directory_abs_path.clone())
1743    }
1744
1745    pub fn git_init(
1746        &self,
1747        path: Arc<Path>,
1748        fallback_branch_name: String,
1749        cx: &App,
1750    ) -> Task<Result<()>> {
1751        match &self.state {
1752            GitStoreState::Local { fs, .. } => {
1753                let fs = fs.clone();
1754                cx.background_executor()
1755                    .spawn(async move { fs.git_init(&path, fallback_branch_name).await })
1756            }
1757            GitStoreState::Remote {
1758                upstream_client,
1759                upstream_project_id: project_id,
1760                ..
1761            } => {
1762                let client = upstream_client.clone();
1763                let project_id = *project_id;
1764                cx.background_executor().spawn(async move {
1765                    client
1766                        .request(proto::GitInit {
1767                            project_id: project_id,
1768                            abs_path: path.to_string_lossy().into_owned(),
1769                            fallback_branch_name,
1770                        })
1771                        .await?;
1772                    Ok(())
1773                })
1774            }
1775        }
1776    }
1777
1778    pub fn git_clone(
1779        &self,
1780        repo: String,
1781        path: impl Into<Arc<std::path::Path>>,
1782        cx: &App,
1783    ) -> Task<Result<()>> {
1784        let path = path.into();
1785        match &self.state {
1786            GitStoreState::Local { fs, .. } => {
1787                let fs = fs.clone();
1788                cx.background_executor()
1789                    .spawn(async move { fs.git_clone(&repo, &path).await })
1790            }
1791            GitStoreState::Remote {
1792                upstream_client,
1793                upstream_project_id,
1794                ..
1795            } => {
1796                if upstream_client.is_via_collab() {
1797                    return Task::ready(Err(anyhow!(
1798                        "Git Clone isn't supported for project guests"
1799                    )));
1800                }
1801                let request = upstream_client.request(proto::GitClone {
1802                    project_id: *upstream_project_id,
1803                    abs_path: path.to_string_lossy().into_owned(),
1804                    remote_repo: repo,
1805                });
1806
1807                cx.background_spawn(async move {
1808                    let result = request.await?;
1809
1810                    match result.success {
1811                        true => Ok(()),
1812                        false => Err(anyhow!("Git Clone failed")),
1813                    }
1814                })
1815            }
1816        }
1817    }
1818
1819    async fn handle_update_repository(
1820        this: Entity<Self>,
1821        envelope: TypedEnvelope<proto::UpdateRepository>,
1822        mut cx: AsyncApp,
1823    ) -> Result<()> {
1824        this.update(&mut cx, |this, cx| {
1825            let path_style = this.worktree_store.read(cx).path_style();
1826            let mut update = envelope.payload;
1827
1828            let id = RepositoryId::from_proto(update.id);
1829            let client = this.upstream_client().context("no upstream client")?;
1830
1831            let mut repo_subscription = None;
1832            let repo = this.repositories.entry(id).or_insert_with(|| {
1833                let git_store = cx.weak_entity();
1834                let repo = cx.new(|cx| {
1835                    Repository::remote(
1836                        id,
1837                        Path::new(&update.abs_path).into(),
1838                        path_style,
1839                        ProjectId(update.project_id),
1840                        client,
1841                        git_store,
1842                        cx,
1843                    )
1844                });
1845                repo_subscription = Some(cx.subscribe(&repo, Self::on_repository_event));
1846                cx.emit(GitStoreEvent::RepositoryAdded);
1847                repo
1848            });
1849            this._subscriptions.extend(repo_subscription);
1850
1851            repo.update(cx, {
1852                let update = update.clone();
1853                |repo, cx| repo.apply_remote_update(update, cx)
1854            })?;
1855
1856            this.active_repo_id.get_or_insert_with(|| {
1857                cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
1858                id
1859            });
1860
1861            if let Some((client, project_id)) = this.downstream_client() {
1862                update.project_id = project_id.to_proto();
1863                client.send(update).log_err();
1864            }
1865            Ok(())
1866        })
1867    }
1868
1869    async fn handle_remove_repository(
1870        this: Entity<Self>,
1871        envelope: TypedEnvelope<proto::RemoveRepository>,
1872        mut cx: AsyncApp,
1873    ) -> Result<()> {
1874        this.update(&mut cx, |this, cx| {
1875            let mut update = envelope.payload;
1876            let id = RepositoryId::from_proto(update.id);
1877            this.repositories.remove(&id);
1878            if let Some((client, project_id)) = this.downstream_client() {
1879                update.project_id = project_id.to_proto();
1880                client.send(update).log_err();
1881            }
1882            if this.active_repo_id == Some(id) {
1883                this.active_repo_id = None;
1884                cx.emit(GitStoreEvent::ActiveRepositoryChanged(None));
1885            }
1886            cx.emit(GitStoreEvent::RepositoryRemoved(id));
1887        });
1888        Ok(())
1889    }
1890
1891    async fn handle_git_init(
1892        this: Entity<Self>,
1893        envelope: TypedEnvelope<proto::GitInit>,
1894        cx: AsyncApp,
1895    ) -> Result<proto::Ack> {
1896        let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
1897        let name = envelope.payload.fallback_branch_name;
1898        cx.update(|cx| this.read(cx).git_init(path, name, cx))
1899            .await?;
1900
1901        Ok(proto::Ack {})
1902    }
1903
1904    async fn handle_git_clone(
1905        this: Entity<Self>,
1906        envelope: TypedEnvelope<proto::GitClone>,
1907        cx: AsyncApp,
1908    ) -> Result<proto::GitCloneResponse> {
1909        let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
1910        let repo_name = envelope.payload.remote_repo;
1911        let result = cx
1912            .update(|cx| this.read(cx).git_clone(repo_name, path, cx))
1913            .await;
1914
1915        Ok(proto::GitCloneResponse {
1916            success: result.is_ok(),
1917        })
1918    }
1919
1920    async fn handle_fetch(
1921        this: Entity<Self>,
1922        envelope: TypedEnvelope<proto::Fetch>,
1923        mut cx: AsyncApp,
1924    ) -> Result<proto::RemoteMessageResponse> {
1925        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1926        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1927        let fetch_options = FetchOptions::from_proto(envelope.payload.remote);
1928        let askpass_id = envelope.payload.askpass_id;
1929
1930        let askpass = make_remote_delegate(
1931            this,
1932            envelope.payload.project_id,
1933            repository_id,
1934            askpass_id,
1935            &mut cx,
1936        );
1937
1938        let remote_output = repository_handle
1939            .update(&mut cx, |repository_handle, cx| {
1940                repository_handle.fetch(fetch_options, askpass, cx)
1941            })
1942            .await??;
1943
1944        Ok(proto::RemoteMessageResponse {
1945            stdout: remote_output.stdout,
1946            stderr: remote_output.stderr,
1947        })
1948    }
1949
1950    async fn handle_push(
1951        this: Entity<Self>,
1952        envelope: TypedEnvelope<proto::Push>,
1953        mut cx: AsyncApp,
1954    ) -> Result<proto::RemoteMessageResponse> {
1955        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1956        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1957
1958        let askpass_id = envelope.payload.askpass_id;
1959        let askpass = make_remote_delegate(
1960            this,
1961            envelope.payload.project_id,
1962            repository_id,
1963            askpass_id,
1964            &mut cx,
1965        );
1966
1967        let options = envelope
1968            .payload
1969            .options
1970            .as_ref()
1971            .map(|_| match envelope.payload.options() {
1972                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
1973                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
1974            });
1975
1976        let branch_name = envelope.payload.branch_name.into();
1977        let remote_branch_name = envelope.payload.remote_branch_name.into();
1978        let remote_name = envelope.payload.remote_name.into();
1979
1980        let remote_output = repository_handle
1981            .update(&mut cx, |repository_handle, cx| {
1982                repository_handle.push(
1983                    branch_name,
1984                    remote_branch_name,
1985                    remote_name,
1986                    options,
1987                    askpass,
1988                    cx,
1989                )
1990            })
1991            .await??;
1992        Ok(proto::RemoteMessageResponse {
1993            stdout: remote_output.stdout,
1994            stderr: remote_output.stderr,
1995        })
1996    }
1997
1998    async fn handle_pull(
1999        this: Entity<Self>,
2000        envelope: TypedEnvelope<proto::Pull>,
2001        mut cx: AsyncApp,
2002    ) -> Result<proto::RemoteMessageResponse> {
2003        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2004        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2005        let askpass_id = envelope.payload.askpass_id;
2006        let askpass = make_remote_delegate(
2007            this,
2008            envelope.payload.project_id,
2009            repository_id,
2010            askpass_id,
2011            &mut cx,
2012        );
2013
2014        let branch_name = envelope.payload.branch_name.map(|name| name.into());
2015        let remote_name = envelope.payload.remote_name.into();
2016        let rebase = envelope.payload.rebase;
2017
2018        let remote_message = repository_handle
2019            .update(&mut cx, |repository_handle, cx| {
2020                repository_handle.pull(branch_name, remote_name, rebase, askpass, cx)
2021            })
2022            .await??;
2023
2024        Ok(proto::RemoteMessageResponse {
2025            stdout: remote_message.stdout,
2026            stderr: remote_message.stderr,
2027        })
2028    }
2029
2030    async fn handle_stage(
2031        this: Entity<Self>,
2032        envelope: TypedEnvelope<proto::Stage>,
2033        mut cx: AsyncApp,
2034    ) -> Result<proto::Ack> {
2035        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2036        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2037
2038        let entries = envelope
2039            .payload
2040            .paths
2041            .into_iter()
2042            .map(|path| RepoPath::new(&path))
2043            .collect::<Result<Vec<_>>>()?;
2044
2045        repository_handle
2046            .update(&mut cx, |repository_handle, cx| {
2047                repository_handle.stage_entries(entries, cx)
2048            })
2049            .await?;
2050        Ok(proto::Ack {})
2051    }
2052
2053    async fn handle_unstage(
2054        this: Entity<Self>,
2055        envelope: TypedEnvelope<proto::Unstage>,
2056        mut cx: AsyncApp,
2057    ) -> Result<proto::Ack> {
2058        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2059        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2060
2061        let entries = envelope
2062            .payload
2063            .paths
2064            .into_iter()
2065            .map(|path| RepoPath::new(&path))
2066            .collect::<Result<Vec<_>>>()?;
2067
2068        repository_handle
2069            .update(&mut cx, |repository_handle, cx| {
2070                repository_handle.unstage_entries(entries, cx)
2071            })
2072            .await?;
2073
2074        Ok(proto::Ack {})
2075    }
2076
2077    async fn handle_stash(
2078        this: Entity<Self>,
2079        envelope: TypedEnvelope<proto::Stash>,
2080        mut cx: AsyncApp,
2081    ) -> Result<proto::Ack> {
2082        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2083        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2084
2085        let entries = envelope
2086            .payload
2087            .paths
2088            .into_iter()
2089            .map(|path| RepoPath::new(&path))
2090            .collect::<Result<Vec<_>>>()?;
2091
2092        repository_handle
2093            .update(&mut cx, |repository_handle, cx| {
2094                repository_handle.stash_entries(entries, cx)
2095            })
2096            .await?;
2097
2098        Ok(proto::Ack {})
2099    }
2100
2101    async fn handle_stash_pop(
2102        this: Entity<Self>,
2103        envelope: TypedEnvelope<proto::StashPop>,
2104        mut cx: AsyncApp,
2105    ) -> Result<proto::Ack> {
2106        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2107        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2108        let stash_index = envelope.payload.stash_index.map(|i| i as usize);
2109
2110        repository_handle
2111            .update(&mut cx, |repository_handle, cx| {
2112                repository_handle.stash_pop(stash_index, cx)
2113            })
2114            .await?;
2115
2116        Ok(proto::Ack {})
2117    }
2118
2119    async fn handle_stash_apply(
2120        this: Entity<Self>,
2121        envelope: TypedEnvelope<proto::StashApply>,
2122        mut cx: AsyncApp,
2123    ) -> Result<proto::Ack> {
2124        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2125        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2126        let stash_index = envelope.payload.stash_index.map(|i| i as usize);
2127
2128        repository_handle
2129            .update(&mut cx, |repository_handle, cx| {
2130                repository_handle.stash_apply(stash_index, cx)
2131            })
2132            .await?;
2133
2134        Ok(proto::Ack {})
2135    }
2136
2137    async fn handle_stash_drop(
2138        this: Entity<Self>,
2139        envelope: TypedEnvelope<proto::StashDrop>,
2140        mut cx: AsyncApp,
2141    ) -> Result<proto::Ack> {
2142        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2143        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2144        let stash_index = envelope.payload.stash_index.map(|i| i as usize);
2145
2146        repository_handle
2147            .update(&mut cx, |repository_handle, cx| {
2148                repository_handle.stash_drop(stash_index, cx)
2149            })
2150            .await??;
2151
2152        Ok(proto::Ack {})
2153    }
2154
2155    async fn handle_set_index_text(
2156        this: Entity<Self>,
2157        envelope: TypedEnvelope<proto::SetIndexText>,
2158        mut cx: AsyncApp,
2159    ) -> Result<proto::Ack> {
2160        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2161        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2162        let repo_path = RepoPath::from_proto(&envelope.payload.path)?;
2163
2164        repository_handle
2165            .update(&mut cx, |repository_handle, cx| {
2166                repository_handle.spawn_set_index_text_job(
2167                    repo_path,
2168                    envelope.payload.text,
2169                    None,
2170                    cx,
2171                )
2172            })
2173            .await??;
2174        Ok(proto::Ack {})
2175    }
2176
2177    async fn handle_run_hook(
2178        this: Entity<Self>,
2179        envelope: TypedEnvelope<proto::RunGitHook>,
2180        mut cx: AsyncApp,
2181    ) -> Result<proto::Ack> {
2182        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2183        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2184        let hook = RunHook::from_proto(envelope.payload.hook).context("invalid hook")?;
2185        repository_handle
2186            .update(&mut cx, |repository_handle, cx| {
2187                repository_handle.run_hook(hook, cx)
2188            })
2189            .await??;
2190        Ok(proto::Ack {})
2191    }
2192
2193    async fn handle_commit(
2194        this: Entity<Self>,
2195        envelope: TypedEnvelope<proto::Commit>,
2196        mut cx: AsyncApp,
2197    ) -> Result<proto::Ack> {
2198        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2199        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2200        let askpass_id = envelope.payload.askpass_id;
2201
2202        let askpass = make_remote_delegate(
2203            this,
2204            envelope.payload.project_id,
2205            repository_id,
2206            askpass_id,
2207            &mut cx,
2208        );
2209
2210        let message = SharedString::from(envelope.payload.message);
2211        let name = envelope.payload.name.map(SharedString::from);
2212        let email = envelope.payload.email.map(SharedString::from);
2213        let options = envelope.payload.options.unwrap_or_default();
2214
2215        repository_handle
2216            .update(&mut cx, |repository_handle, cx| {
2217                repository_handle.commit(
2218                    message,
2219                    name.zip(email),
2220                    CommitOptions {
2221                        amend: options.amend,
2222                        signoff: options.signoff,
2223                    },
2224                    askpass,
2225                    cx,
2226                )
2227            })
2228            .await??;
2229        Ok(proto::Ack {})
2230    }
2231
2232    async fn handle_get_remotes(
2233        this: Entity<Self>,
2234        envelope: TypedEnvelope<proto::GetRemotes>,
2235        mut cx: AsyncApp,
2236    ) -> Result<proto::GetRemotesResponse> {
2237        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2238        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2239
2240        let branch_name = envelope.payload.branch_name;
2241        let is_push = envelope.payload.is_push;
2242
2243        let remotes = repository_handle
2244            .update(&mut cx, |repository_handle, _| {
2245                repository_handle.get_remotes(branch_name, is_push)
2246            })
2247            .await??;
2248
2249        Ok(proto::GetRemotesResponse {
2250            remotes: remotes
2251                .into_iter()
2252                .map(|remotes| proto::get_remotes_response::Remote {
2253                    name: remotes.name.to_string(),
2254                })
2255                .collect::<Vec<_>>(),
2256        })
2257    }
2258
2259    async fn handle_get_worktrees(
2260        this: Entity<Self>,
2261        envelope: TypedEnvelope<proto::GitGetWorktrees>,
2262        mut cx: AsyncApp,
2263    ) -> Result<proto::GitWorktreesResponse> {
2264        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2265        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2266
2267        let worktrees = repository_handle
2268            .update(&mut cx, |repository_handle, _| {
2269                repository_handle.worktrees()
2270            })
2271            .await??;
2272
2273        Ok(proto::GitWorktreesResponse {
2274            worktrees: worktrees
2275                .into_iter()
2276                .map(|worktree| worktree_to_proto(&worktree))
2277                .collect::<Vec<_>>(),
2278        })
2279    }
2280
2281    async fn handle_create_worktree(
2282        this: Entity<Self>,
2283        envelope: TypedEnvelope<proto::GitCreateWorktree>,
2284        mut cx: AsyncApp,
2285    ) -> Result<proto::Ack> {
2286        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2287        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2288        let directory = PathBuf::from(envelope.payload.directory);
2289        let name = envelope.payload.name;
2290        let commit = envelope.payload.commit;
2291
2292        repository_handle
2293            .update(&mut cx, |repository_handle, _| {
2294                repository_handle.create_worktree(name, directory, commit)
2295            })
2296            .await??;
2297
2298        Ok(proto::Ack {})
2299    }
2300
2301    async fn handle_get_branches(
2302        this: Entity<Self>,
2303        envelope: TypedEnvelope<proto::GitGetBranches>,
2304        mut cx: AsyncApp,
2305    ) -> Result<proto::GitBranchesResponse> {
2306        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2307        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2308
2309        let branches = repository_handle
2310            .update(&mut cx, |repository_handle, _| repository_handle.branches())
2311            .await??;
2312
2313        Ok(proto::GitBranchesResponse {
2314            branches: branches
2315                .into_iter()
2316                .map(|branch| branch_to_proto(&branch))
2317                .collect::<Vec<_>>(),
2318        })
2319    }
2320    async fn handle_get_default_branch(
2321        this: Entity<Self>,
2322        envelope: TypedEnvelope<proto::GetDefaultBranch>,
2323        mut cx: AsyncApp,
2324    ) -> Result<proto::GetDefaultBranchResponse> {
2325        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2326        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2327
2328        let branch = repository_handle
2329            .update(&mut cx, |repository_handle, _| {
2330                repository_handle.default_branch(false)
2331            })
2332            .await??
2333            .map(Into::into);
2334
2335        Ok(proto::GetDefaultBranchResponse { branch })
2336    }
2337    async fn handle_create_branch(
2338        this: Entity<Self>,
2339        envelope: TypedEnvelope<proto::GitCreateBranch>,
2340        mut cx: AsyncApp,
2341    ) -> Result<proto::Ack> {
2342        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2343        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2344        let branch_name = envelope.payload.branch_name;
2345
2346        repository_handle
2347            .update(&mut cx, |repository_handle, _| {
2348                repository_handle.create_branch(branch_name, None)
2349            })
2350            .await??;
2351
2352        Ok(proto::Ack {})
2353    }
2354
2355    async fn handle_change_branch(
2356        this: Entity<Self>,
2357        envelope: TypedEnvelope<proto::GitChangeBranch>,
2358        mut cx: AsyncApp,
2359    ) -> Result<proto::Ack> {
2360        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2361        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2362        let branch_name = envelope.payload.branch_name;
2363
2364        repository_handle
2365            .update(&mut cx, |repository_handle, _| {
2366                repository_handle.change_branch(branch_name)
2367            })
2368            .await??;
2369
2370        Ok(proto::Ack {})
2371    }
2372
2373    async fn handle_rename_branch(
2374        this: Entity<Self>,
2375        envelope: TypedEnvelope<proto::GitRenameBranch>,
2376        mut cx: AsyncApp,
2377    ) -> Result<proto::Ack> {
2378        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2379        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2380        let branch = envelope.payload.branch;
2381        let new_name = envelope.payload.new_name;
2382
2383        repository_handle
2384            .update(&mut cx, |repository_handle, _| {
2385                repository_handle.rename_branch(branch, new_name)
2386            })
2387            .await??;
2388
2389        Ok(proto::Ack {})
2390    }
2391
2392    async fn handle_create_remote(
2393        this: Entity<Self>,
2394        envelope: TypedEnvelope<proto::GitCreateRemote>,
2395        mut cx: AsyncApp,
2396    ) -> Result<proto::Ack> {
2397        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2398        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2399        let remote_name = envelope.payload.remote_name;
2400        let remote_url = envelope.payload.remote_url;
2401
2402        repository_handle
2403            .update(&mut cx, |repository_handle, _| {
2404                repository_handle.create_remote(remote_name, remote_url)
2405            })
2406            .await??;
2407
2408        Ok(proto::Ack {})
2409    }
2410
2411    async fn handle_delete_branch(
2412        this: Entity<Self>,
2413        envelope: TypedEnvelope<proto::GitDeleteBranch>,
2414        mut cx: AsyncApp,
2415    ) -> Result<proto::Ack> {
2416        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2417        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2418        let branch_name = envelope.payload.branch_name;
2419
2420        repository_handle
2421            .update(&mut cx, |repository_handle, _| {
2422                repository_handle.delete_branch(branch_name)
2423            })
2424            .await??;
2425
2426        Ok(proto::Ack {})
2427    }
2428
2429    async fn handle_remove_remote(
2430        this: Entity<Self>,
2431        envelope: TypedEnvelope<proto::GitRemoveRemote>,
2432        mut cx: AsyncApp,
2433    ) -> Result<proto::Ack> {
2434        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2435        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2436        let remote_name = envelope.payload.remote_name;
2437
2438        repository_handle
2439            .update(&mut cx, |repository_handle, _| {
2440                repository_handle.remove_remote(remote_name)
2441            })
2442            .await??;
2443
2444        Ok(proto::Ack {})
2445    }
2446
2447    async fn handle_show(
2448        this: Entity<Self>,
2449        envelope: TypedEnvelope<proto::GitShow>,
2450        mut cx: AsyncApp,
2451    ) -> Result<proto::GitCommitDetails> {
2452        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2453        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2454
2455        let commit = repository_handle
2456            .update(&mut cx, |repository_handle, _| {
2457                repository_handle.show(envelope.payload.commit)
2458            })
2459            .await??;
2460        Ok(proto::GitCommitDetails {
2461            sha: commit.sha.into(),
2462            message: commit.message.into(),
2463            commit_timestamp: commit.commit_timestamp,
2464            author_email: commit.author_email.into(),
2465            author_name: commit.author_name.into(),
2466        })
2467    }
2468
2469    async fn handle_load_commit_diff(
2470        this: Entity<Self>,
2471        envelope: TypedEnvelope<proto::LoadCommitDiff>,
2472        mut cx: AsyncApp,
2473    ) -> Result<proto::LoadCommitDiffResponse> {
2474        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2475        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2476
2477        let commit_diff = repository_handle
2478            .update(&mut cx, |repository_handle, _| {
2479                repository_handle.load_commit_diff(envelope.payload.commit)
2480            })
2481            .await??;
2482        Ok(proto::LoadCommitDiffResponse {
2483            files: commit_diff
2484                .files
2485                .into_iter()
2486                .map(|file| proto::CommitFile {
2487                    path: file.path.to_proto(),
2488                    old_text: file.old_text,
2489                    new_text: file.new_text,
2490                    is_binary: file.is_binary,
2491                })
2492                .collect(),
2493        })
2494    }
2495
2496    async fn handle_file_history(
2497        this: Entity<Self>,
2498        envelope: TypedEnvelope<proto::GitFileHistory>,
2499        mut cx: AsyncApp,
2500    ) -> Result<proto::GitFileHistoryResponse> {
2501        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2502        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2503        let path = RepoPath::from_proto(&envelope.payload.path)?;
2504        let skip = envelope.payload.skip as usize;
2505        let limit = envelope.payload.limit.map(|l| l as usize);
2506
2507        let file_history = repository_handle
2508            .update(&mut cx, |repository_handle, _| {
2509                repository_handle.file_history_paginated(path, skip, limit)
2510            })
2511            .await??;
2512
2513        Ok(proto::GitFileHistoryResponse {
2514            entries: file_history
2515                .entries
2516                .into_iter()
2517                .map(|entry| proto::FileHistoryEntry {
2518                    sha: entry.sha.to_string(),
2519                    subject: entry.subject.to_string(),
2520                    message: entry.message.to_string(),
2521                    commit_timestamp: entry.commit_timestamp,
2522                    author_name: entry.author_name.to_string(),
2523                    author_email: entry.author_email.to_string(),
2524                })
2525                .collect(),
2526            path: file_history.path.to_proto(),
2527        })
2528    }
2529
2530    async fn handle_reset(
2531        this: Entity<Self>,
2532        envelope: TypedEnvelope<proto::GitReset>,
2533        mut cx: AsyncApp,
2534    ) -> Result<proto::Ack> {
2535        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2536        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2537
2538        let mode = match envelope.payload.mode() {
2539            git_reset::ResetMode::Soft => ResetMode::Soft,
2540            git_reset::ResetMode::Mixed => ResetMode::Mixed,
2541        };
2542
2543        repository_handle
2544            .update(&mut cx, |repository_handle, cx| {
2545                repository_handle.reset(envelope.payload.commit, mode, cx)
2546            })
2547            .await??;
2548        Ok(proto::Ack {})
2549    }
2550
2551    async fn handle_checkout_files(
2552        this: Entity<Self>,
2553        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
2554        mut cx: AsyncApp,
2555    ) -> Result<proto::Ack> {
2556        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2557        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2558        let paths = envelope
2559            .payload
2560            .paths
2561            .iter()
2562            .map(|s| RepoPath::from_proto(s))
2563            .collect::<Result<Vec<_>>>()?;
2564
2565        repository_handle
2566            .update(&mut cx, |repository_handle, cx| {
2567                repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
2568            })
2569            .await?;
2570        Ok(proto::Ack {})
2571    }
2572
2573    async fn handle_open_commit_message_buffer(
2574        this: Entity<Self>,
2575        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
2576        mut cx: AsyncApp,
2577    ) -> Result<proto::OpenBufferResponse> {
2578        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2579        let repository = Self::repository_for_request(&this, repository_id, &mut cx)?;
2580        let buffer = repository
2581            .update(&mut cx, |repository, cx| {
2582                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
2583            })
2584            .await?;
2585
2586        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id());
2587        this.update(&mut cx, |this, cx| {
2588            this.buffer_store.update(cx, |buffer_store, cx| {
2589                buffer_store
2590                    .create_buffer_for_peer(
2591                        &buffer,
2592                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
2593                        cx,
2594                    )
2595                    .detach_and_log_err(cx);
2596            })
2597        });
2598
2599        Ok(proto::OpenBufferResponse {
2600            buffer_id: buffer_id.to_proto(),
2601        })
2602    }
2603
2604    async fn handle_askpass(
2605        this: Entity<Self>,
2606        envelope: TypedEnvelope<proto::AskPassRequest>,
2607        mut cx: AsyncApp,
2608    ) -> Result<proto::AskPassResponse> {
2609        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2610        let repository = Self::repository_for_request(&this, repository_id, &mut cx)?;
2611
2612        let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone());
2613        let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
2614            debug_panic!("no askpass found");
2615            anyhow::bail!("no askpass found");
2616        };
2617
2618        let response = askpass
2619            .ask_password(envelope.payload.prompt)
2620            .await
2621            .ok_or_else(|| anyhow::anyhow!("askpass cancelled"))?;
2622
2623        delegates
2624            .lock()
2625            .insert(envelope.payload.askpass_id, askpass);
2626
2627        // In fact, we don't quite know what we're doing here, as we're sending askpass password unencrypted, but..
2628        Ok(proto::AskPassResponse {
2629            response: response.decrypt(IKnowWhatIAmDoingAndIHaveReadTheDocs)?,
2630        })
2631    }
2632
2633    async fn handle_check_for_pushed_commits(
2634        this: Entity<Self>,
2635        envelope: TypedEnvelope<proto::CheckForPushedCommits>,
2636        mut cx: AsyncApp,
2637    ) -> Result<proto::CheckForPushedCommitsResponse> {
2638        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2639        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2640
2641        let branches = repository_handle
2642            .update(&mut cx, |repository_handle, _| {
2643                repository_handle.check_for_pushed_commits()
2644            })
2645            .await??;
2646        Ok(proto::CheckForPushedCommitsResponse {
2647            pushed_to: branches
2648                .into_iter()
2649                .map(|commit| commit.to_string())
2650                .collect(),
2651        })
2652    }
2653
2654    async fn handle_git_diff(
2655        this: Entity<Self>,
2656        envelope: TypedEnvelope<proto::GitDiff>,
2657        mut cx: AsyncApp,
2658    ) -> Result<proto::GitDiffResponse> {
2659        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2660        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2661        let diff_type = match envelope.payload.diff_type() {
2662            proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
2663            proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
2664            proto::git_diff::DiffType::MergeBase => {
2665                let base_ref = envelope
2666                    .payload
2667                    .merge_base_ref
2668                    .ok_or_else(|| anyhow!("merge_base_ref is required for MergeBase diff type"))?;
2669                DiffType::MergeBase {
2670                    base_ref: base_ref.into(),
2671                }
2672            }
2673        };
2674
2675        let mut diff = repository_handle
2676            .update(&mut cx, |repository_handle, cx| {
2677                repository_handle.diff(diff_type, cx)
2678            })
2679            .await??;
2680        const ONE_MB: usize = 1_000_000;
2681        if diff.len() > ONE_MB {
2682            diff = diff.chars().take(ONE_MB).collect()
2683        }
2684
2685        Ok(proto::GitDiffResponse { diff })
2686    }
2687
2688    async fn handle_tree_diff(
2689        this: Entity<Self>,
2690        request: TypedEnvelope<proto::GetTreeDiff>,
2691        mut cx: AsyncApp,
2692    ) -> Result<proto::GetTreeDiffResponse> {
2693        let repository_id = RepositoryId(request.payload.repository_id);
2694        let diff_type = if request.payload.is_merge {
2695            DiffTreeType::MergeBase {
2696                base: request.payload.base.into(),
2697                head: request.payload.head.into(),
2698            }
2699        } else {
2700            DiffTreeType::Since {
2701                base: request.payload.base.into(),
2702                head: request.payload.head.into(),
2703            }
2704        };
2705
2706        let diff = this
2707            .update(&mut cx, |this, cx| {
2708                let repository = this.repositories().get(&repository_id)?;
2709                Some(repository.update(cx, |repo, cx| repo.diff_tree(diff_type, cx)))
2710            })
2711            .context("missing repository")?
2712            .await??;
2713
2714        Ok(proto::GetTreeDiffResponse {
2715            entries: diff
2716                .entries
2717                .into_iter()
2718                .map(|(path, status)| proto::TreeDiffStatus {
2719                    path: path.as_ref().to_proto(),
2720                    status: match status {
2721                        TreeDiffStatus::Added {} => proto::tree_diff_status::Status::Added.into(),
2722                        TreeDiffStatus::Modified { .. } => {
2723                            proto::tree_diff_status::Status::Modified.into()
2724                        }
2725                        TreeDiffStatus::Deleted { .. } => {
2726                            proto::tree_diff_status::Status::Deleted.into()
2727                        }
2728                    },
2729                    oid: match status {
2730                        TreeDiffStatus::Deleted { old } | TreeDiffStatus::Modified { old } => {
2731                            Some(old.to_string())
2732                        }
2733                        TreeDiffStatus::Added => None,
2734                    },
2735                })
2736                .collect(),
2737        })
2738    }
2739
2740    async fn handle_get_blob_content(
2741        this: Entity<Self>,
2742        request: TypedEnvelope<proto::GetBlobContent>,
2743        mut cx: AsyncApp,
2744    ) -> Result<proto::GetBlobContentResponse> {
2745        let oid = git::Oid::from_str(&request.payload.oid)?;
2746        let repository_id = RepositoryId(request.payload.repository_id);
2747        let content = this
2748            .update(&mut cx, |this, cx| {
2749                let repository = this.repositories().get(&repository_id)?;
2750                Some(repository.update(cx, |repo, cx| repo.load_blob_content(oid, cx)))
2751            })
2752            .context("missing repository")?
2753            .await?;
2754        Ok(proto::GetBlobContentResponse { content })
2755    }
2756
2757    async fn handle_open_unstaged_diff(
2758        this: Entity<Self>,
2759        request: TypedEnvelope<proto::OpenUnstagedDiff>,
2760        mut cx: AsyncApp,
2761    ) -> Result<proto::OpenUnstagedDiffResponse> {
2762        let buffer_id = BufferId::new(request.payload.buffer_id)?;
2763        let diff = this
2764            .update(&mut cx, |this, cx| {
2765                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
2766                Some(this.open_unstaged_diff(buffer, cx))
2767            })
2768            .context("missing buffer")?
2769            .await?;
2770        this.update(&mut cx, |this, _| {
2771            let shared_diffs = this
2772                .shared_diffs
2773                .entry(request.original_sender_id.unwrap_or(request.sender_id))
2774                .or_default();
2775            shared_diffs.entry(buffer_id).or_default().unstaged = Some(diff.clone());
2776        });
2777        let staged_text = diff.read_with(&cx, |diff, cx| diff.base_text_string(cx));
2778        Ok(proto::OpenUnstagedDiffResponse { staged_text })
2779    }
2780
2781    async fn handle_open_uncommitted_diff(
2782        this: Entity<Self>,
2783        request: TypedEnvelope<proto::OpenUncommittedDiff>,
2784        mut cx: AsyncApp,
2785    ) -> Result<proto::OpenUncommittedDiffResponse> {
2786        let buffer_id = BufferId::new(request.payload.buffer_id)?;
2787        let diff = this
2788            .update(&mut cx, |this, cx| {
2789                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
2790                Some(this.open_uncommitted_diff(buffer, cx))
2791            })
2792            .context("missing buffer")?
2793            .await?;
2794        this.update(&mut cx, |this, _| {
2795            let shared_diffs = this
2796                .shared_diffs
2797                .entry(request.original_sender_id.unwrap_or(request.sender_id))
2798                .or_default();
2799            shared_diffs.entry(buffer_id).or_default().uncommitted = Some(diff.clone());
2800        });
2801        Ok(diff.read_with(&cx, |diff, cx| {
2802            use proto::open_uncommitted_diff_response::Mode;
2803
2804            let unstaged_diff = diff.secondary_diff();
2805            let index_snapshot = unstaged_diff.and_then(|diff| {
2806                let diff = diff.read(cx);
2807                diff.base_text_exists().then(|| diff.base_text(cx))
2808            });
2809
2810            let mode;
2811            let staged_text;
2812            let committed_text;
2813            if diff.base_text_exists() {
2814                let committed_snapshot = diff.base_text(cx);
2815                committed_text = Some(committed_snapshot.text());
2816                if let Some(index_text) = index_snapshot {
2817                    if index_text.remote_id() == committed_snapshot.remote_id() {
2818                        mode = Mode::IndexMatchesHead;
2819                        staged_text = None;
2820                    } else {
2821                        mode = Mode::IndexAndHead;
2822                        staged_text = Some(index_text.text());
2823                    }
2824                } else {
2825                    mode = Mode::IndexAndHead;
2826                    staged_text = None;
2827                }
2828            } else {
2829                mode = Mode::IndexAndHead;
2830                committed_text = None;
2831                staged_text = index_snapshot.as_ref().map(|buffer| buffer.text());
2832            }
2833
2834            proto::OpenUncommittedDiffResponse {
2835                committed_text,
2836                staged_text,
2837                mode: mode.into(),
2838            }
2839        }))
2840    }
2841
2842    async fn handle_update_diff_bases(
2843        this: Entity<Self>,
2844        request: TypedEnvelope<proto::UpdateDiffBases>,
2845        mut cx: AsyncApp,
2846    ) -> Result<()> {
2847        let buffer_id = BufferId::new(request.payload.buffer_id)?;
2848        this.update(&mut cx, |this, cx| {
2849            if let Some(diff_state) = this.diffs.get_mut(&buffer_id)
2850                && let Some(buffer) = this.buffer_store.read(cx).get(buffer_id)
2851            {
2852                let buffer = buffer.read(cx).text_snapshot();
2853                diff_state.update(cx, |diff_state, cx| {
2854                    diff_state.handle_base_texts_updated(buffer, request.payload, cx);
2855                })
2856            }
2857        });
2858        Ok(())
2859    }
2860
2861    async fn handle_blame_buffer(
2862        this: Entity<Self>,
2863        envelope: TypedEnvelope<proto::BlameBuffer>,
2864        mut cx: AsyncApp,
2865    ) -> Result<proto::BlameBufferResponse> {
2866        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
2867        let version = deserialize_version(&envelope.payload.version);
2868        let buffer = this.read_with(&cx, |this, cx| {
2869            this.buffer_store.read(cx).get_existing(buffer_id)
2870        })?;
2871        buffer
2872            .update(&mut cx, |buffer, _| {
2873                buffer.wait_for_version(version.clone())
2874            })
2875            .await?;
2876        let blame = this
2877            .update(&mut cx, |this, cx| {
2878                this.blame_buffer(&buffer, Some(version), cx)
2879            })
2880            .await?;
2881        Ok(serialize_blame_buffer_response(blame))
2882    }
2883
2884    async fn handle_get_permalink_to_line(
2885        this: Entity<Self>,
2886        envelope: TypedEnvelope<proto::GetPermalinkToLine>,
2887        mut cx: AsyncApp,
2888    ) -> Result<proto::GetPermalinkToLineResponse> {
2889        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
2890        // let version = deserialize_version(&envelope.payload.version);
2891        let selection = {
2892            let proto_selection = envelope
2893                .payload
2894                .selection
2895                .context("no selection to get permalink for defined")?;
2896            proto_selection.start as u32..proto_selection.end as u32
2897        };
2898        let buffer = this.read_with(&cx, |this, cx| {
2899            this.buffer_store.read(cx).get_existing(buffer_id)
2900        })?;
2901        let permalink = this
2902            .update(&mut cx, |this, cx| {
2903                this.get_permalink_to_line(&buffer, selection, cx)
2904            })
2905            .await?;
2906        Ok(proto::GetPermalinkToLineResponse {
2907            permalink: permalink.to_string(),
2908        })
2909    }
2910
2911    fn repository_for_request(
2912        this: &Entity<Self>,
2913        id: RepositoryId,
2914        cx: &mut AsyncApp,
2915    ) -> Result<Entity<Repository>> {
2916        this.read_with(cx, |this, _| {
2917            this.repositories
2918                .get(&id)
2919                .context("missing repository handle")
2920                .cloned()
2921        })
2922    }
2923
2924    pub fn repo_snapshots(&self, cx: &App) -> HashMap<RepositoryId, RepositorySnapshot> {
2925        self.repositories
2926            .iter()
2927            .map(|(id, repo)| (*id, repo.read(cx).snapshot.clone()))
2928            .collect()
2929    }
2930
2931    fn process_updated_entries(
2932        &self,
2933        worktree: &Entity<Worktree>,
2934        updated_entries: &[(Arc<RelPath>, ProjectEntryId, PathChange)],
2935        cx: &mut App,
2936    ) -> Task<HashMap<Entity<Repository>, Vec<RepoPath>>> {
2937        let path_style = worktree.read(cx).path_style();
2938        let mut repo_paths = self
2939            .repositories
2940            .values()
2941            .map(|repo| (repo.read(cx).work_directory_abs_path.clone(), repo.clone()))
2942            .collect::<Vec<_>>();
2943        let mut entries: Vec<_> = updated_entries
2944            .iter()
2945            .map(|(path, _, _)| path.clone())
2946            .collect();
2947        entries.sort();
2948        let worktree = worktree.read(cx);
2949
2950        let entries = entries
2951            .into_iter()
2952            .map(|path| worktree.absolutize(&path))
2953            .collect::<Arc<[_]>>();
2954
2955        let executor = cx.background_executor().clone();
2956        cx.background_executor().spawn(async move {
2957            repo_paths.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0));
2958            let mut paths_by_git_repo = HashMap::<_, Vec<_>>::default();
2959            let mut tasks = FuturesOrdered::new();
2960            for (repo_path, repo) in repo_paths.into_iter().rev() {
2961                let entries = entries.clone();
2962                let task = executor.spawn(async move {
2963                    // Find all repository paths that belong to this repo
2964                    let mut ix = entries.partition_point(|path| path < &*repo_path);
2965                    if ix == entries.len() {
2966                        return None;
2967                    };
2968
2969                    let mut paths = Vec::new();
2970                    // All paths prefixed by a given repo will constitute a continuous range.
2971                    while let Some(path) = entries.get(ix)
2972                        && let Some(repo_path) = RepositorySnapshot::abs_path_to_repo_path_inner(
2973                            &repo_path, path, path_style,
2974                        )
2975                    {
2976                        paths.push((repo_path, ix));
2977                        ix += 1;
2978                    }
2979                    if paths.is_empty() {
2980                        None
2981                    } else {
2982                        Some((repo, paths))
2983                    }
2984                });
2985                tasks.push_back(task);
2986            }
2987
2988            // Now, let's filter out the "duplicate" entries that were processed by multiple distinct repos.
2989            let mut path_was_used = vec![false; entries.len()];
2990            let tasks = tasks.collect::<Vec<_>>().await;
2991            // Process tasks from the back: iterating backwards allows us to see more-specific paths first.
2992            // We always want to assign a path to it's innermost repository.
2993            for t in tasks {
2994                let Some((repo, paths)) = t else {
2995                    continue;
2996                };
2997                let entry = paths_by_git_repo.entry(repo).or_default();
2998                for (repo_path, ix) in paths {
2999                    if path_was_used[ix] {
3000                        continue;
3001                    }
3002                    path_was_used[ix] = true;
3003                    entry.push(repo_path);
3004                }
3005            }
3006
3007            paths_by_git_repo
3008        })
3009    }
3010}
3011
3012impl BufferGitState {
3013    fn new(_git_store: WeakEntity<GitStore>) -> Self {
3014        Self {
3015            unstaged_diff: Default::default(),
3016            uncommitted_diff: Default::default(),
3017            oid_diffs: Default::default(),
3018            recalculate_diff_task: Default::default(),
3019            language: Default::default(),
3020            language_registry: Default::default(),
3021            recalculating_tx: postage::watch::channel_with(false).0,
3022            hunk_staging_operation_count: 0,
3023            hunk_staging_operation_count_as_of_write: 0,
3024            head_text: Default::default(),
3025            index_text: Default::default(),
3026            oid_texts: Default::default(),
3027            head_changed: Default::default(),
3028            index_changed: Default::default(),
3029            language_changed: Default::default(),
3030            conflict_updated_futures: Default::default(),
3031            conflict_set: Default::default(),
3032            reparse_conflict_markers_task: Default::default(),
3033        }
3034    }
3035
3036    #[ztracing::instrument(skip_all)]
3037    fn buffer_language_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
3038        self.language = buffer.read(cx).language().cloned();
3039        self.language_changed = true;
3040        let _ = self.recalculate_diffs(buffer.read(cx).text_snapshot(), cx);
3041    }
3042
3043    fn reparse_conflict_markers(
3044        &mut self,
3045        buffer: text::BufferSnapshot,
3046        cx: &mut Context<Self>,
3047    ) -> oneshot::Receiver<()> {
3048        let (tx, rx) = oneshot::channel();
3049
3050        let Some(conflict_set) = self
3051            .conflict_set
3052            .as_ref()
3053            .and_then(|conflict_set| conflict_set.upgrade())
3054        else {
3055            return rx;
3056        };
3057
3058        let old_snapshot = conflict_set.read_with(cx, |conflict_set, _| {
3059            if conflict_set.has_conflict {
3060                Some(conflict_set.snapshot())
3061            } else {
3062                None
3063            }
3064        });
3065
3066        if let Some(old_snapshot) = old_snapshot {
3067            self.conflict_updated_futures.push(tx);
3068            self.reparse_conflict_markers_task = Some(cx.spawn(async move |this, cx| {
3069                let (snapshot, changed_range) = cx
3070                    .background_spawn(async move {
3071                        let new_snapshot = ConflictSet::parse(&buffer);
3072                        let changed_range = old_snapshot.compare(&new_snapshot, &buffer);
3073                        (new_snapshot, changed_range)
3074                    })
3075                    .await;
3076                this.update(cx, |this, cx| {
3077                    if let Some(conflict_set) = &this.conflict_set {
3078                        conflict_set
3079                            .update(cx, |conflict_set, cx| {
3080                                conflict_set.set_snapshot(snapshot, changed_range, cx);
3081                            })
3082                            .ok();
3083                    }
3084                    let futures = std::mem::take(&mut this.conflict_updated_futures);
3085                    for tx in futures {
3086                        tx.send(()).ok();
3087                    }
3088                })
3089            }))
3090        }
3091
3092        rx
3093    }
3094
3095    fn unstaged_diff(&self) -> Option<Entity<BufferDiff>> {
3096        self.unstaged_diff.as_ref().and_then(|set| set.upgrade())
3097    }
3098
3099    fn uncommitted_diff(&self) -> Option<Entity<BufferDiff>> {
3100        self.uncommitted_diff.as_ref().and_then(|set| set.upgrade())
3101    }
3102
3103    fn oid_diff(&self, oid: Option<git::Oid>) -> Option<Entity<BufferDiff>> {
3104        self.oid_diffs.get(&oid).and_then(|weak| weak.upgrade())
3105    }
3106
3107    fn handle_base_texts_updated(
3108        &mut self,
3109        buffer: text::BufferSnapshot,
3110        message: proto::UpdateDiffBases,
3111        cx: &mut Context<Self>,
3112    ) {
3113        use proto::update_diff_bases::Mode;
3114
3115        let Some(mode) = Mode::from_i32(message.mode) else {
3116            return;
3117        };
3118
3119        let diff_bases_change = match mode {
3120            Mode::HeadOnly => DiffBasesChange::SetHead(message.committed_text),
3121            Mode::IndexOnly => DiffBasesChange::SetIndex(message.staged_text),
3122            Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.committed_text),
3123            Mode::IndexAndHead => DiffBasesChange::SetEach {
3124                index: message.staged_text,
3125                head: message.committed_text,
3126            },
3127        };
3128
3129        self.diff_bases_changed(buffer, Some(diff_bases_change), cx);
3130    }
3131
3132    pub fn wait_for_recalculation(&mut self) -> Option<impl Future<Output = ()> + use<>> {
3133        if *self.recalculating_tx.borrow() {
3134            let mut rx = self.recalculating_tx.subscribe();
3135            Some(async move {
3136                loop {
3137                    let is_recalculating = rx.recv().await;
3138                    if is_recalculating != Some(true) {
3139                        break;
3140                    }
3141                }
3142            })
3143        } else {
3144            None
3145        }
3146    }
3147
3148    fn diff_bases_changed(
3149        &mut self,
3150        buffer: text::BufferSnapshot,
3151        diff_bases_change: Option<DiffBasesChange>,
3152        cx: &mut Context<Self>,
3153    ) {
3154        match diff_bases_change {
3155            Some(DiffBasesChange::SetIndex(index)) => {
3156                self.index_text = index.map(|mut index| {
3157                    text::LineEnding::normalize(&mut index);
3158                    Arc::from(index.as_str())
3159                });
3160                self.index_changed = true;
3161            }
3162            Some(DiffBasesChange::SetHead(head)) => {
3163                self.head_text = head.map(|mut head| {
3164                    text::LineEnding::normalize(&mut head);
3165                    Arc::from(head.as_str())
3166                });
3167                self.head_changed = true;
3168            }
3169            Some(DiffBasesChange::SetBoth(text)) => {
3170                let text = text.map(|mut text| {
3171                    text::LineEnding::normalize(&mut text);
3172                    Arc::from(text.as_str())
3173                });
3174                self.head_text = text.clone();
3175                self.index_text = text;
3176                self.head_changed = true;
3177                self.index_changed = true;
3178            }
3179            Some(DiffBasesChange::SetEach { index, head }) => {
3180                self.index_text = index.map(|mut index| {
3181                    text::LineEnding::normalize(&mut index);
3182                    Arc::from(index.as_str())
3183                });
3184                self.index_changed = true;
3185                self.head_text = head.map(|mut head| {
3186                    text::LineEnding::normalize(&mut head);
3187                    Arc::from(head.as_str())
3188                });
3189                self.head_changed = true;
3190            }
3191            None => {}
3192        }
3193
3194        self.recalculate_diffs(buffer, cx)
3195    }
3196
3197    #[ztracing::instrument(skip_all)]
3198    fn recalculate_diffs(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
3199        *self.recalculating_tx.borrow_mut() = true;
3200
3201        let language = self.language.clone();
3202        let language_registry = self.language_registry.clone();
3203        let unstaged_diff = self.unstaged_diff();
3204        let uncommitted_diff = self.uncommitted_diff();
3205        let head = self.head_text.clone();
3206        let index = self.index_text.clone();
3207        let index_changed = self.index_changed;
3208        let head_changed = self.head_changed;
3209        let language_changed = self.language_changed;
3210        let prev_hunk_staging_operation_count = self.hunk_staging_operation_count_as_of_write;
3211        let index_matches_head = match (self.index_text.as_ref(), self.head_text.as_ref()) {
3212            (Some(index), Some(head)) => Arc::ptr_eq(index, head),
3213            (None, None) => true,
3214            _ => false,
3215        };
3216
3217        let oid_diffs: Vec<(Option<git::Oid>, Entity<BufferDiff>, Option<Arc<str>>)> = self
3218            .oid_diffs
3219            .iter()
3220            .filter_map(|(oid, weak)| {
3221                let base_text = oid.and_then(|oid| self.oid_texts.get(&oid).cloned());
3222                weak.upgrade().map(|diff| (*oid, diff, base_text))
3223            })
3224            .collect();
3225
3226        self.oid_diffs.retain(|oid, weak| {
3227            let alive = weak.upgrade().is_some();
3228            if !alive {
3229                if let Some(oid) = oid {
3230                    self.oid_texts.remove(oid);
3231                }
3232            }
3233            alive
3234        });
3235        self.recalculate_diff_task = Some(cx.spawn(async move |this, cx| {
3236            log::debug!(
3237                "start recalculating diffs for buffer {}",
3238                buffer.remote_id()
3239            );
3240
3241            let mut new_unstaged_diff = None;
3242            if let Some(unstaged_diff) = &unstaged_diff {
3243                new_unstaged_diff = Some(
3244                    cx.update(|cx| {
3245                        unstaged_diff.read(cx).update_diff(
3246                            buffer.clone(),
3247                            index,
3248                            index_changed.then_some(false),
3249                            language.clone(),
3250                            cx,
3251                        )
3252                    })
3253                    .await,
3254                );
3255            }
3256
3257            // Dropping BufferDiff can be expensive, so yield back to the event loop
3258            // for a bit
3259            yield_now().await;
3260
3261            let mut new_uncommitted_diff = None;
3262            if let Some(uncommitted_diff) = &uncommitted_diff {
3263                new_uncommitted_diff = if index_matches_head {
3264                    new_unstaged_diff.clone()
3265                } else {
3266                    Some(
3267                        cx.update(|cx| {
3268                            uncommitted_diff.read(cx).update_diff(
3269                                buffer.clone(),
3270                                head,
3271                                head_changed.then_some(true),
3272                                language.clone(),
3273                                cx,
3274                            )
3275                        })
3276                        .await,
3277                    )
3278                }
3279            }
3280
3281            // Dropping BufferDiff can be expensive, so yield back to the event loop
3282            // for a bit
3283            yield_now().await;
3284
3285            let cancel = this.update(cx, |this, _| {
3286                // This checks whether all pending stage/unstage operations
3287                // have quiesced (i.e. both the corresponding write and the
3288                // read of that write have completed). If not, then we cancel
3289                // this recalculation attempt to avoid invalidating pending
3290                // state too quickly; another recalculation will come along
3291                // later and clear the pending state once the state of the index has settled.
3292                if this.hunk_staging_operation_count > prev_hunk_staging_operation_count {
3293                    *this.recalculating_tx.borrow_mut() = false;
3294                    true
3295                } else {
3296                    false
3297                }
3298            })?;
3299            if cancel {
3300                log::debug!(
3301                    concat!(
3302                        "aborting recalculating diffs for buffer {}",
3303                        "due to subsequent hunk operations",
3304                    ),
3305                    buffer.remote_id()
3306                );
3307                return Ok(());
3308            }
3309
3310            let unstaged_changed_range = if let Some((unstaged_diff, new_unstaged_diff)) =
3311                unstaged_diff.as_ref().zip(new_unstaged_diff.clone())
3312            {
3313                let task = unstaged_diff.update(cx, |diff, cx| {
3314                    // For git index buffer we skip assigning the language as we do not really need to perform any syntax highlighting on
3315                    // it. As a result, by skipping it we are potentially shaving off a lot of RSS plus we get a snappier feel for large diff
3316                    // view multibuffers.
3317                    diff.set_snapshot(new_unstaged_diff, &buffer, cx)
3318                });
3319                Some(task.await)
3320            } else {
3321                None
3322            };
3323
3324            yield_now().await;
3325
3326            if let Some((uncommitted_diff, new_uncommitted_diff)) =
3327                uncommitted_diff.as_ref().zip(new_uncommitted_diff.clone())
3328            {
3329                uncommitted_diff
3330                    .update(cx, |diff, cx| {
3331                        if language_changed {
3332                            diff.language_changed(language.clone(), language_registry, cx);
3333                        }
3334                        diff.set_snapshot_with_secondary(
3335                            new_uncommitted_diff,
3336                            &buffer,
3337                            unstaged_changed_range.flatten(),
3338                            true,
3339                            cx,
3340                        )
3341                    })
3342                    .await;
3343            }
3344
3345            yield_now().await;
3346
3347            for (oid, oid_diff, base_text) in oid_diffs {
3348                let new_oid_diff = cx
3349                    .update(|cx| {
3350                        oid_diff.read(cx).update_diff(
3351                            buffer.clone(),
3352                            base_text,
3353                            None,
3354                            language.clone(),
3355                            cx,
3356                        )
3357                    })
3358                    .await;
3359
3360                oid_diff
3361                    .update(cx, |diff, cx| diff.set_snapshot(new_oid_diff, &buffer, cx))
3362                    .await;
3363
3364                log::debug!(
3365                    "finished recalculating oid diff for buffer {} oid {:?}",
3366                    buffer.remote_id(),
3367                    oid
3368                );
3369
3370                yield_now().await;
3371            }
3372
3373            log::debug!(
3374                "finished recalculating diffs for buffer {}",
3375                buffer.remote_id()
3376            );
3377
3378            if let Some(this) = this.upgrade() {
3379                this.update(cx, |this, _| {
3380                    this.index_changed = false;
3381                    this.head_changed = false;
3382                    this.language_changed = false;
3383                    *this.recalculating_tx.borrow_mut() = false;
3384                });
3385            }
3386
3387            Ok(())
3388        }));
3389    }
3390}
3391
3392fn make_remote_delegate(
3393    this: Entity<GitStore>,
3394    project_id: u64,
3395    repository_id: RepositoryId,
3396    askpass_id: u64,
3397    cx: &mut AsyncApp,
3398) -> AskPassDelegate {
3399    AskPassDelegate::new(cx, move |prompt, tx, cx| {
3400        this.update(cx, |this, cx| {
3401            let Some((client, _)) = this.downstream_client() else {
3402                return;
3403            };
3404            let response = client.request(proto::AskPassRequest {
3405                project_id,
3406                repository_id: repository_id.to_proto(),
3407                askpass_id,
3408                prompt,
3409            });
3410            cx.spawn(async move |_, _| {
3411                let mut response = response.await?.response;
3412                tx.send(EncryptedPassword::try_from(response.as_ref())?)
3413                    .ok();
3414                response.zeroize();
3415                anyhow::Ok(())
3416            })
3417            .detach_and_log_err(cx);
3418        });
3419    })
3420}
3421
3422impl RepositoryId {
3423    pub fn to_proto(self) -> u64 {
3424        self.0
3425    }
3426
3427    pub fn from_proto(id: u64) -> Self {
3428        RepositoryId(id)
3429    }
3430}
3431
3432impl RepositorySnapshot {
3433    fn empty(id: RepositoryId, work_directory_abs_path: Arc<Path>, path_style: PathStyle) -> Self {
3434        Self {
3435            id,
3436            statuses_by_path: Default::default(),
3437            work_directory_abs_path,
3438            branch: None,
3439            head_commit: None,
3440            scan_id: 0,
3441            merge: Default::default(),
3442            remote_origin_url: None,
3443            remote_upstream_url: None,
3444            stash_entries: Default::default(),
3445            path_style,
3446        }
3447    }
3448
3449    fn initial_update(&self, project_id: u64) -> proto::UpdateRepository {
3450        proto::UpdateRepository {
3451            branch_summary: self.branch.as_ref().map(branch_to_proto),
3452            head_commit_details: self.head_commit.as_ref().map(commit_details_to_proto),
3453            updated_statuses: self
3454                .statuses_by_path
3455                .iter()
3456                .map(|entry| entry.to_proto())
3457                .collect(),
3458            removed_statuses: Default::default(),
3459            current_merge_conflicts: self
3460                .merge
3461                .conflicted_paths
3462                .iter()
3463                .map(|repo_path| repo_path.to_proto())
3464                .collect(),
3465            merge_message: self.merge.message.as_ref().map(|msg| msg.to_string()),
3466            project_id,
3467            id: self.id.to_proto(),
3468            abs_path: self.work_directory_abs_path.to_string_lossy().into_owned(),
3469            entry_ids: vec![self.id.to_proto()],
3470            scan_id: self.scan_id,
3471            is_last_update: true,
3472            stash_entries: self
3473                .stash_entries
3474                .entries
3475                .iter()
3476                .map(stash_to_proto)
3477                .collect(),
3478            remote_upstream_url: self.remote_upstream_url.clone(),
3479            remote_origin_url: self.remote_origin_url.clone(),
3480        }
3481    }
3482
3483    fn build_update(&self, old: &Self, project_id: u64) -> proto::UpdateRepository {
3484        let mut updated_statuses: Vec<proto::StatusEntry> = Vec::new();
3485        let mut removed_statuses: Vec<String> = Vec::new();
3486
3487        let mut new_statuses = self.statuses_by_path.iter().peekable();
3488        let mut old_statuses = old.statuses_by_path.iter().peekable();
3489
3490        let mut current_new_entry = new_statuses.next();
3491        let mut current_old_entry = old_statuses.next();
3492        loop {
3493            match (current_new_entry, current_old_entry) {
3494                (Some(new_entry), Some(old_entry)) => {
3495                    match new_entry.repo_path.cmp(&old_entry.repo_path) {
3496                        Ordering::Less => {
3497                            updated_statuses.push(new_entry.to_proto());
3498                            current_new_entry = new_statuses.next();
3499                        }
3500                        Ordering::Equal => {
3501                            if new_entry.status != old_entry.status {
3502                                updated_statuses.push(new_entry.to_proto());
3503                            }
3504                            current_old_entry = old_statuses.next();
3505                            current_new_entry = new_statuses.next();
3506                        }
3507                        Ordering::Greater => {
3508                            removed_statuses.push(old_entry.repo_path.to_proto());
3509                            current_old_entry = old_statuses.next();
3510                        }
3511                    }
3512                }
3513                (None, Some(old_entry)) => {
3514                    removed_statuses.push(old_entry.repo_path.to_proto());
3515                    current_old_entry = old_statuses.next();
3516                }
3517                (Some(new_entry), None) => {
3518                    updated_statuses.push(new_entry.to_proto());
3519                    current_new_entry = new_statuses.next();
3520                }
3521                (None, None) => break,
3522            }
3523        }
3524
3525        proto::UpdateRepository {
3526            branch_summary: self.branch.as_ref().map(branch_to_proto),
3527            head_commit_details: self.head_commit.as_ref().map(commit_details_to_proto),
3528            updated_statuses,
3529            removed_statuses,
3530            current_merge_conflicts: self
3531                .merge
3532                .conflicted_paths
3533                .iter()
3534                .map(|path| path.to_proto())
3535                .collect(),
3536            merge_message: self.merge.message.as_ref().map(|msg| msg.to_string()),
3537            project_id,
3538            id: self.id.to_proto(),
3539            abs_path: self.work_directory_abs_path.to_string_lossy().into_owned(),
3540            entry_ids: vec![],
3541            scan_id: self.scan_id,
3542            is_last_update: true,
3543            stash_entries: self
3544                .stash_entries
3545                .entries
3546                .iter()
3547                .map(stash_to_proto)
3548                .collect(),
3549            remote_upstream_url: self.remote_upstream_url.clone(),
3550            remote_origin_url: self.remote_origin_url.clone(),
3551        }
3552    }
3553
3554    pub fn status(&self) -> impl Iterator<Item = StatusEntry> + '_ {
3555        self.statuses_by_path.iter().cloned()
3556    }
3557
3558    pub fn status_summary(&self) -> GitSummary {
3559        self.statuses_by_path.summary().item_summary
3560    }
3561
3562    pub fn status_for_path(&self, path: &RepoPath) -> Option<StatusEntry> {
3563        self.statuses_by_path
3564            .get(&PathKey(path.as_ref().clone()), ())
3565            .cloned()
3566    }
3567
3568    pub fn abs_path_to_repo_path(&self, abs_path: &Path) -> Option<RepoPath> {
3569        Self::abs_path_to_repo_path_inner(&self.work_directory_abs_path, abs_path, self.path_style)
3570    }
3571
3572    fn repo_path_to_abs_path(&self, repo_path: &RepoPath) -> PathBuf {
3573        self.path_style
3574            .join(&self.work_directory_abs_path, repo_path.as_std_path())
3575            .unwrap()
3576            .into()
3577    }
3578
3579    #[inline]
3580    fn abs_path_to_repo_path_inner(
3581        work_directory_abs_path: &Path,
3582        abs_path: &Path,
3583        path_style: PathStyle,
3584    ) -> Option<RepoPath> {
3585        let rel_path = path_style.strip_prefix(abs_path, work_directory_abs_path)?;
3586        Some(RepoPath::from_rel_path(&rel_path))
3587    }
3588
3589    pub fn had_conflict_on_last_merge_head_change(&self, repo_path: &RepoPath) -> bool {
3590        self.merge.conflicted_paths.contains(repo_path)
3591    }
3592
3593    pub fn has_conflict(&self, repo_path: &RepoPath) -> bool {
3594        let had_conflict_on_last_merge_head_change =
3595            self.merge.conflicted_paths.contains(repo_path);
3596        let has_conflict_currently = self
3597            .status_for_path(repo_path)
3598            .is_some_and(|entry| entry.status.is_conflicted());
3599        had_conflict_on_last_merge_head_change || has_conflict_currently
3600    }
3601
3602    /// This is the name that will be displayed in the repository selector for this repository.
3603    pub fn display_name(&self) -> SharedString {
3604        self.work_directory_abs_path
3605            .file_name()
3606            .unwrap_or_default()
3607            .to_string_lossy()
3608            .to_string()
3609            .into()
3610    }
3611}
3612
3613pub fn stash_to_proto(entry: &StashEntry) -> proto::StashEntry {
3614    proto::StashEntry {
3615        oid: entry.oid.as_bytes().to_vec(),
3616        message: entry.message.clone(),
3617        branch: entry.branch.clone(),
3618        index: entry.index as u64,
3619        timestamp: entry.timestamp,
3620    }
3621}
3622
3623pub fn proto_to_stash(entry: &proto::StashEntry) -> Result<StashEntry> {
3624    Ok(StashEntry {
3625        oid: Oid::from_bytes(&entry.oid)?,
3626        message: entry.message.clone(),
3627        index: entry.index as usize,
3628        branch: entry.branch.clone(),
3629        timestamp: entry.timestamp,
3630    })
3631}
3632
3633impl MergeDetails {
3634    async fn load(
3635        backend: &Arc<dyn GitRepository>,
3636        status: &SumTree<StatusEntry>,
3637        prev_snapshot: &RepositorySnapshot,
3638    ) -> Result<(MergeDetails, bool)> {
3639        log::debug!("load merge details");
3640        let message = backend.merge_message().await;
3641        let heads = backend
3642            .revparse_batch(vec![
3643                "MERGE_HEAD".into(),
3644                "CHERRY_PICK_HEAD".into(),
3645                "REBASE_HEAD".into(),
3646                "REVERT_HEAD".into(),
3647                "APPLY_HEAD".into(),
3648            ])
3649            .await
3650            .log_err()
3651            .unwrap_or_default()
3652            .into_iter()
3653            .map(|opt| opt.map(SharedString::from))
3654            .collect::<Vec<_>>();
3655        let merge_heads_changed = heads != prev_snapshot.merge.heads;
3656        let conflicted_paths = if merge_heads_changed {
3657            let current_conflicted_paths = TreeSet::from_ordered_entries(
3658                status
3659                    .iter()
3660                    .filter(|entry| entry.status.is_conflicted())
3661                    .map(|entry| entry.repo_path.clone()),
3662            );
3663
3664            // It can happen that we run a scan while a lengthy merge is in progress
3665            // that will eventually result in conflicts, but before those conflicts
3666            // are reported by `git status`. Since for the moment we only care about
3667            // the merge heads state for the purposes of tracking conflicts, don't update
3668            // this state until we see some conflicts.
3669            if heads.iter().any(Option::is_some)
3670                && !prev_snapshot.merge.heads.iter().any(Option::is_some)
3671                && current_conflicted_paths.is_empty()
3672            {
3673                log::debug!("not updating merge heads because no conflicts found");
3674                return Ok((
3675                    MergeDetails {
3676                        message: message.map(SharedString::from),
3677                        ..prev_snapshot.merge.clone()
3678                    },
3679                    false,
3680                ));
3681            }
3682
3683            current_conflicted_paths
3684        } else {
3685            prev_snapshot.merge.conflicted_paths.clone()
3686        };
3687        let details = MergeDetails {
3688            conflicted_paths,
3689            message: message.map(SharedString::from),
3690            heads,
3691        };
3692        Ok((details, merge_heads_changed))
3693    }
3694}
3695
3696impl Repository {
3697    pub fn snapshot(&self) -> RepositorySnapshot {
3698        self.snapshot.clone()
3699    }
3700
3701    pub fn pending_ops(&self) -> impl Iterator<Item = PendingOps> + '_ {
3702        self.pending_ops.iter().cloned()
3703    }
3704
3705    pub fn pending_ops_summary(&self) -> PathSummary<PendingOpsSummary> {
3706        self.pending_ops.summary().clone()
3707    }
3708
3709    pub fn pending_ops_for_path(&self, path: &RepoPath) -> Option<PendingOps> {
3710        self.pending_ops
3711            .get(&PathKey(path.as_ref().clone()), ())
3712            .cloned()
3713    }
3714
3715    fn local(
3716        id: RepositoryId,
3717        work_directory_abs_path: Arc<Path>,
3718        dot_git_abs_path: Arc<Path>,
3719        project_environment: WeakEntity<ProjectEnvironment>,
3720        fs: Arc<dyn Fs>,
3721        git_store: WeakEntity<GitStore>,
3722        cx: &mut Context<Self>,
3723    ) -> Self {
3724        let snapshot =
3725            RepositorySnapshot::empty(id, work_directory_abs_path.clone(), PathStyle::local());
3726        let state = cx
3727            .spawn(async move |_, cx| {
3728                LocalRepositoryState::new(
3729                    work_directory_abs_path,
3730                    dot_git_abs_path,
3731                    project_environment,
3732                    fs,
3733                    cx,
3734                )
3735                .await
3736                .map_err(|err| err.to_string())
3737            })
3738            .shared();
3739        let job_sender = Repository::spawn_local_git_worker(state.clone(), cx);
3740        let state = cx
3741            .spawn(async move |_, _| {
3742                let state = state.await?;
3743                Ok(RepositoryState::Local(state))
3744            })
3745            .shared();
3746
3747        cx.subscribe_self(|this, event: &RepositoryEvent, _| match event {
3748            RepositoryEvent::BranchChanged | RepositoryEvent::MergeHeadsChanged => {
3749                this.initial_graph_data.clear();
3750            }
3751            _ => {}
3752        })
3753        .detach();
3754
3755        Repository {
3756            this: cx.weak_entity(),
3757            git_store,
3758            snapshot,
3759            pending_ops: Default::default(),
3760            repository_state: state,
3761            commit_message_buffer: None,
3762            askpass_delegates: Default::default(),
3763            paths_needing_status_update: Default::default(),
3764            latest_askpass_id: 0,
3765            job_sender,
3766            job_id: 0,
3767            active_jobs: Default::default(),
3768            initial_graph_data: Default::default(),
3769            commit_data: Default::default(),
3770            graph_commit_data_handler: GraphCommitHandlerState::Closed,
3771        }
3772    }
3773
3774    fn remote(
3775        id: RepositoryId,
3776        work_directory_abs_path: Arc<Path>,
3777        path_style: PathStyle,
3778        project_id: ProjectId,
3779        client: AnyProtoClient,
3780        git_store: WeakEntity<GitStore>,
3781        cx: &mut Context<Self>,
3782    ) -> Self {
3783        let snapshot = RepositorySnapshot::empty(id, work_directory_abs_path, path_style);
3784        let repository_state = RemoteRepositoryState { project_id, client };
3785        let job_sender = Self::spawn_remote_git_worker(repository_state.clone(), cx);
3786        let repository_state = Task::ready(Ok(RepositoryState::Remote(repository_state))).shared();
3787        Self {
3788            this: cx.weak_entity(),
3789            snapshot,
3790            commit_message_buffer: None,
3791            git_store,
3792            pending_ops: Default::default(),
3793            paths_needing_status_update: Default::default(),
3794            job_sender,
3795            repository_state,
3796            askpass_delegates: Default::default(),
3797            latest_askpass_id: 0,
3798            active_jobs: Default::default(),
3799            job_id: 0,
3800            initial_graph_data: Default::default(),
3801            commit_data: Default::default(),
3802            graph_commit_data_handler: GraphCommitHandlerState::Closed,
3803        }
3804    }
3805
3806    pub fn git_store(&self) -> Option<Entity<GitStore>> {
3807        self.git_store.upgrade()
3808    }
3809
3810    fn reload_buffer_diff_bases(&mut self, cx: &mut Context<Self>) {
3811        let this = cx.weak_entity();
3812        let git_store = self.git_store.clone();
3813        let _ = self.send_keyed_job(
3814            Some(GitJobKey::ReloadBufferDiffBases),
3815            None,
3816            |state, mut cx| async move {
3817                let RepositoryState::Local(LocalRepositoryState { backend, .. }) = state else {
3818                    log::error!("tried to recompute diffs for a non-local repository");
3819                    return Ok(());
3820                };
3821
3822                let Some(this) = this.upgrade() else {
3823                    return Ok(());
3824                };
3825
3826                let repo_diff_state_updates = this.update(&mut cx, |this, cx| {
3827                    git_store.update(cx, |git_store, cx| {
3828                        git_store
3829                            .diffs
3830                            .iter()
3831                            .filter_map(|(buffer_id, diff_state)| {
3832                                let buffer_store = git_store.buffer_store.read(cx);
3833                                let buffer = buffer_store.get(*buffer_id)?;
3834                                let file = File::from_dyn(buffer.read(cx).file())?;
3835                                let abs_path = file.worktree.read(cx).absolutize(&file.path);
3836                                let repo_path = this.abs_path_to_repo_path(&abs_path)?;
3837                                log::debug!(
3838                                    "start reload diff bases for repo path {}",
3839                                    repo_path.as_unix_str()
3840                                );
3841                                diff_state.update(cx, |diff_state, _| {
3842                                    let has_unstaged_diff = diff_state
3843                                        .unstaged_diff
3844                                        .as_ref()
3845                                        .is_some_and(|diff| diff.is_upgradable());
3846                                    let has_uncommitted_diff = diff_state
3847                                        .uncommitted_diff
3848                                        .as_ref()
3849                                        .is_some_and(|set| set.is_upgradable());
3850
3851                                    Some((
3852                                        buffer,
3853                                        repo_path,
3854                                        has_unstaged_diff.then(|| diff_state.index_text.clone()),
3855                                        has_uncommitted_diff.then(|| diff_state.head_text.clone()),
3856                                    ))
3857                                })
3858                            })
3859                            .collect::<Vec<_>>()
3860                    })
3861                })?;
3862
3863                let buffer_diff_base_changes = cx
3864                    .background_spawn(async move {
3865                        let mut changes = Vec::new();
3866                        for (buffer, repo_path, current_index_text, current_head_text) in
3867                            &repo_diff_state_updates
3868                        {
3869                            let index_text = if current_index_text.is_some() {
3870                                backend.load_index_text(repo_path.clone()).await
3871                            } else {
3872                                None
3873                            };
3874                            let head_text = if current_head_text.is_some() {
3875                                backend.load_committed_text(repo_path.clone()).await
3876                            } else {
3877                                None
3878                            };
3879
3880                            let change =
3881                                match (current_index_text.as_ref(), current_head_text.as_ref()) {
3882                                    (Some(current_index), Some(current_head)) => {
3883                                        let index_changed =
3884                                            index_text.as_deref() != current_index.as_deref();
3885                                        let head_changed =
3886                                            head_text.as_deref() != current_head.as_deref();
3887                                        if index_changed && head_changed {
3888                                            if index_text == head_text {
3889                                                Some(DiffBasesChange::SetBoth(head_text))
3890                                            } else {
3891                                                Some(DiffBasesChange::SetEach {
3892                                                    index: index_text,
3893                                                    head: head_text,
3894                                                })
3895                                            }
3896                                        } else if index_changed {
3897                                            Some(DiffBasesChange::SetIndex(index_text))
3898                                        } else if head_changed {
3899                                            Some(DiffBasesChange::SetHead(head_text))
3900                                        } else {
3901                                            None
3902                                        }
3903                                    }
3904                                    (Some(current_index), None) => {
3905                                        let index_changed =
3906                                            index_text.as_deref() != current_index.as_deref();
3907                                        index_changed
3908                                            .then_some(DiffBasesChange::SetIndex(index_text))
3909                                    }
3910                                    (None, Some(current_head)) => {
3911                                        let head_changed =
3912                                            head_text.as_deref() != current_head.as_deref();
3913                                        head_changed.then_some(DiffBasesChange::SetHead(head_text))
3914                                    }
3915                                    (None, None) => None,
3916                                };
3917
3918                            changes.push((buffer.clone(), change))
3919                        }
3920                        changes
3921                    })
3922                    .await;
3923
3924                git_store.update(&mut cx, |git_store, cx| {
3925                    for (buffer, diff_bases_change) in buffer_diff_base_changes {
3926                        let buffer_snapshot = buffer.read(cx).text_snapshot();
3927                        let buffer_id = buffer_snapshot.remote_id();
3928                        let Some(diff_state) = git_store.diffs.get(&buffer_id) else {
3929                            continue;
3930                        };
3931
3932                        let downstream_client = git_store.downstream_client();
3933                        diff_state.update(cx, |diff_state, cx| {
3934                            use proto::update_diff_bases::Mode;
3935
3936                            if let Some((diff_bases_change, (client, project_id))) =
3937                                diff_bases_change.clone().zip(downstream_client)
3938                            {
3939                                let (staged_text, committed_text, mode) = match diff_bases_change {
3940                                    DiffBasesChange::SetIndex(index) => {
3941                                        (index, None, Mode::IndexOnly)
3942                                    }
3943                                    DiffBasesChange::SetHead(head) => (None, head, Mode::HeadOnly),
3944                                    DiffBasesChange::SetEach { index, head } => {
3945                                        (index, head, Mode::IndexAndHead)
3946                                    }
3947                                    DiffBasesChange::SetBoth(text) => {
3948                                        (None, text, Mode::IndexMatchesHead)
3949                                    }
3950                                };
3951                                client
3952                                    .send(proto::UpdateDiffBases {
3953                                        project_id: project_id.to_proto(),
3954                                        buffer_id: buffer_id.to_proto(),
3955                                        staged_text,
3956                                        committed_text,
3957                                        mode: mode as i32,
3958                                    })
3959                                    .log_err();
3960                            }
3961
3962                            diff_state.diff_bases_changed(buffer_snapshot, diff_bases_change, cx);
3963                        });
3964                    }
3965                })
3966            },
3967        );
3968    }
3969
3970    pub fn send_job<F, Fut, R>(
3971        &mut self,
3972        status: Option<SharedString>,
3973        job: F,
3974    ) -> oneshot::Receiver<R>
3975    where
3976        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
3977        Fut: Future<Output = R> + 'static,
3978        R: Send + 'static,
3979    {
3980        self.send_keyed_job(None, status, job)
3981    }
3982
3983    fn send_keyed_job<F, Fut, R>(
3984        &mut self,
3985        key: Option<GitJobKey>,
3986        status: Option<SharedString>,
3987        job: F,
3988    ) -> oneshot::Receiver<R>
3989    where
3990        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
3991        Fut: Future<Output = R> + 'static,
3992        R: Send + 'static,
3993    {
3994        let (result_tx, result_rx) = futures::channel::oneshot::channel();
3995        let job_id = post_inc(&mut self.job_id);
3996        let this = self.this.clone();
3997        self.job_sender
3998            .unbounded_send(GitJob {
3999                key,
4000                job: Box::new(move |state, cx: &mut AsyncApp| {
4001                    let job = job(state, cx.clone());
4002                    cx.spawn(async move |cx| {
4003                        if let Some(s) = status.clone() {
4004                            this.update(cx, |this, cx| {
4005                                this.active_jobs.insert(
4006                                    job_id,
4007                                    JobInfo {
4008                                        start: Instant::now(),
4009                                        message: s.clone(),
4010                                    },
4011                                );
4012
4013                                cx.notify();
4014                            })
4015                            .ok();
4016                        }
4017                        let result = job.await;
4018
4019                        this.update(cx, |this, cx| {
4020                            this.active_jobs.remove(&job_id);
4021                            cx.notify();
4022                        })
4023                        .ok();
4024
4025                        result_tx.send(result).ok();
4026                    })
4027                }),
4028            })
4029            .ok();
4030        result_rx
4031    }
4032
4033    pub fn set_as_active_repository(&self, cx: &mut Context<Self>) {
4034        let Some(git_store) = self.git_store.upgrade() else {
4035            return;
4036        };
4037        let entity = cx.entity();
4038        git_store.update(cx, |git_store, cx| {
4039            let Some((&id, _)) = git_store
4040                .repositories
4041                .iter()
4042                .find(|(_, handle)| *handle == &entity)
4043            else {
4044                return;
4045            };
4046            git_store.active_repo_id = Some(id);
4047            cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
4048        });
4049    }
4050
4051    pub fn cached_status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
4052        self.snapshot.status()
4053    }
4054
4055    pub fn cached_stash(&self) -> GitStash {
4056        self.snapshot.stash_entries.clone()
4057    }
4058
4059    pub fn repo_path_to_project_path(&self, path: &RepoPath, cx: &App) -> Option<ProjectPath> {
4060        let git_store = self.git_store.upgrade()?;
4061        let worktree_store = git_store.read(cx).worktree_store.read(cx);
4062        let abs_path = self.snapshot.repo_path_to_abs_path(path);
4063        let abs_path = SanitizedPath::new(&abs_path);
4064        let (worktree, relative_path) = worktree_store.find_worktree(abs_path, cx)?;
4065        Some(ProjectPath {
4066            worktree_id: worktree.read(cx).id(),
4067            path: relative_path,
4068        })
4069    }
4070
4071    pub fn project_path_to_repo_path(&self, path: &ProjectPath, cx: &App) -> Option<RepoPath> {
4072        let git_store = self.git_store.upgrade()?;
4073        let worktree_store = git_store.read(cx).worktree_store.read(cx);
4074        let abs_path = worktree_store.absolutize(path, cx)?;
4075        self.snapshot.abs_path_to_repo_path(&abs_path)
4076    }
4077
4078    pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
4079        other
4080            .read(cx)
4081            .snapshot
4082            .work_directory_abs_path
4083            .starts_with(&self.snapshot.work_directory_abs_path)
4084    }
4085
4086    pub fn open_commit_buffer(
4087        &mut self,
4088        languages: Option<Arc<LanguageRegistry>>,
4089        buffer_store: Entity<BufferStore>,
4090        cx: &mut Context<Self>,
4091    ) -> Task<Result<Entity<Buffer>>> {
4092        let id = self.id;
4093        if let Some(buffer) = self.commit_message_buffer.clone() {
4094            return Task::ready(Ok(buffer));
4095        }
4096        let this = cx.weak_entity();
4097
4098        let rx = self.send_job(None, move |state, mut cx| async move {
4099            let Some(this) = this.upgrade() else {
4100                bail!("git store was dropped");
4101            };
4102            match state {
4103                RepositoryState::Local(..) => {
4104                    this.update(&mut cx, |_, cx| {
4105                        Self::open_local_commit_buffer(languages, buffer_store, cx)
4106                    })
4107                    .await
4108                }
4109                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4110                    let request = client.request(proto::OpenCommitMessageBuffer {
4111                        project_id: project_id.0,
4112                        repository_id: id.to_proto(),
4113                    });
4114                    let response = request.await.context("requesting to open commit buffer")?;
4115                    let buffer_id = BufferId::new(response.buffer_id)?;
4116                    let buffer = buffer_store
4117                        .update(&mut cx, |buffer_store, cx| {
4118                            buffer_store.wait_for_remote_buffer(buffer_id, cx)
4119                        })
4120                        .await?;
4121                    if let Some(language_registry) = languages {
4122                        let git_commit_language =
4123                            language_registry.language_for_name("Git Commit").await?;
4124                        buffer.update(&mut cx, |buffer, cx| {
4125                            buffer.set_language(Some(git_commit_language), cx);
4126                        });
4127                    }
4128                    this.update(&mut cx, |this, _| {
4129                        this.commit_message_buffer = Some(buffer.clone());
4130                    });
4131                    Ok(buffer)
4132                }
4133            }
4134        });
4135
4136        cx.spawn(|_, _: &mut AsyncApp| async move { rx.await? })
4137    }
4138
4139    fn open_local_commit_buffer(
4140        language_registry: Option<Arc<LanguageRegistry>>,
4141        buffer_store: Entity<BufferStore>,
4142        cx: &mut Context<Self>,
4143    ) -> Task<Result<Entity<Buffer>>> {
4144        cx.spawn(async move |repository, cx| {
4145            let git_commit_language = match language_registry {
4146                Some(language_registry) => {
4147                    Some(language_registry.language_for_name("Git Commit").await?)
4148                }
4149                None => None,
4150            };
4151            let buffer = buffer_store
4152                .update(cx, |buffer_store, cx| {
4153                    buffer_store.create_buffer(git_commit_language, false, cx)
4154                })
4155                .await?;
4156
4157            repository.update(cx, |repository, _| {
4158                repository.commit_message_buffer = Some(buffer.clone());
4159            })?;
4160            Ok(buffer)
4161        })
4162    }
4163
4164    pub fn checkout_files(
4165        &mut self,
4166        commit: &str,
4167        paths: Vec<RepoPath>,
4168        cx: &mut Context<Self>,
4169    ) -> Task<Result<()>> {
4170        let commit = commit.to_string();
4171        let id = self.id;
4172
4173        self.spawn_job_with_tracking(
4174            paths.clone(),
4175            pending_op::GitStatus::Reverted,
4176            cx,
4177            async move |this, cx| {
4178                this.update(cx, |this, _cx| {
4179                    this.send_job(
4180                        Some(format!("git checkout {}", commit).into()),
4181                        move |git_repo, _| async move {
4182                            match git_repo {
4183                                RepositoryState::Local(LocalRepositoryState {
4184                                    backend,
4185                                    environment,
4186                                    ..
4187                                }) => {
4188                                    backend
4189                                        .checkout_files(commit, paths, environment.clone())
4190                                        .await
4191                                }
4192                                RepositoryState::Remote(RemoteRepositoryState {
4193                                    project_id,
4194                                    client,
4195                                }) => {
4196                                    client
4197                                        .request(proto::GitCheckoutFiles {
4198                                            project_id: project_id.0,
4199                                            repository_id: id.to_proto(),
4200                                            commit,
4201                                            paths: paths
4202                                                .into_iter()
4203                                                .map(|p| p.to_proto())
4204                                                .collect(),
4205                                        })
4206                                        .await?;
4207
4208                                    Ok(())
4209                                }
4210                            }
4211                        },
4212                    )
4213                })?
4214                .await?
4215            },
4216        )
4217    }
4218
4219    pub fn reset(
4220        &mut self,
4221        commit: String,
4222        reset_mode: ResetMode,
4223        _cx: &mut App,
4224    ) -> oneshot::Receiver<Result<()>> {
4225        let id = self.id;
4226
4227        self.send_job(None, move |git_repo, _| async move {
4228            match git_repo {
4229                RepositoryState::Local(LocalRepositoryState {
4230                    backend,
4231                    environment,
4232                    ..
4233                }) => backend.reset(commit, reset_mode, environment).await,
4234                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4235                    client
4236                        .request(proto::GitReset {
4237                            project_id: project_id.0,
4238                            repository_id: id.to_proto(),
4239                            commit,
4240                            mode: match reset_mode {
4241                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
4242                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
4243                            },
4244                        })
4245                        .await?;
4246
4247                    Ok(())
4248                }
4249            }
4250        })
4251    }
4252
4253    pub fn show(&mut self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
4254        let id = self.id;
4255        self.send_job(None, move |git_repo, _cx| async move {
4256            match git_repo {
4257                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
4258                    backend.show(commit).await
4259                }
4260                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4261                    let resp = client
4262                        .request(proto::GitShow {
4263                            project_id: project_id.0,
4264                            repository_id: id.to_proto(),
4265                            commit,
4266                        })
4267                        .await?;
4268
4269                    Ok(CommitDetails {
4270                        sha: resp.sha.into(),
4271                        message: resp.message.into(),
4272                        commit_timestamp: resp.commit_timestamp,
4273                        author_email: resp.author_email.into(),
4274                        author_name: resp.author_name.into(),
4275                    })
4276                }
4277            }
4278        })
4279    }
4280
4281    pub fn load_commit_diff(&mut self, commit: String) -> oneshot::Receiver<Result<CommitDiff>> {
4282        let id = self.id;
4283        self.send_job(None, move |git_repo, cx| async move {
4284            match git_repo {
4285                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
4286                    backend.load_commit(commit, cx).await
4287                }
4288                RepositoryState::Remote(RemoteRepositoryState {
4289                    client, project_id, ..
4290                }) => {
4291                    let response = client
4292                        .request(proto::LoadCommitDiff {
4293                            project_id: project_id.0,
4294                            repository_id: id.to_proto(),
4295                            commit,
4296                        })
4297                        .await?;
4298                    Ok(CommitDiff {
4299                        files: response
4300                            .files
4301                            .into_iter()
4302                            .map(|file| {
4303                                Ok(CommitFile {
4304                                    path: RepoPath::from_proto(&file.path)?,
4305                                    old_text: file.old_text,
4306                                    new_text: file.new_text,
4307                                    is_binary: file.is_binary,
4308                                })
4309                            })
4310                            .collect::<Result<Vec<_>>>()?,
4311                    })
4312                }
4313            }
4314        })
4315    }
4316
4317    pub fn file_history(
4318        &mut self,
4319        path: RepoPath,
4320    ) -> oneshot::Receiver<Result<git::repository::FileHistory>> {
4321        self.file_history_paginated(path, 0, None)
4322    }
4323
4324    pub fn file_history_paginated(
4325        &mut self,
4326        path: RepoPath,
4327        skip: usize,
4328        limit: Option<usize>,
4329    ) -> oneshot::Receiver<Result<git::repository::FileHistory>> {
4330        let id = self.id;
4331        self.send_job(None, move |git_repo, _cx| async move {
4332            match git_repo {
4333                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
4334                    backend.file_history_paginated(path, skip, limit).await
4335                }
4336                RepositoryState::Remote(RemoteRepositoryState { client, project_id }) => {
4337                    let response = client
4338                        .request(proto::GitFileHistory {
4339                            project_id: project_id.0,
4340                            repository_id: id.to_proto(),
4341                            path: path.to_proto(),
4342                            skip: skip as u64,
4343                            limit: limit.map(|l| l as u64),
4344                        })
4345                        .await?;
4346                    Ok(git::repository::FileHistory {
4347                        entries: response
4348                            .entries
4349                            .into_iter()
4350                            .map(|entry| git::repository::FileHistoryEntry {
4351                                sha: entry.sha.into(),
4352                                subject: entry.subject.into(),
4353                                message: entry.message.into(),
4354                                commit_timestamp: entry.commit_timestamp,
4355                                author_name: entry.author_name.into(),
4356                                author_email: entry.author_email.into(),
4357                            })
4358                            .collect(),
4359                        path: RepoPath::from_proto(&response.path)?,
4360                    })
4361                }
4362            }
4363        })
4364    }
4365
4366    pub fn graph_data(
4367        &mut self,
4368        log_source: LogSource,
4369        log_order: LogOrder,
4370        range: Range<usize>,
4371        cx: &mut Context<Self>,
4372    ) -> (&[Arc<InitialGraphCommitData>], bool) {
4373        let (loading_task, initial_commit_data) = self
4374            .initial_graph_data
4375            .entry((log_order, log_source.clone()))
4376            .or_insert_with(|| {
4377                let state = self.repository_state.clone();
4378                let log_source = log_source.clone();
4379                (
4380                    cx.spawn(async move |repository, cx| {
4381                        let state = state.await;
4382                        match state {
4383                            Ok(RepositoryState::Local(LocalRepositoryState {
4384                                backend, ..
4385                            })) => {
4386                                Self::local_git_graph_data(
4387                                    repository, backend, log_source, log_order, cx,
4388                                )
4389                                .await
4390                            }
4391                            Ok(RepositoryState::Remote(_)) => {
4392                                Err("Git graph is not supported for collab yet".into())
4393                            }
4394                            Err(e) => Err(SharedString::from(e)),
4395                        }
4396                    }),
4397                    vec![],
4398                )
4399            });
4400
4401        let max_start = initial_commit_data.len().saturating_sub(1);
4402        let max_end = initial_commit_data.len();
4403        (
4404            &initial_commit_data[range.start.min(max_start)..range.end.min(max_end)],
4405            !loading_task.is_ready(),
4406        )
4407    }
4408
4409    async fn local_git_graph_data(
4410        this: WeakEntity<Self>,
4411        backend: Arc<dyn GitRepository>,
4412        log_source: LogSource,
4413        log_order: LogOrder,
4414        cx: &mut AsyncApp,
4415    ) -> Result<(), SharedString> {
4416        let (request_tx, request_rx) =
4417            smol::channel::unbounded::<Vec<Arc<InitialGraphCommitData>>>();
4418
4419        let task = cx.background_executor().spawn({
4420            let log_source = log_source.clone();
4421            async move {
4422                backend
4423                    .initial_graph_data(log_source, log_order, request_tx)
4424                    .await
4425                    .map_err(|err| SharedString::from(err.to_string()))
4426            }
4427        });
4428
4429        let graph_data_key = (log_order, log_source.clone());
4430
4431        while let Ok(initial_graph_commit_data) = request_rx.recv().await {
4432            this.update(cx, |repository, cx| {
4433                let graph_data = repository
4434                    .initial_graph_data
4435                    .get_mut(&graph_data_key)
4436                    .map(|(_, graph_data)| graph_data);
4437                debug_assert!(
4438                    graph_data.is_some(),
4439                    "This task should be dropped if data doesn't exist"
4440                );
4441
4442                if let Some(graph_data) = graph_data {
4443                    graph_data.extend(initial_graph_commit_data);
4444                    cx.emit(RepositoryEvent::GitGraphCountUpdated(
4445                        graph_data_key.clone(),
4446                        graph_data.len(),
4447                    ));
4448                }
4449            })
4450            .ok();
4451        }
4452
4453        task.await?;
4454
4455        Ok(())
4456    }
4457
4458    pub fn fetch_commit_data(&mut self, sha: Oid, cx: &mut Context<Self>) -> &CommitDataState {
4459        if !self.commit_data.contains_key(&sha) {
4460            match &self.graph_commit_data_handler {
4461                GraphCommitHandlerState::Open(handler) => {
4462                    if handler.commit_data_request.try_send(sha).is_ok() {
4463                        let old_value = self.commit_data.insert(sha, CommitDataState::Loading);
4464                        debug_assert!(old_value.is_none(), "We should never overwrite commit data");
4465                    }
4466                }
4467                GraphCommitHandlerState::Closed => {
4468                    self.open_graph_commit_data_handler(cx);
4469                }
4470                GraphCommitHandlerState::Starting => {}
4471            }
4472        }
4473
4474        self.commit_data
4475            .get(&sha)
4476            .unwrap_or(&CommitDataState::Loading)
4477    }
4478
4479    fn open_graph_commit_data_handler(&mut self, cx: &mut Context<Self>) {
4480        self.graph_commit_data_handler = GraphCommitHandlerState::Starting;
4481
4482        let state = self.repository_state.clone();
4483        let (result_tx, result_rx) = smol::channel::bounded::<(Oid, GraphCommitData)>(64);
4484        let (request_tx, request_rx) = smol::channel::unbounded::<Oid>();
4485
4486        let foreground_task = cx.spawn(async move |this, cx| {
4487            while let Ok((sha, commit_data)) = result_rx.recv().await {
4488                let result = this.update(cx, |this, cx| {
4489                    let old_value = this
4490                        .commit_data
4491                        .insert(sha, CommitDataState::Loaded(Arc::new(commit_data)));
4492                    debug_assert!(
4493                        !matches!(old_value, Some(CommitDataState::Loaded(_))),
4494                        "We should never overwrite commit data"
4495                    );
4496
4497                    cx.notify();
4498                });
4499                if result.is_err() {
4500                    break;
4501                }
4502            }
4503
4504            this.update(cx, |this, _cx| {
4505                this.graph_commit_data_handler = GraphCommitHandlerState::Closed;
4506            })
4507            .ok();
4508        });
4509
4510        let request_tx_for_handler = request_tx;
4511        let background_executor = cx.background_executor().clone();
4512
4513        cx.background_spawn(async move {
4514            let backend = match state.await {
4515                Ok(RepositoryState::Local(LocalRepositoryState { backend, .. })) => backend,
4516                Ok(RepositoryState::Remote(_)) => {
4517                    log::error!("commit_data_reader not supported for remote repositories");
4518                    return;
4519                }
4520                Err(error) => {
4521                    log::error!("failed to get repository state: {error}");
4522                    return;
4523                }
4524            };
4525
4526            let reader = match backend.commit_data_reader() {
4527                Ok(reader) => reader,
4528                Err(error) => {
4529                    log::error!("failed to create commit data reader: {error:?}");
4530                    return;
4531                }
4532            };
4533
4534            loop {
4535                let timeout = background_executor.timer(std::time::Duration::from_secs(10));
4536
4537                futures::select_biased! {
4538                    sha = futures::FutureExt::fuse(request_rx.recv()) => {
4539                        let Ok(sha) = sha else {
4540                            break;
4541                        };
4542
4543                        match reader.read(sha).await {
4544                            Ok(commit_data) => {
4545                                if result_tx.send((sha, commit_data)).await.is_err() {
4546                                    break;
4547                                }
4548                            }
4549                            Err(error) => {
4550                                log::error!("failed to read commit data for {sha}: {error:?}");
4551                            }
4552                        }
4553                    }
4554                    _ = futures::FutureExt::fuse(timeout) => {
4555                        break;
4556                    }
4557                }
4558            }
4559
4560            drop(result_tx);
4561        })
4562        .detach();
4563
4564        self.graph_commit_data_handler = GraphCommitHandlerState::Open(GraphCommitDataHandler {
4565            _task: foreground_task,
4566            commit_data_request: request_tx_for_handler,
4567        });
4568    }
4569
4570    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
4571        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
4572    }
4573
4574    fn save_buffers<'a>(
4575        &self,
4576        entries: impl IntoIterator<Item = &'a RepoPath>,
4577        cx: &mut Context<Self>,
4578    ) -> Vec<Task<anyhow::Result<()>>> {
4579        let mut save_futures = Vec::new();
4580        if let Some(buffer_store) = self.buffer_store(cx) {
4581            buffer_store.update(cx, |buffer_store, cx| {
4582                for path in entries {
4583                    let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
4584                        continue;
4585                    };
4586                    if let Some(buffer) = buffer_store.get_by_path(&project_path)
4587                        && buffer
4588                            .read(cx)
4589                            .file()
4590                            .is_some_and(|file| file.disk_state().exists())
4591                        && buffer.read(cx).has_unsaved_edits()
4592                    {
4593                        save_futures.push(buffer_store.save_buffer(buffer, cx));
4594                    }
4595                }
4596            })
4597        }
4598        save_futures
4599    }
4600
4601    pub fn stage_entries(
4602        &mut self,
4603        entries: Vec<RepoPath>,
4604        cx: &mut Context<Self>,
4605    ) -> Task<anyhow::Result<()>> {
4606        self.stage_or_unstage_entries(true, entries, cx)
4607    }
4608
4609    pub fn unstage_entries(
4610        &mut self,
4611        entries: Vec<RepoPath>,
4612        cx: &mut Context<Self>,
4613    ) -> Task<anyhow::Result<()>> {
4614        self.stage_or_unstage_entries(false, entries, cx)
4615    }
4616
4617    fn stage_or_unstage_entries(
4618        &mut self,
4619        stage: bool,
4620        entries: Vec<RepoPath>,
4621        cx: &mut Context<Self>,
4622    ) -> Task<anyhow::Result<()>> {
4623        if entries.is_empty() {
4624            return Task::ready(Ok(()));
4625        }
4626        let Some(git_store) = self.git_store.upgrade() else {
4627            return Task::ready(Ok(()));
4628        };
4629        let id = self.id;
4630        let save_tasks = self.save_buffers(&entries, cx);
4631        let paths = entries
4632            .iter()
4633            .map(|p| p.as_unix_str())
4634            .collect::<Vec<_>>()
4635            .join(" ");
4636        let status = if stage {
4637            format!("git add {paths}")
4638        } else {
4639            format!("git reset {paths}")
4640        };
4641        let job_key = GitJobKey::WriteIndex(entries.clone());
4642
4643        self.spawn_job_with_tracking(
4644            entries.clone(),
4645            if stage {
4646                pending_op::GitStatus::Staged
4647            } else {
4648                pending_op::GitStatus::Unstaged
4649            },
4650            cx,
4651            async move |this, cx| {
4652                for save_task in save_tasks {
4653                    save_task.await?;
4654                }
4655
4656                this.update(cx, |this, cx| {
4657                    let weak_this = cx.weak_entity();
4658                    this.send_keyed_job(
4659                        Some(job_key),
4660                        Some(status.into()),
4661                        move |git_repo, mut cx| async move {
4662                            let hunk_staging_operation_counts = weak_this
4663                                .update(&mut cx, |this, cx| {
4664                                    let mut hunk_staging_operation_counts = HashMap::default();
4665                                    for path in &entries {
4666                                        let Some(project_path) =
4667                                            this.repo_path_to_project_path(path, cx)
4668                                        else {
4669                                            continue;
4670                                        };
4671                                        let Some(buffer) = git_store
4672                                            .read(cx)
4673                                            .buffer_store
4674                                            .read(cx)
4675                                            .get_by_path(&project_path)
4676                                        else {
4677                                            continue;
4678                                        };
4679                                        let Some(diff_state) = git_store
4680                                            .read(cx)
4681                                            .diffs
4682                                            .get(&buffer.read(cx).remote_id())
4683                                            .cloned()
4684                                        else {
4685                                            continue;
4686                                        };
4687                                        let Some(uncommitted_diff) =
4688                                            diff_state.read(cx).uncommitted_diff.as_ref().and_then(
4689                                                |uncommitted_diff| uncommitted_diff.upgrade(),
4690                                            )
4691                                        else {
4692                                            continue;
4693                                        };
4694                                        let buffer_snapshot = buffer.read(cx).text_snapshot();
4695                                        let file_exists = buffer
4696                                            .read(cx)
4697                                            .file()
4698                                            .is_some_and(|file| file.disk_state().exists());
4699                                        let hunk_staging_operation_count =
4700                                            diff_state.update(cx, |diff_state, cx| {
4701                                                uncommitted_diff.update(
4702                                                    cx,
4703                                                    |uncommitted_diff, cx| {
4704                                                        uncommitted_diff
4705                                                            .stage_or_unstage_all_hunks(
4706                                                                stage,
4707                                                                &buffer_snapshot,
4708                                                                file_exists,
4709                                                                cx,
4710                                                            );
4711                                                    },
4712                                                );
4713
4714                                                diff_state.hunk_staging_operation_count += 1;
4715                                                diff_state.hunk_staging_operation_count
4716                                            });
4717                                        hunk_staging_operation_counts.insert(
4718                                            diff_state.downgrade(),
4719                                            hunk_staging_operation_count,
4720                                        );
4721                                    }
4722                                    hunk_staging_operation_counts
4723                                })
4724                                .unwrap_or_default();
4725
4726                            let result = match git_repo {
4727                                RepositoryState::Local(LocalRepositoryState {
4728                                    backend,
4729                                    environment,
4730                                    ..
4731                                }) => {
4732                                    if stage {
4733                                        backend.stage_paths(entries, environment.clone()).await
4734                                    } else {
4735                                        backend.unstage_paths(entries, environment.clone()).await
4736                                    }
4737                                }
4738                                RepositoryState::Remote(RemoteRepositoryState {
4739                                    project_id,
4740                                    client,
4741                                }) => {
4742                                    if stage {
4743                                        client
4744                                            .request(proto::Stage {
4745                                                project_id: project_id.0,
4746                                                repository_id: id.to_proto(),
4747                                                paths: entries
4748                                                    .into_iter()
4749                                                    .map(|repo_path| repo_path.to_proto())
4750                                                    .collect(),
4751                                            })
4752                                            .await
4753                                            .context("sending stage request")
4754                                            .map(|_| ())
4755                                    } else {
4756                                        client
4757                                            .request(proto::Unstage {
4758                                                project_id: project_id.0,
4759                                                repository_id: id.to_proto(),
4760                                                paths: entries
4761                                                    .into_iter()
4762                                                    .map(|repo_path| repo_path.to_proto())
4763                                                    .collect(),
4764                                            })
4765                                            .await
4766                                            .context("sending unstage request")
4767                                            .map(|_| ())
4768                                    }
4769                                }
4770                            };
4771
4772                            for (diff_state, hunk_staging_operation_count) in
4773                                hunk_staging_operation_counts
4774                            {
4775                                diff_state
4776                                    .update(&mut cx, |diff_state, cx| {
4777                                        if result.is_ok() {
4778                                            diff_state.hunk_staging_operation_count_as_of_write =
4779                                                hunk_staging_operation_count;
4780                                        } else if let Some(uncommitted_diff) =
4781                                            &diff_state.uncommitted_diff
4782                                        {
4783                                            uncommitted_diff
4784                                                .update(cx, |uncommitted_diff, cx| {
4785                                                    uncommitted_diff.clear_pending_hunks(cx);
4786                                                })
4787                                                .ok();
4788                                        }
4789                                    })
4790                                    .ok();
4791                            }
4792
4793                            result
4794                        },
4795                    )
4796                })?
4797                .await?
4798            },
4799        )
4800    }
4801
4802    pub fn stage_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
4803        let to_stage = self
4804            .cached_status()
4805            .filter_map(|entry| {
4806                if let Some(ops) = self.pending_ops_for_path(&entry.repo_path) {
4807                    if ops.staging() || ops.staged() {
4808                        None
4809                    } else {
4810                        Some(entry.repo_path)
4811                    }
4812                } else if entry.status.staging().is_fully_staged() {
4813                    None
4814                } else {
4815                    Some(entry.repo_path)
4816                }
4817            })
4818            .collect();
4819        self.stage_or_unstage_entries(true, to_stage, cx)
4820    }
4821
4822    pub fn unstage_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
4823        let to_unstage = self
4824            .cached_status()
4825            .filter_map(|entry| {
4826                if let Some(ops) = self.pending_ops_for_path(&entry.repo_path) {
4827                    if !ops.staging() && !ops.staged() {
4828                        None
4829                    } else {
4830                        Some(entry.repo_path)
4831                    }
4832                } else if entry.status.staging().is_fully_unstaged() {
4833                    None
4834                } else {
4835                    Some(entry.repo_path)
4836                }
4837            })
4838            .collect();
4839        self.stage_or_unstage_entries(false, to_unstage, cx)
4840    }
4841
4842    pub fn stash_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
4843        let to_stash = self.cached_status().map(|entry| entry.repo_path).collect();
4844
4845        self.stash_entries(to_stash, cx)
4846    }
4847
4848    pub fn stash_entries(
4849        &mut self,
4850        entries: Vec<RepoPath>,
4851        cx: &mut Context<Self>,
4852    ) -> Task<anyhow::Result<()>> {
4853        let id = self.id;
4854
4855        cx.spawn(async move |this, cx| {
4856            this.update(cx, |this, _| {
4857                this.send_job(None, move |git_repo, _cx| async move {
4858                    match git_repo {
4859                        RepositoryState::Local(LocalRepositoryState {
4860                            backend,
4861                            environment,
4862                            ..
4863                        }) => backend.stash_paths(entries, environment).await,
4864                        RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4865                            client
4866                                .request(proto::Stash {
4867                                    project_id: project_id.0,
4868                                    repository_id: id.to_proto(),
4869                                    paths: entries
4870                                        .into_iter()
4871                                        .map(|repo_path| repo_path.to_proto())
4872                                        .collect(),
4873                                })
4874                                .await
4875                                .context("sending stash request")?;
4876                            Ok(())
4877                        }
4878                    }
4879                })
4880            })?
4881            .await??;
4882            Ok(())
4883        })
4884    }
4885
4886    pub fn stash_pop(
4887        &mut self,
4888        index: Option<usize>,
4889        cx: &mut Context<Self>,
4890    ) -> Task<anyhow::Result<()>> {
4891        let id = self.id;
4892        cx.spawn(async move |this, cx| {
4893            this.update(cx, |this, _| {
4894                this.send_job(None, move |git_repo, _cx| async move {
4895                    match git_repo {
4896                        RepositoryState::Local(LocalRepositoryState {
4897                            backend,
4898                            environment,
4899                            ..
4900                        }) => backend.stash_pop(index, environment).await,
4901                        RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4902                            client
4903                                .request(proto::StashPop {
4904                                    project_id: project_id.0,
4905                                    repository_id: id.to_proto(),
4906                                    stash_index: index.map(|i| i as u64),
4907                                })
4908                                .await
4909                                .context("sending stash pop request")?;
4910                            Ok(())
4911                        }
4912                    }
4913                })
4914            })?
4915            .await??;
4916            Ok(())
4917        })
4918    }
4919
4920    pub fn stash_apply(
4921        &mut self,
4922        index: Option<usize>,
4923        cx: &mut Context<Self>,
4924    ) -> Task<anyhow::Result<()>> {
4925        let id = self.id;
4926        cx.spawn(async move |this, cx| {
4927            this.update(cx, |this, _| {
4928                this.send_job(None, move |git_repo, _cx| async move {
4929                    match git_repo {
4930                        RepositoryState::Local(LocalRepositoryState {
4931                            backend,
4932                            environment,
4933                            ..
4934                        }) => backend.stash_apply(index, environment).await,
4935                        RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4936                            client
4937                                .request(proto::StashApply {
4938                                    project_id: project_id.0,
4939                                    repository_id: id.to_proto(),
4940                                    stash_index: index.map(|i| i as u64),
4941                                })
4942                                .await
4943                                .context("sending stash apply request")?;
4944                            Ok(())
4945                        }
4946                    }
4947                })
4948            })?
4949            .await??;
4950            Ok(())
4951        })
4952    }
4953
4954    pub fn stash_drop(
4955        &mut self,
4956        index: Option<usize>,
4957        cx: &mut Context<Self>,
4958    ) -> oneshot::Receiver<anyhow::Result<()>> {
4959        let id = self.id;
4960        let updates_tx = self
4961            .git_store()
4962            .and_then(|git_store| match &git_store.read(cx).state {
4963                GitStoreState::Local { downstream, .. } => downstream
4964                    .as_ref()
4965                    .map(|downstream| downstream.updates_tx.clone()),
4966                _ => None,
4967            });
4968        let this = cx.weak_entity();
4969        self.send_job(None, move |git_repo, mut cx| async move {
4970            match git_repo {
4971                RepositoryState::Local(LocalRepositoryState {
4972                    backend,
4973                    environment,
4974                    ..
4975                }) => {
4976                    // TODO would be nice to not have to do this manually
4977                    let result = backend.stash_drop(index, environment).await;
4978                    if result.is_ok()
4979                        && let Ok(stash_entries) = backend.stash_entries().await
4980                    {
4981                        let snapshot = this.update(&mut cx, |this, cx| {
4982                            this.snapshot.stash_entries = stash_entries;
4983                            cx.emit(RepositoryEvent::StashEntriesChanged);
4984                            this.snapshot.clone()
4985                        })?;
4986                        if let Some(updates_tx) = updates_tx {
4987                            updates_tx
4988                                .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
4989                                .ok();
4990                        }
4991                    }
4992
4993                    result
4994                }
4995                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4996                    client
4997                        .request(proto::StashDrop {
4998                            project_id: project_id.0,
4999                            repository_id: id.to_proto(),
5000                            stash_index: index.map(|i| i as u64),
5001                        })
5002                        .await
5003                        .context("sending stash pop request")?;
5004                    Ok(())
5005                }
5006            }
5007        })
5008    }
5009
5010    pub fn run_hook(&mut self, hook: RunHook, _cx: &mut App) -> oneshot::Receiver<Result<()>> {
5011        let id = self.id;
5012        self.send_job(
5013            Some(format!("git hook {}", hook.as_str()).into()),
5014            move |git_repo, _cx| async move {
5015                match git_repo {
5016                    RepositoryState::Local(LocalRepositoryState {
5017                        backend,
5018                        environment,
5019                        ..
5020                    }) => backend.run_hook(hook, environment.clone()).await,
5021                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5022                        client
5023                            .request(proto::RunGitHook {
5024                                project_id: project_id.0,
5025                                repository_id: id.to_proto(),
5026                                hook: hook.to_proto(),
5027                            })
5028                            .await?;
5029
5030                        Ok(())
5031                    }
5032                }
5033            },
5034        )
5035    }
5036
5037    pub fn commit(
5038        &mut self,
5039        message: SharedString,
5040        name_and_email: Option<(SharedString, SharedString)>,
5041        options: CommitOptions,
5042        askpass: AskPassDelegate,
5043        cx: &mut App,
5044    ) -> oneshot::Receiver<Result<()>> {
5045        let id = self.id;
5046        let askpass_delegates = self.askpass_delegates.clone();
5047        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5048
5049        let rx = self.run_hook(RunHook::PreCommit, cx);
5050
5051        self.send_job(Some("git commit".into()), move |git_repo, _cx| async move {
5052            rx.await??;
5053
5054            match git_repo {
5055                RepositoryState::Local(LocalRepositoryState {
5056                    backend,
5057                    environment,
5058                    ..
5059                }) => {
5060                    backend
5061                        .commit(message, name_and_email, options, askpass, environment)
5062                        .await
5063                }
5064                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5065                    askpass_delegates.lock().insert(askpass_id, askpass);
5066                    let _defer = util::defer(|| {
5067                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5068                        debug_assert!(askpass_delegate.is_some());
5069                    });
5070                    let (name, email) = name_and_email.unzip();
5071                    client
5072                        .request(proto::Commit {
5073                            project_id: project_id.0,
5074                            repository_id: id.to_proto(),
5075                            message: String::from(message),
5076                            name: name.map(String::from),
5077                            email: email.map(String::from),
5078                            options: Some(proto::commit::CommitOptions {
5079                                amend: options.amend,
5080                                signoff: options.signoff,
5081                            }),
5082                            askpass_id,
5083                        })
5084                        .await
5085                        .context("sending commit request")?;
5086
5087                    Ok(())
5088                }
5089            }
5090        })
5091    }
5092
5093    pub fn fetch(
5094        &mut self,
5095        fetch_options: FetchOptions,
5096        askpass: AskPassDelegate,
5097        _cx: &mut App,
5098    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
5099        let askpass_delegates = self.askpass_delegates.clone();
5100        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5101        let id = self.id;
5102
5103        self.send_job(Some("git fetch".into()), move |git_repo, cx| async move {
5104            match git_repo {
5105                RepositoryState::Local(LocalRepositoryState {
5106                    backend,
5107                    environment,
5108                    ..
5109                }) => backend.fetch(fetch_options, askpass, environment, cx).await,
5110                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5111                    askpass_delegates.lock().insert(askpass_id, askpass);
5112                    let _defer = util::defer(|| {
5113                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5114                        debug_assert!(askpass_delegate.is_some());
5115                    });
5116
5117                    let response = client
5118                        .request(proto::Fetch {
5119                            project_id: project_id.0,
5120                            repository_id: id.to_proto(),
5121                            askpass_id,
5122                            remote: fetch_options.to_proto(),
5123                        })
5124                        .await
5125                        .context("sending fetch request")?;
5126
5127                    Ok(RemoteCommandOutput {
5128                        stdout: response.stdout,
5129                        stderr: response.stderr,
5130                    })
5131                }
5132            }
5133        })
5134    }
5135
5136    pub fn push(
5137        &mut self,
5138        branch: SharedString,
5139        remote_branch: SharedString,
5140        remote: SharedString,
5141        options: Option<PushOptions>,
5142        askpass: AskPassDelegate,
5143        cx: &mut Context<Self>,
5144    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
5145        let askpass_delegates = self.askpass_delegates.clone();
5146        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5147        let id = self.id;
5148
5149        let args = options
5150            .map(|option| match option {
5151                PushOptions::SetUpstream => " --set-upstream",
5152                PushOptions::Force => " --force-with-lease",
5153            })
5154            .unwrap_or("");
5155
5156        let updates_tx = self
5157            .git_store()
5158            .and_then(|git_store| match &git_store.read(cx).state {
5159                GitStoreState::Local { downstream, .. } => downstream
5160                    .as_ref()
5161                    .map(|downstream| downstream.updates_tx.clone()),
5162                _ => None,
5163            });
5164
5165        let this = cx.weak_entity();
5166        self.send_job(
5167            Some(format!("git push {} {} {}:{}", args, remote, branch, remote_branch).into()),
5168            move |git_repo, mut cx| async move {
5169                match git_repo {
5170                    RepositoryState::Local(LocalRepositoryState {
5171                        backend,
5172                        environment,
5173                        ..
5174                    }) => {
5175                        let result = backend
5176                            .push(
5177                                branch.to_string(),
5178                                remote_branch.to_string(),
5179                                remote.to_string(),
5180                                options,
5181                                askpass,
5182                                environment.clone(),
5183                                cx.clone(),
5184                            )
5185                            .await;
5186                        // TODO would be nice to not have to do this manually
5187                        if result.is_ok() {
5188                            let branches = backend.branches().await?;
5189                            let branch = branches.into_iter().find(|branch| branch.is_head);
5190                            log::info!("head branch after scan is {branch:?}");
5191                            let snapshot = this.update(&mut cx, |this, cx| {
5192                                this.snapshot.branch = branch;
5193                                cx.emit(RepositoryEvent::BranchChanged);
5194                                this.snapshot.clone()
5195                            })?;
5196                            if let Some(updates_tx) = updates_tx {
5197                                updates_tx
5198                                    .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
5199                                    .ok();
5200                            }
5201                        }
5202                        result
5203                    }
5204                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5205                        askpass_delegates.lock().insert(askpass_id, askpass);
5206                        let _defer = util::defer(|| {
5207                            let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5208                            debug_assert!(askpass_delegate.is_some());
5209                        });
5210                        let response = client
5211                            .request(proto::Push {
5212                                project_id: project_id.0,
5213                                repository_id: id.to_proto(),
5214                                askpass_id,
5215                                branch_name: branch.to_string(),
5216                                remote_branch_name: remote_branch.to_string(),
5217                                remote_name: remote.to_string(),
5218                                options: options.map(|options| match options {
5219                                    PushOptions::Force => proto::push::PushOptions::Force,
5220                                    PushOptions::SetUpstream => {
5221                                        proto::push::PushOptions::SetUpstream
5222                                    }
5223                                }
5224                                    as i32),
5225                            })
5226                            .await
5227                            .context("sending push request")?;
5228
5229                        Ok(RemoteCommandOutput {
5230                            stdout: response.stdout,
5231                            stderr: response.stderr,
5232                        })
5233                    }
5234                }
5235            },
5236        )
5237    }
5238
5239    pub fn pull(
5240        &mut self,
5241        branch: Option<SharedString>,
5242        remote: SharedString,
5243        rebase: bool,
5244        askpass: AskPassDelegate,
5245        _cx: &mut App,
5246    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
5247        let askpass_delegates = self.askpass_delegates.clone();
5248        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5249        let id = self.id;
5250
5251        let mut status = "git pull".to_string();
5252        if rebase {
5253            status.push_str(" --rebase");
5254        }
5255        status.push_str(&format!(" {}", remote));
5256        if let Some(b) = &branch {
5257            status.push_str(&format!(" {}", b));
5258        }
5259
5260        self.send_job(Some(status.into()), move |git_repo, cx| async move {
5261            match git_repo {
5262                RepositoryState::Local(LocalRepositoryState {
5263                    backend,
5264                    environment,
5265                    ..
5266                }) => {
5267                    backend
5268                        .pull(
5269                            branch.as_ref().map(|b| b.to_string()),
5270                            remote.to_string(),
5271                            rebase,
5272                            askpass,
5273                            environment.clone(),
5274                            cx,
5275                        )
5276                        .await
5277                }
5278                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5279                    askpass_delegates.lock().insert(askpass_id, askpass);
5280                    let _defer = util::defer(|| {
5281                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5282                        debug_assert!(askpass_delegate.is_some());
5283                    });
5284                    let response = client
5285                        .request(proto::Pull {
5286                            project_id: project_id.0,
5287                            repository_id: id.to_proto(),
5288                            askpass_id,
5289                            rebase,
5290                            branch_name: branch.as_ref().map(|b| b.to_string()),
5291                            remote_name: remote.to_string(),
5292                        })
5293                        .await
5294                        .context("sending pull request")?;
5295
5296                    Ok(RemoteCommandOutput {
5297                        stdout: response.stdout,
5298                        stderr: response.stderr,
5299                    })
5300                }
5301            }
5302        })
5303    }
5304
5305    fn spawn_set_index_text_job(
5306        &mut self,
5307        path: RepoPath,
5308        content: Option<String>,
5309        hunk_staging_operation_count: Option<usize>,
5310        cx: &mut Context<Self>,
5311    ) -> oneshot::Receiver<anyhow::Result<()>> {
5312        let id = self.id;
5313        let this = cx.weak_entity();
5314        let git_store = self.git_store.clone();
5315        let abs_path = self.snapshot.repo_path_to_abs_path(&path);
5316        self.send_keyed_job(
5317            Some(GitJobKey::WriteIndex(vec![path.clone()])),
5318            None,
5319            move |git_repo, mut cx| async move {
5320                log::debug!(
5321                    "start updating index text for buffer {}",
5322                    path.as_unix_str()
5323                );
5324
5325                match git_repo {
5326                    RepositoryState::Local(LocalRepositoryState {
5327                        fs,
5328                        backend,
5329                        environment,
5330                        ..
5331                    }) => {
5332                        let executable = match fs.metadata(&abs_path).await {
5333                            Ok(Some(meta)) => meta.is_executable,
5334                            Ok(None) => false,
5335                            Err(_err) => false,
5336                        };
5337                        backend
5338                            .set_index_text(path.clone(), content, environment.clone(), executable)
5339                            .await?;
5340                    }
5341                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5342                        client
5343                            .request(proto::SetIndexText {
5344                                project_id: project_id.0,
5345                                repository_id: id.to_proto(),
5346                                path: path.to_proto(),
5347                                text: content,
5348                            })
5349                            .await?;
5350                    }
5351                }
5352                log::debug!(
5353                    "finish updating index text for buffer {}",
5354                    path.as_unix_str()
5355                );
5356
5357                if let Some(hunk_staging_operation_count) = hunk_staging_operation_count {
5358                    let project_path = this
5359                        .read_with(&cx, |this, cx| this.repo_path_to_project_path(&path, cx))
5360                        .ok()
5361                        .flatten();
5362                    git_store
5363                        .update(&mut cx, |git_store, cx| {
5364                            let buffer_id = git_store
5365                                .buffer_store
5366                                .read(cx)
5367                                .get_by_path(&project_path?)?
5368                                .read(cx)
5369                                .remote_id();
5370                            let diff_state = git_store.diffs.get(&buffer_id)?;
5371                            diff_state.update(cx, |diff_state, _| {
5372                                diff_state.hunk_staging_operation_count_as_of_write =
5373                                    hunk_staging_operation_count;
5374                            });
5375                            Some(())
5376                        })
5377                        .context("Git store dropped")?;
5378                }
5379                Ok(())
5380            },
5381        )
5382    }
5383
5384    pub fn create_remote(
5385        &mut self,
5386        remote_name: String,
5387        remote_url: String,
5388    ) -> oneshot::Receiver<Result<()>> {
5389        let id = self.id;
5390        self.send_job(
5391            Some(format!("git remote add {remote_name} {remote_url}").into()),
5392            move |repo, _cx| async move {
5393                match repo {
5394                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5395                        backend.create_remote(remote_name, remote_url).await
5396                    }
5397                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5398                        client
5399                            .request(proto::GitCreateRemote {
5400                                project_id: project_id.0,
5401                                repository_id: id.to_proto(),
5402                                remote_name,
5403                                remote_url,
5404                            })
5405                            .await?;
5406
5407                        Ok(())
5408                    }
5409                }
5410            },
5411        )
5412    }
5413
5414    pub fn remove_remote(&mut self, remote_name: String) -> oneshot::Receiver<Result<()>> {
5415        let id = self.id;
5416        self.send_job(
5417            Some(format!("git remove remote {remote_name}").into()),
5418            move |repo, _cx| async move {
5419                match repo {
5420                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5421                        backend.remove_remote(remote_name).await
5422                    }
5423                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5424                        client
5425                            .request(proto::GitRemoveRemote {
5426                                project_id: project_id.0,
5427                                repository_id: id.to_proto(),
5428                                remote_name,
5429                            })
5430                            .await?;
5431
5432                        Ok(())
5433                    }
5434                }
5435            },
5436        )
5437    }
5438
5439    pub fn get_remotes(
5440        &mut self,
5441        branch_name: Option<String>,
5442        is_push: bool,
5443    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
5444        let id = self.id;
5445        self.send_job(None, move |repo, _cx| async move {
5446            match repo {
5447                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5448                    let remote = if let Some(branch_name) = branch_name {
5449                        if is_push {
5450                            backend.get_push_remote(branch_name).await?
5451                        } else {
5452                            backend.get_branch_remote(branch_name).await?
5453                        }
5454                    } else {
5455                        None
5456                    };
5457
5458                    match remote {
5459                        Some(remote) => Ok(vec![remote]),
5460                        None => backend.get_all_remotes().await,
5461                    }
5462                }
5463                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5464                    let response = client
5465                        .request(proto::GetRemotes {
5466                            project_id: project_id.0,
5467                            repository_id: id.to_proto(),
5468                            branch_name,
5469                            is_push,
5470                        })
5471                        .await?;
5472
5473                    let remotes = response
5474                        .remotes
5475                        .into_iter()
5476                        .map(|remotes| Remote {
5477                            name: remotes.name.into(),
5478                        })
5479                        .collect();
5480
5481                    Ok(remotes)
5482                }
5483            }
5484        })
5485    }
5486
5487    pub fn branches(&mut self) -> oneshot::Receiver<Result<Vec<Branch>>> {
5488        let id = self.id;
5489        self.send_job(None, move |repo, _| async move {
5490            match repo {
5491                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5492                    backend.branches().await
5493                }
5494                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5495                    let response = client
5496                        .request(proto::GitGetBranches {
5497                            project_id: project_id.0,
5498                            repository_id: id.to_proto(),
5499                        })
5500                        .await?;
5501
5502                    let branches = response
5503                        .branches
5504                        .into_iter()
5505                        .map(|branch| proto_to_branch(&branch))
5506                        .collect();
5507
5508                    Ok(branches)
5509                }
5510            }
5511        })
5512    }
5513
5514    pub fn worktrees(&mut self) -> oneshot::Receiver<Result<Vec<GitWorktree>>> {
5515        let id = self.id;
5516        self.send_job(None, move |repo, _| async move {
5517            match repo {
5518                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5519                    backend.worktrees().await
5520                }
5521                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5522                    let response = client
5523                        .request(proto::GitGetWorktrees {
5524                            project_id: project_id.0,
5525                            repository_id: id.to_proto(),
5526                        })
5527                        .await?;
5528
5529                    let worktrees = response
5530                        .worktrees
5531                        .into_iter()
5532                        .map(|worktree| proto_to_worktree(&worktree))
5533                        .collect();
5534
5535                    Ok(worktrees)
5536                }
5537            }
5538        })
5539    }
5540
5541    pub fn create_worktree(
5542        &mut self,
5543        name: String,
5544        directory: PathBuf,
5545        commit: Option<String>,
5546    ) -> oneshot::Receiver<Result<()>> {
5547        let id = self.id;
5548        self.send_job(
5549            Some("git worktree add".into()),
5550            move |repo, _cx| async move {
5551                match repo {
5552                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5553                        backend.create_worktree(name, directory, commit).await
5554                    }
5555                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5556                        client
5557                            .request(proto::GitCreateWorktree {
5558                                project_id: project_id.0,
5559                                repository_id: id.to_proto(),
5560                                name,
5561                                directory: directory.to_string_lossy().to_string(),
5562                                commit,
5563                            })
5564                            .await?;
5565
5566                        Ok(())
5567                    }
5568                }
5569            },
5570        )
5571    }
5572
5573    pub fn default_branch(
5574        &mut self,
5575        include_remote_name: bool,
5576    ) -> oneshot::Receiver<Result<Option<SharedString>>> {
5577        let id = self.id;
5578        self.send_job(None, move |repo, _| async move {
5579            match repo {
5580                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5581                    backend.default_branch(include_remote_name).await
5582                }
5583                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5584                    let response = client
5585                        .request(proto::GetDefaultBranch {
5586                            project_id: project_id.0,
5587                            repository_id: id.to_proto(),
5588                        })
5589                        .await?;
5590
5591                    anyhow::Ok(response.branch.map(SharedString::from))
5592                }
5593            }
5594        })
5595    }
5596
5597    pub fn diff_tree(
5598        &mut self,
5599        diff_type: DiffTreeType,
5600        _cx: &App,
5601    ) -> oneshot::Receiver<Result<TreeDiff>> {
5602        let repository_id = self.snapshot.id;
5603        self.send_job(None, move |repo, _cx| async move {
5604            match repo {
5605                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5606                    backend.diff_tree(diff_type).await
5607                }
5608                RepositoryState::Remote(RemoteRepositoryState { client, project_id }) => {
5609                    let response = client
5610                        .request(proto::GetTreeDiff {
5611                            project_id: project_id.0,
5612                            repository_id: repository_id.0,
5613                            is_merge: matches!(diff_type, DiffTreeType::MergeBase { .. }),
5614                            base: diff_type.base().to_string(),
5615                            head: diff_type.head().to_string(),
5616                        })
5617                        .await?;
5618
5619                    let entries = response
5620                        .entries
5621                        .into_iter()
5622                        .filter_map(|entry| {
5623                            let status = match entry.status() {
5624                                proto::tree_diff_status::Status::Added => TreeDiffStatus::Added,
5625                                proto::tree_diff_status::Status::Modified => {
5626                                    TreeDiffStatus::Modified {
5627                                        old: git::Oid::from_str(
5628                                            &entry.oid.context("missing oid").log_err()?,
5629                                        )
5630                                        .log_err()?,
5631                                    }
5632                                }
5633                                proto::tree_diff_status::Status::Deleted => {
5634                                    TreeDiffStatus::Deleted {
5635                                        old: git::Oid::from_str(
5636                                            &entry.oid.context("missing oid").log_err()?,
5637                                        )
5638                                        .log_err()?,
5639                                    }
5640                                }
5641                            };
5642                            Some((
5643                                RepoPath::from_rel_path(
5644                                    &RelPath::from_proto(&entry.path).log_err()?,
5645                                ),
5646                                status,
5647                            ))
5648                        })
5649                        .collect();
5650
5651                    Ok(TreeDiff { entries })
5652                }
5653            }
5654        })
5655    }
5656
5657    pub fn diff(&mut self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
5658        let id = self.id;
5659        self.send_job(None, move |repo, _cx| async move {
5660            match repo {
5661                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5662                    backend.diff(diff_type).await
5663                }
5664                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5665                    let (proto_diff_type, merge_base_ref) = match &diff_type {
5666                        DiffType::HeadToIndex => {
5667                            (proto::git_diff::DiffType::HeadToIndex.into(), None)
5668                        }
5669                        DiffType::HeadToWorktree => {
5670                            (proto::git_diff::DiffType::HeadToWorktree.into(), None)
5671                        }
5672                        DiffType::MergeBase { base_ref } => (
5673                            proto::git_diff::DiffType::MergeBase.into(),
5674                            Some(base_ref.to_string()),
5675                        ),
5676                    };
5677                    let response = client
5678                        .request(proto::GitDiff {
5679                            project_id: project_id.0,
5680                            repository_id: id.to_proto(),
5681                            diff_type: proto_diff_type,
5682                            merge_base_ref,
5683                        })
5684                        .await?;
5685
5686                    Ok(response.diff)
5687                }
5688            }
5689        })
5690    }
5691
5692    pub fn create_branch(
5693        &mut self,
5694        branch_name: String,
5695        base_branch: Option<String>,
5696    ) -> oneshot::Receiver<Result<()>> {
5697        let id = self.id;
5698        let status_msg = if let Some(ref base) = base_branch {
5699            format!("git switch -c {branch_name} {base}").into()
5700        } else {
5701            format!("git switch -c {branch_name}").into()
5702        };
5703        self.send_job(Some(status_msg), move |repo, _cx| async move {
5704            match repo {
5705                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5706                    backend.create_branch(branch_name, base_branch).await
5707                }
5708                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5709                    client
5710                        .request(proto::GitCreateBranch {
5711                            project_id: project_id.0,
5712                            repository_id: id.to_proto(),
5713                            branch_name,
5714                        })
5715                        .await?;
5716
5717                    Ok(())
5718                }
5719            }
5720        })
5721    }
5722
5723    pub fn change_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
5724        let id = self.id;
5725        self.send_job(
5726            Some(format!("git switch {branch_name}").into()),
5727            move |repo, _cx| async move {
5728                match repo {
5729                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5730                        backend.change_branch(branch_name).await
5731                    }
5732                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5733                        client
5734                            .request(proto::GitChangeBranch {
5735                                project_id: project_id.0,
5736                                repository_id: id.to_proto(),
5737                                branch_name,
5738                            })
5739                            .await?;
5740
5741                        Ok(())
5742                    }
5743                }
5744            },
5745        )
5746    }
5747
5748    pub fn delete_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
5749        let id = self.id;
5750        self.send_job(
5751            Some(format!("git branch -d {branch_name}").into()),
5752            move |repo, _cx| async move {
5753                match repo {
5754                    RepositoryState::Local(state) => state.backend.delete_branch(branch_name).await,
5755                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5756                        client
5757                            .request(proto::GitDeleteBranch {
5758                                project_id: project_id.0,
5759                                repository_id: id.to_proto(),
5760                                branch_name,
5761                            })
5762                            .await?;
5763
5764                        Ok(())
5765                    }
5766                }
5767            },
5768        )
5769    }
5770
5771    pub fn rename_branch(
5772        &mut self,
5773        branch: String,
5774        new_name: String,
5775    ) -> oneshot::Receiver<Result<()>> {
5776        let id = self.id;
5777        self.send_job(
5778            Some(format!("git branch -m {branch} {new_name}").into()),
5779            move |repo, _cx| async move {
5780                match repo {
5781                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5782                        backend.rename_branch(branch, new_name).await
5783                    }
5784                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5785                        client
5786                            .request(proto::GitRenameBranch {
5787                                project_id: project_id.0,
5788                                repository_id: id.to_proto(),
5789                                branch,
5790                                new_name,
5791                            })
5792                            .await?;
5793
5794                        Ok(())
5795                    }
5796                }
5797            },
5798        )
5799    }
5800
5801    pub fn check_for_pushed_commits(&mut self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
5802        let id = self.id;
5803        self.send_job(None, move |repo, _cx| async move {
5804            match repo {
5805                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5806                    backend.check_for_pushed_commit().await
5807                }
5808                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5809                    let response = client
5810                        .request(proto::CheckForPushedCommits {
5811                            project_id: project_id.0,
5812                            repository_id: id.to_proto(),
5813                        })
5814                        .await?;
5815
5816                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
5817
5818                    Ok(branches)
5819                }
5820            }
5821        })
5822    }
5823
5824    pub fn checkpoint(&mut self) -> oneshot::Receiver<Result<GitRepositoryCheckpoint>> {
5825        self.send_job(None, |repo, _cx| async move {
5826            match repo {
5827                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5828                    backend.checkpoint().await
5829                }
5830                RepositoryState::Remote(..) => anyhow::bail!("not implemented yet"),
5831            }
5832        })
5833    }
5834
5835    pub fn restore_checkpoint(
5836        &mut self,
5837        checkpoint: GitRepositoryCheckpoint,
5838    ) -> oneshot::Receiver<Result<()>> {
5839        self.send_job(None, move |repo, _cx| async move {
5840            match repo {
5841                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5842                    backend.restore_checkpoint(checkpoint).await
5843                }
5844                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
5845            }
5846        })
5847    }
5848
5849    pub(crate) fn apply_remote_update(
5850        &mut self,
5851        update: proto::UpdateRepository,
5852        cx: &mut Context<Self>,
5853    ) -> Result<()> {
5854        let conflicted_paths = TreeSet::from_ordered_entries(
5855            update
5856                .current_merge_conflicts
5857                .into_iter()
5858                .filter_map(|path| RepoPath::from_proto(&path).log_err()),
5859        );
5860        let new_branch = update.branch_summary.as_ref().map(proto_to_branch);
5861        let new_head_commit = update
5862            .head_commit_details
5863            .as_ref()
5864            .map(proto_to_commit_details);
5865        if self.snapshot.branch != new_branch || self.snapshot.head_commit != new_head_commit {
5866            cx.emit(RepositoryEvent::BranchChanged)
5867        }
5868        self.snapshot.branch = new_branch;
5869        self.snapshot.head_commit = new_head_commit;
5870
5871        self.snapshot.merge.conflicted_paths = conflicted_paths;
5872        self.snapshot.merge.message = update.merge_message.map(SharedString::from);
5873        let new_stash_entries = GitStash {
5874            entries: update
5875                .stash_entries
5876                .iter()
5877                .filter_map(|entry| proto_to_stash(entry).ok())
5878                .collect(),
5879        };
5880        if self.snapshot.stash_entries != new_stash_entries {
5881            cx.emit(RepositoryEvent::StashEntriesChanged)
5882        }
5883        self.snapshot.stash_entries = new_stash_entries;
5884        self.snapshot.remote_upstream_url = update.remote_upstream_url;
5885        self.snapshot.remote_origin_url = update.remote_origin_url;
5886
5887        let edits = update
5888            .removed_statuses
5889            .into_iter()
5890            .filter_map(|path| {
5891                Some(sum_tree::Edit::Remove(PathKey(
5892                    RelPath::from_proto(&path).log_err()?,
5893                )))
5894            })
5895            .chain(
5896                update
5897                    .updated_statuses
5898                    .into_iter()
5899                    .filter_map(|updated_status| {
5900                        Some(sum_tree::Edit::Insert(updated_status.try_into().log_err()?))
5901                    }),
5902            )
5903            .collect::<Vec<_>>();
5904        if !edits.is_empty() {
5905            cx.emit(RepositoryEvent::StatusesChanged);
5906        }
5907        self.snapshot.statuses_by_path.edit(edits, ());
5908        if update.is_last_update {
5909            self.snapshot.scan_id = update.scan_id;
5910        }
5911        self.clear_pending_ops(cx);
5912        Ok(())
5913    }
5914
5915    pub fn compare_checkpoints(
5916        &mut self,
5917        left: GitRepositoryCheckpoint,
5918        right: GitRepositoryCheckpoint,
5919    ) -> oneshot::Receiver<Result<bool>> {
5920        self.send_job(None, move |repo, _cx| async move {
5921            match repo {
5922                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5923                    backend.compare_checkpoints(left, right).await
5924                }
5925                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
5926            }
5927        })
5928    }
5929
5930    pub fn diff_checkpoints(
5931        &mut self,
5932        base_checkpoint: GitRepositoryCheckpoint,
5933        target_checkpoint: GitRepositoryCheckpoint,
5934    ) -> oneshot::Receiver<Result<String>> {
5935        self.send_job(None, move |repo, _cx| async move {
5936            match repo {
5937                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5938                    backend
5939                        .diff_checkpoints(base_checkpoint, target_checkpoint)
5940                        .await
5941                }
5942                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
5943            }
5944        })
5945    }
5946
5947    fn clear_pending_ops(&mut self, cx: &mut Context<Self>) {
5948        let updated = SumTree::from_iter(
5949            self.pending_ops.iter().filter_map(|ops| {
5950                let inner_ops: Vec<PendingOp> =
5951                    ops.ops.iter().filter(|op| op.running()).cloned().collect();
5952                if inner_ops.is_empty() {
5953                    None
5954                } else {
5955                    Some(PendingOps {
5956                        repo_path: ops.repo_path.clone(),
5957                        ops: inner_ops,
5958                    })
5959                }
5960            }),
5961            (),
5962        );
5963
5964        if updated != self.pending_ops {
5965            cx.emit(RepositoryEvent::PendingOpsChanged {
5966                pending_ops: self.pending_ops.clone(),
5967            })
5968        }
5969
5970        self.pending_ops = updated;
5971    }
5972
5973    fn schedule_scan(
5974        &mut self,
5975        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
5976        cx: &mut Context<Self>,
5977    ) {
5978        let this = cx.weak_entity();
5979        let _ = self.send_keyed_job(
5980            Some(GitJobKey::ReloadGitState),
5981            None,
5982            |state, mut cx| async move {
5983                log::debug!("run scheduled git status scan");
5984
5985                let Some(this) = this.upgrade() else {
5986                    return Ok(());
5987                };
5988                let RepositoryState::Local(LocalRepositoryState { backend, .. }) = state else {
5989                    bail!("not a local repository")
5990                };
5991                let (snapshot, events) = this
5992                    .update(&mut cx, |this, _| {
5993                        this.paths_needing_status_update.clear();
5994                        compute_snapshot(
5995                            this.id,
5996                            this.work_directory_abs_path.clone(),
5997                            this.snapshot.clone(),
5998                            backend.clone(),
5999                        )
6000                    })
6001                    .await?;
6002                this.update(&mut cx, |this, cx| {
6003                    this.snapshot = snapshot.clone();
6004                    this.clear_pending_ops(cx);
6005                    for event in events {
6006                        cx.emit(event);
6007                    }
6008                });
6009                if let Some(updates_tx) = updates_tx {
6010                    updates_tx
6011                        .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
6012                        .ok();
6013                }
6014                Ok(())
6015            },
6016        );
6017    }
6018
6019    fn spawn_local_git_worker(
6020        state: Shared<Task<Result<LocalRepositoryState, String>>>,
6021        cx: &mut Context<Self>,
6022    ) -> mpsc::UnboundedSender<GitJob> {
6023        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
6024
6025        cx.spawn(async move |_, cx| {
6026            let state = state.await.map_err(|err| anyhow::anyhow!(err))?;
6027            if let Some(git_hosting_provider_registry) =
6028                cx.update(|cx| GitHostingProviderRegistry::try_global(cx))
6029            {
6030                git_hosting_providers::register_additional_providers(
6031                    git_hosting_provider_registry,
6032                    state.backend.clone(),
6033                )
6034                .await;
6035            }
6036            let state = RepositoryState::Local(state);
6037            let mut jobs = VecDeque::new();
6038            loop {
6039                while let Ok(Some(next_job)) = job_rx.try_next() {
6040                    jobs.push_back(next_job);
6041                }
6042
6043                if let Some(job) = jobs.pop_front() {
6044                    if let Some(current_key) = &job.key
6045                        && jobs
6046                            .iter()
6047                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
6048                    {
6049                        continue;
6050                    }
6051                    (job.job)(state.clone(), cx).await;
6052                } else if let Some(job) = job_rx.next().await {
6053                    jobs.push_back(job);
6054                } else {
6055                    break;
6056                }
6057            }
6058            anyhow::Ok(())
6059        })
6060        .detach_and_log_err(cx);
6061
6062        job_tx
6063    }
6064
6065    fn spawn_remote_git_worker(
6066        state: RemoteRepositoryState,
6067        cx: &mut Context<Self>,
6068    ) -> mpsc::UnboundedSender<GitJob> {
6069        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
6070
6071        cx.spawn(async move |_, cx| {
6072            let state = RepositoryState::Remote(state);
6073            let mut jobs = VecDeque::new();
6074            loop {
6075                while let Ok(Some(next_job)) = job_rx.try_next() {
6076                    jobs.push_back(next_job);
6077                }
6078
6079                if let Some(job) = jobs.pop_front() {
6080                    if let Some(current_key) = &job.key
6081                        && jobs
6082                            .iter()
6083                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
6084                    {
6085                        continue;
6086                    }
6087                    (job.job)(state.clone(), cx).await;
6088                } else if let Some(job) = job_rx.next().await {
6089                    jobs.push_back(job);
6090                } else {
6091                    break;
6092                }
6093            }
6094            anyhow::Ok(())
6095        })
6096        .detach_and_log_err(cx);
6097
6098        job_tx
6099    }
6100
6101    fn load_staged_text(
6102        &mut self,
6103        buffer_id: BufferId,
6104        repo_path: RepoPath,
6105        cx: &App,
6106    ) -> Task<Result<Option<String>>> {
6107        let rx = self.send_job(None, move |state, _| async move {
6108            match state {
6109                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6110                    anyhow::Ok(backend.load_index_text(repo_path).await)
6111                }
6112                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
6113                    let response = client
6114                        .request(proto::OpenUnstagedDiff {
6115                            project_id: project_id.to_proto(),
6116                            buffer_id: buffer_id.to_proto(),
6117                        })
6118                        .await?;
6119                    Ok(response.staged_text)
6120                }
6121            }
6122        });
6123        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
6124    }
6125
6126    fn load_committed_text(
6127        &mut self,
6128        buffer_id: BufferId,
6129        repo_path: RepoPath,
6130        cx: &App,
6131    ) -> Task<Result<DiffBasesChange>> {
6132        let rx = self.send_job(None, move |state, _| async move {
6133            match state {
6134                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6135                    let committed_text = backend.load_committed_text(repo_path.clone()).await;
6136                    let staged_text = backend.load_index_text(repo_path).await;
6137                    let diff_bases_change = if committed_text == staged_text {
6138                        DiffBasesChange::SetBoth(committed_text)
6139                    } else {
6140                        DiffBasesChange::SetEach {
6141                            index: staged_text,
6142                            head: committed_text,
6143                        }
6144                    };
6145                    anyhow::Ok(diff_bases_change)
6146                }
6147                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
6148                    use proto::open_uncommitted_diff_response::Mode;
6149
6150                    let response = client
6151                        .request(proto::OpenUncommittedDiff {
6152                            project_id: project_id.to_proto(),
6153                            buffer_id: buffer_id.to_proto(),
6154                        })
6155                        .await?;
6156                    let mode = Mode::from_i32(response.mode).context("Invalid mode")?;
6157                    let bases = match mode {
6158                        Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
6159                        Mode::IndexAndHead => DiffBasesChange::SetEach {
6160                            head: response.committed_text,
6161                            index: response.staged_text,
6162                        },
6163                    };
6164                    Ok(bases)
6165                }
6166            }
6167        });
6168
6169        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
6170    }
6171
6172    fn load_blob_content(&mut self, oid: Oid, cx: &App) -> Task<Result<String>> {
6173        let repository_id = self.snapshot.id;
6174        let rx = self.send_job(None, move |state, _| async move {
6175            match state {
6176                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6177                    backend.load_blob_content(oid).await
6178                }
6179                RepositoryState::Remote(RemoteRepositoryState { client, project_id }) => {
6180                    let response = client
6181                        .request(proto::GetBlobContent {
6182                            project_id: project_id.to_proto(),
6183                            repository_id: repository_id.0,
6184                            oid: oid.to_string(),
6185                        })
6186                        .await?;
6187                    Ok(response.content)
6188                }
6189            }
6190        });
6191        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
6192    }
6193
6194    fn paths_changed(
6195        &mut self,
6196        paths: Vec<RepoPath>,
6197        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
6198        cx: &mut Context<Self>,
6199    ) {
6200        if !paths.is_empty() {
6201            self.paths_needing_status_update.push(paths);
6202        }
6203
6204        let this = cx.weak_entity();
6205        let _ = self.send_keyed_job(
6206            Some(GitJobKey::RefreshStatuses),
6207            None,
6208            |state, mut cx| async move {
6209                let (prev_snapshot, changed_paths) = this.update(&mut cx, |this, _| {
6210                    (
6211                        this.snapshot.clone(),
6212                        mem::take(&mut this.paths_needing_status_update),
6213                    )
6214                })?;
6215                let RepositoryState::Local(LocalRepositoryState { backend, .. }) = state else {
6216                    bail!("not a local repository")
6217                };
6218
6219                if changed_paths.is_empty() {
6220                    return Ok(());
6221                }
6222
6223                let stash_entries = backend.stash_entries().await?;
6224                let changed_path_statuses = cx
6225                    .background_spawn(async move {
6226                        let mut changed_paths =
6227                            changed_paths.into_iter().flatten().collect::<BTreeSet<_>>();
6228                        let statuses = backend
6229                            .status(&changed_paths.iter().cloned().collect::<Vec<_>>())
6230                            .await?;
6231                        let mut changed_path_statuses = Vec::new();
6232                        let prev_statuses = prev_snapshot.statuses_by_path.clone();
6233                        let mut cursor = prev_statuses.cursor::<PathProgress>(());
6234
6235                        for (repo_path, status) in &*statuses.entries {
6236                            changed_paths.remove(repo_path);
6237                            if cursor.seek_forward(&PathTarget::Path(repo_path), Bias::Left)
6238                                && cursor.item().is_some_and(|entry| entry.status == *status)
6239                            {
6240                                continue;
6241                            }
6242
6243                            changed_path_statuses.push(Edit::Insert(StatusEntry {
6244                                repo_path: repo_path.clone(),
6245                                status: *status,
6246                            }));
6247                        }
6248                        let mut cursor = prev_statuses.cursor::<PathProgress>(());
6249                        for path in changed_paths.into_iter() {
6250                            if cursor.seek_forward(&PathTarget::Path(&path), Bias::Left) {
6251                                changed_path_statuses
6252                                    .push(Edit::Remove(PathKey(path.as_ref().clone())));
6253                            }
6254                        }
6255                        anyhow::Ok(changed_path_statuses)
6256                    })
6257                    .await?;
6258
6259                this.update(&mut cx, |this, cx| {
6260                    if this.snapshot.stash_entries != stash_entries {
6261                        cx.emit(RepositoryEvent::StashEntriesChanged);
6262                        this.snapshot.stash_entries = stash_entries;
6263                    }
6264
6265                    if !changed_path_statuses.is_empty() {
6266                        cx.emit(RepositoryEvent::StatusesChanged);
6267                        this.snapshot
6268                            .statuses_by_path
6269                            .edit(changed_path_statuses, ());
6270                        this.snapshot.scan_id += 1;
6271                    }
6272
6273                    if let Some(updates_tx) = updates_tx {
6274                        updates_tx
6275                            .unbounded_send(DownstreamUpdate::UpdateRepository(
6276                                this.snapshot.clone(),
6277                            ))
6278                            .ok();
6279                    }
6280                })
6281            },
6282        );
6283    }
6284
6285    /// currently running git command and when it started
6286    pub fn current_job(&self) -> Option<JobInfo> {
6287        self.active_jobs.values().next().cloned()
6288    }
6289
6290    pub fn barrier(&mut self) -> oneshot::Receiver<()> {
6291        self.send_job(None, |_, _| async {})
6292    }
6293
6294    fn spawn_job_with_tracking<AsyncFn>(
6295        &mut self,
6296        paths: Vec<RepoPath>,
6297        git_status: pending_op::GitStatus,
6298        cx: &mut Context<Self>,
6299        f: AsyncFn,
6300    ) -> Task<Result<()>>
6301    where
6302        AsyncFn: AsyncFnOnce(WeakEntity<Repository>, &mut AsyncApp) -> Result<()> + 'static,
6303    {
6304        let ids = self.new_pending_ops_for_paths(paths, git_status);
6305
6306        cx.spawn(async move |this, cx| {
6307            let (job_status, result) = match f(this.clone(), cx).await {
6308                Ok(()) => (pending_op::JobStatus::Finished, Ok(())),
6309                Err(err) if err.is::<Canceled>() => (pending_op::JobStatus::Skipped, Ok(())),
6310                Err(err) => (pending_op::JobStatus::Error, Err(err)),
6311            };
6312
6313            this.update(cx, |this, _| {
6314                let mut edits = Vec::with_capacity(ids.len());
6315                for (id, entry) in ids {
6316                    if let Some(mut ops) = this
6317                        .pending_ops
6318                        .get(&PathKey(entry.as_ref().clone()), ())
6319                        .cloned()
6320                    {
6321                        if let Some(op) = ops.op_by_id_mut(id) {
6322                            op.job_status = job_status;
6323                        }
6324                        edits.push(sum_tree::Edit::Insert(ops));
6325                    }
6326                }
6327                this.pending_ops.edit(edits, ());
6328            })?;
6329
6330            result
6331        })
6332    }
6333
6334    fn new_pending_ops_for_paths(
6335        &mut self,
6336        paths: Vec<RepoPath>,
6337        git_status: pending_op::GitStatus,
6338    ) -> Vec<(PendingOpId, RepoPath)> {
6339        let mut edits = Vec::with_capacity(paths.len());
6340        let mut ids = Vec::with_capacity(paths.len());
6341        for path in paths {
6342            let mut ops = self
6343                .pending_ops
6344                .get(&PathKey(path.as_ref().clone()), ())
6345                .cloned()
6346                .unwrap_or_else(|| PendingOps::new(&path));
6347            let id = ops.max_id() + 1;
6348            ops.ops.push(PendingOp {
6349                id,
6350                git_status,
6351                job_status: pending_op::JobStatus::Running,
6352            });
6353            edits.push(sum_tree::Edit::Insert(ops));
6354            ids.push((id, path));
6355        }
6356        self.pending_ops.edit(edits, ());
6357        ids
6358    }
6359    pub fn default_remote_url(&self) -> Option<String> {
6360        self.remote_upstream_url
6361            .clone()
6362            .or(self.remote_origin_url.clone())
6363    }
6364}
6365
6366fn get_permalink_in_rust_registry_src(
6367    provider_registry: Arc<GitHostingProviderRegistry>,
6368    path: PathBuf,
6369    selection: Range<u32>,
6370) -> Result<url::Url> {
6371    #[derive(Deserialize)]
6372    struct CargoVcsGit {
6373        sha1: String,
6374    }
6375
6376    #[derive(Deserialize)]
6377    struct CargoVcsInfo {
6378        git: CargoVcsGit,
6379        path_in_vcs: String,
6380    }
6381
6382    #[derive(Deserialize)]
6383    struct CargoPackage {
6384        repository: String,
6385    }
6386
6387    #[derive(Deserialize)]
6388    struct CargoToml {
6389        package: CargoPackage,
6390    }
6391
6392    let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
6393        let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
6394        Some((dir, json))
6395    }) else {
6396        bail!("No .cargo_vcs_info.json found in parent directories")
6397    };
6398    let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
6399    let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
6400    let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
6401    let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
6402        .context("parsing package.repository field of manifest")?;
6403    let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
6404    let permalink = provider.build_permalink(
6405        remote,
6406        BuildPermalinkParams::new(
6407            &cargo_vcs_info.git.sha1,
6408            &RepoPath::from_rel_path(
6409                &RelPath::new(&path, PathStyle::local()).context("invalid path")?,
6410            ),
6411            Some(selection),
6412        ),
6413    );
6414    Ok(permalink)
6415}
6416
6417fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::BlameBufferResponse {
6418    let Some(blame) = blame else {
6419        return proto::BlameBufferResponse {
6420            blame_response: None,
6421        };
6422    };
6423
6424    let entries = blame
6425        .entries
6426        .into_iter()
6427        .map(|entry| proto::BlameEntry {
6428            sha: entry.sha.as_bytes().into(),
6429            start_line: entry.range.start,
6430            end_line: entry.range.end,
6431            original_line_number: entry.original_line_number,
6432            author: entry.author,
6433            author_mail: entry.author_mail,
6434            author_time: entry.author_time,
6435            author_tz: entry.author_tz,
6436            committer: entry.committer_name,
6437            committer_mail: entry.committer_email,
6438            committer_time: entry.committer_time,
6439            committer_tz: entry.committer_tz,
6440            summary: entry.summary,
6441            previous: entry.previous,
6442            filename: entry.filename,
6443        })
6444        .collect::<Vec<_>>();
6445
6446    let messages = blame
6447        .messages
6448        .into_iter()
6449        .map(|(oid, message)| proto::CommitMessage {
6450            oid: oid.as_bytes().into(),
6451            message,
6452        })
6453        .collect::<Vec<_>>();
6454
6455    proto::BlameBufferResponse {
6456        blame_response: Some(proto::blame_buffer_response::BlameResponse { entries, messages }),
6457    }
6458}
6459
6460fn deserialize_blame_buffer_response(
6461    response: proto::BlameBufferResponse,
6462) -> Option<git::blame::Blame> {
6463    let response = response.blame_response?;
6464    let entries = response
6465        .entries
6466        .into_iter()
6467        .filter_map(|entry| {
6468            Some(git::blame::BlameEntry {
6469                sha: git::Oid::from_bytes(&entry.sha).ok()?,
6470                range: entry.start_line..entry.end_line,
6471                original_line_number: entry.original_line_number,
6472                committer_name: entry.committer,
6473                committer_time: entry.committer_time,
6474                committer_tz: entry.committer_tz,
6475                committer_email: entry.committer_mail,
6476                author: entry.author,
6477                author_mail: entry.author_mail,
6478                author_time: entry.author_time,
6479                author_tz: entry.author_tz,
6480                summary: entry.summary,
6481                previous: entry.previous,
6482                filename: entry.filename,
6483            })
6484        })
6485        .collect::<Vec<_>>();
6486
6487    let messages = response
6488        .messages
6489        .into_iter()
6490        .filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
6491        .collect::<HashMap<_, _>>();
6492
6493    Some(Blame { entries, messages })
6494}
6495
6496fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch {
6497    proto::Branch {
6498        is_head: branch.is_head,
6499        ref_name: branch.ref_name.to_string(),
6500        unix_timestamp: branch
6501            .most_recent_commit
6502            .as_ref()
6503            .map(|commit| commit.commit_timestamp as u64),
6504        upstream: branch.upstream.as_ref().map(|upstream| proto::GitUpstream {
6505            ref_name: upstream.ref_name.to_string(),
6506            tracking: upstream
6507                .tracking
6508                .status()
6509                .map(|upstream| proto::UpstreamTracking {
6510                    ahead: upstream.ahead as u64,
6511                    behind: upstream.behind as u64,
6512                }),
6513        }),
6514        most_recent_commit: branch
6515            .most_recent_commit
6516            .as_ref()
6517            .map(|commit| proto::CommitSummary {
6518                sha: commit.sha.to_string(),
6519                subject: commit.subject.to_string(),
6520                commit_timestamp: commit.commit_timestamp,
6521                author_name: commit.author_name.to_string(),
6522            }),
6523    }
6524}
6525
6526fn worktree_to_proto(worktree: &git::repository::Worktree) -> proto::Worktree {
6527    proto::Worktree {
6528        path: worktree.path.to_string_lossy().to_string(),
6529        ref_name: worktree.ref_name.to_string(),
6530        sha: worktree.sha.to_string(),
6531    }
6532}
6533
6534fn proto_to_worktree(proto: &proto::Worktree) -> git::repository::Worktree {
6535    git::repository::Worktree {
6536        path: PathBuf::from(proto.path.clone()),
6537        ref_name: proto.ref_name.clone().into(),
6538        sha: proto.sha.clone().into(),
6539    }
6540}
6541
6542fn proto_to_branch(proto: &proto::Branch) -> git::repository::Branch {
6543    git::repository::Branch {
6544        is_head: proto.is_head,
6545        ref_name: proto.ref_name.clone().into(),
6546        upstream: proto
6547            .upstream
6548            .as_ref()
6549            .map(|upstream| git::repository::Upstream {
6550                ref_name: upstream.ref_name.to_string().into(),
6551                tracking: upstream
6552                    .tracking
6553                    .as_ref()
6554                    .map(|tracking| {
6555                        git::repository::UpstreamTracking::Tracked(UpstreamTrackingStatus {
6556                            ahead: tracking.ahead as u32,
6557                            behind: tracking.behind as u32,
6558                        })
6559                    })
6560                    .unwrap_or(git::repository::UpstreamTracking::Gone),
6561            }),
6562        most_recent_commit: proto.most_recent_commit.as_ref().map(|commit| {
6563            git::repository::CommitSummary {
6564                sha: commit.sha.to_string().into(),
6565                subject: commit.subject.to_string().into(),
6566                commit_timestamp: commit.commit_timestamp,
6567                author_name: commit.author_name.to_string().into(),
6568                has_parent: true,
6569            }
6570        }),
6571    }
6572}
6573
6574fn commit_details_to_proto(commit: &CommitDetails) -> proto::GitCommitDetails {
6575    proto::GitCommitDetails {
6576        sha: commit.sha.to_string(),
6577        message: commit.message.to_string(),
6578        commit_timestamp: commit.commit_timestamp,
6579        author_email: commit.author_email.to_string(),
6580        author_name: commit.author_name.to_string(),
6581    }
6582}
6583
6584fn proto_to_commit_details(proto: &proto::GitCommitDetails) -> CommitDetails {
6585    CommitDetails {
6586        sha: proto.sha.clone().into(),
6587        message: proto.message.clone().into(),
6588        commit_timestamp: proto.commit_timestamp,
6589        author_email: proto.author_email.clone().into(),
6590        author_name: proto.author_name.clone().into(),
6591    }
6592}
6593
6594async fn compute_snapshot(
6595    id: RepositoryId,
6596    work_directory_abs_path: Arc<Path>,
6597    prev_snapshot: RepositorySnapshot,
6598    backend: Arc<dyn GitRepository>,
6599) -> Result<(RepositorySnapshot, Vec<RepositoryEvent>)> {
6600    let mut events = Vec::new();
6601    let branches = backend.branches().await?;
6602    let branch = branches.into_iter().find(|branch| branch.is_head);
6603    let statuses = backend
6604        .status(&[RepoPath::from_rel_path(
6605            &RelPath::new(".".as_ref(), PathStyle::local()).unwrap(),
6606        )])
6607        .await?;
6608    let stash_entries = backend.stash_entries().await?;
6609    let statuses_by_path = SumTree::from_iter(
6610        statuses
6611            .entries
6612            .iter()
6613            .map(|(repo_path, status)| StatusEntry {
6614                repo_path: repo_path.clone(),
6615                status: *status,
6616            }),
6617        (),
6618    );
6619    let (merge_details, merge_heads_changed) =
6620        MergeDetails::load(&backend, &statuses_by_path, &prev_snapshot).await?;
6621    log::debug!("new merge details (changed={merge_heads_changed:?}): {merge_details:?}");
6622
6623    if merge_heads_changed {
6624        events.push(RepositoryEvent::MergeHeadsChanged);
6625    }
6626
6627    if statuses_by_path != prev_snapshot.statuses_by_path {
6628        events.push(RepositoryEvent::StatusesChanged)
6629    }
6630
6631    // Useful when branch is None in detached head state
6632    let head_commit = match backend.head_sha().await {
6633        Some(head_sha) => backend.show(head_sha).await.log_err(),
6634        None => None,
6635    };
6636
6637    if branch != prev_snapshot.branch || head_commit != prev_snapshot.head_commit {
6638        events.push(RepositoryEvent::BranchChanged);
6639    }
6640
6641    let remote_origin_url = backend.remote_url("origin").await;
6642    let remote_upstream_url = backend.remote_url("upstream").await;
6643
6644    let snapshot = RepositorySnapshot {
6645        id,
6646        statuses_by_path,
6647        work_directory_abs_path,
6648        path_style: prev_snapshot.path_style,
6649        scan_id: prev_snapshot.scan_id + 1,
6650        branch,
6651        head_commit,
6652        merge: merge_details,
6653        remote_origin_url,
6654        remote_upstream_url,
6655        stash_entries,
6656    };
6657
6658    Ok((snapshot, events))
6659}
6660
6661fn status_from_proto(
6662    simple_status: i32,
6663    status: Option<proto::GitFileStatus>,
6664) -> anyhow::Result<FileStatus> {
6665    use proto::git_file_status::Variant;
6666
6667    let Some(variant) = status.and_then(|status| status.variant) else {
6668        let code = proto::GitStatus::from_i32(simple_status)
6669            .with_context(|| format!("Invalid git status code: {simple_status}"))?;
6670        let result = match code {
6671            proto::GitStatus::Added => TrackedStatus {
6672                worktree_status: StatusCode::Added,
6673                index_status: StatusCode::Unmodified,
6674            }
6675            .into(),
6676            proto::GitStatus::Modified => TrackedStatus {
6677                worktree_status: StatusCode::Modified,
6678                index_status: StatusCode::Unmodified,
6679            }
6680            .into(),
6681            proto::GitStatus::Conflict => UnmergedStatus {
6682                first_head: UnmergedStatusCode::Updated,
6683                second_head: UnmergedStatusCode::Updated,
6684            }
6685            .into(),
6686            proto::GitStatus::Deleted => TrackedStatus {
6687                worktree_status: StatusCode::Deleted,
6688                index_status: StatusCode::Unmodified,
6689            }
6690            .into(),
6691            _ => anyhow::bail!("Invalid code for simple status: {simple_status}"),
6692        };
6693        return Ok(result);
6694    };
6695
6696    let result = match variant {
6697        Variant::Untracked(_) => FileStatus::Untracked,
6698        Variant::Ignored(_) => FileStatus::Ignored,
6699        Variant::Unmerged(unmerged) => {
6700            let [first_head, second_head] =
6701                [unmerged.first_head, unmerged.second_head].map(|head| {
6702                    let code = proto::GitStatus::from_i32(head)
6703                        .with_context(|| format!("Invalid git status code: {head}"))?;
6704                    let result = match code {
6705                        proto::GitStatus::Added => UnmergedStatusCode::Added,
6706                        proto::GitStatus::Updated => UnmergedStatusCode::Updated,
6707                        proto::GitStatus::Deleted => UnmergedStatusCode::Deleted,
6708                        _ => anyhow::bail!("Invalid code for unmerged status: {code:?}"),
6709                    };
6710                    Ok(result)
6711                });
6712            let [first_head, second_head] = [first_head?, second_head?];
6713            UnmergedStatus {
6714                first_head,
6715                second_head,
6716            }
6717            .into()
6718        }
6719        Variant::Tracked(tracked) => {
6720            let [index_status, worktree_status] = [tracked.index_status, tracked.worktree_status]
6721                .map(|status| {
6722                    let code = proto::GitStatus::from_i32(status)
6723                        .with_context(|| format!("Invalid git status code: {status}"))?;
6724                    let result = match code {
6725                        proto::GitStatus::Modified => StatusCode::Modified,
6726                        proto::GitStatus::TypeChanged => StatusCode::TypeChanged,
6727                        proto::GitStatus::Added => StatusCode::Added,
6728                        proto::GitStatus::Deleted => StatusCode::Deleted,
6729                        proto::GitStatus::Renamed => StatusCode::Renamed,
6730                        proto::GitStatus::Copied => StatusCode::Copied,
6731                        proto::GitStatus::Unmodified => StatusCode::Unmodified,
6732                        _ => anyhow::bail!("Invalid code for tracked status: {code:?}"),
6733                    };
6734                    Ok(result)
6735                });
6736            let [index_status, worktree_status] = [index_status?, worktree_status?];
6737            TrackedStatus {
6738                index_status,
6739                worktree_status,
6740            }
6741            .into()
6742        }
6743    };
6744    Ok(result)
6745}
6746
6747fn status_to_proto(status: FileStatus) -> proto::GitFileStatus {
6748    use proto::git_file_status::{Tracked, Unmerged, Variant};
6749
6750    let variant = match status {
6751        FileStatus::Untracked => Variant::Untracked(Default::default()),
6752        FileStatus::Ignored => Variant::Ignored(Default::default()),
6753        FileStatus::Unmerged(UnmergedStatus {
6754            first_head,
6755            second_head,
6756        }) => Variant::Unmerged(Unmerged {
6757            first_head: unmerged_status_to_proto(first_head),
6758            second_head: unmerged_status_to_proto(second_head),
6759        }),
6760        FileStatus::Tracked(TrackedStatus {
6761            index_status,
6762            worktree_status,
6763        }) => Variant::Tracked(Tracked {
6764            index_status: tracked_status_to_proto(index_status),
6765            worktree_status: tracked_status_to_proto(worktree_status),
6766        }),
6767    };
6768    proto::GitFileStatus {
6769        variant: Some(variant),
6770    }
6771}
6772
6773fn unmerged_status_to_proto(code: UnmergedStatusCode) -> i32 {
6774    match code {
6775        UnmergedStatusCode::Added => proto::GitStatus::Added as _,
6776        UnmergedStatusCode::Deleted => proto::GitStatus::Deleted as _,
6777        UnmergedStatusCode::Updated => proto::GitStatus::Updated as _,
6778    }
6779}
6780
6781fn tracked_status_to_proto(code: StatusCode) -> i32 {
6782    match code {
6783        StatusCode::Added => proto::GitStatus::Added as _,
6784        StatusCode::Deleted => proto::GitStatus::Deleted as _,
6785        StatusCode::Modified => proto::GitStatus::Modified as _,
6786        StatusCode::Renamed => proto::GitStatus::Renamed as _,
6787        StatusCode::TypeChanged => proto::GitStatus::TypeChanged as _,
6788        StatusCode::Copied => proto::GitStatus::Copied as _,
6789        StatusCode::Unmodified => proto::GitStatus::Unmodified as _,
6790    }
6791}