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                                .context("sending stash request")?;
4961                            Ok(())
4962                        }
4963                    }
4964                })
4965            })?
4966            .await??;
4967            Ok(())
4968        })
4969    }
4970
4971    pub fn stash_pop(
4972        &mut self,
4973        index: Option<usize>,
4974        cx: &mut Context<Self>,
4975    ) -> Task<anyhow::Result<()>> {
4976        let id = self.id;
4977        cx.spawn(async move |this, cx| {
4978            this.update(cx, |this, _| {
4979                this.send_job(None, move |git_repo, _cx| async move {
4980                    match git_repo {
4981                        RepositoryState::Local(LocalRepositoryState {
4982                            backend,
4983                            environment,
4984                            ..
4985                        }) => backend.stash_pop(index, environment).await,
4986                        RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
4987                            client
4988                                .request(proto::StashPop {
4989                                    project_id: project_id.0,
4990                                    repository_id: id.to_proto(),
4991                                    stash_index: index.map(|i| i as u64),
4992                                })
4993                                .await
4994                                .context("sending stash pop request")?;
4995                            Ok(())
4996                        }
4997                    }
4998                })
4999            })?
5000            .await??;
5001            Ok(())
5002        })
5003    }
5004
5005    pub fn stash_apply(
5006        &mut self,
5007        index: Option<usize>,
5008        cx: &mut Context<Self>,
5009    ) -> Task<anyhow::Result<()>> {
5010        let id = self.id;
5011        cx.spawn(async move |this, cx| {
5012            this.update(cx, |this, _| {
5013                this.send_job(None, move |git_repo, _cx| async move {
5014                    match git_repo {
5015                        RepositoryState::Local(LocalRepositoryState {
5016                            backend,
5017                            environment,
5018                            ..
5019                        }) => backend.stash_apply(index, environment).await,
5020                        RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5021                            client
5022                                .request(proto::StashApply {
5023                                    project_id: project_id.0,
5024                                    repository_id: id.to_proto(),
5025                                    stash_index: index.map(|i| i as u64),
5026                                })
5027                                .await
5028                                .context("sending stash apply request")?;
5029                            Ok(())
5030                        }
5031                    }
5032                })
5033            })?
5034            .await??;
5035            Ok(())
5036        })
5037    }
5038
5039    pub fn stash_drop(
5040        &mut self,
5041        index: Option<usize>,
5042        cx: &mut Context<Self>,
5043    ) -> oneshot::Receiver<anyhow::Result<()>> {
5044        let id = self.id;
5045        let updates_tx = self
5046            .git_store()
5047            .and_then(|git_store| match &git_store.read(cx).state {
5048                GitStoreState::Local { downstream, .. } => downstream
5049                    .as_ref()
5050                    .map(|downstream| downstream.updates_tx.clone()),
5051                _ => None,
5052            });
5053        let this = cx.weak_entity();
5054        self.send_job(None, move |git_repo, mut cx| async move {
5055            match git_repo {
5056                RepositoryState::Local(LocalRepositoryState {
5057                    backend,
5058                    environment,
5059                    ..
5060                }) => {
5061                    // TODO would be nice to not have to do this manually
5062                    let result = backend.stash_drop(index, environment).await;
5063                    if result.is_ok()
5064                        && let Ok(stash_entries) = backend.stash_entries().await
5065                    {
5066                        let snapshot = this.update(&mut cx, |this, cx| {
5067                            this.snapshot.stash_entries = stash_entries;
5068                            cx.emit(RepositoryEvent::StashEntriesChanged);
5069                            this.snapshot.clone()
5070                        })?;
5071                        if let Some(updates_tx) = updates_tx {
5072                            updates_tx
5073                                .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
5074                                .ok();
5075                        }
5076                    }
5077
5078                    result
5079                }
5080                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5081                    client
5082                        .request(proto::StashDrop {
5083                            project_id: project_id.0,
5084                            repository_id: id.to_proto(),
5085                            stash_index: index.map(|i| i as u64),
5086                        })
5087                        .await
5088                        .context("sending stash pop request")?;
5089                    Ok(())
5090                }
5091            }
5092        })
5093    }
5094
5095    pub fn run_hook(&mut self, hook: RunHook, _cx: &mut App) -> oneshot::Receiver<Result<()>> {
5096        let id = self.id;
5097        self.send_job(
5098            Some(format!("git hook {}", hook.as_str()).into()),
5099            move |git_repo, _cx| async move {
5100                match git_repo {
5101                    RepositoryState::Local(LocalRepositoryState {
5102                        backend,
5103                        environment,
5104                        ..
5105                    }) => backend.run_hook(hook, environment.clone()).await,
5106                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5107                        client
5108                            .request(proto::RunGitHook {
5109                                project_id: project_id.0,
5110                                repository_id: id.to_proto(),
5111                                hook: hook.to_proto(),
5112                            })
5113                            .await?;
5114
5115                        Ok(())
5116                    }
5117                }
5118            },
5119        )
5120    }
5121
5122    pub fn commit(
5123        &mut self,
5124        message: SharedString,
5125        name_and_email: Option<(SharedString, SharedString)>,
5126        options: CommitOptions,
5127        askpass: AskPassDelegate,
5128        cx: &mut App,
5129    ) -> oneshot::Receiver<Result<()>> {
5130        let id = self.id;
5131        let askpass_delegates = self.askpass_delegates.clone();
5132        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5133
5134        let rx = self.run_hook(RunHook::PreCommit, cx);
5135
5136        self.send_job(Some("git commit".into()), move |git_repo, _cx| async move {
5137            rx.await??;
5138
5139            match git_repo {
5140                RepositoryState::Local(LocalRepositoryState {
5141                    backend,
5142                    environment,
5143                    ..
5144                }) => {
5145                    backend
5146                        .commit(message, name_and_email, options, askpass, environment)
5147                        .await
5148                }
5149                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5150                    askpass_delegates.lock().insert(askpass_id, askpass);
5151                    let _defer = util::defer(|| {
5152                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5153                        debug_assert!(askpass_delegate.is_some());
5154                    });
5155                    let (name, email) = name_and_email.unzip();
5156                    client
5157                        .request(proto::Commit {
5158                            project_id: project_id.0,
5159                            repository_id: id.to_proto(),
5160                            message: String::from(message),
5161                            name: name.map(String::from),
5162                            email: email.map(String::from),
5163                            options: Some(proto::commit::CommitOptions {
5164                                amend: options.amend,
5165                                signoff: options.signoff,
5166                            }),
5167                            askpass_id,
5168                        })
5169                        .await
5170                        .context("sending commit request")?;
5171
5172                    Ok(())
5173                }
5174            }
5175        })
5176    }
5177
5178    pub fn fetch(
5179        &mut self,
5180        fetch_options: FetchOptions,
5181        askpass: AskPassDelegate,
5182        _cx: &mut App,
5183    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
5184        let askpass_delegates = self.askpass_delegates.clone();
5185        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5186        let id = self.id;
5187
5188        self.send_job(Some("git fetch".into()), move |git_repo, cx| async move {
5189            match git_repo {
5190                RepositoryState::Local(LocalRepositoryState {
5191                    backend,
5192                    environment,
5193                    ..
5194                }) => backend.fetch(fetch_options, askpass, environment, cx).await,
5195                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5196                    askpass_delegates.lock().insert(askpass_id, askpass);
5197                    let _defer = util::defer(|| {
5198                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5199                        debug_assert!(askpass_delegate.is_some());
5200                    });
5201
5202                    let response = client
5203                        .request(proto::Fetch {
5204                            project_id: project_id.0,
5205                            repository_id: id.to_proto(),
5206                            askpass_id,
5207                            remote: fetch_options.to_proto(),
5208                        })
5209                        .await
5210                        .context("sending fetch request")?;
5211
5212                    Ok(RemoteCommandOutput {
5213                        stdout: response.stdout,
5214                        stderr: response.stderr,
5215                    })
5216                }
5217            }
5218        })
5219    }
5220
5221    pub fn push(
5222        &mut self,
5223        branch: SharedString,
5224        remote_branch: SharedString,
5225        remote: SharedString,
5226        options: Option<PushOptions>,
5227        askpass: AskPassDelegate,
5228        cx: &mut Context<Self>,
5229    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
5230        let askpass_delegates = self.askpass_delegates.clone();
5231        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5232        let id = self.id;
5233
5234        let args = options
5235            .map(|option| match option {
5236                PushOptions::SetUpstream => " --set-upstream",
5237                PushOptions::Force => " --force-with-lease",
5238            })
5239            .unwrap_or("");
5240
5241        let updates_tx = self
5242            .git_store()
5243            .and_then(|git_store| match &git_store.read(cx).state {
5244                GitStoreState::Local { downstream, .. } => downstream
5245                    .as_ref()
5246                    .map(|downstream| downstream.updates_tx.clone()),
5247                _ => None,
5248            });
5249
5250        let this = cx.weak_entity();
5251        self.send_job(
5252            Some(format!("git push {} {} {}:{}", args, remote, branch, remote_branch).into()),
5253            move |git_repo, mut cx| async move {
5254                match git_repo {
5255                    RepositoryState::Local(LocalRepositoryState {
5256                        backend,
5257                        environment,
5258                        ..
5259                    }) => {
5260                        let result = backend
5261                            .push(
5262                                branch.to_string(),
5263                                remote_branch.to_string(),
5264                                remote.to_string(),
5265                                options,
5266                                askpass,
5267                                environment.clone(),
5268                                cx.clone(),
5269                            )
5270                            .await;
5271                        // TODO would be nice to not have to do this manually
5272                        if result.is_ok() {
5273                            let branches = backend.branches().await?;
5274                            let branch = branches.into_iter().find(|branch| branch.is_head);
5275                            log::info!("head branch after scan is {branch:?}");
5276                            let snapshot = this.update(&mut cx, |this, cx| {
5277                                this.snapshot.branch = branch;
5278                                cx.emit(RepositoryEvent::BranchChanged);
5279                                this.snapshot.clone()
5280                            })?;
5281                            if let Some(updates_tx) = updates_tx {
5282                                updates_tx
5283                                    .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
5284                                    .ok();
5285                            }
5286                        }
5287                        result
5288                    }
5289                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5290                        askpass_delegates.lock().insert(askpass_id, askpass);
5291                        let _defer = util::defer(|| {
5292                            let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5293                            debug_assert!(askpass_delegate.is_some());
5294                        });
5295                        let response = client
5296                            .request(proto::Push {
5297                                project_id: project_id.0,
5298                                repository_id: id.to_proto(),
5299                                askpass_id,
5300                                branch_name: branch.to_string(),
5301                                remote_branch_name: remote_branch.to_string(),
5302                                remote_name: remote.to_string(),
5303                                options: options.map(|options| match options {
5304                                    PushOptions::Force => proto::push::PushOptions::Force,
5305                                    PushOptions::SetUpstream => {
5306                                        proto::push::PushOptions::SetUpstream
5307                                    }
5308                                }
5309                                    as i32),
5310                            })
5311                            .await
5312                            .context("sending push request")?;
5313
5314                        Ok(RemoteCommandOutput {
5315                            stdout: response.stdout,
5316                            stderr: response.stderr,
5317                        })
5318                    }
5319                }
5320            },
5321        )
5322    }
5323
5324    pub fn pull(
5325        &mut self,
5326        branch: Option<SharedString>,
5327        remote: SharedString,
5328        rebase: bool,
5329        askpass: AskPassDelegate,
5330        _cx: &mut App,
5331    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
5332        let askpass_delegates = self.askpass_delegates.clone();
5333        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
5334        let id = self.id;
5335
5336        let mut status = "git pull".to_string();
5337        if rebase {
5338            status.push_str(" --rebase");
5339        }
5340        status.push_str(&format!(" {}", remote));
5341        if let Some(b) = &branch {
5342            status.push_str(&format!(" {}", b));
5343        }
5344
5345        self.send_job(Some(status.into()), move |git_repo, cx| async move {
5346            match git_repo {
5347                RepositoryState::Local(LocalRepositoryState {
5348                    backend,
5349                    environment,
5350                    ..
5351                }) => {
5352                    backend
5353                        .pull(
5354                            branch.as_ref().map(|b| b.to_string()),
5355                            remote.to_string(),
5356                            rebase,
5357                            askpass,
5358                            environment.clone(),
5359                            cx,
5360                        )
5361                        .await
5362                }
5363                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5364                    askpass_delegates.lock().insert(askpass_id, askpass);
5365                    let _defer = util::defer(|| {
5366                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
5367                        debug_assert!(askpass_delegate.is_some());
5368                    });
5369                    let response = client
5370                        .request(proto::Pull {
5371                            project_id: project_id.0,
5372                            repository_id: id.to_proto(),
5373                            askpass_id,
5374                            rebase,
5375                            branch_name: branch.as_ref().map(|b| b.to_string()),
5376                            remote_name: remote.to_string(),
5377                        })
5378                        .await
5379                        .context("sending pull request")?;
5380
5381                    Ok(RemoteCommandOutput {
5382                        stdout: response.stdout,
5383                        stderr: response.stderr,
5384                    })
5385                }
5386            }
5387        })
5388    }
5389
5390    fn spawn_set_index_text_job(
5391        &mut self,
5392        path: RepoPath,
5393        content: Option<String>,
5394        hunk_staging_operation_count: Option<usize>,
5395        cx: &mut Context<Self>,
5396    ) -> oneshot::Receiver<anyhow::Result<()>> {
5397        let id = self.id;
5398        let this = cx.weak_entity();
5399        let git_store = self.git_store.clone();
5400        let abs_path = self.snapshot.repo_path_to_abs_path(&path);
5401        self.send_keyed_job(
5402            Some(GitJobKey::WriteIndex(vec![path.clone()])),
5403            None,
5404            move |git_repo, mut cx| async move {
5405                log::debug!(
5406                    "start updating index text for buffer {}",
5407                    path.as_unix_str()
5408                );
5409
5410                match git_repo {
5411                    RepositoryState::Local(LocalRepositoryState {
5412                        fs,
5413                        backend,
5414                        environment,
5415                        ..
5416                    }) => {
5417                        let executable = match fs.metadata(&abs_path).await {
5418                            Ok(Some(meta)) => meta.is_executable,
5419                            Ok(None) => false,
5420                            Err(_err) => false,
5421                        };
5422                        backend
5423                            .set_index_text(path.clone(), content, environment.clone(), executable)
5424                            .await?;
5425                    }
5426                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5427                        client
5428                            .request(proto::SetIndexText {
5429                                project_id: project_id.0,
5430                                repository_id: id.to_proto(),
5431                                path: path.to_proto(),
5432                                text: content,
5433                            })
5434                            .await?;
5435                    }
5436                }
5437                log::debug!(
5438                    "finish updating index text for buffer {}",
5439                    path.as_unix_str()
5440                );
5441
5442                if let Some(hunk_staging_operation_count) = hunk_staging_operation_count {
5443                    let project_path = this
5444                        .read_with(&cx, |this, cx| this.repo_path_to_project_path(&path, cx))
5445                        .ok()
5446                        .flatten();
5447                    git_store
5448                        .update(&mut cx, |git_store, cx| {
5449                            let buffer_id = git_store
5450                                .buffer_store
5451                                .read(cx)
5452                                .get_by_path(&project_path?)?
5453                                .read(cx)
5454                                .remote_id();
5455                            let diff_state = git_store.diffs.get(&buffer_id)?;
5456                            diff_state.update(cx, |diff_state, _| {
5457                                diff_state.hunk_staging_operation_count_as_of_write =
5458                                    hunk_staging_operation_count;
5459                            });
5460                            Some(())
5461                        })
5462                        .context("Git store dropped")?;
5463                }
5464                Ok(())
5465            },
5466        )
5467    }
5468
5469    pub fn create_remote(
5470        &mut self,
5471        remote_name: String,
5472        remote_url: String,
5473    ) -> oneshot::Receiver<Result<()>> {
5474        let id = self.id;
5475        self.send_job(
5476            Some(format!("git remote add {remote_name} {remote_url}").into()),
5477            move |repo, _cx| async move {
5478                match repo {
5479                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5480                        backend.create_remote(remote_name, remote_url).await
5481                    }
5482                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5483                        client
5484                            .request(proto::GitCreateRemote {
5485                                project_id: project_id.0,
5486                                repository_id: id.to_proto(),
5487                                remote_name,
5488                                remote_url,
5489                            })
5490                            .await?;
5491
5492                        Ok(())
5493                    }
5494                }
5495            },
5496        )
5497    }
5498
5499    pub fn remove_remote(&mut self, remote_name: String) -> oneshot::Receiver<Result<()>> {
5500        let id = self.id;
5501        self.send_job(
5502            Some(format!("git remove remote {remote_name}").into()),
5503            move |repo, _cx| async move {
5504                match repo {
5505                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5506                        backend.remove_remote(remote_name).await
5507                    }
5508                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5509                        client
5510                            .request(proto::GitRemoveRemote {
5511                                project_id: project_id.0,
5512                                repository_id: id.to_proto(),
5513                                remote_name,
5514                            })
5515                            .await?;
5516
5517                        Ok(())
5518                    }
5519                }
5520            },
5521        )
5522    }
5523
5524    pub fn get_remotes(
5525        &mut self,
5526        branch_name: Option<String>,
5527        is_push: bool,
5528    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
5529        let id = self.id;
5530        self.send_job(None, move |repo, _cx| async move {
5531            match repo {
5532                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5533                    let remote = if let Some(branch_name) = branch_name {
5534                        if is_push {
5535                            backend.get_push_remote(branch_name).await?
5536                        } else {
5537                            backend.get_branch_remote(branch_name).await?
5538                        }
5539                    } else {
5540                        None
5541                    };
5542
5543                    match remote {
5544                        Some(remote) => Ok(vec![remote]),
5545                        None => backend.get_all_remotes().await,
5546                    }
5547                }
5548                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5549                    let response = client
5550                        .request(proto::GetRemotes {
5551                            project_id: project_id.0,
5552                            repository_id: id.to_proto(),
5553                            branch_name,
5554                            is_push,
5555                        })
5556                        .await?;
5557
5558                    let remotes = response
5559                        .remotes
5560                        .into_iter()
5561                        .map(|remotes| Remote {
5562                            name: remotes.name.into(),
5563                        })
5564                        .collect();
5565
5566                    Ok(remotes)
5567                }
5568            }
5569        })
5570    }
5571
5572    pub fn branches(&mut self) -> oneshot::Receiver<Result<Vec<Branch>>> {
5573        let id = self.id;
5574        self.send_job(None, move |repo, _| async move {
5575            match repo {
5576                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5577                    backend.branches().await
5578                }
5579                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5580                    let response = client
5581                        .request(proto::GitGetBranches {
5582                            project_id: project_id.0,
5583                            repository_id: id.to_proto(),
5584                        })
5585                        .await?;
5586
5587                    let branches = response
5588                        .branches
5589                        .into_iter()
5590                        .map(|branch| proto_to_branch(&branch))
5591                        .collect();
5592
5593                    Ok(branches)
5594                }
5595            }
5596        })
5597    }
5598
5599    pub fn worktrees(&mut self) -> oneshot::Receiver<Result<Vec<GitWorktree>>> {
5600        let id = self.id;
5601        self.send_job(None, move |repo, _| async move {
5602            match repo {
5603                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5604                    backend.worktrees().await
5605                }
5606                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5607                    let response = client
5608                        .request(proto::GitGetWorktrees {
5609                            project_id: project_id.0,
5610                            repository_id: id.to_proto(),
5611                        })
5612                        .await?;
5613
5614                    let worktrees = response
5615                        .worktrees
5616                        .into_iter()
5617                        .map(|worktree| proto_to_worktree(&worktree))
5618                        .collect();
5619
5620                    Ok(worktrees)
5621                }
5622            }
5623        })
5624    }
5625
5626    pub fn create_worktree(
5627        &mut self,
5628        name: String,
5629        directory: PathBuf,
5630        commit: Option<String>,
5631    ) -> oneshot::Receiver<Result<()>> {
5632        let id = self.id;
5633        self.send_job(
5634            Some("git worktree add".into()),
5635            move |repo, _cx| async move {
5636                match repo {
5637                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5638                        backend.create_worktree(name, directory, commit).await
5639                    }
5640                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5641                        client
5642                            .request(proto::GitCreateWorktree {
5643                                project_id: project_id.0,
5644                                repository_id: id.to_proto(),
5645                                name,
5646                                directory: directory.to_string_lossy().to_string(),
5647                                commit,
5648                            })
5649                            .await?;
5650
5651                        Ok(())
5652                    }
5653                }
5654            },
5655        )
5656    }
5657
5658    pub fn default_branch(
5659        &mut self,
5660        include_remote_name: bool,
5661    ) -> oneshot::Receiver<Result<Option<SharedString>>> {
5662        let id = self.id;
5663        self.send_job(None, move |repo, _| async move {
5664            match repo {
5665                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5666                    backend.default_branch(include_remote_name).await
5667                }
5668                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5669                    let response = client
5670                        .request(proto::GetDefaultBranch {
5671                            project_id: project_id.0,
5672                            repository_id: id.to_proto(),
5673                        })
5674                        .await?;
5675
5676                    anyhow::Ok(response.branch.map(SharedString::from))
5677                }
5678            }
5679        })
5680    }
5681
5682    pub fn diff_tree(
5683        &mut self,
5684        diff_type: DiffTreeType,
5685        _cx: &App,
5686    ) -> oneshot::Receiver<Result<TreeDiff>> {
5687        let repository_id = self.snapshot.id;
5688        self.send_job(None, move |repo, _cx| async move {
5689            match repo {
5690                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5691                    backend.diff_tree(diff_type).await
5692                }
5693                RepositoryState::Remote(RemoteRepositoryState { client, project_id }) => {
5694                    let response = client
5695                        .request(proto::GetTreeDiff {
5696                            project_id: project_id.0,
5697                            repository_id: repository_id.0,
5698                            is_merge: matches!(diff_type, DiffTreeType::MergeBase { .. }),
5699                            base: diff_type.base().to_string(),
5700                            head: diff_type.head().to_string(),
5701                        })
5702                        .await?;
5703
5704                    let entries = response
5705                        .entries
5706                        .into_iter()
5707                        .filter_map(|entry| {
5708                            let status = match entry.status() {
5709                                proto::tree_diff_status::Status::Added => TreeDiffStatus::Added,
5710                                proto::tree_diff_status::Status::Modified => {
5711                                    TreeDiffStatus::Modified {
5712                                        old: git::Oid::from_str(
5713                                            &entry.oid.context("missing oid").log_err()?,
5714                                        )
5715                                        .log_err()?,
5716                                    }
5717                                }
5718                                proto::tree_diff_status::Status::Deleted => {
5719                                    TreeDiffStatus::Deleted {
5720                                        old: git::Oid::from_str(
5721                                            &entry.oid.context("missing oid").log_err()?,
5722                                        )
5723                                        .log_err()?,
5724                                    }
5725                                }
5726                            };
5727                            Some((
5728                                RepoPath::from_rel_path(
5729                                    &RelPath::from_proto(&entry.path).log_err()?,
5730                                ),
5731                                status,
5732                            ))
5733                        })
5734                        .collect();
5735
5736                    Ok(TreeDiff { entries })
5737                }
5738            }
5739        })
5740    }
5741
5742    pub fn diff(&mut self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
5743        let id = self.id;
5744        self.send_job(None, move |repo, _cx| async move {
5745            match repo {
5746                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5747                    backend.diff(diff_type).await
5748                }
5749                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5750                    let (proto_diff_type, merge_base_ref) = match &diff_type {
5751                        DiffType::HeadToIndex => {
5752                            (proto::git_diff::DiffType::HeadToIndex.into(), None)
5753                        }
5754                        DiffType::HeadToWorktree => {
5755                            (proto::git_diff::DiffType::HeadToWorktree.into(), None)
5756                        }
5757                        DiffType::MergeBase { base_ref } => (
5758                            proto::git_diff::DiffType::MergeBase.into(),
5759                            Some(base_ref.to_string()),
5760                        ),
5761                    };
5762                    let response = client
5763                        .request(proto::GitDiff {
5764                            project_id: project_id.0,
5765                            repository_id: id.to_proto(),
5766                            diff_type: proto_diff_type,
5767                            merge_base_ref,
5768                        })
5769                        .await?;
5770
5771                    Ok(response.diff)
5772                }
5773            }
5774        })
5775    }
5776
5777    /// Fetches per-line diff statistics (additions/deletions) via `git diff --numstat`.
5778    pub fn diff_stat(
5779        &mut self,
5780        diff_type: DiffType,
5781        _cx: &App,
5782    ) -> oneshot::Receiver<
5783        Result<collections::HashMap<git::repository::RepoPath, git::status::DiffStat>>,
5784    > {
5785        let id = self.id;
5786        self.send_job(None, move |repo, _cx| async move {
5787            match repo {
5788                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5789                    backend.diff_stat(diff_type).await
5790                }
5791                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5792                    let (proto_diff_type, merge_base_ref) = match &diff_type {
5793                        DiffType::HeadToIndex => {
5794                            (proto::git_diff_stat::DiffType::HeadToIndex.into(), None)
5795                        }
5796                        DiffType::HeadToWorktree => {
5797                            (proto::git_diff_stat::DiffType::HeadToWorktree.into(), None)
5798                        }
5799                        DiffType::MergeBase { base_ref } => (
5800                            proto::git_diff_stat::DiffType::MergeBase.into(),
5801                            Some(base_ref.to_string()),
5802                        ),
5803                    };
5804                    let response = client
5805                        .request(proto::GitDiffStat {
5806                            project_id: project_id.0,
5807                            repository_id: id.to_proto(),
5808                            diff_type: proto_diff_type,
5809                            merge_base_ref,
5810                        })
5811                        .await?;
5812
5813                    let stats = response
5814                        .entries
5815                        .into_iter()
5816                        .filter_map(|entry| {
5817                            let path = RepoPath::from_proto(&entry.path).log_err()?;
5818                            Some((
5819                                path,
5820                                git::status::DiffStat {
5821                                    added: entry.added,
5822                                    deleted: entry.deleted,
5823                                },
5824                            ))
5825                        })
5826                        .collect();
5827
5828                    Ok(stats)
5829                }
5830            }
5831        })
5832    }
5833
5834    pub fn create_branch(
5835        &mut self,
5836        branch_name: String,
5837        base_branch: Option<String>,
5838    ) -> oneshot::Receiver<Result<()>> {
5839        let id = self.id;
5840        let status_msg = if let Some(ref base) = base_branch {
5841            format!("git switch -c {branch_name} {base}").into()
5842        } else {
5843            format!("git switch -c {branch_name}").into()
5844        };
5845        self.send_job(Some(status_msg), move |repo, _cx| async move {
5846            match repo {
5847                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5848                    backend.create_branch(branch_name, base_branch).await
5849                }
5850                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5851                    client
5852                        .request(proto::GitCreateBranch {
5853                            project_id: project_id.0,
5854                            repository_id: id.to_proto(),
5855                            branch_name,
5856                        })
5857                        .await?;
5858
5859                    Ok(())
5860                }
5861            }
5862        })
5863    }
5864
5865    pub fn change_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
5866        let id = self.id;
5867        self.send_job(
5868            Some(format!("git switch {branch_name}").into()),
5869            move |repo, _cx| async move {
5870                match repo {
5871                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5872                        backend.change_branch(branch_name).await
5873                    }
5874                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5875                        client
5876                            .request(proto::GitChangeBranch {
5877                                project_id: project_id.0,
5878                                repository_id: id.to_proto(),
5879                                branch_name,
5880                            })
5881                            .await?;
5882
5883                        Ok(())
5884                    }
5885                }
5886            },
5887        )
5888    }
5889
5890    pub fn delete_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
5891        let id = self.id;
5892        self.send_job(
5893            Some(format!("git branch -d {branch_name}").into()),
5894            move |repo, _cx| async move {
5895                match repo {
5896                    RepositoryState::Local(state) => state.backend.delete_branch(branch_name).await,
5897                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5898                        client
5899                            .request(proto::GitDeleteBranch {
5900                                project_id: project_id.0,
5901                                repository_id: id.to_proto(),
5902                                branch_name,
5903                            })
5904                            .await?;
5905
5906                        Ok(())
5907                    }
5908                }
5909            },
5910        )
5911    }
5912
5913    pub fn rename_branch(
5914        &mut self,
5915        branch: String,
5916        new_name: String,
5917    ) -> oneshot::Receiver<Result<()>> {
5918        let id = self.id;
5919        self.send_job(
5920            Some(format!("git branch -m {branch} {new_name}").into()),
5921            move |repo, _cx| async move {
5922                match repo {
5923                    RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5924                        backend.rename_branch(branch, new_name).await
5925                    }
5926                    RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5927                        client
5928                            .request(proto::GitRenameBranch {
5929                                project_id: project_id.0,
5930                                repository_id: id.to_proto(),
5931                                branch,
5932                                new_name,
5933                            })
5934                            .await?;
5935
5936                        Ok(())
5937                    }
5938                }
5939            },
5940        )
5941    }
5942
5943    pub fn check_for_pushed_commits(&mut self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
5944        let id = self.id;
5945        self.send_job(None, move |repo, _cx| async move {
5946            match repo {
5947                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5948                    backend.check_for_pushed_commit().await
5949                }
5950                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
5951                    let response = client
5952                        .request(proto::CheckForPushedCommits {
5953                            project_id: project_id.0,
5954                            repository_id: id.to_proto(),
5955                        })
5956                        .await?;
5957
5958                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
5959
5960                    Ok(branches)
5961                }
5962            }
5963        })
5964    }
5965
5966    pub fn checkpoint(&mut self) -> oneshot::Receiver<Result<GitRepositoryCheckpoint>> {
5967        self.send_job(None, |repo, _cx| async move {
5968            match repo {
5969                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5970                    backend.checkpoint().await
5971                }
5972                RepositoryState::Remote(..) => anyhow::bail!("not implemented yet"),
5973            }
5974        })
5975    }
5976
5977    pub fn restore_checkpoint(
5978        &mut self,
5979        checkpoint: GitRepositoryCheckpoint,
5980    ) -> oneshot::Receiver<Result<()>> {
5981        self.send_job(None, move |repo, _cx| async move {
5982            match repo {
5983                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
5984                    backend.restore_checkpoint(checkpoint).await
5985                }
5986                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
5987            }
5988        })
5989    }
5990
5991    pub(crate) fn apply_remote_update(
5992        &mut self,
5993        update: proto::UpdateRepository,
5994        cx: &mut Context<Self>,
5995    ) -> Result<()> {
5996        let new_branch = update.branch_summary.as_ref().map(proto_to_branch);
5997        let new_head_commit = update
5998            .head_commit_details
5999            .as_ref()
6000            .map(proto_to_commit_details);
6001        if self.snapshot.branch != new_branch || self.snapshot.head_commit != new_head_commit {
6002            cx.emit(RepositoryEvent::BranchChanged)
6003        }
6004        self.snapshot.branch = new_branch;
6005        self.snapshot.head_commit = new_head_commit;
6006
6007        // We don't store any merge head state for downstream projects; the upstream
6008        // will track it and we will just get the updated conflicts
6009        let new_merge_heads = TreeMap::from_ordered_entries(
6010            update
6011                .current_merge_conflicts
6012                .into_iter()
6013                .filter_map(|path| Some((RepoPath::from_proto(&path).ok()?, vec![]))),
6014        );
6015        let conflicts_changed =
6016            self.snapshot.merge.merge_heads_by_conflicted_path != new_merge_heads;
6017        self.snapshot.merge.merge_heads_by_conflicted_path = new_merge_heads;
6018        self.snapshot.merge.message = update.merge_message.map(SharedString::from);
6019        let new_stash_entries = GitStash {
6020            entries: update
6021                .stash_entries
6022                .iter()
6023                .filter_map(|entry| proto_to_stash(entry).ok())
6024                .collect(),
6025        };
6026        if self.snapshot.stash_entries != new_stash_entries {
6027            cx.emit(RepositoryEvent::StashEntriesChanged)
6028        }
6029        self.snapshot.stash_entries = new_stash_entries;
6030        self.snapshot.remote_upstream_url = update.remote_upstream_url;
6031        self.snapshot.remote_origin_url = update.remote_origin_url;
6032
6033        let edits = update
6034            .removed_statuses
6035            .into_iter()
6036            .filter_map(|path| {
6037                Some(sum_tree::Edit::Remove(PathKey(
6038                    RelPath::from_proto(&path).log_err()?,
6039                )))
6040            })
6041            .chain(
6042                update
6043                    .updated_statuses
6044                    .into_iter()
6045                    .filter_map(|updated_status| {
6046                        Some(sum_tree::Edit::Insert(updated_status.try_into().log_err()?))
6047                    }),
6048            )
6049            .collect::<Vec<_>>();
6050        if conflicts_changed || !edits.is_empty() {
6051            cx.emit(RepositoryEvent::StatusesChanged);
6052        }
6053        self.snapshot.statuses_by_path.edit(edits, ());
6054        if update.is_last_update {
6055            self.snapshot.scan_id = update.scan_id;
6056        }
6057        self.clear_pending_ops(cx);
6058        Ok(())
6059    }
6060
6061    pub fn compare_checkpoints(
6062        &mut self,
6063        left: GitRepositoryCheckpoint,
6064        right: GitRepositoryCheckpoint,
6065    ) -> oneshot::Receiver<Result<bool>> {
6066        self.send_job(None, move |repo, _cx| async move {
6067            match repo {
6068                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6069                    backend.compare_checkpoints(left, right).await
6070                }
6071                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
6072            }
6073        })
6074    }
6075
6076    pub fn diff_checkpoints(
6077        &mut self,
6078        base_checkpoint: GitRepositoryCheckpoint,
6079        target_checkpoint: GitRepositoryCheckpoint,
6080    ) -> oneshot::Receiver<Result<String>> {
6081        self.send_job(None, move |repo, _cx| async move {
6082            match repo {
6083                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6084                    backend
6085                        .diff_checkpoints(base_checkpoint, target_checkpoint)
6086                        .await
6087                }
6088                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
6089            }
6090        })
6091    }
6092
6093    fn clear_pending_ops(&mut self, cx: &mut Context<Self>) {
6094        let updated = SumTree::from_iter(
6095            self.pending_ops.iter().filter_map(|ops| {
6096                let inner_ops: Vec<PendingOp> =
6097                    ops.ops.iter().filter(|op| op.running()).cloned().collect();
6098                if inner_ops.is_empty() {
6099                    None
6100                } else {
6101                    Some(PendingOps {
6102                        repo_path: ops.repo_path.clone(),
6103                        ops: inner_ops,
6104                    })
6105                }
6106            }),
6107            (),
6108        );
6109
6110        if updated != self.pending_ops {
6111            cx.emit(RepositoryEvent::PendingOpsChanged {
6112                pending_ops: self.pending_ops.clone(),
6113            })
6114        }
6115
6116        self.pending_ops = updated;
6117    }
6118
6119    fn schedule_scan(
6120        &mut self,
6121        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
6122        cx: &mut Context<Self>,
6123    ) {
6124        let this = cx.weak_entity();
6125        let _ = self.send_keyed_job(
6126            Some(GitJobKey::ReloadGitState),
6127            None,
6128            |state, mut cx| async move {
6129                log::debug!("run scheduled git status scan");
6130
6131                let Some(this) = this.upgrade() else {
6132                    return Ok(());
6133                };
6134                let RepositoryState::Local(LocalRepositoryState { backend, .. }) = state else {
6135                    bail!("not a local repository")
6136                };
6137                let compute_snapshot = this.update(&mut cx, |this, _| {
6138                    this.paths_needing_status_update.clear();
6139                    compute_snapshot(
6140                        this.id,
6141                        this.work_directory_abs_path.clone(),
6142                        this.snapshot.clone(),
6143                        backend.clone(),
6144                    )
6145                });
6146                let (snapshot, events) = cx.background_spawn(compute_snapshot).await?;
6147                this.update(&mut cx, |this, cx| {
6148                    this.snapshot = snapshot.clone();
6149                    this.clear_pending_ops(cx);
6150                    for event in events {
6151                        cx.emit(event);
6152                    }
6153                });
6154                if let Some(updates_tx) = updates_tx {
6155                    updates_tx
6156                        .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
6157                        .ok();
6158                }
6159                Ok(())
6160            },
6161        );
6162    }
6163
6164    fn spawn_local_git_worker(
6165        state: Shared<Task<Result<LocalRepositoryState, String>>>,
6166        cx: &mut Context<Self>,
6167    ) -> mpsc::UnboundedSender<GitJob> {
6168        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
6169
6170        cx.spawn(async move |_, cx| {
6171            let state = state.await.map_err(|err| anyhow::anyhow!(err))?;
6172            if let Some(git_hosting_provider_registry) =
6173                cx.update(|cx| GitHostingProviderRegistry::try_global(cx))
6174            {
6175                git_hosting_providers::register_additional_providers(
6176                    git_hosting_provider_registry,
6177                    state.backend.clone(),
6178                )
6179                .await;
6180            }
6181            let state = RepositoryState::Local(state);
6182            let mut jobs = VecDeque::new();
6183            loop {
6184                while let Ok(Some(next_job)) = job_rx.try_next() {
6185                    jobs.push_back(next_job);
6186                }
6187
6188                if let Some(job) = jobs.pop_front() {
6189                    if let Some(current_key) = &job.key
6190                        && jobs
6191                            .iter()
6192                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
6193                    {
6194                        continue;
6195                    }
6196                    (job.job)(state.clone(), cx).await;
6197                } else if let Some(job) = job_rx.next().await {
6198                    jobs.push_back(job);
6199                } else {
6200                    break;
6201                }
6202            }
6203            anyhow::Ok(())
6204        })
6205        .detach_and_log_err(cx);
6206
6207        job_tx
6208    }
6209
6210    fn spawn_remote_git_worker(
6211        state: RemoteRepositoryState,
6212        cx: &mut Context<Self>,
6213    ) -> mpsc::UnboundedSender<GitJob> {
6214        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
6215
6216        cx.spawn(async move |_, cx| {
6217            let state = RepositoryState::Remote(state);
6218            let mut jobs = VecDeque::new();
6219            loop {
6220                while let Ok(Some(next_job)) = job_rx.try_next() {
6221                    jobs.push_back(next_job);
6222                }
6223
6224                if let Some(job) = jobs.pop_front() {
6225                    if let Some(current_key) = &job.key
6226                        && jobs
6227                            .iter()
6228                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
6229                    {
6230                        continue;
6231                    }
6232                    (job.job)(state.clone(), cx).await;
6233                } else if let Some(job) = job_rx.next().await {
6234                    jobs.push_back(job);
6235                } else {
6236                    break;
6237                }
6238            }
6239            anyhow::Ok(())
6240        })
6241        .detach_and_log_err(cx);
6242
6243        job_tx
6244    }
6245
6246    fn load_staged_text(
6247        &mut self,
6248        buffer_id: BufferId,
6249        repo_path: RepoPath,
6250        cx: &App,
6251    ) -> Task<Result<Option<String>>> {
6252        let rx = self.send_job(None, move |state, _| async move {
6253            match state {
6254                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6255                    anyhow::Ok(backend.load_index_text(repo_path).await)
6256                }
6257                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
6258                    let response = client
6259                        .request(proto::OpenUnstagedDiff {
6260                            project_id: project_id.to_proto(),
6261                            buffer_id: buffer_id.to_proto(),
6262                        })
6263                        .await?;
6264                    Ok(response.staged_text)
6265                }
6266            }
6267        });
6268        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
6269    }
6270
6271    fn load_committed_text(
6272        &mut self,
6273        buffer_id: BufferId,
6274        repo_path: RepoPath,
6275        cx: &App,
6276    ) -> Task<Result<DiffBasesChange>> {
6277        let rx = self.send_job(None, move |state, _| async move {
6278            match state {
6279                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6280                    let committed_text = backend.load_committed_text(repo_path.clone()).await;
6281                    let staged_text = backend.load_index_text(repo_path).await;
6282                    let diff_bases_change = if committed_text == staged_text {
6283                        DiffBasesChange::SetBoth(committed_text)
6284                    } else {
6285                        DiffBasesChange::SetEach {
6286                            index: staged_text,
6287                            head: committed_text,
6288                        }
6289                    };
6290                    anyhow::Ok(diff_bases_change)
6291                }
6292                RepositoryState::Remote(RemoteRepositoryState { project_id, client }) => {
6293                    use proto::open_uncommitted_diff_response::Mode;
6294
6295                    let response = client
6296                        .request(proto::OpenUncommittedDiff {
6297                            project_id: project_id.to_proto(),
6298                            buffer_id: buffer_id.to_proto(),
6299                        })
6300                        .await?;
6301                    let mode = Mode::from_i32(response.mode).context("Invalid mode")?;
6302                    let bases = match mode {
6303                        Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
6304                        Mode::IndexAndHead => DiffBasesChange::SetEach {
6305                            head: response.committed_text,
6306                            index: response.staged_text,
6307                        },
6308                    };
6309                    Ok(bases)
6310                }
6311            }
6312        });
6313
6314        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
6315    }
6316
6317    fn load_blob_content(&mut self, oid: Oid, cx: &App) -> Task<Result<String>> {
6318        let repository_id = self.snapshot.id;
6319        let rx = self.send_job(None, move |state, _| async move {
6320            match state {
6321                RepositoryState::Local(LocalRepositoryState { backend, .. }) => {
6322                    backend.load_blob_content(oid).await
6323                }
6324                RepositoryState::Remote(RemoteRepositoryState { client, project_id }) => {
6325                    let response = client
6326                        .request(proto::GetBlobContent {
6327                            project_id: project_id.to_proto(),
6328                            repository_id: repository_id.0,
6329                            oid: oid.to_string(),
6330                        })
6331                        .await?;
6332                    Ok(response.content)
6333                }
6334            }
6335        });
6336        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
6337    }
6338
6339    fn paths_changed(
6340        &mut self,
6341        paths: Vec<RepoPath>,
6342        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
6343        cx: &mut Context<Self>,
6344    ) {
6345        if !paths.is_empty() {
6346            self.paths_needing_status_update.push(paths);
6347        }
6348
6349        let this = cx.weak_entity();
6350        let _ = self.send_keyed_job(
6351            Some(GitJobKey::RefreshStatuses),
6352            None,
6353            |state, mut cx| async move {
6354                let (prev_snapshot, changed_paths) = this.update(&mut cx, |this, _| {
6355                    (
6356                        this.snapshot.clone(),
6357                        mem::take(&mut this.paths_needing_status_update),
6358                    )
6359                })?;
6360                let RepositoryState::Local(LocalRepositoryState { backend, .. }) = state else {
6361                    bail!("not a local repository")
6362                };
6363
6364                if changed_paths.is_empty() {
6365                    return Ok(());
6366                }
6367
6368                let stash_entries = backend.stash_entries().await?;
6369                let changed_path_statuses = cx
6370                    .background_spawn(async move {
6371                        let mut changed_paths =
6372                            changed_paths.into_iter().flatten().collect::<BTreeSet<_>>();
6373                        let statuses = backend
6374                            .status(&changed_paths.iter().cloned().collect::<Vec<_>>())
6375                            .await?;
6376                        let mut changed_path_statuses = Vec::new();
6377                        let prev_statuses = prev_snapshot.statuses_by_path.clone();
6378                        let mut cursor = prev_statuses.cursor::<PathProgress>(());
6379
6380                        for (repo_path, status) in &*statuses.entries {
6381                            changed_paths.remove(repo_path);
6382                            if cursor.seek_forward(&PathTarget::Path(repo_path), Bias::Left)
6383                                && cursor.item().is_some_and(|entry| entry.status == *status)
6384                            {
6385                                continue;
6386                            }
6387
6388                            changed_path_statuses.push(Edit::Insert(StatusEntry {
6389                                repo_path: repo_path.clone(),
6390                                status: *status,
6391                            }));
6392                        }
6393                        let mut cursor = prev_statuses.cursor::<PathProgress>(());
6394                        for path in changed_paths.into_iter() {
6395                            if cursor.seek_forward(&PathTarget::Path(&path), Bias::Left) {
6396                                changed_path_statuses
6397                                    .push(Edit::Remove(PathKey(path.as_ref().clone())));
6398                            }
6399                        }
6400                        anyhow::Ok(changed_path_statuses)
6401                    })
6402                    .await?;
6403
6404                this.update(&mut cx, |this, cx| {
6405                    if this.snapshot.stash_entries != stash_entries {
6406                        cx.emit(RepositoryEvent::StashEntriesChanged);
6407                        this.snapshot.stash_entries = stash_entries;
6408                    }
6409
6410                    if !changed_path_statuses.is_empty() {
6411                        cx.emit(RepositoryEvent::StatusesChanged);
6412                        this.snapshot
6413                            .statuses_by_path
6414                            .edit(changed_path_statuses, ());
6415                        this.snapshot.scan_id += 1;
6416                    }
6417
6418                    if let Some(updates_tx) = updates_tx {
6419                        updates_tx
6420                            .unbounded_send(DownstreamUpdate::UpdateRepository(
6421                                this.snapshot.clone(),
6422                            ))
6423                            .ok();
6424                    }
6425                })
6426            },
6427        );
6428    }
6429
6430    /// currently running git command and when it started
6431    pub fn current_job(&self) -> Option<JobInfo> {
6432        self.active_jobs.values().next().cloned()
6433    }
6434
6435    pub fn barrier(&mut self) -> oneshot::Receiver<()> {
6436        self.send_job(None, |_, _| async {})
6437    }
6438
6439    fn spawn_job_with_tracking<AsyncFn>(
6440        &mut self,
6441        paths: Vec<RepoPath>,
6442        git_status: pending_op::GitStatus,
6443        cx: &mut Context<Self>,
6444        f: AsyncFn,
6445    ) -> Task<Result<()>>
6446    where
6447        AsyncFn: AsyncFnOnce(WeakEntity<Repository>, &mut AsyncApp) -> Result<()> + 'static,
6448    {
6449        let ids = self.new_pending_ops_for_paths(paths, git_status);
6450
6451        cx.spawn(async move |this, cx| {
6452            let (job_status, result) = match f(this.clone(), cx).await {
6453                Ok(()) => (pending_op::JobStatus::Finished, Ok(())),
6454                Err(err) if err.is::<Canceled>() => (pending_op::JobStatus::Skipped, Ok(())),
6455                Err(err) => (pending_op::JobStatus::Error, Err(err)),
6456            };
6457
6458            this.update(cx, |this, _| {
6459                let mut edits = Vec::with_capacity(ids.len());
6460                for (id, entry) in ids {
6461                    if let Some(mut ops) = this
6462                        .pending_ops
6463                        .get(&PathKey(entry.as_ref().clone()), ())
6464                        .cloned()
6465                    {
6466                        if let Some(op) = ops.op_by_id_mut(id) {
6467                            op.job_status = job_status;
6468                        }
6469                        edits.push(sum_tree::Edit::Insert(ops));
6470                    }
6471                }
6472                this.pending_ops.edit(edits, ());
6473            })?;
6474
6475            result
6476        })
6477    }
6478
6479    fn new_pending_ops_for_paths(
6480        &mut self,
6481        paths: Vec<RepoPath>,
6482        git_status: pending_op::GitStatus,
6483    ) -> Vec<(PendingOpId, RepoPath)> {
6484        let mut edits = Vec::with_capacity(paths.len());
6485        let mut ids = Vec::with_capacity(paths.len());
6486        for path in paths {
6487            let mut ops = self
6488                .pending_ops
6489                .get(&PathKey(path.as_ref().clone()), ())
6490                .cloned()
6491                .unwrap_or_else(|| PendingOps::new(&path));
6492            let id = ops.max_id() + 1;
6493            ops.ops.push(PendingOp {
6494                id,
6495                git_status,
6496                job_status: pending_op::JobStatus::Running,
6497            });
6498            edits.push(sum_tree::Edit::Insert(ops));
6499            ids.push((id, path));
6500        }
6501        self.pending_ops.edit(edits, ());
6502        ids
6503    }
6504    pub fn default_remote_url(&self) -> Option<String> {
6505        self.remote_upstream_url
6506            .clone()
6507            .or(self.remote_origin_url.clone())
6508    }
6509}
6510
6511fn get_permalink_in_rust_registry_src(
6512    provider_registry: Arc<GitHostingProviderRegistry>,
6513    path: PathBuf,
6514    selection: Range<u32>,
6515) -> Result<url::Url> {
6516    #[derive(Deserialize)]
6517    struct CargoVcsGit {
6518        sha1: String,
6519    }
6520
6521    #[derive(Deserialize)]
6522    struct CargoVcsInfo {
6523        git: CargoVcsGit,
6524        path_in_vcs: String,
6525    }
6526
6527    #[derive(Deserialize)]
6528    struct CargoPackage {
6529        repository: String,
6530    }
6531
6532    #[derive(Deserialize)]
6533    struct CargoToml {
6534        package: CargoPackage,
6535    }
6536
6537    let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
6538        let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
6539        Some((dir, json))
6540    }) else {
6541        bail!("No .cargo_vcs_info.json found in parent directories")
6542    };
6543    let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
6544    let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
6545    let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
6546    let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
6547        .context("parsing package.repository field of manifest")?;
6548    let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
6549    let permalink = provider.build_permalink(
6550        remote,
6551        BuildPermalinkParams::new(
6552            &cargo_vcs_info.git.sha1,
6553            &RepoPath::from_rel_path(
6554                &RelPath::new(&path, PathStyle::local()).context("invalid path")?,
6555            ),
6556            Some(selection),
6557        ),
6558    );
6559    Ok(permalink)
6560}
6561
6562fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::BlameBufferResponse {
6563    let Some(blame) = blame else {
6564        return proto::BlameBufferResponse {
6565            blame_response: None,
6566        };
6567    };
6568
6569    let entries = blame
6570        .entries
6571        .into_iter()
6572        .map(|entry| proto::BlameEntry {
6573            sha: entry.sha.as_bytes().into(),
6574            start_line: entry.range.start,
6575            end_line: entry.range.end,
6576            original_line_number: entry.original_line_number,
6577            author: entry.author,
6578            author_mail: entry.author_mail,
6579            author_time: entry.author_time,
6580            author_tz: entry.author_tz,
6581            committer: entry.committer_name,
6582            committer_mail: entry.committer_email,
6583            committer_time: entry.committer_time,
6584            committer_tz: entry.committer_tz,
6585            summary: entry.summary,
6586            previous: entry.previous,
6587            filename: entry.filename,
6588        })
6589        .collect::<Vec<_>>();
6590
6591    let messages = blame
6592        .messages
6593        .into_iter()
6594        .map(|(oid, message)| proto::CommitMessage {
6595            oid: oid.as_bytes().into(),
6596            message,
6597        })
6598        .collect::<Vec<_>>();
6599
6600    proto::BlameBufferResponse {
6601        blame_response: Some(proto::blame_buffer_response::BlameResponse { entries, messages }),
6602    }
6603}
6604
6605fn deserialize_blame_buffer_response(
6606    response: proto::BlameBufferResponse,
6607) -> Option<git::blame::Blame> {
6608    let response = response.blame_response?;
6609    let entries = response
6610        .entries
6611        .into_iter()
6612        .filter_map(|entry| {
6613            Some(git::blame::BlameEntry {
6614                sha: git::Oid::from_bytes(&entry.sha).ok()?,
6615                range: entry.start_line..entry.end_line,
6616                original_line_number: entry.original_line_number,
6617                committer_name: entry.committer,
6618                committer_time: entry.committer_time,
6619                committer_tz: entry.committer_tz,
6620                committer_email: entry.committer_mail,
6621                author: entry.author,
6622                author_mail: entry.author_mail,
6623                author_time: entry.author_time,
6624                author_tz: entry.author_tz,
6625                summary: entry.summary,
6626                previous: entry.previous,
6627                filename: entry.filename,
6628            })
6629        })
6630        .collect::<Vec<_>>();
6631
6632    let messages = response
6633        .messages
6634        .into_iter()
6635        .filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
6636        .collect::<HashMap<_, _>>();
6637
6638    Some(Blame { entries, messages })
6639}
6640
6641fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch {
6642    proto::Branch {
6643        is_head: branch.is_head,
6644        ref_name: branch.ref_name.to_string(),
6645        unix_timestamp: branch
6646            .most_recent_commit
6647            .as_ref()
6648            .map(|commit| commit.commit_timestamp as u64),
6649        upstream: branch.upstream.as_ref().map(|upstream| proto::GitUpstream {
6650            ref_name: upstream.ref_name.to_string(),
6651            tracking: upstream
6652                .tracking
6653                .status()
6654                .map(|upstream| proto::UpstreamTracking {
6655                    ahead: upstream.ahead as u64,
6656                    behind: upstream.behind as u64,
6657                }),
6658        }),
6659        most_recent_commit: branch
6660            .most_recent_commit
6661            .as_ref()
6662            .map(|commit| proto::CommitSummary {
6663                sha: commit.sha.to_string(),
6664                subject: commit.subject.to_string(),
6665                commit_timestamp: commit.commit_timestamp,
6666                author_name: commit.author_name.to_string(),
6667            }),
6668    }
6669}
6670
6671fn worktree_to_proto(worktree: &git::repository::Worktree) -> proto::Worktree {
6672    proto::Worktree {
6673        path: worktree.path.to_string_lossy().to_string(),
6674        ref_name: worktree.ref_name.to_string(),
6675        sha: worktree.sha.to_string(),
6676    }
6677}
6678
6679fn proto_to_worktree(proto: &proto::Worktree) -> git::repository::Worktree {
6680    git::repository::Worktree {
6681        path: PathBuf::from(proto.path.clone()),
6682        ref_name: proto.ref_name.clone().into(),
6683        sha: proto.sha.clone().into(),
6684    }
6685}
6686
6687fn proto_to_branch(proto: &proto::Branch) -> git::repository::Branch {
6688    git::repository::Branch {
6689        is_head: proto.is_head,
6690        ref_name: proto.ref_name.clone().into(),
6691        upstream: proto
6692            .upstream
6693            .as_ref()
6694            .map(|upstream| git::repository::Upstream {
6695                ref_name: upstream.ref_name.to_string().into(),
6696                tracking: upstream
6697                    .tracking
6698                    .as_ref()
6699                    .map(|tracking| {
6700                        git::repository::UpstreamTracking::Tracked(UpstreamTrackingStatus {
6701                            ahead: tracking.ahead as u32,
6702                            behind: tracking.behind as u32,
6703                        })
6704                    })
6705                    .unwrap_or(git::repository::UpstreamTracking::Gone),
6706            }),
6707        most_recent_commit: proto.most_recent_commit.as_ref().map(|commit| {
6708            git::repository::CommitSummary {
6709                sha: commit.sha.to_string().into(),
6710                subject: commit.subject.to_string().into(),
6711                commit_timestamp: commit.commit_timestamp,
6712                author_name: commit.author_name.to_string().into(),
6713                has_parent: true,
6714            }
6715        }),
6716    }
6717}
6718
6719fn commit_details_to_proto(commit: &CommitDetails) -> proto::GitCommitDetails {
6720    proto::GitCommitDetails {
6721        sha: commit.sha.to_string(),
6722        message: commit.message.to_string(),
6723        commit_timestamp: commit.commit_timestamp,
6724        author_email: commit.author_email.to_string(),
6725        author_name: commit.author_name.to_string(),
6726    }
6727}
6728
6729fn proto_to_commit_details(proto: &proto::GitCommitDetails) -> CommitDetails {
6730    CommitDetails {
6731        sha: proto.sha.clone().into(),
6732        message: proto.message.clone().into(),
6733        commit_timestamp: proto.commit_timestamp,
6734        author_email: proto.author_email.clone().into(),
6735        author_name: proto.author_name.clone().into(),
6736    }
6737}
6738
6739async fn compute_snapshot(
6740    id: RepositoryId,
6741    work_directory_abs_path: Arc<Path>,
6742    prev_snapshot: RepositorySnapshot,
6743    backend: Arc<dyn GitRepository>,
6744) -> Result<(RepositorySnapshot, Vec<RepositoryEvent>)> {
6745    let mut events = Vec::new();
6746    let branches = backend.branches().await?;
6747    let branch = branches.into_iter().find(|branch| branch.is_head);
6748    let statuses = backend
6749        .status(&[RepoPath::from_rel_path(
6750            &RelPath::new(".".as_ref(), PathStyle::local()).unwrap(),
6751        )])
6752        .await?;
6753    let stash_entries = backend.stash_entries().await?;
6754    let mut conflicted_paths = Vec::new();
6755    let statuses_by_path = SumTree::from_iter(
6756        statuses.entries.iter().map(|(repo_path, status)| {
6757            if status.is_conflicted() {
6758                conflicted_paths.push(repo_path.clone());
6759            }
6760            StatusEntry {
6761                repo_path: repo_path.clone(),
6762                status: *status,
6763            }
6764        }),
6765        (),
6766    );
6767    let mut merge_details = prev_snapshot.merge;
6768    let conflicts_changed = merge_details.update(&backend, conflicted_paths).await?;
6769    log::debug!("new merge details: {merge_details:?}");
6770
6771    if conflicts_changed || statuses_by_path != prev_snapshot.statuses_by_path {
6772        events.push(RepositoryEvent::StatusesChanged)
6773    }
6774
6775    // Useful when branch is None in detached head state
6776    let head_commit = match backend.head_sha().await {
6777        Some(head_sha) => backend.show(head_sha).await.log_err(),
6778        None => None,
6779    };
6780
6781    if branch != prev_snapshot.branch || head_commit != prev_snapshot.head_commit {
6782        events.push(RepositoryEvent::BranchChanged);
6783    }
6784
6785    let remote_origin_url = backend.remote_url("origin").await;
6786    let remote_upstream_url = backend.remote_url("upstream").await;
6787
6788    let snapshot = RepositorySnapshot {
6789        id,
6790        statuses_by_path,
6791        work_directory_abs_path,
6792        path_style: prev_snapshot.path_style,
6793        scan_id: prev_snapshot.scan_id + 1,
6794        branch,
6795        head_commit,
6796        merge: merge_details,
6797        remote_origin_url,
6798        remote_upstream_url,
6799        stash_entries,
6800    };
6801
6802    Ok((snapshot, events))
6803}
6804
6805fn status_from_proto(
6806    simple_status: i32,
6807    status: Option<proto::GitFileStatus>,
6808) -> anyhow::Result<FileStatus> {
6809    use proto::git_file_status::Variant;
6810
6811    let Some(variant) = status.and_then(|status| status.variant) else {
6812        let code = proto::GitStatus::from_i32(simple_status)
6813            .with_context(|| format!("Invalid git status code: {simple_status}"))?;
6814        let result = match code {
6815            proto::GitStatus::Added => TrackedStatus {
6816                worktree_status: StatusCode::Added,
6817                index_status: StatusCode::Unmodified,
6818            }
6819            .into(),
6820            proto::GitStatus::Modified => TrackedStatus {
6821                worktree_status: StatusCode::Modified,
6822                index_status: StatusCode::Unmodified,
6823            }
6824            .into(),
6825            proto::GitStatus::Conflict => UnmergedStatus {
6826                first_head: UnmergedStatusCode::Updated,
6827                second_head: UnmergedStatusCode::Updated,
6828            }
6829            .into(),
6830            proto::GitStatus::Deleted => TrackedStatus {
6831                worktree_status: StatusCode::Deleted,
6832                index_status: StatusCode::Unmodified,
6833            }
6834            .into(),
6835            _ => anyhow::bail!("Invalid code for simple status: {simple_status}"),
6836        };
6837        return Ok(result);
6838    };
6839
6840    let result = match variant {
6841        Variant::Untracked(_) => FileStatus::Untracked,
6842        Variant::Ignored(_) => FileStatus::Ignored,
6843        Variant::Unmerged(unmerged) => {
6844            let [first_head, second_head] =
6845                [unmerged.first_head, unmerged.second_head].map(|head| {
6846                    let code = proto::GitStatus::from_i32(head)
6847                        .with_context(|| format!("Invalid git status code: {head}"))?;
6848                    let result = match code {
6849                        proto::GitStatus::Added => UnmergedStatusCode::Added,
6850                        proto::GitStatus::Updated => UnmergedStatusCode::Updated,
6851                        proto::GitStatus::Deleted => UnmergedStatusCode::Deleted,
6852                        _ => anyhow::bail!("Invalid code for unmerged status: {code:?}"),
6853                    };
6854                    Ok(result)
6855                });
6856            let [first_head, second_head] = [first_head?, second_head?];
6857            UnmergedStatus {
6858                first_head,
6859                second_head,
6860            }
6861            .into()
6862        }
6863        Variant::Tracked(tracked) => {
6864            let [index_status, worktree_status] = [tracked.index_status, tracked.worktree_status]
6865                .map(|status| {
6866                    let code = proto::GitStatus::from_i32(status)
6867                        .with_context(|| format!("Invalid git status code: {status}"))?;
6868                    let result = match code {
6869                        proto::GitStatus::Modified => StatusCode::Modified,
6870                        proto::GitStatus::TypeChanged => StatusCode::TypeChanged,
6871                        proto::GitStatus::Added => StatusCode::Added,
6872                        proto::GitStatus::Deleted => StatusCode::Deleted,
6873                        proto::GitStatus::Renamed => StatusCode::Renamed,
6874                        proto::GitStatus::Copied => StatusCode::Copied,
6875                        proto::GitStatus::Unmodified => StatusCode::Unmodified,
6876                        _ => anyhow::bail!("Invalid code for tracked status: {code:?}"),
6877                    };
6878                    Ok(result)
6879                });
6880            let [index_status, worktree_status] = [index_status?, worktree_status?];
6881            TrackedStatus {
6882                index_status,
6883                worktree_status,
6884            }
6885            .into()
6886        }
6887    };
6888    Ok(result)
6889}
6890
6891fn status_to_proto(status: FileStatus) -> proto::GitFileStatus {
6892    use proto::git_file_status::{Tracked, Unmerged, Variant};
6893
6894    let variant = match status {
6895        FileStatus::Untracked => Variant::Untracked(Default::default()),
6896        FileStatus::Ignored => Variant::Ignored(Default::default()),
6897        FileStatus::Unmerged(UnmergedStatus {
6898            first_head,
6899            second_head,
6900        }) => Variant::Unmerged(Unmerged {
6901            first_head: unmerged_status_to_proto(first_head),
6902            second_head: unmerged_status_to_proto(second_head),
6903        }),
6904        FileStatus::Tracked(TrackedStatus {
6905            index_status,
6906            worktree_status,
6907        }) => Variant::Tracked(Tracked {
6908            index_status: tracked_status_to_proto(index_status),
6909            worktree_status: tracked_status_to_proto(worktree_status),
6910        }),
6911    };
6912    proto::GitFileStatus {
6913        variant: Some(variant),
6914    }
6915}
6916
6917fn unmerged_status_to_proto(code: UnmergedStatusCode) -> i32 {
6918    match code {
6919        UnmergedStatusCode::Added => proto::GitStatus::Added as _,
6920        UnmergedStatusCode::Deleted => proto::GitStatus::Deleted as _,
6921        UnmergedStatusCode::Updated => proto::GitStatus::Updated as _,
6922    }
6923}
6924
6925fn tracked_status_to_proto(code: StatusCode) -> i32 {
6926    match code {
6927        StatusCode::Added => proto::GitStatus::Added as _,
6928        StatusCode::Deleted => proto::GitStatus::Deleted as _,
6929        StatusCode::Modified => proto::GitStatus::Modified as _,
6930        StatusCode::Renamed => proto::GitStatus::Renamed as _,
6931        StatusCode::TypeChanged => proto::GitStatus::TypeChanged as _,
6932        StatusCode::Copied => proto::GitStatus::Copied as _,
6933        StatusCode::Unmodified => proto::GitStatus::Unmodified as _,
6934    }
6935}