git_store.rs

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