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