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