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, PendingOpId, PendingOpStatus};
  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: SumTree<PendingOp>,
 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: 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_op_by_id(&self, id: PendingOpId) -> Option<PendingOp> {
3082        self.pending_ops.get(&id, ()).cloned()
3083    }
3084
3085    pub fn pending_ops_for_path(&self, path: &RepoPath) -> Vec<PendingOp> {
3086        self.pending_ops
3087            .filter::<_, PathKey>((), |sum| sum.max_path == path.0)
3088            .into_iter()
3089            .map(Clone::clone)
3090            .collect()
3091    }
3092
3093    pub fn abs_path_to_repo_path(&self, abs_path: &Path) -> Option<RepoPath> {
3094        Self::abs_path_to_repo_path_inner(&self.work_directory_abs_path, abs_path, self.path_style)
3095    }
3096
3097    fn repo_path_to_abs_path(&self, repo_path: &RepoPath) -> PathBuf {
3098        self.path_style
3099            .join(&self.work_directory_abs_path, repo_path.as_std_path())
3100            .unwrap()
3101            .into()
3102    }
3103
3104    #[inline]
3105    fn abs_path_to_repo_path_inner(
3106        work_directory_abs_path: &Path,
3107        abs_path: &Path,
3108        path_style: PathStyle,
3109    ) -> Option<RepoPath> {
3110        abs_path
3111            .strip_prefix(&work_directory_abs_path)
3112            .ok()
3113            .and_then(|path| RepoPath::from_std_path(path, path_style).ok())
3114    }
3115
3116    pub fn had_conflict_on_last_merge_head_change(&self, repo_path: &RepoPath) -> bool {
3117        self.merge.conflicted_paths.contains(repo_path)
3118    }
3119
3120    pub fn has_conflict(&self, repo_path: &RepoPath) -> bool {
3121        let had_conflict_on_last_merge_head_change =
3122            self.merge.conflicted_paths.contains(repo_path);
3123        let has_conflict_currently = self
3124            .status_for_path(repo_path)
3125            .is_some_and(|entry| entry.status.is_conflicted());
3126        had_conflict_on_last_merge_head_change || has_conflict_currently
3127    }
3128
3129    /// This is the name that will be displayed in the repository selector for this repository.
3130    pub fn display_name(&self) -> SharedString {
3131        self.work_directory_abs_path
3132            .file_name()
3133            .unwrap_or_default()
3134            .to_string_lossy()
3135            .to_string()
3136            .into()
3137    }
3138}
3139
3140pub fn stash_to_proto(entry: &StashEntry) -> proto::StashEntry {
3141    proto::StashEntry {
3142        oid: entry.oid.as_bytes().to_vec(),
3143        message: entry.message.clone(),
3144        branch: entry.branch.clone(),
3145        index: entry.index as u64,
3146        timestamp: entry.timestamp,
3147    }
3148}
3149
3150pub fn proto_to_stash(entry: &proto::StashEntry) -> Result<StashEntry> {
3151    Ok(StashEntry {
3152        oid: Oid::from_bytes(&entry.oid)?,
3153        message: entry.message.clone(),
3154        index: entry.index as usize,
3155        branch: entry.branch.clone(),
3156        timestamp: entry.timestamp,
3157    })
3158}
3159
3160impl MergeDetails {
3161    async fn load(
3162        backend: &Arc<dyn GitRepository>,
3163        status: &SumTree<StatusEntry>,
3164        prev_snapshot: &RepositorySnapshot,
3165    ) -> Result<(MergeDetails, bool)> {
3166        log::debug!("load merge details");
3167        let message = backend.merge_message().await;
3168        let heads = backend
3169            .revparse_batch(vec![
3170                "MERGE_HEAD".into(),
3171                "CHERRY_PICK_HEAD".into(),
3172                "REBASE_HEAD".into(),
3173                "REVERT_HEAD".into(),
3174                "APPLY_HEAD".into(),
3175            ])
3176            .await
3177            .log_err()
3178            .unwrap_or_default()
3179            .into_iter()
3180            .map(|opt| opt.map(SharedString::from))
3181            .collect::<Vec<_>>();
3182        let merge_heads_changed = heads != prev_snapshot.merge.heads;
3183        let conflicted_paths = if merge_heads_changed {
3184            let current_conflicted_paths = TreeSet::from_ordered_entries(
3185                status
3186                    .iter()
3187                    .filter(|entry| entry.status.is_conflicted())
3188                    .map(|entry| entry.repo_path.clone()),
3189            );
3190
3191            // It can happen that we run a scan while a lengthy merge is in progress
3192            // that will eventually result in conflicts, but before those conflicts
3193            // are reported by `git status`. Since for the moment we only care about
3194            // the merge heads state for the purposes of tracking conflicts, don't update
3195            // this state until we see some conflicts.
3196            if heads.iter().any(Option::is_some)
3197                && !prev_snapshot.merge.heads.iter().any(Option::is_some)
3198                && current_conflicted_paths.is_empty()
3199            {
3200                log::debug!("not updating merge heads because no conflicts found");
3201                return Ok((
3202                    MergeDetails {
3203                        message: message.map(SharedString::from),
3204                        ..prev_snapshot.merge.clone()
3205                    },
3206                    false,
3207                ));
3208            }
3209
3210            current_conflicted_paths
3211        } else {
3212            prev_snapshot.merge.conflicted_paths.clone()
3213        };
3214        let details = MergeDetails {
3215            conflicted_paths,
3216            message: message.map(SharedString::from),
3217            heads,
3218        };
3219        Ok((details, merge_heads_changed))
3220    }
3221}
3222
3223impl Repository {
3224    pub fn snapshot(&self) -> RepositorySnapshot {
3225        self.snapshot.clone()
3226    }
3227
3228    fn local(
3229        id: RepositoryId,
3230        work_directory_abs_path: Arc<Path>,
3231        dot_git_abs_path: Arc<Path>,
3232        repository_dir_abs_path: Arc<Path>,
3233        common_dir_abs_path: Arc<Path>,
3234        project_environment: WeakEntity<ProjectEnvironment>,
3235        fs: Arc<dyn Fs>,
3236        git_store: WeakEntity<GitStore>,
3237        cx: &mut Context<Self>,
3238    ) -> Self {
3239        let snapshot =
3240            RepositorySnapshot::empty(id, work_directory_abs_path.clone(), PathStyle::local());
3241        Repository {
3242            this: cx.weak_entity(),
3243            git_store,
3244            snapshot,
3245            commit_message_buffer: None,
3246            askpass_delegates: Default::default(),
3247            paths_needing_status_update: Default::default(),
3248            latest_askpass_id: 0,
3249            job_sender: Repository::spawn_local_git_worker(
3250                work_directory_abs_path,
3251                dot_git_abs_path,
3252                repository_dir_abs_path,
3253                common_dir_abs_path,
3254                project_environment,
3255                fs,
3256                cx,
3257            ),
3258            job_id: 0,
3259            active_jobs: Default::default(),
3260        }
3261    }
3262
3263    fn remote(
3264        id: RepositoryId,
3265        work_directory_abs_path: Arc<Path>,
3266        path_style: PathStyle,
3267        project_id: ProjectId,
3268        client: AnyProtoClient,
3269        git_store: WeakEntity<GitStore>,
3270        cx: &mut Context<Self>,
3271    ) -> Self {
3272        let snapshot = RepositorySnapshot::empty(id, work_directory_abs_path, path_style);
3273        Self {
3274            this: cx.weak_entity(),
3275            snapshot,
3276            commit_message_buffer: None,
3277            git_store,
3278            paths_needing_status_update: Default::default(),
3279            job_sender: Self::spawn_remote_git_worker(project_id, client, cx),
3280            askpass_delegates: Default::default(),
3281            latest_askpass_id: 0,
3282            active_jobs: Default::default(),
3283            job_id: 0,
3284        }
3285    }
3286
3287    pub fn git_store(&self) -> Option<Entity<GitStore>> {
3288        self.git_store.upgrade()
3289    }
3290
3291    fn reload_buffer_diff_bases(&mut self, cx: &mut Context<Self>) {
3292        let this = cx.weak_entity();
3293        let git_store = self.git_store.clone();
3294        let _ = self.send_keyed_job(
3295            Some(GitJobKey::ReloadBufferDiffBases),
3296            None,
3297            |state, mut cx| async move {
3298                let RepositoryState::Local { backend, .. } = state else {
3299                    log::error!("tried to recompute diffs for a non-local repository");
3300                    return Ok(());
3301                };
3302
3303                let Some(this) = this.upgrade() else {
3304                    return Ok(());
3305                };
3306
3307                let repo_diff_state_updates = this.update(&mut cx, |this, cx| {
3308                    git_store.update(cx, |git_store, cx| {
3309                        git_store
3310                            .diffs
3311                            .iter()
3312                            .filter_map(|(buffer_id, diff_state)| {
3313                                let buffer_store = git_store.buffer_store.read(cx);
3314                                let buffer = buffer_store.get(*buffer_id)?;
3315                                let file = File::from_dyn(buffer.read(cx).file())?;
3316                                let abs_path = file.worktree.read(cx).absolutize(&file.path);
3317                                let repo_path = this.abs_path_to_repo_path(&abs_path)?;
3318                                log::debug!(
3319                                    "start reload diff bases for repo path {}",
3320                                    repo_path.as_unix_str()
3321                                );
3322                                diff_state.update(cx, |diff_state, _| {
3323                                    let has_unstaged_diff = diff_state
3324                                        .unstaged_diff
3325                                        .as_ref()
3326                                        .is_some_and(|diff| diff.is_upgradable());
3327                                    let has_uncommitted_diff = diff_state
3328                                        .uncommitted_diff
3329                                        .as_ref()
3330                                        .is_some_and(|set| set.is_upgradable());
3331
3332                                    Some((
3333                                        buffer,
3334                                        repo_path,
3335                                        has_unstaged_diff.then(|| diff_state.index_text.clone()),
3336                                        has_uncommitted_diff.then(|| diff_state.head_text.clone()),
3337                                    ))
3338                                })
3339                            })
3340                            .collect::<Vec<_>>()
3341                    })
3342                })??;
3343
3344                let buffer_diff_base_changes = cx
3345                    .background_spawn(async move {
3346                        let mut changes = Vec::new();
3347                        for (buffer, repo_path, current_index_text, current_head_text) in
3348                            &repo_diff_state_updates
3349                        {
3350                            let index_text = if current_index_text.is_some() {
3351                                backend.load_index_text(repo_path.clone()).await
3352                            } else {
3353                                None
3354                            };
3355                            let head_text = if current_head_text.is_some() {
3356                                backend.load_committed_text(repo_path.clone()).await
3357                            } else {
3358                                None
3359                            };
3360
3361                            let change =
3362                                match (current_index_text.as_ref(), current_head_text.as_ref()) {
3363                                    (Some(current_index), Some(current_head)) => {
3364                                        let index_changed =
3365                                            index_text.as_ref() != current_index.as_deref();
3366                                        let head_changed =
3367                                            head_text.as_ref() != current_head.as_deref();
3368                                        if index_changed && head_changed {
3369                                            if index_text == head_text {
3370                                                Some(DiffBasesChange::SetBoth(head_text))
3371                                            } else {
3372                                                Some(DiffBasesChange::SetEach {
3373                                                    index: index_text,
3374                                                    head: head_text,
3375                                                })
3376                                            }
3377                                        } else if index_changed {
3378                                            Some(DiffBasesChange::SetIndex(index_text))
3379                                        } else if head_changed {
3380                                            Some(DiffBasesChange::SetHead(head_text))
3381                                        } else {
3382                                            None
3383                                        }
3384                                    }
3385                                    (Some(current_index), None) => {
3386                                        let index_changed =
3387                                            index_text.as_ref() != current_index.as_deref();
3388                                        index_changed
3389                                            .then_some(DiffBasesChange::SetIndex(index_text))
3390                                    }
3391                                    (None, Some(current_head)) => {
3392                                        let head_changed =
3393                                            head_text.as_ref() != current_head.as_deref();
3394                                        head_changed.then_some(DiffBasesChange::SetHead(head_text))
3395                                    }
3396                                    (None, None) => None,
3397                                };
3398
3399                            changes.push((buffer.clone(), change))
3400                        }
3401                        changes
3402                    })
3403                    .await;
3404
3405                git_store.update(&mut cx, |git_store, cx| {
3406                    for (buffer, diff_bases_change) in buffer_diff_base_changes {
3407                        let buffer_snapshot = buffer.read(cx).text_snapshot();
3408                        let buffer_id = buffer_snapshot.remote_id();
3409                        let Some(diff_state) = git_store.diffs.get(&buffer_id) else {
3410                            continue;
3411                        };
3412
3413                        let downstream_client = git_store.downstream_client();
3414                        diff_state.update(cx, |diff_state, cx| {
3415                            use proto::update_diff_bases::Mode;
3416
3417                            if let Some((diff_bases_change, (client, project_id))) =
3418                                diff_bases_change.clone().zip(downstream_client)
3419                            {
3420                                let (staged_text, committed_text, mode) = match diff_bases_change {
3421                                    DiffBasesChange::SetIndex(index) => {
3422                                        (index, None, Mode::IndexOnly)
3423                                    }
3424                                    DiffBasesChange::SetHead(head) => (None, head, Mode::HeadOnly),
3425                                    DiffBasesChange::SetEach { index, head } => {
3426                                        (index, head, Mode::IndexAndHead)
3427                                    }
3428                                    DiffBasesChange::SetBoth(text) => {
3429                                        (None, text, Mode::IndexMatchesHead)
3430                                    }
3431                                };
3432                                client
3433                                    .send(proto::UpdateDiffBases {
3434                                        project_id: project_id.to_proto(),
3435                                        buffer_id: buffer_id.to_proto(),
3436                                        staged_text,
3437                                        committed_text,
3438                                        mode: mode as i32,
3439                                    })
3440                                    .log_err();
3441                            }
3442
3443                            diff_state.diff_bases_changed(buffer_snapshot, diff_bases_change, cx);
3444                        });
3445                    }
3446                })
3447            },
3448        );
3449    }
3450
3451    pub fn send_job<F, Fut, R>(
3452        &mut self,
3453        status: Option<SharedString>,
3454        job: F,
3455    ) -> oneshot::Receiver<R>
3456    where
3457        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
3458        Fut: Future<Output = R> + 'static,
3459        R: Send + 'static,
3460    {
3461        self.send_keyed_job(None, status, job)
3462    }
3463
3464    fn send_keyed_job<F, Fut, R>(
3465        &mut self,
3466        key: Option<GitJobKey>,
3467        status: Option<SharedString>,
3468        job: F,
3469    ) -> oneshot::Receiver<R>
3470    where
3471        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
3472        Fut: Future<Output = R> + 'static,
3473        R: Send + 'static,
3474    {
3475        let (result_tx, result_rx) = futures::channel::oneshot::channel();
3476        let job_id = post_inc(&mut self.job_id);
3477        let this = self.this.clone();
3478        self.job_sender
3479            .unbounded_send(GitJob {
3480                key,
3481                job: Box::new(move |state, cx: &mut AsyncApp| {
3482                    let job = job(state, cx.clone());
3483                    cx.spawn(async move |cx| {
3484                        if let Some(s) = status.clone() {
3485                            this.update(cx, |this, cx| {
3486                                this.active_jobs.insert(
3487                                    job_id,
3488                                    JobInfo {
3489                                        start: Instant::now(),
3490                                        message: s.clone(),
3491                                    },
3492                                );
3493
3494                                cx.notify();
3495                            })
3496                            .ok();
3497                        }
3498                        let result = job.await;
3499
3500                        this.update(cx, |this, cx| {
3501                            this.active_jobs.remove(&job_id);
3502                            cx.notify();
3503                        })
3504                        .ok();
3505
3506                        result_tx.send(result).ok();
3507                    })
3508                }),
3509            })
3510            .ok();
3511        result_rx
3512    }
3513
3514    pub fn set_as_active_repository(&self, cx: &mut Context<Self>) {
3515        let Some(git_store) = self.git_store.upgrade() else {
3516            return;
3517        };
3518        let entity = cx.entity();
3519        git_store.update(cx, |git_store, cx| {
3520            let Some((&id, _)) = git_store
3521                .repositories
3522                .iter()
3523                .find(|(_, handle)| *handle == &entity)
3524            else {
3525                return;
3526            };
3527            git_store.active_repo_id = Some(id);
3528            cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
3529        });
3530    }
3531
3532    pub fn cached_status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
3533        self.snapshot.status()
3534    }
3535
3536    pub fn cached_stash(&self) -> GitStash {
3537        self.snapshot.stash_entries.clone()
3538    }
3539
3540    pub fn repo_path_to_project_path(&self, path: &RepoPath, cx: &App) -> Option<ProjectPath> {
3541        let git_store = self.git_store.upgrade()?;
3542        let worktree_store = git_store.read(cx).worktree_store.read(cx);
3543        let abs_path = self.snapshot.repo_path_to_abs_path(path);
3544        let abs_path = SanitizedPath::new(&abs_path);
3545        let (worktree, relative_path) = worktree_store.find_worktree(abs_path, cx)?;
3546        Some(ProjectPath {
3547            worktree_id: worktree.read(cx).id(),
3548            path: relative_path,
3549        })
3550    }
3551
3552    pub fn project_path_to_repo_path(&self, path: &ProjectPath, cx: &App) -> Option<RepoPath> {
3553        let git_store = self.git_store.upgrade()?;
3554        let worktree_store = git_store.read(cx).worktree_store.read(cx);
3555        let abs_path = worktree_store.absolutize(path, cx)?;
3556        self.snapshot.abs_path_to_repo_path(&abs_path)
3557    }
3558
3559    pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
3560        other
3561            .read(cx)
3562            .snapshot
3563            .work_directory_abs_path
3564            .starts_with(&self.snapshot.work_directory_abs_path)
3565    }
3566
3567    pub fn open_commit_buffer(
3568        &mut self,
3569        languages: Option<Arc<LanguageRegistry>>,
3570        buffer_store: Entity<BufferStore>,
3571        cx: &mut Context<Self>,
3572    ) -> Task<Result<Entity<Buffer>>> {
3573        let id = self.id;
3574        if let Some(buffer) = self.commit_message_buffer.clone() {
3575            return Task::ready(Ok(buffer));
3576        }
3577        let this = cx.weak_entity();
3578
3579        let rx = self.send_job(None, move |state, mut cx| async move {
3580            let Some(this) = this.upgrade() else {
3581                bail!("git store was dropped");
3582            };
3583            match state {
3584                RepositoryState::Local { .. } => {
3585                    this.update(&mut cx, |_, cx| {
3586                        Self::open_local_commit_buffer(languages, buffer_store, cx)
3587                    })?
3588                    .await
3589                }
3590                RepositoryState::Remote { project_id, client } => {
3591                    let request = client.request(proto::OpenCommitMessageBuffer {
3592                        project_id: project_id.0,
3593                        repository_id: id.to_proto(),
3594                    });
3595                    let response = request.await.context("requesting to open commit buffer")?;
3596                    let buffer_id = BufferId::new(response.buffer_id)?;
3597                    let buffer = buffer_store
3598                        .update(&mut cx, |buffer_store, cx| {
3599                            buffer_store.wait_for_remote_buffer(buffer_id, cx)
3600                        })?
3601                        .await?;
3602                    if let Some(language_registry) = languages {
3603                        let git_commit_language =
3604                            language_registry.language_for_name("Git Commit").await?;
3605                        buffer.update(&mut cx, |buffer, cx| {
3606                            buffer.set_language(Some(git_commit_language), cx);
3607                        })?;
3608                    }
3609                    this.update(&mut cx, |this, _| {
3610                        this.commit_message_buffer = Some(buffer.clone());
3611                    })?;
3612                    Ok(buffer)
3613                }
3614            }
3615        });
3616
3617        cx.spawn(|_, _: &mut AsyncApp| async move { rx.await? })
3618    }
3619
3620    fn open_local_commit_buffer(
3621        language_registry: Option<Arc<LanguageRegistry>>,
3622        buffer_store: Entity<BufferStore>,
3623        cx: &mut Context<Self>,
3624    ) -> Task<Result<Entity<Buffer>>> {
3625        cx.spawn(async move |repository, cx| {
3626            let buffer = buffer_store
3627                .update(cx, |buffer_store, cx| buffer_store.create_buffer(false, cx))?
3628                .await?;
3629
3630            if let Some(language_registry) = language_registry {
3631                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
3632                buffer.update(cx, |buffer, cx| {
3633                    buffer.set_language(Some(git_commit_language), cx);
3634                })?;
3635            }
3636
3637            repository.update(cx, |repository, _| {
3638                repository.commit_message_buffer = Some(buffer.clone());
3639            })?;
3640            Ok(buffer)
3641        })
3642    }
3643
3644    pub fn checkout_files(
3645        &mut self,
3646        commit: &str,
3647        paths: Vec<RepoPath>,
3648        _cx: &mut App,
3649    ) -> oneshot::Receiver<Result<()>> {
3650        let commit = commit.to_string();
3651        let id = self.id;
3652
3653        self.send_job(
3654            Some(format!("git checkout {}", commit).into()),
3655            move |git_repo, _| async move {
3656                match git_repo {
3657                    RepositoryState::Local {
3658                        backend,
3659                        environment,
3660                        ..
3661                    } => {
3662                        backend
3663                            .checkout_files(commit, paths, environment.clone())
3664                            .await
3665                    }
3666                    RepositoryState::Remote { project_id, client } => {
3667                        client
3668                            .request(proto::GitCheckoutFiles {
3669                                project_id: project_id.0,
3670                                repository_id: id.to_proto(),
3671                                commit,
3672                                paths: paths.into_iter().map(|p| p.to_proto()).collect(),
3673                            })
3674                            .await?;
3675
3676                        Ok(())
3677                    }
3678                }
3679            },
3680        )
3681    }
3682
3683    pub fn reset(
3684        &mut self,
3685        commit: String,
3686        reset_mode: ResetMode,
3687        _cx: &mut App,
3688    ) -> oneshot::Receiver<Result<()>> {
3689        let id = self.id;
3690
3691        self.send_job(None, move |git_repo, _| async move {
3692            match git_repo {
3693                RepositoryState::Local {
3694                    backend,
3695                    environment,
3696                    ..
3697                } => backend.reset(commit, reset_mode, environment).await,
3698                RepositoryState::Remote { project_id, client } => {
3699                    client
3700                        .request(proto::GitReset {
3701                            project_id: project_id.0,
3702                            repository_id: id.to_proto(),
3703                            commit,
3704                            mode: match reset_mode {
3705                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
3706                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
3707                            },
3708                        })
3709                        .await?;
3710
3711                    Ok(())
3712                }
3713            }
3714        })
3715    }
3716
3717    pub fn show(&mut self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
3718        let id = self.id;
3719        self.send_job(None, move |git_repo, _cx| async move {
3720            match git_repo {
3721                RepositoryState::Local { backend, .. } => backend.show(commit).await,
3722                RepositoryState::Remote { project_id, client } => {
3723                    let resp = client
3724                        .request(proto::GitShow {
3725                            project_id: project_id.0,
3726                            repository_id: id.to_proto(),
3727                            commit,
3728                        })
3729                        .await?;
3730
3731                    Ok(CommitDetails {
3732                        sha: resp.sha.into(),
3733                        message: resp.message.into(),
3734                        commit_timestamp: resp.commit_timestamp,
3735                        author_email: resp.author_email.into(),
3736                        author_name: resp.author_name.into(),
3737                    })
3738                }
3739            }
3740        })
3741    }
3742
3743    pub fn load_commit_diff(&mut self, commit: String) -> oneshot::Receiver<Result<CommitDiff>> {
3744        let id = self.id;
3745        self.send_job(None, move |git_repo, cx| async move {
3746            match git_repo {
3747                RepositoryState::Local { backend, .. } => backend.load_commit(commit, cx).await,
3748                RepositoryState::Remote {
3749                    client, project_id, ..
3750                } => {
3751                    let response = client
3752                        .request(proto::LoadCommitDiff {
3753                            project_id: project_id.0,
3754                            repository_id: id.to_proto(),
3755                            commit,
3756                        })
3757                        .await?;
3758                    Ok(CommitDiff {
3759                        files: response
3760                            .files
3761                            .into_iter()
3762                            .map(|file| {
3763                                Ok(CommitFile {
3764                                    path: RepoPath::from_proto(&file.path)?,
3765                                    old_text: file.old_text,
3766                                    new_text: file.new_text,
3767                                })
3768                            })
3769                            .collect::<Result<Vec<_>>>()?,
3770                    })
3771                }
3772            }
3773        })
3774    }
3775
3776    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
3777        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
3778    }
3779
3780    fn save_buffers<'a>(
3781        &self,
3782        entries: impl IntoIterator<Item = &'a RepoPath>,
3783        cx: &mut Context<Self>,
3784    ) -> Vec<Task<anyhow::Result<()>>> {
3785        let mut save_futures = Vec::new();
3786        if let Some(buffer_store) = self.buffer_store(cx) {
3787            buffer_store.update(cx, |buffer_store, cx| {
3788                for path in entries {
3789                    let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
3790                        continue;
3791                    };
3792                    if let Some(buffer) = buffer_store.get_by_path(&project_path)
3793                        && buffer
3794                            .read(cx)
3795                            .file()
3796                            .is_some_and(|file| file.disk_state().exists())
3797                        && buffer.read(cx).has_unsaved_edits()
3798                    {
3799                        save_futures.push(buffer_store.save_buffer(buffer, cx));
3800                    }
3801                }
3802            })
3803        }
3804        save_futures
3805    }
3806
3807    pub fn stage_entries(
3808        &mut self,
3809        entries: Vec<RepoPath>,
3810        cx: &mut Context<Self>,
3811    ) -> Task<anyhow::Result<()>> {
3812        if entries.is_empty() {
3813            return Task::ready(Ok(()));
3814        }
3815        let id = self.id;
3816        let save_tasks = self.save_buffers(&entries, cx);
3817        let paths = entries
3818            .iter()
3819            .map(|p| p.as_unix_str())
3820            .collect::<Vec<_>>()
3821            .join(" ");
3822        let status = format!("git add {paths}");
3823        let job_key = match entries.len() {
3824            1 => Some(GitJobKey::WriteIndex(entries[0].clone())),
3825            _ => None,
3826        };
3827
3828        let mut ids = Vec::with_capacity(entries.len());
3829
3830        for entry in &entries {
3831            let id = self.snapshot.pending_ops.summary().item_summary.max_id + 1;
3832            self.snapshot.pending_ops.push(
3833                PendingOp {
3834                    repo_path: entry.clone(),
3835                    id,
3836                    status: PendingOpStatus::Staged,
3837                    finished: false,
3838                },
3839                (),
3840            );
3841            ids.push(id);
3842        }
3843
3844        cx.spawn(async move |this, cx| {
3845            for save_task in save_tasks {
3846                save_task.await?;
3847            }
3848
3849            this.update(cx, |this, _| {
3850                this.send_keyed_job(
3851                    job_key,
3852                    Some(status.into()),
3853                    move |git_repo, _cx| async move {
3854                        match git_repo {
3855                            RepositoryState::Local {
3856                                backend,
3857                                environment,
3858                                ..
3859                            } => backend.stage_paths(entries, environment.clone()).await,
3860                            RepositoryState::Remote { project_id, client } => {
3861                                client
3862                                    .request(proto::Stage {
3863                                        project_id: project_id.0,
3864                                        repository_id: id.to_proto(),
3865                                        paths: entries
3866                                            .into_iter()
3867                                            .map(|repo_path| repo_path.to_proto())
3868                                            .collect(),
3869                                    })
3870                                    .await
3871                                    .context("sending stage request")?;
3872
3873                                Ok(())
3874                            }
3875                        }
3876                    },
3877                )
3878            })?
3879            .await??;
3880
3881            this.update(cx, |this, _| {
3882                for id in ids {
3883                    if let Some(mut op) = this.snapshot.pending_op_by_id(id) {
3884                        op.finished = true;
3885                        this.snapshot
3886                            .pending_ops
3887                            .edit(vec![sum_tree::Edit::Insert(op)], ());
3888                    }
3889                }
3890            })?;
3891
3892            Ok(())
3893        })
3894    }
3895
3896    pub fn unstage_entries(
3897        &mut self,
3898        entries: Vec<RepoPath>,
3899        cx: &mut Context<Self>,
3900    ) -> Task<anyhow::Result<()>> {
3901        if entries.is_empty() {
3902            return Task::ready(Ok(()));
3903        }
3904        let id = self.id;
3905        let save_tasks = self.save_buffers(&entries, cx);
3906        let paths = entries
3907            .iter()
3908            .map(|p| p.as_unix_str())
3909            .collect::<Vec<_>>()
3910            .join(" ");
3911        let status = format!("git reset {paths}");
3912        let job_key = match entries.len() {
3913            1 => Some(GitJobKey::WriteIndex(entries[0].clone())),
3914            _ => None,
3915        };
3916
3917        let mut ids = Vec::with_capacity(entries.len());
3918
3919        for entry in &entries {
3920            let id = self.snapshot.pending_ops.summary().item_summary.max_id + 1;
3921            self.snapshot.pending_ops.push(
3922                PendingOp {
3923                    repo_path: entry.clone(),
3924                    id,
3925                    status: PendingOpStatus::Unstaged,
3926                    finished: false,
3927                },
3928                (),
3929            );
3930            ids.push(id);
3931        }
3932
3933        cx.spawn(async move |this, cx| {
3934            for save_task in save_tasks {
3935                save_task.await?;
3936            }
3937
3938            this.update(cx, |this, _| {
3939                this.send_keyed_job(
3940                    job_key,
3941                    Some(status.into()),
3942                    move |git_repo, _cx| async move {
3943                        match git_repo {
3944                            RepositoryState::Local {
3945                                backend,
3946                                environment,
3947                                ..
3948                            } => backend.unstage_paths(entries, environment).await,
3949                            RepositoryState::Remote { project_id, client } => {
3950                                client
3951                                    .request(proto::Unstage {
3952                                        project_id: project_id.0,
3953                                        repository_id: id.to_proto(),
3954                                        paths: entries
3955                                            .into_iter()
3956                                            .map(|repo_path| repo_path.to_proto())
3957                                            .collect(),
3958                                    })
3959                                    .await
3960                                    .context("sending unstage request")?;
3961
3962                                Ok(())
3963                            }
3964                        }
3965                    },
3966                )
3967            })?
3968            .await??;
3969
3970            this.update(cx, |this, _| {
3971                for id in ids {
3972                    if let Some(mut op) = this.snapshot.pending_op_by_id(id) {
3973                        op.finished = true;
3974                        this.snapshot
3975                            .pending_ops
3976                            .edit(vec![sum_tree::Edit::Insert(op)], ());
3977                    }
3978                }
3979            })?;
3980
3981            Ok(())
3982        })
3983    }
3984
3985    pub fn stage_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
3986        let to_stage = self
3987            .cached_status()
3988            .filter(|entry| !entry.status.staging().is_fully_staged())
3989            .map(|entry| entry.repo_path)
3990            .collect();
3991        self.stage_entries(to_stage, cx)
3992    }
3993
3994    pub fn unstage_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
3995        let to_unstage = self
3996            .cached_status()
3997            .filter(|entry| entry.status.staging().has_staged())
3998            .map(|entry| entry.repo_path)
3999            .collect();
4000        self.unstage_entries(to_unstage, cx)
4001    }
4002
4003    pub fn stash_all(&mut self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
4004        let to_stash = self.cached_status().map(|entry| entry.repo_path).collect();
4005
4006        self.stash_entries(to_stash, cx)
4007    }
4008
4009    pub fn stash_entries(
4010        &mut self,
4011        entries: Vec<RepoPath>,
4012        cx: &mut Context<Self>,
4013    ) -> Task<anyhow::Result<()>> {
4014        let id = self.id;
4015
4016        cx.spawn(async move |this, cx| {
4017            this.update(cx, |this, _| {
4018                this.send_job(None, move |git_repo, _cx| async move {
4019                    match git_repo {
4020                        RepositoryState::Local {
4021                            backend,
4022                            environment,
4023                            ..
4024                        } => backend.stash_paths(entries, environment).await,
4025                        RepositoryState::Remote { project_id, client } => {
4026                            client
4027                                .request(proto::Stash {
4028                                    project_id: project_id.0,
4029                                    repository_id: id.to_proto(),
4030                                    paths: entries
4031                                        .into_iter()
4032                                        .map(|repo_path| repo_path.to_proto())
4033                                        .collect(),
4034                                })
4035                                .await
4036                                .context("sending stash request")?;
4037                            Ok(())
4038                        }
4039                    }
4040                })
4041            })?
4042            .await??;
4043            Ok(())
4044        })
4045    }
4046
4047    pub fn stash_pop(
4048        &mut self,
4049        index: Option<usize>,
4050        cx: &mut Context<Self>,
4051    ) -> Task<anyhow::Result<()>> {
4052        let id = self.id;
4053        cx.spawn(async move |this, cx| {
4054            this.update(cx, |this, _| {
4055                this.send_job(None, move |git_repo, _cx| async move {
4056                    match git_repo {
4057                        RepositoryState::Local {
4058                            backend,
4059                            environment,
4060                            ..
4061                        } => backend.stash_pop(index, environment).await,
4062                        RepositoryState::Remote { project_id, client } => {
4063                            client
4064                                .request(proto::StashPop {
4065                                    project_id: project_id.0,
4066                                    repository_id: id.to_proto(),
4067                                    stash_index: index.map(|i| i as u64),
4068                                })
4069                                .await
4070                                .context("sending stash pop request")?;
4071                            Ok(())
4072                        }
4073                    }
4074                })
4075            })?
4076            .await??;
4077            Ok(())
4078        })
4079    }
4080
4081    pub fn stash_apply(
4082        &mut self,
4083        index: Option<usize>,
4084        cx: &mut Context<Self>,
4085    ) -> Task<anyhow::Result<()>> {
4086        let id = self.id;
4087        cx.spawn(async move |this, cx| {
4088            this.update(cx, |this, _| {
4089                this.send_job(None, move |git_repo, _cx| async move {
4090                    match git_repo {
4091                        RepositoryState::Local {
4092                            backend,
4093                            environment,
4094                            ..
4095                        } => backend.stash_apply(index, environment).await,
4096                        RepositoryState::Remote { project_id, client } => {
4097                            client
4098                                .request(proto::StashApply {
4099                                    project_id: project_id.0,
4100                                    repository_id: id.to_proto(),
4101                                    stash_index: index.map(|i| i as u64),
4102                                })
4103                                .await
4104                                .context("sending stash apply request")?;
4105                            Ok(())
4106                        }
4107                    }
4108                })
4109            })?
4110            .await??;
4111            Ok(())
4112        })
4113    }
4114
4115    pub fn stash_drop(
4116        &mut self,
4117        index: Option<usize>,
4118        cx: &mut Context<Self>,
4119    ) -> oneshot::Receiver<anyhow::Result<()>> {
4120        let id = self.id;
4121        let updates_tx = self
4122            .git_store()
4123            .and_then(|git_store| match &git_store.read(cx).state {
4124                GitStoreState::Local { downstream, .. } => downstream
4125                    .as_ref()
4126                    .map(|downstream| downstream.updates_tx.clone()),
4127                _ => None,
4128            });
4129        let this = cx.weak_entity();
4130        self.send_job(None, move |git_repo, mut cx| async move {
4131            match git_repo {
4132                RepositoryState::Local {
4133                    backend,
4134                    environment,
4135                    ..
4136                } => {
4137                    // TODO would be nice to not have to do this manually
4138                    let result = backend.stash_drop(index, environment).await;
4139                    if result.is_ok()
4140                        && let Ok(stash_entries) = backend.stash_entries().await
4141                    {
4142                        let snapshot = this.update(&mut cx, |this, cx| {
4143                            this.snapshot.stash_entries = stash_entries;
4144                            cx.emit(RepositoryEvent::StashEntriesChanged);
4145                            this.snapshot.clone()
4146                        })?;
4147                        if let Some(updates_tx) = updates_tx {
4148                            updates_tx
4149                                .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
4150                                .ok();
4151                        }
4152                    }
4153
4154                    result
4155                }
4156                RepositoryState::Remote { project_id, client } => {
4157                    client
4158                        .request(proto::StashDrop {
4159                            project_id: project_id.0,
4160                            repository_id: id.to_proto(),
4161                            stash_index: index.map(|i| i as u64),
4162                        })
4163                        .await
4164                        .context("sending stash pop request")?;
4165                    Ok(())
4166                }
4167            }
4168        })
4169    }
4170
4171    pub fn commit(
4172        &mut self,
4173        message: SharedString,
4174        name_and_email: Option<(SharedString, SharedString)>,
4175        options: CommitOptions,
4176        _cx: &mut App,
4177    ) -> oneshot::Receiver<Result<()>> {
4178        let id = self.id;
4179
4180        self.send_job(Some("git commit".into()), move |git_repo, _cx| async move {
4181            match git_repo {
4182                RepositoryState::Local {
4183                    backend,
4184                    environment,
4185                    ..
4186                } => {
4187                    backend
4188                        .commit(message, name_and_email, options, environment)
4189                        .await
4190                }
4191                RepositoryState::Remote { project_id, client } => {
4192                    let (name, email) = name_and_email.unzip();
4193                    client
4194                        .request(proto::Commit {
4195                            project_id: project_id.0,
4196                            repository_id: id.to_proto(),
4197                            message: String::from(message),
4198                            name: name.map(String::from),
4199                            email: email.map(String::from),
4200                            options: Some(proto::commit::CommitOptions {
4201                                amend: options.amend,
4202                                signoff: options.signoff,
4203                            }),
4204                        })
4205                        .await
4206                        .context("sending commit request")?;
4207
4208                    Ok(())
4209                }
4210            }
4211        })
4212    }
4213
4214    pub fn fetch(
4215        &mut self,
4216        fetch_options: FetchOptions,
4217        askpass: AskPassDelegate,
4218        _cx: &mut App,
4219    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
4220        let askpass_delegates = self.askpass_delegates.clone();
4221        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
4222        let id = self.id;
4223
4224        self.send_job(Some("git fetch".into()), move |git_repo, cx| async move {
4225            match git_repo {
4226                RepositoryState::Local {
4227                    backend,
4228                    environment,
4229                    ..
4230                } => backend.fetch(fetch_options, askpass, environment, cx).await,
4231                RepositoryState::Remote { project_id, client } => {
4232                    askpass_delegates.lock().insert(askpass_id, askpass);
4233                    let _defer = util::defer(|| {
4234                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
4235                        debug_assert!(askpass_delegate.is_some());
4236                    });
4237
4238                    let response = client
4239                        .request(proto::Fetch {
4240                            project_id: project_id.0,
4241                            repository_id: id.to_proto(),
4242                            askpass_id,
4243                            remote: fetch_options.to_proto(),
4244                        })
4245                        .await
4246                        .context("sending fetch request")?;
4247
4248                    Ok(RemoteCommandOutput {
4249                        stdout: response.stdout,
4250                        stderr: response.stderr,
4251                    })
4252                }
4253            }
4254        })
4255    }
4256
4257    pub fn push(
4258        &mut self,
4259        branch: SharedString,
4260        remote: SharedString,
4261        options: Option<PushOptions>,
4262        askpass: AskPassDelegate,
4263        cx: &mut Context<Self>,
4264    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
4265        let askpass_delegates = self.askpass_delegates.clone();
4266        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
4267        let id = self.id;
4268
4269        let args = options
4270            .map(|option| match option {
4271                PushOptions::SetUpstream => " --set-upstream",
4272                PushOptions::Force => " --force-with-lease",
4273            })
4274            .unwrap_or("");
4275
4276        let updates_tx = self
4277            .git_store()
4278            .and_then(|git_store| match &git_store.read(cx).state {
4279                GitStoreState::Local { downstream, .. } => downstream
4280                    .as_ref()
4281                    .map(|downstream| downstream.updates_tx.clone()),
4282                _ => None,
4283            });
4284
4285        let this = cx.weak_entity();
4286        self.send_job(
4287            Some(format!("git push {} {} {}", args, remote, branch).into()),
4288            move |git_repo, mut cx| async move {
4289                match git_repo {
4290                    RepositoryState::Local {
4291                        backend,
4292                        environment,
4293                        ..
4294                    } => {
4295                        let result = backend
4296                            .push(
4297                                branch.to_string(),
4298                                remote.to_string(),
4299                                options,
4300                                askpass,
4301                                environment.clone(),
4302                                cx.clone(),
4303                            )
4304                            .await;
4305                        // TODO would be nice to not have to do this manually
4306                        if result.is_ok() {
4307                            let branches = backend.branches().await?;
4308                            let branch = branches.into_iter().find(|branch| branch.is_head);
4309                            log::info!("head branch after scan is {branch:?}");
4310                            let snapshot = this.update(&mut cx, |this, cx| {
4311                                this.snapshot.branch = branch;
4312                                cx.emit(RepositoryEvent::BranchChanged);
4313                                this.snapshot.clone()
4314                            })?;
4315                            if let Some(updates_tx) = updates_tx {
4316                                updates_tx
4317                                    .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
4318                                    .ok();
4319                            }
4320                        }
4321                        result
4322                    }
4323                    RepositoryState::Remote { project_id, client } => {
4324                        askpass_delegates.lock().insert(askpass_id, askpass);
4325                        let _defer = util::defer(|| {
4326                            let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
4327                            debug_assert!(askpass_delegate.is_some());
4328                        });
4329                        let response = client
4330                            .request(proto::Push {
4331                                project_id: project_id.0,
4332                                repository_id: id.to_proto(),
4333                                askpass_id,
4334                                branch_name: branch.to_string(),
4335                                remote_name: remote.to_string(),
4336                                options: options.map(|options| match options {
4337                                    PushOptions::Force => proto::push::PushOptions::Force,
4338                                    PushOptions::SetUpstream => {
4339                                        proto::push::PushOptions::SetUpstream
4340                                    }
4341                                }
4342                                    as i32),
4343                            })
4344                            .await
4345                            .context("sending push request")?;
4346
4347                        Ok(RemoteCommandOutput {
4348                            stdout: response.stdout,
4349                            stderr: response.stderr,
4350                        })
4351                    }
4352                }
4353            },
4354        )
4355    }
4356
4357    pub fn pull(
4358        &mut self,
4359        branch: SharedString,
4360        remote: SharedString,
4361        askpass: AskPassDelegate,
4362        _cx: &mut App,
4363    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
4364        let askpass_delegates = self.askpass_delegates.clone();
4365        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
4366        let id = self.id;
4367
4368        self.send_job(
4369            Some(format!("git pull {} {}", remote, branch).into()),
4370            move |git_repo, cx| async move {
4371                match git_repo {
4372                    RepositoryState::Local {
4373                        backend,
4374                        environment,
4375                        ..
4376                    } => {
4377                        backend
4378                            .pull(
4379                                branch.to_string(),
4380                                remote.to_string(),
4381                                askpass,
4382                                environment.clone(),
4383                                cx,
4384                            )
4385                            .await
4386                    }
4387                    RepositoryState::Remote { project_id, client } => {
4388                        askpass_delegates.lock().insert(askpass_id, askpass);
4389                        let _defer = util::defer(|| {
4390                            let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
4391                            debug_assert!(askpass_delegate.is_some());
4392                        });
4393                        let response = client
4394                            .request(proto::Pull {
4395                                project_id: project_id.0,
4396                                repository_id: id.to_proto(),
4397                                askpass_id,
4398                                branch_name: branch.to_string(),
4399                                remote_name: remote.to_string(),
4400                            })
4401                            .await
4402                            .context("sending pull request")?;
4403
4404                        Ok(RemoteCommandOutput {
4405                            stdout: response.stdout,
4406                            stderr: response.stderr,
4407                        })
4408                    }
4409                }
4410            },
4411        )
4412    }
4413
4414    fn spawn_set_index_text_job(
4415        &mut self,
4416        path: RepoPath,
4417        content: Option<String>,
4418        hunk_staging_operation_count: Option<usize>,
4419        cx: &mut Context<Self>,
4420    ) -> oneshot::Receiver<anyhow::Result<()>> {
4421        let id = self.id;
4422        let this = cx.weak_entity();
4423        let git_store = self.git_store.clone();
4424        self.send_keyed_job(
4425            Some(GitJobKey::WriteIndex(path.clone())),
4426            None,
4427            move |git_repo, mut cx| async move {
4428                log::debug!(
4429                    "start updating index text for buffer {}",
4430                    path.as_unix_str()
4431                );
4432                match git_repo {
4433                    RepositoryState::Local {
4434                        backend,
4435                        environment,
4436                        ..
4437                    } => {
4438                        backend
4439                            .set_index_text(path.clone(), content, environment.clone())
4440                            .await?;
4441                    }
4442                    RepositoryState::Remote { project_id, client } => {
4443                        client
4444                            .request(proto::SetIndexText {
4445                                project_id: project_id.0,
4446                                repository_id: id.to_proto(),
4447                                path: path.to_proto(),
4448                                text: content,
4449                            })
4450                            .await?;
4451                    }
4452                }
4453                log::debug!(
4454                    "finish updating index text for buffer {}",
4455                    path.as_unix_str()
4456                );
4457
4458                if let Some(hunk_staging_operation_count) = hunk_staging_operation_count {
4459                    let project_path = this
4460                        .read_with(&cx, |this, cx| this.repo_path_to_project_path(&path, cx))
4461                        .ok()
4462                        .flatten();
4463                    git_store.update(&mut cx, |git_store, cx| {
4464                        let buffer_id = git_store
4465                            .buffer_store
4466                            .read(cx)
4467                            .get_by_path(&project_path?)?
4468                            .read(cx)
4469                            .remote_id();
4470                        let diff_state = git_store.diffs.get(&buffer_id)?;
4471                        diff_state.update(cx, |diff_state, _| {
4472                            diff_state.hunk_staging_operation_count_as_of_write =
4473                                hunk_staging_operation_count;
4474                        });
4475                        Some(())
4476                    })?;
4477                }
4478                Ok(())
4479            },
4480        )
4481    }
4482
4483    pub fn get_remotes(
4484        &mut self,
4485        branch_name: Option<String>,
4486    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
4487        let id = self.id;
4488        self.send_job(None, move |repo, _cx| async move {
4489            match repo {
4490                RepositoryState::Local { backend, .. } => backend.get_remotes(branch_name).await,
4491                RepositoryState::Remote { project_id, client } => {
4492                    let response = client
4493                        .request(proto::GetRemotes {
4494                            project_id: project_id.0,
4495                            repository_id: id.to_proto(),
4496                            branch_name,
4497                        })
4498                        .await?;
4499
4500                    let remotes = response
4501                        .remotes
4502                        .into_iter()
4503                        .map(|remotes| git::repository::Remote {
4504                            name: remotes.name.into(),
4505                        })
4506                        .collect();
4507
4508                    Ok(remotes)
4509                }
4510            }
4511        })
4512    }
4513
4514    pub fn branches(&mut self) -> oneshot::Receiver<Result<Vec<Branch>>> {
4515        let id = self.id;
4516        self.send_job(None, move |repo, _| async move {
4517            match repo {
4518                RepositoryState::Local { backend, .. } => backend.branches().await,
4519                RepositoryState::Remote { project_id, client } => {
4520                    let response = client
4521                        .request(proto::GitGetBranches {
4522                            project_id: project_id.0,
4523                            repository_id: id.to_proto(),
4524                        })
4525                        .await?;
4526
4527                    let branches = response
4528                        .branches
4529                        .into_iter()
4530                        .map(|branch| proto_to_branch(&branch))
4531                        .collect();
4532
4533                    Ok(branches)
4534                }
4535            }
4536        })
4537    }
4538
4539    pub fn worktrees(&mut self) -> oneshot::Receiver<Result<Vec<GitWorktree>>> {
4540        let id = self.id;
4541        self.send_job(None, move |repo, _| async move {
4542            match repo {
4543                RepositoryState::Local { backend, .. } => backend.worktrees().await,
4544                RepositoryState::Remote { project_id, client } => {
4545                    let response = client
4546                        .request(proto::GitGetWorktrees {
4547                            project_id: project_id.0,
4548                            repository_id: id.to_proto(),
4549                        })
4550                        .await?;
4551
4552                    let worktrees = response
4553                        .worktrees
4554                        .into_iter()
4555                        .map(|worktree| proto_to_worktree(&worktree))
4556                        .collect();
4557
4558                    Ok(worktrees)
4559                }
4560            }
4561        })
4562    }
4563
4564    pub fn create_worktree(
4565        &mut self,
4566        name: String,
4567        path: PathBuf,
4568        commit: Option<String>,
4569    ) -> oneshot::Receiver<Result<()>> {
4570        let id = self.id;
4571        self.send_job(
4572            Some("git worktree add".into()),
4573            move |repo, _cx| async move {
4574                match repo {
4575                    RepositoryState::Local { backend, .. } => {
4576                        backend.create_worktree(name, path, commit).await
4577                    }
4578                    RepositoryState::Remote { project_id, client } => {
4579                        client
4580                            .request(proto::GitCreateWorktree {
4581                                project_id: project_id.0,
4582                                repository_id: id.to_proto(),
4583                                name,
4584                                directory: path.to_string_lossy().to_string(),
4585                                commit,
4586                            })
4587                            .await?;
4588
4589                        Ok(())
4590                    }
4591                }
4592            },
4593        )
4594    }
4595
4596    pub fn default_branch(&mut self) -> oneshot::Receiver<Result<Option<SharedString>>> {
4597        let id = self.id;
4598        self.send_job(None, move |repo, _| async move {
4599            match repo {
4600                RepositoryState::Local { backend, .. } => backend.default_branch().await,
4601                RepositoryState::Remote { project_id, client } => {
4602                    let response = client
4603                        .request(proto::GetDefaultBranch {
4604                            project_id: project_id.0,
4605                            repository_id: id.to_proto(),
4606                        })
4607                        .await?;
4608
4609                    anyhow::Ok(response.branch.map(SharedString::from))
4610                }
4611            }
4612        })
4613    }
4614
4615    pub fn diff_tree(
4616        &mut self,
4617        diff_type: DiffTreeType,
4618        _cx: &App,
4619    ) -> oneshot::Receiver<Result<TreeDiff>> {
4620        let repository_id = self.snapshot.id;
4621        self.send_job(None, move |repo, _cx| async move {
4622            match repo {
4623                RepositoryState::Local { backend, .. } => backend.diff_tree(diff_type).await,
4624                RepositoryState::Remote { client, project_id } => {
4625                    let response = client
4626                        .request(proto::GetTreeDiff {
4627                            project_id: project_id.0,
4628                            repository_id: repository_id.0,
4629                            is_merge: matches!(diff_type, DiffTreeType::MergeBase { .. }),
4630                            base: diff_type.base().to_string(),
4631                            head: diff_type.head().to_string(),
4632                        })
4633                        .await?;
4634
4635                    let entries = response
4636                        .entries
4637                        .into_iter()
4638                        .filter_map(|entry| {
4639                            let status = match entry.status() {
4640                                proto::tree_diff_status::Status::Added => TreeDiffStatus::Added,
4641                                proto::tree_diff_status::Status::Modified => {
4642                                    TreeDiffStatus::Modified {
4643                                        old: git::Oid::from_str(
4644                                            &entry.oid.context("missing oid").log_err()?,
4645                                        )
4646                                        .log_err()?,
4647                                    }
4648                                }
4649                                proto::tree_diff_status::Status::Deleted => {
4650                                    TreeDiffStatus::Deleted {
4651                                        old: git::Oid::from_str(
4652                                            &entry.oid.context("missing oid").log_err()?,
4653                                        )
4654                                        .log_err()?,
4655                                    }
4656                                }
4657                            };
4658                            Some((
4659                                RepoPath(RelPath::from_proto(&entry.path).log_err()?),
4660                                status,
4661                            ))
4662                        })
4663                        .collect();
4664
4665                    Ok(TreeDiff { entries })
4666                }
4667            }
4668        })
4669    }
4670
4671    pub fn diff(&mut self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
4672        let id = self.id;
4673        self.send_job(None, move |repo, _cx| async move {
4674            match repo {
4675                RepositoryState::Local { backend, .. } => backend.diff(diff_type).await,
4676                RepositoryState::Remote { project_id, client } => {
4677                    let response = client
4678                        .request(proto::GitDiff {
4679                            project_id: project_id.0,
4680                            repository_id: id.to_proto(),
4681                            diff_type: match diff_type {
4682                                DiffType::HeadToIndex => {
4683                                    proto::git_diff::DiffType::HeadToIndex.into()
4684                                }
4685                                DiffType::HeadToWorktree => {
4686                                    proto::git_diff::DiffType::HeadToWorktree.into()
4687                                }
4688                            },
4689                        })
4690                        .await?;
4691
4692                    Ok(response.diff)
4693                }
4694            }
4695        })
4696    }
4697
4698    pub fn create_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
4699        let id = self.id;
4700        self.send_job(
4701            Some(format!("git switch -c {branch_name}").into()),
4702            move |repo, _cx| async move {
4703                match repo {
4704                    RepositoryState::Local { backend, .. } => {
4705                        backend.create_branch(branch_name).await
4706                    }
4707                    RepositoryState::Remote { project_id, client } => {
4708                        client
4709                            .request(proto::GitCreateBranch {
4710                                project_id: project_id.0,
4711                                repository_id: id.to_proto(),
4712                                branch_name,
4713                            })
4714                            .await?;
4715
4716                        Ok(())
4717                    }
4718                }
4719            },
4720        )
4721    }
4722
4723    pub fn change_branch(&mut self, branch_name: String) -> oneshot::Receiver<Result<()>> {
4724        let id = self.id;
4725        self.send_job(
4726            Some(format!("git switch {branch_name}").into()),
4727            move |repo, _cx| async move {
4728                match repo {
4729                    RepositoryState::Local { backend, .. } => {
4730                        backend.change_branch(branch_name).await
4731                    }
4732                    RepositoryState::Remote { project_id, client } => {
4733                        client
4734                            .request(proto::GitChangeBranch {
4735                                project_id: project_id.0,
4736                                repository_id: id.to_proto(),
4737                                branch_name,
4738                            })
4739                            .await?;
4740
4741                        Ok(())
4742                    }
4743                }
4744            },
4745        )
4746    }
4747
4748    pub fn rename_branch(
4749        &mut self,
4750        branch: String,
4751        new_name: String,
4752    ) -> oneshot::Receiver<Result<()>> {
4753        let id = self.id;
4754        self.send_job(
4755            Some(format!("git branch -m {branch} {new_name}").into()),
4756            move |repo, _cx| async move {
4757                match repo {
4758                    RepositoryState::Local { backend, .. } => {
4759                        backend.rename_branch(branch, new_name).await
4760                    }
4761                    RepositoryState::Remote { project_id, client } => {
4762                        client
4763                            .request(proto::GitRenameBranch {
4764                                project_id: project_id.0,
4765                                repository_id: id.to_proto(),
4766                                branch,
4767                                new_name,
4768                            })
4769                            .await?;
4770
4771                        Ok(())
4772                    }
4773                }
4774            },
4775        )
4776    }
4777
4778    pub fn check_for_pushed_commits(&mut self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
4779        let id = self.id;
4780        self.send_job(None, move |repo, _cx| async move {
4781            match repo {
4782                RepositoryState::Local { backend, .. } => backend.check_for_pushed_commit().await,
4783                RepositoryState::Remote { project_id, client } => {
4784                    let response = client
4785                        .request(proto::CheckForPushedCommits {
4786                            project_id: project_id.0,
4787                            repository_id: id.to_proto(),
4788                        })
4789                        .await?;
4790
4791                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
4792
4793                    Ok(branches)
4794                }
4795            }
4796        })
4797    }
4798
4799    pub fn checkpoint(&mut self) -> oneshot::Receiver<Result<GitRepositoryCheckpoint>> {
4800        self.send_job(None, |repo, _cx| async move {
4801            match repo {
4802                RepositoryState::Local { backend, .. } => backend.checkpoint().await,
4803                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
4804            }
4805        })
4806    }
4807
4808    pub fn restore_checkpoint(
4809        &mut self,
4810        checkpoint: GitRepositoryCheckpoint,
4811    ) -> oneshot::Receiver<Result<()>> {
4812        self.send_job(None, move |repo, _cx| async move {
4813            match repo {
4814                RepositoryState::Local { backend, .. } => {
4815                    backend.restore_checkpoint(checkpoint).await
4816                }
4817                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
4818            }
4819        })
4820    }
4821
4822    pub(crate) fn apply_remote_update(
4823        &mut self,
4824        update: proto::UpdateRepository,
4825        cx: &mut Context<Self>,
4826    ) -> Result<()> {
4827        let conflicted_paths = TreeSet::from_ordered_entries(
4828            update
4829                .current_merge_conflicts
4830                .into_iter()
4831                .filter_map(|path| RepoPath::from_proto(&path).log_err()),
4832        );
4833        let new_branch = update.branch_summary.as_ref().map(proto_to_branch);
4834        let new_head_commit = update
4835            .head_commit_details
4836            .as_ref()
4837            .map(proto_to_commit_details);
4838        if self.snapshot.branch != new_branch || self.snapshot.head_commit != new_head_commit {
4839            cx.emit(RepositoryEvent::BranchChanged)
4840        }
4841        self.snapshot.branch = new_branch;
4842        self.snapshot.head_commit = new_head_commit;
4843
4844        self.snapshot.merge.conflicted_paths = conflicted_paths;
4845        self.snapshot.merge.message = update.merge_message.map(SharedString::from);
4846        let new_stash_entries = GitStash {
4847            entries: update
4848                .stash_entries
4849                .iter()
4850                .filter_map(|entry| proto_to_stash(entry).ok())
4851                .collect(),
4852        };
4853        if self.snapshot.stash_entries != new_stash_entries {
4854            cx.emit(RepositoryEvent::StashEntriesChanged)
4855        }
4856        self.snapshot.stash_entries = new_stash_entries;
4857
4858        let edits = update
4859            .removed_statuses
4860            .into_iter()
4861            .filter_map(|path| {
4862                Some(sum_tree::Edit::Remove(PathKey(
4863                    RelPath::from_proto(&path).log_err()?,
4864                )))
4865            })
4866            .chain(
4867                update
4868                    .updated_statuses
4869                    .into_iter()
4870                    .filter_map(|updated_status| {
4871                        Some(sum_tree::Edit::Insert(updated_status.try_into().log_err()?))
4872                    }),
4873            )
4874            .collect::<Vec<_>>();
4875        if !edits.is_empty() {
4876            cx.emit(RepositoryEvent::StatusesChanged { full_scan: true });
4877        }
4878        self.snapshot.statuses_by_path.edit(edits, ());
4879        if update.is_last_update {
4880            self.snapshot.scan_id = update.scan_id;
4881        }
4882        Ok(())
4883    }
4884
4885    pub fn compare_checkpoints(
4886        &mut self,
4887        left: GitRepositoryCheckpoint,
4888        right: GitRepositoryCheckpoint,
4889    ) -> oneshot::Receiver<Result<bool>> {
4890        self.send_job(None, move |repo, _cx| async move {
4891            match repo {
4892                RepositoryState::Local { backend, .. } => {
4893                    backend.compare_checkpoints(left, right).await
4894                }
4895                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
4896            }
4897        })
4898    }
4899
4900    pub fn diff_checkpoints(
4901        &mut self,
4902        base_checkpoint: GitRepositoryCheckpoint,
4903        target_checkpoint: GitRepositoryCheckpoint,
4904    ) -> oneshot::Receiver<Result<String>> {
4905        self.send_job(None, move |repo, _cx| async move {
4906            match repo {
4907                RepositoryState::Local { backend, .. } => {
4908                    backend
4909                        .diff_checkpoints(base_checkpoint, target_checkpoint)
4910                        .await
4911                }
4912                RepositoryState::Remote { .. } => anyhow::bail!("not implemented yet"),
4913            }
4914        })
4915    }
4916
4917    fn schedule_scan(
4918        &mut self,
4919        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
4920        cx: &mut Context<Self>,
4921    ) {
4922        let this = cx.weak_entity();
4923        let _ = self.send_keyed_job(
4924            Some(GitJobKey::ReloadGitState),
4925            None,
4926            |state, mut cx| async move {
4927                log::debug!("run scheduled git status scan");
4928
4929                let Some(this) = this.upgrade() else {
4930                    return Ok(());
4931                };
4932                let RepositoryState::Local { backend, .. } = state else {
4933                    bail!("not a local repository")
4934                };
4935                let (snapshot, events) = this
4936                    .update(&mut cx, |this, _| {
4937                        this.paths_needing_status_update.clear();
4938                        compute_snapshot(
4939                            this.id,
4940                            this.work_directory_abs_path.clone(),
4941                            this.snapshot.clone(),
4942                            backend.clone(),
4943                        )
4944                    })?
4945                    .await?;
4946                this.update(&mut cx, |this, cx| {
4947                    this.snapshot = snapshot.clone();
4948                    for event in events {
4949                        cx.emit(event);
4950                    }
4951                })?;
4952                if let Some(updates_tx) = updates_tx {
4953                    updates_tx
4954                        .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
4955                        .ok();
4956                }
4957                Ok(())
4958            },
4959        );
4960    }
4961
4962    fn spawn_local_git_worker(
4963        work_directory_abs_path: Arc<Path>,
4964        dot_git_abs_path: Arc<Path>,
4965        _repository_dir_abs_path: Arc<Path>,
4966        _common_dir_abs_path: Arc<Path>,
4967        project_environment: WeakEntity<ProjectEnvironment>,
4968        fs: Arc<dyn Fs>,
4969        cx: &mut Context<Self>,
4970    ) -> mpsc::UnboundedSender<GitJob> {
4971        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
4972
4973        cx.spawn(async move |_, cx| {
4974            let environment = project_environment
4975                .upgrade()
4976                .context("missing project environment")?
4977                .update(cx, |project_environment, cx| {
4978                    project_environment.local_directory_environment(&Shell::System, work_directory_abs_path.clone(), cx)
4979                })?
4980                .await
4981                .unwrap_or_else(|| {
4982                    log::error!("failed to get working directory environment for repository {work_directory_abs_path:?}");
4983                    HashMap::default()
4984                });
4985            let search_paths = environment.get("PATH").map(|val| val.to_owned());
4986            let backend = cx
4987                .background_spawn(async move {
4988                    let system_git_binary_path = search_paths.and_then(|search_paths| which::which_in("git", Some(search_paths), &work_directory_abs_path).ok())
4989                        .or_else(|| which::which("git").ok());
4990                    fs.open_repo(&dot_git_abs_path, system_git_binary_path.as_deref())
4991                        .with_context(|| format!("opening repository at {dot_git_abs_path:?}"))
4992                })
4993                .await?;
4994
4995            if let Some(git_hosting_provider_registry) =
4996                cx.update(|cx| GitHostingProviderRegistry::try_global(cx))?
4997            {
4998                git_hosting_providers::register_additional_providers(
4999                    git_hosting_provider_registry,
5000                    backend.clone(),
5001                );
5002            }
5003
5004            let state = RepositoryState::Local {
5005                backend,
5006                environment: Arc::new(environment),
5007            };
5008            let mut jobs = VecDeque::new();
5009            loop {
5010                while let Ok(Some(next_job)) = job_rx.try_next() {
5011                    jobs.push_back(next_job);
5012                }
5013
5014                if let Some(job) = jobs.pop_front() {
5015                    if let Some(current_key) = &job.key
5016                        && jobs
5017                            .iter()
5018                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
5019                        {
5020                            continue;
5021                        }
5022                    (job.job)(state.clone(), cx).await;
5023                } else if let Some(job) = job_rx.next().await {
5024                    jobs.push_back(job);
5025                } else {
5026                    break;
5027                }
5028            }
5029            anyhow::Ok(())
5030        })
5031        .detach_and_log_err(cx);
5032
5033        job_tx
5034    }
5035
5036    fn spawn_remote_git_worker(
5037        project_id: ProjectId,
5038        client: AnyProtoClient,
5039        cx: &mut Context<Self>,
5040    ) -> mpsc::UnboundedSender<GitJob> {
5041        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
5042
5043        cx.spawn(async move |_, cx| {
5044            let state = RepositoryState::Remote { project_id, client };
5045            let mut jobs = VecDeque::new();
5046            loop {
5047                while let Ok(Some(next_job)) = job_rx.try_next() {
5048                    jobs.push_back(next_job);
5049                }
5050
5051                if let Some(job) = jobs.pop_front() {
5052                    if let Some(current_key) = &job.key
5053                        && jobs
5054                            .iter()
5055                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
5056                    {
5057                        continue;
5058                    }
5059                    (job.job)(state.clone(), cx).await;
5060                } else if let Some(job) = job_rx.next().await {
5061                    jobs.push_back(job);
5062                } else {
5063                    break;
5064                }
5065            }
5066            anyhow::Ok(())
5067        })
5068        .detach_and_log_err(cx);
5069
5070        job_tx
5071    }
5072
5073    fn load_staged_text(
5074        &mut self,
5075        buffer_id: BufferId,
5076        repo_path: RepoPath,
5077        cx: &App,
5078    ) -> Task<Result<Option<String>>> {
5079        let rx = self.send_job(None, move |state, _| async move {
5080            match state {
5081                RepositoryState::Local { backend, .. } => {
5082                    anyhow::Ok(backend.load_index_text(repo_path).await)
5083                }
5084                RepositoryState::Remote { project_id, client } => {
5085                    let response = client
5086                        .request(proto::OpenUnstagedDiff {
5087                            project_id: project_id.to_proto(),
5088                            buffer_id: buffer_id.to_proto(),
5089                        })
5090                        .await?;
5091                    Ok(response.staged_text)
5092                }
5093            }
5094        });
5095        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
5096    }
5097
5098    fn load_committed_text(
5099        &mut self,
5100        buffer_id: BufferId,
5101        repo_path: RepoPath,
5102        cx: &App,
5103    ) -> Task<Result<DiffBasesChange>> {
5104        let rx = self.send_job(None, move |state, _| async move {
5105            match state {
5106                RepositoryState::Local { backend, .. } => {
5107                    let committed_text = backend.load_committed_text(repo_path.clone()).await;
5108                    let staged_text = backend.load_index_text(repo_path).await;
5109                    let diff_bases_change = if committed_text == staged_text {
5110                        DiffBasesChange::SetBoth(committed_text)
5111                    } else {
5112                        DiffBasesChange::SetEach {
5113                            index: staged_text,
5114                            head: committed_text,
5115                        }
5116                    };
5117                    anyhow::Ok(diff_bases_change)
5118                }
5119                RepositoryState::Remote { project_id, client } => {
5120                    use proto::open_uncommitted_diff_response::Mode;
5121
5122                    let response = client
5123                        .request(proto::OpenUncommittedDiff {
5124                            project_id: project_id.to_proto(),
5125                            buffer_id: buffer_id.to_proto(),
5126                        })
5127                        .await?;
5128                    let mode = Mode::from_i32(response.mode).context("Invalid mode")?;
5129                    let bases = match mode {
5130                        Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
5131                        Mode::IndexAndHead => DiffBasesChange::SetEach {
5132                            head: response.committed_text,
5133                            index: response.staged_text,
5134                        },
5135                    };
5136                    Ok(bases)
5137                }
5138            }
5139        });
5140
5141        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
5142    }
5143    fn load_blob_content(&mut self, oid: Oid, cx: &App) -> Task<Result<String>> {
5144        let repository_id = self.snapshot.id;
5145        let rx = self.send_job(None, move |state, _| async move {
5146            match state {
5147                RepositoryState::Local { backend, .. } => backend.load_blob_content(oid).await,
5148                RepositoryState::Remote { client, project_id } => {
5149                    let response = client
5150                        .request(proto::GetBlobContent {
5151                            project_id: project_id.to_proto(),
5152                            repository_id: repository_id.0,
5153                            oid: oid.to_string(),
5154                        })
5155                        .await?;
5156                    Ok(response.content)
5157                }
5158            }
5159        });
5160        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
5161    }
5162
5163    fn paths_changed(
5164        &mut self,
5165        paths: Vec<RepoPath>,
5166        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
5167        cx: &mut Context<Self>,
5168    ) {
5169        self.paths_needing_status_update.extend(paths);
5170
5171        let this = cx.weak_entity();
5172        let _ = self.send_keyed_job(
5173            Some(GitJobKey::RefreshStatuses),
5174            None,
5175            |state, mut cx| async move {
5176                let (prev_snapshot, mut changed_paths) = this.update(&mut cx, |this, _| {
5177                    (
5178                        this.snapshot.clone(),
5179                        mem::take(&mut this.paths_needing_status_update),
5180                    )
5181                })?;
5182                let RepositoryState::Local { backend, .. } = state else {
5183                    bail!("not a local repository")
5184                };
5185
5186                let paths = changed_paths.iter().cloned().collect::<Vec<_>>();
5187                if paths.is_empty() {
5188                    return Ok(());
5189                }
5190                let statuses = backend.status(&paths).await?;
5191                let stash_entries = backend.stash_entries().await?;
5192
5193                let changed_path_statuses = cx
5194                    .background_spawn(async move {
5195                        let mut changed_path_statuses = Vec::new();
5196                        let prev_statuses = prev_snapshot.statuses_by_path.clone();
5197                        let mut cursor = prev_statuses.cursor::<PathProgress>(());
5198
5199                        for (repo_path, status) in &*statuses.entries {
5200                            changed_paths.remove(repo_path);
5201                            if cursor.seek_forward(&PathTarget::Path(repo_path), Bias::Left)
5202                                && cursor.item().is_some_and(|entry| entry.status == *status)
5203                            {
5204                                continue;
5205                            }
5206
5207                            changed_path_statuses.push(Edit::Insert(StatusEntry {
5208                                repo_path: repo_path.clone(),
5209                                status: *status,
5210                            }));
5211                        }
5212                        let mut cursor = prev_statuses.cursor::<PathProgress>(());
5213                        for path in changed_paths.into_iter() {
5214                            if cursor.seek_forward(&PathTarget::Path(&path), Bias::Left) {
5215                                changed_path_statuses.push(Edit::Remove(PathKey(path.0)));
5216                            }
5217                        }
5218                        changed_path_statuses
5219                    })
5220                    .await;
5221
5222                this.update(&mut cx, |this, cx| {
5223                    if this.snapshot.stash_entries != stash_entries {
5224                        cx.emit(RepositoryEvent::StashEntriesChanged);
5225                        this.snapshot.stash_entries = stash_entries;
5226                    }
5227
5228                    if !changed_path_statuses.is_empty() {
5229                        cx.emit(RepositoryEvent::StatusesChanged { full_scan: false });
5230                        this.snapshot
5231                            .statuses_by_path
5232                            .edit(changed_path_statuses, ());
5233                        this.snapshot.scan_id += 1;
5234                    }
5235
5236                    if let Some(updates_tx) = updates_tx {
5237                        updates_tx
5238                            .unbounded_send(DownstreamUpdate::UpdateRepository(
5239                                this.snapshot.clone(),
5240                            ))
5241                            .ok();
5242                    }
5243                })
5244            },
5245        );
5246    }
5247
5248    /// currently running git command and when it started
5249    pub fn current_job(&self) -> Option<JobInfo> {
5250        self.active_jobs.values().next().cloned()
5251    }
5252
5253    pub fn barrier(&mut self) -> oneshot::Receiver<()> {
5254        self.send_job(None, |_, _| async {})
5255    }
5256}
5257
5258fn get_permalink_in_rust_registry_src(
5259    provider_registry: Arc<GitHostingProviderRegistry>,
5260    path: PathBuf,
5261    selection: Range<u32>,
5262) -> Result<url::Url> {
5263    #[derive(Deserialize)]
5264    struct CargoVcsGit {
5265        sha1: String,
5266    }
5267
5268    #[derive(Deserialize)]
5269    struct CargoVcsInfo {
5270        git: CargoVcsGit,
5271        path_in_vcs: String,
5272    }
5273
5274    #[derive(Deserialize)]
5275    struct CargoPackage {
5276        repository: String,
5277    }
5278
5279    #[derive(Deserialize)]
5280    struct CargoToml {
5281        package: CargoPackage,
5282    }
5283
5284    let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
5285        let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
5286        Some((dir, json))
5287    }) else {
5288        bail!("No .cargo_vcs_info.json found in parent directories")
5289    };
5290    let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
5291    let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
5292    let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
5293    let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
5294        .context("parsing package.repository field of manifest")?;
5295    let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
5296    let permalink = provider.build_permalink(
5297        remote,
5298        BuildPermalinkParams::new(
5299            &cargo_vcs_info.git.sha1,
5300            &RepoPath(
5301                RelPath::new(&path, PathStyle::local())
5302                    .context("invalid path")?
5303                    .into_arc(),
5304            ),
5305            Some(selection),
5306        ),
5307    );
5308    Ok(permalink)
5309}
5310
5311fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::BlameBufferResponse {
5312    let Some(blame) = blame else {
5313        return proto::BlameBufferResponse {
5314            blame_response: None,
5315        };
5316    };
5317
5318    let entries = blame
5319        .entries
5320        .into_iter()
5321        .map(|entry| proto::BlameEntry {
5322            sha: entry.sha.as_bytes().into(),
5323            start_line: entry.range.start,
5324            end_line: entry.range.end,
5325            original_line_number: entry.original_line_number,
5326            author: entry.author,
5327            author_mail: entry.author_mail,
5328            author_time: entry.author_time,
5329            author_tz: entry.author_tz,
5330            committer: entry.committer_name,
5331            committer_mail: entry.committer_email,
5332            committer_time: entry.committer_time,
5333            committer_tz: entry.committer_tz,
5334            summary: entry.summary,
5335            previous: entry.previous,
5336            filename: entry.filename,
5337        })
5338        .collect::<Vec<_>>();
5339
5340    let messages = blame
5341        .messages
5342        .into_iter()
5343        .map(|(oid, message)| proto::CommitMessage {
5344            oid: oid.as_bytes().into(),
5345            message,
5346        })
5347        .collect::<Vec<_>>();
5348
5349    proto::BlameBufferResponse {
5350        blame_response: Some(proto::blame_buffer_response::BlameResponse {
5351            entries,
5352            messages,
5353            remote_url: blame.remote_url,
5354        }),
5355    }
5356}
5357
5358fn deserialize_blame_buffer_response(
5359    response: proto::BlameBufferResponse,
5360) -> Option<git::blame::Blame> {
5361    let response = response.blame_response?;
5362    let entries = response
5363        .entries
5364        .into_iter()
5365        .filter_map(|entry| {
5366            Some(git::blame::BlameEntry {
5367                sha: git::Oid::from_bytes(&entry.sha).ok()?,
5368                range: entry.start_line..entry.end_line,
5369                original_line_number: entry.original_line_number,
5370                committer_name: entry.committer,
5371                committer_time: entry.committer_time,
5372                committer_tz: entry.committer_tz,
5373                committer_email: entry.committer_mail,
5374                author: entry.author,
5375                author_mail: entry.author_mail,
5376                author_time: entry.author_time,
5377                author_tz: entry.author_tz,
5378                summary: entry.summary,
5379                previous: entry.previous,
5380                filename: entry.filename,
5381            })
5382        })
5383        .collect::<Vec<_>>();
5384
5385    let messages = response
5386        .messages
5387        .into_iter()
5388        .filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
5389        .collect::<HashMap<_, _>>();
5390
5391    Some(Blame {
5392        entries,
5393        messages,
5394        remote_url: response.remote_url,
5395    })
5396}
5397
5398fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch {
5399    proto::Branch {
5400        is_head: branch.is_head,
5401        ref_name: branch.ref_name.to_string(),
5402        unix_timestamp: branch
5403            .most_recent_commit
5404            .as_ref()
5405            .map(|commit| commit.commit_timestamp as u64),
5406        upstream: branch.upstream.as_ref().map(|upstream| proto::GitUpstream {
5407            ref_name: upstream.ref_name.to_string(),
5408            tracking: upstream
5409                .tracking
5410                .status()
5411                .map(|upstream| proto::UpstreamTracking {
5412                    ahead: upstream.ahead as u64,
5413                    behind: upstream.behind as u64,
5414                }),
5415        }),
5416        most_recent_commit: branch
5417            .most_recent_commit
5418            .as_ref()
5419            .map(|commit| proto::CommitSummary {
5420                sha: commit.sha.to_string(),
5421                subject: commit.subject.to_string(),
5422                commit_timestamp: commit.commit_timestamp,
5423                author_name: commit.author_name.to_string(),
5424            }),
5425    }
5426}
5427
5428fn worktree_to_proto(worktree: &git::repository::Worktree) -> proto::Worktree {
5429    proto::Worktree {
5430        path: worktree.path.to_string_lossy().to_string(),
5431        ref_name: worktree.ref_name.to_string(),
5432        sha: worktree.sha.to_string(),
5433    }
5434}
5435
5436fn proto_to_worktree(proto: &proto::Worktree) -> git::repository::Worktree {
5437    git::repository::Worktree {
5438        path: PathBuf::from(proto.path.clone()),
5439        ref_name: proto.ref_name.clone().into(),
5440        sha: proto.sha.clone().into(),
5441    }
5442}
5443
5444fn proto_to_branch(proto: &proto::Branch) -> git::repository::Branch {
5445    git::repository::Branch {
5446        is_head: proto.is_head,
5447        ref_name: proto.ref_name.clone().into(),
5448        upstream: proto
5449            .upstream
5450            .as_ref()
5451            .map(|upstream| git::repository::Upstream {
5452                ref_name: upstream.ref_name.to_string().into(),
5453                tracking: upstream
5454                    .tracking
5455                    .as_ref()
5456                    .map(|tracking| {
5457                        git::repository::UpstreamTracking::Tracked(UpstreamTrackingStatus {
5458                            ahead: tracking.ahead as u32,
5459                            behind: tracking.behind as u32,
5460                        })
5461                    })
5462                    .unwrap_or(git::repository::UpstreamTracking::Gone),
5463            }),
5464        most_recent_commit: proto.most_recent_commit.as_ref().map(|commit| {
5465            git::repository::CommitSummary {
5466                sha: commit.sha.to_string().into(),
5467                subject: commit.subject.to_string().into(),
5468                commit_timestamp: commit.commit_timestamp,
5469                author_name: commit.author_name.to_string().into(),
5470                has_parent: true,
5471            }
5472        }),
5473    }
5474}
5475
5476fn commit_details_to_proto(commit: &CommitDetails) -> proto::GitCommitDetails {
5477    proto::GitCommitDetails {
5478        sha: commit.sha.to_string(),
5479        message: commit.message.to_string(),
5480        commit_timestamp: commit.commit_timestamp,
5481        author_email: commit.author_email.to_string(),
5482        author_name: commit.author_name.to_string(),
5483    }
5484}
5485
5486fn proto_to_commit_details(proto: &proto::GitCommitDetails) -> CommitDetails {
5487    CommitDetails {
5488        sha: proto.sha.clone().into(),
5489        message: proto.message.clone().into(),
5490        commit_timestamp: proto.commit_timestamp,
5491        author_email: proto.author_email.clone().into(),
5492        author_name: proto.author_name.clone().into(),
5493    }
5494}
5495
5496async fn compute_snapshot(
5497    id: RepositoryId,
5498    work_directory_abs_path: Arc<Path>,
5499    prev_snapshot: RepositorySnapshot,
5500    backend: Arc<dyn GitRepository>,
5501) -> Result<(RepositorySnapshot, Vec<RepositoryEvent>)> {
5502    let mut events = Vec::new();
5503    let branches = backend.branches().await?;
5504    let branch = branches.into_iter().find(|branch| branch.is_head);
5505    let statuses = backend.status(&[RelPath::empty().into()]).await?;
5506    let stash_entries = backend.stash_entries().await?;
5507    let statuses_by_path = SumTree::from_iter(
5508        statuses
5509            .entries
5510            .iter()
5511            .map(|(repo_path, status)| StatusEntry {
5512                repo_path: repo_path.clone(),
5513                status: *status,
5514            }),
5515        (),
5516    );
5517    let (merge_details, merge_heads_changed) =
5518        MergeDetails::load(&backend, &statuses_by_path, &prev_snapshot).await?;
5519    log::debug!("new merge details (changed={merge_heads_changed:?}): {merge_details:?}");
5520
5521    let pending_ops = prev_snapshot.pending_ops.clone();
5522
5523    if merge_heads_changed {
5524        events.push(RepositoryEvent::MergeHeadsChanged);
5525    }
5526
5527    if statuses_by_path != prev_snapshot.statuses_by_path {
5528        events.push(RepositoryEvent::StatusesChanged { full_scan: true })
5529    }
5530
5531    // Useful when branch is None in detached head state
5532    let head_commit = match backend.head_sha().await {
5533        Some(head_sha) => backend.show(head_sha).await.log_err(),
5534        None => None,
5535    };
5536
5537    if branch != prev_snapshot.branch || head_commit != prev_snapshot.head_commit {
5538        events.push(RepositoryEvent::BranchChanged);
5539    }
5540
5541    // Used by edit prediction data collection
5542    let remote_origin_url = backend.remote_url("origin");
5543    let remote_upstream_url = backend.remote_url("upstream");
5544
5545    let snapshot = RepositorySnapshot {
5546        id,
5547        statuses_by_path,
5548        pending_ops,
5549        work_directory_abs_path,
5550        path_style: prev_snapshot.path_style,
5551        scan_id: prev_snapshot.scan_id + 1,
5552        branch,
5553        head_commit,
5554        merge: merge_details,
5555        remote_origin_url,
5556        remote_upstream_url,
5557        stash_entries,
5558    };
5559
5560    Ok((snapshot, events))
5561}
5562
5563fn status_from_proto(
5564    simple_status: i32,
5565    status: Option<proto::GitFileStatus>,
5566) -> anyhow::Result<FileStatus> {
5567    use proto::git_file_status::Variant;
5568
5569    let Some(variant) = status.and_then(|status| status.variant) else {
5570        let code = proto::GitStatus::from_i32(simple_status)
5571            .with_context(|| format!("Invalid git status code: {simple_status}"))?;
5572        let result = match code {
5573            proto::GitStatus::Added => TrackedStatus {
5574                worktree_status: StatusCode::Added,
5575                index_status: StatusCode::Unmodified,
5576            }
5577            .into(),
5578            proto::GitStatus::Modified => TrackedStatus {
5579                worktree_status: StatusCode::Modified,
5580                index_status: StatusCode::Unmodified,
5581            }
5582            .into(),
5583            proto::GitStatus::Conflict => UnmergedStatus {
5584                first_head: UnmergedStatusCode::Updated,
5585                second_head: UnmergedStatusCode::Updated,
5586            }
5587            .into(),
5588            proto::GitStatus::Deleted => TrackedStatus {
5589                worktree_status: StatusCode::Deleted,
5590                index_status: StatusCode::Unmodified,
5591            }
5592            .into(),
5593            _ => anyhow::bail!("Invalid code for simple status: {simple_status}"),
5594        };
5595        return Ok(result);
5596    };
5597
5598    let result = match variant {
5599        Variant::Untracked(_) => FileStatus::Untracked,
5600        Variant::Ignored(_) => FileStatus::Ignored,
5601        Variant::Unmerged(unmerged) => {
5602            let [first_head, second_head] =
5603                [unmerged.first_head, unmerged.second_head].map(|head| {
5604                    let code = proto::GitStatus::from_i32(head)
5605                        .with_context(|| format!("Invalid git status code: {head}"))?;
5606                    let result = match code {
5607                        proto::GitStatus::Added => UnmergedStatusCode::Added,
5608                        proto::GitStatus::Updated => UnmergedStatusCode::Updated,
5609                        proto::GitStatus::Deleted => UnmergedStatusCode::Deleted,
5610                        _ => anyhow::bail!("Invalid code for unmerged status: {code:?}"),
5611                    };
5612                    Ok(result)
5613                });
5614            let [first_head, second_head] = [first_head?, second_head?];
5615            UnmergedStatus {
5616                first_head,
5617                second_head,
5618            }
5619            .into()
5620        }
5621        Variant::Tracked(tracked) => {
5622            let [index_status, worktree_status] = [tracked.index_status, tracked.worktree_status]
5623                .map(|status| {
5624                    let code = proto::GitStatus::from_i32(status)
5625                        .with_context(|| format!("Invalid git status code: {status}"))?;
5626                    let result = match code {
5627                        proto::GitStatus::Modified => StatusCode::Modified,
5628                        proto::GitStatus::TypeChanged => StatusCode::TypeChanged,
5629                        proto::GitStatus::Added => StatusCode::Added,
5630                        proto::GitStatus::Deleted => StatusCode::Deleted,
5631                        proto::GitStatus::Renamed => StatusCode::Renamed,
5632                        proto::GitStatus::Copied => StatusCode::Copied,
5633                        proto::GitStatus::Unmodified => StatusCode::Unmodified,
5634                        _ => anyhow::bail!("Invalid code for tracked status: {code:?}"),
5635                    };
5636                    Ok(result)
5637                });
5638            let [index_status, worktree_status] = [index_status?, worktree_status?];
5639            TrackedStatus {
5640                index_status,
5641                worktree_status,
5642            }
5643            .into()
5644        }
5645    };
5646    Ok(result)
5647}
5648
5649fn status_to_proto(status: FileStatus) -> proto::GitFileStatus {
5650    use proto::git_file_status::{Tracked, Unmerged, Variant};
5651
5652    let variant = match status {
5653        FileStatus::Untracked => Variant::Untracked(Default::default()),
5654        FileStatus::Ignored => Variant::Ignored(Default::default()),
5655        FileStatus::Unmerged(UnmergedStatus {
5656            first_head,
5657            second_head,
5658        }) => Variant::Unmerged(Unmerged {
5659            first_head: unmerged_status_to_proto(first_head),
5660            second_head: unmerged_status_to_proto(second_head),
5661        }),
5662        FileStatus::Tracked(TrackedStatus {
5663            index_status,
5664            worktree_status,
5665        }) => Variant::Tracked(Tracked {
5666            index_status: tracked_status_to_proto(index_status),
5667            worktree_status: tracked_status_to_proto(worktree_status),
5668        }),
5669    };
5670    proto::GitFileStatus {
5671        variant: Some(variant),
5672    }
5673}
5674
5675fn unmerged_status_to_proto(code: UnmergedStatusCode) -> i32 {
5676    match code {
5677        UnmergedStatusCode::Added => proto::GitStatus::Added as _,
5678        UnmergedStatusCode::Deleted => proto::GitStatus::Deleted as _,
5679        UnmergedStatusCode::Updated => proto::GitStatus::Updated as _,
5680    }
5681}
5682
5683fn tracked_status_to_proto(code: StatusCode) -> i32 {
5684    match code {
5685        StatusCode::Added => proto::GitStatus::Added as _,
5686        StatusCode::Deleted => proto::GitStatus::Deleted as _,
5687        StatusCode::Modified => proto::GitStatus::Modified as _,
5688        StatusCode::Renamed => proto::GitStatus::Renamed as _,
5689        StatusCode::TypeChanged => proto::GitStatus::TypeChanged as _,
5690        StatusCode::Copied => proto::GitStatus::Copied as _,
5691        StatusCode::Unmodified => proto::GitStatus::Unmodified as _,
5692    }
5693}