git_store.rs

   1pub mod git_traversal;
   2
   3use crate::{
   4    ProjectEnvironment, ProjectItem, ProjectPath,
   5    buffer_store::{BufferStore, BufferStoreEvent},
   6    worktree_store::{WorktreeStore, WorktreeStoreEvent},
   7};
   8use anyhow::{Context as _, Result, anyhow, bail};
   9use askpass::AskPassDelegate;
  10use buffer_diff::{BufferDiff, BufferDiffEvent};
  11use client::ProjectId;
  12use collections::HashMap;
  13use fs::Fs;
  14use futures::{
  15    FutureExt as _, StreamExt as _,
  16    channel::{mpsc, oneshot},
  17    future::{self, Shared},
  18};
  19use git::{
  20    BuildPermalinkParams, GitHostingProviderRegistry, WORK_DIRECTORY_REPO_PATH,
  21    blame::Blame,
  22    parse_git_remote_url,
  23    repository::{
  24        Branch, CommitDetails, CommitDiff, CommitFile, DiffType, GitRepository,
  25        GitRepositoryCheckpoint, PushOptions, Remote, RemoteCommandOutput, RepoPath, ResetMode,
  26        UpstreamTrackingStatus,
  27    },
  28    status::{
  29        FileStatus, GitSummary, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode,
  30    },
  31};
  32use gpui::{
  33    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
  34    WeakEntity,
  35};
  36use language::{
  37    Buffer, BufferEvent, Language, LanguageRegistry,
  38    proto::{deserialize_version, serialize_version},
  39};
  40use parking_lot::Mutex;
  41use rpc::{
  42    AnyProtoClient, TypedEnvelope,
  43    proto::{self, FromProto, SSH_PROJECT_ID, ToProto, git_reset, split_repository_update},
  44};
  45use serde::Deserialize;
  46use std::{
  47    cmp::Ordering,
  48    collections::{BTreeSet, VecDeque},
  49    future::Future,
  50    mem,
  51    ops::Range,
  52    path::{Path, PathBuf},
  53    sync::{
  54        Arc,
  55        atomic::{self, AtomicU64},
  56    },
  57};
  58use sum_tree::{Edit, SumTree, TreeSet};
  59use text::{Bias, BufferId};
  60use util::{ResultExt, debug_panic};
  61use worktree::{
  62    File, PathKey, PathProgress, PathSummary, PathTarget, UpdatedGitRepositoriesSet, Worktree,
  63};
  64
  65pub struct GitStore {
  66    state: GitStoreState,
  67    buffer_store: Entity<BufferStore>,
  68    worktree_store: Entity<WorktreeStore>,
  69    repositories: HashMap<RepositoryId, Entity<Repository>>,
  70    active_repo_id: Option<RepositoryId>,
  71    #[allow(clippy::type_complexity)]
  72    loading_diffs:
  73        HashMap<(BufferId, DiffKind), Shared<Task<Result<Entity<BufferDiff>, Arc<anyhow::Error>>>>>,
  74    diffs: HashMap<BufferId, Entity<BufferDiffState>>,
  75    shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>,
  76    _subscriptions: Vec<Subscription>,
  77}
  78
  79#[derive(Default)]
  80struct SharedDiffs {
  81    unstaged: Option<Entity<BufferDiff>>,
  82    uncommitted: Option<Entity<BufferDiff>>,
  83}
  84
  85#[derive(Default)]
  86struct BufferDiffState {
  87    unstaged_diff: Option<WeakEntity<BufferDiff>>,
  88    uncommitted_diff: Option<WeakEntity<BufferDiff>>,
  89    recalculate_diff_task: Option<Task<Result<()>>>,
  90    language: Option<Arc<Language>>,
  91    language_registry: Option<Arc<LanguageRegistry>>,
  92    diff_updated_futures: Vec<oneshot::Sender<()>>,
  93    hunk_staging_operation_count: usize,
  94
  95    head_text: Option<Arc<String>>,
  96    index_text: Option<Arc<String>>,
  97    head_changed: bool,
  98    index_changed: bool,
  99    language_changed: bool,
 100}
 101
 102#[derive(Clone, Debug)]
 103enum DiffBasesChange {
 104    SetIndex(Option<String>),
 105    SetHead(Option<String>),
 106    SetEach {
 107        index: Option<String>,
 108        head: Option<String>,
 109    },
 110    SetBoth(Option<String>),
 111}
 112
 113#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 114enum DiffKind {
 115    Unstaged,
 116    Uncommitted,
 117}
 118
 119enum GitStoreState {
 120    Local {
 121        next_repository_id: Arc<AtomicU64>,
 122        downstream: Option<LocalDownstreamState>,
 123        project_environment: Entity<ProjectEnvironment>,
 124        fs: Arc<dyn Fs>,
 125    },
 126    Ssh {
 127        upstream_client: AnyProtoClient,
 128        upstream_project_id: ProjectId,
 129        downstream: Option<(AnyProtoClient, ProjectId)>,
 130    },
 131    Remote {
 132        upstream_client: AnyProtoClient,
 133        upstream_project_id: ProjectId,
 134    },
 135}
 136
 137enum DownstreamUpdate {
 138    UpdateRepository(RepositorySnapshot),
 139    RemoveRepository(RepositoryId),
 140}
 141
 142struct LocalDownstreamState {
 143    client: AnyProtoClient,
 144    project_id: ProjectId,
 145    updates_tx: mpsc::UnboundedSender<DownstreamUpdate>,
 146    _task: Task<Result<()>>,
 147}
 148
 149#[derive(Clone)]
 150pub struct GitStoreCheckpoint {
 151    checkpoints_by_work_dir_abs_path: HashMap<Arc<Path>, GitRepositoryCheckpoint>,
 152}
 153
 154#[derive(Clone, Debug, PartialEq, Eq)]
 155pub struct StatusEntry {
 156    pub repo_path: RepoPath,
 157    pub status: FileStatus,
 158}
 159
 160impl StatusEntry {
 161    fn to_proto(&self) -> proto::StatusEntry {
 162        let simple_status = match self.status {
 163            FileStatus::Ignored | FileStatus::Untracked => proto::GitStatus::Added as i32,
 164            FileStatus::Unmerged { .. } => proto::GitStatus::Conflict as i32,
 165            FileStatus::Tracked(TrackedStatus {
 166                index_status,
 167                worktree_status,
 168            }) => tracked_status_to_proto(if worktree_status != StatusCode::Unmodified {
 169                worktree_status
 170            } else {
 171                index_status
 172            }),
 173        };
 174
 175        proto::StatusEntry {
 176            repo_path: self.repo_path.as_ref().to_proto(),
 177            simple_status,
 178            status: Some(status_to_proto(self.status)),
 179        }
 180    }
 181}
 182
 183impl TryFrom<proto::StatusEntry> for StatusEntry {
 184    type Error = anyhow::Error;
 185
 186    fn try_from(value: proto::StatusEntry) -> Result<Self, Self::Error> {
 187        let repo_path = RepoPath(Arc::<Path>::from_proto(value.repo_path));
 188        let status = status_from_proto(value.simple_status, value.status)?;
 189        Ok(Self { repo_path, status })
 190    }
 191}
 192
 193impl sum_tree::Item for StatusEntry {
 194    type Summary = PathSummary<GitSummary>;
 195
 196    fn summary(&self, _: &<Self::Summary as sum_tree::Summary>::Context) -> Self::Summary {
 197        PathSummary {
 198            max_path: self.repo_path.0.clone(),
 199            item_summary: self.status.summary(),
 200        }
 201    }
 202}
 203
 204impl sum_tree::KeyedItem for StatusEntry {
 205    type Key = PathKey;
 206
 207    fn key(&self) -> Self::Key {
 208        PathKey(self.repo_path.0.clone())
 209    }
 210}
 211
 212#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 213pub struct RepositoryId(pub u64);
 214
 215#[derive(Clone, Debug, PartialEq, Eq)]
 216pub struct RepositorySnapshot {
 217    pub id: RepositoryId,
 218    pub merge_message: Option<SharedString>,
 219    pub statuses_by_path: SumTree<StatusEntry>,
 220    pub work_directory_abs_path: Arc<Path>,
 221    pub branch: Option<Branch>,
 222    pub merge_conflicts: TreeSet<RepoPath>,
 223    pub merge_head_shas: Vec<SharedString>,
 224    pub scan_id: u64,
 225}
 226
 227pub struct Repository {
 228    snapshot: RepositorySnapshot,
 229    commit_message_buffer: Option<Entity<Buffer>>,
 230    git_store: WeakEntity<GitStore>,
 231    // For a local repository, holds paths that have had worktree events since the last status scan completed,
 232    // and that should be examined during the next status scan.
 233    paths_needing_status_update: BTreeSet<RepoPath>,
 234    job_sender: mpsc::UnboundedSender<GitJob>,
 235    askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,
 236    latest_askpass_id: u64,
 237}
 238
 239impl std::ops::Deref for Repository {
 240    type Target = RepositorySnapshot;
 241
 242    fn deref(&self) -> &Self::Target {
 243        &self.snapshot
 244    }
 245}
 246
 247#[derive(Clone)]
 248pub enum RepositoryState {
 249    Local {
 250        backend: Arc<dyn GitRepository>,
 251        environment: Arc<HashMap<String, String>>,
 252    },
 253    Remote {
 254        project_id: ProjectId,
 255        client: AnyProtoClient,
 256    },
 257}
 258
 259#[derive(Clone, Debug)]
 260pub enum RepositoryEvent {
 261    Updated { full_scan: bool },
 262    MergeHeadsChanged,
 263}
 264
 265#[derive(Debug)]
 266pub enum GitStoreEvent {
 267    ActiveRepositoryChanged(Option<RepositoryId>),
 268    RepositoryUpdated(RepositoryId, RepositoryEvent, bool),
 269    RepositoryAdded(RepositoryId),
 270    RepositoryRemoved(RepositoryId),
 271    IndexWriteError(anyhow::Error),
 272}
 273
 274impl EventEmitter<RepositoryEvent> for Repository {}
 275impl EventEmitter<GitStoreEvent> for GitStore {}
 276
 277struct GitJob {
 278    job: Box<dyn FnOnce(RepositoryState, &mut AsyncApp) -> Task<()>>,
 279    key: Option<GitJobKey>,
 280}
 281
 282#[derive(PartialEq, Eq)]
 283enum GitJobKey {
 284    WriteIndex(RepoPath),
 285    BatchReadIndex,
 286    RefreshStatuses,
 287    ReloadGitState,
 288}
 289
 290impl GitStore {
 291    pub fn local(
 292        worktree_store: &Entity<WorktreeStore>,
 293        buffer_store: Entity<BufferStore>,
 294        environment: Entity<ProjectEnvironment>,
 295        fs: Arc<dyn Fs>,
 296        cx: &mut Context<Self>,
 297    ) -> Self {
 298        Self::new(
 299            worktree_store.clone(),
 300            buffer_store,
 301            GitStoreState::Local {
 302                next_repository_id: Arc::new(AtomicU64::new(1)),
 303                downstream: None,
 304                project_environment: environment,
 305                fs,
 306            },
 307            cx,
 308        )
 309    }
 310
 311    pub fn remote(
 312        worktree_store: &Entity<WorktreeStore>,
 313        buffer_store: Entity<BufferStore>,
 314        upstream_client: AnyProtoClient,
 315        project_id: ProjectId,
 316        cx: &mut Context<Self>,
 317    ) -> Self {
 318        Self::new(
 319            worktree_store.clone(),
 320            buffer_store,
 321            GitStoreState::Remote {
 322                upstream_client,
 323                upstream_project_id: project_id,
 324            },
 325            cx,
 326        )
 327    }
 328
 329    pub fn ssh(
 330        worktree_store: &Entity<WorktreeStore>,
 331        buffer_store: Entity<BufferStore>,
 332        upstream_client: AnyProtoClient,
 333        cx: &mut Context<Self>,
 334    ) -> Self {
 335        Self::new(
 336            worktree_store.clone(),
 337            buffer_store,
 338            GitStoreState::Ssh {
 339                upstream_client,
 340                upstream_project_id: ProjectId(SSH_PROJECT_ID),
 341                downstream: None,
 342            },
 343            cx,
 344        )
 345    }
 346
 347    fn new(
 348        worktree_store: Entity<WorktreeStore>,
 349        buffer_store: Entity<BufferStore>,
 350        state: GitStoreState,
 351        cx: &mut Context<Self>,
 352    ) -> Self {
 353        let _subscriptions = vec![
 354            cx.subscribe(&worktree_store, Self::on_worktree_store_event),
 355            cx.subscribe(&buffer_store, Self::on_buffer_store_event),
 356        ];
 357
 358        GitStore {
 359            state,
 360            buffer_store,
 361            worktree_store,
 362            repositories: HashMap::default(),
 363            active_repo_id: None,
 364            _subscriptions,
 365            loading_diffs: HashMap::default(),
 366            shared_diffs: HashMap::default(),
 367            diffs: HashMap::default(),
 368        }
 369    }
 370
 371    pub fn init(client: &AnyProtoClient) {
 372        client.add_entity_request_handler(Self::handle_get_remotes);
 373        client.add_entity_request_handler(Self::handle_get_branches);
 374        client.add_entity_request_handler(Self::handle_change_branch);
 375        client.add_entity_request_handler(Self::handle_create_branch);
 376        client.add_entity_request_handler(Self::handle_git_init);
 377        client.add_entity_request_handler(Self::handle_push);
 378        client.add_entity_request_handler(Self::handle_pull);
 379        client.add_entity_request_handler(Self::handle_fetch);
 380        client.add_entity_request_handler(Self::handle_stage);
 381        client.add_entity_request_handler(Self::handle_unstage);
 382        client.add_entity_request_handler(Self::handle_commit);
 383        client.add_entity_request_handler(Self::handle_reset);
 384        client.add_entity_request_handler(Self::handle_show);
 385        client.add_entity_request_handler(Self::handle_load_commit_diff);
 386        client.add_entity_request_handler(Self::handle_checkout_files);
 387        client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
 388        client.add_entity_request_handler(Self::handle_set_index_text);
 389        client.add_entity_request_handler(Self::handle_askpass);
 390        client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
 391        client.add_entity_request_handler(Self::handle_git_diff);
 392        client.add_entity_request_handler(Self::handle_open_unstaged_diff);
 393        client.add_entity_request_handler(Self::handle_open_uncommitted_diff);
 394        client.add_entity_message_handler(Self::handle_update_diff_bases);
 395        client.add_entity_request_handler(Self::handle_get_permalink_to_line);
 396        client.add_entity_request_handler(Self::handle_blame_buffer);
 397        client.add_entity_message_handler(Self::handle_update_repository);
 398        client.add_entity_message_handler(Self::handle_remove_repository);
 399    }
 400
 401    pub fn is_local(&self) -> bool {
 402        matches!(self.state, GitStoreState::Local { .. })
 403    }
 404
 405    pub fn shared(&mut self, project_id: u64, client: AnyProtoClient, cx: &mut Context<Self>) {
 406        match &mut self.state {
 407            GitStoreState::Ssh {
 408                downstream: downstream_client,
 409                ..
 410            } => {
 411                for repo in self.repositories.values() {
 412                    let update = repo.read(cx).snapshot.initial_update(project_id);
 413                    for update in split_repository_update(update) {
 414                        client.send(update).log_err();
 415                    }
 416                }
 417                *downstream_client = Some((client, ProjectId(project_id)));
 418            }
 419            GitStoreState::Local {
 420                downstream: downstream_client,
 421                ..
 422            } => {
 423                let mut snapshots = HashMap::default();
 424                let (updates_tx, mut updates_rx) = mpsc::unbounded();
 425                for repo in self.repositories.values() {
 426                    updates_tx
 427                        .unbounded_send(DownstreamUpdate::UpdateRepository(
 428                            repo.read(cx).snapshot.clone(),
 429                        ))
 430                        .ok();
 431                }
 432                *downstream_client = Some(LocalDownstreamState {
 433                    client: client.clone(),
 434                    project_id: ProjectId(project_id),
 435                    updates_tx,
 436                    _task: cx.spawn(async move |this, cx| {
 437                        cx.background_spawn(async move {
 438                            while let Some(update) = updates_rx.next().await {
 439                                match update {
 440                                    DownstreamUpdate::UpdateRepository(snapshot) => {
 441                                        if let Some(old_snapshot) = snapshots.get_mut(&snapshot.id)
 442                                        {
 443                                            let update =
 444                                                snapshot.build_update(old_snapshot, project_id);
 445                                            *old_snapshot = snapshot;
 446                                            for update in split_repository_update(update) {
 447                                                client.send(update)?;
 448                                            }
 449                                        } else {
 450                                            let update = snapshot.initial_update(project_id);
 451                                            for update in split_repository_update(update) {
 452                                                client.send(update)?;
 453                                            }
 454                                            snapshots.insert(snapshot.id, snapshot);
 455                                        }
 456                                    }
 457                                    DownstreamUpdate::RemoveRepository(id) => {
 458                                        client.send(proto::RemoveRepository {
 459                                            project_id,
 460                                            id: id.to_proto(),
 461                                        })?;
 462                                    }
 463                                }
 464                            }
 465                            anyhow::Ok(())
 466                        })
 467                        .await
 468                        .ok();
 469                        this.update(cx, |this, _| {
 470                            if let GitStoreState::Local {
 471                                downstream: downstream_client,
 472                                ..
 473                            } = &mut this.state
 474                            {
 475                                downstream_client.take();
 476                            } else {
 477                                unreachable!("unshared called on remote store");
 478                            }
 479                        })
 480                    }),
 481                });
 482            }
 483            GitStoreState::Remote { .. } => {
 484                debug_panic!("shared called on remote store");
 485            }
 486        }
 487    }
 488
 489    pub fn unshared(&mut self, _cx: &mut Context<Self>) {
 490        match &mut self.state {
 491            GitStoreState::Local {
 492                downstream: downstream_client,
 493                ..
 494            } => {
 495                downstream_client.take();
 496            }
 497            GitStoreState::Ssh {
 498                downstream: downstream_client,
 499                ..
 500            } => {
 501                downstream_client.take();
 502            }
 503            GitStoreState::Remote { .. } => {
 504                debug_panic!("unshared called on remote store");
 505            }
 506        }
 507        self.shared_diffs.clear();
 508    }
 509
 510    pub(crate) fn forget_shared_diffs_for(&mut self, peer_id: &proto::PeerId) {
 511        self.shared_diffs.remove(peer_id);
 512    }
 513
 514    pub fn active_repository(&self) -> Option<Entity<Repository>> {
 515        self.active_repo_id
 516            .as_ref()
 517            .map(|id| self.repositories[&id].clone())
 518    }
 519
 520    pub fn open_unstaged_diff(
 521        &mut self,
 522        buffer: Entity<Buffer>,
 523        cx: &mut Context<Self>,
 524    ) -> Task<Result<Entity<BufferDiff>>> {
 525        let buffer_id = buffer.read(cx).remote_id();
 526        if let Some(diff_state) = self.diffs.get(&buffer_id) {
 527            if let Some(unstaged_diff) = diff_state
 528                .read(cx)
 529                .unstaged_diff
 530                .as_ref()
 531                .and_then(|weak| weak.upgrade())
 532            {
 533                if let Some(task) =
 534                    diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
 535                {
 536                    return cx.background_executor().spawn(async move {
 537                        task.await?;
 538                        Ok(unstaged_diff)
 539                    });
 540                }
 541                return Task::ready(Ok(unstaged_diff));
 542            }
 543        }
 544
 545        let Some((repo, repo_path)) =
 546            self.repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
 547        else {
 548            return Task::ready(Err(anyhow!("failed to find git repository for buffer")));
 549        };
 550
 551        let task = self
 552            .loading_diffs
 553            .entry((buffer_id, DiffKind::Unstaged))
 554            .or_insert_with(|| {
 555                let staged_text = repo.read(cx).load_staged_text(buffer_id, repo_path, cx);
 556                cx.spawn(async move |this, cx| {
 557                    Self::open_diff_internal(
 558                        this,
 559                        DiffKind::Unstaged,
 560                        staged_text.await.map(DiffBasesChange::SetIndex),
 561                        buffer,
 562                        cx,
 563                    )
 564                    .await
 565                    .map_err(Arc::new)
 566                })
 567                .shared()
 568            })
 569            .clone();
 570
 571        cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
 572    }
 573
 574    pub fn open_uncommitted_diff(
 575        &mut self,
 576        buffer: Entity<Buffer>,
 577        cx: &mut Context<Self>,
 578    ) -> Task<Result<Entity<BufferDiff>>> {
 579        let buffer_id = buffer.read(cx).remote_id();
 580
 581        if let Some(diff_state) = self.diffs.get(&buffer_id) {
 582            if let Some(uncommitted_diff) = diff_state
 583                .read(cx)
 584                .uncommitted_diff
 585                .as_ref()
 586                .and_then(|weak| weak.upgrade())
 587            {
 588                if let Some(task) =
 589                    diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
 590                {
 591                    return cx.background_executor().spawn(async move {
 592                        task.await?;
 593                        Ok(uncommitted_diff)
 594                    });
 595                }
 596                return Task::ready(Ok(uncommitted_diff));
 597            }
 598        }
 599
 600        let Some((repo, repo_path)) =
 601            self.repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
 602        else {
 603            return Task::ready(Err(anyhow!("failed to find git repository for buffer")));
 604        };
 605
 606        let task = self
 607            .loading_diffs
 608            .entry((buffer_id, DiffKind::Uncommitted))
 609            .or_insert_with(|| {
 610                let changes = repo.read(cx).load_committed_text(buffer_id, repo_path, cx);
 611                cx.spawn(async move |this, cx| {
 612                    Self::open_diff_internal(this, DiffKind::Uncommitted, changes.await, buffer, cx)
 613                        .await
 614                        .map_err(Arc::new)
 615                })
 616                .shared()
 617            })
 618            .clone();
 619
 620        cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
 621    }
 622
 623    async fn open_diff_internal(
 624        this: WeakEntity<Self>,
 625        kind: DiffKind,
 626        texts: Result<DiffBasesChange>,
 627        buffer_entity: Entity<Buffer>,
 628        cx: &mut AsyncApp,
 629    ) -> Result<Entity<BufferDiff>> {
 630        let diff_bases_change = match texts {
 631            Err(e) => {
 632                this.update(cx, |this, cx| {
 633                    let buffer = buffer_entity.read(cx);
 634                    let buffer_id = buffer.remote_id();
 635                    this.loading_diffs.remove(&(buffer_id, kind));
 636                })?;
 637                return Err(e);
 638            }
 639            Ok(change) => change,
 640        };
 641
 642        this.update(cx, |this, cx| {
 643            let buffer = buffer_entity.read(cx);
 644            let buffer_id = buffer.remote_id();
 645            let language = buffer.language().cloned();
 646            let language_registry = buffer.language_registry();
 647            let text_snapshot = buffer.text_snapshot();
 648            this.loading_diffs.remove(&(buffer_id, kind));
 649
 650            let diff_state = this
 651                .diffs
 652                .entry(buffer_id)
 653                .or_insert_with(|| cx.new(|_| BufferDiffState::default()));
 654
 655            let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
 656
 657            cx.subscribe(&diff, Self::on_buffer_diff_event).detach();
 658            diff_state.update(cx, |diff_state, cx| {
 659                diff_state.language = language;
 660                diff_state.language_registry = language_registry;
 661
 662                match kind {
 663                    DiffKind::Unstaged => diff_state.unstaged_diff = Some(diff.downgrade()),
 664                    DiffKind::Uncommitted => {
 665                        let unstaged_diff = if let Some(diff) = diff_state.unstaged_diff() {
 666                            diff
 667                        } else {
 668                            let unstaged_diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
 669                            diff_state.unstaged_diff = Some(unstaged_diff.downgrade());
 670                            unstaged_diff
 671                        };
 672
 673                        diff.update(cx, |diff, _| diff.set_secondary_diff(unstaged_diff));
 674                        diff_state.uncommitted_diff = Some(diff.downgrade())
 675                    }
 676                }
 677
 678                let rx = diff_state.diff_bases_changed(text_snapshot, diff_bases_change, 0, cx);
 679
 680                anyhow::Ok(async move {
 681                    rx.await.ok();
 682                    Ok(diff)
 683                })
 684            })
 685        })??
 686        .await
 687    }
 688
 689    pub fn get_unstaged_diff(&self, buffer_id: BufferId, cx: &App) -> Option<Entity<BufferDiff>> {
 690        let diff_state = self.diffs.get(&buffer_id)?;
 691        diff_state.read(cx).unstaged_diff.as_ref()?.upgrade()
 692    }
 693
 694    pub fn get_uncommitted_diff(
 695        &self,
 696        buffer_id: BufferId,
 697        cx: &App,
 698    ) -> Option<Entity<BufferDiff>> {
 699        let diff_state = self.diffs.get(&buffer_id)?;
 700        diff_state.read(cx).uncommitted_diff.as_ref()?.upgrade()
 701    }
 702
 703    pub fn project_path_git_status(
 704        &self,
 705        project_path: &ProjectPath,
 706        cx: &App,
 707    ) -> Option<FileStatus> {
 708        let (repo, repo_path) = self.repository_and_path_for_project_path(project_path, cx)?;
 709        Some(repo.read(cx).status_for_path(&repo_path)?.status)
 710    }
 711
 712    pub fn checkpoint(&self, cx: &App) -> Task<Result<GitStoreCheckpoint>> {
 713        let mut work_directory_abs_paths = Vec::new();
 714        let mut checkpoints = Vec::new();
 715        for repository in self.repositories.values() {
 716            let repository = repository.read(cx);
 717            work_directory_abs_paths.push(repository.snapshot.work_directory_abs_path.clone());
 718            checkpoints.push(repository.checkpoint().map(|checkpoint| checkpoint?));
 719        }
 720
 721        cx.background_executor().spawn(async move {
 722            let checkpoints = future::try_join_all(checkpoints).await?;
 723            Ok(GitStoreCheckpoint {
 724                checkpoints_by_work_dir_abs_path: work_directory_abs_paths
 725                    .into_iter()
 726                    .zip(checkpoints)
 727                    .collect(),
 728            })
 729        })
 730    }
 731
 732    pub fn restore_checkpoint(&self, checkpoint: GitStoreCheckpoint, cx: &App) -> Task<Result<()>> {
 733        let repositories_by_work_dir_abs_path = self
 734            .repositories
 735            .values()
 736            .map(|repo| (repo.read(cx).snapshot.work_directory_abs_path.clone(), repo))
 737            .collect::<HashMap<_, _>>();
 738
 739        let mut tasks = Vec::new();
 740        for (work_dir_abs_path, checkpoint) in checkpoint.checkpoints_by_work_dir_abs_path {
 741            if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path) {
 742                let restore = repository.read(cx).restore_checkpoint(checkpoint);
 743                tasks.push(async move { restore.await? });
 744            }
 745        }
 746        cx.background_spawn(async move {
 747            future::try_join_all(tasks).await?;
 748            Ok(())
 749        })
 750    }
 751
 752    /// Compares two checkpoints, returning true if they are equal.
 753    pub fn compare_checkpoints(
 754        &self,
 755        left: GitStoreCheckpoint,
 756        mut right: GitStoreCheckpoint,
 757        cx: &App,
 758    ) -> Task<Result<bool>> {
 759        let repositories_by_work_dir_abs_path = self
 760            .repositories
 761            .values()
 762            .map(|repo| (repo.read(cx).snapshot.work_directory_abs_path.clone(), repo))
 763            .collect::<HashMap<_, _>>();
 764
 765        let mut tasks = Vec::new();
 766        for (work_dir_abs_path, left_checkpoint) in left.checkpoints_by_work_dir_abs_path {
 767            if let Some(right_checkpoint) = right
 768                .checkpoints_by_work_dir_abs_path
 769                .remove(&work_dir_abs_path)
 770            {
 771                if let Some(repository) = repositories_by_work_dir_abs_path.get(&work_dir_abs_path)
 772                {
 773                    let compare = repository
 774                        .read(cx)
 775                        .compare_checkpoints(left_checkpoint, right_checkpoint);
 776                    tasks.push(async move { compare.await? });
 777                }
 778            } else {
 779                return Task::ready(Ok(false));
 780            }
 781        }
 782        cx.background_spawn(async move {
 783            Ok(future::try_join_all(tasks)
 784                .await?
 785                .into_iter()
 786                .all(|result| result))
 787        })
 788    }
 789
 790    pub fn delete_checkpoint(&self, checkpoint: GitStoreCheckpoint, cx: &App) -> Task<Result<()>> {
 791        let repositories_by_work_directory_abs_path = self
 792            .repositories
 793            .values()
 794            .map(|repo| (repo.read(cx).snapshot.work_directory_abs_path.clone(), repo))
 795            .collect::<HashMap<_, _>>();
 796
 797        let mut tasks = Vec::new();
 798        for (work_dir_abs_path, checkpoint) in checkpoint.checkpoints_by_work_dir_abs_path {
 799            if let Some(repository) =
 800                repositories_by_work_directory_abs_path.get(&work_dir_abs_path)
 801            {
 802                let delete = repository.read(cx).delete_checkpoint(checkpoint);
 803                tasks.push(async move { delete.await? });
 804            }
 805        }
 806        cx.background_spawn(async move {
 807            future::try_join_all(tasks).await?;
 808            Ok(())
 809        })
 810    }
 811
 812    /// Blames a buffer.
 813    pub fn blame_buffer(
 814        &self,
 815        buffer: &Entity<Buffer>,
 816        version: Option<clock::Global>,
 817        cx: &App,
 818    ) -> Task<Result<Option<Blame>>> {
 819        let buffer = buffer.read(cx);
 820        let Some((repo, repo_path)) =
 821            self.repository_and_path_for_buffer_id(buffer.remote_id(), cx)
 822        else {
 823            return Task::ready(Err(anyhow!("failed to find a git repository for buffer")));
 824        };
 825        let content = match &version {
 826            Some(version) => buffer.rope_for_version(version).clone(),
 827            None => buffer.as_rope().clone(),
 828        };
 829        let version = version.unwrap_or(buffer.version());
 830        let buffer_id = buffer.remote_id();
 831
 832        let rx = repo.read(cx).send_job(move |state, _| async move {
 833            match state {
 834                RepositoryState::Local { backend, .. } => backend
 835                    .blame(repo_path.clone(), content)
 836                    .await
 837                    .with_context(|| format!("Failed to blame {:?}", repo_path.0))
 838                    .map(Some),
 839                RepositoryState::Remote { project_id, client } => {
 840                    let response = client
 841                        .request(proto::BlameBuffer {
 842                            project_id: project_id.to_proto(),
 843                            buffer_id: buffer_id.into(),
 844                            version: serialize_version(&version),
 845                        })
 846                        .await?;
 847                    Ok(deserialize_blame_buffer_response(response))
 848                }
 849            }
 850        });
 851
 852        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
 853    }
 854
 855    pub fn get_permalink_to_line(
 856        &self,
 857        buffer: &Entity<Buffer>,
 858        selection: Range<u32>,
 859        cx: &App,
 860    ) -> Task<Result<url::Url>> {
 861        let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
 862            return Task::ready(Err(anyhow!("buffer has no file")));
 863        };
 864
 865        let Some((repo, repo_path)) = self.repository_and_path_for_project_path(
 866            &(file.worktree.read(cx).id(), file.path.clone()).into(),
 867            cx,
 868        ) else {
 869            // If we're not in a Git repo, check whether this is a Rust source
 870            // file in the Cargo registry (presumably opened with go-to-definition
 871            // from a normal Rust file). If so, we can put together a permalink
 872            // using crate metadata.
 873            if buffer
 874                .read(cx)
 875                .language()
 876                .is_none_or(|lang| lang.name() != "Rust".into())
 877            {
 878                return Task::ready(Err(anyhow!("no permalink available")));
 879            }
 880            let Some(file_path) = file.worktree.read(cx).absolutize(&file.path).ok() else {
 881                return Task::ready(Err(anyhow!("no permalink available")));
 882            };
 883            return cx.spawn(async move |cx| {
 884                let provider_registry = cx.update(GitHostingProviderRegistry::default_global)?;
 885                get_permalink_in_rust_registry_src(provider_registry, file_path, selection)
 886                    .map_err(|_| anyhow!("no permalink available"))
 887            });
 888
 889            // TODO remote case
 890        };
 891
 892        let buffer_id = buffer.read(cx).remote_id();
 893        let branch = repo.read(cx).branch.clone();
 894        let remote = branch
 895            .as_ref()
 896            .and_then(|b| b.upstream.as_ref())
 897            .and_then(|b| b.remote_name())
 898            .unwrap_or("origin")
 899            .to_string();
 900        let rx = repo.read(cx).send_job(move |state, cx| async move {
 901            match state {
 902                RepositoryState::Local { backend, .. } => {
 903                    let origin_url = backend
 904                        .remote_url(&remote)
 905                        .ok_or_else(|| anyhow!("remote \"{remote}\" not found"))?;
 906
 907                    let sha = backend
 908                        .head_sha()
 909                        .ok_or_else(|| anyhow!("failed to read HEAD SHA"))?;
 910
 911                    let provider_registry =
 912                        cx.update(GitHostingProviderRegistry::default_global)?;
 913
 914                    let (provider, remote) =
 915                        parse_git_remote_url(provider_registry, &origin_url)
 916                            .ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
 917
 918                    let path = repo_path
 919                        .to_str()
 920                        .ok_or_else(|| anyhow!("failed to convert path to string"))?;
 921
 922                    Ok(provider.build_permalink(
 923                        remote,
 924                        BuildPermalinkParams {
 925                            sha: &sha,
 926                            path,
 927                            selection: Some(selection),
 928                        },
 929                    ))
 930                }
 931                RepositoryState::Remote { project_id, client } => {
 932                    let response = client
 933                        .request(proto::GetPermalinkToLine {
 934                            project_id: project_id.to_proto(),
 935                            buffer_id: buffer_id.into(),
 936                            selection: Some(proto::Range {
 937                                start: selection.start as u64,
 938                                end: selection.end as u64,
 939                            }),
 940                        })
 941                        .await?;
 942
 943                    url::Url::parse(&response.permalink).context("failed to parse permalink")
 944                }
 945            }
 946        });
 947        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
 948    }
 949
 950    fn downstream_client(&self) -> Option<(AnyProtoClient, ProjectId)> {
 951        match &self.state {
 952            GitStoreState::Local {
 953                downstream: downstream_client,
 954                ..
 955            } => downstream_client
 956                .as_ref()
 957                .map(|state| (state.client.clone(), state.project_id)),
 958            GitStoreState::Ssh {
 959                downstream: downstream_client,
 960                ..
 961            } => downstream_client.clone(),
 962            GitStoreState::Remote { .. } => None,
 963        }
 964    }
 965
 966    fn upstream_client(&self) -> Option<AnyProtoClient> {
 967        match &self.state {
 968            GitStoreState::Local { .. } => None,
 969            GitStoreState::Ssh {
 970                upstream_client, ..
 971            }
 972            | GitStoreState::Remote {
 973                upstream_client, ..
 974            } => Some(upstream_client.clone()),
 975        }
 976    }
 977
 978    fn on_worktree_store_event(
 979        &mut self,
 980        worktree_store: Entity<WorktreeStore>,
 981        event: &WorktreeStoreEvent,
 982        cx: &mut Context<Self>,
 983    ) {
 984        let GitStoreState::Local {
 985            project_environment,
 986            downstream,
 987            next_repository_id,
 988            fs,
 989        } = &self.state
 990        else {
 991            return;
 992        };
 993
 994        match event {
 995            WorktreeStoreEvent::WorktreeUpdatedEntries(worktree_id, updated_entries) => {
 996                let mut paths_by_git_repo = HashMap::<_, Vec<_>>::default();
 997                for (relative_path, _, _) in updated_entries.iter() {
 998                    let Some((repo, repo_path)) = self.repository_and_path_for_project_path(
 999                        &(*worktree_id, relative_path.clone()).into(),
1000                        cx,
1001                    ) else {
1002                        continue;
1003                    };
1004                    paths_by_git_repo.entry(repo).or_default().push(repo_path)
1005                }
1006
1007                for (repo, paths) in paths_by_git_repo {
1008                    repo.update(cx, |repo, cx| {
1009                        repo.paths_changed(
1010                            paths,
1011                            downstream
1012                                .as_ref()
1013                                .map(|downstream| downstream.updates_tx.clone()),
1014                            cx,
1015                        );
1016                    });
1017                }
1018            }
1019            WorktreeStoreEvent::WorktreeUpdatedGitRepositories(worktree_id, changed_repos) => {
1020                let Some(worktree) = worktree_store.read(cx).worktree_for_id(*worktree_id, cx)
1021                else {
1022                    return;
1023                };
1024                if !worktree.read(cx).is_visible() {
1025                    log::debug!(
1026                        "not adding repositories for local worktree {:?} because it's not visible",
1027                        worktree.read(cx).abs_path()
1028                    );
1029                    return;
1030                }
1031                self.update_repositories_from_worktree(
1032                    project_environment.clone(),
1033                    next_repository_id.clone(),
1034                    downstream
1035                        .as_ref()
1036                        .map(|downstream| downstream.updates_tx.clone()),
1037                    changed_repos.clone(),
1038                    fs.clone(),
1039                    cx,
1040                );
1041                self.local_worktree_git_repos_changed(worktree, changed_repos, cx);
1042            }
1043            _ => {}
1044        }
1045    }
1046
1047    fn on_repository_event(
1048        &mut self,
1049        repo: Entity<Repository>,
1050        event: &RepositoryEvent,
1051        cx: &mut Context<Self>,
1052    ) {
1053        let id = repo.read(cx).id;
1054        cx.emit(GitStoreEvent::RepositoryUpdated(
1055            id,
1056            event.clone(),
1057            self.active_repo_id == Some(id),
1058        ))
1059    }
1060
1061    /// Update our list of repositories and schedule git scans in response to a notification from a worktree,
1062    fn update_repositories_from_worktree(
1063        &mut self,
1064        project_environment: Entity<ProjectEnvironment>,
1065        next_repository_id: Arc<AtomicU64>,
1066        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
1067        updated_git_repositories: UpdatedGitRepositoriesSet,
1068        fs: Arc<dyn Fs>,
1069        cx: &mut Context<Self>,
1070    ) {
1071        let mut removed_ids = Vec::new();
1072        for update in updated_git_repositories.iter() {
1073            if let Some((id, existing)) = self.repositories.iter().find(|(_, repo)| {
1074                let existing_work_directory_abs_path =
1075                    repo.read(cx).work_directory_abs_path.clone();
1076                Some(&existing_work_directory_abs_path)
1077                    == update.old_work_directory_abs_path.as_ref()
1078                    || Some(&existing_work_directory_abs_path)
1079                        == update.new_work_directory_abs_path.as_ref()
1080            }) {
1081                if let Some(new_work_directory_abs_path) =
1082                    update.new_work_directory_abs_path.clone()
1083                {
1084                    existing.update(cx, |existing, cx| {
1085                        existing.snapshot.work_directory_abs_path = new_work_directory_abs_path;
1086                        existing.schedule_scan(updates_tx.clone(), cx);
1087                    });
1088                } else {
1089                    removed_ids.push(*id);
1090                }
1091            } else if let Some((work_directory_abs_path, dot_git_abs_path)) = update
1092                .new_work_directory_abs_path
1093                .clone()
1094                .zip(update.dot_git_abs_path.clone())
1095            {
1096                let id = RepositoryId(next_repository_id.fetch_add(1, atomic::Ordering::Release));
1097                let git_store = cx.weak_entity();
1098                let repo = cx.new(|cx| {
1099                    let mut repo = Repository::local(
1100                        id,
1101                        work_directory_abs_path,
1102                        dot_git_abs_path,
1103                        project_environment.downgrade(),
1104                        fs.clone(),
1105                        git_store,
1106                        cx,
1107                    );
1108                    repo.schedule_scan(updates_tx.clone(), cx);
1109                    repo
1110                });
1111                self._subscriptions
1112                    .push(cx.subscribe(&repo, Self::on_repository_event));
1113                self.repositories.insert(id, repo);
1114                cx.emit(GitStoreEvent::RepositoryAdded(id));
1115                self.active_repo_id.get_or_insert_with(|| {
1116                    cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
1117                    id
1118                });
1119            }
1120        }
1121
1122        for id in removed_ids {
1123            if self.active_repo_id == Some(id) {
1124                self.active_repo_id = None;
1125                cx.emit(GitStoreEvent::ActiveRepositoryChanged(None));
1126            }
1127            self.repositories.remove(&id);
1128            if let Some(updates_tx) = updates_tx.as_ref() {
1129                updates_tx
1130                    .unbounded_send(DownstreamUpdate::RemoveRepository(id))
1131                    .ok();
1132            }
1133        }
1134    }
1135
1136    fn on_buffer_store_event(
1137        &mut self,
1138        _: Entity<BufferStore>,
1139        event: &BufferStoreEvent,
1140        cx: &mut Context<Self>,
1141    ) {
1142        match event {
1143            BufferStoreEvent::BufferAdded(buffer) => {
1144                cx.subscribe(&buffer, |this, buffer, event, cx| {
1145                    if let BufferEvent::LanguageChanged = event {
1146                        let buffer_id = buffer.read(cx).remote_id();
1147                        if let Some(diff_state) = this.diffs.get(&buffer_id) {
1148                            diff_state.update(cx, |diff_state, cx| {
1149                                diff_state.buffer_language_changed(buffer, cx);
1150                            });
1151                        }
1152                    }
1153                })
1154                .detach();
1155            }
1156            BufferStoreEvent::SharedBufferClosed(peer_id, buffer_id) => {
1157                if let Some(diffs) = self.shared_diffs.get_mut(peer_id) {
1158                    diffs.remove(buffer_id);
1159                }
1160            }
1161            BufferStoreEvent::BufferDropped(buffer_id) => {
1162                self.diffs.remove(&buffer_id);
1163                for diffs in self.shared_diffs.values_mut() {
1164                    diffs.remove(buffer_id);
1165                }
1166            }
1167
1168            _ => {}
1169        }
1170    }
1171
1172    pub fn recalculate_buffer_diffs(
1173        &mut self,
1174        buffers: Vec<Entity<Buffer>>,
1175        cx: &mut Context<Self>,
1176    ) -> impl Future<Output = ()> + use<> {
1177        let mut futures = Vec::new();
1178        for buffer in buffers {
1179            if let Some(diff_state) = self.diffs.get_mut(&buffer.read(cx).remote_id()) {
1180                let buffer = buffer.read(cx).text_snapshot();
1181                futures.push(diff_state.update(cx, |diff_state, cx| {
1182                    diff_state.recalculate_diffs(
1183                        buffer,
1184                        diff_state.hunk_staging_operation_count,
1185                        cx,
1186                    )
1187                }));
1188            }
1189        }
1190        async move {
1191            futures::future::join_all(futures).await;
1192        }
1193    }
1194
1195    fn on_buffer_diff_event(
1196        &mut self,
1197        diff: Entity<buffer_diff::BufferDiff>,
1198        event: &BufferDiffEvent,
1199        cx: &mut Context<Self>,
1200    ) {
1201        if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
1202            let buffer_id = diff.read(cx).buffer_id;
1203            if let Some(diff_state) = self.diffs.get(&buffer_id) {
1204                diff_state.update(cx, |diff_state, _| {
1205                    diff_state.hunk_staging_operation_count += 1;
1206                });
1207            }
1208            if let Some((repo, path)) = self.repository_and_path_for_buffer_id(buffer_id, cx) {
1209                let recv = repo.update(cx, |repo, cx| {
1210                    log::debug!("updating index text for buffer {}", path.display());
1211                    repo.spawn_set_index_text_job(
1212                        path,
1213                        new_index_text.as_ref().map(|rope| rope.to_string()),
1214                        cx,
1215                    )
1216                });
1217                let diff = diff.downgrade();
1218                cx.spawn(async move |this, cx| {
1219                    if let Ok(Err(error)) = cx.background_spawn(recv).await {
1220                        diff.update(cx, |diff, cx| {
1221                            diff.clear_pending_hunks(cx);
1222                        })
1223                        .ok();
1224                        this.update(cx, |_, cx| cx.emit(GitStoreEvent::IndexWriteError(error)))
1225                            .ok();
1226                    }
1227                })
1228                .detach();
1229            }
1230        }
1231    }
1232
1233    fn local_worktree_git_repos_changed(
1234        &mut self,
1235        worktree: Entity<Worktree>,
1236        changed_repos: &UpdatedGitRepositoriesSet,
1237        cx: &mut Context<Self>,
1238    ) {
1239        log::debug!("local worktree repos changed");
1240        debug_assert!(worktree.read(cx).is_local());
1241
1242        let mut diff_state_updates = HashMap::<Entity<Repository>, Vec<_>>::default();
1243        for (buffer_id, diff_state) in &self.diffs {
1244            let Some(buffer) = self.buffer_store.read(cx).get(*buffer_id) else {
1245                continue;
1246            };
1247            let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
1248                continue;
1249            };
1250            if file.worktree != worktree {
1251                continue;
1252            }
1253            let Some((repo, repo_path)) =
1254                self.repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
1255            else {
1256                continue;
1257            };
1258            if !changed_repos.iter().any(|update| {
1259                update.old_work_directory_abs_path.as_ref()
1260                    == Some(&repo.read(cx).work_directory_abs_path)
1261                    || update.new_work_directory_abs_path.as_ref()
1262                        == Some(&repo.read(cx).work_directory_abs_path)
1263            }) {
1264                continue;
1265            }
1266
1267            let diff_state = diff_state.read(cx);
1268            let has_unstaged_diff = diff_state
1269                .unstaged_diff
1270                .as_ref()
1271                .is_some_and(|diff| diff.is_upgradable());
1272            let has_uncommitted_diff = diff_state
1273                .uncommitted_diff
1274                .as_ref()
1275                .is_some_and(|set| set.is_upgradable());
1276
1277            let update = (
1278                buffer,
1279                repo_path,
1280                has_unstaged_diff.then(|| diff_state.index_text.clone()),
1281                has_uncommitted_diff.then(|| diff_state.head_text.clone()),
1282                diff_state.hunk_staging_operation_count,
1283            );
1284            diff_state_updates.entry(repo).or_default().push(update);
1285        }
1286
1287        if diff_state_updates.is_empty() {
1288            return;
1289        }
1290
1291        for (repo, repo_diff_state_updates) in diff_state_updates.into_iter() {
1292            let git_store = cx.weak_entity();
1293
1294            let _ = repo.read(cx).send_keyed_job(
1295                Some(GitJobKey::BatchReadIndex),
1296                |state, mut cx| async move {
1297                    let RepositoryState::Local { backend, .. } = state else {
1298                        log::error!("tried to recompute diffs for a non-local repository");
1299                        return;
1300                    };
1301                    let mut diff_bases_changes_by_buffer = Vec::new();
1302                    for (
1303                        buffer,
1304                        repo_path,
1305                        current_index_text,
1306                        current_head_text,
1307                        hunk_staging_operation_count,
1308                    ) in &repo_diff_state_updates
1309                    {
1310                        let index_text = if current_index_text.is_some() {
1311                            backend.load_index_text(repo_path.clone()).await
1312                        } else {
1313                            None
1314                        };
1315                        let head_text = if current_head_text.is_some() {
1316                            backend.load_committed_text(repo_path.clone()).await
1317                        } else {
1318                            None
1319                        };
1320
1321                        // Avoid triggering a diff update if the base text has not changed.
1322                        if let Some((current_index, current_head)) =
1323                            current_index_text.as_ref().zip(current_head_text.as_ref())
1324                        {
1325                            if current_index.as_deref() == index_text.as_ref()
1326                                && current_head.as_deref() == head_text.as_ref()
1327                            {
1328                                continue;
1329                            }
1330                        }
1331
1332                        let diff_bases_change =
1333                            match (current_index_text.is_some(), current_head_text.is_some()) {
1334                                (true, true) => Some(if index_text == head_text {
1335                                    DiffBasesChange::SetBoth(head_text)
1336                                } else {
1337                                    DiffBasesChange::SetEach {
1338                                        index: index_text,
1339                                        head: head_text,
1340                                    }
1341                                }),
1342                                (true, false) => Some(DiffBasesChange::SetIndex(index_text)),
1343                                (false, true) => Some(DiffBasesChange::SetHead(head_text)),
1344                                (false, false) => None,
1345                            };
1346
1347                        diff_bases_changes_by_buffer.push((
1348                            buffer,
1349                            diff_bases_change,
1350                            *hunk_staging_operation_count,
1351                        ))
1352                    }
1353
1354                    git_store
1355                        .update(&mut cx, |git_store, cx| {
1356                            for (buffer, diff_bases_change, hunk_staging_operation_count) in
1357                                diff_bases_changes_by_buffer
1358                            {
1359                                let Some(diff_state) =
1360                                    git_store.diffs.get(&buffer.read(cx).remote_id())
1361                                else {
1362                                    continue;
1363                                };
1364                                let Some(diff_bases_change) = diff_bases_change else {
1365                                    continue;
1366                                };
1367
1368                                let downstream_client = git_store.downstream_client();
1369                                diff_state.update(cx, |diff_state, cx| {
1370                                    use proto::update_diff_bases::Mode;
1371
1372                                    let buffer = buffer.read(cx);
1373                                    if let Some((client, project_id)) = downstream_client {
1374                                        let (staged_text, committed_text, mode) =
1375                                            match diff_bases_change.clone() {
1376                                                DiffBasesChange::SetIndex(index) => {
1377                                                    (index, None, Mode::IndexOnly)
1378                                                }
1379                                                DiffBasesChange::SetHead(head) => {
1380                                                    (None, head, Mode::HeadOnly)
1381                                                }
1382                                                DiffBasesChange::SetEach { index, head } => {
1383                                                    (index, head, Mode::IndexAndHead)
1384                                                }
1385                                                DiffBasesChange::SetBoth(text) => {
1386                                                    (None, text, Mode::IndexMatchesHead)
1387                                                }
1388                                            };
1389                                        let message = proto::UpdateDiffBases {
1390                                            project_id: project_id.to_proto(),
1391                                            buffer_id: buffer.remote_id().to_proto(),
1392                                            staged_text,
1393                                            committed_text,
1394                                            mode: mode as i32,
1395                                        };
1396
1397                                        client.send(message).log_err();
1398                                    }
1399
1400                                    let _ = diff_state.diff_bases_changed(
1401                                        buffer.text_snapshot(),
1402                                        diff_bases_change,
1403                                        hunk_staging_operation_count,
1404                                        cx,
1405                                    );
1406                                });
1407                            }
1408                        })
1409                        .ok();
1410                },
1411            );
1412        }
1413    }
1414
1415    pub fn repositories(&self) -> &HashMap<RepositoryId, Entity<Repository>> {
1416        &self.repositories
1417    }
1418
1419    pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
1420        let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
1421        let status = repo.read(cx).snapshot.status_for_path(&path)?;
1422        Some(status.status)
1423    }
1424
1425    pub fn repository_and_path_for_buffer_id(
1426        &self,
1427        buffer_id: BufferId,
1428        cx: &App,
1429    ) -> Option<(Entity<Repository>, RepoPath)> {
1430        let buffer = self.buffer_store.read(cx).get(buffer_id)?;
1431        let project_path = buffer.read(cx).project_path(cx)?;
1432        self.repository_and_path_for_project_path(&project_path, cx)
1433    }
1434
1435    pub fn repository_and_path_for_project_path(
1436        &self,
1437        path: &ProjectPath,
1438        cx: &App,
1439    ) -> Option<(Entity<Repository>, RepoPath)> {
1440        let abs_path = self.worktree_store.read(cx).absolutize(path, cx)?;
1441        self.repositories
1442            .values()
1443            .filter_map(|repo| {
1444                let repo_path = repo.read(cx).abs_path_to_repo_path(&abs_path)?;
1445                Some((repo.clone(), repo_path))
1446            })
1447            .max_by_key(|(repo, _)| repo.read(cx).work_directory_abs_path.clone())
1448    }
1449
1450    pub fn git_init(
1451        &self,
1452        path: Arc<Path>,
1453        fallback_branch_name: String,
1454        cx: &App,
1455    ) -> Task<Result<()>> {
1456        match &self.state {
1457            GitStoreState::Local { fs, .. } => {
1458                let fs = fs.clone();
1459                cx.background_executor()
1460                    .spawn(async move { fs.git_init(&path, fallback_branch_name) })
1461            }
1462            GitStoreState::Ssh {
1463                upstream_client,
1464                upstream_project_id: project_id,
1465                ..
1466            }
1467            | GitStoreState::Remote {
1468                upstream_client,
1469                upstream_project_id: project_id,
1470                ..
1471            } => {
1472                let client = upstream_client.clone();
1473                let project_id = *project_id;
1474                cx.background_executor().spawn(async move {
1475                    client
1476                        .request(proto::GitInit {
1477                            project_id: project_id.0,
1478                            abs_path: path.to_string_lossy().to_string(),
1479                            fallback_branch_name,
1480                        })
1481                        .await?;
1482                    Ok(())
1483                })
1484            }
1485        }
1486    }
1487
1488    async fn handle_update_repository(
1489        this: Entity<Self>,
1490        envelope: TypedEnvelope<proto::UpdateRepository>,
1491        mut cx: AsyncApp,
1492    ) -> Result<()> {
1493        this.update(&mut cx, |this, cx| {
1494            let mut update = envelope.payload;
1495
1496            let id = RepositoryId::from_proto(update.id);
1497            let client = this
1498                .upstream_client()
1499                .context("no upstream client")?
1500                .clone();
1501
1502            let mut is_new = false;
1503            let repo = this.repositories.entry(id).or_insert_with(|| {
1504                is_new = true;
1505                let git_store = cx.weak_entity();
1506                cx.new(|cx| {
1507                    Repository::remote(
1508                        id,
1509                        Path::new(&update.abs_path).into(),
1510                        ProjectId(update.project_id),
1511                        client,
1512                        git_store,
1513                        cx,
1514                    )
1515                })
1516            });
1517            if is_new {
1518                this._subscriptions
1519                    .push(cx.subscribe(&repo, Self::on_repository_event))
1520            }
1521
1522            repo.update(cx, {
1523                let update = update.clone();
1524                |repo, cx| repo.apply_remote_update(update, cx)
1525            })?;
1526
1527            this.active_repo_id.get_or_insert_with(|| {
1528                cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
1529                id
1530            });
1531
1532            if let Some((client, project_id)) = this.downstream_client() {
1533                update.project_id = project_id.to_proto();
1534                client.send(update).log_err();
1535            }
1536            Ok(())
1537        })?
1538    }
1539
1540    async fn handle_remove_repository(
1541        this: Entity<Self>,
1542        envelope: TypedEnvelope<proto::RemoveRepository>,
1543        mut cx: AsyncApp,
1544    ) -> Result<()> {
1545        this.update(&mut cx, |this, cx| {
1546            let mut update = envelope.payload;
1547            let id = RepositoryId::from_proto(update.id);
1548            this.repositories.remove(&id);
1549            if let Some((client, project_id)) = this.downstream_client() {
1550                update.project_id = project_id.to_proto();
1551                client.send(update).log_err();
1552            }
1553            if this.active_repo_id == Some(id) {
1554                this.active_repo_id = None;
1555                cx.emit(GitStoreEvent::ActiveRepositoryChanged(None));
1556            }
1557            cx.emit(GitStoreEvent::RepositoryRemoved(id));
1558        })
1559    }
1560
1561    async fn handle_git_init(
1562        this: Entity<Self>,
1563        envelope: TypedEnvelope<proto::GitInit>,
1564        cx: AsyncApp,
1565    ) -> Result<proto::Ack> {
1566        let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
1567        let name = envelope.payload.fallback_branch_name;
1568        cx.update(|cx| this.read(cx).git_init(path, name, cx))?
1569            .await?;
1570
1571        Ok(proto::Ack {})
1572    }
1573
1574    async fn handle_fetch(
1575        this: Entity<Self>,
1576        envelope: TypedEnvelope<proto::Fetch>,
1577        mut cx: AsyncApp,
1578    ) -> Result<proto::RemoteMessageResponse> {
1579        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1580        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1581        let askpass_id = envelope.payload.askpass_id;
1582
1583        let askpass = make_remote_delegate(
1584            this,
1585            envelope.payload.project_id,
1586            repository_id,
1587            askpass_id,
1588            &mut cx,
1589        );
1590
1591        let remote_output = repository_handle
1592            .update(&mut cx, |repository_handle, cx| {
1593                repository_handle.fetch(askpass, cx)
1594            })?
1595            .await??;
1596
1597        Ok(proto::RemoteMessageResponse {
1598            stdout: remote_output.stdout,
1599            stderr: remote_output.stderr,
1600        })
1601    }
1602
1603    async fn handle_push(
1604        this: Entity<Self>,
1605        envelope: TypedEnvelope<proto::Push>,
1606        mut cx: AsyncApp,
1607    ) -> Result<proto::RemoteMessageResponse> {
1608        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1609        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1610
1611        let askpass_id = envelope.payload.askpass_id;
1612        let askpass = make_remote_delegate(
1613            this,
1614            envelope.payload.project_id,
1615            repository_id,
1616            askpass_id,
1617            &mut cx,
1618        );
1619
1620        let options = envelope
1621            .payload
1622            .options
1623            .as_ref()
1624            .map(|_| match envelope.payload.options() {
1625                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
1626                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
1627            });
1628
1629        let branch_name = envelope.payload.branch_name.into();
1630        let remote_name = envelope.payload.remote_name.into();
1631
1632        let remote_output = repository_handle
1633            .update(&mut cx, |repository_handle, cx| {
1634                repository_handle.push(branch_name, remote_name, options, askpass, cx)
1635            })?
1636            .await??;
1637        Ok(proto::RemoteMessageResponse {
1638            stdout: remote_output.stdout,
1639            stderr: remote_output.stderr,
1640        })
1641    }
1642
1643    async fn handle_pull(
1644        this: Entity<Self>,
1645        envelope: TypedEnvelope<proto::Pull>,
1646        mut cx: AsyncApp,
1647    ) -> Result<proto::RemoteMessageResponse> {
1648        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1649        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1650        let askpass_id = envelope.payload.askpass_id;
1651        let askpass = make_remote_delegate(
1652            this,
1653            envelope.payload.project_id,
1654            repository_id,
1655            askpass_id,
1656            &mut cx,
1657        );
1658
1659        let branch_name = envelope.payload.branch_name.into();
1660        let remote_name = envelope.payload.remote_name.into();
1661
1662        let remote_message = repository_handle
1663            .update(&mut cx, |repository_handle, cx| {
1664                repository_handle.pull(branch_name, remote_name, askpass, cx)
1665            })?
1666            .await??;
1667
1668        Ok(proto::RemoteMessageResponse {
1669            stdout: remote_message.stdout,
1670            stderr: remote_message.stderr,
1671        })
1672    }
1673
1674    async fn handle_stage(
1675        this: Entity<Self>,
1676        envelope: TypedEnvelope<proto::Stage>,
1677        mut cx: AsyncApp,
1678    ) -> Result<proto::Ack> {
1679        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1680        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1681
1682        let entries = envelope
1683            .payload
1684            .paths
1685            .into_iter()
1686            .map(PathBuf::from)
1687            .map(RepoPath::new)
1688            .collect();
1689
1690        repository_handle
1691            .update(&mut cx, |repository_handle, cx| {
1692                repository_handle.stage_entries(entries, cx)
1693            })?
1694            .await?;
1695        Ok(proto::Ack {})
1696    }
1697
1698    async fn handle_unstage(
1699        this: Entity<Self>,
1700        envelope: TypedEnvelope<proto::Unstage>,
1701        mut cx: AsyncApp,
1702    ) -> Result<proto::Ack> {
1703        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1704        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1705
1706        let entries = envelope
1707            .payload
1708            .paths
1709            .into_iter()
1710            .map(PathBuf::from)
1711            .map(RepoPath::new)
1712            .collect();
1713
1714        repository_handle
1715            .update(&mut cx, |repository_handle, cx| {
1716                repository_handle.unstage_entries(entries, cx)
1717            })?
1718            .await?;
1719
1720        Ok(proto::Ack {})
1721    }
1722
1723    async fn handle_set_index_text(
1724        this: Entity<Self>,
1725        envelope: TypedEnvelope<proto::SetIndexText>,
1726        mut cx: AsyncApp,
1727    ) -> Result<proto::Ack> {
1728        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1729        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1730
1731        repository_handle
1732            .update(&mut cx, |repository_handle, cx| {
1733                repository_handle.spawn_set_index_text_job(
1734                    RepoPath::from_str(&envelope.payload.path),
1735                    envelope.payload.text,
1736                    cx,
1737                )
1738            })?
1739            .await??;
1740        Ok(proto::Ack {})
1741    }
1742
1743    async fn handle_commit(
1744        this: Entity<Self>,
1745        envelope: TypedEnvelope<proto::Commit>,
1746        mut cx: AsyncApp,
1747    ) -> Result<proto::Ack> {
1748        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1749        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1750
1751        let message = SharedString::from(envelope.payload.message);
1752        let name = envelope.payload.name.map(SharedString::from);
1753        let email = envelope.payload.email.map(SharedString::from);
1754
1755        repository_handle
1756            .update(&mut cx, |repository_handle, cx| {
1757                repository_handle.commit(message, name.zip(email), cx)
1758            })?
1759            .await??;
1760        Ok(proto::Ack {})
1761    }
1762
1763    async fn handle_get_remotes(
1764        this: Entity<Self>,
1765        envelope: TypedEnvelope<proto::GetRemotes>,
1766        mut cx: AsyncApp,
1767    ) -> Result<proto::GetRemotesResponse> {
1768        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1769        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1770
1771        let branch_name = envelope.payload.branch_name;
1772
1773        let remotes = repository_handle
1774            .update(&mut cx, |repository_handle, _| {
1775                repository_handle.get_remotes(branch_name)
1776            })?
1777            .await??;
1778
1779        Ok(proto::GetRemotesResponse {
1780            remotes: remotes
1781                .into_iter()
1782                .map(|remotes| proto::get_remotes_response::Remote {
1783                    name: remotes.name.to_string(),
1784                })
1785                .collect::<Vec<_>>(),
1786        })
1787    }
1788
1789    async fn handle_get_branches(
1790        this: Entity<Self>,
1791        envelope: TypedEnvelope<proto::GitGetBranches>,
1792        mut cx: AsyncApp,
1793    ) -> Result<proto::GitBranchesResponse> {
1794        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1795        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1796
1797        let branches = repository_handle
1798            .update(&mut cx, |repository_handle, _| repository_handle.branches())?
1799            .await??;
1800
1801        Ok(proto::GitBranchesResponse {
1802            branches: branches
1803                .into_iter()
1804                .map(|branch| branch_to_proto(&branch))
1805                .collect::<Vec<_>>(),
1806        })
1807    }
1808    async fn handle_create_branch(
1809        this: Entity<Self>,
1810        envelope: TypedEnvelope<proto::GitCreateBranch>,
1811        mut cx: AsyncApp,
1812    ) -> Result<proto::Ack> {
1813        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1814        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1815        let branch_name = envelope.payload.branch_name;
1816
1817        repository_handle
1818            .update(&mut cx, |repository_handle, _| {
1819                repository_handle.create_branch(branch_name)
1820            })?
1821            .await??;
1822
1823        Ok(proto::Ack {})
1824    }
1825
1826    async fn handle_change_branch(
1827        this: Entity<Self>,
1828        envelope: TypedEnvelope<proto::GitChangeBranch>,
1829        mut cx: AsyncApp,
1830    ) -> Result<proto::Ack> {
1831        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1832        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1833        let branch_name = envelope.payload.branch_name;
1834
1835        repository_handle
1836            .update(&mut cx, |repository_handle, _| {
1837                repository_handle.change_branch(branch_name)
1838            })?
1839            .await??;
1840
1841        Ok(proto::Ack {})
1842    }
1843
1844    async fn handle_show(
1845        this: Entity<Self>,
1846        envelope: TypedEnvelope<proto::GitShow>,
1847        mut cx: AsyncApp,
1848    ) -> Result<proto::GitCommitDetails> {
1849        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1850        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1851
1852        let commit = repository_handle
1853            .update(&mut cx, |repository_handle, _| {
1854                repository_handle.show(envelope.payload.commit)
1855            })?
1856            .await??;
1857        Ok(proto::GitCommitDetails {
1858            sha: commit.sha.into(),
1859            message: commit.message.into(),
1860            commit_timestamp: commit.commit_timestamp,
1861            author_email: commit.author_email.into(),
1862            author_name: commit.author_name.into(),
1863        })
1864    }
1865
1866    async fn handle_load_commit_diff(
1867        this: Entity<Self>,
1868        envelope: TypedEnvelope<proto::LoadCommitDiff>,
1869        mut cx: AsyncApp,
1870    ) -> Result<proto::LoadCommitDiffResponse> {
1871        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1872        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1873
1874        let commit_diff = repository_handle
1875            .update(&mut cx, |repository_handle, _| {
1876                repository_handle.load_commit_diff(envelope.payload.commit)
1877            })?
1878            .await??;
1879        Ok(proto::LoadCommitDiffResponse {
1880            files: commit_diff
1881                .files
1882                .into_iter()
1883                .map(|file| proto::CommitFile {
1884                    path: file.path.to_string(),
1885                    old_text: file.old_text,
1886                    new_text: file.new_text,
1887                })
1888                .collect(),
1889        })
1890    }
1891
1892    async fn handle_reset(
1893        this: Entity<Self>,
1894        envelope: TypedEnvelope<proto::GitReset>,
1895        mut cx: AsyncApp,
1896    ) -> Result<proto::Ack> {
1897        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1898        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1899
1900        let mode = match envelope.payload.mode() {
1901            git_reset::ResetMode::Soft => ResetMode::Soft,
1902            git_reset::ResetMode::Mixed => ResetMode::Mixed,
1903        };
1904
1905        repository_handle
1906            .update(&mut cx, |repository_handle, cx| {
1907                repository_handle.reset(envelope.payload.commit, mode, cx)
1908            })?
1909            .await??;
1910        Ok(proto::Ack {})
1911    }
1912
1913    async fn handle_checkout_files(
1914        this: Entity<Self>,
1915        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
1916        mut cx: AsyncApp,
1917    ) -> Result<proto::Ack> {
1918        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1919        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1920        let paths = envelope
1921            .payload
1922            .paths
1923            .iter()
1924            .map(|s| RepoPath::from_str(s))
1925            .collect();
1926
1927        repository_handle
1928            .update(&mut cx, |repository_handle, cx| {
1929                repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
1930            })?
1931            .await??;
1932        Ok(proto::Ack {})
1933    }
1934
1935    async fn handle_open_commit_message_buffer(
1936        this: Entity<Self>,
1937        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
1938        mut cx: AsyncApp,
1939    ) -> Result<proto::OpenBufferResponse> {
1940        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1941        let repository = Self::repository_for_request(&this, repository_id, &mut cx)?;
1942        let buffer = repository
1943            .update(&mut cx, |repository, cx| {
1944                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
1945            })?
1946            .await?;
1947
1948        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
1949        this.update(&mut cx, |this, cx| {
1950            this.buffer_store.update(cx, |buffer_store, cx| {
1951                buffer_store
1952                    .create_buffer_for_peer(
1953                        &buffer,
1954                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
1955                        cx,
1956                    )
1957                    .detach_and_log_err(cx);
1958            })
1959        })?;
1960
1961        Ok(proto::OpenBufferResponse {
1962            buffer_id: buffer_id.to_proto(),
1963        })
1964    }
1965
1966    async fn handle_askpass(
1967        this: Entity<Self>,
1968        envelope: TypedEnvelope<proto::AskPassRequest>,
1969        mut cx: AsyncApp,
1970    ) -> Result<proto::AskPassResponse> {
1971        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1972        let repository = Self::repository_for_request(&this, repository_id, &mut cx)?;
1973
1974        let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone())?;
1975        let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
1976            debug_panic!("no askpass found");
1977            return Err(anyhow::anyhow!("no askpass found"));
1978        };
1979
1980        let response = askpass.ask_password(envelope.payload.prompt).await?;
1981
1982        delegates
1983            .lock()
1984            .insert(envelope.payload.askpass_id, askpass);
1985
1986        Ok(proto::AskPassResponse { response })
1987    }
1988
1989    async fn handle_check_for_pushed_commits(
1990        this: Entity<Self>,
1991        envelope: TypedEnvelope<proto::CheckForPushedCommits>,
1992        mut cx: AsyncApp,
1993    ) -> Result<proto::CheckForPushedCommitsResponse> {
1994        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
1995        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
1996
1997        let branches = repository_handle
1998            .update(&mut cx, |repository_handle, _| {
1999                repository_handle.check_for_pushed_commits()
2000            })?
2001            .await??;
2002        Ok(proto::CheckForPushedCommitsResponse {
2003            pushed_to: branches
2004                .into_iter()
2005                .map(|commit| commit.to_string())
2006                .collect(),
2007        })
2008    }
2009
2010    async fn handle_git_diff(
2011        this: Entity<Self>,
2012        envelope: TypedEnvelope<proto::GitDiff>,
2013        mut cx: AsyncApp,
2014    ) -> Result<proto::GitDiffResponse> {
2015        let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
2016        let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
2017        let diff_type = match envelope.payload.diff_type() {
2018            proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
2019            proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
2020        };
2021
2022        let mut diff = repository_handle
2023            .update(&mut cx, |repository_handle, cx| {
2024                repository_handle.diff(diff_type, cx)
2025            })?
2026            .await??;
2027        const ONE_MB: usize = 1_000_000;
2028        if diff.len() > ONE_MB {
2029            diff = diff.chars().take(ONE_MB).collect()
2030        }
2031
2032        Ok(proto::GitDiffResponse { diff })
2033    }
2034
2035    async fn handle_open_unstaged_diff(
2036        this: Entity<Self>,
2037        request: TypedEnvelope<proto::OpenUnstagedDiff>,
2038        mut cx: AsyncApp,
2039    ) -> Result<proto::OpenUnstagedDiffResponse> {
2040        let buffer_id = BufferId::new(request.payload.buffer_id)?;
2041        let diff = this
2042            .update(&mut cx, |this, cx| {
2043                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
2044                Some(this.open_unstaged_diff(buffer, cx))
2045            })?
2046            .ok_or_else(|| anyhow!("no such buffer"))?
2047            .await?;
2048        this.update(&mut cx, |this, _| {
2049            let shared_diffs = this
2050                .shared_diffs
2051                .entry(request.original_sender_id.unwrap_or(request.sender_id))
2052                .or_default();
2053            shared_diffs.entry(buffer_id).or_default().unstaged = Some(diff.clone());
2054        })?;
2055        let staged_text = diff.read_with(&cx, |diff, _| diff.base_text_string())?;
2056        Ok(proto::OpenUnstagedDiffResponse { staged_text })
2057    }
2058
2059    async fn handle_open_uncommitted_diff(
2060        this: Entity<Self>,
2061        request: TypedEnvelope<proto::OpenUncommittedDiff>,
2062        mut cx: AsyncApp,
2063    ) -> Result<proto::OpenUncommittedDiffResponse> {
2064        let buffer_id = BufferId::new(request.payload.buffer_id)?;
2065        let diff = this
2066            .update(&mut cx, |this, cx| {
2067                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
2068                Some(this.open_uncommitted_diff(buffer, cx))
2069            })?
2070            .ok_or_else(|| anyhow!("no such buffer"))?
2071            .await?;
2072        this.update(&mut cx, |this, _| {
2073            let shared_diffs = this
2074                .shared_diffs
2075                .entry(request.original_sender_id.unwrap_or(request.sender_id))
2076                .or_default();
2077            shared_diffs.entry(buffer_id).or_default().uncommitted = Some(diff.clone());
2078        })?;
2079        diff.read_with(&cx, |diff, cx| {
2080            use proto::open_uncommitted_diff_response::Mode;
2081
2082            let unstaged_diff = diff.secondary_diff();
2083            let index_snapshot = unstaged_diff.and_then(|diff| {
2084                let diff = diff.read(cx);
2085                diff.base_text_exists().then(|| diff.base_text())
2086            });
2087
2088            let mode;
2089            let staged_text;
2090            let committed_text;
2091            if diff.base_text_exists() {
2092                let committed_snapshot = diff.base_text();
2093                committed_text = Some(committed_snapshot.text());
2094                if let Some(index_text) = index_snapshot {
2095                    if index_text.remote_id() == committed_snapshot.remote_id() {
2096                        mode = Mode::IndexMatchesHead;
2097                        staged_text = None;
2098                    } else {
2099                        mode = Mode::IndexAndHead;
2100                        staged_text = Some(index_text.text());
2101                    }
2102                } else {
2103                    mode = Mode::IndexAndHead;
2104                    staged_text = None;
2105                }
2106            } else {
2107                mode = Mode::IndexAndHead;
2108                committed_text = None;
2109                staged_text = index_snapshot.as_ref().map(|buffer| buffer.text());
2110            }
2111
2112            proto::OpenUncommittedDiffResponse {
2113                committed_text,
2114                staged_text,
2115                mode: mode.into(),
2116            }
2117        })
2118    }
2119
2120    async fn handle_update_diff_bases(
2121        this: Entity<Self>,
2122        request: TypedEnvelope<proto::UpdateDiffBases>,
2123        mut cx: AsyncApp,
2124    ) -> Result<()> {
2125        let buffer_id = BufferId::new(request.payload.buffer_id)?;
2126        this.update(&mut cx, |this, cx| {
2127            if let Some(diff_state) = this.diffs.get_mut(&buffer_id) {
2128                if let Some(buffer) = this.buffer_store.read(cx).get(buffer_id) {
2129                    let buffer = buffer.read(cx).text_snapshot();
2130                    diff_state.update(cx, |diff_state, cx| {
2131                        diff_state.handle_base_texts_updated(buffer, request.payload, cx);
2132                    })
2133                }
2134            }
2135        })
2136    }
2137
2138    async fn handle_blame_buffer(
2139        this: Entity<Self>,
2140        envelope: TypedEnvelope<proto::BlameBuffer>,
2141        mut cx: AsyncApp,
2142    ) -> Result<proto::BlameBufferResponse> {
2143        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
2144        let version = deserialize_version(&envelope.payload.version);
2145        let buffer = this.read_with(&cx, |this, cx| {
2146            this.buffer_store.read(cx).get_existing(buffer_id)
2147        })??;
2148        buffer
2149            .update(&mut cx, |buffer, _| {
2150                buffer.wait_for_version(version.clone())
2151            })?
2152            .await?;
2153        let blame = this
2154            .update(&mut cx, |this, cx| {
2155                this.blame_buffer(&buffer, Some(version), cx)
2156            })?
2157            .await?;
2158        Ok(serialize_blame_buffer_response(blame))
2159    }
2160
2161    async fn handle_get_permalink_to_line(
2162        this: Entity<Self>,
2163        envelope: TypedEnvelope<proto::GetPermalinkToLine>,
2164        mut cx: AsyncApp,
2165    ) -> Result<proto::GetPermalinkToLineResponse> {
2166        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
2167        // let version = deserialize_version(&envelope.payload.version);
2168        let selection = {
2169            let proto_selection = envelope
2170                .payload
2171                .selection
2172                .context("no selection to get permalink for defined")?;
2173            proto_selection.start as u32..proto_selection.end as u32
2174        };
2175        let buffer = this.read_with(&cx, |this, cx| {
2176            this.buffer_store.read(cx).get_existing(buffer_id)
2177        })??;
2178        let permalink = this
2179            .update(&mut cx, |this, cx| {
2180                this.get_permalink_to_line(&buffer, selection, cx)
2181            })?
2182            .await?;
2183        Ok(proto::GetPermalinkToLineResponse {
2184            permalink: permalink.to_string(),
2185        })
2186    }
2187
2188    fn repository_for_request(
2189        this: &Entity<Self>,
2190        id: RepositoryId,
2191        cx: &mut AsyncApp,
2192    ) -> Result<Entity<Repository>> {
2193        this.update(cx, |this, _| {
2194            this.repositories
2195                .get(&id)
2196                .context("missing repository handle")
2197                .cloned()
2198        })?
2199    }
2200
2201    pub fn repo_snapshots(&self, cx: &App) -> HashMap<RepositoryId, RepositorySnapshot> {
2202        self.repositories
2203            .iter()
2204            .map(|(id, repo)| (*id, repo.read(cx).snapshot.clone()))
2205            .collect()
2206    }
2207}
2208
2209impl BufferDiffState {
2210    fn buffer_language_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
2211        self.language = buffer.read(cx).language().cloned();
2212        self.language_changed = true;
2213        let _ = self.recalculate_diffs(
2214            buffer.read(cx).text_snapshot(),
2215            self.hunk_staging_operation_count,
2216            cx,
2217        );
2218    }
2219
2220    fn unstaged_diff(&self) -> Option<Entity<BufferDiff>> {
2221        self.unstaged_diff.as_ref().and_then(|set| set.upgrade())
2222    }
2223
2224    fn uncommitted_diff(&self) -> Option<Entity<BufferDiff>> {
2225        self.uncommitted_diff.as_ref().and_then(|set| set.upgrade())
2226    }
2227
2228    fn handle_base_texts_updated(
2229        &mut self,
2230        buffer: text::BufferSnapshot,
2231        message: proto::UpdateDiffBases,
2232        cx: &mut Context<Self>,
2233    ) {
2234        use proto::update_diff_bases::Mode;
2235
2236        let Some(mode) = Mode::from_i32(message.mode) else {
2237            return;
2238        };
2239
2240        let diff_bases_change = match mode {
2241            Mode::HeadOnly => DiffBasesChange::SetHead(message.committed_text),
2242            Mode::IndexOnly => DiffBasesChange::SetIndex(message.staged_text),
2243            Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.committed_text),
2244            Mode::IndexAndHead => DiffBasesChange::SetEach {
2245                index: message.staged_text,
2246                head: message.committed_text,
2247            },
2248        };
2249
2250        let _ = self.diff_bases_changed(
2251            buffer,
2252            diff_bases_change,
2253            self.hunk_staging_operation_count,
2254            cx,
2255        );
2256    }
2257
2258    pub fn wait_for_recalculation(&mut self) -> Option<oneshot::Receiver<()>> {
2259        if self.diff_updated_futures.is_empty() {
2260            return None;
2261        }
2262        let (tx, rx) = oneshot::channel();
2263        self.diff_updated_futures.push(tx);
2264        Some(rx)
2265    }
2266
2267    fn diff_bases_changed(
2268        &mut self,
2269        buffer: text::BufferSnapshot,
2270        diff_bases_change: DiffBasesChange,
2271        prev_hunk_staging_operation_count: usize,
2272        cx: &mut Context<Self>,
2273    ) -> oneshot::Receiver<()> {
2274        match diff_bases_change {
2275            DiffBasesChange::SetIndex(index) => {
2276                self.index_text = index.map(|mut index| {
2277                    text::LineEnding::normalize(&mut index);
2278                    Arc::new(index)
2279                });
2280                self.index_changed = true;
2281            }
2282            DiffBasesChange::SetHead(head) => {
2283                self.head_text = head.map(|mut head| {
2284                    text::LineEnding::normalize(&mut head);
2285                    Arc::new(head)
2286                });
2287                self.head_changed = true;
2288            }
2289            DiffBasesChange::SetBoth(text) => {
2290                let text = text.map(|mut text| {
2291                    text::LineEnding::normalize(&mut text);
2292                    Arc::new(text)
2293                });
2294                self.head_text = text.clone();
2295                self.index_text = text;
2296                self.head_changed = true;
2297                self.index_changed = true;
2298            }
2299            DiffBasesChange::SetEach { index, head } => {
2300                self.index_text = index.map(|mut index| {
2301                    text::LineEnding::normalize(&mut index);
2302                    Arc::new(index)
2303                });
2304                self.index_changed = true;
2305                self.head_text = head.map(|mut head| {
2306                    text::LineEnding::normalize(&mut head);
2307                    Arc::new(head)
2308                });
2309                self.head_changed = true;
2310            }
2311        }
2312
2313        self.recalculate_diffs(buffer, prev_hunk_staging_operation_count, cx)
2314    }
2315
2316    fn recalculate_diffs(
2317        &mut self,
2318        buffer: text::BufferSnapshot,
2319        prev_hunk_staging_operation_count: usize,
2320        cx: &mut Context<Self>,
2321    ) -> oneshot::Receiver<()> {
2322        log::debug!("recalculate diffs");
2323        let (tx, rx) = oneshot::channel();
2324        self.diff_updated_futures.push(tx);
2325
2326        let language = self.language.clone();
2327        let language_registry = self.language_registry.clone();
2328        let unstaged_diff = self.unstaged_diff();
2329        let uncommitted_diff = self.uncommitted_diff();
2330        let head = self.head_text.clone();
2331        let index = self.index_text.clone();
2332        let index_changed = self.index_changed;
2333        let head_changed = self.head_changed;
2334        let language_changed = self.language_changed;
2335        let index_matches_head = match (self.index_text.as_ref(), self.head_text.as_ref()) {
2336            (Some(index), Some(head)) => Arc::ptr_eq(index, head),
2337            (None, None) => true,
2338            _ => false,
2339        };
2340        self.recalculate_diff_task = Some(cx.spawn(async move |this, cx| {
2341            let mut new_unstaged_diff = None;
2342            if let Some(unstaged_diff) = &unstaged_diff {
2343                new_unstaged_diff = Some(
2344                    BufferDiff::update_diff(
2345                        unstaged_diff.clone(),
2346                        buffer.clone(),
2347                        index,
2348                        index_changed,
2349                        language_changed,
2350                        language.clone(),
2351                        language_registry.clone(),
2352                        cx,
2353                    )
2354                    .await?,
2355                );
2356            }
2357
2358            let mut new_uncommitted_diff = None;
2359            if let Some(uncommitted_diff) = &uncommitted_diff {
2360                new_uncommitted_diff = if index_matches_head {
2361                    new_unstaged_diff.clone()
2362                } else {
2363                    Some(
2364                        BufferDiff::update_diff(
2365                            uncommitted_diff.clone(),
2366                            buffer.clone(),
2367                            head,
2368                            head_changed,
2369                            language_changed,
2370                            language.clone(),
2371                            language_registry.clone(),
2372                            cx,
2373                        )
2374                        .await?,
2375                    )
2376                }
2377            }
2378
2379            if this.update(cx, |this, _| {
2380                this.hunk_staging_operation_count > prev_hunk_staging_operation_count
2381            })? {
2382                eprintln!("early return");
2383                return Ok(());
2384            }
2385
2386            let unstaged_changed_range = if let Some((unstaged_diff, new_unstaged_diff)) =
2387                unstaged_diff.as_ref().zip(new_unstaged_diff.clone())
2388            {
2389                unstaged_diff.update(cx, |diff, cx| {
2390                    if language_changed {
2391                        diff.language_changed(cx);
2392                    }
2393                    diff.set_snapshot(new_unstaged_diff, &buffer, None, cx)
2394                })?
2395            } else {
2396                None
2397            };
2398
2399            if let Some((uncommitted_diff, new_uncommitted_diff)) =
2400                uncommitted_diff.as_ref().zip(new_uncommitted_diff.clone())
2401            {
2402                uncommitted_diff.update(cx, |diff, cx| {
2403                    if language_changed {
2404                        diff.language_changed(cx);
2405                    }
2406                    diff.set_snapshot(new_uncommitted_diff, &buffer, unstaged_changed_range, cx);
2407                })?;
2408            }
2409
2410            if let Some(this) = this.upgrade() {
2411                this.update(cx, |this, _| {
2412                    this.index_changed = false;
2413                    this.head_changed = false;
2414                    this.language_changed = false;
2415                    for tx in this.diff_updated_futures.drain(..) {
2416                        tx.send(()).ok();
2417                    }
2418                })?;
2419            }
2420
2421            Ok(())
2422        }));
2423
2424        rx
2425    }
2426}
2427
2428fn make_remote_delegate(
2429    this: Entity<GitStore>,
2430    project_id: u64,
2431    repository_id: RepositoryId,
2432    askpass_id: u64,
2433    cx: &mut AsyncApp,
2434) -> AskPassDelegate {
2435    AskPassDelegate::new(cx, move |prompt, tx, cx| {
2436        this.update(cx, |this, cx| {
2437            let Some((client, _)) = this.downstream_client() else {
2438                return;
2439            };
2440            let response = client.request(proto::AskPassRequest {
2441                project_id,
2442                repository_id: repository_id.to_proto(),
2443                askpass_id,
2444                prompt,
2445            });
2446            cx.spawn(async move |_, _| {
2447                tx.send(response.await?.response).ok();
2448                anyhow::Ok(())
2449            })
2450            .detach_and_log_err(cx);
2451        })
2452        .log_err();
2453    })
2454}
2455
2456impl RepositoryId {
2457    pub fn to_proto(self) -> u64 {
2458        self.0
2459    }
2460
2461    pub fn from_proto(id: u64) -> Self {
2462        RepositoryId(id)
2463    }
2464}
2465
2466impl RepositorySnapshot {
2467    fn empty(id: RepositoryId, work_directory_abs_path: Arc<Path>) -> Self {
2468        Self {
2469            id,
2470            merge_message: None,
2471            statuses_by_path: Default::default(),
2472            work_directory_abs_path,
2473            branch: None,
2474            merge_conflicts: Default::default(),
2475            merge_head_shas: Default::default(),
2476            scan_id: 0,
2477        }
2478    }
2479
2480    fn initial_update(&self, project_id: u64) -> proto::UpdateRepository {
2481        proto::UpdateRepository {
2482            branch_summary: self.branch.as_ref().map(branch_to_proto),
2483            updated_statuses: self
2484                .statuses_by_path
2485                .iter()
2486                .map(|entry| entry.to_proto())
2487                .collect(),
2488            removed_statuses: Default::default(),
2489            current_merge_conflicts: self
2490                .merge_conflicts
2491                .iter()
2492                .map(|repo_path| repo_path.to_proto())
2493                .collect(),
2494            project_id,
2495            id: self.id.to_proto(),
2496            abs_path: self.work_directory_abs_path.to_proto(),
2497            entry_ids: vec![self.id.to_proto()],
2498            scan_id: self.scan_id,
2499            is_last_update: true,
2500        }
2501    }
2502
2503    fn build_update(&self, old: &Self, project_id: u64) -> proto::UpdateRepository {
2504        let mut updated_statuses: Vec<proto::StatusEntry> = Vec::new();
2505        let mut removed_statuses: Vec<String> = Vec::new();
2506
2507        let mut new_statuses = self.statuses_by_path.iter().peekable();
2508        let mut old_statuses = old.statuses_by_path.iter().peekable();
2509
2510        let mut current_new_entry = new_statuses.next();
2511        let mut current_old_entry = old_statuses.next();
2512        loop {
2513            match (current_new_entry, current_old_entry) {
2514                (Some(new_entry), Some(old_entry)) => {
2515                    match new_entry.repo_path.cmp(&old_entry.repo_path) {
2516                        Ordering::Less => {
2517                            updated_statuses.push(new_entry.to_proto());
2518                            current_new_entry = new_statuses.next();
2519                        }
2520                        Ordering::Equal => {
2521                            if new_entry.status != old_entry.status {
2522                                updated_statuses.push(new_entry.to_proto());
2523                            }
2524                            current_old_entry = old_statuses.next();
2525                            current_new_entry = new_statuses.next();
2526                        }
2527                        Ordering::Greater => {
2528                            removed_statuses.push(old_entry.repo_path.as_ref().to_proto());
2529                            current_old_entry = old_statuses.next();
2530                        }
2531                    }
2532                }
2533                (None, Some(old_entry)) => {
2534                    removed_statuses.push(old_entry.repo_path.as_ref().to_proto());
2535                    current_old_entry = old_statuses.next();
2536                }
2537                (Some(new_entry), None) => {
2538                    updated_statuses.push(new_entry.to_proto());
2539                    current_new_entry = new_statuses.next();
2540                }
2541                (None, None) => break,
2542            }
2543        }
2544
2545        proto::UpdateRepository {
2546            branch_summary: self.branch.as_ref().map(branch_to_proto),
2547            updated_statuses,
2548            removed_statuses,
2549            current_merge_conflicts: self
2550                .merge_conflicts
2551                .iter()
2552                .map(|path| path.as_ref().to_proto())
2553                .collect(),
2554            project_id,
2555            id: self.id.to_proto(),
2556            abs_path: self.work_directory_abs_path.to_proto(),
2557            entry_ids: vec![],
2558            scan_id: self.scan_id,
2559            is_last_update: true,
2560        }
2561    }
2562
2563    pub fn status(&self) -> impl Iterator<Item = StatusEntry> + '_ {
2564        self.statuses_by_path.iter().cloned()
2565    }
2566
2567    pub fn status_summary(&self) -> GitSummary {
2568        self.statuses_by_path.summary().item_summary
2569    }
2570
2571    pub fn status_for_path(&self, path: &RepoPath) -> Option<StatusEntry> {
2572        self.statuses_by_path
2573            .get(&PathKey(path.0.clone()), &())
2574            .cloned()
2575    }
2576
2577    pub fn abs_path_to_repo_path(&self, abs_path: &Path) -> Option<RepoPath> {
2578        abs_path
2579            .strip_prefix(&self.work_directory_abs_path)
2580            .map(RepoPath::from)
2581            .ok()
2582    }
2583
2584    pub fn has_conflict(&self, repo_path: &RepoPath) -> bool {
2585        self.merge_conflicts.contains(repo_path)
2586    }
2587
2588    /// This is the name that will be displayed in the repository selector for this repository.
2589    pub fn display_name(&self) -> SharedString {
2590        self.work_directory_abs_path
2591            .file_name()
2592            .unwrap_or_default()
2593            .to_string_lossy()
2594            .to_string()
2595            .into()
2596    }
2597}
2598
2599impl Repository {
2600    fn local(
2601        id: RepositoryId,
2602        work_directory_abs_path: Arc<Path>,
2603        dot_git_abs_path: Arc<Path>,
2604        project_environment: WeakEntity<ProjectEnvironment>,
2605        fs: Arc<dyn Fs>,
2606        git_store: WeakEntity<GitStore>,
2607        cx: &mut Context<Self>,
2608    ) -> Self {
2609        let snapshot = RepositorySnapshot::empty(id, work_directory_abs_path.clone());
2610        Repository {
2611            git_store,
2612            snapshot,
2613            commit_message_buffer: None,
2614            askpass_delegates: Default::default(),
2615            paths_needing_status_update: Default::default(),
2616            latest_askpass_id: 0,
2617            job_sender: Repository::spawn_local_git_worker(
2618                work_directory_abs_path,
2619                dot_git_abs_path,
2620                project_environment,
2621                fs,
2622                cx,
2623            ),
2624        }
2625    }
2626
2627    fn remote(
2628        id: RepositoryId,
2629        work_directory_abs_path: Arc<Path>,
2630        project_id: ProjectId,
2631        client: AnyProtoClient,
2632        git_store: WeakEntity<GitStore>,
2633        cx: &mut Context<Self>,
2634    ) -> Self {
2635        let snapshot = RepositorySnapshot::empty(id, work_directory_abs_path);
2636        Self {
2637            snapshot,
2638            commit_message_buffer: None,
2639            git_store,
2640            paths_needing_status_update: Default::default(),
2641            job_sender: Self::spawn_remote_git_worker(project_id, client, cx),
2642            askpass_delegates: Default::default(),
2643            latest_askpass_id: 0,
2644        }
2645    }
2646
2647    pub fn git_store(&self) -> Option<Entity<GitStore>> {
2648        self.git_store.upgrade()
2649    }
2650
2651    pub fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
2652    where
2653        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
2654        Fut: Future<Output = R> + 'static,
2655        R: Send + 'static,
2656    {
2657        self.send_keyed_job(None, job)
2658    }
2659
2660    fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
2661    where
2662        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
2663        Fut: Future<Output = R> + 'static,
2664        R: Send + 'static,
2665    {
2666        let (result_tx, result_rx) = futures::channel::oneshot::channel();
2667        self.job_sender
2668            .unbounded_send(GitJob {
2669                key,
2670                job: Box::new(|state, cx: &mut AsyncApp| {
2671                    let job = job(state, cx.clone());
2672                    cx.spawn(async move |_| {
2673                        let result = job.await;
2674                        result_tx.send(result).ok();
2675                    })
2676                }),
2677            })
2678            .ok();
2679        result_rx
2680    }
2681
2682    pub fn set_as_active_repository(&self, cx: &mut Context<Self>) {
2683        let Some(git_store) = self.git_store.upgrade() else {
2684            return;
2685        };
2686        let entity = cx.entity();
2687        git_store.update(cx, |git_store, cx| {
2688            let Some((&id, _)) = git_store
2689                .repositories
2690                .iter()
2691                .find(|(_, handle)| *handle == &entity)
2692            else {
2693                return;
2694            };
2695            git_store.active_repo_id = Some(id);
2696            cx.emit(GitStoreEvent::ActiveRepositoryChanged(Some(id)));
2697        });
2698    }
2699
2700    pub fn cached_status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
2701        self.snapshot.status()
2702    }
2703
2704    pub fn repo_path_to_project_path(&self, path: &RepoPath, cx: &App) -> Option<ProjectPath> {
2705        let git_store = self.git_store.upgrade()?;
2706        let worktree_store = git_store.read(cx).worktree_store.read(cx);
2707        let abs_path = self.snapshot.work_directory_abs_path.join(&path.0);
2708        let (worktree, relative_path) = worktree_store.find_worktree(abs_path, cx)?;
2709        Some(ProjectPath {
2710            worktree_id: worktree.read(cx).id(),
2711            path: relative_path.into(),
2712        })
2713    }
2714
2715    pub fn project_path_to_repo_path(&self, path: &ProjectPath, cx: &App) -> Option<RepoPath> {
2716        let git_store = self.git_store.upgrade()?;
2717        let worktree_store = git_store.read(cx).worktree_store.read(cx);
2718        let abs_path = worktree_store.absolutize(path, cx)?;
2719        self.snapshot.abs_path_to_repo_path(&abs_path)
2720    }
2721
2722    pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
2723        other
2724            .read(cx)
2725            .snapshot
2726            .work_directory_abs_path
2727            .starts_with(&self.snapshot.work_directory_abs_path)
2728    }
2729
2730    pub fn open_commit_buffer(
2731        &mut self,
2732        languages: Option<Arc<LanguageRegistry>>,
2733        buffer_store: Entity<BufferStore>,
2734        cx: &mut Context<Self>,
2735    ) -> Task<Result<Entity<Buffer>>> {
2736        let id = self.id;
2737        if let Some(buffer) = self.commit_message_buffer.clone() {
2738            return Task::ready(Ok(buffer));
2739        }
2740        let this = cx.weak_entity();
2741
2742        let rx = self.send_job(move |state, mut cx| async move {
2743            let Some(this) = this.upgrade() else {
2744                bail!("git store was dropped");
2745            };
2746            match state {
2747                RepositoryState::Local { .. } => {
2748                    this.update(&mut cx, |_, cx| {
2749                        Self::open_local_commit_buffer(languages, buffer_store, cx)
2750                    })?
2751                    .await
2752                }
2753                RepositoryState::Remote { project_id, client } => {
2754                    let request = client.request(proto::OpenCommitMessageBuffer {
2755                        project_id: project_id.0,
2756                        repository_id: id.to_proto(),
2757                    });
2758                    let response = request.await.context("requesting to open commit buffer")?;
2759                    let buffer_id = BufferId::new(response.buffer_id)?;
2760                    let buffer = buffer_store
2761                        .update(&mut cx, |buffer_store, cx| {
2762                            buffer_store.wait_for_remote_buffer(buffer_id, cx)
2763                        })?
2764                        .await?;
2765                    if let Some(language_registry) = languages {
2766                        let git_commit_language =
2767                            language_registry.language_for_name("Git Commit").await?;
2768                        buffer.update(&mut cx, |buffer, cx| {
2769                            buffer.set_language(Some(git_commit_language), cx);
2770                        })?;
2771                    }
2772                    this.update(&mut cx, |this, _| {
2773                        this.commit_message_buffer = Some(buffer.clone());
2774                    })?;
2775                    Ok(buffer)
2776                }
2777            }
2778        });
2779
2780        cx.spawn(|_, _: &mut AsyncApp| async move { rx.await? })
2781    }
2782
2783    fn open_local_commit_buffer(
2784        language_registry: Option<Arc<LanguageRegistry>>,
2785        buffer_store: Entity<BufferStore>,
2786        cx: &mut Context<Self>,
2787    ) -> Task<Result<Entity<Buffer>>> {
2788        cx.spawn(async move |repository, cx| {
2789            let buffer = buffer_store
2790                .update(cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
2791                .await?;
2792
2793            if let Some(language_registry) = language_registry {
2794                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
2795                buffer.update(cx, |buffer, cx| {
2796                    buffer.set_language(Some(git_commit_language), cx);
2797                })?;
2798            }
2799
2800            repository.update(cx, |repository, _| {
2801                repository.commit_message_buffer = Some(buffer.clone());
2802            })?;
2803            Ok(buffer)
2804        })
2805    }
2806
2807    pub fn checkout_files(
2808        &self,
2809        commit: &str,
2810        paths: Vec<RepoPath>,
2811        _cx: &mut App,
2812    ) -> oneshot::Receiver<Result<()>> {
2813        let commit = commit.to_string();
2814        let id = self.id;
2815
2816        self.send_job(move |git_repo, _| async move {
2817            match git_repo {
2818                RepositoryState::Local {
2819                    backend,
2820                    environment,
2821                    ..
2822                } => {
2823                    backend
2824                        .checkout_files(commit, paths, environment.clone())
2825                        .await
2826                }
2827                RepositoryState::Remote { project_id, client } => {
2828                    client
2829                        .request(proto::GitCheckoutFiles {
2830                            project_id: project_id.0,
2831                            repository_id: id.to_proto(),
2832                            commit,
2833                            paths: paths
2834                                .into_iter()
2835                                .map(|p| p.to_string_lossy().to_string())
2836                                .collect(),
2837                        })
2838                        .await?;
2839
2840                    Ok(())
2841                }
2842            }
2843        })
2844    }
2845
2846    pub fn reset(
2847        &self,
2848        commit: String,
2849        reset_mode: ResetMode,
2850        _cx: &mut App,
2851    ) -> oneshot::Receiver<Result<()>> {
2852        let commit = commit.to_string();
2853        let id = self.id;
2854
2855        self.send_job(move |git_repo, _| async move {
2856            match git_repo {
2857                RepositoryState::Local {
2858                    backend,
2859                    environment,
2860                    ..
2861                } => backend.reset(commit, reset_mode, environment).await,
2862                RepositoryState::Remote { project_id, client } => {
2863                    client
2864                        .request(proto::GitReset {
2865                            project_id: project_id.0,
2866                            repository_id: id.to_proto(),
2867                            commit,
2868                            mode: match reset_mode {
2869                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
2870                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
2871                            },
2872                        })
2873                        .await?;
2874
2875                    Ok(())
2876                }
2877            }
2878        })
2879    }
2880
2881    pub fn show(&self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
2882        let id = self.id;
2883        self.send_job(move |git_repo, _cx| async move {
2884            match git_repo {
2885                RepositoryState::Local { backend, .. } => backend.show(commit).await,
2886                RepositoryState::Remote { project_id, client } => {
2887                    let resp = client
2888                        .request(proto::GitShow {
2889                            project_id: project_id.0,
2890                            repository_id: id.to_proto(),
2891                            commit,
2892                        })
2893                        .await?;
2894
2895                    Ok(CommitDetails {
2896                        sha: resp.sha.into(),
2897                        message: resp.message.into(),
2898                        commit_timestamp: resp.commit_timestamp,
2899                        author_email: resp.author_email.into(),
2900                        author_name: resp.author_name.into(),
2901                    })
2902                }
2903            }
2904        })
2905    }
2906
2907    pub fn load_commit_diff(&self, commit: String) -> oneshot::Receiver<Result<CommitDiff>> {
2908        let id = self.id;
2909        self.send_job(move |git_repo, cx| async move {
2910            match git_repo {
2911                RepositoryState::Local { backend, .. } => backend.load_commit(commit, cx).await,
2912                RepositoryState::Remote {
2913                    client, project_id, ..
2914                } => {
2915                    let response = client
2916                        .request(proto::LoadCommitDiff {
2917                            project_id: project_id.0,
2918                            repository_id: id.to_proto(),
2919                            commit,
2920                        })
2921                        .await?;
2922                    Ok(CommitDiff {
2923                        files: response
2924                            .files
2925                            .into_iter()
2926                            .map(|file| CommitFile {
2927                                path: Path::new(&file.path).into(),
2928                                old_text: file.old_text,
2929                                new_text: file.new_text,
2930                            })
2931                            .collect(),
2932                    })
2933                }
2934            }
2935        })
2936    }
2937
2938    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
2939        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
2940    }
2941
2942    pub fn stage_entries(
2943        &self,
2944        entries: Vec<RepoPath>,
2945        cx: &mut Context<Self>,
2946    ) -> Task<anyhow::Result<()>> {
2947        if entries.is_empty() {
2948            return Task::ready(Ok(()));
2949        }
2950        let id = self.id;
2951
2952        let mut save_futures = Vec::new();
2953        if let Some(buffer_store) = self.buffer_store(cx) {
2954            buffer_store.update(cx, |buffer_store, cx| {
2955                for path in &entries {
2956                    let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
2957                        continue;
2958                    };
2959                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
2960                        if buffer
2961                            .read(cx)
2962                            .file()
2963                            .map_or(false, |file| file.disk_state().exists())
2964                        {
2965                            save_futures.push(buffer_store.save_buffer(buffer, cx));
2966                        }
2967                    }
2968                }
2969            })
2970        }
2971
2972        cx.spawn(async move |this, cx| {
2973            for save_future in save_futures {
2974                save_future.await?;
2975            }
2976
2977            this.update(cx, |this, _| {
2978                this.send_job(move |git_repo, _cx| async move {
2979                    match git_repo {
2980                        RepositoryState::Local {
2981                            backend,
2982                            environment,
2983                            ..
2984                        } => backend.stage_paths(entries, environment.clone()).await,
2985                        RepositoryState::Remote { project_id, client } => {
2986                            client
2987                                .request(proto::Stage {
2988                                    project_id: project_id.0,
2989                                    repository_id: id.to_proto(),
2990                                    paths: entries
2991                                        .into_iter()
2992                                        .map(|repo_path| repo_path.as_ref().to_proto())
2993                                        .collect(),
2994                                })
2995                                .await
2996                                .context("sending stage request")?;
2997
2998                            Ok(())
2999                        }
3000                    }
3001                })
3002            })?
3003            .await??;
3004
3005            Ok(())
3006        })
3007    }
3008
3009    pub fn unstage_entries(
3010        &self,
3011        entries: Vec<RepoPath>,
3012        cx: &mut Context<Self>,
3013    ) -> Task<anyhow::Result<()>> {
3014        if entries.is_empty() {
3015            return Task::ready(Ok(()));
3016        }
3017        let id = self.id;
3018
3019        let mut save_futures = Vec::new();
3020        if let Some(buffer_store) = self.buffer_store(cx) {
3021            buffer_store.update(cx, |buffer_store, cx| {
3022                for path in &entries {
3023                    let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
3024                        continue;
3025                    };
3026                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
3027                        if buffer
3028                            .read(cx)
3029                            .file()
3030                            .map_or(false, |file| file.disk_state().exists())
3031                        {
3032                            save_futures.push(buffer_store.save_buffer(buffer, cx));
3033                        }
3034                    }
3035                }
3036            })
3037        }
3038
3039        cx.spawn(async move |this, cx| {
3040            for save_future in save_futures {
3041                save_future.await?;
3042            }
3043
3044            this.update(cx, |this, _| {
3045                this.send_job(move |git_repo, _cx| async move {
3046                    match git_repo {
3047                        RepositoryState::Local {
3048                            backend,
3049                            environment,
3050                            ..
3051                        } => backend.unstage_paths(entries, environment).await,
3052                        RepositoryState::Remote { project_id, client } => {
3053                            client
3054                                .request(proto::Unstage {
3055                                    project_id: project_id.0,
3056                                    repository_id: id.to_proto(),
3057                                    paths: entries
3058                                        .into_iter()
3059                                        .map(|repo_path| repo_path.as_ref().to_proto())
3060                                        .collect(),
3061                                })
3062                                .await
3063                                .context("sending unstage request")?;
3064
3065                            Ok(())
3066                        }
3067                    }
3068                })
3069            })?
3070            .await??;
3071
3072            Ok(())
3073        })
3074    }
3075
3076    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
3077        let to_stage = self
3078            .cached_status()
3079            .filter(|entry| !entry.status.staging().is_fully_staged())
3080            .map(|entry| entry.repo_path.clone())
3081            .collect();
3082        self.stage_entries(to_stage, cx)
3083    }
3084
3085    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
3086        let to_unstage = self
3087            .cached_status()
3088            .filter(|entry| entry.status.staging().has_staged())
3089            .map(|entry| entry.repo_path.clone())
3090            .collect();
3091        self.unstage_entries(to_unstage, cx)
3092    }
3093
3094    pub fn commit(
3095        &self,
3096        message: SharedString,
3097        name_and_email: Option<(SharedString, SharedString)>,
3098        _cx: &mut App,
3099    ) -> oneshot::Receiver<Result<()>> {
3100        let id = self.id;
3101
3102        self.send_job(move |git_repo, _cx| async move {
3103            match git_repo {
3104                RepositoryState::Local {
3105                    backend,
3106                    environment,
3107                    ..
3108                } => backend.commit(message, name_and_email, environment).await,
3109                RepositoryState::Remote { project_id, client } => {
3110                    let (name, email) = name_and_email.unzip();
3111                    client
3112                        .request(proto::Commit {
3113                            project_id: project_id.0,
3114                            repository_id: id.to_proto(),
3115                            message: String::from(message),
3116                            name: name.map(String::from),
3117                            email: email.map(String::from),
3118                        })
3119                        .await
3120                        .context("sending commit request")?;
3121
3122                    Ok(())
3123                }
3124            }
3125        })
3126    }
3127
3128    pub fn fetch(
3129        &mut self,
3130        askpass: AskPassDelegate,
3131        _cx: &mut App,
3132    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
3133        let askpass_delegates = self.askpass_delegates.clone();
3134        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
3135        let id = self.id;
3136
3137        self.send_job(move |git_repo, cx| async move {
3138            match git_repo {
3139                RepositoryState::Local {
3140                    backend,
3141                    environment,
3142                    ..
3143                } => backend.fetch(askpass, environment, cx).await,
3144                RepositoryState::Remote { project_id, client } => {
3145                    askpass_delegates.lock().insert(askpass_id, askpass);
3146                    let _defer = util::defer(|| {
3147                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
3148                        debug_assert!(askpass_delegate.is_some());
3149                    });
3150
3151                    let response = client
3152                        .request(proto::Fetch {
3153                            project_id: project_id.0,
3154                            repository_id: id.to_proto(),
3155                            askpass_id,
3156                        })
3157                        .await
3158                        .context("sending fetch request")?;
3159
3160                    Ok(RemoteCommandOutput {
3161                        stdout: response.stdout,
3162                        stderr: response.stderr,
3163                    })
3164                }
3165            }
3166        })
3167    }
3168
3169    pub fn push(
3170        &mut self,
3171        branch: SharedString,
3172        remote: SharedString,
3173        options: Option<PushOptions>,
3174        askpass: AskPassDelegate,
3175        cx: &mut Context<Self>,
3176    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
3177        let askpass_delegates = self.askpass_delegates.clone();
3178        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
3179        let id = self.id;
3180
3181        let updates_tx = self
3182            .git_store()
3183            .and_then(|git_store| match &git_store.read(cx).state {
3184                GitStoreState::Local { downstream, .. } => downstream
3185                    .as_ref()
3186                    .map(|downstream| downstream.updates_tx.clone()),
3187                _ => None,
3188            });
3189        let this = cx.weak_entity();
3190
3191        self.send_job(move |git_repo, mut cx| async move {
3192            match git_repo {
3193                RepositoryState::Local {
3194                    backend,
3195                    environment,
3196                    ..
3197                } => {
3198                    let result = backend
3199                        .push(
3200                            branch.to_string(),
3201                            remote.to_string(),
3202                            options,
3203                            askpass,
3204                            environment.clone(),
3205                            cx.clone(),
3206                        )
3207                        .await;
3208                    if result.is_ok() {
3209                        let branches = backend.branches().await?;
3210                        let branch = branches.into_iter().find(|branch| branch.is_head);
3211                        log::info!("head branch after scan is {branch:?}");
3212                        let snapshot = this.update(&mut cx, |this, cx| {
3213                            this.snapshot.branch = branch;
3214                            let snapshot = this.snapshot.clone();
3215                            cx.emit(RepositoryEvent::Updated { full_scan: false });
3216                            snapshot
3217                        })?;
3218                        if let Some(updates_tx) = updates_tx {
3219                            updates_tx
3220                                .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
3221                                .ok();
3222                        }
3223                    }
3224                    result
3225                }
3226                RepositoryState::Remote { project_id, client } => {
3227                    askpass_delegates.lock().insert(askpass_id, askpass);
3228                    let _defer = util::defer(|| {
3229                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
3230                        debug_assert!(askpass_delegate.is_some());
3231                    });
3232                    let response = client
3233                        .request(proto::Push {
3234                            project_id: project_id.0,
3235                            repository_id: id.to_proto(),
3236                            askpass_id,
3237                            branch_name: branch.to_string(),
3238                            remote_name: remote.to_string(),
3239                            options: options.map(|options| match options {
3240                                PushOptions::Force => proto::push::PushOptions::Force,
3241                                PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
3242                            } as i32),
3243                        })
3244                        .await
3245                        .context("sending push request")?;
3246
3247                    Ok(RemoteCommandOutput {
3248                        stdout: response.stdout,
3249                        stderr: response.stderr,
3250                    })
3251                }
3252            }
3253        })
3254    }
3255
3256    pub fn pull(
3257        &mut self,
3258        branch: SharedString,
3259        remote: SharedString,
3260        askpass: AskPassDelegate,
3261        _cx: &mut App,
3262    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
3263        let askpass_delegates = self.askpass_delegates.clone();
3264        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
3265        let id = self.id;
3266
3267        self.send_job(move |git_repo, cx| async move {
3268            match git_repo {
3269                RepositoryState::Local {
3270                    backend,
3271                    environment,
3272                    ..
3273                } => {
3274                    backend
3275                        .pull(
3276                            branch.to_string(),
3277                            remote.to_string(),
3278                            askpass,
3279                            environment.clone(),
3280                            cx,
3281                        )
3282                        .await
3283                }
3284                RepositoryState::Remote { project_id, client } => {
3285                    askpass_delegates.lock().insert(askpass_id, askpass);
3286                    let _defer = util::defer(|| {
3287                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
3288                        debug_assert!(askpass_delegate.is_some());
3289                    });
3290                    let response = client
3291                        .request(proto::Pull {
3292                            project_id: project_id.0,
3293                            repository_id: id.to_proto(),
3294                            askpass_id,
3295                            branch_name: branch.to_string(),
3296                            remote_name: remote.to_string(),
3297                        })
3298                        .await
3299                        .context("sending pull request")?;
3300
3301                    Ok(RemoteCommandOutput {
3302                        stdout: response.stdout,
3303                        stderr: response.stderr,
3304                    })
3305                }
3306            }
3307        })
3308    }
3309
3310    fn spawn_set_index_text_job(
3311        &self,
3312        path: RepoPath,
3313        content: Option<String>,
3314        _cx: &mut App,
3315    ) -> oneshot::Receiver<anyhow::Result<()>> {
3316        let id = self.id;
3317
3318        self.send_keyed_job(
3319            Some(GitJobKey::WriteIndex(path.clone())),
3320            move |git_repo, _cx| async move {
3321                match git_repo {
3322                    RepositoryState::Local {
3323                        backend,
3324                        environment,
3325                        ..
3326                    } => {
3327                        backend
3328                            .set_index_text(path, content, environment.clone())
3329                            .await
3330                    }
3331                    RepositoryState::Remote { project_id, client } => {
3332                        client
3333                            .request(proto::SetIndexText {
3334                                project_id: project_id.0,
3335                                repository_id: id.to_proto(),
3336                                path: path.as_ref().to_proto(),
3337                                text: content,
3338                            })
3339                            .await?;
3340                        Ok(())
3341                    }
3342                }
3343            },
3344        )
3345    }
3346
3347    pub fn get_remotes(
3348        &self,
3349        branch_name: Option<String>,
3350    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
3351        let id = self.id;
3352        self.send_job(move |repo, _cx| async move {
3353            match repo {
3354                RepositoryState::Local { backend, .. } => backend.get_remotes(branch_name).await,
3355                RepositoryState::Remote { project_id, client } => {
3356                    let response = client
3357                        .request(proto::GetRemotes {
3358                            project_id: project_id.0,
3359                            repository_id: id.to_proto(),
3360                            branch_name,
3361                        })
3362                        .await?;
3363
3364                    let remotes = response
3365                        .remotes
3366                        .into_iter()
3367                        .map(|remotes| git::repository::Remote {
3368                            name: remotes.name.into(),
3369                        })
3370                        .collect();
3371
3372                    Ok(remotes)
3373                }
3374            }
3375        })
3376    }
3377
3378    pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
3379        let id = self.id;
3380        self.send_job(move |repo, cx| async move {
3381            match repo {
3382                RepositoryState::Local { backend, .. } => {
3383                    let backend = backend.clone();
3384                    cx.background_spawn(async move { backend.branches().await })
3385                        .await
3386                }
3387                RepositoryState::Remote { project_id, client } => {
3388                    let response = client
3389                        .request(proto::GitGetBranches {
3390                            project_id: project_id.0,
3391                            repository_id: id.to_proto(),
3392                        })
3393                        .await?;
3394
3395                    let branches = response
3396                        .branches
3397                        .into_iter()
3398                        .map(|branch| proto_to_branch(&branch))
3399                        .collect();
3400
3401                    Ok(branches)
3402                }
3403            }
3404        })
3405    }
3406
3407    pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
3408        let id = self.id;
3409        self.send_job(move |repo, _cx| async move {
3410            match repo {
3411                RepositoryState::Local { backend, .. } => backend.diff(diff_type).await,
3412                RepositoryState::Remote { project_id, client } => {
3413                    let response = client
3414                        .request(proto::GitDiff {
3415                            project_id: project_id.0,
3416                            repository_id: id.to_proto(),
3417                            diff_type: match diff_type {
3418                                DiffType::HeadToIndex => {
3419                                    proto::git_diff::DiffType::HeadToIndex.into()
3420                                }
3421                                DiffType::HeadToWorktree => {
3422                                    proto::git_diff::DiffType::HeadToWorktree.into()
3423                                }
3424                            },
3425                        })
3426                        .await?;
3427
3428                    Ok(response.diff)
3429                }
3430            }
3431        })
3432    }
3433
3434    pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
3435        let id = self.id;
3436        self.send_job(move |repo, _cx| async move {
3437            match repo {
3438                RepositoryState::Local { backend, .. } => backend.create_branch(branch_name).await,
3439                RepositoryState::Remote { project_id, client } => {
3440                    client
3441                        .request(proto::GitCreateBranch {
3442                            project_id: project_id.0,
3443                            repository_id: id.to_proto(),
3444                            branch_name,
3445                        })
3446                        .await?;
3447
3448                    Ok(())
3449                }
3450            }
3451        })
3452    }
3453
3454    pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
3455        let id = self.id;
3456        self.send_job(move |repo, _cx| async move {
3457            match repo {
3458                RepositoryState::Local { backend, .. } => backend.change_branch(branch_name).await,
3459                RepositoryState::Remote { project_id, client } => {
3460                    client
3461                        .request(proto::GitChangeBranch {
3462                            project_id: project_id.0,
3463                            repository_id: id.to_proto(),
3464                            branch_name,
3465                        })
3466                        .await?;
3467
3468                    Ok(())
3469                }
3470            }
3471        })
3472    }
3473
3474    pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
3475        let id = self.id;
3476        self.send_job(move |repo, _cx| async move {
3477            match repo {
3478                RepositoryState::Local { backend, .. } => backend.check_for_pushed_commit().await,
3479                RepositoryState::Remote { project_id, client } => {
3480                    let response = client
3481                        .request(proto::CheckForPushedCommits {
3482                            project_id: project_id.0,
3483                            repository_id: id.to_proto(),
3484                        })
3485                        .await?;
3486
3487                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
3488
3489                    Ok(branches)
3490                }
3491            }
3492        })
3493    }
3494
3495    pub fn checkpoint(&self) -> oneshot::Receiver<Result<GitRepositoryCheckpoint>> {
3496        self.send_job(|repo, _cx| async move {
3497            match repo {
3498                RepositoryState::Local { backend, .. } => backend.checkpoint().await,
3499                RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3500            }
3501        })
3502    }
3503
3504    pub fn restore_checkpoint(
3505        &self,
3506        checkpoint: GitRepositoryCheckpoint,
3507    ) -> oneshot::Receiver<Result<()>> {
3508        self.send_job(move |repo, _cx| async move {
3509            match repo {
3510                RepositoryState::Local { backend, .. } => {
3511                    backend.restore_checkpoint(checkpoint).await
3512                }
3513                RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3514            }
3515        })
3516    }
3517
3518    pub(crate) fn apply_remote_update(
3519        &mut self,
3520        update: proto::UpdateRepository,
3521        cx: &mut Context<Self>,
3522    ) -> Result<()> {
3523        let conflicted_paths = TreeSet::from_ordered_entries(
3524            update
3525                .current_merge_conflicts
3526                .into_iter()
3527                .map(|path| RepoPath(Path::new(&path).into())),
3528        );
3529        self.snapshot.branch = update.branch_summary.as_ref().map(proto_to_branch);
3530        self.snapshot.merge_conflicts = conflicted_paths;
3531
3532        let edits = update
3533            .removed_statuses
3534            .into_iter()
3535            .map(|path| sum_tree::Edit::Remove(PathKey(FromProto::from_proto(path))))
3536            .chain(
3537                update
3538                    .updated_statuses
3539                    .into_iter()
3540                    .filter_map(|updated_status| {
3541                        Some(sum_tree::Edit::Insert(updated_status.try_into().log_err()?))
3542                    }),
3543            )
3544            .collect::<Vec<_>>();
3545        self.snapshot.statuses_by_path.edit(edits, &());
3546        if update.is_last_update {
3547            self.snapshot.scan_id = update.scan_id;
3548        }
3549        cx.emit(RepositoryEvent::Updated { full_scan: true });
3550        Ok(())
3551    }
3552
3553    pub fn compare_checkpoints(
3554        &self,
3555        left: GitRepositoryCheckpoint,
3556        right: GitRepositoryCheckpoint,
3557    ) -> oneshot::Receiver<Result<bool>> {
3558        self.send_job(move |repo, _cx| async move {
3559            match repo {
3560                RepositoryState::Local { backend, .. } => {
3561                    backend.compare_checkpoints(left, right).await
3562                }
3563                RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3564            }
3565        })
3566    }
3567
3568    pub fn delete_checkpoint(
3569        &self,
3570        checkpoint: GitRepositoryCheckpoint,
3571    ) -> oneshot::Receiver<Result<()>> {
3572        self.send_job(move |repo, _cx| async move {
3573            match repo {
3574                RepositoryState::Local { backend, .. } => {
3575                    backend.delete_checkpoint(checkpoint).await
3576                }
3577                RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3578            }
3579        })
3580    }
3581
3582    pub fn diff_checkpoints(
3583        &self,
3584        base_checkpoint: GitRepositoryCheckpoint,
3585        target_checkpoint: GitRepositoryCheckpoint,
3586    ) -> oneshot::Receiver<Result<String>> {
3587        self.send_job(move |repo, _cx| async move {
3588            match repo {
3589                RepositoryState::Local { backend, .. } => {
3590                    backend
3591                        .diff_checkpoints(base_checkpoint, target_checkpoint)
3592                        .await
3593                }
3594                RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3595            }
3596        })
3597    }
3598
3599    fn schedule_scan(
3600        &mut self,
3601        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
3602        cx: &mut Context<Self>,
3603    ) {
3604        let this = cx.weak_entity();
3605        let _ = self.send_keyed_job(
3606            Some(GitJobKey::ReloadGitState),
3607            |state, mut cx| async move {
3608                let Some(this) = this.upgrade() else {
3609                    return Ok(());
3610                };
3611                let RepositoryState::Local { backend, .. } = state else {
3612                    bail!("not a local repository")
3613                };
3614                let (snapshot, events) = this
3615                    .update(&mut cx, |this, _| {
3616                        compute_snapshot(
3617                            this.id,
3618                            this.work_directory_abs_path.clone(),
3619                            this.snapshot.clone(),
3620                            backend.clone(),
3621                        )
3622                    })?
3623                    .await?;
3624                this.update(&mut cx, |this, cx| {
3625                    this.snapshot = snapshot.clone();
3626                    for event in events {
3627                        cx.emit(event);
3628                    }
3629                })?;
3630                if let Some(updates_tx) = updates_tx {
3631                    updates_tx
3632                        .unbounded_send(DownstreamUpdate::UpdateRepository(snapshot))
3633                        .ok();
3634                }
3635                Ok(())
3636            },
3637        );
3638    }
3639
3640    fn spawn_local_git_worker(
3641        work_directory_abs_path: Arc<Path>,
3642        dot_git_abs_path: Arc<Path>,
3643        project_environment: WeakEntity<ProjectEnvironment>,
3644        fs: Arc<dyn Fs>,
3645        cx: &mut Context<Self>,
3646    ) -> mpsc::UnboundedSender<GitJob> {
3647        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
3648
3649        cx.spawn(async move |_, cx| {
3650            let environment = project_environment
3651                .upgrade()
3652                .ok_or_else(|| anyhow!("missing project environment"))?
3653                .update(cx, |project_environment, cx| {
3654                    project_environment.get_directory_environment(work_directory_abs_path.clone(), cx)
3655                })?
3656                .await
3657                .unwrap_or_else(|| {
3658                    log::error!("failed to get working directory environment for repository {work_directory_abs_path:?}");
3659                    HashMap::default()
3660                });
3661            let backend = cx
3662                .background_spawn(async move {
3663                    fs.open_repo(&dot_git_abs_path)
3664                        .ok_or_else(|| anyhow!("failed to build repository"))
3665                })
3666                .await?;
3667
3668            if let Some(git_hosting_provider_registry) =
3669                cx.update(|cx| GitHostingProviderRegistry::try_global(cx))?
3670            {
3671                git_hosting_providers::register_additional_providers(
3672                    git_hosting_provider_registry,
3673                    backend.clone(),
3674                );
3675            }
3676
3677            let state = RepositoryState::Local {
3678                backend,
3679                environment: Arc::new(environment),
3680            };
3681            let mut jobs = VecDeque::new();
3682            loop {
3683                while let Ok(Some(next_job)) = job_rx.try_next() {
3684                    jobs.push_back(next_job);
3685                }
3686
3687                if let Some(job) = jobs.pop_front() {
3688                    if let Some(current_key) = &job.key {
3689                        if jobs
3690                            .iter()
3691                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
3692                        {
3693                            continue;
3694                        }
3695                    }
3696                    (job.job)(state.clone(), cx).await;
3697                } else if let Some(job) = job_rx.next().await {
3698                    jobs.push_back(job);
3699                } else {
3700                    break;
3701                }
3702            }
3703            anyhow::Ok(())
3704        })
3705        .detach_and_log_err(cx);
3706
3707        job_tx
3708    }
3709
3710    fn spawn_remote_git_worker(
3711        project_id: ProjectId,
3712        client: AnyProtoClient,
3713        cx: &mut Context<Self>,
3714    ) -> mpsc::UnboundedSender<GitJob> {
3715        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
3716
3717        cx.spawn(async move |_, cx| {
3718            let state = RepositoryState::Remote { project_id, client };
3719            let mut jobs = VecDeque::new();
3720            loop {
3721                while let Ok(Some(next_job)) = job_rx.try_next() {
3722                    jobs.push_back(next_job);
3723                }
3724
3725                if let Some(job) = jobs.pop_front() {
3726                    if let Some(current_key) = &job.key {
3727                        if jobs
3728                            .iter()
3729                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
3730                        {
3731                            continue;
3732                        }
3733                    }
3734                    (job.job)(state.clone(), cx).await;
3735                } else if let Some(job) = job_rx.next().await {
3736                    jobs.push_back(job);
3737                } else {
3738                    break;
3739                }
3740            }
3741            anyhow::Ok(())
3742        })
3743        .detach_and_log_err(cx);
3744
3745        job_tx
3746    }
3747
3748    fn load_staged_text(
3749        &self,
3750        buffer_id: BufferId,
3751        repo_path: RepoPath,
3752        cx: &App,
3753    ) -> Task<Result<Option<String>>> {
3754        let rx = self.send_job(move |state, _| async move {
3755            match state {
3756                RepositoryState::Local { backend, .. } => {
3757                    anyhow::Ok(backend.load_index_text(repo_path).await)
3758                }
3759                RepositoryState::Remote { project_id, client } => {
3760                    let response = client
3761                        .request(proto::OpenUnstagedDiff {
3762                            project_id: project_id.to_proto(),
3763                            buffer_id: buffer_id.to_proto(),
3764                        })
3765                        .await?;
3766                    Ok(response.staged_text)
3767                }
3768            }
3769        });
3770        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
3771    }
3772
3773    fn load_committed_text(
3774        &self,
3775        buffer_id: BufferId,
3776        repo_path: RepoPath,
3777        cx: &App,
3778    ) -> Task<Result<DiffBasesChange>> {
3779        let rx = self.send_job(move |state, _| async move {
3780            match state {
3781                RepositoryState::Local { backend, .. } => {
3782                    let committed_text = backend.load_committed_text(repo_path.clone()).await;
3783                    let staged_text = backend.load_index_text(repo_path).await;
3784                    let diff_bases_change = if committed_text == staged_text {
3785                        DiffBasesChange::SetBoth(committed_text)
3786                    } else {
3787                        DiffBasesChange::SetEach {
3788                            index: staged_text,
3789                            head: committed_text,
3790                        }
3791                    };
3792                    anyhow::Ok(diff_bases_change)
3793                }
3794                RepositoryState::Remote { project_id, client } => {
3795                    use proto::open_uncommitted_diff_response::Mode;
3796
3797                    let response = client
3798                        .request(proto::OpenUncommittedDiff {
3799                            project_id: project_id.to_proto(),
3800                            buffer_id: buffer_id.to_proto(),
3801                        })
3802                        .await?;
3803                    let mode =
3804                        Mode::from_i32(response.mode).ok_or_else(|| anyhow!("Invalid mode"))?;
3805                    let bases = match mode {
3806                        Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
3807                        Mode::IndexAndHead => DiffBasesChange::SetEach {
3808                            head: response.committed_text,
3809                            index: response.staged_text,
3810                        },
3811                    };
3812                    Ok(bases)
3813                }
3814            }
3815        });
3816
3817        cx.spawn(|_: &mut AsyncApp| async move { rx.await? })
3818    }
3819
3820    fn paths_changed(
3821        &mut self,
3822        paths: Vec<RepoPath>,
3823        updates_tx: Option<mpsc::UnboundedSender<DownstreamUpdate>>,
3824        cx: &mut Context<Self>,
3825    ) {
3826        self.paths_needing_status_update.extend(paths);
3827
3828        let this = cx.weak_entity();
3829        let _ = self.send_keyed_job(
3830            Some(GitJobKey::RefreshStatuses),
3831            |state, mut cx| async move {
3832                let (prev_snapshot, mut changed_paths) = this.update(&mut cx, |this, _| {
3833                    (
3834                        this.snapshot.clone(),
3835                        mem::take(&mut this.paths_needing_status_update),
3836                    )
3837                })?;
3838                let RepositoryState::Local { backend, .. } = state else {
3839                    bail!("not a local repository")
3840                };
3841
3842                let paths = changed_paths.iter().cloned().collect::<Vec<_>>();
3843                let statuses = backend.status(&paths).await?;
3844
3845                let changed_path_statuses = cx
3846                    .background_spawn(async move {
3847                        let mut changed_path_statuses = Vec::new();
3848                        let prev_statuses = prev_snapshot.statuses_by_path.clone();
3849                        let mut cursor = prev_statuses.cursor::<PathProgress>(&());
3850
3851                        for (repo_path, status) in &*statuses.entries {
3852                            changed_paths.remove(repo_path);
3853                            if cursor.seek_forward(&PathTarget::Path(repo_path), Bias::Left, &()) {
3854                                if cursor.item().is_some_and(|entry| entry.status == *status) {
3855                                    continue;
3856                                }
3857                            }
3858
3859                            changed_path_statuses.push(Edit::Insert(StatusEntry {
3860                                repo_path: repo_path.clone(),
3861                                status: *status,
3862                            }));
3863                        }
3864                        let mut cursor = prev_statuses.cursor::<PathProgress>(&());
3865                        for path in changed_paths.iter() {
3866                            if cursor.seek_forward(&PathTarget::Path(&path), Bias::Left, &()) {
3867                                changed_path_statuses.push(Edit::Remove(PathKey(path.0.clone())));
3868                            }
3869                        }
3870                        changed_path_statuses
3871                    })
3872                    .await;
3873
3874                this.update(&mut cx, |this, cx| {
3875                    if !changed_path_statuses.is_empty() {
3876                        this.snapshot
3877                            .statuses_by_path
3878                            .edit(changed_path_statuses, &());
3879                        this.snapshot.scan_id += 1;
3880                        if let Some(updates_tx) = updates_tx {
3881                            updates_tx
3882                                .unbounded_send(DownstreamUpdate::UpdateRepository(
3883                                    this.snapshot.clone(),
3884                                ))
3885                                .ok();
3886                        }
3887                    }
3888                    cx.emit(RepositoryEvent::Updated { full_scan: false });
3889                })
3890            },
3891        );
3892    }
3893}
3894
3895fn get_permalink_in_rust_registry_src(
3896    provider_registry: Arc<GitHostingProviderRegistry>,
3897    path: PathBuf,
3898    selection: Range<u32>,
3899) -> Result<url::Url> {
3900    #[derive(Deserialize)]
3901    struct CargoVcsGit {
3902        sha1: String,
3903    }
3904
3905    #[derive(Deserialize)]
3906    struct CargoVcsInfo {
3907        git: CargoVcsGit,
3908        path_in_vcs: String,
3909    }
3910
3911    #[derive(Deserialize)]
3912    struct CargoPackage {
3913        repository: String,
3914    }
3915
3916    #[derive(Deserialize)]
3917    struct CargoToml {
3918        package: CargoPackage,
3919    }
3920
3921    let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
3922        let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
3923        Some((dir, json))
3924    }) else {
3925        bail!("No .cargo_vcs_info.json found in parent directories")
3926    };
3927    let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
3928    let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
3929    let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
3930    let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
3931        .ok_or_else(|| anyhow!("Failed to parse package.repository field of manifest"))?;
3932    let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
3933    let permalink = provider.build_permalink(
3934        remote,
3935        BuildPermalinkParams {
3936            sha: &cargo_vcs_info.git.sha1,
3937            path: &path.to_string_lossy(),
3938            selection: Some(selection),
3939        },
3940    );
3941    Ok(permalink)
3942}
3943
3944fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::BlameBufferResponse {
3945    let Some(blame) = blame else {
3946        return proto::BlameBufferResponse {
3947            blame_response: None,
3948        };
3949    };
3950
3951    let entries = blame
3952        .entries
3953        .into_iter()
3954        .map(|entry| proto::BlameEntry {
3955            sha: entry.sha.as_bytes().into(),
3956            start_line: entry.range.start,
3957            end_line: entry.range.end,
3958            original_line_number: entry.original_line_number,
3959            author: entry.author.clone(),
3960            author_mail: entry.author_mail.clone(),
3961            author_time: entry.author_time,
3962            author_tz: entry.author_tz.clone(),
3963            committer: entry.committer_name.clone(),
3964            committer_mail: entry.committer_email.clone(),
3965            committer_time: entry.committer_time,
3966            committer_tz: entry.committer_tz.clone(),
3967            summary: entry.summary.clone(),
3968            previous: entry.previous.clone(),
3969            filename: entry.filename.clone(),
3970        })
3971        .collect::<Vec<_>>();
3972
3973    let messages = blame
3974        .messages
3975        .into_iter()
3976        .map(|(oid, message)| proto::CommitMessage {
3977            oid: oid.as_bytes().into(),
3978            message,
3979        })
3980        .collect::<Vec<_>>();
3981
3982    proto::BlameBufferResponse {
3983        blame_response: Some(proto::blame_buffer_response::BlameResponse {
3984            entries,
3985            messages,
3986            remote_url: blame.remote_url,
3987        }),
3988    }
3989}
3990
3991fn deserialize_blame_buffer_response(
3992    response: proto::BlameBufferResponse,
3993) -> Option<git::blame::Blame> {
3994    let response = response.blame_response?;
3995    let entries = response
3996        .entries
3997        .into_iter()
3998        .filter_map(|entry| {
3999            Some(git::blame::BlameEntry {
4000                sha: git::Oid::from_bytes(&entry.sha).ok()?,
4001                range: entry.start_line..entry.end_line,
4002                original_line_number: entry.original_line_number,
4003                committer_name: entry.committer,
4004                committer_time: entry.committer_time,
4005                committer_tz: entry.committer_tz,
4006                committer_email: entry.committer_mail,
4007                author: entry.author,
4008                author_mail: entry.author_mail,
4009                author_time: entry.author_time,
4010                author_tz: entry.author_tz,
4011                summary: entry.summary,
4012                previous: entry.previous,
4013                filename: entry.filename,
4014            })
4015        })
4016        .collect::<Vec<_>>();
4017
4018    let messages = response
4019        .messages
4020        .into_iter()
4021        .filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
4022        .collect::<HashMap<_, _>>();
4023
4024    Some(Blame {
4025        entries,
4026        messages,
4027        remote_url: response.remote_url,
4028    })
4029}
4030
4031fn branch_to_proto(branch: &git::repository::Branch) -> proto::Branch {
4032    proto::Branch {
4033        is_head: branch.is_head,
4034        name: branch.name.to_string(),
4035        unix_timestamp: branch
4036            .most_recent_commit
4037            .as_ref()
4038            .map(|commit| commit.commit_timestamp as u64),
4039        upstream: branch.upstream.as_ref().map(|upstream| proto::GitUpstream {
4040            ref_name: upstream.ref_name.to_string(),
4041            tracking: upstream
4042                .tracking
4043                .status()
4044                .map(|upstream| proto::UpstreamTracking {
4045                    ahead: upstream.ahead as u64,
4046                    behind: upstream.behind as u64,
4047                }),
4048        }),
4049        most_recent_commit: branch
4050            .most_recent_commit
4051            .as_ref()
4052            .map(|commit| proto::CommitSummary {
4053                sha: commit.sha.to_string(),
4054                subject: commit.subject.to_string(),
4055                commit_timestamp: commit.commit_timestamp,
4056            }),
4057    }
4058}
4059
4060fn proto_to_branch(proto: &proto::Branch) -> git::repository::Branch {
4061    git::repository::Branch {
4062        is_head: proto.is_head,
4063        name: proto.name.clone().into(),
4064        upstream: proto
4065            .upstream
4066            .as_ref()
4067            .map(|upstream| git::repository::Upstream {
4068                ref_name: upstream.ref_name.to_string().into(),
4069                tracking: upstream
4070                    .tracking
4071                    .as_ref()
4072                    .map(|tracking| {
4073                        git::repository::UpstreamTracking::Tracked(UpstreamTrackingStatus {
4074                            ahead: tracking.ahead as u32,
4075                            behind: tracking.behind as u32,
4076                        })
4077                    })
4078                    .unwrap_or(git::repository::UpstreamTracking::Gone),
4079            }),
4080        most_recent_commit: proto.most_recent_commit.as_ref().map(|commit| {
4081            git::repository::CommitSummary {
4082                sha: commit.sha.to_string().into(),
4083                subject: commit.subject.to_string().into(),
4084                commit_timestamp: commit.commit_timestamp,
4085                has_parent: true,
4086            }
4087        }),
4088    }
4089}
4090
4091async fn compute_snapshot(
4092    id: RepositoryId,
4093    work_directory_abs_path: Arc<Path>,
4094    prev_snapshot: RepositorySnapshot,
4095    backend: Arc<dyn GitRepository>,
4096) -> Result<(RepositorySnapshot, Vec<RepositoryEvent>)> {
4097    let mut events = Vec::new();
4098    let branches = backend.branches().await?;
4099    let branch = branches.into_iter().find(|branch| branch.is_head);
4100    let statuses = backend.status(&[WORK_DIRECTORY_REPO_PATH.clone()]).await?;
4101    let merge_message = backend
4102        .merge_message()
4103        .await
4104        .and_then(|msg| Some(msg.lines().nth(0)?.to_owned().into()));
4105    let merge_head_shas = backend
4106        .merge_head_shas()
4107        .into_iter()
4108        .map(SharedString::from)
4109        .collect();
4110
4111    let statuses_by_path = SumTree::from_iter(
4112        statuses
4113            .entries
4114            .iter()
4115            .map(|(repo_path, status)| StatusEntry {
4116                repo_path: repo_path.clone(),
4117                status: *status,
4118            }),
4119        &(),
4120    );
4121
4122    let merge_head_shas_changed = merge_head_shas != prev_snapshot.merge_head_shas;
4123
4124    if merge_head_shas_changed
4125        || branch != prev_snapshot.branch
4126        || statuses_by_path != prev_snapshot.statuses_by_path
4127    {
4128        events.push(RepositoryEvent::Updated { full_scan: true });
4129    }
4130
4131    let mut current_merge_conflicts = TreeSet::default();
4132    for (repo_path, status) in statuses.entries.iter() {
4133        if status.is_conflicted() {
4134            current_merge_conflicts.insert(repo_path.clone());
4135        }
4136    }
4137
4138    // Cache merge conflict paths so they don't change from staging/unstaging,
4139    // until the merge heads change (at commit time, etc.).
4140    let mut merge_conflicts = prev_snapshot.merge_conflicts.clone();
4141    if merge_head_shas_changed {
4142        merge_conflicts = current_merge_conflicts;
4143        events.push(RepositoryEvent::MergeHeadsChanged);
4144    }
4145
4146    let snapshot = RepositorySnapshot {
4147        id,
4148        merge_message,
4149        statuses_by_path,
4150        work_directory_abs_path,
4151        scan_id: prev_snapshot.scan_id + 1,
4152        branch,
4153        merge_conflicts,
4154        merge_head_shas,
4155    };
4156
4157    Ok((snapshot, events))
4158}
4159
4160fn status_from_proto(
4161    simple_status: i32,
4162    status: Option<proto::GitFileStatus>,
4163) -> anyhow::Result<FileStatus> {
4164    use proto::git_file_status::Variant;
4165
4166    let Some(variant) = status.and_then(|status| status.variant) else {
4167        let code = proto::GitStatus::from_i32(simple_status)
4168            .ok_or_else(|| anyhow!("Invalid git status code: {simple_status}"))?;
4169        let result = match code {
4170            proto::GitStatus::Added => TrackedStatus {
4171                worktree_status: StatusCode::Added,
4172                index_status: StatusCode::Unmodified,
4173            }
4174            .into(),
4175            proto::GitStatus::Modified => TrackedStatus {
4176                worktree_status: StatusCode::Modified,
4177                index_status: StatusCode::Unmodified,
4178            }
4179            .into(),
4180            proto::GitStatus::Conflict => UnmergedStatus {
4181                first_head: UnmergedStatusCode::Updated,
4182                second_head: UnmergedStatusCode::Updated,
4183            }
4184            .into(),
4185            proto::GitStatus::Deleted => TrackedStatus {
4186                worktree_status: StatusCode::Deleted,
4187                index_status: StatusCode::Unmodified,
4188            }
4189            .into(),
4190            _ => return Err(anyhow!("Invalid code for simple status: {simple_status}")),
4191        };
4192        return Ok(result);
4193    };
4194
4195    let result = match variant {
4196        Variant::Untracked(_) => FileStatus::Untracked,
4197        Variant::Ignored(_) => FileStatus::Ignored,
4198        Variant::Unmerged(unmerged) => {
4199            let [first_head, second_head] =
4200                [unmerged.first_head, unmerged.second_head].map(|head| {
4201                    let code = proto::GitStatus::from_i32(head)
4202                        .ok_or_else(|| anyhow!("Invalid git status code: {head}"))?;
4203                    let result = match code {
4204                        proto::GitStatus::Added => UnmergedStatusCode::Added,
4205                        proto::GitStatus::Updated => UnmergedStatusCode::Updated,
4206                        proto::GitStatus::Deleted => UnmergedStatusCode::Deleted,
4207                        _ => return Err(anyhow!("Invalid code for unmerged status: {code:?}")),
4208                    };
4209                    Ok(result)
4210                });
4211            let [first_head, second_head] = [first_head?, second_head?];
4212            UnmergedStatus {
4213                first_head,
4214                second_head,
4215            }
4216            .into()
4217        }
4218        Variant::Tracked(tracked) => {
4219            let [index_status, worktree_status] = [tracked.index_status, tracked.worktree_status]
4220                .map(|status| {
4221                    let code = proto::GitStatus::from_i32(status)
4222                        .ok_or_else(|| anyhow!("Invalid git status code: {status}"))?;
4223                    let result = match code {
4224                        proto::GitStatus::Modified => StatusCode::Modified,
4225                        proto::GitStatus::TypeChanged => StatusCode::TypeChanged,
4226                        proto::GitStatus::Added => StatusCode::Added,
4227                        proto::GitStatus::Deleted => StatusCode::Deleted,
4228                        proto::GitStatus::Renamed => StatusCode::Renamed,
4229                        proto::GitStatus::Copied => StatusCode::Copied,
4230                        proto::GitStatus::Unmodified => StatusCode::Unmodified,
4231                        _ => return Err(anyhow!("Invalid code for tracked status: {code:?}")),
4232                    };
4233                    Ok(result)
4234                });
4235            let [index_status, worktree_status] = [index_status?, worktree_status?];
4236            TrackedStatus {
4237                index_status,
4238                worktree_status,
4239            }
4240            .into()
4241        }
4242    };
4243    Ok(result)
4244}
4245
4246fn status_to_proto(status: FileStatus) -> proto::GitFileStatus {
4247    use proto::git_file_status::{Tracked, Unmerged, Variant};
4248
4249    let variant = match status {
4250        FileStatus::Untracked => Variant::Untracked(Default::default()),
4251        FileStatus::Ignored => Variant::Ignored(Default::default()),
4252        FileStatus::Unmerged(UnmergedStatus {
4253            first_head,
4254            second_head,
4255        }) => Variant::Unmerged(Unmerged {
4256            first_head: unmerged_status_to_proto(first_head),
4257            second_head: unmerged_status_to_proto(second_head),
4258        }),
4259        FileStatus::Tracked(TrackedStatus {
4260            index_status,
4261            worktree_status,
4262        }) => Variant::Tracked(Tracked {
4263            index_status: tracked_status_to_proto(index_status),
4264            worktree_status: tracked_status_to_proto(worktree_status),
4265        }),
4266    };
4267    proto::GitFileStatus {
4268        variant: Some(variant),
4269    }
4270}
4271
4272fn unmerged_status_to_proto(code: UnmergedStatusCode) -> i32 {
4273    match code {
4274        UnmergedStatusCode::Added => proto::GitStatus::Added as _,
4275        UnmergedStatusCode::Deleted => proto::GitStatus::Deleted as _,
4276        UnmergedStatusCode::Updated => proto::GitStatus::Updated as _,
4277    }
4278}
4279
4280fn tracked_status_to_proto(code: StatusCode) -> i32 {
4281    match code {
4282        StatusCode::Added => proto::GitStatus::Added as _,
4283        StatusCode::Deleted => proto::GitStatus::Deleted as _,
4284        StatusCode::Modified => proto::GitStatus::Modified as _,
4285        StatusCode::Renamed => proto::GitStatus::Renamed as _,
4286        StatusCode::TypeChanged => proto::GitStatus::TypeChanged as _,
4287        StatusCode::Copied => proto::GitStatus::Copied as _,
4288        StatusCode::Unmodified => proto::GitStatus::Unmodified as _,
4289    }
4290}