git.rs

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