git.rs

   1use crate::{
   2    buffer_store::{BufferStore, BufferStoreEvent},
   3    worktree_store::{WorktreeStore, WorktreeStoreEvent},
   4    Project, ProjectEnvironment, ProjectItem, ProjectPath,
   5};
   6use anyhow::{anyhow, Context as _, Result};
   7use askpass::{AskPassDelegate, AskPassSession};
   8use buffer_diff::{BufferDiff, BufferDiffEvent};
   9use client::ProjectId;
  10use collections::HashMap;
  11use fs::Fs;
  12use futures::{
  13    channel::{mpsc, oneshot},
  14    future::{self, OptionFuture, Shared},
  15    FutureExt as _, StreamExt as _,
  16};
  17use git::{repository::DiffType, Oid};
  18use git::{
  19    repository::{
  20        Branch, CommitDetails, GitRepository, PushOptions, Remote, RemoteCommandOutput, RepoPath,
  21        ResetMode,
  22    },
  23    status::FileStatus,
  24};
  25use gpui::{
  26    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
  27    WeakEntity,
  28};
  29use language::{Buffer, BufferEvent, Language, LanguageRegistry};
  30use parking_lot::Mutex;
  31use rpc::{
  32    proto::{self, git_reset, ToProto, SSH_PROJECT_ID},
  33    AnyProtoClient, TypedEnvelope,
  34};
  35use settings::WorktreeId;
  36use std::{
  37    collections::{hash_map, VecDeque},
  38    future::Future,
  39    path::{Path, PathBuf},
  40    sync::Arc,
  41};
  42use text::BufferId;
  43use util::{debug_panic, maybe, ResultExt};
  44use worktree::{
  45    File, ProjectEntryId, RepositoryEntry, StatusEntry, UpdatedGitRepositoriesSet, WorkDirectory,
  46    Worktree,
  47};
  48
  49pub struct GitStore {
  50    state: GitStoreState,
  51    buffer_store: Entity<BufferStore>,
  52    repositories: HashMap<ProjectEntryId, Entity<Repository>>,
  53    active_repo_id: Option<ProjectEntryId>,
  54    #[allow(clippy::type_complexity)]
  55    loading_diffs:
  56        HashMap<(BufferId, DiffKind), Shared<Task<Result<Entity<BufferDiff>, Arc<anyhow::Error>>>>>,
  57    diffs: HashMap<BufferId, Entity<BufferDiffState>>,
  58    update_sender: mpsc::UnboundedSender<GitJob>,
  59    shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>,
  60    _subscriptions: [Subscription; 2],
  61}
  62
  63#[derive(Default)]
  64struct SharedDiffs {
  65    unstaged: Option<Entity<BufferDiff>>,
  66    uncommitted: Option<Entity<BufferDiff>>,
  67}
  68
  69#[derive(Default)]
  70struct BufferDiffState {
  71    unstaged_diff: Option<WeakEntity<BufferDiff>>,
  72    uncommitted_diff: Option<WeakEntity<BufferDiff>>,
  73    recalculate_diff_task: Option<Task<Result<()>>>,
  74    language: Option<Arc<Language>>,
  75    language_registry: Option<Arc<LanguageRegistry>>,
  76    diff_updated_futures: Vec<oneshot::Sender<()>>,
  77
  78    head_text: Option<Arc<String>>,
  79    index_text: Option<Arc<String>>,
  80    head_changed: bool,
  81    index_changed: bool,
  82    language_changed: bool,
  83}
  84
  85#[derive(Clone, Debug)]
  86enum DiffBasesChange {
  87    SetIndex(Option<String>),
  88    SetHead(Option<String>),
  89    SetEach {
  90        index: Option<String>,
  91        head: Option<String>,
  92    },
  93    SetBoth(Option<String>),
  94}
  95
  96#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
  97enum DiffKind {
  98    Unstaged,
  99    Uncommitted,
 100}
 101
 102enum GitStoreState {
 103    Local {
 104        downstream_client: Option<(AnyProtoClient, ProjectId)>,
 105        environment: Entity<ProjectEnvironment>,
 106        fs: Arc<dyn Fs>,
 107    },
 108    Ssh {
 109        upstream_client: AnyProtoClient,
 110        upstream_project_id: ProjectId,
 111        downstream_client: Option<(AnyProtoClient, ProjectId)>,
 112        environment: Entity<ProjectEnvironment>,
 113    },
 114    Remote {
 115        upstream_client: AnyProtoClient,
 116        project_id: ProjectId,
 117    },
 118}
 119
 120#[derive(Clone)]
 121pub struct GitStoreCheckpoint {
 122    checkpoints_by_dot_git_abs_path: HashMap<PathBuf, RepositoryCheckpoint>,
 123}
 124
 125#[derive(Copy, Clone)]
 126pub struct RepositoryCheckpoint {
 127    sha: Oid,
 128}
 129
 130pub struct Repository {
 131    commit_message_buffer: Option<Entity<Buffer>>,
 132    git_store: WeakEntity<GitStore>,
 133    project_environment: Option<WeakEntity<ProjectEnvironment>>,
 134    pub worktree_id: WorktreeId,
 135    pub repository_entry: RepositoryEntry,
 136    pub dot_git_abs_path: PathBuf,
 137    pub worktree_abs_path: Arc<Path>,
 138    pub is_from_single_file_worktree: bool,
 139    pub git_repo: GitRepo,
 140    pub merge_message: Option<String>,
 141    job_sender: mpsc::UnboundedSender<GitJob>,
 142    askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,
 143    latest_askpass_id: u64,
 144}
 145
 146#[derive(Clone)]
 147pub enum GitRepo {
 148    Local(Arc<dyn GitRepository>),
 149    Remote {
 150        project_id: ProjectId,
 151        client: AnyProtoClient,
 152        worktree_id: WorktreeId,
 153        work_directory_id: ProjectEntryId,
 154    },
 155}
 156
 157#[derive(Debug)]
 158pub enum GitEvent {
 159    ActiveRepositoryChanged,
 160    FileSystemUpdated,
 161    GitStateUpdated,
 162    IndexWriteError(anyhow::Error),
 163}
 164
 165struct GitJob {
 166    job: Box<dyn FnOnce(&mut AsyncApp) -> Task<()>>,
 167    key: Option<GitJobKey>,
 168}
 169
 170#[derive(PartialEq, Eq)]
 171enum GitJobKey {
 172    WriteIndex(RepoPath),
 173    BatchReadIndex(ProjectEntryId),
 174}
 175
 176impl EventEmitter<GitEvent> for GitStore {}
 177
 178impl GitStore {
 179    pub fn local(
 180        worktree_store: &Entity<WorktreeStore>,
 181        buffer_store: Entity<BufferStore>,
 182        environment: Entity<ProjectEnvironment>,
 183        fs: Arc<dyn Fs>,
 184        cx: &mut Context<Self>,
 185    ) -> Self {
 186        Self::new(
 187            worktree_store,
 188            buffer_store,
 189            GitStoreState::Local {
 190                downstream_client: None,
 191                environment,
 192                fs,
 193            },
 194            cx,
 195        )
 196    }
 197
 198    pub fn remote(
 199        worktree_store: &Entity<WorktreeStore>,
 200        buffer_store: Entity<BufferStore>,
 201        upstream_client: AnyProtoClient,
 202        project_id: ProjectId,
 203        cx: &mut Context<Self>,
 204    ) -> Self {
 205        Self::new(
 206            worktree_store,
 207            buffer_store,
 208            GitStoreState::Remote {
 209                upstream_client,
 210                project_id,
 211            },
 212            cx,
 213        )
 214    }
 215
 216    pub fn ssh(
 217        worktree_store: &Entity<WorktreeStore>,
 218        buffer_store: Entity<BufferStore>,
 219        environment: Entity<ProjectEnvironment>,
 220        upstream_client: AnyProtoClient,
 221        cx: &mut Context<Self>,
 222    ) -> Self {
 223        Self::new(
 224            worktree_store,
 225            buffer_store,
 226            GitStoreState::Ssh {
 227                upstream_client,
 228                upstream_project_id: ProjectId(SSH_PROJECT_ID),
 229                downstream_client: None,
 230                environment,
 231            },
 232            cx,
 233        )
 234    }
 235
 236    fn new(
 237        worktree_store: &Entity<WorktreeStore>,
 238        buffer_store: Entity<BufferStore>,
 239        state: GitStoreState,
 240        cx: &mut Context<Self>,
 241    ) -> Self {
 242        let update_sender = Self::spawn_git_worker(cx);
 243        let _subscriptions = [
 244            cx.subscribe(worktree_store, Self::on_worktree_store_event),
 245            cx.subscribe(&buffer_store, Self::on_buffer_store_event),
 246        ];
 247
 248        GitStore {
 249            state,
 250            buffer_store,
 251            repositories: HashMap::default(),
 252            active_repo_id: None,
 253            update_sender,
 254            _subscriptions,
 255            loading_diffs: HashMap::default(),
 256            shared_diffs: HashMap::default(),
 257            diffs: HashMap::default(),
 258        }
 259    }
 260
 261    pub fn init(client: &AnyProtoClient) {
 262        client.add_entity_request_handler(Self::handle_get_remotes);
 263        client.add_entity_request_handler(Self::handle_get_branches);
 264        client.add_entity_request_handler(Self::handle_change_branch);
 265        client.add_entity_request_handler(Self::handle_create_branch);
 266        client.add_entity_request_handler(Self::handle_git_init);
 267        client.add_entity_request_handler(Self::handle_push);
 268        client.add_entity_request_handler(Self::handle_pull);
 269        client.add_entity_request_handler(Self::handle_fetch);
 270        client.add_entity_request_handler(Self::handle_stage);
 271        client.add_entity_request_handler(Self::handle_unstage);
 272        client.add_entity_request_handler(Self::handle_commit);
 273        client.add_entity_request_handler(Self::handle_reset);
 274        client.add_entity_request_handler(Self::handle_show);
 275        client.add_entity_request_handler(Self::handle_checkout_files);
 276        client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
 277        client.add_entity_request_handler(Self::handle_set_index_text);
 278        client.add_entity_request_handler(Self::handle_askpass);
 279        client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
 280        client.add_entity_request_handler(Self::handle_git_diff);
 281        client.add_entity_request_handler(Self::handle_open_unstaged_diff);
 282        client.add_entity_request_handler(Self::handle_open_uncommitted_diff);
 283        client.add_entity_message_handler(Self::handle_update_diff_bases);
 284    }
 285
 286    pub fn is_local(&self) -> bool {
 287        matches!(self.state, GitStoreState::Local { .. })
 288    }
 289
 290    pub fn shared(&mut self, remote_id: u64, client: AnyProtoClient, _cx: &mut App) {
 291        match &mut self.state {
 292            GitStoreState::Local {
 293                downstream_client, ..
 294            }
 295            | GitStoreState::Ssh {
 296                downstream_client, ..
 297            } => {
 298                *downstream_client = Some((client, ProjectId(remote_id)));
 299            }
 300            GitStoreState::Remote { .. } => {
 301                debug_panic!("shared called on remote store");
 302            }
 303        }
 304    }
 305
 306    pub fn unshared(&mut self, _cx: &mut Context<Self>) {
 307        match &mut self.state {
 308            GitStoreState::Local {
 309                downstream_client, ..
 310            }
 311            | GitStoreState::Ssh {
 312                downstream_client, ..
 313            } => {
 314                downstream_client.take();
 315            }
 316            GitStoreState::Remote { .. } => {
 317                debug_panic!("unshared called on remote store");
 318            }
 319        }
 320        self.shared_diffs.clear();
 321    }
 322
 323    pub(crate) fn forget_shared_diffs_for(&mut self, peer_id: &proto::PeerId) {
 324        self.shared_diffs.remove(peer_id);
 325    }
 326
 327    pub fn active_repository(&self) -> Option<Entity<Repository>> {
 328        self.active_repo_id
 329            .as_ref()
 330            .map(|id| self.repositories[&id].clone())
 331    }
 332
 333    pub fn open_unstaged_diff(
 334        &mut self,
 335        buffer: Entity<Buffer>,
 336        cx: &mut Context<Self>,
 337    ) -> Task<Result<Entity<BufferDiff>>> {
 338        let buffer_id = buffer.read(cx).remote_id();
 339        if let Some(diff_state) = self.diffs.get(&buffer_id) {
 340            if let Some(unstaged_diff) = diff_state
 341                .read(cx)
 342                .unstaged_diff
 343                .as_ref()
 344                .and_then(|weak| weak.upgrade())
 345            {
 346                if let Some(task) =
 347                    diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
 348                {
 349                    return cx.background_executor().spawn(async move {
 350                        task.await?;
 351                        Ok(unstaged_diff)
 352                    });
 353                }
 354                return Task::ready(Ok(unstaged_diff));
 355            }
 356        }
 357
 358        let task = match self.loading_diffs.entry((buffer_id, DiffKind::Unstaged)) {
 359            hash_map::Entry::Occupied(e) => e.get().clone(),
 360            hash_map::Entry::Vacant(entry) => {
 361                let staged_text = self.state.load_staged_text(&buffer, &self.buffer_store, cx);
 362                entry
 363                    .insert(
 364                        cx.spawn(async move |this, cx| {
 365                            Self::open_diff_internal(
 366                                this,
 367                                DiffKind::Unstaged,
 368                                staged_text.await.map(DiffBasesChange::SetIndex),
 369                                buffer,
 370                                cx,
 371                            )
 372                            .await
 373                            .map_err(Arc::new)
 374                        })
 375                        .shared(),
 376                    )
 377                    .clone()
 378            }
 379        };
 380
 381        cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
 382    }
 383
 384    pub fn open_uncommitted_diff(
 385        &mut self,
 386        buffer: Entity<Buffer>,
 387        cx: &mut Context<Self>,
 388    ) -> Task<Result<Entity<BufferDiff>>> {
 389        let buffer_id = buffer.read(cx).remote_id();
 390
 391        if let Some(diff_state) = self.diffs.get(&buffer_id) {
 392            if let Some(uncommitted_diff) = diff_state
 393                .read(cx)
 394                .uncommitted_diff
 395                .as_ref()
 396                .and_then(|weak| weak.upgrade())
 397            {
 398                if let Some(task) =
 399                    diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
 400                {
 401                    return cx.background_executor().spawn(async move {
 402                        task.await?;
 403                        Ok(uncommitted_diff)
 404                    });
 405                }
 406                return Task::ready(Ok(uncommitted_diff));
 407            }
 408        }
 409
 410        let task = match self.loading_diffs.entry((buffer_id, DiffKind::Uncommitted)) {
 411            hash_map::Entry::Occupied(e) => e.get().clone(),
 412            hash_map::Entry::Vacant(entry) => {
 413                let changes = self
 414                    .state
 415                    .load_committed_text(&buffer, &self.buffer_store, cx);
 416
 417                entry
 418                    .insert(
 419                        cx.spawn(async move |this, cx| {
 420                            Self::open_diff_internal(
 421                                this,
 422                                DiffKind::Uncommitted,
 423                                changes.await,
 424                                buffer,
 425                                cx,
 426                            )
 427                            .await
 428                            .map_err(Arc::new)
 429                        })
 430                        .shared(),
 431                    )
 432                    .clone()
 433            }
 434        };
 435
 436        cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
 437    }
 438
 439    async fn open_diff_internal(
 440        this: WeakEntity<Self>,
 441        kind: DiffKind,
 442        texts: Result<DiffBasesChange>,
 443        buffer_entity: Entity<Buffer>,
 444        cx: &mut AsyncApp,
 445    ) -> Result<Entity<BufferDiff>> {
 446        let diff_bases_change = match texts {
 447            Err(e) => {
 448                this.update(cx, |this, cx| {
 449                    let buffer = buffer_entity.read(cx);
 450                    let buffer_id = buffer.remote_id();
 451                    this.loading_diffs.remove(&(buffer_id, kind));
 452                })?;
 453                return Err(e);
 454            }
 455            Ok(change) => change,
 456        };
 457
 458        this.update(cx, |this, cx| {
 459            let buffer = buffer_entity.read(cx);
 460            let buffer_id = buffer.remote_id();
 461            let language = buffer.language().cloned();
 462            let language_registry = buffer.language_registry();
 463            let text_snapshot = buffer.text_snapshot();
 464            this.loading_diffs.remove(&(buffer_id, kind));
 465
 466            let diff_state = this
 467                .diffs
 468                .entry(buffer_id)
 469                .or_insert_with(|| cx.new(|_| BufferDiffState::default()));
 470
 471            let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
 472
 473            cx.subscribe(&diff, Self::on_buffer_diff_event).detach();
 474            diff_state.update(cx, |diff_state, cx| {
 475                diff_state.language = language;
 476                diff_state.language_registry = language_registry;
 477
 478                match kind {
 479                    DiffKind::Unstaged => diff_state.unstaged_diff = Some(diff.downgrade()),
 480                    DiffKind::Uncommitted => {
 481                        let unstaged_diff = if let Some(diff) = diff_state.unstaged_diff() {
 482                            diff
 483                        } else {
 484                            let unstaged_diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
 485                            diff_state.unstaged_diff = Some(unstaged_diff.downgrade());
 486                            unstaged_diff
 487                        };
 488
 489                        diff.update(cx, |diff, _| diff.set_secondary_diff(unstaged_diff));
 490                        diff_state.uncommitted_diff = Some(diff.downgrade())
 491                    }
 492                }
 493
 494                let rx = diff_state.diff_bases_changed(text_snapshot, diff_bases_change, cx);
 495
 496                anyhow::Ok(async move {
 497                    rx.await.ok();
 498                    Ok(diff)
 499                })
 500            })
 501        })??
 502        .await
 503    }
 504
 505    pub fn get_unstaged_diff(&self, buffer_id: BufferId, cx: &App) -> Option<Entity<BufferDiff>> {
 506        let diff_state = self.diffs.get(&buffer_id)?;
 507        diff_state.read(cx).unstaged_diff.as_ref()?.upgrade()
 508    }
 509
 510    pub fn get_uncommitted_diff(
 511        &self,
 512        buffer_id: BufferId,
 513        cx: &App,
 514    ) -> Option<Entity<BufferDiff>> {
 515        let diff_state = self.diffs.get(&buffer_id)?;
 516        diff_state.read(cx).uncommitted_diff.as_ref()?.upgrade()
 517    }
 518
 519    pub fn checkpoint(&self, cx: &App) -> Task<Result<GitStoreCheckpoint>> {
 520        let mut dot_git_abs_paths = Vec::new();
 521        let mut checkpoints = Vec::new();
 522        for repository in self.repositories.values() {
 523            let repository = repository.read(cx);
 524            dot_git_abs_paths.push(repository.dot_git_abs_path.clone());
 525            checkpoints.push(repository.checkpoint().map(|checkpoint| checkpoint?));
 526        }
 527
 528        cx.background_executor().spawn(async move {
 529            let checkpoints: Vec<RepositoryCheckpoint> = future::try_join_all(checkpoints).await?;
 530            Ok(GitStoreCheckpoint {
 531                checkpoints_by_dot_git_abs_path: dot_git_abs_paths
 532                    .into_iter()
 533                    .zip(checkpoints)
 534                    .collect(),
 535            })
 536        })
 537    }
 538
 539    pub fn restore_checkpoint(&self, checkpoint: GitStoreCheckpoint, cx: &App) -> Task<Result<()>> {
 540        let repositories_by_dot_git_abs_path = self
 541            .repositories
 542            .values()
 543            .map(|repo| (repo.read(cx).dot_git_abs_path.clone(), repo))
 544            .collect::<HashMap<_, _>>();
 545
 546        let mut tasks = Vec::new();
 547        for (dot_git_abs_path, checkpoint) in checkpoint.checkpoints_by_dot_git_abs_path {
 548            if let Some(repository) = repositories_by_dot_git_abs_path.get(&dot_git_abs_path) {
 549                tasks.push(repository.read(cx).restore_checkpoint(checkpoint));
 550            }
 551        }
 552        cx.background_spawn(async move {
 553            future::try_join_all(tasks).await?;
 554            Ok(())
 555        })
 556    }
 557
 558    fn downstream_client(&self) -> Option<(AnyProtoClient, ProjectId)> {
 559        match &self.state {
 560            GitStoreState::Local {
 561                downstream_client, ..
 562            }
 563            | GitStoreState::Ssh {
 564                downstream_client, ..
 565            } => downstream_client.clone(),
 566            GitStoreState::Remote { .. } => None,
 567        }
 568    }
 569
 570    fn upstream_client(&self) -> Option<AnyProtoClient> {
 571        match &self.state {
 572            GitStoreState::Local { .. } => None,
 573            GitStoreState::Ssh {
 574                upstream_client, ..
 575            }
 576            | GitStoreState::Remote {
 577                upstream_client, ..
 578            } => Some(upstream_client.clone()),
 579        }
 580    }
 581
 582    fn project_environment(&self) -> Option<Entity<ProjectEnvironment>> {
 583        match &self.state {
 584            GitStoreState::Local { environment, .. } => Some(environment.clone()),
 585            GitStoreState::Ssh { environment, .. } => Some(environment.clone()),
 586            GitStoreState::Remote { .. } => None,
 587        }
 588    }
 589
 590    fn project_id(&self) -> Option<ProjectId> {
 591        match &self.state {
 592            GitStoreState::Local { .. } => None,
 593            GitStoreState::Ssh { .. } => Some(ProjectId(proto::SSH_PROJECT_ID)),
 594            GitStoreState::Remote { project_id, .. } => Some(*project_id),
 595        }
 596    }
 597
 598    fn on_worktree_store_event(
 599        &mut self,
 600        worktree_store: Entity<WorktreeStore>,
 601        event: &WorktreeStoreEvent,
 602        cx: &mut Context<Self>,
 603    ) {
 604        let mut new_repositories = HashMap::default();
 605        let git_store = cx.weak_entity();
 606
 607        worktree_store.update(cx, |worktree_store, cx| {
 608            for worktree in worktree_store.worktrees() {
 609                worktree.update(cx, |worktree, cx| {
 610                    let snapshot = worktree.snapshot();
 611                    for repo_entry in snapshot.repositories().iter() {
 612                        let git_repo_and_merge_message = worktree
 613                            .as_local()
 614                            .and_then(|local_worktree| local_worktree.get_local_repo(repo_entry))
 615                            .map(|local_repo| {
 616                                (
 617                                    GitRepo::Local(local_repo.repo().clone()),
 618                                    local_repo.merge_message.clone(),
 619                                )
 620                            })
 621                            .or_else(|| {
 622                                let git_repo = GitRepo::Remote {
 623                                    project_id: self.project_id()?,
 624                                    client: self
 625                                        .upstream_client()
 626                                        .context("no upstream client")
 627                                        .log_err()?
 628                                        .clone(),
 629                                    worktree_id: worktree.id(),
 630                                    work_directory_id: repo_entry.work_directory_id(),
 631                                };
 632                                Some((git_repo, None))
 633                            });
 634
 635                        let Some((git_repo, merge_message)) = git_repo_and_merge_message else {
 636                            continue;
 637                        };
 638
 639                        let existing_repo = self.repositories.values().find(|repo| {
 640                            repo.read(cx).id() == (worktree.id(), repo_entry.work_directory_id())
 641                        });
 642
 643                        let repo = if let Some(existing_repo) = existing_repo {
 644                            // Update the statuses and merge message but keep everything else.
 645                            let existing_repo = existing_repo.clone();
 646                            existing_repo.update(cx, |existing_repo, _| {
 647                                existing_repo.repository_entry = repo_entry.clone();
 648                                if matches!(git_repo, GitRepo::Local { .. }) {
 649                                    existing_repo.merge_message = merge_message;
 650                                }
 651                            });
 652                            existing_repo
 653                        } else {
 654                            cx.new(|_| Repository {
 655                                project_environment: self
 656                                    .project_environment()
 657                                    .as_ref()
 658                                    .map(|env| env.downgrade()),
 659                                git_store: git_store.clone(),
 660                                worktree_id: worktree.id(),
 661                                askpass_delegates: Default::default(),
 662                                latest_askpass_id: 0,
 663                                repository_entry: repo_entry.clone(),
 664                                dot_git_abs_path: worktree
 665                                    .dot_git_abs_path(&repo_entry.work_directory),
 666                                worktree_abs_path: worktree.abs_path(),
 667                                is_from_single_file_worktree: worktree.is_single_file(),
 668                                git_repo,
 669                                job_sender: self.update_sender.clone(),
 670                                merge_message,
 671                                commit_message_buffer: None,
 672                            })
 673                        };
 674                        new_repositories.insert(repo_entry.work_directory_id(), repo);
 675                    }
 676                })
 677            }
 678        });
 679
 680        self.repositories = new_repositories;
 681        if let Some(id) = self.active_repo_id.as_ref() {
 682            if !self.repositories.contains_key(id) {
 683                self.active_repo_id = None;
 684            }
 685        } else if let Some(&first_id) = self.repositories.keys().next() {
 686            self.active_repo_id = Some(first_id);
 687        }
 688
 689        match event {
 690            WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
 691                cx.emit(GitEvent::GitStateUpdated);
 692            }
 693            WorktreeStoreEvent::WorktreeAdded(worktree) => {
 694                if self.is_local() {
 695                    cx.subscribe(worktree, Self::on_worktree_event).detach();
 696                }
 697            }
 698            _ => {
 699                cx.emit(GitEvent::FileSystemUpdated);
 700            }
 701        }
 702    }
 703
 704    fn on_worktree_event(
 705        &mut self,
 706        worktree: Entity<Worktree>,
 707        event: &worktree::Event,
 708        cx: &mut Context<Self>,
 709    ) {
 710        if let worktree::Event::UpdatedGitRepositories(changed_repos) = event {
 711            self.local_worktree_git_repos_changed(worktree, changed_repos, cx);
 712        }
 713    }
 714
 715    fn on_buffer_store_event(
 716        &mut self,
 717        _: Entity<BufferStore>,
 718        event: &BufferStoreEvent,
 719        cx: &mut Context<Self>,
 720    ) {
 721        match event {
 722            BufferStoreEvent::BufferAdded(buffer) => {
 723                cx.subscribe(&buffer, |this, buffer, event, cx| {
 724                    if let BufferEvent::LanguageChanged = event {
 725                        let buffer_id = buffer.read(cx).remote_id();
 726                        if let Some(diff_state) = this.diffs.get(&buffer_id) {
 727                            diff_state.update(cx, |diff_state, cx| {
 728                                diff_state.buffer_language_changed(buffer, cx);
 729                            });
 730                        }
 731                    }
 732                })
 733                .detach();
 734            }
 735            BufferStoreEvent::SharedBufferClosed(peer_id, buffer_id) => {
 736                if let Some(diffs) = self.shared_diffs.get_mut(peer_id) {
 737                    diffs.remove(buffer_id);
 738                }
 739            }
 740            BufferStoreEvent::BufferDropped(buffer_id) => {
 741                self.diffs.remove(&buffer_id);
 742                for diffs in self.shared_diffs.values_mut() {
 743                    diffs.remove(buffer_id);
 744                }
 745            }
 746
 747            _ => {}
 748        }
 749    }
 750
 751    pub fn recalculate_buffer_diffs(
 752        &mut self,
 753        buffers: Vec<Entity<Buffer>>,
 754        cx: &mut Context<Self>,
 755    ) -> impl Future<Output = ()> {
 756        let mut futures = Vec::new();
 757        for buffer in buffers {
 758            if let Some(diff_state) = self.diffs.get_mut(&buffer.read(cx).remote_id()) {
 759                let buffer = buffer.read(cx).text_snapshot();
 760                futures.push(diff_state.update(cx, |diff_state, cx| {
 761                    diff_state.recalculate_diffs(buffer, cx)
 762                }));
 763            }
 764        }
 765        async move {
 766            futures::future::join_all(futures).await;
 767        }
 768    }
 769
 770    fn on_buffer_diff_event(
 771        &mut self,
 772        diff: Entity<buffer_diff::BufferDiff>,
 773        event: &BufferDiffEvent,
 774        cx: &mut Context<Self>,
 775    ) {
 776        if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
 777            let buffer_id = diff.read(cx).buffer_id;
 778            if let Some((repo, path)) = self.repository_and_path_for_buffer_id(buffer_id, cx) {
 779                let recv = repo.update(cx, |repo, cx| {
 780                    log::debug!("updating index text for buffer {}", path.display());
 781                    repo.spawn_set_index_text_job(
 782                        path,
 783                        new_index_text.as_ref().map(|rope| rope.to_string()),
 784                        cx,
 785                    )
 786                });
 787                let diff = diff.downgrade();
 788                cx.spawn(async move |this, cx| {
 789                    if let Ok(Err(error)) = cx.background_spawn(recv).await {
 790                        diff.update(cx, |diff, cx| {
 791                            diff.clear_pending_hunks(cx);
 792                        })
 793                        .ok();
 794                        this.update(cx, |_, cx| cx.emit(GitEvent::IndexWriteError(error)))
 795                            .ok();
 796                    }
 797                })
 798                .detach();
 799            }
 800        }
 801    }
 802
 803    fn local_worktree_git_repos_changed(
 804        &mut self,
 805        worktree: Entity<Worktree>,
 806        changed_repos: &UpdatedGitRepositoriesSet,
 807        cx: &mut Context<Self>,
 808    ) {
 809        debug_assert!(worktree.read(cx).is_local());
 810
 811        let Some(active_repo) = self.active_repository() else {
 812            log::error!("local worktree changed but we have no active repository");
 813            return;
 814        };
 815
 816        let mut diff_state_updates = HashMap::<ProjectEntryId, Vec<_>>::default();
 817        for (buffer_id, diff_state) in &self.diffs {
 818            let Some(buffer) = self.buffer_store.read(cx).get(*buffer_id) else {
 819                continue;
 820            };
 821            let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
 822                continue;
 823            };
 824            if file.worktree != worktree {
 825                continue;
 826            }
 827            let Some(repo_id) = changed_repos
 828                .iter()
 829                .map(|(entry, _)| entry.id)
 830                .find(|repo_id| self.repositories().contains_key(&repo_id))
 831            else {
 832                continue;
 833            };
 834
 835            let diff_state = diff_state.read(cx);
 836            let has_unstaged_diff = diff_state
 837                .unstaged_diff
 838                .as_ref()
 839                .is_some_and(|diff| diff.is_upgradable());
 840            let has_uncommitted_diff = diff_state
 841                .uncommitted_diff
 842                .as_ref()
 843                .is_some_and(|set| set.is_upgradable());
 844
 845            let update = (
 846                buffer,
 847                file.path.clone(),
 848                has_unstaged_diff.then(|| diff_state.index_text.clone()),
 849                has_uncommitted_diff.then(|| diff_state.head_text.clone()),
 850            );
 851            diff_state_updates.entry(repo_id).or_default().push(update);
 852        }
 853
 854        if diff_state_updates.is_empty() {
 855            return;
 856        }
 857
 858        for (repo_id, repo_diff_state_updates) in diff_state_updates.into_iter() {
 859            let worktree = worktree.downgrade();
 860            let git_store = cx.weak_entity();
 861
 862            let _ = active_repo.read(cx).send_keyed_job(
 863                Some(GitJobKey::BatchReadIndex(repo_id)),
 864                |_, mut cx| async move {
 865                    let snapshot = worktree.update(&mut cx, |tree, _| {
 866                        tree.as_local().map(|local_tree| local_tree.snapshot())
 867                    });
 868                    let Ok(Some(snapshot)) = snapshot else {
 869                        return;
 870                    };
 871
 872                    let mut diff_bases_changes_by_buffer = Vec::new();
 873                    for (buffer, path, current_index_text, current_head_text) in
 874                        &repo_diff_state_updates
 875                    {
 876                        let Some(local_repo) = snapshot.local_repo_for_path(&path) else {
 877                            continue;
 878                        };
 879                        let Some(relative_path) = local_repo.relativize(&path).ok() else {
 880                            continue;
 881                        };
 882
 883                        log::debug!("reloading git state for buffer {}", path.display());
 884                        let index_text = if current_index_text.is_some() {
 885                            local_repo
 886                                .repo()
 887                                .load_index_text(relative_path.clone(), cx.clone())
 888                                .await
 889                        } else {
 890                            None
 891                        };
 892                        let head_text = if current_head_text.is_some() {
 893                            local_repo
 894                                .repo()
 895                                .load_committed_text(relative_path, cx.clone())
 896                                .await
 897                        } else {
 898                            None
 899                        };
 900
 901                        // Avoid triggering a diff update if the base text has not changed.
 902                        if let Some((current_index, current_head)) =
 903                            current_index_text.as_ref().zip(current_head_text.as_ref())
 904                        {
 905                            if current_index.as_deref() == index_text.as_ref()
 906                                && current_head.as_deref() == head_text.as_ref()
 907                            {
 908                                continue;
 909                            }
 910                        }
 911
 912                        let diff_bases_change =
 913                            match (current_index_text.is_some(), current_head_text.is_some()) {
 914                                (true, true) => Some(if index_text == head_text {
 915                                    DiffBasesChange::SetBoth(head_text)
 916                                } else {
 917                                    DiffBasesChange::SetEach {
 918                                        index: index_text,
 919                                        head: head_text,
 920                                    }
 921                                }),
 922                                (true, false) => Some(DiffBasesChange::SetIndex(index_text)),
 923                                (false, true) => Some(DiffBasesChange::SetHead(head_text)),
 924                                (false, false) => None,
 925                            };
 926
 927                        diff_bases_changes_by_buffer.push((buffer, diff_bases_change))
 928                    }
 929
 930                    git_store
 931                        .update(&mut cx, |git_store, cx| {
 932                            for (buffer, diff_bases_change) in diff_bases_changes_by_buffer {
 933                                let Some(diff_state) =
 934                                    git_store.diffs.get(&buffer.read(cx).remote_id())
 935                                else {
 936                                    continue;
 937                                };
 938                                let Some(diff_bases_change) = diff_bases_change else {
 939                                    continue;
 940                                };
 941
 942                                let downstream_client = git_store.downstream_client();
 943                                diff_state.update(cx, |diff_state, cx| {
 944                                    use proto::update_diff_bases::Mode;
 945
 946                                    let buffer = buffer.read(cx);
 947                                    if let Some((client, project_id)) = downstream_client {
 948                                        let (staged_text, committed_text, mode) =
 949                                            match diff_bases_change.clone() {
 950                                                DiffBasesChange::SetIndex(index) => {
 951                                                    (index, None, Mode::IndexOnly)
 952                                                }
 953                                                DiffBasesChange::SetHead(head) => {
 954                                                    (None, head, Mode::HeadOnly)
 955                                                }
 956                                                DiffBasesChange::SetEach { index, head } => {
 957                                                    (index, head, Mode::IndexAndHead)
 958                                                }
 959                                                DiffBasesChange::SetBoth(text) => {
 960                                                    (None, text, Mode::IndexMatchesHead)
 961                                                }
 962                                            };
 963                                        let message = proto::UpdateDiffBases {
 964                                            project_id: project_id.to_proto(),
 965                                            buffer_id: buffer.remote_id().to_proto(),
 966                                            staged_text,
 967                                            committed_text,
 968                                            mode: mode as i32,
 969                                        };
 970
 971                                        client.send(message).log_err();
 972                                    }
 973
 974                                    let _ = diff_state.diff_bases_changed(
 975                                        buffer.text_snapshot(),
 976                                        diff_bases_change,
 977                                        cx,
 978                                    );
 979                                });
 980                            }
 981                        })
 982                        .ok();
 983                },
 984            );
 985        }
 986    }
 987
 988    pub fn repositories(&self) -> &HashMap<ProjectEntryId, Entity<Repository>> {
 989        &self.repositories
 990    }
 991
 992    pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
 993        let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
 994        let status = repo.read(cx).repository_entry.status_for_path(&path)?;
 995        Some(status.status)
 996    }
 997
 998    fn repository_and_path_for_buffer_id(
 999        &self,
1000        buffer_id: BufferId,
1001        cx: &App,
1002    ) -> Option<(Entity<Repository>, RepoPath)> {
1003        let buffer = self.buffer_store.read(cx).get(buffer_id)?;
1004        let path = buffer.read(cx).project_path(cx)?;
1005        let mut result: Option<(Entity<Repository>, RepoPath)> = None;
1006        for repo_handle in self.repositories.values() {
1007            let repo = repo_handle.read(cx);
1008            if repo.worktree_id == path.worktree_id {
1009                if let Ok(relative_path) = repo.repository_entry.relativize(&path.path) {
1010                    if result
1011                        .as_ref()
1012                        .is_none_or(|(result, _)| !repo.contains_sub_repo(result, cx))
1013                    {
1014                        result = Some((repo_handle.clone(), relative_path))
1015                    }
1016                }
1017            }
1018        }
1019        result
1020    }
1021
1022    fn spawn_git_worker(cx: &mut Context<GitStore>) -> mpsc::UnboundedSender<GitJob> {
1023        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
1024
1025        cx.spawn(async move |_, cx| {
1026            let mut jobs = VecDeque::new();
1027            loop {
1028                while let Ok(Some(next_job)) = job_rx.try_next() {
1029                    jobs.push_back(next_job);
1030                }
1031
1032                if let Some(job) = jobs.pop_front() {
1033                    if let Some(current_key) = &job.key {
1034                        if jobs
1035                            .iter()
1036                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
1037                        {
1038                            continue;
1039                        }
1040                    }
1041                    (job.job)(cx).await;
1042                } else if let Some(job) = job_rx.next().await {
1043                    jobs.push_back(job);
1044                } else {
1045                    break;
1046                }
1047            }
1048        })
1049        .detach();
1050        job_tx
1051    }
1052
1053    pub fn git_init(
1054        &self,
1055        path: Arc<Path>,
1056        fallback_branch_name: String,
1057        cx: &App,
1058    ) -> Task<Result<()>> {
1059        match &self.state {
1060            GitStoreState::Local { fs, .. } => {
1061                let fs = fs.clone();
1062                cx.background_executor()
1063                    .spawn(async move { fs.git_init(&path, fallback_branch_name) })
1064            }
1065            GitStoreState::Ssh {
1066                upstream_client,
1067                upstream_project_id: project_id,
1068                ..
1069            }
1070            | GitStoreState::Remote {
1071                upstream_client,
1072                project_id,
1073                ..
1074            } => {
1075                let client = upstream_client.clone();
1076                let project_id = *project_id;
1077                cx.background_executor().spawn(async move {
1078                    client
1079                        .request(proto::GitInit {
1080                            project_id: project_id.0,
1081                            abs_path: path.to_string_lossy().to_string(),
1082                            fallback_branch_name,
1083                        })
1084                        .await?;
1085                    Ok(())
1086                })
1087            }
1088        }
1089    }
1090
1091    async fn handle_git_init(
1092        this: Entity<Self>,
1093        envelope: TypedEnvelope<proto::GitInit>,
1094        cx: AsyncApp,
1095    ) -> Result<proto::Ack> {
1096        let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
1097        let name = envelope.payload.fallback_branch_name;
1098        cx.update(|cx| this.read(cx).git_init(path, name, cx))?
1099            .await?;
1100
1101        Ok(proto::Ack {})
1102    }
1103
1104    async fn handle_fetch(
1105        this: Entity<Self>,
1106        envelope: TypedEnvelope<proto::Fetch>,
1107        mut cx: AsyncApp,
1108    ) -> Result<proto::RemoteMessageResponse> {
1109        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1110        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1111        let repository_handle =
1112            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1113        let askpass_id = envelope.payload.askpass_id;
1114
1115        let askpass = make_remote_delegate(
1116            this,
1117            envelope.payload.project_id,
1118            worktree_id,
1119            work_directory_id,
1120            askpass_id,
1121            &mut cx,
1122        );
1123
1124        let remote_output = repository_handle
1125            .update(&mut cx, |repository_handle, cx| {
1126                repository_handle.fetch(askpass, cx)
1127            })?
1128            .await??;
1129
1130        Ok(proto::RemoteMessageResponse {
1131            stdout: remote_output.stdout,
1132            stderr: remote_output.stderr,
1133        })
1134    }
1135
1136    async fn handle_push(
1137        this: Entity<Self>,
1138        envelope: TypedEnvelope<proto::Push>,
1139        mut cx: AsyncApp,
1140    ) -> Result<proto::RemoteMessageResponse> {
1141        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1142        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1143        let repository_handle =
1144            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1145
1146        let askpass_id = envelope.payload.askpass_id;
1147        let askpass = make_remote_delegate(
1148            this,
1149            envelope.payload.project_id,
1150            worktree_id,
1151            work_directory_id,
1152            askpass_id,
1153            &mut cx,
1154        );
1155
1156        let options = envelope
1157            .payload
1158            .options
1159            .as_ref()
1160            .map(|_| match envelope.payload.options() {
1161                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
1162                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
1163            });
1164
1165        let branch_name = envelope.payload.branch_name.into();
1166        let remote_name = envelope.payload.remote_name.into();
1167
1168        let remote_output = repository_handle
1169            .update(&mut cx, |repository_handle, cx| {
1170                repository_handle.push(branch_name, remote_name, options, askpass, cx)
1171            })?
1172            .await??;
1173        Ok(proto::RemoteMessageResponse {
1174            stdout: remote_output.stdout,
1175            stderr: remote_output.stderr,
1176        })
1177    }
1178
1179    async fn handle_pull(
1180        this: Entity<Self>,
1181        envelope: TypedEnvelope<proto::Pull>,
1182        mut cx: AsyncApp,
1183    ) -> Result<proto::RemoteMessageResponse> {
1184        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1185        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1186        let repository_handle =
1187            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1188        let askpass_id = envelope.payload.askpass_id;
1189        let askpass = make_remote_delegate(
1190            this,
1191            envelope.payload.project_id,
1192            worktree_id,
1193            work_directory_id,
1194            askpass_id,
1195            &mut cx,
1196        );
1197
1198        let branch_name = envelope.payload.branch_name.into();
1199        let remote_name = envelope.payload.remote_name.into();
1200
1201        let remote_message = repository_handle
1202            .update(&mut cx, |repository_handle, cx| {
1203                repository_handle.pull(branch_name, remote_name, askpass, cx)
1204            })?
1205            .await??;
1206
1207        Ok(proto::RemoteMessageResponse {
1208            stdout: remote_message.stdout,
1209            stderr: remote_message.stderr,
1210        })
1211    }
1212
1213    async fn handle_stage(
1214        this: Entity<Self>,
1215        envelope: TypedEnvelope<proto::Stage>,
1216        mut cx: AsyncApp,
1217    ) -> Result<proto::Ack> {
1218        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1219        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1220        let repository_handle =
1221            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1222
1223        let entries = envelope
1224            .payload
1225            .paths
1226            .into_iter()
1227            .map(PathBuf::from)
1228            .map(RepoPath::new)
1229            .collect();
1230
1231        repository_handle
1232            .update(&mut cx, |repository_handle, cx| {
1233                repository_handle.stage_entries(entries, cx)
1234            })?
1235            .await?;
1236        Ok(proto::Ack {})
1237    }
1238
1239    async fn handle_unstage(
1240        this: Entity<Self>,
1241        envelope: TypedEnvelope<proto::Unstage>,
1242        mut cx: AsyncApp,
1243    ) -> Result<proto::Ack> {
1244        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1245        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1246        let repository_handle =
1247            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1248
1249        let entries = envelope
1250            .payload
1251            .paths
1252            .into_iter()
1253            .map(PathBuf::from)
1254            .map(RepoPath::new)
1255            .collect();
1256
1257        repository_handle
1258            .update(&mut cx, |repository_handle, cx| {
1259                repository_handle.unstage_entries(entries, cx)
1260            })?
1261            .await?;
1262
1263        Ok(proto::Ack {})
1264    }
1265
1266    async fn handle_set_index_text(
1267        this: Entity<Self>,
1268        envelope: TypedEnvelope<proto::SetIndexText>,
1269        mut cx: AsyncApp,
1270    ) -> Result<proto::Ack> {
1271        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1272        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1273        let repository_handle =
1274            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1275
1276        repository_handle
1277            .update(&mut cx, |repository_handle, cx| {
1278                repository_handle.spawn_set_index_text_job(
1279                    RepoPath::from_str(&envelope.payload.path),
1280                    envelope.payload.text,
1281                    cx,
1282                )
1283            })?
1284            .await??;
1285        Ok(proto::Ack {})
1286    }
1287
1288    async fn handle_commit(
1289        this: Entity<Self>,
1290        envelope: TypedEnvelope<proto::Commit>,
1291        mut cx: AsyncApp,
1292    ) -> Result<proto::Ack> {
1293        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1294        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1295        let repository_handle =
1296            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1297
1298        let message = SharedString::from(envelope.payload.message);
1299        let name = envelope.payload.name.map(SharedString::from);
1300        let email = envelope.payload.email.map(SharedString::from);
1301
1302        repository_handle
1303            .update(&mut cx, |repository_handle, cx| {
1304                repository_handle.commit(message, name.zip(email), cx)
1305            })?
1306            .await??;
1307        Ok(proto::Ack {})
1308    }
1309
1310    async fn handle_get_remotes(
1311        this: Entity<Self>,
1312        envelope: TypedEnvelope<proto::GetRemotes>,
1313        mut cx: AsyncApp,
1314    ) -> Result<proto::GetRemotesResponse> {
1315        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1316        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1317        let repository_handle =
1318            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1319
1320        let branch_name = envelope.payload.branch_name;
1321
1322        let remotes = repository_handle
1323            .update(&mut cx, |repository_handle, _| {
1324                repository_handle.get_remotes(branch_name)
1325            })?
1326            .await??;
1327
1328        Ok(proto::GetRemotesResponse {
1329            remotes: remotes
1330                .into_iter()
1331                .map(|remotes| proto::get_remotes_response::Remote {
1332                    name: remotes.name.to_string(),
1333                })
1334                .collect::<Vec<_>>(),
1335        })
1336    }
1337
1338    async fn handle_get_branches(
1339        this: Entity<Self>,
1340        envelope: TypedEnvelope<proto::GitGetBranches>,
1341        mut cx: AsyncApp,
1342    ) -> Result<proto::GitBranchesResponse> {
1343        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1344        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1345        let repository_handle =
1346            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1347
1348        let branches = repository_handle
1349            .update(&mut cx, |repository_handle, _| repository_handle.branches())?
1350            .await??;
1351
1352        Ok(proto::GitBranchesResponse {
1353            branches: branches
1354                .into_iter()
1355                .map(|branch| worktree::branch_to_proto(&branch))
1356                .collect::<Vec<_>>(),
1357        })
1358    }
1359    async fn handle_create_branch(
1360        this: Entity<Self>,
1361        envelope: TypedEnvelope<proto::GitCreateBranch>,
1362        mut cx: AsyncApp,
1363    ) -> Result<proto::Ack> {
1364        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1365        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1366        let repository_handle =
1367            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1368        let branch_name = envelope.payload.branch_name;
1369
1370        repository_handle
1371            .update(&mut cx, |repository_handle, _| {
1372                repository_handle.create_branch(branch_name)
1373            })?
1374            .await??;
1375
1376        Ok(proto::Ack {})
1377    }
1378
1379    async fn handle_change_branch(
1380        this: Entity<Self>,
1381        envelope: TypedEnvelope<proto::GitChangeBranch>,
1382        mut cx: AsyncApp,
1383    ) -> Result<proto::Ack> {
1384        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1385        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1386        let repository_handle =
1387            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1388        let branch_name = envelope.payload.branch_name;
1389
1390        repository_handle
1391            .update(&mut cx, |repository_handle, _| {
1392                repository_handle.change_branch(branch_name)
1393            })?
1394            .await??;
1395
1396        Ok(proto::Ack {})
1397    }
1398
1399    async fn handle_show(
1400        this: Entity<Self>,
1401        envelope: TypedEnvelope<proto::GitShow>,
1402        mut cx: AsyncApp,
1403    ) -> Result<proto::GitCommitDetails> {
1404        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1405        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1406        let repository_handle =
1407            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1408
1409        let commit = repository_handle
1410            .update(&mut cx, |repository_handle, _| {
1411                repository_handle.show(envelope.payload.commit)
1412            })?
1413            .await??;
1414        Ok(proto::GitCommitDetails {
1415            sha: commit.sha.into(),
1416            message: commit.message.into(),
1417            commit_timestamp: commit.commit_timestamp,
1418            committer_email: commit.committer_email.into(),
1419            committer_name: commit.committer_name.into(),
1420        })
1421    }
1422
1423    async fn handle_reset(
1424        this: Entity<Self>,
1425        envelope: TypedEnvelope<proto::GitReset>,
1426        mut cx: AsyncApp,
1427    ) -> Result<proto::Ack> {
1428        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1429        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1430        let repository_handle =
1431            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1432
1433        let mode = match envelope.payload.mode() {
1434            git_reset::ResetMode::Soft => ResetMode::Soft,
1435            git_reset::ResetMode::Mixed => ResetMode::Mixed,
1436        };
1437
1438        repository_handle
1439            .update(&mut cx, |repository_handle, cx| {
1440                repository_handle.reset(envelope.payload.commit, mode, cx)
1441            })?
1442            .await??;
1443        Ok(proto::Ack {})
1444    }
1445
1446    async fn handle_checkout_files(
1447        this: Entity<Self>,
1448        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
1449        mut cx: AsyncApp,
1450    ) -> Result<proto::Ack> {
1451        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1452        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1453        let repository_handle =
1454            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1455        let paths = envelope
1456            .payload
1457            .paths
1458            .iter()
1459            .map(|s| RepoPath::from_str(s))
1460            .collect();
1461
1462        repository_handle
1463            .update(&mut cx, |repository_handle, cx| {
1464                repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
1465            })?
1466            .await??;
1467        Ok(proto::Ack {})
1468    }
1469
1470    async fn handle_open_commit_message_buffer(
1471        this: Entity<Self>,
1472        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
1473        mut cx: AsyncApp,
1474    ) -> Result<proto::OpenBufferResponse> {
1475        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1476        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1477        let repository =
1478            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1479        let buffer = repository
1480            .update(&mut cx, |repository, cx| {
1481                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
1482            })?
1483            .await?;
1484
1485        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
1486        this.update(&mut cx, |this, cx| {
1487            this.buffer_store.update(cx, |buffer_store, cx| {
1488                buffer_store
1489                    .create_buffer_for_peer(
1490                        &buffer,
1491                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
1492                        cx,
1493                    )
1494                    .detach_and_log_err(cx);
1495            })
1496        })?;
1497
1498        Ok(proto::OpenBufferResponse {
1499            buffer_id: buffer_id.to_proto(),
1500        })
1501    }
1502
1503    async fn handle_askpass(
1504        this: Entity<Self>,
1505        envelope: TypedEnvelope<proto::AskPassRequest>,
1506        mut cx: AsyncApp,
1507    ) -> Result<proto::AskPassResponse> {
1508        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1509        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1510        let repository =
1511            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1512
1513        let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone())?;
1514        let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
1515            debug_panic!("no askpass found");
1516            return Err(anyhow::anyhow!("no askpass found"));
1517        };
1518
1519        let response = askpass.ask_password(envelope.payload.prompt).await?;
1520
1521        delegates
1522            .lock()
1523            .insert(envelope.payload.askpass_id, askpass);
1524
1525        Ok(proto::AskPassResponse { response })
1526    }
1527
1528    async fn handle_check_for_pushed_commits(
1529        this: Entity<Self>,
1530        envelope: TypedEnvelope<proto::CheckForPushedCommits>,
1531        mut cx: AsyncApp,
1532    ) -> Result<proto::CheckForPushedCommitsResponse> {
1533        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1534        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1535        let repository_handle =
1536            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1537
1538        let branches = repository_handle
1539            .update(&mut cx, |repository_handle, _| {
1540                repository_handle.check_for_pushed_commits()
1541            })?
1542            .await??;
1543        Ok(proto::CheckForPushedCommitsResponse {
1544            pushed_to: branches
1545                .into_iter()
1546                .map(|commit| commit.to_string())
1547                .collect(),
1548        })
1549    }
1550
1551    async fn handle_git_diff(
1552        this: Entity<Self>,
1553        envelope: TypedEnvelope<proto::GitDiff>,
1554        mut cx: AsyncApp,
1555    ) -> Result<proto::GitDiffResponse> {
1556        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1557        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1558        let repository_handle =
1559            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1560        let diff_type = match envelope.payload.diff_type() {
1561            proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
1562            proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
1563        };
1564
1565        let mut diff = repository_handle
1566            .update(&mut cx, |repository_handle, cx| {
1567                repository_handle.diff(diff_type, cx)
1568            })?
1569            .await??;
1570        const ONE_MB: usize = 1_000_000;
1571        if diff.len() > ONE_MB {
1572            diff = diff.chars().take(ONE_MB).collect()
1573        }
1574
1575        Ok(proto::GitDiffResponse { diff })
1576    }
1577
1578    pub async fn handle_open_unstaged_diff(
1579        this: Entity<Self>,
1580        request: TypedEnvelope<proto::OpenUnstagedDiff>,
1581        mut cx: AsyncApp,
1582    ) -> Result<proto::OpenUnstagedDiffResponse> {
1583        let buffer_id = BufferId::new(request.payload.buffer_id)?;
1584        let diff = this
1585            .update(&mut cx, |this, cx| {
1586                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
1587                Some(this.open_unstaged_diff(buffer, cx))
1588            })?
1589            .ok_or_else(|| anyhow!("no such buffer"))?
1590            .await?;
1591        this.update(&mut cx, |this, _| {
1592            let shared_diffs = this
1593                .shared_diffs
1594                .entry(request.original_sender_id.unwrap_or(request.sender_id))
1595                .or_default();
1596            shared_diffs.entry(buffer_id).or_default().unstaged = Some(diff.clone());
1597        })?;
1598        let staged_text = diff.read_with(&cx, |diff, _| diff.base_text_string())?;
1599        Ok(proto::OpenUnstagedDiffResponse { staged_text })
1600    }
1601
1602    pub async fn handle_open_uncommitted_diff(
1603        this: Entity<Self>,
1604        request: TypedEnvelope<proto::OpenUncommittedDiff>,
1605        mut cx: AsyncApp,
1606    ) -> Result<proto::OpenUncommittedDiffResponse> {
1607        let buffer_id = BufferId::new(request.payload.buffer_id)?;
1608        let diff = this
1609            .update(&mut cx, |this, cx| {
1610                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
1611                Some(this.open_uncommitted_diff(buffer, cx))
1612            })?
1613            .ok_or_else(|| anyhow!("no such buffer"))?
1614            .await?;
1615        this.update(&mut cx, |this, _| {
1616            let shared_diffs = this
1617                .shared_diffs
1618                .entry(request.original_sender_id.unwrap_or(request.sender_id))
1619                .or_default();
1620            shared_diffs.entry(buffer_id).or_default().uncommitted = Some(diff.clone());
1621        })?;
1622        diff.read_with(&cx, |diff, cx| {
1623            use proto::open_uncommitted_diff_response::Mode;
1624
1625            let unstaged_diff = diff.secondary_diff();
1626            let index_snapshot = unstaged_diff.and_then(|diff| {
1627                let diff = diff.read(cx);
1628                diff.base_text_exists().then(|| diff.base_text())
1629            });
1630
1631            let mode;
1632            let staged_text;
1633            let committed_text;
1634            if diff.base_text_exists() {
1635                let committed_snapshot = diff.base_text();
1636                committed_text = Some(committed_snapshot.text());
1637                if let Some(index_text) = index_snapshot {
1638                    if index_text.remote_id() == committed_snapshot.remote_id() {
1639                        mode = Mode::IndexMatchesHead;
1640                        staged_text = None;
1641                    } else {
1642                        mode = Mode::IndexAndHead;
1643                        staged_text = Some(index_text.text());
1644                    }
1645                } else {
1646                    mode = Mode::IndexAndHead;
1647                    staged_text = None;
1648                }
1649            } else {
1650                mode = Mode::IndexAndHead;
1651                committed_text = None;
1652                staged_text = index_snapshot.as_ref().map(|buffer| buffer.text());
1653            }
1654
1655            proto::OpenUncommittedDiffResponse {
1656                committed_text,
1657                staged_text,
1658                mode: mode.into(),
1659            }
1660        })
1661    }
1662
1663    pub async fn handle_update_diff_bases(
1664        this: Entity<Self>,
1665        request: TypedEnvelope<proto::UpdateDiffBases>,
1666        mut cx: AsyncApp,
1667    ) -> Result<()> {
1668        let buffer_id = BufferId::new(request.payload.buffer_id)?;
1669        this.update(&mut cx, |this, cx| {
1670            if let Some(diff_state) = this.diffs.get_mut(&buffer_id) {
1671                if let Some(buffer) = this.buffer_store.read(cx).get(buffer_id) {
1672                    let buffer = buffer.read(cx).text_snapshot();
1673                    diff_state.update(cx, |diff_state, cx| {
1674                        diff_state.handle_base_texts_updated(buffer, request.payload, cx);
1675                    })
1676                }
1677            }
1678        })
1679    }
1680
1681    fn repository_for_request(
1682        this: &Entity<Self>,
1683        worktree_id: WorktreeId,
1684        work_directory_id: ProjectEntryId,
1685        cx: &mut AsyncApp,
1686    ) -> Result<Entity<Repository>> {
1687        this.update(cx, |this, cx| {
1688            this.repositories
1689                .values()
1690                .find(|repository_handle| {
1691                    repository_handle.read(cx).worktree_id == worktree_id
1692                        && repository_handle
1693                            .read(cx)
1694                            .repository_entry
1695                            .work_directory_id()
1696                            == work_directory_id
1697                })
1698                .context("missing repository handle")
1699                .cloned()
1700        })?
1701    }
1702}
1703
1704impl BufferDiffState {
1705    fn buffer_language_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
1706        self.language = buffer.read(cx).language().cloned();
1707        self.language_changed = true;
1708        let _ = self.recalculate_diffs(buffer.read(cx).text_snapshot(), cx);
1709    }
1710
1711    fn unstaged_diff(&self) -> Option<Entity<BufferDiff>> {
1712        self.unstaged_diff.as_ref().and_then(|set| set.upgrade())
1713    }
1714
1715    fn uncommitted_diff(&self) -> Option<Entity<BufferDiff>> {
1716        self.uncommitted_diff.as_ref().and_then(|set| set.upgrade())
1717    }
1718
1719    fn handle_base_texts_updated(
1720        &mut self,
1721        buffer: text::BufferSnapshot,
1722        message: proto::UpdateDiffBases,
1723        cx: &mut Context<Self>,
1724    ) {
1725        use proto::update_diff_bases::Mode;
1726
1727        let Some(mode) = Mode::from_i32(message.mode) else {
1728            return;
1729        };
1730
1731        let diff_bases_change = match mode {
1732            Mode::HeadOnly => DiffBasesChange::SetHead(message.committed_text),
1733            Mode::IndexOnly => DiffBasesChange::SetIndex(message.staged_text),
1734            Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.committed_text),
1735            Mode::IndexAndHead => DiffBasesChange::SetEach {
1736                index: message.staged_text,
1737                head: message.committed_text,
1738            },
1739        };
1740
1741        let _ = self.diff_bases_changed(buffer, diff_bases_change, cx);
1742    }
1743
1744    pub fn wait_for_recalculation(&mut self) -> Option<oneshot::Receiver<()>> {
1745        if self.diff_updated_futures.is_empty() {
1746            return None;
1747        }
1748        let (tx, rx) = oneshot::channel();
1749        self.diff_updated_futures.push(tx);
1750        Some(rx)
1751    }
1752
1753    fn diff_bases_changed(
1754        &mut self,
1755        buffer: text::BufferSnapshot,
1756        diff_bases_change: DiffBasesChange,
1757        cx: &mut Context<Self>,
1758    ) -> oneshot::Receiver<()> {
1759        match diff_bases_change {
1760            DiffBasesChange::SetIndex(index) => {
1761                self.index_text = index.map(|mut index| {
1762                    text::LineEnding::normalize(&mut index);
1763                    Arc::new(index)
1764                });
1765                self.index_changed = true;
1766            }
1767            DiffBasesChange::SetHead(head) => {
1768                self.head_text = head.map(|mut head| {
1769                    text::LineEnding::normalize(&mut head);
1770                    Arc::new(head)
1771                });
1772                self.head_changed = true;
1773            }
1774            DiffBasesChange::SetBoth(text) => {
1775                let text = text.map(|mut text| {
1776                    text::LineEnding::normalize(&mut text);
1777                    Arc::new(text)
1778                });
1779                self.head_text = text.clone();
1780                self.index_text = text;
1781                self.head_changed = true;
1782                self.index_changed = true;
1783            }
1784            DiffBasesChange::SetEach { index, head } => {
1785                self.index_text = index.map(|mut index| {
1786                    text::LineEnding::normalize(&mut index);
1787                    Arc::new(index)
1788                });
1789                self.index_changed = true;
1790                self.head_text = head.map(|mut head| {
1791                    text::LineEnding::normalize(&mut head);
1792                    Arc::new(head)
1793                });
1794                self.head_changed = true;
1795            }
1796        }
1797
1798        self.recalculate_diffs(buffer, cx)
1799    }
1800
1801    fn recalculate_diffs(
1802        &mut self,
1803        buffer: text::BufferSnapshot,
1804        cx: &mut Context<Self>,
1805    ) -> oneshot::Receiver<()> {
1806        log::debug!("recalculate diffs");
1807        let (tx, rx) = oneshot::channel();
1808        self.diff_updated_futures.push(tx);
1809
1810        let language = self.language.clone();
1811        let language_registry = self.language_registry.clone();
1812        let unstaged_diff = self.unstaged_diff();
1813        let uncommitted_diff = self.uncommitted_diff();
1814        let head = self.head_text.clone();
1815        let index = self.index_text.clone();
1816        let index_changed = self.index_changed;
1817        let head_changed = self.head_changed;
1818        let language_changed = self.language_changed;
1819        let index_matches_head = match (self.index_text.as_ref(), self.head_text.as_ref()) {
1820            (Some(index), Some(head)) => Arc::ptr_eq(index, head),
1821            (None, None) => true,
1822            _ => false,
1823        };
1824        self.recalculate_diff_task = Some(cx.spawn(async move |this, cx| {
1825            let mut new_unstaged_diff = None;
1826            if let Some(unstaged_diff) = &unstaged_diff {
1827                new_unstaged_diff = Some(
1828                    BufferDiff::update_diff(
1829                        unstaged_diff.clone(),
1830                        buffer.clone(),
1831                        index,
1832                        index_changed,
1833                        language_changed,
1834                        language.clone(),
1835                        language_registry.clone(),
1836                        cx,
1837                    )
1838                    .await?,
1839                );
1840            }
1841
1842            let mut new_uncommitted_diff = None;
1843            if let Some(uncommitted_diff) = &uncommitted_diff {
1844                new_uncommitted_diff = if index_matches_head {
1845                    new_unstaged_diff.clone()
1846                } else {
1847                    Some(
1848                        BufferDiff::update_diff(
1849                            uncommitted_diff.clone(),
1850                            buffer.clone(),
1851                            head,
1852                            head_changed,
1853                            language_changed,
1854                            language.clone(),
1855                            language_registry.clone(),
1856                            cx,
1857                        )
1858                        .await?,
1859                    )
1860                }
1861            }
1862
1863            let unstaged_changed_range = if let Some((unstaged_diff, new_unstaged_diff)) =
1864                unstaged_diff.as_ref().zip(new_unstaged_diff.clone())
1865            {
1866                unstaged_diff.update(cx, |diff, cx| {
1867                    diff.set_snapshot(&buffer, new_unstaged_diff, language_changed, None, cx)
1868                })?
1869            } else {
1870                None
1871            };
1872
1873            if let Some((uncommitted_diff, new_uncommitted_diff)) =
1874                uncommitted_diff.as_ref().zip(new_uncommitted_diff.clone())
1875            {
1876                uncommitted_diff.update(cx, |uncommitted_diff, cx| {
1877                    uncommitted_diff.set_snapshot(
1878                        &buffer,
1879                        new_uncommitted_diff,
1880                        language_changed,
1881                        unstaged_changed_range,
1882                        cx,
1883                    );
1884                })?;
1885            }
1886
1887            if let Some(this) = this.upgrade() {
1888                this.update(cx, |this, _| {
1889                    this.index_changed = false;
1890                    this.head_changed = false;
1891                    this.language_changed = false;
1892                    for tx in this.diff_updated_futures.drain(..) {
1893                        tx.send(()).ok();
1894                    }
1895                })?;
1896            }
1897
1898            Ok(())
1899        }));
1900
1901        rx
1902    }
1903}
1904
1905fn make_remote_delegate(
1906    this: Entity<GitStore>,
1907    project_id: u64,
1908    worktree_id: WorktreeId,
1909    work_directory_id: ProjectEntryId,
1910    askpass_id: u64,
1911    cx: &mut AsyncApp,
1912) -> AskPassDelegate {
1913    AskPassDelegate::new(cx, move |prompt, tx, cx| {
1914        this.update(cx, |this, cx| {
1915            let Some((client, _)) = this.downstream_client() else {
1916                return;
1917            };
1918            let response = client.request(proto::AskPassRequest {
1919                project_id,
1920                worktree_id: worktree_id.to_proto(),
1921                work_directory_id: work_directory_id.to_proto(),
1922                askpass_id,
1923                prompt,
1924            });
1925            cx.spawn(async move |_, _| {
1926                tx.send(response.await?.response).ok();
1927                anyhow::Ok(())
1928            })
1929            .detach_and_log_err(cx);
1930        })
1931        .log_err();
1932    })
1933}
1934
1935impl GitStoreState {
1936    fn load_staged_text(
1937        &self,
1938        buffer: &Entity<Buffer>,
1939        buffer_store: &Entity<BufferStore>,
1940        cx: &App,
1941    ) -> Task<Result<Option<String>>> {
1942        match self {
1943            GitStoreState::Local { .. } => {
1944                if let Some((worktree, path)) =
1945                    buffer_store.read(cx).worktree_for_buffer(buffer, cx)
1946                {
1947                    worktree.read(cx).load_staged_file(path.as_ref(), cx)
1948                } else {
1949                    return Task::ready(Err(anyhow!("no such worktree")));
1950                }
1951            }
1952            GitStoreState::Ssh {
1953                upstream_client,
1954                upstream_project_id: project_id,
1955                ..
1956            }
1957            | GitStoreState::Remote {
1958                upstream_client,
1959                project_id,
1960            } => {
1961                let buffer_id = buffer.read(cx).remote_id();
1962                let project_id = *project_id;
1963                let client = upstream_client.clone();
1964                cx.background_spawn(async move {
1965                    let response = client
1966                        .request(proto::OpenUnstagedDiff {
1967                            project_id: project_id.to_proto(),
1968                            buffer_id: buffer_id.to_proto(),
1969                        })
1970                        .await?;
1971                    Ok(response.staged_text)
1972                })
1973            }
1974        }
1975    }
1976
1977    fn load_committed_text(
1978        &self,
1979        buffer: &Entity<Buffer>,
1980        buffer_store: &Entity<BufferStore>,
1981        cx: &App,
1982    ) -> Task<Result<DiffBasesChange>> {
1983        match self {
1984            GitStoreState::Local { .. } => {
1985                if let Some((worktree, path)) =
1986                    buffer_store.read(cx).worktree_for_buffer(buffer, cx)
1987                {
1988                    let worktree = worktree.read(cx);
1989                    let committed_text = worktree.load_committed_file(&path, cx);
1990                    let staged_text = worktree.load_staged_file(&path, cx);
1991                    cx.background_spawn(async move {
1992                        let committed_text = committed_text.await?;
1993                        let staged_text = staged_text.await?;
1994                        let diff_bases_change = if committed_text == staged_text {
1995                            DiffBasesChange::SetBoth(committed_text)
1996                        } else {
1997                            DiffBasesChange::SetEach {
1998                                index: staged_text,
1999                                head: committed_text,
2000                            }
2001                        };
2002                        Ok(diff_bases_change)
2003                    })
2004                } else {
2005                    Task::ready(Err(anyhow!("no such worktree")))
2006                }
2007            }
2008            GitStoreState::Ssh {
2009                upstream_client,
2010                upstream_project_id: project_id,
2011                ..
2012            }
2013            | GitStoreState::Remote {
2014                upstream_client,
2015                project_id,
2016            } => {
2017                use proto::open_uncommitted_diff_response::Mode;
2018
2019                let buffer_id = buffer.read(cx).remote_id();
2020                let project_id = *project_id;
2021                let client = upstream_client.clone();
2022                cx.background_spawn(async move {
2023                    let response = client
2024                        .request(proto::OpenUncommittedDiff {
2025                            project_id: project_id.to_proto(),
2026                            buffer_id: buffer_id.to_proto(),
2027                        })
2028                        .await?;
2029                    let mode =
2030                        Mode::from_i32(response.mode).ok_or_else(|| anyhow!("Invalid mode"))?;
2031                    let bases = match mode {
2032                        Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
2033                        Mode::IndexAndHead => DiffBasesChange::SetEach {
2034                            head: response.committed_text,
2035                            index: response.staged_text,
2036                        },
2037                    };
2038                    Ok(bases)
2039                })
2040            }
2041        }
2042    }
2043}
2044
2045impl Repository {
2046    pub fn git_store(&self) -> Option<Entity<GitStore>> {
2047        self.git_store.upgrade()
2048    }
2049
2050    fn id(&self) -> (WorktreeId, ProjectEntryId) {
2051        (self.worktree_id, self.repository_entry.work_directory_id())
2052    }
2053
2054    pub fn current_branch(&self) -> Option<&Branch> {
2055        self.repository_entry.branch()
2056    }
2057
2058    fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
2059    where
2060        F: FnOnce(GitRepo, AsyncApp) -> Fut + 'static,
2061        Fut: Future<Output = R> + 'static,
2062        R: Send + 'static,
2063    {
2064        self.send_keyed_job(None, job)
2065    }
2066
2067    fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
2068    where
2069        F: FnOnce(GitRepo, AsyncApp) -> Fut + 'static,
2070        Fut: Future<Output = R> + 'static,
2071        R: Send + 'static,
2072    {
2073        let (result_tx, result_rx) = futures::channel::oneshot::channel();
2074        let git_repo = self.git_repo.clone();
2075        self.job_sender
2076            .unbounded_send(GitJob {
2077                key,
2078                job: Box::new(|cx: &mut AsyncApp| {
2079                    let job = job(git_repo, cx.clone());
2080                    cx.spawn(async move |_| {
2081                        let result = job.await;
2082                        result_tx.send(result).ok();
2083                    })
2084                }),
2085            })
2086            .ok();
2087        result_rx
2088    }
2089
2090    pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
2091        maybe!({
2092            let project_path = self.repo_path_to_project_path(&"".into())?;
2093            let worktree_name = project
2094                .worktree_for_id(project_path.worktree_id, cx)?
2095                .read(cx)
2096                .root_name();
2097
2098            let mut path = PathBuf::new();
2099            path = path.join(worktree_name);
2100            if project_path.path.components().count() > 0 {
2101                path = path.join(project_path.path);
2102            }
2103            Some(path.to_string_lossy().to_string())
2104        })
2105        .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
2106        .into()
2107    }
2108
2109    pub fn set_as_active_repository(&self, cx: &mut Context<Self>) {
2110        let Some(git_store) = self.git_store.upgrade() else {
2111            return;
2112        };
2113        let entity = cx.entity();
2114        git_store.update(cx, |git_store, cx| {
2115            let Some((&id, _)) = git_store
2116                .repositories
2117                .iter()
2118                .find(|(_, handle)| *handle == &entity)
2119            else {
2120                return;
2121            };
2122            git_store.active_repo_id = Some(id);
2123            cx.emit(GitEvent::ActiveRepositoryChanged);
2124        });
2125    }
2126
2127    pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
2128        self.repository_entry.status()
2129    }
2130
2131    pub fn has_conflict(&self, path: &RepoPath) -> bool {
2132        self.repository_entry
2133            .current_merge_conflicts
2134            .contains(&path)
2135    }
2136
2137    pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
2138        let path = self.repository_entry.try_unrelativize(path)?;
2139        Some((self.worktree_id, path).into())
2140    }
2141
2142    pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
2143        self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
2144    }
2145
2146    // note: callers must verify these come from the same worktree
2147    pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
2148        let other_work_dir = &other.read(cx).repository_entry.work_directory;
2149        match (&self.repository_entry.work_directory, other_work_dir) {
2150            (WorkDirectory::InProject { .. }, WorkDirectory::AboveProject { .. }) => false,
2151            (WorkDirectory::AboveProject { .. }, WorkDirectory::InProject { .. }) => true,
2152            (
2153                WorkDirectory::InProject {
2154                    relative_path: this_path,
2155                },
2156                WorkDirectory::InProject {
2157                    relative_path: other_path,
2158                },
2159            ) => other_path.starts_with(this_path),
2160            (
2161                WorkDirectory::AboveProject {
2162                    absolute_path: this_path,
2163                    ..
2164                },
2165                WorkDirectory::AboveProject {
2166                    absolute_path: other_path,
2167                    ..
2168                },
2169            ) => other_path.starts_with(this_path),
2170        }
2171    }
2172
2173    pub fn worktree_id_path_to_repo_path(
2174        &self,
2175        worktree_id: WorktreeId,
2176        path: &Path,
2177    ) -> Option<RepoPath> {
2178        if worktree_id != self.worktree_id {
2179            return None;
2180        }
2181        self.repository_entry.relativize(path).log_err()
2182    }
2183
2184    pub fn open_commit_buffer(
2185        &mut self,
2186        languages: Option<Arc<LanguageRegistry>>,
2187        buffer_store: Entity<BufferStore>,
2188        cx: &mut Context<Self>,
2189    ) -> Task<Result<Entity<Buffer>>> {
2190        if let Some(buffer) = self.commit_message_buffer.clone() {
2191            return Task::ready(Ok(buffer));
2192        }
2193
2194        if let GitRepo::Remote {
2195            project_id,
2196            client,
2197            worktree_id,
2198            work_directory_id,
2199        } = self.git_repo.clone()
2200        {
2201            let client = client.clone();
2202            cx.spawn(async move |repository, cx| {
2203                let request = client.request(proto::OpenCommitMessageBuffer {
2204                    project_id: project_id.0,
2205                    worktree_id: worktree_id.to_proto(),
2206                    work_directory_id: work_directory_id.to_proto(),
2207                });
2208                let response = request.await.context("requesting to open commit buffer")?;
2209                let buffer_id = BufferId::new(response.buffer_id)?;
2210                let buffer = buffer_store
2211                    .update(cx, |buffer_store, cx| {
2212                        buffer_store.wait_for_remote_buffer(buffer_id, cx)
2213                    })?
2214                    .await?;
2215                if let Some(language_registry) = languages {
2216                    let git_commit_language =
2217                        language_registry.language_for_name("Git Commit").await?;
2218                    buffer.update(cx, |buffer, cx| {
2219                        buffer.set_language(Some(git_commit_language), cx);
2220                    })?;
2221                }
2222                repository.update(cx, |repository, _| {
2223                    repository.commit_message_buffer = Some(buffer.clone());
2224                })?;
2225                Ok(buffer)
2226            })
2227        } else {
2228            self.open_local_commit_buffer(languages, buffer_store, cx)
2229        }
2230    }
2231
2232    fn open_local_commit_buffer(
2233        &mut self,
2234        language_registry: Option<Arc<LanguageRegistry>>,
2235        buffer_store: Entity<BufferStore>,
2236        cx: &mut Context<Self>,
2237    ) -> Task<Result<Entity<Buffer>>> {
2238        cx.spawn(async move |repository, cx| {
2239            let buffer = buffer_store
2240                .update(cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
2241                .await?;
2242
2243            if let Some(language_registry) = language_registry {
2244                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
2245                buffer.update(cx, |buffer, cx| {
2246                    buffer.set_language(Some(git_commit_language), cx);
2247                })?;
2248            }
2249
2250            repository.update(cx, |repository, _| {
2251                repository.commit_message_buffer = Some(buffer.clone());
2252            })?;
2253            Ok(buffer)
2254        })
2255    }
2256
2257    pub fn checkout_files(
2258        &self,
2259        commit: &str,
2260        paths: Vec<RepoPath>,
2261        cx: &mut App,
2262    ) -> oneshot::Receiver<Result<()>> {
2263        let commit = commit.to_string();
2264        let env = self.worktree_environment(cx);
2265
2266        self.send_job(|git_repo, _| async move {
2267            match git_repo {
2268                GitRepo::Local(repo) => repo.checkout_files(commit, paths, env.await).await,
2269                GitRepo::Remote {
2270                    project_id,
2271                    client,
2272                    worktree_id,
2273                    work_directory_id,
2274                } => {
2275                    client
2276                        .request(proto::GitCheckoutFiles {
2277                            project_id: project_id.0,
2278                            worktree_id: worktree_id.to_proto(),
2279                            work_directory_id: work_directory_id.to_proto(),
2280                            commit,
2281                            paths: paths
2282                                .into_iter()
2283                                .map(|p| p.to_string_lossy().to_string())
2284                                .collect(),
2285                        })
2286                        .await?;
2287
2288                    Ok(())
2289                }
2290            }
2291        })
2292    }
2293
2294    pub fn reset(
2295        &self,
2296        commit: String,
2297        reset_mode: ResetMode,
2298        cx: &mut App,
2299    ) -> oneshot::Receiver<Result<()>> {
2300        let commit = commit.to_string();
2301        let env = self.worktree_environment(cx);
2302        self.send_job(|git_repo, _| async move {
2303            match git_repo {
2304                GitRepo::Local(git_repo) => {
2305                    let env = env.await;
2306                    git_repo.reset(commit, reset_mode, env).await
2307                }
2308                GitRepo::Remote {
2309                    project_id,
2310                    client,
2311                    worktree_id,
2312                    work_directory_id,
2313                } => {
2314                    client
2315                        .request(proto::GitReset {
2316                            project_id: project_id.0,
2317                            worktree_id: worktree_id.to_proto(),
2318                            work_directory_id: work_directory_id.to_proto(),
2319                            commit,
2320                            mode: match reset_mode {
2321                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
2322                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
2323                            },
2324                        })
2325                        .await?;
2326
2327                    Ok(())
2328                }
2329            }
2330        })
2331    }
2332
2333    pub fn show(&self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
2334        self.send_job(|git_repo, cx| async move {
2335            match git_repo {
2336                GitRepo::Local(git_repository) => git_repository.show(commit, cx).await,
2337                GitRepo::Remote {
2338                    project_id,
2339                    client,
2340                    worktree_id,
2341                    work_directory_id,
2342                } => {
2343                    let resp = client
2344                        .request(proto::GitShow {
2345                            project_id: project_id.0,
2346                            worktree_id: worktree_id.to_proto(),
2347                            work_directory_id: work_directory_id.to_proto(),
2348                            commit,
2349                        })
2350                        .await?;
2351
2352                    Ok(CommitDetails {
2353                        sha: resp.sha.into(),
2354                        message: resp.message.into(),
2355                        commit_timestamp: resp.commit_timestamp,
2356                        committer_email: resp.committer_email.into(),
2357                        committer_name: resp.committer_name.into(),
2358                    })
2359                }
2360            }
2361        })
2362    }
2363
2364    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
2365        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
2366    }
2367
2368    pub fn stage_entries(
2369        &self,
2370        entries: Vec<RepoPath>,
2371        cx: &mut Context<Self>,
2372    ) -> Task<anyhow::Result<()>> {
2373        if entries.is_empty() {
2374            return Task::ready(Ok(()));
2375        }
2376        let env = self.worktree_environment(cx);
2377
2378        let mut save_futures = Vec::new();
2379        if let Some(buffer_store) = self.buffer_store(cx) {
2380            buffer_store.update(cx, |buffer_store, cx| {
2381                for path in &entries {
2382                    let Some(path) = self.repository_entry.try_unrelativize(path) else {
2383                        continue;
2384                    };
2385                    let project_path = (self.worktree_id, path).into();
2386                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
2387                        if buffer
2388                            .read(cx)
2389                            .file()
2390                            .map_or(false, |file| file.disk_state().exists())
2391                        {
2392                            save_futures.push(buffer_store.save_buffer(buffer, cx));
2393                        }
2394                    }
2395                }
2396            })
2397        }
2398
2399        cx.spawn(async move |this, cx| {
2400            for save_future in save_futures {
2401                save_future.await?;
2402            }
2403            let env = env.await;
2404
2405            this.update(cx, |this, _| {
2406                this.send_job(|git_repo, cx| async move {
2407                    match git_repo {
2408                        GitRepo::Local(repo) => repo.stage_paths(entries, env, cx).await,
2409                        GitRepo::Remote {
2410                            project_id,
2411                            client,
2412                            worktree_id,
2413                            work_directory_id,
2414                        } => {
2415                            client
2416                                .request(proto::Stage {
2417                                    project_id: project_id.0,
2418                                    worktree_id: worktree_id.to_proto(),
2419                                    work_directory_id: work_directory_id.to_proto(),
2420                                    paths: entries
2421                                        .into_iter()
2422                                        .map(|repo_path| repo_path.as_ref().to_proto())
2423                                        .collect(),
2424                                })
2425                                .await
2426                                .context("sending stage request")?;
2427
2428                            Ok(())
2429                        }
2430                    }
2431                })
2432            })?
2433            .await??;
2434
2435            Ok(())
2436        })
2437    }
2438
2439    pub fn unstage_entries(
2440        &self,
2441        entries: Vec<RepoPath>,
2442        cx: &mut Context<Self>,
2443    ) -> Task<anyhow::Result<()>> {
2444        if entries.is_empty() {
2445            return Task::ready(Ok(()));
2446        }
2447        let env = self.worktree_environment(cx);
2448
2449        let mut save_futures = Vec::new();
2450        if let Some(buffer_store) = self.buffer_store(cx) {
2451            buffer_store.update(cx, |buffer_store, cx| {
2452                for path in &entries {
2453                    let Some(path) = self.repository_entry.try_unrelativize(path) else {
2454                        continue;
2455                    };
2456                    let project_path = (self.worktree_id, path).into();
2457                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
2458                        if buffer
2459                            .read(cx)
2460                            .file()
2461                            .map_or(false, |file| file.disk_state().exists())
2462                        {
2463                            save_futures.push(buffer_store.save_buffer(buffer, cx));
2464                        }
2465                    }
2466                }
2467            })
2468        }
2469
2470        cx.spawn(async move |this, cx| {
2471            for save_future in save_futures {
2472                save_future.await?;
2473            }
2474            let env = env.await;
2475
2476            this.update(cx, |this, _| {
2477                this.send_job(|git_repo, cx| async move {
2478                    match git_repo {
2479                        GitRepo::Local(repo) => repo.unstage_paths(entries, env, cx).await,
2480                        GitRepo::Remote {
2481                            project_id,
2482                            client,
2483                            worktree_id,
2484                            work_directory_id,
2485                        } => {
2486                            client
2487                                .request(proto::Unstage {
2488                                    project_id: project_id.0,
2489                                    worktree_id: worktree_id.to_proto(),
2490                                    work_directory_id: work_directory_id.to_proto(),
2491                                    paths: entries
2492                                        .into_iter()
2493                                        .map(|repo_path| repo_path.as_ref().to_proto())
2494                                        .collect(),
2495                                })
2496                                .await
2497                                .context("sending unstage request")?;
2498
2499                            Ok(())
2500                        }
2501                    }
2502                })
2503            })?
2504            .await??;
2505
2506            Ok(())
2507        })
2508    }
2509
2510    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
2511        let to_stage = self
2512            .repository_entry
2513            .status()
2514            .filter(|entry| !entry.status.staging().is_fully_staged())
2515            .map(|entry| entry.repo_path.clone())
2516            .collect();
2517        self.stage_entries(to_stage, cx)
2518    }
2519
2520    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
2521        let to_unstage = self
2522            .repository_entry
2523            .status()
2524            .filter(|entry| entry.status.staging().has_staged())
2525            .map(|entry| entry.repo_path.clone())
2526            .collect();
2527        self.unstage_entries(to_unstage, cx)
2528    }
2529
2530    /// Get a count of all entries in the active repository, including
2531    /// untracked files.
2532    pub fn entry_count(&self) -> usize {
2533        self.repository_entry.status_len()
2534    }
2535
2536    fn worktree_environment(
2537        &self,
2538        cx: &mut App,
2539    ) -> impl Future<Output = HashMap<String, String>> + 'static {
2540        let task = self.project_environment.as_ref().and_then(|env| {
2541            env.update(cx, |env, cx| {
2542                env.get_environment(
2543                    Some(self.worktree_id),
2544                    Some(self.worktree_abs_path.clone()),
2545                    cx,
2546                )
2547            })
2548            .ok()
2549        });
2550        async move { OptionFuture::from(task).await.flatten().unwrap_or_default() }
2551    }
2552
2553    pub fn commit(
2554        &self,
2555        message: SharedString,
2556        name_and_email: Option<(SharedString, SharedString)>,
2557        cx: &mut App,
2558    ) -> oneshot::Receiver<Result<()>> {
2559        let env = self.worktree_environment(cx);
2560        self.send_job(|git_repo, cx| async move {
2561            match git_repo {
2562                GitRepo::Local(repo) => {
2563                    let env = env.await;
2564                    repo.commit(message, name_and_email, env, cx).await
2565                }
2566                GitRepo::Remote {
2567                    project_id,
2568                    client,
2569                    worktree_id,
2570                    work_directory_id,
2571                } => {
2572                    let (name, email) = name_and_email.unzip();
2573                    client
2574                        .request(proto::Commit {
2575                            project_id: project_id.0,
2576                            worktree_id: worktree_id.to_proto(),
2577                            work_directory_id: work_directory_id.to_proto(),
2578                            message: String::from(message),
2579                            name: name.map(String::from),
2580                            email: email.map(String::from),
2581                        })
2582                        .await
2583                        .context("sending commit request")?;
2584
2585                    Ok(())
2586                }
2587            }
2588        })
2589    }
2590
2591    pub fn fetch(
2592        &mut self,
2593        askpass: AskPassDelegate,
2594        cx: &mut App,
2595    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
2596        let executor = cx.background_executor().clone();
2597        let askpass_delegates = self.askpass_delegates.clone();
2598        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
2599        let env = self.worktree_environment(cx);
2600
2601        self.send_job(move |git_repo, cx| async move {
2602            match git_repo {
2603                GitRepo::Local(git_repository) => {
2604                    let askpass = AskPassSession::new(&executor, askpass).await?;
2605                    let env = env.await;
2606                    git_repository.fetch(askpass, env, cx).await
2607                }
2608                GitRepo::Remote {
2609                    project_id,
2610                    client,
2611                    worktree_id,
2612                    work_directory_id,
2613                } => {
2614                    askpass_delegates.lock().insert(askpass_id, askpass);
2615                    let _defer = util::defer(|| {
2616                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
2617                        debug_assert!(askpass_delegate.is_some());
2618                    });
2619
2620                    let response = client
2621                        .request(proto::Fetch {
2622                            project_id: project_id.0,
2623                            worktree_id: worktree_id.to_proto(),
2624                            work_directory_id: work_directory_id.to_proto(),
2625                            askpass_id,
2626                        })
2627                        .await
2628                        .context("sending fetch request")?;
2629
2630                    Ok(RemoteCommandOutput {
2631                        stdout: response.stdout,
2632                        stderr: response.stderr,
2633                    })
2634                }
2635            }
2636        })
2637    }
2638
2639    pub fn push(
2640        &mut self,
2641        branch: SharedString,
2642        remote: SharedString,
2643        options: Option<PushOptions>,
2644        askpass: AskPassDelegate,
2645        cx: &mut App,
2646    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
2647        let executor = cx.background_executor().clone();
2648        let askpass_delegates = self.askpass_delegates.clone();
2649        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
2650        let env = self.worktree_environment(cx);
2651
2652        self.send_job(move |git_repo, cx| async move {
2653            match git_repo {
2654                GitRepo::Local(git_repository) => {
2655                    let env = env.await;
2656                    let askpass = AskPassSession::new(&executor, askpass).await?;
2657                    git_repository
2658                        .push(
2659                            branch.to_string(),
2660                            remote.to_string(),
2661                            options,
2662                            askpass,
2663                            env,
2664                            cx,
2665                        )
2666                        .await
2667                }
2668                GitRepo::Remote {
2669                    project_id,
2670                    client,
2671                    worktree_id,
2672                    work_directory_id,
2673                } => {
2674                    askpass_delegates.lock().insert(askpass_id, askpass);
2675                    let _defer = util::defer(|| {
2676                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
2677                        debug_assert!(askpass_delegate.is_some());
2678                    });
2679                    let response = client
2680                        .request(proto::Push {
2681                            project_id: project_id.0,
2682                            worktree_id: worktree_id.to_proto(),
2683                            work_directory_id: work_directory_id.to_proto(),
2684                            askpass_id,
2685                            branch_name: branch.to_string(),
2686                            remote_name: remote.to_string(),
2687                            options: options.map(|options| match options {
2688                                PushOptions::Force => proto::push::PushOptions::Force,
2689                                PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
2690                            } as i32),
2691                        })
2692                        .await
2693                        .context("sending push request")?;
2694
2695                    Ok(RemoteCommandOutput {
2696                        stdout: response.stdout,
2697                        stderr: response.stderr,
2698                    })
2699                }
2700            }
2701        })
2702    }
2703
2704    pub fn pull(
2705        &mut self,
2706        branch: SharedString,
2707        remote: SharedString,
2708        askpass: AskPassDelegate,
2709        cx: &mut App,
2710    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
2711        let executor = cx.background_executor().clone();
2712        let askpass_delegates = self.askpass_delegates.clone();
2713        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
2714        let env = self.worktree_environment(cx);
2715
2716        self.send_job(move |git_repo, cx| async move {
2717            match git_repo {
2718                GitRepo::Local(git_repository) => {
2719                    let askpass = AskPassSession::new(&executor, askpass).await?;
2720                    let env = env.await;
2721                    git_repository
2722                        .pull(branch.to_string(), remote.to_string(), askpass, env, cx)
2723                        .await
2724                }
2725                GitRepo::Remote {
2726                    project_id,
2727                    client,
2728                    worktree_id,
2729                    work_directory_id,
2730                } => {
2731                    askpass_delegates.lock().insert(askpass_id, askpass);
2732                    let _defer = util::defer(|| {
2733                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
2734                        debug_assert!(askpass_delegate.is_some());
2735                    });
2736                    let response = client
2737                        .request(proto::Pull {
2738                            project_id: project_id.0,
2739                            worktree_id: worktree_id.to_proto(),
2740                            work_directory_id: work_directory_id.to_proto(),
2741                            askpass_id,
2742                            branch_name: branch.to_string(),
2743                            remote_name: remote.to_string(),
2744                        })
2745                        .await
2746                        .context("sending pull request")?;
2747
2748                    Ok(RemoteCommandOutput {
2749                        stdout: response.stdout,
2750                        stderr: response.stderr,
2751                    })
2752                }
2753            }
2754        })
2755    }
2756
2757    fn spawn_set_index_text_job(
2758        &self,
2759        path: RepoPath,
2760        content: Option<String>,
2761        cx: &mut App,
2762    ) -> oneshot::Receiver<anyhow::Result<()>> {
2763        let env = self.worktree_environment(cx);
2764
2765        self.send_keyed_job(
2766            Some(GitJobKey::WriteIndex(path.clone())),
2767            |git_repo, cx| async {
2768                match git_repo {
2769                    GitRepo::Local(repo) => repo.set_index_text(path, content, env.await, cx).await,
2770                    GitRepo::Remote {
2771                        project_id,
2772                        client,
2773                        worktree_id,
2774                        work_directory_id,
2775                    } => {
2776                        client
2777                            .request(proto::SetIndexText {
2778                                project_id: project_id.0,
2779                                worktree_id: worktree_id.to_proto(),
2780                                work_directory_id: work_directory_id.to_proto(),
2781                                path: path.as_ref().to_proto(),
2782                                text: content,
2783                            })
2784                            .await?;
2785                        Ok(())
2786                    }
2787                }
2788            },
2789        )
2790    }
2791
2792    pub fn get_remotes(
2793        &self,
2794        branch_name: Option<String>,
2795    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
2796        self.send_job(|repo, cx| async move {
2797            match repo {
2798                GitRepo::Local(git_repository) => git_repository.get_remotes(branch_name, cx).await,
2799                GitRepo::Remote {
2800                    project_id,
2801                    client,
2802                    worktree_id,
2803                    work_directory_id,
2804                } => {
2805                    let response = client
2806                        .request(proto::GetRemotes {
2807                            project_id: project_id.0,
2808                            worktree_id: worktree_id.to_proto(),
2809                            work_directory_id: work_directory_id.to_proto(),
2810                            branch_name,
2811                        })
2812                        .await?;
2813
2814                    let remotes = response
2815                        .remotes
2816                        .into_iter()
2817                        .map(|remotes| git::repository::Remote {
2818                            name: remotes.name.into(),
2819                        })
2820                        .collect();
2821
2822                    Ok(remotes)
2823                }
2824            }
2825        })
2826    }
2827
2828    pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
2829        self.send_job(|repo, cx| async move {
2830            match repo {
2831                GitRepo::Local(git_repository) => {
2832                    let git_repository = git_repository.clone();
2833                    cx.background_spawn(async move { git_repository.branches().await })
2834                        .await
2835                }
2836                GitRepo::Remote {
2837                    project_id,
2838                    client,
2839                    worktree_id,
2840                    work_directory_id,
2841                } => {
2842                    let response = client
2843                        .request(proto::GitGetBranches {
2844                            project_id: project_id.0,
2845                            worktree_id: worktree_id.to_proto(),
2846                            work_directory_id: work_directory_id.to_proto(),
2847                        })
2848                        .await?;
2849
2850                    let branches = response
2851                        .branches
2852                        .into_iter()
2853                        .map(|branch| worktree::proto_to_branch(&branch))
2854                        .collect();
2855
2856                    Ok(branches)
2857                }
2858            }
2859        })
2860    }
2861
2862    pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
2863        self.send_job(|repo, cx| async move {
2864            match repo {
2865                GitRepo::Local(git_repository) => git_repository.diff(diff_type, cx).await,
2866                GitRepo::Remote {
2867                    project_id,
2868                    client,
2869                    worktree_id,
2870                    work_directory_id,
2871                    ..
2872                } => {
2873                    let response = client
2874                        .request(proto::GitDiff {
2875                            project_id: project_id.0,
2876                            worktree_id: worktree_id.to_proto(),
2877                            work_directory_id: work_directory_id.to_proto(),
2878                            diff_type: match diff_type {
2879                                DiffType::HeadToIndex => {
2880                                    proto::git_diff::DiffType::HeadToIndex.into()
2881                                }
2882                                DiffType::HeadToWorktree => {
2883                                    proto::git_diff::DiffType::HeadToWorktree.into()
2884                                }
2885                            },
2886                        })
2887                        .await?;
2888
2889                    Ok(response.diff)
2890                }
2891            }
2892        })
2893    }
2894
2895    pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
2896        self.send_job(|repo, cx| async move {
2897            match repo {
2898                GitRepo::Local(git_repository) => {
2899                    git_repository.create_branch(branch_name, cx).await
2900                }
2901                GitRepo::Remote {
2902                    project_id,
2903                    client,
2904                    worktree_id,
2905                    work_directory_id,
2906                } => {
2907                    client
2908                        .request(proto::GitCreateBranch {
2909                            project_id: project_id.0,
2910                            worktree_id: worktree_id.to_proto(),
2911                            work_directory_id: work_directory_id.to_proto(),
2912                            branch_name,
2913                        })
2914                        .await?;
2915
2916                    Ok(())
2917                }
2918            }
2919        })
2920    }
2921
2922    pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
2923        self.send_job(|repo, cx| async move {
2924            match repo {
2925                GitRepo::Local(git_repository) => {
2926                    git_repository.change_branch(branch_name, cx).await
2927                }
2928                GitRepo::Remote {
2929                    project_id,
2930                    client,
2931                    worktree_id,
2932                    work_directory_id,
2933                } => {
2934                    client
2935                        .request(proto::GitChangeBranch {
2936                            project_id: project_id.0,
2937                            worktree_id: worktree_id.to_proto(),
2938                            work_directory_id: work_directory_id.to_proto(),
2939                            branch_name,
2940                        })
2941                        .await?;
2942
2943                    Ok(())
2944                }
2945            }
2946        })
2947    }
2948
2949    pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
2950        self.send_job(|repo, cx| async move {
2951            match repo {
2952                GitRepo::Local(git_repository) => git_repository.check_for_pushed_commit(cx).await,
2953                GitRepo::Remote {
2954                    project_id,
2955                    client,
2956                    worktree_id,
2957                    work_directory_id,
2958                } => {
2959                    let response = client
2960                        .request(proto::CheckForPushedCommits {
2961                            project_id: project_id.0,
2962                            worktree_id: worktree_id.to_proto(),
2963                            work_directory_id: work_directory_id.to_proto(),
2964                        })
2965                        .await?;
2966
2967                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
2968
2969                    Ok(branches)
2970                }
2971            }
2972        })
2973    }
2974
2975    pub fn checkpoint(&self) -> oneshot::Receiver<Result<RepositoryCheckpoint>> {
2976        self.send_job(|repo, cx| async move {
2977            match repo {
2978                GitRepo::Local(git_repository) => {
2979                    let sha = git_repository.checkpoint(cx).await?;
2980                    Ok(RepositoryCheckpoint { sha })
2981                }
2982                GitRepo::Remote { .. } => Err(anyhow!("not implemented yet")),
2983            }
2984        })
2985    }
2986
2987    pub fn restore_checkpoint(
2988        &self,
2989        checkpoint: RepositoryCheckpoint,
2990    ) -> oneshot::Receiver<Result<()>> {
2991        self.send_job(move |repo, cx| async move {
2992            match repo {
2993                GitRepo::Local(git_repository) => {
2994                    git_repository.restore_checkpoint(checkpoint.sha, cx).await
2995                }
2996                GitRepo::Remote { .. } => Err(anyhow!("not implemented yet")),
2997            }
2998        })
2999    }
3000}