git_store.rs

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