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