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