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