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