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