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")
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    pub fn recalculate_buffer_diffs(
1622        &mut self,
1623        buffers: Vec<Entity<Buffer>>,
1624        cx: &mut Context<Self>,
1625    ) -> impl Future<Output = ()> + use<> {
1626        let mut futures = Vec::new();
1627        for buffer in buffers {
1628            if let Some(diff_state) = self.diffs.get_mut(&buffer.read(cx).remote_id()) {
1629                let buffer = buffer.read(cx).text_snapshot();
1630                diff_state.update(cx, |diff_state, cx| {
1631                    diff_state.recalculate_diffs(buffer.clone(), cx);
1632                    futures.extend(diff_state.wait_for_recalculation().map(FutureExt::boxed));
1633                });
1634                futures.push(diff_state.update(cx, |diff_state, cx| {
1635                    diff_state
1636                        .reparse_conflict_markers(buffer, cx)
1637                        .map(|_| {})
1638                        .boxed()
1639                }));
1640            }
1641        }
1642        async move {
1643            futures::future::join_all(futures).await;
1644        }
1645    }
1646
1647    fn on_buffer_diff_event(
1648        &mut self,
1649        diff: Entity<buffer_diff::BufferDiff>,
1650        event: &BufferDiffEvent,
1651        cx: &mut Context<Self>,
1652    ) {
1653        if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
1654            let buffer_id = diff.read(cx).buffer_id;
1655            if let Some(diff_state) = self.diffs.get(&buffer_id) {
1656                let hunk_staging_operation_count = diff_state.update(cx, |diff_state, _| {
1657                    diff_state.hunk_staging_operation_count += 1;
1658                    diff_state.hunk_staging_operation_count
1659                });
1660                if let Some((repo, path)) = self.repository_and_path_for_buffer_id(buffer_id, cx) {
1661                    let recv = repo.update(cx, |repo, cx| {
1662                        log::debug!("hunks changed for {}", path.as_unix_str());
1663                        repo.spawn_set_index_text_job(
1664                            path,
1665                            new_index_text.as_ref().map(|rope| rope.to_string()),
1666                            Some(hunk_staging_operation_count),
1667                            cx,
1668                        )
1669                    });
1670                    let diff = diff.downgrade();
1671                    cx.spawn(async move |this, cx| {
1672                        if let Ok(Err(error)) = cx.background_spawn(recv).await {
1673                            diff.update(cx, |diff, cx| {
1674                                diff.clear_pending_hunks(cx);
1675                            })
1676                            .ok();
1677                            this.update(cx, |_, cx| cx.emit(GitStoreEvent::IndexWriteError(error)))
1678                                .ok();
1679                        }
1680                    })
1681                    .detach();
1682                }
1683            }
1684        }
1685    }
1686
1687    fn local_worktree_git_repos_changed(
1688        &mut self,
1689        worktree: Entity<Worktree>,
1690        changed_repos: &UpdatedGitRepositoriesSet,
1691        cx: &mut Context<Self>,
1692    ) {
1693        log::debug!("local worktree repos changed");
1694        debug_assert!(worktree.read(cx).is_local());
1695
1696        for repository in self.repositories.values() {
1697            repository.update(cx, |repository, cx| {
1698                let repo_abs_path = &repository.work_directory_abs_path;
1699                if changed_repos.iter().any(|update| {
1700                    update.old_work_directory_abs_path.as_ref() == Some(repo_abs_path)
1701                        || update.new_work_directory_abs_path.as_ref() == Some(repo_abs_path)
1702                }) {
1703                    repository.reload_buffer_diff_bases(cx);
1704                }
1705            });
1706        }
1707    }
1708
1709    pub fn repositories(&self) -> &HashMap<RepositoryId, Entity<Repository>> {
1710        &self.repositories
1711    }
1712
1713    pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
1714        let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
1715        let status = repo.read(cx).snapshot.status_for_path(&path)?;
1716        Some(status.status)
1717    }
1718
1719    pub fn repository_and_path_for_buffer_id(
1720        &self,
1721        buffer_id: BufferId,
1722        cx: &App,
1723    ) -> Option<(Entity<Repository>, RepoPath)> {
1724        let buffer = self.buffer_store.read(cx).get(buffer_id)?;
1725        let project_path = buffer.read(cx).project_path(cx)?;
1726        self.repository_and_path_for_project_path(&project_path, cx)
1727    }
1728
1729    pub fn repository_and_path_for_project_path(
1730        &self,
1731        path: &ProjectPath,
1732        cx: &App,
1733    ) -> Option<(Entity<Repository>, RepoPath)> {
1734        let abs_path = self.worktree_store.read(cx).absolutize(path, cx)?;
1735        self.repositories
1736            .values()
1737            .filter_map(|repo| {
1738                let repo_path = repo.read(cx).abs_path_to_repo_path(&abs_path)?;
1739                Some((repo.clone(), repo_path))
1740            })
1741            .max_by_key(|(repo, _)| repo.read(cx).work_directory_abs_path.clone())
1742    }
1743
1744    pub fn git_init(
1745        &self,
1746        path: Arc<Path>,
1747        fallback_branch_name: String,
1748        cx: &App,
1749    ) -> Task<Result<()>> {
1750        match &self.state {
1751            GitStoreState::Local { fs, .. } => {
1752                let fs = fs.clone();
1753                cx.background_executor()
1754                    .spawn(async move { fs.git_init(&path, fallback_branch_name).await })
1755            }
1756            GitStoreState::Remote {
1757                upstream_client,
1758                upstream_project_id: project_id,
1759                ..
1760            } => {
1761                let client = upstream_client.clone();
1762                let project_id = *project_id;
1763                cx.background_executor().spawn(async move {
1764                    client
1765                        .request(proto::GitInit {
1766                            project_id: project_id,
1767                            abs_path: path.to_string_lossy().into_owned(),
1768                            fallback_branch_name,
1769                        })
1770                        .await?;
1771                    Ok(())
1772                })
1773            }
1774        }
1775    }
1776
1777    pub fn git_clone(
1778        &self,
1779        repo: String,
1780        path: impl Into<Arc<std::path::Path>>,
1781        cx: &App,
1782    ) -> Task<Result<()>> {
1783        let path = path.into();
1784        match &self.state {
1785            GitStoreState::Local { fs, .. } => {
1786                let fs = fs.clone();
1787                cx.background_executor()
1788                    .spawn(async move { fs.git_clone(&repo, &path).await })
1789            }
1790            GitStoreState::Remote {
1791                upstream_client,
1792                upstream_project_id,
1793                ..
1794            } => {
1795                if upstream_client.is_via_collab() {
1796                    return Task::ready(Err(anyhow!(
1797                        "Git Clone isn't supported for project guests"
1798                    )));
1799                }
1800                let request = upstream_client.request(proto::GitClone {
1801                    project_id: *upstream_project_id,
1802                    abs_path: path.to_string_lossy().into_owned(),
1803                    remote_repo: repo,
1804                });
1805
1806                cx.background_spawn(async move {
1807                    let result = request.await?;
1808
1809                    match result.success {
1810                        true => Ok(()),
1811                        false => Err(anyhow!("Git Clone failed")),
1812                    }
1813                })
1814            }
1815        }
1816    }
1817
1818    async fn handle_update_repository(
1819        this: Entity<Self>,
1820        envelope: TypedEnvelope<proto::UpdateRepository>,
1821        mut cx: AsyncApp,
1822    ) -> Result<()> {
1823        this.update(&mut cx, |this, cx| {
1824            let path_style = this.worktree_store.read(cx).path_style();
1825            let mut update = envelope.payload;
1826
1827            let id = RepositoryId::from_proto(update.id);
1828            let client = this.upstream_client().context("no upstream client")?;
1829
1830            let mut repo_subscription = None;
1831            let repo = this.repositories.entry(id).or_insert_with(|| {
1832                let git_store = cx.weak_entity();
1833                let repo = cx.new(|cx| {
1834                    Repository::remote(
1835                        id,
1836                        Path::new(&update.abs_path).into(),
1837                        path_style,
1838                        ProjectId(update.project_id),
1839                        client,
1840                        git_store,
1841                        cx,
1842                    )
1843                });
1844                repo_subscription = Some(cx.subscribe(&repo, Self::on_repository_event));
1845                cx.emit(GitStoreEvent::RepositoryAdded);
1846                repo
1847            });
1848            this._subscriptions.extend(repo_subscription);
1849
1850            repo.update(cx, {
1851                let update = update.clone();
1852                |repo, cx| repo.apply_remote_update(update, cx)
1853            })?;
1854
1855            this.active_repo_id.get_or_insert_with(|| {
1856                cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
1857                id
1858            });
1859
1860            if let Some((client, project_id)) = this.downstream_client() {
1861                update.project_id = project_id.to_proto();
1862                client.send(update).log_err();
1863            }
1864            Ok(())
1865        })
1866    }
1867
1868    async fn handle_remove_repository(
1869        this: Entity<Self>,
1870        envelope: TypedEnvelope<proto::RemoveRepository>,
1871        mut cx: AsyncApp,
1872    ) -> Result<()> {
1873        this.update(&mut cx, |this, cx| {
1874            let mut update = envelope.payload;
1875            let id = RepositoryId::from_proto(update.id);
1876            this.repositories.remove(&id);
1877            if let Some((client, project_id)) = this.downstream_client() {
1878                update.project_id = project_id.to_proto();
1879                client.send(update).log_err();
1880            }
1881            if this.active_repo_id == Some(id) {
1882                this.active_repo_id = None;
1883                cx.emit(GitStoreEvent::ActiveRepositoryChanged(None));
1884            }
1885            cx.emit(GitStoreEvent::RepositoryRemoved(id));
1886        });
1887        Ok(())
1888    }
1889
1890    async fn handle_git_init(
1891        this: Entity<Self>,
1892        envelope: TypedEnvelope<proto::GitInit>,
1893        cx: AsyncApp,
1894    ) -> Result<proto::Ack> {
1895        let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
1896        let name = envelope.payload.fallback_branch_name;
1897        cx.update(|cx| this.read(cx).git_init(path, name, cx))
1898            .await?;
1899
1900        Ok(proto::Ack {})
1901    }
1902
1903    async fn handle_git_clone(
1904        this: Entity<Self>,
1905        envelope: TypedEnvelope<proto::GitClone>,
1906        cx: AsyncApp,
1907    ) -> Result<proto::GitCloneResponse> {
1908        let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
1909        let repo_name = envelope.payload.remote_repo;
1910        let result = cx
1911            .update(|cx| this.read(cx).git_clone(repo_name, path, cx))
1912            .await;
1913
1914        Ok(proto::GitCloneResponse {
1915            success: result.is_ok(),
1916        })
1917    }
1918
1919    async fn handle_fetch(
1920        this: Entity<Self>,
1921        envelope: TypedEnvelope<proto::Fetch>,
1922        mut cx: AsyncApp,
1923    ) -> Result<proto::RemoteMessageResponse> {
1924        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1925        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1926        let fetch_options = FetchOptions::from_proto(envelope.payload.remote);
1927        let askpass_id = envelope.payload.askpass_id;
1928
1929        let askpass = make_remote_delegate(
1930            this,
1931            envelope.payload.project_id,
1932            repository_id,
1933            askpass_id,
1934            &mut cx,
1935        );
1936
1937        let remote_output = repository_handle
1938            .update(&mut cx, |repository_handle, cx| {
1939                repository_handle.fetch(fetch_options, askpass, cx)
1940            })
1941            .await??;
1942
1943        Ok(proto::RemoteMessageResponse {
1944            stdout: remote_output.stdout,
1945            stderr: remote_output.stderr,
1946        })
1947    }
1948
1949    async fn handle_push(
1950        this: Entity<Self>,
1951        envelope: TypedEnvelope<proto::Push>,
1952        mut cx: AsyncApp,
1953    ) -> Result<proto::RemoteMessageResponse> {
1954        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1955        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1956
1957        let askpass_id = envelope.payload.askpass_id;
1958        let askpass = make_remote_delegate(
1959            this,
1960            envelope.payload.project_id,
1961            repository_id,
1962            askpass_id,
1963            &mut cx,
1964        );
1965
1966        let options = envelope
1967            .payload
1968            .options
1969            .as_ref()
1970            .map(|_| match envelope.payload.options() {
1971                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
1972                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
1973            });
1974
1975        let branch_name = envelope.payload.branch_name.into();
1976        let remote_branch_name = envelope.payload.remote_branch_name.into();
1977        let remote_name = envelope.payload.remote_name.into();
1978
1979        let remote_output = repository_handle
1980            .update(&mut cx, |repository_handle, cx| {
1981                repository_handle.push(
1982                    branch_name,
1983                    remote_branch_name,
1984                    remote_name,
1985                    options,
1986                    askpass,
1987                    cx,
1988                )
1989            })
1990            .await??;
1991        Ok(proto::RemoteMessageResponse {
1992            stdout: remote_output.stdout,
1993            stderr: remote_output.stderr,
1994        })
1995    }
1996
1997    async fn handle_pull(
1998        this: Entity<Self>,
1999        envelope: TypedEnvelope<proto::Pull>,
2000        mut cx: AsyncApp,
2001    ) -> Result<proto::RemoteMessageResponse> {
2002        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2003        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2004        let askpass_id = envelope.payload.askpass_id;
2005        let askpass = make_remote_delegate(
2006            this,
2007            envelope.payload.project_id,
2008            repository_id,
2009            askpass_id,
2010            &mut cx,
2011        );
2012
2013        let branch_name = envelope.payload.branch_name.map(|name| name.into());
2014        let remote_name = envelope.payload.remote_name.into();
2015        let rebase = envelope.payload.rebase;
2016
2017        let remote_message = repository_handle
2018            .update(&mut cx, |repository_handle, cx| {
2019                repository_handle.pull(branch_name, remote_name, rebase, askpass, cx)
2020            })
2021            .await??;
2022
2023        Ok(proto::RemoteMessageResponse {
2024            stdout: remote_message.stdout,
2025            stderr: remote_message.stderr,
2026        })
2027    }
2028
2029    async fn handle_stage(
2030        this: Entity<Self>,
2031        envelope: TypedEnvelope<proto::Stage>,
2032        mut cx: AsyncApp,
2033    ) -> Result<proto::Ack> {
2034        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2035        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2036
2037        let entries = envelope
2038            .payload
2039            .paths
2040            .into_iter()
2041            .map(|path| RepoPath::new(&path))
2042            .collect::<Result<Vec<_>>>()?;
2043
2044        repository_handle
2045            .update(&mut cx, |repository_handle, cx| {
2046                repository_handle.stage_entries(entries, cx)
2047            })
2048            .await?;
2049        Ok(proto::Ack {})
2050    }
2051
2052    async fn handle_unstage(
2053        this: Entity<Self>,
2054        envelope: TypedEnvelope<proto::Unstage>,
2055        mut cx: AsyncApp,
2056    ) -> Result<proto::Ack> {
2057        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2058        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2059
2060        let entries = envelope
2061            .payload
2062            .paths
2063            .into_iter()
2064            .map(|path| RepoPath::new(&path))
2065            .collect::<Result<Vec<_>>>()?;
2066
2067        repository_handle
2068            .update(&mut cx, |repository_handle, cx| {
2069                repository_handle.unstage_entries(entries, cx)
2070            })
2071            .await?;
2072
2073        Ok(proto::Ack {})
2074    }
2075
2076    async fn handle_stash(
2077        this: Entity<Self>,
2078        envelope: TypedEnvelope<proto::Stash>,
2079        mut cx: AsyncApp,
2080    ) -> Result<proto::Ack> {
2081        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2082        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2083
2084        let entries = envelope
2085            .payload
2086            .paths
2087            .into_iter()
2088            .map(|path| RepoPath::new(&path))
2089            .collect::<Result<Vec<_>>>()?;
2090
2091        repository_handle
2092            .update(&mut cx, |repository_handle, cx| {
2093                repository_handle.stash_entries(entries, cx)
2094            })
2095            .await?;
2096
2097        Ok(proto::Ack {})
2098    }
2099
2100    async fn handle_stash_pop(
2101        this: Entity<Self>,
2102        envelope: TypedEnvelope<proto::StashPop>,
2103        mut cx: AsyncApp,
2104    ) -> Result<proto::Ack> {
2105        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2106        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2107        let stash_index = envelope.payload.stash_index.map(|i| i as usize);
2108
2109        repository_handle
2110            .update(&mut cx, |repository_handle, cx| {
2111                repository_handle.stash_pop(stash_index, cx)
2112            })
2113            .await?;
2114
2115        Ok(proto::Ack {})
2116    }
2117
2118    async fn handle_stash_apply(
2119        this: Entity<Self>,
2120        envelope: TypedEnvelope<proto::StashApply>,
2121        mut cx: AsyncApp,
2122    ) -> Result<proto::Ack> {
2123        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2124        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2125        let stash_index = envelope.payload.stash_index.map(|i| i as usize);
2126
2127        repository_handle
2128            .update(&mut cx, |repository_handle, cx| {
2129                repository_handle.stash_apply(stash_index, cx)
2130            })
2131            .await?;
2132
2133        Ok(proto::Ack {})
2134    }
2135
2136    async fn handle_stash_drop(
2137        this: Entity<Self>,
2138        envelope: TypedEnvelope<proto::StashDrop>,
2139        mut cx: AsyncApp,
2140    ) -> Result<proto::Ack> {
2141        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2142        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2143        let stash_index = envelope.payload.stash_index.map(|i| i as usize);
2144
2145        repository_handle
2146            .update(&mut cx, |repository_handle, cx| {
2147                repository_handle.stash_drop(stash_index, cx)
2148            })
2149            .await??;
2150
2151        Ok(proto::Ack {})
2152    }
2153
2154    async fn handle_set_index_text(
2155        this: Entity<Self>,
2156        envelope: TypedEnvelope<proto::SetIndexText>,
2157        mut cx: AsyncApp,
2158    ) -> Result<proto::Ack> {
2159        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2160        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2161        let repo_path = RepoPath::from_proto(&envelope.payload.path)?;
2162
2163        repository_handle
2164            .update(&mut cx, |repository_handle, cx| {
2165                repository_handle.spawn_set_index_text_job(
2166                    repo_path,
2167                    envelope.payload.text,
2168                    None,
2169                    cx,
2170                )
2171            })
2172            .await??;
2173        Ok(proto::Ack {})
2174    }
2175
2176    async fn handle_run_hook(
2177        this: Entity<Self>,
2178        envelope: TypedEnvelope<proto::RunGitHook>,
2179        mut cx: AsyncApp,
2180    ) -> Result<proto::Ack> {
2181        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2182        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2183        let hook = RunHook::from_proto(envelope.payload.hook).context("invalid hook")?;
2184        repository_handle
2185            .update(&mut cx, |repository_handle, cx| {
2186                repository_handle.run_hook(hook, cx)
2187            })
2188            .await??;
2189        Ok(proto::Ack {})
2190    }
2191
2192    async fn handle_commit(
2193        this: Entity<Self>,
2194        envelope: TypedEnvelope<proto::Commit>,
2195        mut cx: AsyncApp,
2196    ) -> Result<proto::Ack> {
2197        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2198        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2199        let askpass_id = envelope.payload.askpass_id;
2200
2201        let askpass = make_remote_delegate(
2202            this,
2203            envelope.payload.project_id,
2204            repository_id,
2205            askpass_id,
2206            &mut cx,
2207        );
2208
2209        let message = SharedString::from(envelope.payload.message);
2210        let name = envelope.payload.name.map(SharedString::from);
2211        let email = envelope.payload.email.map(SharedString::from);
2212        let options = envelope.payload.options.unwrap_or_default();
2213
2214        repository_handle
2215            .update(&mut cx, |repository_handle, cx| {
2216                repository_handle.commit(
2217                    message,
2218                    name.zip(email),
2219                    CommitOptions {
2220                        amend: options.amend,
2221                        signoff: options.signoff,
2222                    },
2223                    askpass,
2224                    cx,
2225                )
2226            })
2227            .await??;
2228        Ok(proto::Ack {})
2229    }
2230
2231    async fn handle_get_remotes(
2232        this: Entity<Self>,
2233        envelope: TypedEnvelope<proto::GetRemotes>,
2234        mut cx: AsyncApp,
2235    ) -> Result<proto::GetRemotesResponse> {
2236        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2237        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2238
2239        let branch_name = envelope.payload.branch_name;
2240        let is_push = envelope.payload.is_push;
2241
2242        let remotes = repository_handle
2243            .update(&mut cx, |repository_handle, _| {
2244                repository_handle.get_remotes(branch_name, is_push)
2245            })
2246            .await??;
2247
2248        Ok(proto::GetRemotesResponse {
2249            remotes: remotes
2250                .into_iter()
2251                .map(|remotes| proto::get_remotes_response::Remote {
2252                    name: remotes.name.to_string(),
2253                })
2254                .collect::<Vec<_>>(),
2255        })
2256    }
2257
2258    async fn handle_get_worktrees(
2259        this: Entity<Self>,
2260        envelope: TypedEnvelope<proto::GitGetWorktrees>,
2261        mut cx: AsyncApp,
2262    ) -> Result<proto::GitWorktreesResponse> {
2263        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2264        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2265
2266        let worktrees = repository_handle
2267            .update(&mut cx, |repository_handle, _| {
2268                repository_handle.worktrees()
2269            })
2270            .await??;
2271
2272        Ok(proto::GitWorktreesResponse {
2273            worktrees: worktrees
2274                .into_iter()
2275                .map(|worktree| worktree_to_proto(&worktree))
2276                .collect::<Vec<_>>(),
2277        })
2278    }
2279
2280    async fn handle_create_worktree(
2281        this: Entity<Self>,
2282        envelope: TypedEnvelope<proto::GitCreateWorktree>,
2283        mut cx: AsyncApp,
2284    ) -> Result<proto::Ack> {
2285        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2286        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2287        let directory = PathBuf::from(envelope.payload.directory);
2288        let name = envelope.payload.name;
2289        let commit = envelope.payload.commit;
2290
2291        repository_handle
2292            .update(&mut cx, |repository_handle, _| {
2293                repository_handle.create_worktree(name, directory, commit)
2294            })
2295            .await??;
2296
2297        Ok(proto::Ack {})
2298    }
2299
2300    async fn handle_get_branches(
2301        this: Entity<Self>,
2302        envelope: TypedEnvelope<proto::GitGetBranches>,
2303        mut cx: AsyncApp,
2304    ) -> Result<proto::GitBranchesResponse> {
2305        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2306        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2307
2308        let branches = repository_handle
2309            .update(&mut cx, |repository_handle, _| repository_handle.branches())
2310            .await??;
2311
2312        Ok(proto::GitBranchesResponse {
2313            branches: branches
2314                .into_iter()
2315                .map(|branch| branch_to_proto(&branch))
2316                .collect::<Vec<_>>(),
2317        })
2318    }
2319    async fn handle_get_default_branch(
2320        this: Entity<Self>,
2321        envelope: TypedEnvelope<proto::GetDefaultBranch>,
2322        mut cx: AsyncApp,
2323    ) -> Result<proto::GetDefaultBranchResponse> {
2324        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2325        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2326
2327        let branch = repository_handle
2328            .update(&mut cx, |repository_handle, _| {
2329                repository_handle.default_branch(false)
2330            })
2331            .await??
2332            .map(Into::into);
2333
2334        Ok(proto::GetDefaultBranchResponse { branch })
2335    }
2336    async fn handle_create_branch(
2337        this: Entity<Self>,
2338        envelope: TypedEnvelope<proto::GitCreateBranch>,
2339        mut cx: AsyncApp,
2340    ) -> Result<proto::Ack> {
2341        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2342        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2343        let branch_name = envelope.payload.branch_name;
2344
2345        repository_handle
2346            .update(&mut cx, |repository_handle, _| {
2347                repository_handle.create_branch(branch_name, None)
2348            })
2349            .await??;
2350
2351        Ok(proto::Ack {})
2352    }
2353
2354    async fn handle_change_branch(
2355        this: Entity<Self>,
2356        envelope: TypedEnvelope<proto::GitChangeBranch>,
2357        mut cx: AsyncApp,
2358    ) -> Result<proto::Ack> {
2359        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2360        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2361        let branch_name = envelope.payload.branch_name;
2362
2363        repository_handle
2364            .update(&mut cx, |repository_handle, _| {
2365                repository_handle.change_branch(branch_name)
2366            })
2367            .await??;
2368
2369        Ok(proto::Ack {})
2370    }
2371
2372    async fn handle_rename_branch(
2373        this: Entity<Self>,
2374        envelope: TypedEnvelope<proto::GitRenameBranch>,
2375        mut cx: AsyncApp,
2376    ) -> Result<proto::Ack> {
2377        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2378        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2379        let branch = envelope.payload.branch;
2380        let new_name = envelope.payload.new_name;
2381
2382        repository_handle
2383            .update(&mut cx, |repository_handle, _| {
2384                repository_handle.rename_branch(branch, new_name)
2385            })
2386            .await??;
2387
2388        Ok(proto::Ack {})
2389    }
2390
2391    async fn handle_create_remote(
2392        this: Entity<Self>,
2393        envelope: TypedEnvelope<proto::GitCreateRemote>,
2394        mut cx: AsyncApp,
2395    ) -> Result<proto::Ack> {
2396        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2397        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2398        let remote_name = envelope.payload.remote_name;
2399        let remote_url = envelope.payload.remote_url;
2400
2401        repository_handle
2402            .update(&mut cx, |repository_handle, _| {
2403                repository_handle.create_remote(remote_name, remote_url)
2404            })
2405            .await??;
2406
2407        Ok(proto::Ack {})
2408    }
2409
2410    async fn handle_delete_branch(
2411        this: Entity<Self>,
2412        envelope: TypedEnvelope<proto::GitDeleteBranch>,
2413        mut cx: AsyncApp,
2414    ) -> Result<proto::Ack> {
2415        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2416        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2417        let branch_name = envelope.payload.branch_name;
2418
2419        repository_handle
2420            .update(&mut cx, |repository_handle, _| {
2421                repository_handle.delete_branch(branch_name)
2422            })
2423            .await??;
2424
2425        Ok(proto::Ack {})
2426    }
2427
2428    async fn handle_remove_remote(
2429        this: Entity<Self>,
2430        envelope: TypedEnvelope<proto::GitRemoveRemote>,
2431        mut cx: AsyncApp,
2432    ) -> Result<proto::Ack> {
2433        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2434        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2435        let remote_name = envelope.payload.remote_name;
2436
2437        repository_handle
2438            .update(&mut cx, |repository_handle, _| {
2439                repository_handle.remove_remote(remote_name)
2440            })
2441            .await??;
2442
2443        Ok(proto::Ack {})
2444    }
2445
2446    async fn handle_show(
2447        this: Entity<Self>,
2448        envelope: TypedEnvelope<proto::GitShow>,
2449        mut cx: AsyncApp,
2450    ) -> Result<proto::GitCommitDetails> {
2451        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2452        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2453
2454        let commit = repository_handle
2455            .update(&mut cx, |repository_handle, _| {
2456                repository_handle.show(envelope.payload.commit)
2457            })
2458            .await??;
2459        Ok(proto::GitCommitDetails {
2460            sha: commit.sha.into(),
2461            message: commit.message.into(),
2462            commit_timestamp: commit.commit_timestamp,
2463            author_email: commit.author_email.into(),
2464            author_name: commit.author_name.into(),
2465        })
2466    }
2467
2468    async fn handle_load_commit_diff(
2469        this: Entity<Self>,
2470        envelope: TypedEnvelope<proto::LoadCommitDiff>,
2471        mut cx: AsyncApp,
2472    ) -> Result<proto::LoadCommitDiffResponse> {
2473        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2474        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2475
2476        let commit_diff = repository_handle
2477            .update(&mut cx, |repository_handle, _| {
2478                repository_handle.load_commit_diff(envelope.payload.commit)
2479            })
2480            .await??;
2481        Ok(proto::LoadCommitDiffResponse {
2482            files: commit_diff
2483                .files
2484                .into_iter()
2485                .map(|file| proto::CommitFile {
2486                    path: file.path.to_proto(),
2487                    old_text: file.old_text,
2488                    new_text: file.new_text,
2489                    is_binary: file.is_binary,
2490                })
2491                .collect(),
2492        })
2493    }
2494
2495    async fn handle_file_history(
2496        this: Entity<Self>,
2497        envelope: TypedEnvelope<proto::GitFileHistory>,
2498        mut cx: AsyncApp,
2499    ) -> Result<proto::GitFileHistoryResponse> {
2500        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2501        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2502        let path = RepoPath::from_proto(&envelope.payload.path)?;
2503        let skip = envelope.payload.skip as usize;
2504        let limit = envelope.payload.limit.map(|l| l as usize);
2505
2506        let file_history = repository_handle
2507            .update(&mut cx, |repository_handle, _| {
2508                repository_handle.file_history_paginated(path, skip, limit)
2509            })
2510            .await??;
2511
2512        Ok(proto::GitFileHistoryResponse {
2513            entries: file_history
2514                .entries
2515                .into_iter()
2516                .map(|entry| proto::FileHistoryEntry {
2517                    sha: entry.sha.to_string(),
2518                    subject: entry.subject.to_string(),
2519                    message: entry.message.to_string(),
2520                    commit_timestamp: entry.commit_timestamp,
2521                    author_name: entry.author_name.to_string(),
2522                    author_email: entry.author_email.to_string(),
2523                })
2524                .collect(),
2525            path: file_history.path.to_proto(),
2526        })
2527    }
2528
2529    async fn handle_reset(
2530        this: Entity<Self>,
2531        envelope: TypedEnvelope<proto::GitReset>,
2532        mut cx: AsyncApp,
2533    ) -> Result<proto::Ack> {
2534        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2535        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2536
2537        let mode = match envelope.payload.mode() {
2538            git_reset::ResetMode::Soft => ResetMode::Soft,
2539            git_reset::ResetMode::Mixed => ResetMode::Mixed,
2540        };
2541
2542        repository_handle
2543            .update(&mut cx, |repository_handle, cx| {
2544                repository_handle.reset(envelope.payload.commit, mode, cx)
2545            })
2546            .await??;
2547        Ok(proto::Ack {})
2548    }
2549
2550    async fn handle_checkout_files(
2551        this: Entity<Self>,
2552        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
2553        mut cx: AsyncApp,
2554    ) -> Result<proto::Ack> {
2555        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2556        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2557        let paths = envelope
2558            .payload
2559            .paths
2560            .iter()
2561            .map(|s| RepoPath::from_proto(s))
2562            .collect::<Result<Vec<_>>>()?;
2563
2564        repository_handle
2565            .update(&mut cx, |repository_handle, cx| {
2566                repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
2567            })
2568            .await?;
2569        Ok(proto::Ack {})
2570    }
2571
2572    async fn handle_open_commit_message_buffer(
2573        this: Entity<Self>,
2574        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
2575        mut cx: AsyncApp,
2576    ) -> Result<proto::OpenBufferResponse> {
2577        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2578        let repository = Self::repository_for_request(&this, repository_id, &mut cx)?;
2579        let buffer = repository
2580            .update(&mut cx, |repository, cx| {
2581                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
2582            })
2583            .await?;
2584
2585        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id());
2586        this.update(&mut cx, |this, cx| {
2587            this.buffer_store.update(cx, |buffer_store, cx| {
2588                buffer_store
2589                    .create_buffer_for_peer(
2590                        &buffer,
2591                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
2592                        cx,
2593                    )
2594                    .detach_and_log_err(cx);
2595            })
2596        });
2597
2598        Ok(proto::OpenBufferResponse {
2599            buffer_id: buffer_id.to_proto(),
2600        })
2601    }
2602
2603    async fn handle_askpass(
2604        this: Entity<Self>,
2605        envelope: TypedEnvelope<proto::AskPassRequest>,
2606        mut cx: AsyncApp,
2607    ) -> Result<proto::AskPassResponse> {
2608        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2609        let repository = Self::repository_for_request(&this, repository_id, &mut cx)?;
2610
2611        let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone());
2612        let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
2613            debug_panic!("no askpass found");
2614            anyhow::bail!("no askpass found");
2615        };
2616
2617        let response = askpass
2618            .ask_password(envelope.payload.prompt)
2619            .await
2620            .ok_or_else(|| anyhow::anyhow!("askpass cancelled"))?;
2621
2622        delegates
2623            .lock()
2624            .insert(envelope.payload.askpass_id, askpass);
2625
2626        // In fact, we don't quite know what we're doing here, as we're sending askpass password unencrypted, but..
2627        Ok(proto::AskPassResponse {
2628            response: response.decrypt(IKnowWhatIAmDoingAndIHaveReadTheDocs)?,
2629        })
2630    }
2631
2632    async fn handle_check_for_pushed_commits(
2633        this: Entity<Self>,
2634        envelope: TypedEnvelope<proto::CheckForPushedCommits>,
2635        mut cx: AsyncApp,
2636    ) -> Result<proto::CheckForPushedCommitsResponse> {
2637        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2638        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2639
2640        let branches = repository_handle
2641            .update(&mut cx, |repository_handle, _| {
2642                repository_handle.check_for_pushed_commits()
2643            })
2644            .await??;
2645        Ok(proto::CheckForPushedCommitsResponse {
2646            pushed_to: branches
2647                .into_iter()
2648                .map(|commit| commit.to_string())
2649                .collect(),
2650        })
2651    }
2652
2653    async fn handle_git_diff(
2654        this: Entity<Self>,
2655        envelope: TypedEnvelope<proto::GitDiff>,
2656        mut cx: AsyncApp,
2657    ) -> Result<proto::GitDiffResponse> {
2658        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2659        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2660        let diff_type = match envelope.payload.diff_type() {
2661            proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
2662            proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
2663            proto::git_diff::DiffType::MergeBase => {
2664                let base_ref = envelope
2665                    .payload
2666                    .merge_base_ref
2667                    .ok_or_else(|| anyhow!("merge_base_ref is required for MergeBase diff type"))?;
2668                DiffType::MergeBase {
2669                    base_ref: base_ref.into(),
2670                }
2671            }
2672        };
2673
2674        let mut diff = repository_handle
2675            .update(&mut cx, |repository_handle, cx| {
2676                repository_handle.diff(diff_type, cx)
2677            })
2678            .await??;
2679        const ONE_MB: usize = 1_000_000;
2680        if diff.len() > ONE_MB {
2681            diff = diff.chars().take(ONE_MB).collect()
2682        }
2683
2684        Ok(proto::GitDiffResponse { diff })
2685    }
2686
2687    async fn handle_tree_diff(
2688        this: Entity<Self>,
2689        request: TypedEnvelope<proto::GetTreeDiff>,
2690        mut cx: AsyncApp,
2691    ) -> Result<proto::GetTreeDiffResponse> {
2692        let repository_id = RepositoryId(request.payload.repository_id);
2693        let diff_type = if request.payload.is_merge {
2694            DiffTreeType::MergeBase {
2695                base: request.payload.base.into(),
2696                head: request.payload.head.into(),
2697            }
2698        } else {
2699            DiffTreeType::Since {
2700                base: request.payload.base.into(),
2701                head: request.payload.head.into(),
2702            }
2703        };
2704
2705        let diff = this
2706            .update(&mut cx, |this, cx| {
2707                let repository = this.repositories().get(&repository_id)?;
2708                Some(repository.update(cx, |repo, cx| repo.diff_tree(diff_type, cx)))
2709            })
2710            .context("missing repository")?
2711            .await??;
2712
2713        Ok(proto::GetTreeDiffResponse {
2714            entries: diff
2715                .entries
2716                .into_iter()
2717                .map(|(path, status)| proto::TreeDiffStatus {
2718                    path: path.as_ref().to_proto(),
2719                    status: match status {
2720                        TreeDiffStatus::Added {} => proto::tree_diff_status::Status::Added.into(),
2721                        TreeDiffStatus::Modified { .. } => {
2722                            proto::tree_diff_status::Status::Modified.into()
2723                        }
2724                        TreeDiffStatus::Deleted { .. } => {
2725                            proto::tree_diff_status::Status::Deleted.into()
2726                        }
2727                    },
2728                    oid: match status {
2729                        TreeDiffStatus::Deleted { old } | TreeDiffStatus::Modified { old } => {
2730                            Some(old.to_string())
2731                        }
2732                        TreeDiffStatus::Added => None,
2733                    },
2734                })
2735                .collect(),
2736        })
2737    }
2738
2739    async fn handle_get_blob_content(
2740        this: Entity<Self>,
2741        request: TypedEnvelope<proto::GetBlobContent>,
2742        mut cx: AsyncApp,
2743    ) -> Result<proto::GetBlobContentResponse> {
2744        let oid = git::Oid::from_str(&request.payload.oid)?;
2745        let repository_id = RepositoryId(request.payload.repository_id);
2746        let content = this
2747            .update(&mut cx, |this, cx| {
2748                let repository = this.repositories().get(&repository_id)?;
2749                Some(repository.update(cx, |repo, cx| repo.load_blob_content(oid, cx)))
2750            })
2751            .context("missing repository")?
2752            .await?;
2753        Ok(proto::GetBlobContentResponse { content })
2754    }
2755
2756    async fn handle_open_unstaged_diff(
2757        this: Entity<Self>,
2758        request: TypedEnvelope<proto::OpenUnstagedDiff>,
2759        mut cx: AsyncApp,
2760    ) -> Result<proto::OpenUnstagedDiffResponse> {
2761        let buffer_id = BufferId::new(request.payload.buffer_id)?;
2762        let diff = this
2763            .update(&mut cx, |this, cx| {
2764                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
2765                Some(this.open_unstaged_diff(buffer, cx))
2766            })
2767            .context("missing buffer")?
2768            .await?;
2769        this.update(&mut cx, |this, _| {
2770            let shared_diffs = this
2771                .shared_diffs
2772                .entry(request.original_sender_id.unwrap_or(request.sender_id))
2773                .or_default();
2774            shared_diffs.entry(buffer_id).or_default().unstaged = Some(diff.clone());
2775        });
2776        let staged_text = diff.read_with(&cx, |diff, cx| diff.base_text_string(cx));
2777        Ok(proto::OpenUnstagedDiffResponse { staged_text })
2778    }
2779
2780    async fn handle_open_uncommitted_diff(
2781        this: Entity<Self>,
2782        request: TypedEnvelope<proto::OpenUncommittedDiff>,
2783        mut cx: AsyncApp,
2784    ) -> Result<proto::OpenUncommittedDiffResponse> {
2785        let buffer_id = BufferId::new(request.payload.buffer_id)?;
2786        let diff = this
2787            .update(&mut cx, |this, cx| {
2788                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
2789                Some(this.open_uncommitted_diff(buffer, cx))
2790            })
2791            .context("missing buffer")?
2792            .await?;
2793        this.update(&mut cx, |this, _| {
2794            let shared_diffs = this
2795                .shared_diffs
2796                .entry(request.original_sender_id.unwrap_or(request.sender_id))
2797                .or_default();
2798            shared_diffs.entry(buffer_id).or_default().uncommitted = Some(diff.clone());
2799        });
2800        Ok(diff.read_with(&cx, |diff, cx| {
2801            use proto::open_uncommitted_diff_response::Mode;
2802
2803            let unstaged_diff = diff.secondary_diff();
2804            let index_snapshot = unstaged_diff.and_then(|diff| {
2805                let diff = diff.read(cx);
2806                diff.base_text_exists().then(|| diff.base_text(cx))
2807            });
2808
2809            let mode;
2810            let staged_text;
2811            let committed_text;
2812            if diff.base_text_exists() {
2813                let committed_snapshot = diff.base_text(cx);
2814                committed_text = Some(committed_snapshot.text());
2815                if let Some(index_text) = index_snapshot {
2816                    if index_text.remote_id() == committed_snapshot.remote_id() {
2817                        mode = Mode::IndexMatchesHead;
2818                        staged_text = None;
2819                    } else {
2820                        mode = Mode::IndexAndHead;
2821                        staged_text = Some(index_text.text());
2822                    }
2823                } else {
2824                    mode = Mode::IndexAndHead;
2825                    staged_text = None;
2826                }
2827            } else {
2828                mode = Mode::IndexAndHead;
2829                committed_text = None;
2830                staged_text = index_snapshot.as_ref().map(|buffer| buffer.text());
2831            }
2832
2833            proto::OpenUncommittedDiffResponse {
2834                committed_text,
2835                staged_text,
2836                mode: mode.into(),
2837            }
2838        }))
2839    }
2840
2841    async fn handle_update_diff_bases(
2842        this: Entity<Self>,
2843        request: TypedEnvelope<proto::UpdateDiffBases>,
2844        mut cx: AsyncApp,
2845    ) -> Result<()> {
2846        let buffer_id = BufferId::new(request.payload.buffer_id)?;
2847        this.update(&mut cx, |this, cx| {
2848            if let Some(diff_state) = this.diffs.get_mut(&buffer_id)
2849                && let Some(buffer) = this.buffer_store.read(cx).get(buffer_id)
2850            {
2851                let buffer = buffer.read(cx).text_snapshot();
2852                diff_state.update(cx, |diff_state, cx| {
2853                    diff_state.handle_base_texts_updated(buffer, request.payload, cx);
2854                })
2855            }
2856        });
2857        Ok(())
2858    }
2859
2860    async fn handle_blame_buffer(
2861        this: Entity<Self>,
2862        envelope: TypedEnvelope<proto::BlameBuffer>,
2863        mut cx: AsyncApp,
2864    ) -> Result<proto::BlameBufferResponse> {
2865        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
2866        let version = deserialize_version(&envelope.payload.version);
2867        let buffer = this.read_with(&cx, |this, cx| {
2868            this.buffer_store.read(cx).get_existing(buffer_id)
2869        })?;
2870        buffer
2871            .update(&mut cx, |buffer, _| {
2872                buffer.wait_for_version(version.clone())
2873            })
2874            .await?;
2875        let blame = this
2876            .update(&mut cx, |this, cx| {
2877                this.blame_buffer(&buffer, Some(version), cx)
2878            })
2879            .await?;
2880        Ok(serialize_blame_buffer_response(blame))
2881    }
2882
2883    async fn handle_get_permalink_to_line(
2884        this: Entity<Self>,
2885        envelope: TypedEnvelope<proto::GetPermalinkToLine>,
2886        mut cx: AsyncApp,
2887    ) -> Result<proto::GetPermalinkToLineResponse> {
2888        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
2889        // let version = deserialize_version(&envelope.payload.version);
2890        let selection = {
2891            let proto_selection = envelope
2892                .payload
2893                .selection
2894                .context("no selection to get permalink for defined")?;
2895            proto_selection.start as u32..proto_selection.end as u32
2896        };
2897        let buffer = this.read_with(&cx, |this, cx| {
2898            this.buffer_store.read(cx).get_existing(buffer_id)
2899        })?;
2900        let permalink = this
2901            .update(&mut cx, |this, cx| {
2902                this.get_permalink_to_line(&buffer, selection, cx)
2903            })
2904            .await?;
2905        Ok(proto::GetPermalinkToLineResponse {
2906            permalink: permalink.to_string(),
2907        })
2908    }
2909
2910    fn repository_for_request(
2911        this: &Entity<Self>,
2912        id: RepositoryId,
2913        cx: &mut AsyncApp,
2914    ) -> Result<Entity<Repository>> {
2915        this.read_with(cx, |this, _| {
2916            this.repositories
2917                .get(&id)
2918                .context("missing repository handle")
2919                .cloned()
2920        })
2921    }
2922
2923    pub fn repo_snapshots(&self, cx: &App) -> HashMap<RepositoryId, RepositorySnapshot> {
2924        self.repositories
2925            .iter()
2926            .map(|(id, repo)| (*id, repo.read(cx).snapshot.clone()))
2927            .collect()
2928    }
2929
2930    fn process_updated_entries(
2931        &self,
2932        worktree: &Entity<Worktree>,
2933        updated_entries: &[(Arc<RelPath>, ProjectEntryId, PathChange)],
2934        cx: &mut App,
2935    ) -> Task<HashMap<Entity<Repository>, Vec<RepoPath>>> {
2936        let path_style = worktree.read(cx).path_style();
2937        let mut repo_paths = self
2938            .repositories
2939            .values()
2940            .map(|repo| (repo.read(cx).work_directory_abs_path.clone(), repo.clone()))
2941            .collect::<Vec<_>>();
2942        let mut entries: Vec<_> = updated_entries
2943            .iter()
2944            .map(|(path, _, _)| path.clone())
2945            .collect();
2946        entries.sort();
2947        let worktree = worktree.read(cx);
2948
2949        let entries = entries
2950            .into_iter()
2951            .map(|path| worktree.absolutize(&path))
2952            .collect::<Arc<[_]>>();
2953
2954        let executor = cx.background_executor().clone();
2955        cx.background_executor().spawn(async move {
2956            repo_paths.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0));
2957            let mut paths_by_git_repo = HashMap::<_, Vec<_>>::default();
2958            let mut tasks = FuturesOrdered::new();
2959            for (repo_path, repo) in repo_paths.into_iter().rev() {
2960                let entries = entries.clone();
2961                let task = executor.spawn(async move {
2962                    // Find all repository paths that belong to this repo
2963                    let mut ix = entries.partition_point(|path| path < &*repo_path);
2964                    if ix == entries.len() {
2965                        return None;
2966                    };
2967
2968                    let mut paths = Vec::new();
2969                    // All paths prefixed by a given repo will constitute a continuous range.
2970                    while let Some(path) = entries.get(ix)
2971                        && let Some(repo_path) = RepositorySnapshot::abs_path_to_repo_path_inner(
2972                            &repo_path, path, path_style,
2973                        )
2974                    {
2975                        paths.push((repo_path, ix));
2976                        ix += 1;
2977                    }
2978                    if paths.is_empty() {
2979                        None
2980                    } else {
2981                        Some((repo, paths))
2982                    }
2983                });
2984                tasks.push_back(task);
2985            }
2986
2987            // Now, let's filter out the "duplicate" entries that were processed by multiple distinct repos.
2988            let mut path_was_used = vec![false; entries.len()];
2989            let tasks = tasks.collect::<Vec<_>>().await;
2990            // Process tasks from the back: iterating backwards allows us to see more-specific paths first.
2991            // We always want to assign a path to it's innermost repository.
2992            for t in tasks {
2993                let Some((repo, paths)) = t else {
2994                    continue;
2995                };
2996                let entry = paths_by_git_repo.entry(repo).or_default();
2997                for (repo_path, ix) in paths {
2998                    if path_was_used[ix] {
2999                        continue;
3000                    }
3001                    path_was_used[ix] = true;
3002                    entry.push(repo_path);
3003                }
3004            }
3005
3006            paths_by_git_repo
3007        })
3008    }
3009}
3010
3011impl BufferGitState {
3012    fn new(_git_store: WeakEntity<GitStore>) -> Self {
3013        Self {
3014            unstaged_diff: Default::default(),
3015            uncommitted_diff: Default::default(),
3016            oid_diffs: Default::default(),
3017            recalculate_diff_task: Default::default(),
3018            language: Default::default(),
3019            language_registry: Default::default(),
3020            recalculating_tx: postage::watch::channel_with(false).0,
3021            hunk_staging_operation_count: 0,
3022            hunk_staging_operation_count_as_of_write: 0,
3023            head_text: Default::default(),
3024            index_text: Default::default(),
3025            oid_texts: Default::default(),
3026            head_changed: Default::default(),
3027            index_changed: Default::default(),
3028            language_changed: Default::default(),
3029            conflict_updated_futures: Default::default(),
3030            conflict_set: Default::default(),
3031            reparse_conflict_markers_task: Default::default(),
3032        }
3033    }
3034
3035    #[ztracing::instrument(skip_all)]
3036    fn buffer_language_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
3037        self.language = buffer.read(cx).language().cloned();
3038        self.language_changed = true;
3039        let _ = self.recalculate_diffs(buffer.read(cx).text_snapshot(), cx);
3040    }
3041
3042    fn reparse_conflict_markers(
3043        &mut self,
3044        buffer: text::BufferSnapshot,
3045        cx: &mut Context<Self>,
3046    ) -> oneshot::Receiver<()> {
3047        let (tx, rx) = oneshot::channel();
3048
3049        let Some(conflict_set) = self
3050            .conflict_set
3051            .as_ref()
3052            .and_then(|conflict_set| conflict_set.upgrade())
3053        else {
3054            return rx;
3055        };
3056
3057        let old_snapshot = conflict_set.read_with(cx, |conflict_set, _| {
3058            if conflict_set.has_conflict {
3059                Some(conflict_set.snapshot())
3060            } else {
3061                None
3062            }
3063        });
3064
3065        if let Some(old_snapshot) = old_snapshot {
3066            self.conflict_updated_futures.push(tx);
3067            self.reparse_conflict_markers_task = Some(cx.spawn(async move |this, cx| {
3068                let (snapshot, changed_range) = cx
3069                    .background_spawn(async move {
3070                        let new_snapshot = ConflictSet::parse(&buffer);
3071                        let changed_range = old_snapshot.compare(&new_snapshot, &buffer);
3072                        (new_snapshot, changed_range)
3073                    })
3074                    .await;
3075                this.update(cx, |this, cx| {
3076                    if let Some(conflict_set) = &this.conflict_set {
3077                        conflict_set
3078                            .update(cx, |conflict_set, cx| {
3079                                conflict_set.set_snapshot(snapshot, changed_range, cx);
3080                            })
3081                            .ok();
3082                    }
3083                    let futures = std::mem::take(&mut this.conflict_updated_futures);
3084                    for tx in futures {
3085                        tx.send(()).ok();
3086                    }
3087                })
3088            }))
3089        }
3090
3091        rx
3092    }
3093
3094    fn unstaged_diff(&self) -> Option<Entity<BufferDiff>> {
3095        self.unstaged_diff.as_ref().and_then(|set| set.upgrade())
3096    }
3097
3098    fn uncommitted_diff(&self) -> Option<Entity<BufferDiff>> {
3099        self.uncommitted_diff.as_ref().and_then(|set| set.upgrade())
3100    }
3101
3102    fn oid_diff(&self, oid: Option<git::Oid>) -> Option<Entity<BufferDiff>> {
3103        self.oid_diffs.get(&oid).and_then(|weak| weak.upgrade())
3104    }
3105
3106    fn handle_base_texts_updated(
3107        &mut self,
3108        buffer: text::BufferSnapshot,
3109        message: proto::UpdateDiffBases,
3110        cx: &mut Context<Self>,
3111    ) {
3112        use proto::update_diff_bases::Mode;
3113
3114        let Some(mode) = Mode::from_i32(message.mode) else {
3115            return;
3116        };
3117
3118        let diff_bases_change = match mode {
3119            Mode::HeadOnly => DiffBasesChange::SetHead(message.committed_text),
3120            Mode::IndexOnly => DiffBasesChange::SetIndex(message.staged_text),
3121            Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.committed_text),
3122            Mode::IndexAndHead => DiffBasesChange::SetEach {
3123                index: message.staged_text,
3124                head: message.committed_text,
3125            },
3126        };
3127
3128        self.diff_bases_changed(buffer, Some(diff_bases_change), cx);
3129    }
3130
3131    pub fn wait_for_recalculation(&mut self) -> Option<impl Future<Output = ()> + use<>> {
3132        if *self.recalculating_tx.borrow() {
3133            let mut rx = self.recalculating_tx.subscribe();
3134            Some(async move {
3135                loop {
3136                    let is_recalculating = rx.recv().await;
3137                    if is_recalculating != Some(true) {
3138                        break;
3139                    }
3140                }
3141            })
3142        } else {
3143            None
3144        }
3145    }
3146
3147    fn diff_bases_changed(
3148        &mut self,
3149        buffer: text::BufferSnapshot,
3150        diff_bases_change: Option<DiffBasesChange>,
3151        cx: &mut Context<Self>,
3152    ) {
3153        match diff_bases_change {
3154            Some(DiffBasesChange::SetIndex(index)) => {
3155                self.index_text = index.map(|mut index| {
3156                    text::LineEnding::normalize(&mut index);
3157                    Arc::from(index.as_str())
3158                });
3159                self.index_changed = true;
3160            }
3161            Some(DiffBasesChange::SetHead(head)) => {
3162                self.head_text = head.map(|mut head| {
3163                    text::LineEnding::normalize(&mut head);
3164                    Arc::from(head.as_str())
3165                });
3166                self.head_changed = true;
3167            }
3168            Some(DiffBasesChange::SetBoth(text)) => {
3169                let text = text.map(|mut text| {
3170                    text::LineEnding::normalize(&mut text);
3171                    Arc::from(text.as_str())
3172                });
3173                self.head_text = text.clone();
3174                self.index_text = text;
3175                self.head_changed = true;
3176                self.index_changed = true;
3177            }
3178            Some(DiffBasesChange::SetEach { index, head }) => {
3179                self.index_text = index.map(|mut index| {
3180                    text::LineEnding::normalize(&mut index);
3181                    Arc::from(index.as_str())
3182                });
3183                self.index_changed = true;
3184                self.head_text = head.map(|mut head| {
3185                    text::LineEnding::normalize(&mut head);
3186                    Arc::from(head.as_str())
3187                });
3188                self.head_changed = true;
3189            }
3190            None => {}
3191        }
3192
3193        self.recalculate_diffs(buffer, cx)
3194    }
3195
3196    #[ztracing::instrument(skip_all)]
3197    fn recalculate_diffs(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
3198        *self.recalculating_tx.borrow_mut() = true;
3199
3200        let language = self.language.clone();
3201        let language_registry = self.language_registry.clone();
3202        let unstaged_diff = self.unstaged_diff();
3203        let uncommitted_diff = self.uncommitted_diff();
3204        let head = self.head_text.clone();
3205        let index = self.index_text.clone();
3206        let index_changed = self.index_changed;
3207        let head_changed = self.head_changed;
3208        let language_changed = self.language_changed;
3209        let prev_hunk_staging_operation_count = self.hunk_staging_operation_count_as_of_write;
3210        let index_matches_head = match (self.index_text.as_ref(), self.head_text.as_ref()) {
3211            (Some(index), Some(head)) => Arc::ptr_eq(index, head),
3212            (None, None) => true,
3213            _ => false,
3214        };
3215
3216        let oid_diffs: Vec<(Option<git::Oid>, Entity<BufferDiff>, Option<Arc<str>>)> = self
3217            .oid_diffs
3218            .iter()
3219            .filter_map(|(oid, weak)| {
3220                let base_text = oid.and_then(|oid| self.oid_texts.get(&oid).cloned());
3221                weak.upgrade().map(|diff| (*oid, diff, base_text))
3222            })
3223            .collect();
3224
3225        self.oid_diffs.retain(|oid, weak| {
3226            let alive = weak.upgrade().is_some();
3227            if !alive {
3228                if let Some(oid) = oid {
3229                    self.oid_texts.remove(oid);
3230                }
3231            }
3232            alive
3233        });
3234        self.recalculate_diff_task = Some(cx.spawn(async move |this, cx| {
3235            log::debug!(
3236                "start recalculating diffs for buffer {}",
3237                buffer.remote_id()
3238            );
3239
3240            let mut new_unstaged_diff = None;
3241            if let Some(unstaged_diff) = &unstaged_diff {
3242                new_unstaged_diff = Some(
3243                    cx.update(|cx| {
3244                        unstaged_diff.read(cx).update_diff(
3245                            buffer.clone(),
3246                            index,
3247                            index_changed.then_some(false),
3248                            language.clone(),
3249                            cx,
3250                        )
3251                    })
3252                    .await,
3253                );
3254            }
3255
3256            // Dropping BufferDiff can be expensive, so yield back to the event loop
3257            // for a bit
3258            yield_now().await;
3259
3260            let mut new_uncommitted_diff = None;
3261            if let Some(uncommitted_diff) = &uncommitted_diff {
3262                new_uncommitted_diff = if index_matches_head {
3263                    new_unstaged_diff.clone()
3264                } else {
3265                    Some(
3266                        cx.update(|cx| {
3267                            uncommitted_diff.read(cx).update_diff(
3268                                buffer.clone(),
3269                                head,
3270                                head_changed.then_some(true),
3271                                language.clone(),
3272                                cx,
3273                            )
3274                        })
3275                        .await,
3276                    )
3277                }
3278            }
3279
3280            // Dropping BufferDiff can be expensive, so yield back to the event loop
3281            // for a bit
3282            yield_now().await;
3283
3284            let cancel = this.update(cx, |this, _| {
3285                // This checks whether all pending stage/unstage operations
3286                // have quiesced (i.e. both the corresponding write and the
3287                // read of that write have completed). If not, then we cancel
3288                // this recalculation attempt to avoid invalidating pending
3289                // state too quickly; another recalculation will come along
3290                // later and clear the pending state once the state of the index has settled.
3291                if this.hunk_staging_operation_count > prev_hunk_staging_operation_count {
3292                    *this.recalculating_tx.borrow_mut() = false;
3293                    true
3294                } else {
3295                    false
3296                }
3297            })?;
3298            if cancel {
3299                log::debug!(
3300                    concat!(
3301                        "aborting recalculating diffs for buffer {}",
3302                        "due to subsequent hunk operations",
3303                    ),
3304                    buffer.remote_id()
3305                );
3306                return Ok(());
3307            }
3308
3309            let unstaged_changed_range = if let Some((unstaged_diff, new_unstaged_diff)) =
3310                unstaged_diff.as_ref().zip(new_unstaged_diff.clone())
3311            {
3312                let task = unstaged_diff.update(cx, |diff, cx| {
3313                    // For git index buffer we skip assigning the language as we do not really need to perform any syntax highlighting on
3314                    // 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
3315                    // view multibuffers.
3316                    diff.set_snapshot(new_unstaged_diff, &buffer, cx)
3317                });
3318                Some(task.await)
3319            } else {
3320                None
3321            };
3322
3323            yield_now().await;
3324
3325            if let Some((uncommitted_diff, new_uncommitted_diff)) =
3326                uncommitted_diff.as_ref().zip(new_uncommitted_diff.clone())
3327            {
3328                uncommitted_diff
3329                    .update(cx, |diff, cx| {
3330                        if language_changed {
3331                            diff.language_changed(language.clone(), language_registry, cx);
3332                        }
3333                        diff.set_snapshot_with_secondary(
3334                            new_uncommitted_diff,
3335                            &buffer,
3336                            unstaged_changed_range.flatten(),
3337                            true,
3338                            cx,
3339                        )
3340                    })
3341                    .await;
3342            }
3343
3344            yield_now().await;
3345
3346            for (oid, oid_diff, base_text) in oid_diffs {
3347                let new_oid_diff = cx
3348                    .update(|cx| {
3349                        oid_diff.read(cx).update_diff(
3350                            buffer.clone(),
3351                            base_text,
3352                            None,
3353                            language.clone(),
3354                            cx,
3355                        )
3356                    })
3357                    .await;
3358
3359                oid_diff
3360                    .update(cx, |diff, cx| diff.set_snapshot(new_oid_diff, &buffer, cx))
3361                    .await;
3362
3363                log::debug!(
3364                    "finished recalculating oid diff for buffer {} oid {:?}",
3365                    buffer.remote_id(),
3366                    oid
3367                );
3368
3369                yield_now().await;
3370            }
3371
3372            log::debug!(
3373                "finished recalculating diffs for buffer {}",
3374                buffer.remote_id()
3375            );
3376
3377            if let Some(this) = this.upgrade() {
3378                this.update(cx, |this, _| {
3379                    this.index_changed = false;
3380                    this.head_changed = false;
3381                    this.language_changed = false;
3382                    *this.recalculating_tx.borrow_mut() = false;
3383                });
3384            }
3385
3386            Ok(())
3387        }));
3388    }
3389}
3390
3391fn make_remote_delegate(
3392    this: Entity<GitStore>,
3393    project_id: u64,
3394    repository_id: RepositoryId,
3395    askpass_id: u64,
3396    cx: &mut AsyncApp,
3397) -> AskPassDelegate {
3398    AskPassDelegate::new(cx, move |prompt, tx, cx| {
3399        this.update(cx, |this, cx| {
3400            let Some((client, _)) = this.downstream_client() else {
3401                return;
3402            };
3403            let response = client.request(proto::AskPassRequest {
3404                project_id,
3405                repository_id: repository_id.to_proto(),
3406                askpass_id,
3407                prompt,
3408            });
3409            cx.spawn(async move |_, _| {
3410                let mut response = response.await?.response;
3411                tx.send(EncryptedPassword::try_from(response.as_ref())?)
3412                    .ok();
3413                response.zeroize();
3414                anyhow::Ok(())
3415            })
3416            .detach_and_log_err(cx);
3417        });
3418    })
3419}
3420
3421impl RepositoryId {
3422    pub fn to_proto(self) -> u64 {
3423        self.0
3424    }
3425
3426    pub fn from_proto(id: u64) -> Self {
3427        RepositoryId(id)
3428    }
3429}
3430
3431impl RepositorySnapshot {
3432    fn empty(id: RepositoryId, work_directory_abs_path: Arc<Path>, path_style: PathStyle) -> Self {
3433        Self {
3434            id,
3435            statuses_by_path: Default::default(),
3436            work_directory_abs_path,
3437            branch: None,
3438            head_commit: None,
3439            scan_id: 0,
3440            merge: Default::default(),
3441            remote_origin_url: None,
3442            remote_upstream_url: None,
3443            stash_entries: Default::default(),
3444            path_style,
3445        }
3446    }
3447
3448    fn initial_update(&self, project_id: u64) -> proto::UpdateRepository {
3449        proto::UpdateRepository {
3450            branch_summary: self.branch.as_ref().map(branch_to_proto),
3451            head_commit_details: self.head_commit.as_ref().map(commit_details_to_proto),
3452            updated_statuses: self
3453                .statuses_by_path
3454                .iter()
3455                .map(|entry| entry.to_proto())
3456                .collect(),
3457            removed_statuses: Default::default(),
3458            current_merge_conflicts: self
3459                .merge
3460                .conflicted_paths
3461                .iter()
3462                .map(|repo_path| repo_path.to_proto())
3463                .collect(),
3464            merge_message: self.merge.message.as_ref().map(|msg| msg.to_string()),
3465            project_id,
3466            id: self.id.to_proto(),
3467            abs_path: self.work_directory_abs_path.to_string_lossy().into_owned(),
3468            entry_ids: vec![self.id.to_proto()],
3469            scan_id: self.scan_id,
3470            is_last_update: true,
3471            stash_entries: self
3472                .stash_entries
3473                .entries
3474                .iter()
3475                .map(stash_to_proto)
3476                .collect(),
3477            remote_upstream_url: self.remote_upstream_url.clone(),
3478            remote_origin_url: self.remote_origin_url.clone(),
3479        }
3480    }
3481
3482    fn build_update(&self, old: &Self, project_id: u64) -> proto::UpdateRepository {
3483        let mut updated_statuses: Vec<proto::StatusEntry> = Vec::new();
3484        let mut removed_statuses: Vec<String> = Vec::new();
3485
3486        let mut new_statuses = self.statuses_by_path.iter().peekable();
3487        let mut old_statuses = old.statuses_by_path.iter().peekable();
3488
3489        let mut current_new_entry = new_statuses.next();
3490        let mut current_old_entry = old_statuses.next();
3491        loop {
3492            match (current_new_entry, current_old_entry) {
3493                (Some(new_entry), Some(old_entry)) => {
3494                    match new_entry.repo_path.cmp(&old_entry.repo_path) {
3495                        Ordering::Less => {
3496                            updated_statuses.push(new_entry.to_proto());
3497                            current_new_entry = new_statuses.next();
3498                        }
3499                        Ordering::Equal => {
3500                            if new_entry.status != old_entry.status {
3501                                updated_statuses.push(new_entry.to_proto());
3502                            }
3503                            current_old_entry = old_statuses.next();
3504                            current_new_entry = new_statuses.next();
3505                        }
3506                        Ordering::Greater => {
3507                            removed_statuses.push(old_entry.repo_path.to_proto());
3508                            current_old_entry = old_statuses.next();
3509                        }
3510                    }
3511                }
3512                (None, Some(old_entry)) => {
3513                    removed_statuses.push(old_entry.repo_path.to_proto());
3514                    current_old_entry = old_statuses.next();
3515                }
3516                (Some(new_entry), None) => {
3517                    updated_statuses.push(new_entry.to_proto());
3518                    current_new_entry = new_statuses.next();
3519                }
3520                (None, None) => break,
3521            }
3522        }
3523
3524        proto::UpdateRepository {
3525            branch_summary: self.branch.as_ref().map(branch_to_proto),
3526            head_commit_details: self.head_commit.as_ref().map(commit_details_to_proto),
3527            updated_statuses,
3528            removed_statuses,
3529            current_merge_conflicts: self
3530                .merge
3531                .conflicted_paths
3532                .iter()
3533                .map(|path| path.to_proto())
3534                .collect(),
3535            merge_message: self.merge.message.as_ref().map(|msg| msg.to_string()),
3536            project_id,
3537            id: self.id.to_proto(),
3538            abs_path: self.work_directory_abs_path.to_string_lossy().into_owned(),
3539            entry_ids: vec![],
3540            scan_id: self.scan_id,
3541            is_last_update: true,
3542            stash_entries: self
3543                .stash_entries
3544                .entries
3545                .iter()
3546                .map(stash_to_proto)
3547                .collect(),
3548            remote_upstream_url: self.remote_upstream_url.clone(),
3549            remote_origin_url: self.remote_origin_url.clone(),
3550        }
3551    }
3552
3553    pub fn status(&self) -> impl Iterator<Item = StatusEntry> + '_ {
3554        self.statuses_by_path.iter().cloned()
3555    }
3556
3557    pub fn status_summary(&self) -> GitSummary {
3558        self.statuses_by_path.summary().item_summary
3559    }
3560
3561    pub fn status_for_path(&self, path: &RepoPath) -> Option<StatusEntry> {
3562        self.statuses_by_path
3563            .get(&PathKey(path.as_ref().clone()), ())
3564            .cloned()
3565    }
3566
3567    pub fn abs_path_to_repo_path(&self, abs_path: &Path) -> Option<RepoPath> {
3568        Self::abs_path_to_repo_path_inner(&self.work_directory_abs_path, abs_path, self.path_style)
3569    }
3570
3571    fn repo_path_to_abs_path(&self, repo_path: &RepoPath) -> PathBuf {
3572        self.path_style
3573            .join(&self.work_directory_abs_path, repo_path.as_std_path())
3574            .unwrap()
3575            .into()
3576    }
3577
3578    #[inline]
3579    fn abs_path_to_repo_path_inner(
3580        work_directory_abs_path: &Path,
3581        abs_path: &Path,
3582        path_style: PathStyle,
3583    ) -> Option<RepoPath> {
3584        let rel_path = path_style.strip_prefix(abs_path, work_directory_abs_path)?;
3585        Some(RepoPath::from_rel_path(&rel_path))
3586    }
3587
3588    pub fn had_conflict_on_last_merge_head_change(&self, repo_path: &RepoPath) -> bool {
3589        self.merge.conflicted_paths.contains(repo_path)
3590    }
3591
3592    pub fn has_conflict(&self, repo_path: &RepoPath) -> bool {
3593        let had_conflict_on_last_merge_head_change =
3594            self.merge.conflicted_paths.contains(repo_path);
3595        let has_conflict_currently = self
3596            .status_for_path(repo_path)
3597            .is_some_and(|entry| entry.status.is_conflicted());
3598        had_conflict_on_last_merge_head_change || has_conflict_currently
3599    }
3600
3601    /// This is the name that will be displayed in the repository selector for this repository.
3602    pub fn display_name(&self) -> SharedString {
3603        self.work_directory_abs_path
3604            .file_name()
3605            .unwrap_or_default()
3606            .to_string_lossy()
3607            .to_string()
3608            .into()
3609    }
3610}
3611
3612pub fn stash_to_proto(entry: &StashEntry) -> proto::StashEntry {
3613    proto::StashEntry {
3614        oid: entry.oid.as_bytes().to_vec(),
3615        message: entry.message.clone(),
3616        branch: entry.branch.clone(),
3617        index: entry.index as u64,
3618        timestamp: entry.timestamp,
3619    }
3620}
3621
3622pub fn proto_to_stash(entry: &proto::StashEntry) -> Result<StashEntry> {
3623    Ok(StashEntry {
3624        oid: Oid::from_bytes(&entry.oid)?,
3625        message: entry.message.clone(),
3626        index: entry.index as usize,
3627        branch: entry.branch.clone(),
3628        timestamp: entry.timestamp,
3629    })
3630}
3631
3632impl MergeDetails {
3633    async fn load(
3634        backend: &Arc<dyn GitRepository>,
3635        status: &SumTree<StatusEntry>,
3636        prev_snapshot: &RepositorySnapshot,
3637    ) -> Result<(MergeDetails, bool)> {
3638        log::debug!("load merge details");
3639        let message = backend.merge_message().await;
3640        let heads = backend
3641            .revparse_batch(vec![
3642                "MERGE_HEAD".into(),
3643                "CHERRY_PICK_HEAD".into(),
3644                "REBASE_HEAD".into(),
3645                "REVERT_HEAD".into(),
3646                "APPLY_HEAD".into(),
3647            ])
3648            .await
3649            .log_err()
3650            .unwrap_or_default()
3651            .into_iter()
3652            .map(|opt| opt.map(SharedString::from))
3653            .collect::<Vec<_>>();
3654        let merge_heads_changed = heads != prev_snapshot.merge.heads;
3655        let conflicted_paths = if merge_heads_changed {
3656            let current_conflicted_paths = TreeSet::from_ordered_entries(
3657                status
3658                    .iter()
3659                    .filter(|entry| entry.status.is_conflicted())
3660                    .map(|entry| entry.repo_path.clone()),
3661            );
3662
3663            // It can happen that we run a scan while a lengthy merge is in progress
3664            // that will eventually result in conflicts, but before those conflicts
3665            // are reported by `git status`. Since for the moment we only care about
3666            // the merge heads state for the purposes of tracking conflicts, don't update
3667            // this state until we see some conflicts.
3668            if heads.iter().any(Option::is_some)
3669                && !prev_snapshot.merge.heads.iter().any(Option::is_some)
3670                && current_conflicted_paths.is_empty()
3671            {
3672                log::debug!("not updating merge heads because no conflicts found");
3673                return Ok((
3674                    MergeDetails {
3675                        message: message.map(SharedString::from),
3676                        ..prev_snapshot.merge.clone()
3677                    },
3678                    false,
3679                ));
3680            }
3681
3682            current_conflicted_paths
3683        } else {
3684            prev_snapshot.merge.conflicted_paths.clone()
3685        };
3686        let details = MergeDetails {
3687            conflicted_paths,
3688            message: message.map(SharedString::from),
3689            heads,
3690        };
3691        Ok((details, merge_heads_changed))
3692    }
3693}
3694
3695impl Repository {
3696    pub fn snapshot(&self) -> RepositorySnapshot {
3697        self.snapshot.clone()
3698    }
3699
3700    pub fn pending_ops(&self) -> impl Iterator<Item = PendingOps> + '_ {
3701        self.pending_ops.iter().cloned()
3702    }
3703
3704    pub fn pending_ops_summary(&self) -> PathSummary<PendingOpsSummary> {
3705        self.pending_ops.summary().clone()
3706    }
3707
3708    pub fn pending_ops_for_path(&self, path: &RepoPath) -> Option<PendingOps> {
3709        self.pending_ops
3710            .get(&PathKey(path.as_ref().clone()), ())
3711            .cloned()
3712    }
3713
3714    fn local(
3715        id: RepositoryId,
3716        work_directory_abs_path: Arc<Path>,
3717        dot_git_abs_path: Arc<Path>,
3718        project_environment: WeakEntity<ProjectEnvironment>,
3719        fs: Arc<dyn Fs>,
3720        git_store: WeakEntity<GitStore>,
3721        cx: &mut Context<Self>,
3722    ) -> Self {
3723        let snapshot =
3724            RepositorySnapshot::empty(id, work_directory_abs_path.clone(), PathStyle::local());
3725        let state = cx
3726            .spawn(async move |_, cx| {
3727                LocalRepositoryState::new(
3728                    work_directory_abs_path,
3729                    dot_git_abs_path,
3730                    project_environment,
3731                    fs,
3732                    cx,
3733                )
3734                .await
3735                .map_err(|err| err.to_string())
3736            })
3737            .shared();
3738        let job_sender = Repository::spawn_local_git_worker(state.clone(), cx);
3739        let state = cx
3740            .spawn(async move |_, _| {
3741                let state = state.await?;
3742                Ok(RepositoryState::Local(state))
3743            })
3744            .shared();
3745
3746        cx.subscribe_self(|this, event: &RepositoryEvent, _| match event {
3747            RepositoryEvent::BranchChanged | RepositoryEvent::MergeHeadsChanged => {
3748                this.initial_graph_data.clear();
3749            }
3750            _ => {}
3751        })
3752        .detach();
3753
3754        Repository {
3755            this: cx.weak_entity(),
3756            git_store,
3757            snapshot,
3758            pending_ops: Default::default(),
3759            repository_state: state,
3760            commit_message_buffer: None,
3761            askpass_delegates: Default::default(),
3762            paths_needing_status_update: Default::default(),
3763            latest_askpass_id: 0,
3764            job_sender,
3765            job_id: 0,
3766            active_jobs: Default::default(),
3767            initial_graph_data: Default::default(),
3768            commit_data: Default::default(),
3769            graph_commit_data_handler: GraphCommitHandlerState::Closed,
3770        }
3771    }
3772
3773    fn remote(
3774        id: RepositoryId,
3775        work_directory_abs_path: Arc<Path>,
3776        path_style: PathStyle,
3777        project_id: ProjectId,
3778        client: AnyProtoClient,
3779        git_store: WeakEntity<GitStore>,
3780        cx: &mut Context<Self>,
3781    ) -> Self {
3782        let snapshot = RepositorySnapshot::empty(id, work_directory_abs_path, path_style);
3783        let repository_state = RemoteRepositoryState { project_id, client };
3784        let job_sender = Self::spawn_remote_git_worker(repository_state.clone(), cx);
3785        let repository_state = Task::ready(Ok(RepositoryState::Remote(repository_state))).shared();
3786        Self {
3787            this: cx.weak_entity(),
3788            snapshot,
3789            commit_message_buffer: None,
3790            git_store,
3791            pending_ops: Default::default(),
3792            paths_needing_status_update: Default::default(),
3793            job_sender,
3794            repository_state,
3795            askpass_delegates: Default::default(),
3796            latest_askpass_id: 0,
3797            active_jobs: Default::default(),
3798            job_id: 0,
3799            initial_graph_data: Default::default(),
3800            commit_data: Default::default(),
3801            graph_commit_data_handler: GraphCommitHandlerState::Closed,
3802        }
3803    }
3804
3805    pub fn git_store(&self) -> Option<Entity<GitStore>> {
3806        self.git_store.upgrade()
3807    }
3808
3809    fn reload_buffer_diff_bases(&mut self, cx: &mut Context<Self>) {
3810        let this = cx.weak_entity();
3811        let git_store = self.git_store.clone();
3812        let _ = self.send_keyed_job(
3813            Some(GitJobKey::ReloadBufferDiffBases),
3814            None,
3815            |state, mut cx| async move {
3816                let RepositoryState::Local(LocalRepositoryState { backend, .. }) = state else {
3817                    log::error!("tried to recompute diffs for a non-local repository");
3818                    return Ok(());
3819                };
3820
3821                let Some(this) = this.upgrade() else {
3822                    return Ok(());
3823                };
3824
3825                let repo_diff_state_updates = this.update(&mut cx, |this, cx| {
3826                    git_store.update(cx, |git_store, cx| {
3827                        git_store
3828                            .diffs
3829                            .iter()
3830                            .filter_map(|(buffer_id, diff_state)| {
3831                                let buffer_store = git_store.buffer_store.read(cx);
3832                                let buffer = buffer_store.get(*buffer_id)?;
3833                                let file = File::from_dyn(buffer.read(cx).file())?;
3834                                let abs_path = file.worktree.read(cx).absolutize(&file.path);
3835                                let repo_path = this.abs_path_to_repo_path(&abs_path)?;
3836                                log::debug!(
3837                                    "start reload diff bases for repo path {}",
3838                                    repo_path.as_unix_str()
3839                                );
3840                                diff_state.update(cx, |diff_state, _| {
3841                                    let has_unstaged_diff = diff_state
3842                                        .unstaged_diff
3843                                        .as_ref()
3844                                        .is_some_and(|diff| diff.is_upgradable());
3845                                    let has_uncommitted_diff = diff_state
3846                                        .uncommitted_diff
3847                                        .as_ref()
3848                                        .is_some_and(|set| set.is_upgradable());
3849
3850                                    Some((
3851                                        buffer,
3852                                        repo_path,
3853                                        has_unstaged_diff.then(|| diff_state.index_text.clone()),
3854                                        has_uncommitted_diff.then(|| diff_state.head_text.clone()),
3855                                    ))
3856                                })
3857                            })
3858                            .collect::<Vec<_>>()
3859                    })
3860                })?;
3861
3862                let buffer_diff_base_changes = cx
3863                    .background_spawn(async move {
3864                        let mut changes = Vec::new();
3865                        for (buffer, repo_path, current_index_text, current_head_text) in
3866                            &repo_diff_state_updates
3867                        {
3868                            let index_text = if current_index_text.is_some() {
3869                                backend.load_index_text(repo_path.clone()).await
3870                            } else {
3871                                None
3872                            };
3873                            let head_text = if current_head_text.is_some() {
3874                                backend.load_committed_text(repo_path.clone()).await
3875                            } else {
3876                                None
3877                            };
3878
3879                            let change =
3880                                match (current_index_text.as_ref(), current_head_text.as_ref()) {
3881                                    (Some(current_index), Some(current_head)) => {
3882                                        let index_changed =
3883                                            index_text.as_deref() != current_index.as_deref();
3884                                        let head_changed =
3885                                            head_text.as_deref() != current_head.as_deref();
3886                                        if index_changed && head_changed {
3887                                            if index_text == head_text {
3888                                                Some(DiffBasesChange::SetBoth(head_text))
3889                                            } else {
3890                                                Some(DiffBasesChange::SetEach {
3891                                                    index: index_text,
3892                                                    head: head_text,
3893                                                })
3894                                            }
3895                                        } else if index_changed {
3896                                            Some(DiffBasesChange::SetIndex(index_text))
3897                                        } else if head_changed {
3898                                            Some(DiffBasesChange::SetHead(head_text))
3899                                        } else {
3900                                            None
3901                                        }
3902                                    }
3903                                    (Some(current_index), None) => {
3904                                        let index_changed =
3905                                            index_text.as_deref() != current_index.as_deref();
3906                                        index_changed
3907                                            .then_some(DiffBasesChange::SetIndex(index_text))
3908                                    }
3909                                    (None, Some(current_head)) => {
3910                                        let head_changed =
3911                                            head_text.as_deref() != current_head.as_deref();
3912                                        head_changed.then_some(DiffBasesChange::SetHead(head_text))
3913                                    }
3914                                    (None, None) => None,
3915                                };
3916
3917                            changes.push((buffer.clone(), change))
3918                        }
3919                        changes
3920                    })
3921                    .await;
3922
3923                git_store.update(&mut cx, |git_store, cx| {
3924                    for (buffer, diff_bases_change) in buffer_diff_base_changes {
3925                        let buffer_snapshot = buffer.read(cx).text_snapshot();
3926                        let buffer_id = buffer_snapshot.remote_id();
3927                        let Some(diff_state) = git_store.diffs.get(&buffer_id) else {
3928                            continue;
3929                        };
3930
3931                        let downstream_client = git_store.downstream_client();
3932                        diff_state.update(cx, |diff_state, cx| {
3933                            use proto::update_diff_bases::Mode;
3934
3935                            if let Some((diff_bases_change, (client, project_id))) =
3936                                diff_bases_change.clone().zip(downstream_client)
3937                            {
3938                                let (staged_text, committed_text, mode) = match diff_bases_change {
3939                                    DiffBasesChange::SetIndex(index) => {
3940                                        (index, None, Mode::IndexOnly)
3941                                    }
3942                                    DiffBasesChange::SetHead(head) => (None, head, Mode::HeadOnly),
3943                                    DiffBasesChange::SetEach { index, head } => {
3944                                        (index, head, Mode::IndexAndHead)
3945                                    }
3946                                    DiffBasesChange::SetBoth(text) => {
3947                                        (None, text, Mode::IndexMatchesHead)
3948                                    }
3949                                };
3950                                client
3951                                    .send(proto::UpdateDiffBases {
3952                                        project_id: project_id.to_proto(),
3953                                        buffer_id: buffer_id.to_proto(),
3954                                        staged_text,
3955                                        committed_text,
3956                                        mode: mode as i32,
3957                                    })
3958                                    .log_err();
3959                            }
3960
3961                            diff_state.diff_bases_changed(buffer_snapshot, diff_bases_change, cx);
3962                        });
3963                    }
3964                })
3965            },
3966        );
3967    }
3968
3969    pub fn send_job<F, Fut, R>(
3970        &mut self,
3971        status: Option<SharedString>,
3972        job: F,
3973    ) -> oneshot::Receiver<R>
3974    where
3975        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
3976        Fut: Future<Output = R> + 'static,
3977        R: Send + 'static,
3978    {
3979        self.send_keyed_job(None, status, job)
3980    }
3981
3982    fn send_keyed_job<F, Fut, R>(
3983        &mut self,
3984        key: Option<GitJobKey>,
3985        status: Option<SharedString>,
3986        job: F,
3987    ) -> oneshot::Receiver<R>
3988    where
3989        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
3990        Fut: Future<Output = R> + 'static,
3991        R: Send + 'static,
3992    {
3993        let (result_tx, result_rx) = futures::channel::oneshot::channel();
3994        let job_id = post_inc(&mut self.job_id);
3995        let this = self.this.clone();
3996        self.job_sender
3997            .unbounded_send(GitJob {
3998                key,
3999                job: Box::new(move |state, cx: &mut AsyncApp| {
4000                    let job = job(state, cx.clone());
4001                    cx.spawn(async move |cx| {
4002                        if let Some(s) = status.clone() {
4003                            this.update(cx, |this, cx| {
4004                                this.active_jobs.insert(
4005                                    job_id,
4006                                    JobInfo {
4007                                        start: Instant::now(),
4008                                        message: s.clone(),
4009                                    },
4010                                );
4011
4012                                cx.notify();
4013                            })
4014                            .ok();
4015                        }
4016                        let result = job.await;
4017
4018                        this.update(cx, |this, cx| {
4019                            this.active_jobs.remove(&job_id);
4020                            cx.notify();
4021                        })
4022                        .ok();
4023
4024                        result_tx.send(result).ok();
4025                    })
4026                }),
4027            })
4028            .ok();
4029        result_rx
4030    }
4031
4032    pub fn set_as_active_repository(&self, cx: &mut Context<Self>) {
4033        let Some(git_store) = self.git_store.upgrade() else {
4034            return;
4035        };
4036        let entity = cx.entity();
4037        git_store.update(cx, |git_store, cx| {
4038            let Some((&id, _)) = git_store
4039                .repositories
4040                .iter()
4041                .find(|(_, handle)| *handle == &entity)
4042            else {
4043                return;
4044            };
4045            git_store.active_repo_id = Some(id);
4046            cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
4047        });
4048    }
4049
4050    pub fn cached_status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
4051        self.snapshot.status()
4052    }
4053
4054    pub fn cached_stash(&self) -> GitStash {
4055        self.snapshot.stash_entries.clone()
4056    }
4057
4058    pub fn repo_path_to_project_path(&self, path: &RepoPath, cx: &App) -> Option<ProjectPath> {
4059        let git_store = self.git_store.upgrade()?;
4060        let worktree_store = git_store.read(cx).worktree_store.read(cx);
4061        let abs_path = self.snapshot.repo_path_to_abs_path(path);
4062        let abs_path = SanitizedPath::new(&abs_path);
4063        let (worktree, relative_path) = worktree_store.find_worktree(abs_path, cx)?;
4064        Some(ProjectPath {
4065            worktree_id: worktree.read(cx).id(),
4066            path: relative_path,
4067        })
4068    }
4069
4070    pub fn project_path_to_repo_path(&self, path: &ProjectPath, cx: &App) -> Option<RepoPath> {
4071        let git_store = self.git_store.upgrade()?;
4072        let worktree_store = git_store.read(cx).worktree_store.read(cx);
4073        let abs_path = worktree_store.absolutize(path, cx)?;
4074        self.snapshot.abs_path_to_repo_path(&abs_path)
4075    }
4076
4077    pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
4078        other
4079            .read(cx)
4080            .snapshot
4081            .work_directory_abs_path
4082            .starts_with(&self.snapshot.work_directory_abs_path)
4083    }
4084
4085    pub fn open_commit_buffer(
4086        &mut self,
4087        languages: Option<Arc<LanguageRegistry>>,
4088        buffer_store: Entity<BufferStore>,
4089        cx: &mut Context<Self>,
4090    ) -> Task<Result<Entity<Buffer>>> {
4091        let id = self.id;
4092        if let Some(buffer) = self.commit_message_buffer.clone() {
4093            return Task::ready(Ok(buffer));
4094        }
4095        let this = cx.weak_entity();
4096
4097        let rx = self.send_job(None, move |state, mut cx| async move {
4098            let Some(this) = this.upgrade() else {
4099                bail!("git store was dropped");
4100            };
4101            match state {
4102                RepositoryState::Local(..) => {
4103                    this.update(&mut cx, |_, cx| {
4104                        Self::open_local_commit_buffer(languages, buffer_store, cx)
4105                    })
4106                    .await
4107                }
4108                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4109                    let request = client.request(proto::OpenCommitMessageBuffer {
4110                        project_id: project_id.0,
4111                        repository_id: id.to_proto(),
4112                    });
4113                    let response = request.await.context("requesting to open commit buffer")?;
4114                    let buffer_id = BufferId::new(response.buffer_id)?;
4115                    let buffer = buffer_store
4116                        .update(&mut cx, |buffer_store, cx| {
4117                            buffer_store.wait_for_remote_buffer(buffer_id, cx)
4118                        })
4119                        .await?;
4120                    if let Some(language_registry) = languages {
4121                        let git_commit_language =
4122                            language_registry.language_for_name("Git Commit").await?;
4123                        buffer.update(&mut cx, |buffer, cx| {
4124                            buffer.set_language(Some(git_commit_language), cx);
4125                        });
4126                    }
4127                    this.update(&mut cx, |this, _| {
4128                        this.commit_message_buffer = Some(buffer.clone());
4129                    });
4130                    Ok(buffer)
4131                }
4132            }
4133        });
4134
4135        cx.spawn(|_, _: &mut AsyncApp| async move { rx.await? })
4136    }
4137
4138    fn open_local_commit_buffer(
4139        language_registry: Option<Arc<LanguageRegistry>>,
4140        buffer_store: Entity<BufferStore>,
4141        cx: &mut Context<Self>,
4142    ) -> Task<Result<Entity<Buffer>>> {
4143        cx.spawn(async move |repository, cx| {
4144            let git_commit_language = match language_registry {
4145                Some(language_registry) => {
4146                    Some(language_registry.language_for_name("Git Commit").await?)
4147                }
4148                None => None,
4149            };
4150            let buffer = buffer_store
4151                .update(cx, |buffer_store, cx| {
4152                    buffer_store.create_buffer(git_commit_language, false, cx)
4153                })
4154                .await?;
4155
4156            repository.update(cx, |repository, _| {
4157                repository.commit_message_buffer = Some(buffer.clone());
4158            })?;
4159            Ok(buffer)
4160        })
4161    }
4162
4163    pub fn checkout_files(
4164        &mut self,
4165        commit: &str,
4166        paths: Vec<RepoPath>,
4167        cx: &mut Context<Self>,
4168    ) -> Task<Result<()>> {
4169        let commit = commit.to_string();
4170        let id = self.id;
4171
4172        self.spawn_job_with_tracking(
4173            paths.clone(),
4174            pending_op::GitStatus::Reverted,
4175            cx,
4176            async move |this, cx| {
4177                this.update(cx, |this, _cx| {
4178                    this.send_job(
4179                        Some(format!("git checkout {}", commit).into()),
4180                        move |git_repo, _| async move {
4181                            match git_repo {
4182                                RepositoryState::Local(LocalRepositoryState {
4183                                    backend,
4184                                    environment,
4185                                    ..
4186                                }) => {
4187                                    backend
4188                                        .checkout_files(commit, paths, environment.clone())
4189                                        .await
4190                                }
4191                                RepositoryState::Remote(RemoteRepositoryState {
4192                                    project_id,
4193                                    client,
4194                                }) => {
4195                                    client
4196                                        .request(proto::GitCheckoutFiles {
4197                                            project_id: project_id.0,
4198                                            repository_id: id.to_proto(),
4199                                            commit,
4200                                            paths: paths
4201                                                .into_iter()
4202                                                .map(|p| p.to_proto())
4203                                                .collect(),
4204                                        })
4205                                        .await?;
4206
4207                                    Ok(())
4208                                }
4209                            }
4210                        },
4211                    )
4212                })?
4213                .await?
4214            },
4215        )
4216    }
4217
4218    pub fn reset(
4219        &mut self,
4220        commit: String,
4221        reset_mode: ResetMode,
4222        _cx: &mut App,
4223    ) -> oneshot::Receiver<Result<()>> {
4224        let id = self.id;
4225
4226        self.send_job(None, move |git_repo, _| async move {
4227            match git_repo {
4228                RepositoryState::Local(LocalRepositoryState {
4229                    backend,
4230                    environment,
4231                    ..
4232                }) => backend.reset(commit, reset_mode, environment).await,
4233                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4234                    client
4235                        .request(proto::GitReset {
4236                            project_id: project_id.0,
4237                            repository_id: id.to_proto(),
4238                            commit,
4239                            mode: match reset_mode {
4240                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
4241                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
4242                            },
4243                        })
4244                        .await?;
4245
4246                    Ok(())
4247                }
4248            }
4249        })
4250    }
4251
4252    pub fn show(&mut self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
4253        let id = self.id;
4254        self.send_job(None, move |git_repo, _cx| async move {
4255            match git_repo {
4256                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
4257                    backend.show(commit).await
4258                }
4259                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4260                    let resp = client
4261                        .request(proto::GitShow {
4262                            project_id: project_id.0,
4263                            repository_id: id.to_proto(),
4264                            commit,
4265                        })
4266                        .await?;
4267
4268                    Ok(CommitDetails {
4269                        sha: resp.sha.into(),
4270                        message: resp.message.into(),
4271                        commit_timestamp: resp.commit_timestamp,
4272                        author_email: resp.author_email.into(),
4273                        author_name: resp.author_name.into(),
4274                    })
4275                }
4276            }
4277        })
4278    }
4279
4280    pub fn load_commit_diff(&mut self, commit: String) -> oneshot::Receiver<Result<CommitDiff>> {
4281        let id = self.id;
4282        self.send_job(None, move |git_repo, cx| async move {
4283            match git_repo {
4284                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
4285                    backend.load_commit(commit, cx).await
4286                }
4287                RepositoryState::Remote(RemoteRepositoryState {
4288                    client, project_id, ..
4289                }) => {
4290                    let response = client
4291                        .request(proto::LoadCommitDiff {
4292                            project_id: project_id.0,
4293                            repository_id: id.to_proto(),
4294                            commit,
4295                        })
4296                        .await?;
4297                    Ok(CommitDiff {
4298                        files: response
4299                            .files
4300                            .into_iter()
4301                            .map(|file| {
4302                                Ok(CommitFile {
4303                                    path: RepoPath::from_proto(&file.path)?,
4304                                    old_text: file.old_text,
4305                                    new_text: file.new_text,
4306                                    is_binary: file.is_binary,
4307                                })
4308                            })
4309                            .collect::<Result<Vec<_>>>()?,
4310                    })
4311                }
4312            }
4313        })
4314    }
4315
4316    pub fn file_history(
4317        &mut self,
4318        path: RepoPath,
4319    ) -> oneshot::Receiver<Result<git::repository::FileHistory>> {
4320        self.file_history_paginated(path, 0, None)
4321    }
4322
4323    pub fn file_history_paginated(
4324        &mut self,
4325        path: RepoPath,
4326        skip: usize,
4327        limit: Option<usize>,
4328    ) -> oneshot::Receiver<Result<git::repository::FileHistory>> {
4329        let id = self.id;
4330        self.send_job(None, move |git_repo, _cx| async move {
4331            match git_repo {
4332                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
4333                    backend.file_history_paginated(path, skip, limit).await
4334                }
4335                RepositoryState::Remote(RemoteRepositoryState { client, project_id }) => {
4336                    let response = client
4337                        .request(proto::GitFileHistory {
4338                            project_id: project_id.0,
4339                            repository_id: id.to_proto(),
4340                            path: path.to_proto(),
4341                            skip: skip as u64,
4342                            limit: limit.map(|l| l as u64),
4343                        })
4344                        .await?;
4345                    Ok(git::repository::FileHistory {
4346                        entries: response
4347                            .entries
4348                            .into_iter()
4349                            .map(|entry| git::repository::FileHistoryEntry {
4350                                sha: entry.sha.into(),
4351                                subject: entry.subject.into(),
4352                                message: entry.message.into(),
4353                                commit_timestamp: entry.commit_timestamp,
4354                                author_name: entry.author_name.into(),
4355                                author_email: entry.author_email.into(),
4356                            })
4357                            .collect(),
4358                        path: RepoPath::from_proto(&response.path)?,
4359                    })
4360                }
4361            }
4362        })
4363    }
4364
4365    pub fn graph_data(
4366        &mut self,
4367        log_source: LogSource,
4368        log_order: LogOrder,
4369        range: Range<usize>,
4370        cx: &mut Context<Self>,
4371    ) -> (&[Arc<InitialGraphCommitData>], bool) {
4372        let (loading_task, initial_commit_data) = self
4373            .initial_graph_data
4374            .entry((log_order, log_source.clone()))
4375            .or_insert_with(|| {
4376                let state = self.repository_state.clone();
4377                let log_source = log_source.clone();
4378                (
4379                    cx.spawn(async move |repository, cx| {
4380                        let state = state.await;
4381                        match state {
4382                            Ok(RepositoryState::Local(LocalRepositoryState {
4383                                backend, ..
4384                            })) => {
4385                                Self::local_git_graph_data(
4386                                    repository, backend, log_source, log_order, cx,
4387                                )
4388                                .await
4389                            }
4390                            Ok(RepositoryState::Remote(_)) => {
4391                                Err("Git graph is not supported for collab yet".into())
4392                            }
4393                            Err(e) => Err(SharedString::from(e)),
4394                        }
4395                    }),
4396                    vec![],
4397                )
4398            });
4399
4400        let max_start = initial_commit_data.len().saturating_sub(1);
4401        let max_end = initial_commit_data.len();
4402        (
4403            &initial_commit_data[range.start.min(max_start)..range.end.min(max_end)],
4404            !loading_task.is_ready(),
4405        )
4406    }
4407
4408    async fn local_git_graph_data(
4409        this: WeakEntity<Self>,
4410        backend: Arc<dyn GitRepository>,
4411        log_source: LogSource,
4412        log_order: LogOrder,
4413        cx: &mut AsyncApp,
4414    ) -> Result<(), SharedString> {
4415        let (request_tx, request_rx) =
4416            smol::channel::unbounded::<Vec<Arc<InitialGraphCommitData>>>();
4417
4418        let task = cx.background_executor().spawn({
4419            let log_source = log_source.clone();
4420            async move {
4421                backend
4422                    .initial_graph_data(log_source, log_order, request_tx)
4423                    .await
4424                    .map_err(|err| SharedString::from(err.to_string()))
4425            }
4426        });
4427
4428        let graph_data_key = (log_order, log_source.clone());
4429
4430        while let Ok(initial_graph_commit_data) = request_rx.recv().await {
4431            this.update(cx, |repository, cx| {
4432                let graph_data = repository
4433                    .initial_graph_data
4434                    .get_mut(&graph_data_key)
4435                    .map(|(_, graph_data)| graph_data);
4436                debug_assert!(
4437                    graph_data.is_some(),
4438                    "This task should be dropped if data doesn't exist"
4439                );
4440
4441                if let Some(graph_data) = graph_data {
4442                    graph_data.extend(initial_graph_commit_data);
4443                    cx.emit(RepositoryEvent::GitGraphCountUpdated(
4444                        graph_data_key.clone(),
4445                        graph_data.len(),
4446                    ));
4447                }
4448            })
4449            .ok();
4450        }
4451
4452        task.await?;
4453
4454        Ok(())
4455    }
4456
4457    pub fn fetch_commit_data(&mut self, sha: Oid, cx: &mut Context<Self>) -> &CommitDataState {
4458        if !self.commit_data.contains_key(&sha) {
4459            match &self.graph_commit_data_handler {
4460                GraphCommitHandlerState::Open(handler) => {
4461                    if handler.commit_data_request.try_send(sha).is_ok() {
4462                        let old_value = self.commit_data.insert(sha, CommitDataState::Loading);
4463                        debug_assert!(old_value.is_none(), "We should never overwrite commit data");
4464                    }
4465                }
4466                GraphCommitHandlerState::Closed => {
4467                    self.open_graph_commit_data_handler(cx);
4468                }
4469                GraphCommitHandlerState::Starting => {}
4470            }
4471        }
4472
4473        self.commit_data
4474            .get(&sha)
4475            .unwrap_or(&CommitDataState::Loading)
4476    }
4477
4478    fn open_graph_commit_data_handler(&mut self, cx: &mut Context<Self>) {
4479        self.graph_commit_data_handler = GraphCommitHandlerState::Starting;
4480
4481        let state = self.repository_state.clone();
4482        let (result_tx, result_rx) = smol::channel::bounded::<(Oid, GraphCommitData)>(64);
4483        let (request_tx, request_rx) = smol::channel::unbounded::<Oid>();
4484
4485        let foreground_task = cx.spawn(async move |this, cx| {
4486            while let Ok((sha, commit_data)) = result_rx.recv().await {
4487                let result = this.update(cx, |this, cx| {
4488                    let old_value = this
4489                        .commit_data
4490                        .insert(sha, CommitDataState::Loaded(Arc::new(commit_data)));
4491                    debug_assert!(
4492                        !matches!(old_value, Some(CommitDataState::Loaded(_))),
4493                        "We should never overwrite commit data"
4494                    );
4495
4496                    cx.notify();
4497                });
4498                if result.is_err() {
4499                    break;
4500                }
4501            }
4502
4503            this.update(cx, |this, _cx| {
4504                this.graph_commit_data_handler = GraphCommitHandlerState::Closed;
4505            })
4506            .ok();
4507        });
4508
4509        let request_tx_for_handler = request_tx;
4510        let background_executor = cx.background_executor().clone();
4511
4512        cx.background_spawn(async move {
4513            let backend = match state.await {
4514                Ok(RepositoryState::Local(LocalRepositoryState { backend, .. })) => backend,
4515                Ok(RepositoryState::Remote(_)) => {
4516                    log::error!("commit_data_reader not supported for remote repositories");
4517                    return;
4518                }
4519                Err(error) => {
4520                    log::error!("failed to get repository state: {error}");
4521                    return;
4522                }
4523            };
4524
4525            let reader = match backend.commit_data_reader() {
4526                Ok(reader) => reader,
4527                Err(error) => {
4528                    log::error!("failed to create commit data reader: {error:?}");
4529                    return;
4530                }
4531            };
4532
4533            loop {
4534                let timeout = background_executor.timer(std::time::Duration::from_secs(10));
4535
4536                futures::select_biased! {
4537                    sha = futures::FutureExt::fuse(request_rx.recv()) => {
4538                        let Ok(sha) = sha else {
4539                            break;
4540                        };
4541
4542                        match reader.read(sha).await {
4543                            Ok(commit_data) => {
4544                                if result_tx.send((sha, commit_data)).await.is_err() {
4545                                    break;
4546                                }
4547                            }
4548                            Err(error) => {
4549                                log::error!("failed to read commit data for {sha}: {error:?}");
4550                            }
4551                        }
4552                    }
4553                    _ = futures::FutureExt::fuse(timeout) => {
4554                        break;
4555                    }
4556                }
4557            }
4558
4559            drop(result_tx);
4560        })
4561        .detach();
4562
4563        self.graph_commit_data_handler = GraphCommitHandlerState::Open(GraphCommitDataHandler {
4564            _task: foreground_task,
4565            commit_data_request: request_tx_for_handler,
4566        });
4567    }
4568
4569    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
4570        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
4571    }
4572
4573    fn save_buffers<'a>(
4574        &self,
4575        entries: impl IntoIterator<Item = &'a RepoPath>,
4576        cx: &mut Context<Self>,
4577    ) -> Vec<Task<anyhow::Result<()>>> {
4578        let mut save_futures = Vec::new();
4579        if let Some(buffer_store) = self.buffer_store(cx) {
4580            buffer_store.update(cx, |buffer_store, cx| {
4581                for path in entries {
4582                    let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
4583                        continue;
4584                    };
4585                    if let Some(buffer) = buffer_store.get_by_path(&project_path)
4586                        && buffer
4587                            .read(cx)
4588                            .file()
4589                            .is_some_and(|file| file.disk_state().exists())
4590                        && buffer.read(cx).has_unsaved_edits()
4591                    {
4592                        save_futures.push(buffer_store.save_buffer(buffer, cx));
4593                    }
4594                }
4595            })
4596        }
4597        save_futures
4598    }
4599
4600    pub fn stage_entries(
4601        &mut self,
4602        entries: Vec<RepoPath>,
4603        cx: &mut Context<Self>,
4604    ) -> Task<anyhow::Result<()>> {
4605        self.stage_or_unstage_entries(true, entries, cx)
4606    }
4607
4608    pub fn unstage_entries(
4609        &mut self,
4610        entries: Vec<RepoPath>,
4611        cx: &mut Context<Self>,
4612    ) -> Task<anyhow::Result<()>> {
4613        self.stage_or_unstage_entries(false, entries, cx)
4614    }
4615
4616    fn stage_or_unstage_entries(
4617        &mut self,
4618        stage: bool,
4619        entries: Vec<RepoPath>,
4620        cx: &mut Context<Self>,
4621    ) -> Task<anyhow::Result<()>> {
4622        if entries.is_empty() {
4623            return Task::ready(Ok(()));
4624        }
4625        let Some(git_store) = self.git_store.upgrade() else {
4626            return Task::ready(Ok(()));
4627        };
4628        let id = self.id;
4629        let save_tasks = self.save_buffers(&entries, cx);
4630        let paths = entries
4631            .iter()
4632            .map(|p| p.as_unix_str())
4633            .collect::<Vec<_>>()
4634            .join(" ");
4635        let status = if stage {
4636            format!("git add {paths}")
4637        } else {
4638            format!("git reset {paths}")
4639        };
4640        let job_key = GitJobKey::WriteIndex(entries.clone());
4641
4642        self.spawn_job_with_tracking(
4643            entries.clone(),
4644            if stage {
4645                pending_op::GitStatus::Staged
4646            } else {
4647                pending_op::GitStatus::Unstaged
4648            },
4649            cx,
4650            async move |this, cx| {
4651                for save_task in save_tasks {
4652                    save_task.await?;
4653                }
4654
4655                this.update(cx, |this, cx| {
4656                    let weak_this = cx.weak_entity();
4657                    this.send_keyed_job(
4658                        Some(job_key),
4659                        Some(status.into()),
4660                        move |git_repo, mut cx| async move {
4661                            let hunk_staging_operation_counts = weak_this
4662                                .update(&mut cx, |this, cx| {
4663                                    let mut hunk_staging_operation_counts = HashMap::default();
4664                                    for path in &entries {
4665                                        let Some(project_path) =
4666                                            this.repo_path_to_project_path(path, cx)
4667                                        else {
4668                                            continue;
4669                                        };
4670                                        let Some(buffer) = git_store
4671                                            .read(cx)
4672                                            .buffer_store
4673                                            .read(cx)
4674                                            .get_by_path(&project_path)
4675                                        else {
4676                                            continue;
4677                                        };
4678                                        let Some(diff_state) = git_store
4679                                            .read(cx)
4680                                            .diffs
4681                                            .get(&buffer.read(cx).remote_id())
4682                                            .cloned()
4683                                        else {
4684                                            continue;
4685                                        };
4686                                        let Some(uncommitted_diff) =
4687                                            diff_state.read(cx).uncommitted_diff.as_ref().and_then(
4688                                                |uncommitted_diff| uncommitted_diff.upgrade(),
4689                                            )
4690                                        else {
4691                                            continue;
4692                                        };
4693                                        let buffer_snapshot = buffer.read(cx).text_snapshot();
4694                                        let file_exists = buffer
4695                                            .read(cx)
4696                                            .file()
4697                                            .is_some_and(|file| file.disk_state().exists());
4698                                        let hunk_staging_operation_count =
4699                                            diff_state.update(cx, |diff_state, cx| {
4700                                                uncommitted_diff.update(
4701                                                    cx,
4702                                                    |uncommitted_diff, cx| {
4703                                                        uncommitted_diff
4704                                                            .stage_or_unstage_all_hunks(
4705                                                                stage,
4706                                                                &buffer_snapshot,
4707                                                                file_exists,
4708                                                                cx,
4709                                                            );
4710                                                    },
4711                                                );
4712
4713                                                diff_state.hunk_staging_operation_count += 1;
4714                                                diff_state.hunk_staging_operation_count
4715                                            });
4716                                        hunk_staging_operation_counts.insert(
4717                                            diff_state.downgrade(),
4718                                            hunk_staging_operation_count,
4719                                        );
4720                                    }
4721                                    hunk_staging_operation_counts
4722                                })
4723                                .unwrap_or_default();
4724
4725                            let result = match git_repo {
4726                                RepositoryState::Local(LocalRepositoryState {
4727                                    backend,
4728                                    environment,
4729                                    ..
4730                                }) => {
4731                                    if stage {
4732                                        backend.stage_paths(entries, environment.clone()).await
4733                                    } else {
4734                                        backend.unstage_paths(entries, environment.clone()).await
4735                                    }
4736                                }
4737                                RepositoryState::Remote(RemoteRepositoryState {
4738                                    project_id,
4739                                    client,
4740                                }) => {
4741                                    if stage {
4742                                        client
4743                                            .request(proto::Stage {
4744                                                project_id: project_id.0,
4745                                                repository_id: id.to_proto(),
4746                                                paths: entries
4747                                                    .into_iter()
4748                                                    .map(|repo_path| repo_path.to_proto())
4749                                                    .collect(),
4750                                            })
4751                                            .await
4752                                            .context("sending stage request")
4753                                            .map(|_| ())
4754                                    } else {
4755                                        client
4756                                            .request(proto::Unstage {
4757                                                project_id: project_id.0,
4758                                                repository_id: id.to_proto(),
4759                                                paths: entries
4760                                                    .into_iter()
4761                                                    .map(|repo_path| repo_path.to_proto())
4762                                                    .collect(),
4763                                            })
4764                                            .await
4765                                            .context("sending unstage request")
4766                                            .map(|_| ())
4767                                    }
4768                                }
4769                            };
4770
4771                            for (diff_state, hunk_staging_operation_count) in
4772                                hunk_staging_operation_counts
4773                            {
4774                                diff_state
4775                                    .update(&mut cx, |diff_state, cx| {
4776                                        if result.is_ok() {
4777                                            diff_state.hunk_staging_operation_count_as_of_write =
4778                                                hunk_staging_operation_count;
4779                                        } else if let Some(uncommitted_diff) =
4780                                            &diff_state.uncommitted_diff
4781                                        {
4782                                            uncommitted_diff
4783                                                .update(cx, |uncommitted_diff, cx| {
4784                                                    uncommitted_diff.clear_pending_hunks(cx);
4785                                                })
4786                                                .ok();
4787                                        }
4788                                    })
4789                                    .ok();
4790                            }
4791
4792                            result
4793                        },
4794                    )
4795                })?
4796                .await?
4797            },
4798        )
4799    }
4800
4801    pub fn stage_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
4802        let to_stage = self
4803            .cached_status()
4804            .filter_map(|entry| {
4805                if let Some(ops) = self.pending_ops_for_path(&entry.repo_path) {
4806                    if ops.staging() || ops.staged() {
4807                        None
4808                    } else {
4809                        Some(entry.repo_path)
4810                    }
4811                } else if entry.status.staging().is_fully_staged() {
4812                    None
4813                } else {
4814                    Some(entry.repo_path)
4815                }
4816            })
4817            .collect();
4818        self.stage_or_unstage_entries(true, to_stage, cx)
4819    }
4820
4821    pub fn unstage_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
4822        let to_unstage = self
4823            .cached_status()
4824            .filter_map(|entry| {
4825                if let Some(ops) = self.pending_ops_for_path(&entry.repo_path) {
4826                    if !ops.staging() && !ops.staged() {
4827                        None
4828                    } else {
4829                        Some(entry.repo_path)
4830                    }
4831                } else if entry.status.staging().is_fully_unstaged() {
4832                    None
4833                } else {
4834                    Some(entry.repo_path)
4835                }
4836            })
4837            .collect();
4838        self.stage_or_unstage_entries(false, to_unstage, cx)
4839    }
4840
4841    pub fn stash_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
4842        let to_stash = self.cached_status().map(|entry| entry.repo_path).collect();
4843
4844        self.stash_entries(to_stash, cx)
4845    }
4846
4847    pub fn stash_entries(
4848        &mut self,
4849        entries: Vec<RepoPath>,
4850        cx: &mut Context<Self>,
4851    ) -> Task<anyhow::Result<()>> {
4852        let id = self.id;
4853
4854        cx.spawn(async move |this, cx| {
4855            this.update(cx, |this, _| {
4856                this.send_job(None, move |git_repo, _cx| async move {
4857                    match git_repo {
4858                        RepositoryState::Local(LocalRepositoryState {
4859                            backend,
4860                            environment,
4861                            ..
4862                        }) => backend.stash_paths(entries, environment).await,
4863                        RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4864                            client
4865                                .request(proto::Stash {
4866                                    project_id: project_id.0,
4867                                    repository_id: id.to_proto(),
4868                                    paths: entries
4869                                        .into_iter()
4870                                        .map(|repo_path| repo_path.to_proto())
4871                                        .collect(),
4872                                })
4873                                .await
4874                                .context("sending stash request")?;
4875                            Ok(())
4876                        }
4877                    }
4878                })
4879            })?
4880            .await??;
4881            Ok(())
4882        })
4883    }
4884
4885    pub fn stash_pop(
4886        &mut self,
4887        index: Option<usize>,
4888        cx: &mut Context<Self>,
4889    ) -> Task<anyhow::Result<()>> {
4890        let id = self.id;
4891        cx.spawn(async move |this, cx| {
4892            this.update(cx, |this, _| {
4893                this.send_job(None, move |git_repo, _cx| async move {
4894                    match git_repo {
4895                        RepositoryState::Local(LocalRepositoryState {
4896                            backend,
4897                            environment,
4898                            ..
4899                        }) => backend.stash_pop(index, environment).await,
4900                        RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4901                            client
4902                                .request(proto::StashPop {
4903                                    project_id: project_id.0,
4904                                    repository_id: id.to_proto(),
4905                                    stash_index: index.map(|i| i as u64),
4906                                })
4907                                .await
4908                                .context("sending stash pop request")?;
4909                            Ok(())
4910                        }
4911                    }
4912                })
4913            })?
4914            .await??;
4915            Ok(())
4916        })
4917    }
4918
4919    pub fn stash_apply(
4920        &mut self,
4921        index: Option<usize>,
4922        cx: &mut Context<Self>,
4923    ) -> Task<anyhow::Result<()>> {
4924        let id = self.id;
4925        cx.spawn(async move |this, cx| {
4926            this.update(cx, |this, _| {
4927                this.send_job(None, move |git_repo, _cx| async move {
4928                    match git_repo {
4929                        RepositoryState::Local(LocalRepositoryState {
4930                            backend,
4931                            environment,
4932                            ..
4933                        }) => backend.stash_apply(index, environment).await,
4934                        RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4935                            client
4936                                .request(proto::StashApply {
4937                                    project_id: project_id.0,
4938                                    repository_id: id.to_proto(),
4939                                    stash_index: index.map(|i| i as u64),
4940                                })
4941                                .await
4942                                .context("sending stash apply request")?;
4943                            Ok(())
4944                        }
4945                    }
4946                })
4947            })?
4948            .await??;
4949            Ok(())
4950        })
4951    }
4952
4953    pub fn stash_drop(
4954        &mut self,
4955        index: Option<usize>,
4956        cx: &mut Context<Self>,
4957    ) -> oneshot::Receiver<anyhow::Result<()>> {
4958        let id = self.id;
4959        let updates_tx = self
4960            .git_store()
4961            .and_then(|git_store| match &git_store.read(cx).state {
4962                GitStoreState::Local { downstream, .. } => downstream
4963                    .as_ref()
4964                    .map(|downstream| downstream.updates_tx.clone()),
4965                _ => None,
4966            });
4967        let this = cx.weak_entity();
4968        self.send_job(None, move |git_repo, mut cx| async move {
4969            match git_repo {
4970                RepositoryState::Local(LocalRepositoryState {
4971                    backend,
4972                    environment,
4973                    ..
4974                }) => {
4975                    // TODO would be nice to not have to do this manually
4976                    let result = backend.stash_drop(index, environment).await;
4977                    if result.is_ok()
4978                        && let Ok(stash_entries) = backend.stash_entries().await
4979                    {
4980                        let snapshot = this.update(&mut cx, |this, cx| {
4981                            this.snapshot.stash_entries = stash_entries;
4982                            cx.emit(RepositoryEvent::StashEntriesChanged);
4983                            this.snapshot.clone()
4984                        })?;
4985                        if let Some(updates_tx) = updates_tx {
4986                            updates_tx
4987                                .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
4988                                .ok();
4989                        }
4990                    }
4991
4992                    result
4993                }
4994                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4995                    client
4996                        .request(proto::StashDrop {
4997                            project_id: project_id.0,
4998                            repository_id: id.to_proto(),
4999                            stash_index: index.map(|i| i as u64),
5000                        })
5001                        .await
5002                        .context("sending stash pop request")?;
5003                    Ok(())
5004                }
5005            }
5006        })
5007    }
5008
5009    pub fn run_hook(&mut self, hook: RunHook, _cx: &mut App) -> oneshot::Receiver<Result<()>> {
5010        let id = self.id;
5011        self.send_job(
5012            Some(format!("git hook {}", hook.as_str()).into()),
5013            move |git_repo, _cx| async move {
5014                match git_repo {
5015                    RepositoryState::Local(LocalRepositoryState {
5016                        backend,
5017                        environment,
5018                        ..
5019                    }) => backend.run_hook(hook, environment.clone()).await,
5020                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5021                        client
5022                            .request(proto::RunGitHook {
5023                                project_id: project_id.0,
5024                                repository_id: id.to_proto(),
5025                                hook: hook.to_proto(),
5026                            })
5027                            .await?;
5028
5029                        Ok(())
5030                    }
5031                }
5032            },
5033        )
5034    }
5035
5036    pub fn commit(
5037        &mut self,
5038        message: SharedString,
5039        name_and_email: Option<(SharedString, SharedString)>,
5040        options: CommitOptions,
5041        askpass: AskPassDelegate,
5042        cx: &mut App,
5043    ) -> oneshot::Receiver<Result<()>> {
5044        let id = self.id;
5045        let askpass_delegates = self.askpass_delegates.clone();
5046        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5047
5048        let rx = self.run_hook(RunHook::PreCommit, cx);
5049
5050        self.send_job(Some("git commit".into()), move |git_repo, _cx| async move {
5051            rx.await??;
5052
5053            match git_repo {
5054                RepositoryState::Local(LocalRepositoryState {
5055                    backend,
5056                    environment,
5057                    ..
5058                }) => {
5059                    backend
5060                        .commit(message, name_and_email, options, askpass, environment)
5061                        .await
5062                }
5063                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5064                    askpass_delegates.lock().insert(askpass_id, askpass);
5065                    let _defer = util::defer(|| {
5066                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5067                        debug_assert!(askpass_delegate.is_some());
5068                    });
5069                    let (name, email) = name_and_email.unzip();
5070                    client
5071                        .request(proto::Commit {
5072                            project_id: project_id.0,
5073                            repository_id: id.to_proto(),
5074                            message: String::from(message),
5075                            name: name.map(String::from),
5076                            email: email.map(String::from),
5077                            options: Some(proto::commit::CommitOptions {
5078                                amend: options.amend,
5079                                signoff: options.signoff,
5080                            }),
5081                            askpass_id,
5082                        })
5083                        .await
5084                        .context("sending commit request")?;
5085
5086                    Ok(())
5087                }
5088            }
5089        })
5090    }
5091
5092    pub fn fetch(
5093        &mut self,
5094        fetch_options: FetchOptions,
5095        askpass: AskPassDelegate,
5096        _cx: &mut App,
5097    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
5098        let askpass_delegates = self.askpass_delegates.clone();
5099        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5100        let id = self.id;
5101
5102        self.send_job(Some("git fetch".into()), move |git_repo, cx| async move {
5103            match git_repo {
5104                RepositoryState::Local(LocalRepositoryState {
5105                    backend,
5106                    environment,
5107                    ..
5108                }) => backend.fetch(fetch_options, askpass, environment, cx).await,
5109                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5110                    askpass_delegates.lock().insert(askpass_id, askpass);
5111                    let _defer = util::defer(|| {
5112                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5113                        debug_assert!(askpass_delegate.is_some());
5114                    });
5115
5116                    let response = client
5117                        .request(proto::Fetch {
5118                            project_id: project_id.0,
5119                            repository_id: id.to_proto(),
5120                            askpass_id,
5121                            remote: fetch_options.to_proto(),
5122                        })
5123                        .await
5124                        .context("sending fetch request")?;
5125
5126                    Ok(RemoteCommandOutput {
5127                        stdout: response.stdout,
5128                        stderr: response.stderr,
5129                    })
5130                }
5131            }
5132        })
5133    }
5134
5135    pub fn push(
5136        &mut self,
5137        branch: SharedString,
5138        remote_branch: SharedString,
5139        remote: SharedString,
5140        options: Option<PushOptions>,
5141        askpass: AskPassDelegate,
5142        cx: &mut Context<Self>,
5143    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
5144        let askpass_delegates = self.askpass_delegates.clone();
5145        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5146        let id = self.id;
5147
5148        let args = options
5149            .map(|option| match option {
5150                PushOptions::SetUpstream => " --set-upstream",
5151                PushOptions::Force => " --force-with-lease",
5152            })
5153            .unwrap_or("");
5154
5155        let updates_tx = self
5156            .git_store()
5157            .and_then(|git_store| match &git_store.read(cx).state {
5158                GitStoreState::Local { downstream, .. } => downstream
5159                    .as_ref()
5160                    .map(|downstream| downstream.updates_tx.clone()),
5161                _ => None,
5162            });
5163
5164        let this = cx.weak_entity();
5165        self.send_job(
5166            Some(format!("git push {} {} {}:{}", args, remote, branch, remote_branch).into()),
5167            move |git_repo, mut cx| async move {
5168                match git_repo {
5169                    RepositoryState::Local(LocalRepositoryState {
5170                        backend,
5171                        environment,
5172                        ..
5173                    }) => {
5174                        let result = backend
5175                            .push(
5176                                branch.to_string(),
5177                                remote_branch.to_string(),
5178                                remote.to_string(),
5179                                options,
5180                                askpass,
5181                                environment.clone(),
5182                                cx.clone(),
5183                            )
5184                            .await;
5185                        // TODO would be nice to not have to do this manually
5186                        if result.is_ok() {
5187                            let branches = backend.branches().await?;
5188                            let branch = branches.into_iter().find(|branch| branch.is_head);
5189                            log::info!("head branch after scan is {branch:?}");
5190                            let snapshot = this.update(&mut cx, |this, cx| {
5191                                this.snapshot.branch = branch;
5192                                cx.emit(RepositoryEvent::BranchChanged);
5193                                this.snapshot.clone()
5194                            })?;
5195                            if let Some(updates_tx) = updates_tx {
5196                                updates_tx
5197                                    .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
5198                                    .ok();
5199                            }
5200                        }
5201                        result
5202                    }
5203                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5204                        askpass_delegates.lock().insert(askpass_id, askpass);
5205                        let _defer = util::defer(|| {
5206                            let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5207                            debug_assert!(askpass_delegate.is_some());
5208                        });
5209                        let response = client
5210                            .request(proto::Push {
5211                                project_id: project_id.0,
5212                                repository_id: id.to_proto(),
5213                                askpass_id,
5214                                branch_name: branch.to_string(),
5215                                remote_branch_name: remote_branch.to_string(),
5216                                remote_name: remote.to_string(),
5217                                options: options.map(|options| match options {
5218                                    PushOptions::Force => proto::push::PushOptions::Force,
5219                                    PushOptions::SetUpstream => {
5220                                        proto::push::PushOptions::SetUpstream
5221                                    }
5222                                }
5223                                    as i32),
5224                            })
5225                            .await
5226                            .context("sending push request")?;
5227
5228                        Ok(RemoteCommandOutput {
5229                            stdout: response.stdout,
5230                            stderr: response.stderr,
5231                        })
5232                    }
5233                }
5234            },
5235        )
5236    }
5237
5238    pub fn pull(
5239        &mut self,
5240        branch: Option<SharedString>,
5241        remote: SharedString,
5242        rebase: bool,
5243        askpass: AskPassDelegate,
5244        _cx: &mut App,
5245    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
5246        let askpass_delegates = self.askpass_delegates.clone();
5247        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5248        let id = self.id;
5249
5250        let mut status = "git pull".to_string();
5251        if rebase {
5252            status.push_str(" --rebase");
5253        }
5254        status.push_str(&format!(" {}", remote));
5255        if let Some(b) = &branch {
5256            status.push_str(&format!(" {}", b));
5257        }
5258
5259        self.send_job(Some(status.into()), move |git_repo, cx| async move {
5260            match git_repo {
5261                RepositoryState::Local(LocalRepositoryState {
5262                    backend,
5263                    environment,
5264                    ..
5265                }) => {
5266                    backend
5267                        .pull(
5268                            branch.as_ref().map(|b| b.to_string()),
5269                            remote.to_string(),
5270                            rebase,
5271                            askpass,
5272                            environment.clone(),
5273                            cx,
5274                        )
5275                        .await
5276                }
5277                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5278                    askpass_delegates.lock().insert(askpass_id, askpass);
5279                    let _defer = util::defer(|| {
5280                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5281                        debug_assert!(askpass_delegate.is_some());
5282                    });
5283                    let response = client
5284                        .request(proto::Pull {
5285                            project_id: project_id.0,
5286                            repository_id: id.to_proto(),
5287                            askpass_id,
5288                            rebase,
5289                            branch_name: branch.as_ref().map(|b| b.to_string()),
5290                            remote_name: remote.to_string(),
5291                        })
5292                        .await
5293                        .context("sending pull request")?;
5294
5295                    Ok(RemoteCommandOutput {
5296                        stdout: response.stdout,
5297                        stderr: response.stderr,
5298                    })
5299                }
5300            }
5301        })
5302    }
5303
5304    fn spawn_set_index_text_job(
5305        &mut self,
5306        path: RepoPath,
5307        content: Option<String>,
5308        hunk_staging_operation_count: Option<usize>,
5309        cx: &mut Context<Self>,
5310    ) -> oneshot::Receiver<anyhow::Result<()>> {
5311        let id = self.id;
5312        let this = cx.weak_entity();
5313        let git_store = self.git_store.clone();
5314        let abs_path = self.snapshot.repo_path_to_abs_path(&path);
5315        self.send_keyed_job(
5316            Some(GitJobKey::WriteIndex(vec![path.clone()])),
5317            None,
5318            move |git_repo, mut cx| async move {
5319                log::debug!(
5320                    "start updating index text for buffer {}",
5321                    path.as_unix_str()
5322                );
5323
5324                match git_repo {
5325                    RepositoryState::Local(LocalRepositoryState {
5326                        fs,
5327                        backend,
5328                        environment,
5329                        ..
5330                    }) => {
5331                        let executable = match fs.metadata(&abs_path).await {
5332                            Ok(Some(meta)) => meta.is_executable,
5333                            Ok(None) => false,
5334                            Err(_err) => false,
5335                        };
5336                        backend
5337                            .set_index_text(path.clone(), content, environment.clone(), executable)
5338                            .await?;
5339                    }
5340                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5341                        client
5342                            .request(proto::SetIndexText {
5343                                project_id: project_id.0,
5344                                repository_id: id.to_proto(),
5345                                path: path.to_proto(),
5346                                text: content,
5347                            })
5348                            .await?;
5349                    }
5350                }
5351                log::debug!(
5352                    "finish updating index text for buffer {}",
5353                    path.as_unix_str()
5354                );
5355
5356                if let Some(hunk_staging_operation_count) = hunk_staging_operation_count {
5357                    let project_path = this
5358                        .read_with(&cx, |this, cx| this.repo_path_to_project_path(&path, cx))
5359                        .ok()
5360                        .flatten();
5361                    git_store
5362                        .update(&mut cx, |git_store, cx| {
5363                            let buffer_id = git_store
5364                                .buffer_store
5365                                .read(cx)
5366                                .get_by_path(&project_path?)?
5367                                .read(cx)
5368                                .remote_id();
5369                            let diff_state = git_store.diffs.get(&buffer_id)?;
5370                            diff_state.update(cx, |diff_state, _| {
5371                                diff_state.hunk_staging_operation_count_as_of_write =
5372                                    hunk_staging_operation_count;
5373                            });
5374                            Some(())
5375                        })
5376                        .context("Git store dropped")?;
5377                }
5378                Ok(())
5379            },
5380        )
5381    }
5382
5383    pub fn create_remote(
5384        &mut self,
5385        remote_name: String,
5386        remote_url: String,
5387    ) -> oneshot::Receiver<Result<()>> {
5388        let id = self.id;
5389        self.send_job(
5390            Some(format!("git remote add {remote_name} {remote_url}").into()),
5391            move |repo, _cx| async move {
5392                match repo {
5393                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5394                        backend.create_remote(remote_name, remote_url).await
5395                    }
5396                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5397                        client
5398                            .request(proto::GitCreateRemote {
5399                                project_id: project_id.0,
5400                                repository_id: id.to_proto(),
5401                                remote_name,
5402                                remote_url,
5403                            })
5404                            .await?;
5405
5406                        Ok(())
5407                    }
5408                }
5409            },
5410        )
5411    }
5412
5413    pub fn remove_remote(&mut self, remote_name: String) -> oneshot::Receiver<Result<()>> {
5414        let id = self.id;
5415        self.send_job(
5416            Some(format!("git remove remote {remote_name}").into()),
5417            move |repo, _cx| async move {
5418                match repo {
5419                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5420                        backend.remove_remote(remote_name).await
5421                    }
5422                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5423                        client
5424                            .request(proto::GitRemoveRemote {
5425                                project_id: project_id.0,
5426                                repository_id: id.to_proto(),
5427                                remote_name,
5428                            })
5429                            .await?;
5430
5431                        Ok(())
5432                    }
5433                }
5434            },
5435        )
5436    }
5437
5438    pub fn get_remotes(
5439        &mut self,
5440        branch_name: Option<String>,
5441        is_push: bool,
5442    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
5443        let id = self.id;
5444        self.send_job(None, move |repo, _cx| async move {
5445            match repo {
5446                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5447                    let remote = if let Some(branch_name) = branch_name {
5448                        if is_push {
5449                            backend.get_push_remote(branch_name).await?
5450                        } else {
5451                            backend.get_branch_remote(branch_name).await?
5452                        }
5453                    } else {
5454                        None
5455                    };
5456
5457                    match remote {
5458                        Some(remote) => Ok(vec![remote]),
5459                        None => backend.get_all_remotes().await,
5460                    }
5461                }
5462                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5463                    let response = client
5464                        .request(proto::GetRemotes {
5465                            project_id: project_id.0,
5466                            repository_id: id.to_proto(),
5467                            branch_name,
5468                            is_push,
5469                        })
5470                        .await?;
5471
5472                    let remotes = response
5473                        .remotes
5474                        .into_iter()
5475                        .map(|remotes| Remote {
5476                            name: remotes.name.into(),
5477                        })
5478                        .collect();
5479
5480                    Ok(remotes)
5481                }
5482            }
5483        })
5484    }
5485
5486    pub fn branches(&mut self) -> oneshot::Receiver<Result<Vec<Branch>>> {
5487        let id = self.id;
5488        self.send_job(None, move |repo, _| async move {
5489            match repo {
5490                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5491                    backend.branches().await
5492                }
5493                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5494                    let response = client
5495                        .request(proto::GitGetBranches {
5496                            project_id: project_id.0,
5497                            repository_id: id.to_proto(),
5498                        })
5499                        .await?;
5500
5501                    let branches = response
5502                        .branches
5503                        .into_iter()
5504                        .map(|branch| proto_to_branch(&branch))
5505                        .collect();
5506
5507                    Ok(branches)
5508                }
5509            }
5510        })
5511    }
5512
5513    pub fn worktrees(&mut self) -> oneshot::Receiver<Result<Vec<GitWorktree>>> {
5514        let id = self.id;
5515        self.send_job(None, move |repo, _| async move {
5516            match repo {
5517                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5518                    backend.worktrees().await
5519                }
5520                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5521                    let response = client
5522                        .request(proto::GitGetWorktrees {
5523                            project_id: project_id.0,
5524                            repository_id: id.to_proto(),
5525                        })
5526                        .await?;
5527
5528                    let worktrees = response
5529                        .worktrees
5530                        .into_iter()
5531                        .map(|worktree| proto_to_worktree(&worktree))
5532                        .collect();
5533
5534                    Ok(worktrees)
5535                }
5536            }
5537        })
5538    }
5539
5540    pub fn create_worktree(
5541        &mut self,
5542        name: String,
5543        directory: PathBuf,
5544        commit: Option<String>,
5545    ) -> oneshot::Receiver<Result<()>> {
5546        let id = self.id;
5547        self.send_job(
5548            Some("git worktree add".into()),
5549            move |repo, _cx| async move {
5550                match repo {
5551                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5552                        backend.create_worktree(name, directory, commit).await
5553                    }
5554                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5555                        client
5556                            .request(proto::GitCreateWorktree {
5557                                project_id: project_id.0,
5558                                repository_id: id.to_proto(),
5559                                name,
5560                                directory: directory.to_string_lossy().to_string(),
5561                                commit,
5562                            })
5563                            .await?;
5564
5565                        Ok(())
5566                    }
5567                }
5568            },
5569        )
5570    }
5571
5572    pub fn default_branch(
5573        &mut self,
5574        include_remote_name: bool,
5575    ) -> oneshot::Receiver<Result<Option<SharedString>>> {
5576        let id = self.id;
5577        self.send_job(None, move |repo, _| async move {
5578            match repo {
5579                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5580                    backend.default_branch(include_remote_name).await
5581                }
5582                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5583                    let response = client
5584                        .request(proto::GetDefaultBranch {
5585                            project_id: project_id.0,
5586                            repository_id: id.to_proto(),
5587                        })
5588                        .await?;
5589
5590                    anyhow::Ok(response.branch.map(SharedString::from))
5591                }
5592            }
5593        })
5594    }
5595
5596    pub fn diff_tree(
5597        &mut self,
5598        diff_type: DiffTreeType,
5599        _cx: &App,
5600    ) -> oneshot::Receiver<Result<TreeDiff>> {
5601        let repository_id = self.snapshot.id;
5602        self.send_job(None, move |repo, _cx| async move {
5603            match repo {
5604                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5605                    backend.diff_tree(diff_type).await
5606                }
5607                RepositoryState::Remote(RemoteRepositoryState { client, project_id }) => {
5608                    let response = client
5609                        .request(proto::GetTreeDiff {
5610                            project_id: project_id.0,
5611                            repository_id: repository_id.0,
5612                            is_merge: matches!(diff_type, DiffTreeType::MergeBase { .. }),
5613                            base: diff_type.base().to_string(),
5614                            head: diff_type.head().to_string(),
5615                        })
5616                        .await?;
5617
5618                    let entries = response
5619                        .entries
5620                        .into_iter()
5621                        .filter_map(|entry| {
5622                            let status = match entry.status() {
5623                                proto::tree_diff_status::Status::Added => TreeDiffStatus::Added,
5624                                proto::tree_diff_status::Status::Modified => {
5625                                    TreeDiffStatus::Modified {
5626                                        old: git::Oid::from_str(
5627                                            &entry.oid.context("missing oid").log_err()?,
5628                                        )
5629                                        .log_err()?,
5630                                    }
5631                                }
5632                                proto::tree_diff_status::Status::Deleted => {
5633                                    TreeDiffStatus::Deleted {
5634                                        old: git::Oid::from_str(
5635                                            &entry.oid.context("missing oid").log_err()?,
5636                                        )
5637                                        .log_err()?,
5638                                    }
5639                                }
5640                            };
5641                            Some((
5642                                RepoPath::from_rel_path(
5643                                    &RelPath::from_proto(&entry.path).log_err()?,
5644                                ),
5645                                status,
5646                            ))
5647                        })
5648                        .collect();
5649
5650                    Ok(TreeDiff { entries })
5651                }
5652            }
5653        })
5654    }
5655
5656    pub fn diff(&mut self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
5657        let id = self.id;
5658        self.send_job(None, move |repo, _cx| async move {
5659            match repo {
5660                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5661                    backend.diff(diff_type).await
5662                }
5663                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5664                    let (proto_diff_type, merge_base_ref) = match &diff_type {
5665                        DiffType::HeadToIndex => {
5666                            (proto::git_diff::DiffType::HeadToIndex.into(), None)
5667                        }
5668                        DiffType::HeadToWorktree => {
5669                            (proto::git_diff::DiffType::HeadToWorktree.into(), None)
5670                        }
5671                        DiffType::MergeBase { base_ref } => (
5672                            proto::git_diff::DiffType::MergeBase.into(),
5673                            Some(base_ref.to_string()),
5674                        ),
5675                    };
5676                    let response = client
5677                        .request(proto::GitDiff {
5678                            project_id: project_id.0,
5679                            repository_id: id.to_proto(),
5680                            diff_type: proto_diff_type,
5681                            merge_base_ref,
5682                        })
5683                        .await?;
5684
5685                    Ok(response.diff)
5686                }
5687            }
5688        })
5689    }
5690
5691    pub fn create_branch(
5692        &mut self,
5693        branch_name: String,
5694        base_branch: Option<String>,
5695    ) -> oneshot::Receiver<Result<()>> {
5696        let id = self.id;
5697        let status_msg = if let Some(ref base) = base_branch {
5698            format!("git switch -c {branch_name} {base}").into()
5699        } else {
5700            format!("git switch -c {branch_name}").into()
5701        };
5702        self.send_job(Some(status_msg), move |repo, _cx| async move {
5703            match repo {
5704                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5705                    backend.create_branch(branch_name, base_branch).await
5706                }
5707                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5708                    client
5709                        .request(proto::GitCreateBranch {
5710                            project_id: project_id.0,
5711                            repository_id: id.to_proto(),
5712                            branch_name,
5713                        })
5714                        .await?;
5715
5716                    Ok(())
5717                }
5718            }
5719        })
5720    }
5721
5722    pub fn change_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
5723        let id = self.id;
5724        self.send_job(
5725            Some(format!("git switch {branch_name}").into()),
5726            move |repo, _cx| async move {
5727                match repo {
5728                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5729                        backend.change_branch(branch_name).await
5730                    }
5731                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5732                        client
5733                            .request(proto::GitChangeBranch {
5734                                project_id: project_id.0,
5735                                repository_id: id.to_proto(),
5736                                branch_name,
5737                            })
5738                            .await?;
5739
5740                        Ok(())
5741                    }
5742                }
5743            },
5744        )
5745    }
5746
5747    pub fn delete_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
5748        let id = self.id;
5749        self.send_job(
5750            Some(format!("git branch -d {branch_name}").into()),
5751            move |repo, _cx| async move {
5752                match repo {
5753                    RepositoryState::Local(state) => state.backend.delete_branch(branch_name).await,
5754                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5755                        client
5756                            .request(proto::GitDeleteBranch {
5757                                project_id: project_id.0,
5758                                repository_id: id.to_proto(),
5759                                branch_name,
5760                            })
5761                            .await?;
5762
5763                        Ok(())
5764                    }
5765                }
5766            },
5767        )
5768    }
5769
5770    pub fn rename_branch(
5771        &mut self,
5772        branch: String,
5773        new_name: String,
5774    ) -> oneshot::Receiver<Result<()>> {
5775        let id = self.id;
5776        self.send_job(
5777            Some(format!("git branch -m {branch} {new_name}").into()),
5778            move |repo, _cx| async move {
5779                match repo {
5780                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5781                        backend.rename_branch(branch, new_name).await
5782                    }
5783                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5784                        client
5785                            .request(proto::GitRenameBranch {
5786                                project_id: project_id.0,
5787                                repository_id: id.to_proto(),
5788                                branch,
5789                                new_name,
5790                            })
5791                            .await?;
5792
5793                        Ok(())
5794                    }
5795                }
5796            },
5797        )
5798    }
5799
5800    pub fn check_for_pushed_commits(&mut self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
5801        let id = self.id;
5802        self.send_job(None, move |repo, _cx| async move {
5803            match repo {
5804                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5805                    backend.check_for_pushed_commit().await
5806                }
5807                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5808                    let response = client
5809                        .request(proto::CheckForPushedCommits {
5810                            project_id: project_id.0,
5811                            repository_id: id.to_proto(),
5812                        })
5813                        .await?;
5814
5815                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
5816
5817                    Ok(branches)
5818                }
5819            }
5820        })
5821    }
5822
5823    pub fn checkpoint(&mut self) -> oneshot::Receiver<Result<GitRepositoryCheckpoint>> {
5824        self.send_job(None, |repo, _cx| async move {
5825            match repo {
5826                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5827                    backend.checkpoint().await
5828                }
5829                RepositoryState::Remote(..) => anyhow::bail!("not implemented yet"),
5830            }
5831        })
5832    }
5833
5834    pub fn restore_checkpoint(
5835        &mut self,
5836        checkpoint: GitRepositoryCheckpoint,
5837    ) -> oneshot::Receiver<Result<()>> {
5838        self.send_job(None, move |repo, _cx| async move {
5839            match repo {
5840                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5841                    backend.restore_checkpoint(checkpoint).await
5842                }
5843                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
5844            }
5845        })
5846    }
5847
5848    pub(crate) fn apply_remote_update(
5849        &mut self,
5850        update: proto::UpdateRepository,
5851        cx: &mut Context<Self>,
5852    ) -> Result<()> {
5853        let conflicted_paths = TreeSet::from_ordered_entries(
5854            update
5855                .current_merge_conflicts
5856                .into_iter()
5857                .filter_map(|path| RepoPath::from_proto(&path).log_err()),
5858        );
5859        let new_branch = update.branch_summary.as_ref().map(proto_to_branch);
5860        let new_head_commit = update
5861            .head_commit_details
5862            .as_ref()
5863            .map(proto_to_commit_details);
5864        if self.snapshot.branch != new_branch || self.snapshot.head_commit != new_head_commit {
5865            cx.emit(RepositoryEvent::BranchChanged)
5866        }
5867        self.snapshot.branch = new_branch;
5868        self.snapshot.head_commit = new_head_commit;
5869
5870        self.snapshot.merge.conflicted_paths = conflicted_paths;
5871        self.snapshot.merge.message = update.merge_message.map(SharedString::from);
5872        let new_stash_entries = GitStash {
5873            entries: update
5874                .stash_entries
5875                .iter()
5876                .filter_map(|entry| proto_to_stash(entry).ok())
5877                .collect(),
5878        };
5879        if self.snapshot.stash_entries != new_stash_entries {
5880            cx.emit(RepositoryEvent::StashEntriesChanged)
5881        }
5882        self.snapshot.stash_entries = new_stash_entries;
5883        self.snapshot.remote_upstream_url = update.remote_upstream_url;
5884        self.snapshot.remote_origin_url = update.remote_origin_url;
5885
5886        let edits = update
5887            .removed_statuses
5888            .into_iter()
5889            .filter_map(|path| {
5890                Some(sum_tree::Edit::Remove(PathKey(
5891                    RelPath::from_proto(&path).log_err()?,
5892                )))
5893            })
5894            .chain(
5895                update
5896                    .updated_statuses
5897                    .into_iter()
5898                    .filter_map(|updated_status| {
5899                        Some(sum_tree::Edit::Insert(updated_status.try_into().log_err()?))
5900                    }),
5901            )
5902            .collect::<Vec<_>>();
5903        if !edits.is_empty() {
5904            cx.emit(RepositoryEvent::StatusesChanged);
5905        }
5906        self.snapshot.statuses_by_path.edit(edits, ());
5907        if update.is_last_update {
5908            self.snapshot.scan_id = update.scan_id;
5909        }
5910        self.clear_pending_ops(cx);
5911        Ok(())
5912    }
5913
5914    pub fn compare_checkpoints(
5915        &mut self,
5916        left: GitRepositoryCheckpoint,
5917        right: GitRepositoryCheckpoint,
5918    ) -> oneshot::Receiver<Result<bool>> {
5919        self.send_job(None, move |repo, _cx| async move {
5920            match repo {
5921                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5922                    backend.compare_checkpoints(left, right).await
5923                }
5924                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
5925            }
5926        })
5927    }
5928
5929    pub fn diff_checkpoints(
5930        &mut self,
5931        base_checkpoint: GitRepositoryCheckpoint,
5932        target_checkpoint: GitRepositoryCheckpoint,
5933    ) -> oneshot::Receiver<Result<String>> {
5934        self.send_job(None, move |repo, _cx| async move {
5935            match repo {
5936                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5937                    backend
5938                        .diff_checkpoints(base_checkpoint, target_checkpoint)
5939                        .await
5940                }
5941                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
5942            }
5943        })
5944    }
5945
5946    fn clear_pending_ops(&mut self, cx: &mut Context<Self>) {
5947        let updated = SumTree::from_iter(
5948            self.pending_ops.iter().filter_map(|ops| {
5949                let inner_ops: Vec<PendingOp> =
5950                    ops.ops.iter().filter(|op| op.running()).cloned().collect();
5951                if inner_ops.is_empty() {
5952                    None
5953                } else {
5954                    Some(PendingOps {
5955                        repo_path: ops.repo_path.clone(),
5956                        ops: inner_ops,
5957                    })
5958                }
5959            }),
5960            (),
5961        );
5962
5963        if updated != self.pending_ops {
5964            cx.emit(RepositoryEvent::PendingOpsChanged {
5965                pending_ops: self.pending_ops.clone(),
5966            })
5967        }
5968
5969        self.pending_ops = updated;
5970    }
5971
5972    fn schedule_scan(
5973        &mut self,
5974        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
5975        cx: &mut Context<Self>,
5976    ) {
5977        let this = cx.weak_entity();
5978        let _ = self.send_keyed_job(
5979            Some(GitJobKey::ReloadGitState),
5980            None,
5981            |state, mut cx| async move {
5982                log::debug!("run scheduled git status scan");
5983
5984                let Some(this) = this.upgrade() else {
5985                    return Ok(());
5986                };
5987                let RepositoryState::Local(LocalRepositoryState { backend, .. }) = state else {
5988                    bail!("not a local repository")
5989                };
5990                let (snapshot, events) = this
5991                    .update(&mut cx, |this, _| {
5992                        this.paths_needing_status_update.clear();
5993                        compute_snapshot(
5994                            this.id,
5995                            this.work_directory_abs_path.clone(),
5996                            this.snapshot.clone(),
5997                            backend.clone(),
5998                        )
5999                    })
6000                    .await?;
6001                this.update(&mut cx, |this, cx| {
6002                    this.snapshot = snapshot.clone();
6003                    this.clear_pending_ops(cx);
6004                    for event in events {
6005                        cx.emit(event);
6006                    }
6007                });
6008                if let Some(updates_tx) = updates_tx {
6009                    updates_tx
6010                        .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
6011                        .ok();
6012                }
6013                Ok(())
6014            },
6015        );
6016    }
6017
6018    fn spawn_local_git_worker(
6019        state: Shared<Task<Result<LocalRepositoryState, String>>>,
6020        cx: &mut Context<Self>,
6021    ) -> mpsc::UnboundedSender<GitJob> {
6022        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
6023
6024        cx.spawn(async move |_, cx| {
6025            let state = state.await.map_err(|err| anyhow::anyhow!(err))?;
6026            if let Some(git_hosting_provider_registry) =
6027                cx.update(|cx| GitHostingProviderRegistry::try_global(cx))
6028            {
6029                git_hosting_providers::register_additional_providers(
6030                    git_hosting_provider_registry,
6031                    state.backend.clone(),
6032                )
6033                .await;
6034            }
6035            let state = RepositoryState::Local(state);
6036            let mut jobs = VecDeque::new();
6037            loop {
6038                while let Ok(Some(next_job)) = job_rx.try_next() {
6039                    jobs.push_back(next_job);
6040                }
6041
6042                if let Some(job) = jobs.pop_front() {
6043                    if let Some(current_key) = &job.key
6044                        && jobs
6045                            .iter()
6046                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
6047                    {
6048                        continue;
6049                    }
6050                    (job.job)(state.clone(), cx).await;
6051                } else if let Some(job) = job_rx.next().await {
6052                    jobs.push_back(job);
6053                } else {
6054                    break;
6055                }
6056            }
6057            anyhow::Ok(())
6058        })
6059        .detach_and_log_err(cx);
6060
6061        job_tx
6062    }
6063
6064    fn spawn_remote_git_worker(
6065        state: RemoteRepositoryState,
6066        cx: &mut Context<Self>,
6067    ) -> mpsc::UnboundedSender<GitJob> {
6068        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
6069
6070        cx.spawn(async move |_, cx| {
6071            let state = RepositoryState::Remote(state);
6072            let mut jobs = VecDeque::new();
6073            loop {
6074                while let Ok(Some(next_job)) = job_rx.try_next() {
6075                    jobs.push_back(next_job);
6076                }
6077
6078                if let Some(job) = jobs.pop_front() {
6079                    if let Some(current_key) = &job.key
6080                        && jobs
6081                            .iter()
6082                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
6083                    {
6084                        continue;
6085                    }
6086                    (job.job)(state.clone(), cx).await;
6087                } else if let Some(job) = job_rx.next().await {
6088                    jobs.push_back(job);
6089                } else {
6090                    break;
6091                }
6092            }
6093            anyhow::Ok(())
6094        })
6095        .detach_and_log_err(cx);
6096
6097        job_tx
6098    }
6099
6100    fn load_staged_text(
6101        &mut self,
6102        buffer_id: BufferId,
6103        repo_path: RepoPath,
6104        cx: &App,
6105    ) -> Task<Result<Option<String>>> {
6106        let rx = self.send_job(None, move |state, _| async move {
6107            match state {
6108                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6109                    anyhow::Ok(backend.load_index_text(repo_path).await)
6110                }
6111                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
6112                    let response = client
6113                        .request(proto::OpenUnstagedDiff {
6114                            project_id: project_id.to_proto(),
6115                            buffer_id: buffer_id.to_proto(),
6116                        })
6117                        .await?;
6118                    Ok(response.staged_text)
6119                }
6120            }
6121        });
6122        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
6123    }
6124
6125    fn load_committed_text(
6126        &mut self,
6127        buffer_id: BufferId,
6128        repo_path: RepoPath,
6129        cx: &App,
6130    ) -> Task<Result<DiffBasesChange>> {
6131        let rx = self.send_job(None, move |state, _| async move {
6132            match state {
6133                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6134                    let committed_text = backend.load_committed_text(repo_path.clone()).await;
6135                    let staged_text = backend.load_index_text(repo_path).await;
6136                    let diff_bases_change = if committed_text == staged_text {
6137                        DiffBasesChange::SetBoth(committed_text)
6138                    } else {
6139                        DiffBasesChange::SetEach {
6140                            index: staged_text,
6141                            head: committed_text,
6142                        }
6143                    };
6144                    anyhow::Ok(diff_bases_change)
6145                }
6146                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
6147                    use proto::open_uncommitted_diff_response::Mode;
6148
6149                    let response = client
6150                        .request(proto::OpenUncommittedDiff {
6151                            project_id: project_id.to_proto(),
6152                            buffer_id: buffer_id.to_proto(),
6153                        })
6154                        .await?;
6155                    let mode = Mode::from_i32(response.mode).context("Invalid mode")?;
6156                    let bases = match mode {
6157                        Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
6158                        Mode::IndexAndHead => DiffBasesChange::SetEach {
6159                            head: response.committed_text,
6160                            index: response.staged_text,
6161                        },
6162                    };
6163                    Ok(bases)
6164                }
6165            }
6166        });
6167
6168        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
6169    }
6170
6171    fn load_blob_content(&mut self, oid: Oid, cx: &App) -> Task<Result<String>> {
6172        let repository_id = self.snapshot.id;
6173        let rx = self.send_job(None, move |state, _| async move {
6174            match state {
6175                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6176                    backend.load_blob_content(oid).await
6177                }
6178                RepositoryState::Remote(RemoteRepositoryState { client, project_id }) => {
6179                    let response = client
6180                        .request(proto::GetBlobContent {
6181                            project_id: project_id.to_proto(),
6182                            repository_id: repository_id.0,
6183                            oid: oid.to_string(),
6184                        })
6185                        .await?;
6186                    Ok(response.content)
6187                }
6188            }
6189        });
6190        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
6191    }
6192
6193    fn paths_changed(
6194        &mut self,
6195        paths: Vec<RepoPath>,
6196        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
6197        cx: &mut Context<Self>,
6198    ) {
6199        if !paths.is_empty() {
6200            self.paths_needing_status_update.push(paths);
6201        }
6202
6203        let this = cx.weak_entity();
6204        let _ = self.send_keyed_job(
6205            Some(GitJobKey::RefreshStatuses),
6206            None,
6207            |state, mut cx| async move {
6208                let (prev_snapshot, changed_paths) = this.update(&mut cx, |this, _| {
6209                    (
6210                        this.snapshot.clone(),
6211                        mem::take(&mut this.paths_needing_status_update),
6212                    )
6213                })?;
6214                let RepositoryState::Local(LocalRepositoryState { backend, .. }) = state else {
6215                    bail!("not a local repository")
6216                };
6217
6218                if changed_paths.is_empty() {
6219                    return Ok(());
6220                }
6221
6222                let stash_entries = backend.stash_entries().await?;
6223                let changed_path_statuses = cx
6224                    .background_spawn(async move {
6225                        let mut changed_paths =
6226                            changed_paths.into_iter().flatten().collect::<BTreeSet<_>>();
6227                        let statuses = backend
6228                            .status(&changed_paths.iter().cloned().collect::<Vec<_>>())
6229                            .await?;
6230                        let mut changed_path_statuses = Vec::new();
6231                        let prev_statuses = prev_snapshot.statuses_by_path.clone();
6232                        let mut cursor = prev_statuses.cursor::<PathProgress>(());
6233
6234                        for (repo_path, status) in &*statuses.entries {
6235                            changed_paths.remove(repo_path);
6236                            if cursor.seek_forward(&PathTarget::Path(repo_path), Bias::Left)
6237                                && cursor.item().is_some_and(|entry| entry.status == *status)
6238                            {
6239                                continue;
6240                            }
6241
6242                            changed_path_statuses.push(Edit::Insert(StatusEntry {
6243                                repo_path: repo_path.clone(),
6244                                status: *status,
6245                            }));
6246                        }
6247                        let mut cursor = prev_statuses.cursor::<PathProgress>(());
6248                        for path in changed_paths.into_iter() {
6249                            if cursor.seek_forward(&PathTarget::Path(&path), Bias::Left) {
6250                                changed_path_statuses
6251                                    .push(Edit::Remove(PathKey(path.as_ref().clone())));
6252                            }
6253                        }
6254                        anyhow::Ok(changed_path_statuses)
6255                    })
6256                    .await?;
6257
6258                this.update(&mut cx, |this, cx| {
6259                    if this.snapshot.stash_entries != stash_entries {
6260                        cx.emit(RepositoryEvent::StashEntriesChanged);
6261                        this.snapshot.stash_entries = stash_entries;
6262                    }
6263
6264                    if !changed_path_statuses.is_empty() {
6265                        cx.emit(RepositoryEvent::StatusesChanged);
6266                        this.snapshot
6267                            .statuses_by_path
6268                            .edit(changed_path_statuses, ());
6269                        this.snapshot.scan_id += 1;
6270                    }
6271
6272                    if let Some(updates_tx) = updates_tx {
6273                        updates_tx
6274                            .unbounded_send(DownstreamUpdate::UpdateRepository(
6275                                this.snapshot.clone(),
6276                            ))
6277                            .ok();
6278                    }
6279                })
6280            },
6281        );
6282    }
6283
6284    /// currently running git command and when it started
6285    pub fn current_job(&self) -> Option<JobInfo> {
6286        self.active_jobs.values().next().cloned()
6287    }
6288
6289    pub fn barrier(&mut self) -> oneshot::Receiver<()> {
6290        self.send_job(None, |_, _| async {})
6291    }
6292
6293    fn spawn_job_with_tracking<AsyncFn>(
6294        &mut self,
6295        paths: Vec<RepoPath>,
6296        git_status: pending_op::GitStatus,
6297        cx: &mut Context<Self>,
6298        f: AsyncFn,
6299    ) -> Task<Result<()>>
6300    where
6301        AsyncFn: AsyncFnOnce(WeakEntity<Repository>, &mut AsyncApp) -> Result<()> + 'static,
6302    {
6303        let ids = self.new_pending_ops_for_paths(paths, git_status);
6304
6305        cx.spawn(async move |this, cx| {
6306            let (job_status, result) = match f(this.clone(), cx).await {
6307                Ok(()) => (pending_op::JobStatus::Finished, Ok(())),
6308                Err(err) if err.is::<Canceled>() => (pending_op::JobStatus::Skipped, Ok(())),
6309                Err(err) => (pending_op::JobStatus::Error, Err(err)),
6310            };
6311
6312            this.update(cx, |this, _| {
6313                let mut edits = Vec::with_capacity(ids.len());
6314                for (id, entry) in ids {
6315                    if let Some(mut ops) = this
6316                        .pending_ops
6317                        .get(&PathKey(entry.as_ref().clone()), ())
6318                        .cloned()
6319                    {
6320                        if let Some(op) = ops.op_by_id_mut(id) {
6321                            op.job_status = job_status;
6322                        }
6323                        edits.push(sum_tree::Edit::Insert(ops));
6324                    }
6325                }
6326                this.pending_ops.edit(edits, ());
6327            })?;
6328
6329            result
6330        })
6331    }
6332
6333    fn new_pending_ops_for_paths(
6334        &mut self,
6335        paths: Vec<RepoPath>,
6336        git_status: pending_op::GitStatus,
6337    ) -> Vec<(PendingOpId, RepoPath)> {
6338        let mut edits = Vec::with_capacity(paths.len());
6339        let mut ids = Vec::with_capacity(paths.len());
6340        for path in paths {
6341            let mut ops = self
6342                .pending_ops
6343                .get(&PathKey(path.as_ref().clone()), ())
6344                .cloned()
6345                .unwrap_or_else(|| PendingOps::new(&path));
6346            let id = ops.max_id() + 1;
6347            ops.ops.push(PendingOp {
6348                id,
6349                git_status,
6350                job_status: pending_op::JobStatus::Running,
6351            });
6352            edits.push(sum_tree::Edit::Insert(ops));
6353            ids.push((id, path));
6354        }
6355        self.pending_ops.edit(edits, ());
6356        ids
6357    }
6358    pub fn default_remote_url(&self) -> Option<String> {
6359        self.remote_upstream_url
6360            .clone()
6361            .or(self.remote_origin_url.clone())
6362    }
6363}
6364
6365fn get_permalink_in_rust_registry_src(
6366    provider_registry: Arc<GitHostingProviderRegistry>,
6367    path: PathBuf,
6368    selection: Range<u32>,
6369) -> Result<url::Url> {
6370    #[derive(Deserialize)]
6371    struct CargoVcsGit {
6372        sha1: String,
6373    }
6374
6375    #[derive(Deserialize)]
6376    struct CargoVcsInfo {
6377        git: CargoVcsGit,
6378        path_in_vcs: String,
6379    }
6380
6381    #[derive(Deserialize)]
6382    struct CargoPackage {
6383        repository: String,
6384    }
6385
6386    #[derive(Deserialize)]
6387    struct CargoToml {
6388        package: CargoPackage,
6389    }
6390
6391    let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
6392        let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
6393        Some((dir, json))
6394    }) else {
6395        bail!("No .cargo_vcs_info.json found in parent directories")
6396    };
6397    let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
6398    let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
6399    let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
6400    let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
6401        .context("parsing package.repository field of manifest")?;
6402    let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
6403    let permalink = provider.build_permalink(
6404        remote,
6405        BuildPermalinkParams::new(
6406            &cargo_vcs_info.git.sha1,
6407            &RepoPath::from_rel_path(
6408                &RelPath::new(&path, PathStyle::local()).context("invalid path")?,
6409            ),
6410            Some(selection),
6411        ),
6412    );
6413    Ok(permalink)
6414}
6415
6416fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::BlameBufferResponse {
6417    let Some(blame) = blame else {
6418        return proto::BlameBufferResponse {
6419            blame_response: None,
6420        };
6421    };
6422
6423    let entries = blame
6424        .entries
6425        .into_iter()
6426        .map(|entry| proto::BlameEntry {
6427            sha: entry.sha.as_bytes().into(),
6428            start_line: entry.range.start,
6429            end_line: entry.range.end,
6430            original_line_number: entry.original_line_number,
6431            author: entry.author,
6432            author_mail: entry.author_mail,
6433            author_time: entry.author_time,
6434            author_tz: entry.author_tz,
6435            committer: entry.committer_name,
6436            committer_mail: entry.committer_email,
6437            committer_time: entry.committer_time,
6438            committer_tz: entry.committer_tz,
6439            summary: entry.summary,
6440            previous: entry.previous,
6441            filename: entry.filename,
6442        })
6443        .collect::<Vec<_>>();
6444
6445    let messages = blame
6446        .messages
6447        .into_iter()
6448        .map(|(oid, message)| proto::CommitMessage {
6449            oid: oid.as_bytes().into(),
6450            message,
6451        })
6452        .collect::<Vec<_>>();
6453
6454    proto::BlameBufferResponse {
6455        blame_response: Some(proto::blame_buffer_response::BlameResponse { entries, messages }),
6456    }
6457}
6458
6459fn deserialize_blame_buffer_response(
6460    response: proto::BlameBufferResponse,
6461) -> Option<git::blame::Blame> {
6462    let response = response.blame_response?;
6463    let entries = response
6464        .entries
6465        .into_iter()
6466        .filter_map(|entry| {
6467            Some(git::blame::BlameEntry {
6468                sha: git::Oid::from_bytes(&entry.sha).ok()?,
6469                range: entry.start_line..entry.end_line,
6470                original_line_number: entry.original_line_number,
6471                committer_name: entry.committer,
6472                committer_time: entry.committer_time,
6473                committer_tz: entry.committer_tz,
6474                committer_email: entry.committer_mail,
6475                author: entry.author,
6476                author_mail: entry.author_mail,
6477                author_time: entry.author_time,
6478                author_tz: entry.author_tz,
6479                summary: entry.summary,
6480                previous: entry.previous,
6481                filename: entry.filename,
6482            })
6483        })
6484        .collect::<Vec<_>>();
6485
6486    let messages = response
6487        .messages
6488        .into_iter()
6489        .filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
6490        .collect::<HashMap<_, _>>();
6491
6492    Some(Blame { entries, messages })
6493}
6494
6495fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch {
6496    proto::Branch {
6497        is_head: branch.is_head,
6498        ref_name: branch.ref_name.to_string(),
6499        unix_timestamp: branch
6500            .most_recent_commit
6501            .as_ref()
6502            .map(|commit| commit.commit_timestamp as u64),
6503        upstream: branch.upstream.as_ref().map(|upstream| proto::GitUpstream {
6504            ref_name: upstream.ref_name.to_string(),
6505            tracking: upstream
6506                .tracking
6507                .status()
6508                .map(|upstream| proto::UpstreamTracking {
6509                    ahead: upstream.ahead as u64,
6510                    behind: upstream.behind as u64,
6511                }),
6512        }),
6513        most_recent_commit: branch
6514            .most_recent_commit
6515            .as_ref()
6516            .map(|commit| proto::CommitSummary {
6517                sha: commit.sha.to_string(),
6518                subject: commit.subject.to_string(),
6519                commit_timestamp: commit.commit_timestamp,
6520                author_name: commit.author_name.to_string(),
6521            }),
6522    }
6523}
6524
6525fn worktree_to_proto(worktree: &git::repository::Worktree) -> proto::Worktree {
6526    proto::Worktree {
6527        path: worktree.path.to_string_lossy().to_string(),
6528        ref_name: worktree.ref_name.to_string(),
6529        sha: worktree.sha.to_string(),
6530    }
6531}
6532
6533fn proto_to_worktree(proto: &proto::Worktree) -> git::repository::Worktree {
6534    git::repository::Worktree {
6535        path: PathBuf::from(proto.path.clone()),
6536        ref_name: proto.ref_name.clone().into(),
6537        sha: proto.sha.clone().into(),
6538    }
6539}
6540
6541fn proto_to_branch(proto: &proto::Branch) -> git::repository::Branch {
6542    git::repository::Branch {
6543        is_head: proto.is_head,
6544        ref_name: proto.ref_name.clone().into(),
6545        upstream: proto
6546            .upstream
6547            .as_ref()
6548            .map(|upstream| git::repository::Upstream {
6549                ref_name: upstream.ref_name.to_string().into(),
6550                tracking: upstream
6551                    .tracking
6552                    .as_ref()
6553                    .map(|tracking| {
6554                        git::repository::UpstreamTracking::Tracked(UpstreamTrackingStatus {
6555                            ahead: tracking.ahead as u32,
6556                            behind: tracking.behind as u32,
6557                        })
6558                    })
6559                    .unwrap_or(git::repository::UpstreamTracking::Gone),
6560            }),
6561        most_recent_commit: proto.most_recent_commit.as_ref().map(|commit| {
6562            git::repository::CommitSummary {
6563                sha: commit.sha.to_string().into(),
6564                subject: commit.subject.to_string().into(),
6565                commit_timestamp: commit.commit_timestamp,
6566                author_name: commit.author_name.to_string().into(),
6567                has_parent: true,
6568            }
6569        }),
6570    }
6571}
6572
6573fn commit_details_to_proto(commit: &CommitDetails) -> proto::GitCommitDetails {
6574    proto::GitCommitDetails {
6575        sha: commit.sha.to_string(),
6576        message: commit.message.to_string(),
6577        commit_timestamp: commit.commit_timestamp,
6578        author_email: commit.author_email.to_string(),
6579        author_name: commit.author_name.to_string(),
6580    }
6581}
6582
6583fn proto_to_commit_details(proto: &proto::GitCommitDetails) -> CommitDetails {
6584    CommitDetails {
6585        sha: proto.sha.clone().into(),
6586        message: proto.message.clone().into(),
6587        commit_timestamp: proto.commit_timestamp,
6588        author_email: proto.author_email.clone().into(),
6589        author_name: proto.author_name.clone().into(),
6590    }
6591}
6592
6593async fn compute_snapshot(
6594    id: RepositoryId,
6595    work_directory_abs_path: Arc<Path>,
6596    prev_snapshot: RepositorySnapshot,
6597    backend: Arc<dyn GitRepository>,
6598) -> Result<(RepositorySnapshot, Vec<RepositoryEvent>)> {
6599    let mut events = Vec::new();
6600    let branches = backend.branches().await?;
6601    let branch = branches.into_iter().find(|branch| branch.is_head);
6602    let statuses = backend
6603        .status(&[RepoPath::from_rel_path(
6604            &RelPath::new(".".as_ref(), PathStyle::local()).unwrap(),
6605        )])
6606        .await?;
6607    let stash_entries = backend.stash_entries().await?;
6608    let statuses_by_path = SumTree::from_iter(
6609        statuses
6610            .entries
6611            .iter()
6612            .map(|(repo_path, status)| StatusEntry {
6613                repo_path: repo_path.clone(),
6614                status: *status,
6615            }),
6616        (),
6617    );
6618    let (merge_details, merge_heads_changed) =
6619        MergeDetails::load(&backend, &statuses_by_path, &prev_snapshot).await?;
6620    log::debug!("new merge details (changed={merge_heads_changed:?}): {merge_details:?}");
6621
6622    if merge_heads_changed {
6623        events.push(RepositoryEvent::MergeHeadsChanged);
6624    }
6625
6626    if statuses_by_path != prev_snapshot.statuses_by_path {
6627        events.push(RepositoryEvent::StatusesChanged)
6628    }
6629
6630    // Useful when branch is None in detached head state
6631    let head_commit = match backend.head_sha().await {
6632        Some(head_sha) => backend.show(head_sha).await.log_err(),
6633        None => None,
6634    };
6635
6636    if branch != prev_snapshot.branch || head_commit != prev_snapshot.head_commit {
6637        events.push(RepositoryEvent::BranchChanged);
6638    }
6639
6640    let remote_origin_url = backend.remote_url("origin").await;
6641    let remote_upstream_url = backend.remote_url("upstream").await;
6642
6643    let snapshot = RepositorySnapshot {
6644        id,
6645        statuses_by_path,
6646        work_directory_abs_path,
6647        path_style: prev_snapshot.path_style,
6648        scan_id: prev_snapshot.scan_id + 1,
6649        branch,
6650        head_commit,
6651        merge: merge_details,
6652        remote_origin_url,
6653        remote_upstream_url,
6654        stash_entries,
6655    };
6656
6657    Ok((snapshot, events))
6658}
6659
6660fn status_from_proto(
6661    simple_status: i32,
6662    status: Option<proto::GitFileStatus>,
6663) -> anyhow::Result<FileStatus> {
6664    use proto::git_file_status::Variant;
6665
6666    let Some(variant) = status.and_then(|status| status.variant) else {
6667        let code = proto::GitStatus::from_i32(simple_status)
6668            .with_context(|| format!("Invalid git status code: {simple_status}"))?;
6669        let result = match code {
6670            proto::GitStatus::Added => TrackedStatus {
6671                worktree_status: StatusCode::Added,
6672                index_status: StatusCode::Unmodified,
6673            }
6674            .into(),
6675            proto::GitStatus::Modified => TrackedStatus {
6676                worktree_status: StatusCode::Modified,
6677                index_status: StatusCode::Unmodified,
6678            }
6679            .into(),
6680            proto::GitStatus::Conflict => UnmergedStatus {
6681                first_head: UnmergedStatusCode::Updated,
6682                second_head: UnmergedStatusCode::Updated,
6683            }
6684            .into(),
6685            proto::GitStatus::Deleted => TrackedStatus {
6686                worktree_status: StatusCode::Deleted,
6687                index_status: StatusCode::Unmodified,
6688            }
6689            .into(),
6690            _ => anyhow::bail!("Invalid code for simple status: {simple_status}"),
6691        };
6692        return Ok(result);
6693    };
6694
6695    let result = match variant {
6696        Variant::Untracked(_) => FileStatus::Untracked,
6697        Variant::Ignored(_) => FileStatus::Ignored,
6698        Variant::Unmerged(unmerged) => {
6699            let [first_head, second_head] =
6700                [unmerged.first_head, unmerged.second_head].map(|head| {
6701                    let code = proto::GitStatus::from_i32(head)
6702                        .with_context(|| format!("Invalid git status code: {head}"))?;
6703                    let result = match code {
6704                        proto::GitStatus::Added => UnmergedStatusCode::Added,
6705                        proto::GitStatus::Updated => UnmergedStatusCode::Updated,
6706                        proto::GitStatus::Deleted => UnmergedStatusCode::Deleted,
6707                        _ => anyhow::bail!("Invalid code for unmerged status: {code:?}"),
6708                    };
6709                    Ok(result)
6710                });
6711            let [first_head, second_head] = [first_head?, second_head?];
6712            UnmergedStatus {
6713                first_head,
6714                second_head,
6715            }
6716            .into()
6717        }
6718        Variant::Tracked(tracked) => {
6719            let [index_status, worktree_status] = [tracked.index_status, tracked.worktree_status]
6720                .map(|status| {
6721                    let code = proto::GitStatus::from_i32(status)
6722                        .with_context(|| format!("Invalid git status code: {status}"))?;
6723                    let result = match code {
6724                        proto::GitStatus::Modified => StatusCode::Modified,
6725                        proto::GitStatus::TypeChanged => StatusCode::TypeChanged,
6726                        proto::GitStatus::Added => StatusCode::Added,
6727                        proto::GitStatus::Deleted => StatusCode::Deleted,
6728                        proto::GitStatus::Renamed => StatusCode::Renamed,
6729                        proto::GitStatus::Copied => StatusCode::Copied,
6730                        proto::GitStatus::Unmodified => StatusCode::Unmodified,
6731                        _ => anyhow::bail!("Invalid code for tracked status: {code:?}"),
6732                    };
6733                    Ok(result)
6734                });
6735            let [index_status, worktree_status] = [index_status?, worktree_status?];
6736            TrackedStatus {
6737                index_status,
6738                worktree_status,
6739            }
6740            .into()
6741        }
6742    };
6743    Ok(result)
6744}
6745
6746fn status_to_proto(status: FileStatus) -> proto::GitFileStatus {
6747    use proto::git_file_status::{Tracked, Unmerged, Variant};
6748
6749    let variant = match status {
6750        FileStatus::Untracked => Variant::Untracked(Default::default()),
6751        FileStatus::Ignored => Variant::Ignored(Default::default()),
6752        FileStatus::Unmerged(UnmergedStatus {
6753            first_head,
6754            second_head,
6755        }) => Variant::Unmerged(Unmerged {
6756            first_head: unmerged_status_to_proto(first_head),
6757            second_head: unmerged_status_to_proto(second_head),
6758        }),
6759        FileStatus::Tracked(TrackedStatus {
6760            index_status,
6761            worktree_status,
6762        }) => Variant::Tracked(Tracked {
6763            index_status: tracked_status_to_proto(index_status),
6764            worktree_status: tracked_status_to_proto(worktree_status),
6765        }),
6766    };
6767    proto::GitFileStatus {
6768        variant: Some(variant),
6769    }
6770}
6771
6772fn unmerged_status_to_proto(code: UnmergedStatusCode) -> i32 {
6773    match code {
6774        UnmergedStatusCode::Added => proto::GitStatus::Added as _,
6775        UnmergedStatusCode::Deleted => proto::GitStatus::Deleted as _,
6776        UnmergedStatusCode::Updated => proto::GitStatus::Updated as _,
6777    }
6778}
6779
6780fn tracked_status_to_proto(code: StatusCode) -> i32 {
6781    match code {
6782        StatusCode::Added => proto::GitStatus::Added as _,
6783        StatusCode::Deleted => proto::GitStatus::Deleted as _,
6784        StatusCode::Modified => proto::GitStatus::Modified as _,
6785        StatusCode::Renamed => proto::GitStatus::Renamed as _,
6786        StatusCode::TypeChanged => proto::GitStatus::TypeChanged as _,
6787        StatusCode::Copied => proto::GitStatus::Copied as _,
6788        StatusCode::Unmodified => proto::GitStatus::Unmodified as _,
6789    }
6790}