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