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