git.rs

   1use crate::{
   2    buffer_store::{BufferStore, BufferStoreEvent},
   3    worktree_store::{WorktreeStore, WorktreeStoreEvent},
   4    Project, ProjectItem, ProjectPath,
   5};
   6use anyhow::{Context as _, Result};
   7use askpass::{AskPassDelegate, AskPassSession};
   8use buffer_diff::BufferDiffEvent;
   9use client::ProjectId;
  10use collections::HashMap;
  11use futures::{
  12    channel::{mpsc, oneshot},
  13    StreamExt as _,
  14};
  15use git::{
  16    repository::{
  17        Branch, CommitDetails, GitRepository, PushOptions, Remote, RemoteCommandOutput, RepoPath,
  18        ResetMode,
  19    },
  20    status::FileStatus,
  21};
  22use gpui::{
  23    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
  24    WeakEntity,
  25};
  26use language::{Buffer, LanguageRegistry};
  27use parking_lot::Mutex;
  28use rpc::{
  29    proto::{self, git_reset, ToProto},
  30    AnyProtoClient, TypedEnvelope,
  31};
  32use settings::WorktreeId;
  33use std::{
  34    collections::VecDeque,
  35    future::Future,
  36    path::{Path, PathBuf},
  37    sync::Arc,
  38};
  39use text::BufferId;
  40use util::{debug_panic, maybe, ResultExt};
  41use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory};
  42
  43pub struct GitStore {
  44    buffer_store: Entity<BufferStore>,
  45    pub(super) project_id: Option<ProjectId>,
  46    pub(super) client: AnyProtoClient,
  47    repositories: Vec<Entity<Repository>>,
  48    active_index: Option<usize>,
  49    update_sender: mpsc::UnboundedSender<GitJob>,
  50    _subscriptions: [Subscription; 2],
  51}
  52
  53pub struct Repository {
  54    commit_message_buffer: Option<Entity<Buffer>>,
  55    git_store: WeakEntity<GitStore>,
  56    pub worktree_id: WorktreeId,
  57    pub repository_entry: RepositoryEntry,
  58    pub git_repo: GitRepo,
  59    pub merge_message: Option<String>,
  60    job_sender: mpsc::UnboundedSender<GitJob>,
  61    askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,
  62    latest_askpass_id: u64,
  63}
  64
  65#[derive(Clone)]
  66pub enum GitRepo {
  67    Local(Arc<dyn GitRepository>),
  68    Remote {
  69        project_id: ProjectId,
  70        client: AnyProtoClient,
  71        worktree_id: WorktreeId,
  72        work_directory_id: ProjectEntryId,
  73    },
  74}
  75
  76#[derive(Debug)]
  77pub enum GitEvent {
  78    ActiveRepositoryChanged,
  79    FileSystemUpdated,
  80    GitStateUpdated,
  81    IndexWriteError(anyhow::Error),
  82}
  83
  84struct GitJob {
  85    job: Box<dyn FnOnce(&mut AsyncApp) -> Task<()>>,
  86    key: Option<GitJobKey>,
  87}
  88
  89#[derive(PartialEq, Eq)]
  90enum GitJobKey {
  91    WriteIndex(RepoPath),
  92}
  93
  94impl EventEmitter<GitEvent> for GitStore {}
  95
  96impl GitStore {
  97    pub fn new(
  98        worktree_store: &Entity<WorktreeStore>,
  99        buffer_store: Entity<BufferStore>,
 100        client: AnyProtoClient,
 101        project_id: Option<ProjectId>,
 102        cx: &mut Context<'_, Self>,
 103    ) -> Self {
 104        let update_sender = Self::spawn_git_worker(cx);
 105        let _subscriptions = [
 106            cx.subscribe(worktree_store, Self::on_worktree_store_event),
 107            cx.subscribe(&buffer_store, Self::on_buffer_store_event),
 108        ];
 109
 110        GitStore {
 111            project_id,
 112            client,
 113            buffer_store,
 114            repositories: Vec::new(),
 115            active_index: None,
 116            update_sender,
 117            _subscriptions,
 118        }
 119    }
 120
 121    pub fn init(client: &AnyProtoClient) {
 122        client.add_entity_request_handler(Self::handle_get_remotes);
 123        client.add_entity_request_handler(Self::handle_get_branches);
 124        client.add_entity_request_handler(Self::handle_change_branch);
 125        client.add_entity_request_handler(Self::handle_create_branch);
 126        client.add_entity_request_handler(Self::handle_push);
 127        client.add_entity_request_handler(Self::handle_pull);
 128        client.add_entity_request_handler(Self::handle_fetch);
 129        client.add_entity_request_handler(Self::handle_stage);
 130        client.add_entity_request_handler(Self::handle_unstage);
 131        client.add_entity_request_handler(Self::handle_commit);
 132        client.add_entity_request_handler(Self::handle_reset);
 133        client.add_entity_request_handler(Self::handle_show);
 134        client.add_entity_request_handler(Self::handle_checkout_files);
 135        client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
 136        client.add_entity_request_handler(Self::handle_set_index_text);
 137        client.add_entity_request_handler(Self::handle_askpass);
 138        client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
 139    }
 140
 141    pub fn active_repository(&self) -> Option<Entity<Repository>> {
 142        self.active_index
 143            .map(|index| self.repositories[index].clone())
 144    }
 145
 146    fn on_worktree_store_event(
 147        &mut self,
 148        worktree_store: Entity<WorktreeStore>,
 149        event: &WorktreeStoreEvent,
 150        cx: &mut Context<'_, Self>,
 151    ) {
 152        let mut new_repositories = Vec::new();
 153        let mut new_active_index = None;
 154        let this = cx.weak_entity();
 155        let client = self.client.clone();
 156        let project_id = self.project_id;
 157
 158        worktree_store.update(cx, |worktree_store, cx| {
 159            for worktree in worktree_store.worktrees() {
 160                worktree.update(cx, |worktree, cx| {
 161                    let snapshot = worktree.snapshot();
 162                    for repo in snapshot.repositories().iter() {
 163                        let git_data = worktree
 164                            .as_local()
 165                            .and_then(|local_worktree| local_worktree.get_local_repo(repo))
 166                            .map(|local_repo| {
 167                                (
 168                                    GitRepo::Local(local_repo.repo().clone()),
 169                                    local_repo.merge_message.clone(),
 170                                )
 171                            })
 172                            .or_else(|| {
 173                                let client = client.clone();
 174                                let project_id = project_id?;
 175                                Some((
 176                                    GitRepo::Remote {
 177                                        project_id,
 178                                        client,
 179                                        worktree_id: worktree.id(),
 180                                        work_directory_id: repo.work_directory_id(),
 181                                    },
 182                                    None,
 183                                ))
 184                            });
 185                        let Some((git_repo, merge_message)) = git_data else {
 186                            continue;
 187                        };
 188                        let worktree_id = worktree.id();
 189                        let existing =
 190                            self.repositories
 191                                .iter()
 192                                .enumerate()
 193                                .find(|(_, existing_handle)| {
 194                                    existing_handle.read(cx).id()
 195                                        == (worktree_id, repo.work_directory_id())
 196                                });
 197                        let handle = if let Some((index, handle)) = existing {
 198                            if self.active_index == Some(index) {
 199                                new_active_index = Some(new_repositories.len());
 200                            }
 201                            // Update the statuses and merge message but keep everything else.
 202                            let existing_handle = handle.clone();
 203                            existing_handle.update(cx, |existing_handle, cx| {
 204                                existing_handle.repository_entry = repo.clone();
 205                                if matches!(git_repo, GitRepo::Local { .. })
 206                                    && existing_handle.merge_message != merge_message
 207                                {
 208                                    if let (Some(merge_message), Some(buffer)) =
 209                                        (&merge_message, &existing_handle.commit_message_buffer)
 210                                    {
 211                                        buffer.update(cx, |buffer, cx| {
 212                                            if buffer.is_empty() {
 213                                                buffer.set_text(merge_message.as_str(), cx);
 214                                            }
 215                                        })
 216                                    }
 217                                    existing_handle.merge_message = merge_message;
 218                                }
 219                            });
 220                            existing_handle
 221                        } else {
 222                            cx.new(|_| Repository {
 223                                git_store: this.clone(),
 224                                worktree_id,
 225                                askpass_delegates: Default::default(),
 226                                latest_askpass_id: 0,
 227                                repository_entry: repo.clone(),
 228                                git_repo,
 229                                job_sender: self.update_sender.clone(),
 230                                merge_message,
 231                                commit_message_buffer: None,
 232                            })
 233                        };
 234                        new_repositories.push(handle);
 235                    }
 236                })
 237            }
 238        });
 239
 240        if new_active_index == None && new_repositories.len() > 0 {
 241            new_active_index = Some(0);
 242        }
 243
 244        self.repositories = new_repositories;
 245        self.active_index = new_active_index;
 246
 247        match event {
 248            WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
 249                cx.emit(GitEvent::GitStateUpdated);
 250            }
 251            _ => {
 252                cx.emit(GitEvent::FileSystemUpdated);
 253            }
 254        }
 255    }
 256
 257    fn on_buffer_store_event(
 258        &mut self,
 259        _: Entity<BufferStore>,
 260        event: &BufferStoreEvent,
 261        cx: &mut Context<'_, Self>,
 262    ) {
 263        if let BufferStoreEvent::BufferDiffAdded(diff) = event {
 264            cx.subscribe(diff, Self::on_buffer_diff_event).detach();
 265        }
 266    }
 267
 268    fn on_buffer_diff_event(
 269        this: &mut GitStore,
 270        diff: Entity<buffer_diff::BufferDiff>,
 271        event: &BufferDiffEvent,
 272        cx: &mut Context<'_, GitStore>,
 273    ) {
 274        if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
 275            let buffer_id = diff.read(cx).buffer_id;
 276            if let Some((repo, path)) = this.repository_and_path_for_buffer_id(buffer_id, cx) {
 277                let recv = repo
 278                    .read(cx)
 279                    .set_index_text(&path, new_index_text.as_ref().map(|rope| rope.to_string()));
 280                let diff = diff.downgrade();
 281                cx.spawn(|this, mut cx| async move {
 282                    if let Some(result) = cx.background_spawn(async move { recv.await.ok() }).await
 283                    {
 284                        if let Err(error) = result {
 285                            diff.update(&mut cx, |diff, cx| {
 286                                diff.clear_pending_hunks(cx);
 287                            })
 288                            .ok();
 289                            this.update(&mut cx, |_, cx| cx.emit(GitEvent::IndexWriteError(error)))
 290                                .ok();
 291                        }
 292                    }
 293                })
 294                .detach();
 295            }
 296        }
 297    }
 298
 299    pub fn all_repositories(&self) -> Vec<Entity<Repository>> {
 300        self.repositories.clone()
 301    }
 302
 303    pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
 304        let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
 305        let status = repo.read(cx).repository_entry.status_for_path(&path)?;
 306        Some(status.status)
 307    }
 308
 309    fn repository_and_path_for_buffer_id(
 310        &self,
 311        buffer_id: BufferId,
 312        cx: &App,
 313    ) -> Option<(Entity<Repository>, RepoPath)> {
 314        let buffer = self.buffer_store.read(cx).get(buffer_id)?;
 315        let path = buffer.read(cx).project_path(cx)?;
 316        let mut result: Option<(Entity<Repository>, RepoPath)> = None;
 317        for repo_handle in &self.repositories {
 318            let repo = repo_handle.read(cx);
 319            if repo.worktree_id == path.worktree_id {
 320                if let Ok(relative_path) = repo.repository_entry.relativize(&path.path) {
 321                    if result
 322                        .as_ref()
 323                        .is_none_or(|(result, _)| !repo.contains_sub_repo(result, cx))
 324                    {
 325                        result = Some((repo_handle.clone(), relative_path))
 326                    }
 327                }
 328            }
 329        }
 330        result
 331    }
 332
 333    fn spawn_git_worker(cx: &mut Context<'_, GitStore>) -> mpsc::UnboundedSender<GitJob> {
 334        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
 335
 336        cx.spawn(|_, mut cx| async move {
 337            let mut jobs = VecDeque::new();
 338            loop {
 339                while let Ok(Some(next_job)) = job_rx.try_next() {
 340                    jobs.push_back(next_job);
 341                }
 342
 343                if let Some(job) = jobs.pop_front() {
 344                    if let Some(current_key) = &job.key {
 345                        if jobs
 346                            .iter()
 347                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
 348                        {
 349                            continue;
 350                        }
 351                    }
 352                    (job.job)(&mut cx).await;
 353                } else if let Some(job) = job_rx.next().await {
 354                    jobs.push_back(job);
 355                } else {
 356                    break;
 357                }
 358            }
 359        })
 360        .detach();
 361        job_tx
 362    }
 363
 364    async fn handle_fetch(
 365        this: Entity<Self>,
 366        envelope: TypedEnvelope<proto::Fetch>,
 367        mut cx: AsyncApp,
 368    ) -> Result<proto::RemoteMessageResponse> {
 369        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 370        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 371        let repository_handle =
 372            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 373        let askpass_id = envelope.payload.askpass_id;
 374
 375        let askpass = make_remote_delegate(
 376            this,
 377            envelope.payload.project_id,
 378            worktree_id,
 379            work_directory_id,
 380            askpass_id,
 381            &mut cx,
 382        );
 383
 384        let remote_output = repository_handle
 385            .update(&mut cx, |repository_handle, cx| {
 386                repository_handle.fetch(askpass, cx)
 387            })?
 388            .await??;
 389
 390        Ok(proto::RemoteMessageResponse {
 391            stdout: remote_output.stdout,
 392            stderr: remote_output.stderr,
 393        })
 394    }
 395
 396    async fn handle_push(
 397        this: Entity<Self>,
 398        envelope: TypedEnvelope<proto::Push>,
 399        mut cx: AsyncApp,
 400    ) -> Result<proto::RemoteMessageResponse> {
 401        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 402        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 403        let repository_handle =
 404            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 405
 406        let askpass_id = envelope.payload.askpass_id;
 407        let askpass = make_remote_delegate(
 408            this,
 409            envelope.payload.project_id,
 410            worktree_id,
 411            work_directory_id,
 412            askpass_id,
 413            &mut cx,
 414        );
 415
 416        let options = envelope
 417            .payload
 418            .options
 419            .as_ref()
 420            .map(|_| match envelope.payload.options() {
 421                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
 422                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
 423            });
 424
 425        let branch_name = envelope.payload.branch_name.into();
 426        let remote_name = envelope.payload.remote_name.into();
 427
 428        let remote_output = repository_handle
 429            .update(&mut cx, |repository_handle, cx| {
 430                repository_handle.push(branch_name, remote_name, options, askpass, cx)
 431            })?
 432            .await??;
 433        Ok(proto::RemoteMessageResponse {
 434            stdout: remote_output.stdout,
 435            stderr: remote_output.stderr,
 436        })
 437    }
 438
 439    async fn handle_pull(
 440        this: Entity<Self>,
 441        envelope: TypedEnvelope<proto::Pull>,
 442        mut cx: AsyncApp,
 443    ) -> Result<proto::RemoteMessageResponse> {
 444        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 445        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 446        let repository_handle =
 447            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 448        let askpass_id = envelope.payload.askpass_id;
 449        let askpass = make_remote_delegate(
 450            this,
 451            envelope.payload.project_id,
 452            worktree_id,
 453            work_directory_id,
 454            askpass_id,
 455            &mut cx,
 456        );
 457
 458        let branch_name = envelope.payload.branch_name.into();
 459        let remote_name = envelope.payload.remote_name.into();
 460
 461        let remote_message = repository_handle
 462            .update(&mut cx, |repository_handle, cx| {
 463                repository_handle.pull(branch_name, remote_name, askpass, cx)
 464            })?
 465            .await??;
 466
 467        Ok(proto::RemoteMessageResponse {
 468            stdout: remote_message.stdout,
 469            stderr: remote_message.stderr,
 470        })
 471    }
 472
 473    async fn handle_stage(
 474        this: Entity<Self>,
 475        envelope: TypedEnvelope<proto::Stage>,
 476        mut cx: AsyncApp,
 477    ) -> Result<proto::Ack> {
 478        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 479        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 480        let repository_handle =
 481            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 482
 483        let entries = envelope
 484            .payload
 485            .paths
 486            .into_iter()
 487            .map(PathBuf::from)
 488            .map(RepoPath::new)
 489            .collect();
 490
 491        repository_handle
 492            .update(&mut cx, |repository_handle, cx| {
 493                repository_handle.stage_entries(entries, cx)
 494            })?
 495            .await?;
 496        Ok(proto::Ack {})
 497    }
 498
 499    async fn handle_unstage(
 500        this: Entity<Self>,
 501        envelope: TypedEnvelope<proto::Unstage>,
 502        mut cx: AsyncApp,
 503    ) -> Result<proto::Ack> {
 504        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 505        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 506        let repository_handle =
 507            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 508
 509        let entries = envelope
 510            .payload
 511            .paths
 512            .into_iter()
 513            .map(PathBuf::from)
 514            .map(RepoPath::new)
 515            .collect();
 516
 517        repository_handle
 518            .update(&mut cx, |repository_handle, cx| {
 519                repository_handle.unstage_entries(entries, cx)
 520            })?
 521            .await?;
 522
 523        Ok(proto::Ack {})
 524    }
 525
 526    async fn handle_set_index_text(
 527        this: Entity<Self>,
 528        envelope: TypedEnvelope<proto::SetIndexText>,
 529        mut cx: AsyncApp,
 530    ) -> Result<proto::Ack> {
 531        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 532        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 533        let repository_handle =
 534            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 535
 536        repository_handle
 537            .update(&mut cx, |repository_handle, _| {
 538                repository_handle.set_index_text(
 539                    &RepoPath::from_str(&envelope.payload.path),
 540                    envelope.payload.text,
 541                )
 542            })?
 543            .await??;
 544        Ok(proto::Ack {})
 545    }
 546
 547    async fn handle_commit(
 548        this: Entity<Self>,
 549        envelope: TypedEnvelope<proto::Commit>,
 550        mut cx: AsyncApp,
 551    ) -> Result<proto::Ack> {
 552        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 553        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 554        let repository_handle =
 555            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 556
 557        let message = SharedString::from(envelope.payload.message);
 558        let name = envelope.payload.name.map(SharedString::from);
 559        let email = envelope.payload.email.map(SharedString::from);
 560
 561        repository_handle
 562            .update(&mut cx, |repository_handle, _| {
 563                repository_handle.commit(message, name.zip(email))
 564            })?
 565            .await??;
 566        Ok(proto::Ack {})
 567    }
 568
 569    async fn handle_get_remotes(
 570        this: Entity<Self>,
 571        envelope: TypedEnvelope<proto::GetRemotes>,
 572        mut cx: AsyncApp,
 573    ) -> Result<proto::GetRemotesResponse> {
 574        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 575        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 576        let repository_handle =
 577            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 578
 579        let branch_name = envelope.payload.branch_name;
 580
 581        let remotes = repository_handle
 582            .update(&mut cx, |repository_handle, _| {
 583                repository_handle.get_remotes(branch_name)
 584            })?
 585            .await??;
 586
 587        Ok(proto::GetRemotesResponse {
 588            remotes: remotes
 589                .into_iter()
 590                .map(|remotes| proto::get_remotes_response::Remote {
 591                    name: remotes.name.to_string(),
 592                })
 593                .collect::<Vec<_>>(),
 594        })
 595    }
 596
 597    async fn handle_get_branches(
 598        this: Entity<Self>,
 599        envelope: TypedEnvelope<proto::GitGetBranches>,
 600        mut cx: AsyncApp,
 601    ) -> Result<proto::GitBranchesResponse> {
 602        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 603        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 604        let repository_handle =
 605            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 606
 607        let branches = repository_handle
 608            .update(&mut cx, |repository_handle, _| repository_handle.branches())?
 609            .await??;
 610
 611        Ok(proto::GitBranchesResponse {
 612            branches: branches
 613                .into_iter()
 614                .map(|branch| worktree::branch_to_proto(&branch))
 615                .collect::<Vec<_>>(),
 616        })
 617    }
 618    async fn handle_create_branch(
 619        this: Entity<Self>,
 620        envelope: TypedEnvelope<proto::GitCreateBranch>,
 621        mut cx: AsyncApp,
 622    ) -> Result<proto::Ack> {
 623        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 624        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 625        let repository_handle =
 626            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 627        let branch_name = envelope.payload.branch_name;
 628
 629        repository_handle
 630            .update(&mut cx, |repository_handle, _| {
 631                repository_handle.create_branch(branch_name)
 632            })?
 633            .await??;
 634
 635        Ok(proto::Ack {})
 636    }
 637
 638    async fn handle_change_branch(
 639        this: Entity<Self>,
 640        envelope: TypedEnvelope<proto::GitChangeBranch>,
 641        mut cx: AsyncApp,
 642    ) -> Result<proto::Ack> {
 643        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 644        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 645        let repository_handle =
 646            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 647        let branch_name = envelope.payload.branch_name;
 648
 649        repository_handle
 650            .update(&mut cx, |repository_handle, _| {
 651                repository_handle.change_branch(branch_name)
 652            })?
 653            .await??;
 654
 655        Ok(proto::Ack {})
 656    }
 657
 658    async fn handle_show(
 659        this: Entity<Self>,
 660        envelope: TypedEnvelope<proto::GitShow>,
 661        mut cx: AsyncApp,
 662    ) -> Result<proto::GitCommitDetails> {
 663        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 664        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 665        let repository_handle =
 666            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 667
 668        let commit = repository_handle
 669            .update(&mut cx, |repository_handle, _| {
 670                repository_handle.show(&envelope.payload.commit)
 671            })?
 672            .await??;
 673        Ok(proto::GitCommitDetails {
 674            sha: commit.sha.into(),
 675            message: commit.message.into(),
 676            commit_timestamp: commit.commit_timestamp,
 677            committer_email: commit.committer_email.into(),
 678            committer_name: commit.committer_name.into(),
 679        })
 680    }
 681
 682    async fn handle_reset(
 683        this: Entity<Self>,
 684        envelope: TypedEnvelope<proto::GitReset>,
 685        mut cx: AsyncApp,
 686    ) -> Result<proto::Ack> {
 687        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 688        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 689        let repository_handle =
 690            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 691
 692        let mode = match envelope.payload.mode() {
 693            git_reset::ResetMode::Soft => ResetMode::Soft,
 694            git_reset::ResetMode::Mixed => ResetMode::Mixed,
 695        };
 696
 697        repository_handle
 698            .update(&mut cx, |repository_handle, _| {
 699                repository_handle.reset(&envelope.payload.commit, mode)
 700            })?
 701            .await??;
 702        Ok(proto::Ack {})
 703    }
 704
 705    async fn handle_checkout_files(
 706        this: Entity<Self>,
 707        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
 708        mut cx: AsyncApp,
 709    ) -> Result<proto::Ack> {
 710        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 711        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 712        let repository_handle =
 713            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 714        let paths = envelope
 715            .payload
 716            .paths
 717            .iter()
 718            .map(|s| RepoPath::from_str(s))
 719            .collect();
 720
 721        repository_handle
 722            .update(&mut cx, |repository_handle, _| {
 723                repository_handle.checkout_files(&envelope.payload.commit, paths)
 724            })?
 725            .await??;
 726        Ok(proto::Ack {})
 727    }
 728
 729    async fn handle_open_commit_message_buffer(
 730        this: Entity<Self>,
 731        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
 732        mut cx: AsyncApp,
 733    ) -> Result<proto::OpenBufferResponse> {
 734        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 735        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 736        let repository =
 737            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 738        let buffer = repository
 739            .update(&mut cx, |repository, cx| {
 740                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
 741            })?
 742            .await?;
 743
 744        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
 745        this.update(&mut cx, |this, cx| {
 746            this.buffer_store.update(cx, |buffer_store, cx| {
 747                buffer_store
 748                    .create_buffer_for_peer(
 749                        &buffer,
 750                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
 751                        cx,
 752                    )
 753                    .detach_and_log_err(cx);
 754            })
 755        })?;
 756
 757        Ok(proto::OpenBufferResponse {
 758            buffer_id: buffer_id.to_proto(),
 759        })
 760    }
 761
 762    async fn handle_askpass(
 763        this: Entity<Self>,
 764        envelope: TypedEnvelope<proto::AskPassRequest>,
 765        mut cx: AsyncApp,
 766    ) -> Result<proto::AskPassResponse> {
 767        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 768        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 769        let repository =
 770            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 771
 772        let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone())?;
 773        let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
 774            debug_panic!("no askpass found");
 775            return Err(anyhow::anyhow!("no askpass found"));
 776        };
 777
 778        let response = askpass.ask_password(envelope.payload.prompt).await?;
 779
 780        delegates
 781            .lock()
 782            .insert(envelope.payload.askpass_id, askpass);
 783
 784        Ok(proto::AskPassResponse { response })
 785    }
 786
 787    async fn handle_check_for_pushed_commits(
 788        this: Entity<Self>,
 789        envelope: TypedEnvelope<proto::CheckForPushedCommits>,
 790        mut cx: AsyncApp,
 791    ) -> Result<proto::CheckForPushedCommitsResponse> {
 792        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 793        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 794        let repository_handle =
 795            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 796
 797        let branches = repository_handle
 798            .update(&mut cx, |repository_handle, _| {
 799                repository_handle.check_for_pushed_commits()
 800            })?
 801            .await??;
 802        Ok(proto::CheckForPushedCommitsResponse {
 803            pushed_to: branches
 804                .into_iter()
 805                .map(|commit| commit.to_string())
 806                .collect(),
 807        })
 808    }
 809
 810    fn repository_for_request(
 811        this: &Entity<Self>,
 812        worktree_id: WorktreeId,
 813        work_directory_id: ProjectEntryId,
 814        cx: &mut AsyncApp,
 815    ) -> Result<Entity<Repository>> {
 816        this.update(cx, |this, cx| {
 817            this.repositories
 818                .iter()
 819                .find(|repository_handle| {
 820                    repository_handle.read(cx).worktree_id == worktree_id
 821                        && repository_handle
 822                            .read(cx)
 823                            .repository_entry
 824                            .work_directory_id()
 825                            == work_directory_id
 826                })
 827                .context("missing repository handle")
 828                .cloned()
 829        })?
 830    }
 831}
 832
 833fn make_remote_delegate(
 834    this: Entity<GitStore>,
 835    project_id: u64,
 836    worktree_id: WorktreeId,
 837    work_directory_id: ProjectEntryId,
 838    askpass_id: u64,
 839    cx: &mut AsyncApp,
 840) -> AskPassDelegate {
 841    AskPassDelegate::new(cx, move |prompt, tx, cx| {
 842        this.update(cx, |this, cx| {
 843            let response = this.client.request(proto::AskPassRequest {
 844                project_id,
 845                worktree_id: worktree_id.to_proto(),
 846                work_directory_id: work_directory_id.to_proto(),
 847                askpass_id,
 848                prompt,
 849            });
 850            cx.spawn(|_, _| async move {
 851                tx.send(response.await?.response).ok();
 852                anyhow::Ok(())
 853            })
 854            .detach_and_log_err(cx);
 855        })
 856        .log_err();
 857    })
 858}
 859
 860impl GitRepo {}
 861
 862impl Repository {
 863    pub fn git_store(&self) -> Option<Entity<GitStore>> {
 864        self.git_store.upgrade()
 865    }
 866
 867    fn id(&self) -> (WorktreeId, ProjectEntryId) {
 868        (self.worktree_id, self.repository_entry.work_directory_id())
 869    }
 870
 871    pub fn current_branch(&self) -> Option<&Branch> {
 872        self.repository_entry.branch()
 873    }
 874
 875    fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
 876    where
 877        F: FnOnce(GitRepo) -> Fut + 'static,
 878        Fut: Future<Output = R> + Send + 'static,
 879        R: Send + 'static,
 880    {
 881        self.send_keyed_job(None, job)
 882    }
 883
 884    fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
 885    where
 886        F: FnOnce(GitRepo) -> Fut + 'static,
 887        Fut: Future<Output = R> + Send + 'static,
 888        R: Send + 'static,
 889    {
 890        let (result_tx, result_rx) = futures::channel::oneshot::channel();
 891        let git_repo = self.git_repo.clone();
 892        self.job_sender
 893            .unbounded_send(GitJob {
 894                key,
 895                job: Box::new(|cx: &mut AsyncApp| {
 896                    let job = job(git_repo);
 897                    cx.background_spawn(async move {
 898                        let result = job.await;
 899                        result_tx.send(result).ok();
 900                    })
 901                }),
 902            })
 903            .ok();
 904        result_rx
 905    }
 906
 907    pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
 908        maybe!({
 909            let project_path = self.repo_path_to_project_path(&"".into())?;
 910            let worktree_name = project
 911                .worktree_for_id(project_path.worktree_id, cx)?
 912                .read(cx)
 913                .root_name();
 914
 915            let mut path = PathBuf::new();
 916            path = path.join(worktree_name);
 917            path = path.join(project_path.path);
 918            Some(path.to_string_lossy().to_string())
 919        })
 920        .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
 921        .into()
 922    }
 923
 924    pub fn activate(&self, cx: &mut Context<Self>) {
 925        let Some(git_store) = self.git_store.upgrade() else {
 926            return;
 927        };
 928        let entity = cx.entity();
 929        git_store.update(cx, |git_store, cx| {
 930            let Some(index) = git_store
 931                .repositories
 932                .iter()
 933                .position(|handle| *handle == entity)
 934            else {
 935                return;
 936            };
 937            git_store.active_index = Some(index);
 938            cx.emit(GitEvent::ActiveRepositoryChanged);
 939        });
 940    }
 941
 942    pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
 943        self.repository_entry.status()
 944    }
 945
 946    pub fn has_conflict(&self, path: &RepoPath) -> bool {
 947        self.repository_entry
 948            .current_merge_conflicts
 949            .contains(&path)
 950    }
 951
 952    pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
 953        let path = self.repository_entry.unrelativize(path)?;
 954        Some((self.worktree_id, path).into())
 955    }
 956
 957    pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
 958        self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
 959    }
 960
 961    // note: callers must verify these come from the same worktree
 962    pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
 963        let other_work_dir = &other.read(cx).repository_entry.work_directory;
 964        match (&self.repository_entry.work_directory, other_work_dir) {
 965            (WorkDirectory::InProject { .. }, WorkDirectory::AboveProject { .. }) => false,
 966            (WorkDirectory::AboveProject { .. }, WorkDirectory::InProject { .. }) => true,
 967            (
 968                WorkDirectory::InProject {
 969                    relative_path: this_path,
 970                },
 971                WorkDirectory::InProject {
 972                    relative_path: other_path,
 973                },
 974            ) => other_path.starts_with(this_path),
 975            (
 976                WorkDirectory::AboveProject {
 977                    absolute_path: this_path,
 978                    ..
 979                },
 980                WorkDirectory::AboveProject {
 981                    absolute_path: other_path,
 982                    ..
 983                },
 984            ) => other_path.starts_with(this_path),
 985        }
 986    }
 987
 988    pub fn worktree_id_path_to_repo_path(
 989        &self,
 990        worktree_id: WorktreeId,
 991        path: &Path,
 992    ) -> Option<RepoPath> {
 993        if worktree_id != self.worktree_id {
 994            return None;
 995        }
 996        self.repository_entry.relativize(path).log_err()
 997    }
 998
 999    pub fn open_commit_buffer(
1000        &mut self,
1001        languages: Option<Arc<LanguageRegistry>>,
1002        buffer_store: Entity<BufferStore>,
1003        cx: &mut Context<Self>,
1004    ) -> Task<Result<Entity<Buffer>>> {
1005        if let Some(buffer) = self.commit_message_buffer.clone() {
1006            return Task::ready(Ok(buffer));
1007        }
1008
1009        if let GitRepo::Remote {
1010            project_id,
1011            client,
1012            worktree_id,
1013            work_directory_id,
1014        } = self.git_repo.clone()
1015        {
1016            let client = client.clone();
1017            cx.spawn(|repository, mut cx| async move {
1018                let request = client.request(proto::OpenCommitMessageBuffer {
1019                    project_id: project_id.0,
1020                    worktree_id: worktree_id.to_proto(),
1021                    work_directory_id: work_directory_id.to_proto(),
1022                });
1023                let response = request.await.context("requesting to open commit buffer")?;
1024                let buffer_id = BufferId::new(response.buffer_id)?;
1025                let buffer = buffer_store
1026                    .update(&mut cx, |buffer_store, cx| {
1027                        buffer_store.wait_for_remote_buffer(buffer_id, cx)
1028                    })?
1029                    .await?;
1030                if let Some(language_registry) = languages {
1031                    let git_commit_language =
1032                        language_registry.language_for_name("Git Commit").await?;
1033                    buffer.update(&mut cx, |buffer, cx| {
1034                        buffer.set_language(Some(git_commit_language), cx);
1035                    })?;
1036                }
1037                repository.update(&mut cx, |repository, _| {
1038                    repository.commit_message_buffer = Some(buffer.clone());
1039                })?;
1040                Ok(buffer)
1041            })
1042        } else {
1043            self.open_local_commit_buffer(languages, buffer_store, cx)
1044        }
1045    }
1046
1047    fn open_local_commit_buffer(
1048        &mut self,
1049        language_registry: Option<Arc<LanguageRegistry>>,
1050        buffer_store: Entity<BufferStore>,
1051        cx: &mut Context<Self>,
1052    ) -> Task<Result<Entity<Buffer>>> {
1053        let merge_message = self.merge_message.clone();
1054        cx.spawn(|repository, mut cx| async move {
1055            let buffer = buffer_store
1056                .update(&mut cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
1057                .await?;
1058
1059            if let Some(language_registry) = language_registry {
1060                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
1061                buffer.update(&mut cx, |buffer, cx| {
1062                    buffer.set_language(Some(git_commit_language), cx);
1063                })?;
1064            }
1065
1066            if let Some(merge_message) = merge_message {
1067                buffer.update(&mut cx, |buffer, cx| {
1068                    buffer.set_text(merge_message.as_str(), cx)
1069                })?;
1070            }
1071
1072            repository.update(&mut cx, |repository, _| {
1073                repository.commit_message_buffer = Some(buffer.clone());
1074            })?;
1075            Ok(buffer)
1076        })
1077    }
1078
1079    pub fn checkout_files(
1080        &self,
1081        commit: &str,
1082        paths: Vec<RepoPath>,
1083    ) -> oneshot::Receiver<Result<()>> {
1084        let commit = commit.to_string();
1085        self.send_job(|git_repo| async move {
1086            match git_repo {
1087                GitRepo::Local(repo) => repo.checkout_files(&commit, &paths),
1088                GitRepo::Remote {
1089                    project_id,
1090                    client,
1091                    worktree_id,
1092                    work_directory_id,
1093                } => {
1094                    client
1095                        .request(proto::GitCheckoutFiles {
1096                            project_id: project_id.0,
1097                            worktree_id: worktree_id.to_proto(),
1098                            work_directory_id: work_directory_id.to_proto(),
1099                            commit,
1100                            paths: paths
1101                                .into_iter()
1102                                .map(|p| p.to_string_lossy().to_string())
1103                                .collect(),
1104                        })
1105                        .await?;
1106
1107                    Ok(())
1108                }
1109            }
1110        })
1111    }
1112
1113    pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> {
1114        let commit = commit.to_string();
1115        self.send_job(|git_repo| async move {
1116            match git_repo {
1117                GitRepo::Local(git_repo) => git_repo.reset(&commit, reset_mode),
1118                GitRepo::Remote {
1119                    project_id,
1120                    client,
1121                    worktree_id,
1122                    work_directory_id,
1123                } => {
1124                    client
1125                        .request(proto::GitReset {
1126                            project_id: project_id.0,
1127                            worktree_id: worktree_id.to_proto(),
1128                            work_directory_id: work_directory_id.to_proto(),
1129                            commit,
1130                            mode: match reset_mode {
1131                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
1132                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
1133                            },
1134                        })
1135                        .await?;
1136
1137                    Ok(())
1138                }
1139            }
1140        })
1141    }
1142
1143    pub fn show(&self, commit: &str) -> oneshot::Receiver<Result<CommitDetails>> {
1144        let commit = commit.to_string();
1145        self.send_job(|git_repo| async move {
1146            match git_repo {
1147                GitRepo::Local(git_repository) => git_repository.show(&commit),
1148                GitRepo::Remote {
1149                    project_id,
1150                    client,
1151                    worktree_id,
1152                    work_directory_id,
1153                } => {
1154                    let resp = client
1155                        .request(proto::GitShow {
1156                            project_id: project_id.0,
1157                            worktree_id: worktree_id.to_proto(),
1158                            work_directory_id: work_directory_id.to_proto(),
1159                            commit,
1160                        })
1161                        .await?;
1162
1163                    Ok(CommitDetails {
1164                        sha: resp.sha.into(),
1165                        message: resp.message.into(),
1166                        commit_timestamp: resp.commit_timestamp,
1167                        committer_email: resp.committer_email.into(),
1168                        committer_name: resp.committer_name.into(),
1169                    })
1170                }
1171            }
1172        })
1173    }
1174
1175    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
1176        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
1177    }
1178
1179    pub fn stage_entries(
1180        &self,
1181        entries: Vec<RepoPath>,
1182        cx: &mut Context<Self>,
1183    ) -> Task<anyhow::Result<()>> {
1184        if entries.is_empty() {
1185            return Task::ready(Ok(()));
1186        }
1187
1188        let mut save_futures = Vec::new();
1189        if let Some(buffer_store) = self.buffer_store(cx) {
1190            buffer_store.update(cx, |buffer_store, cx| {
1191                for path in &entries {
1192                    let Some(path) = self.repository_entry.unrelativize(path) else {
1193                        continue;
1194                    };
1195                    let project_path = (self.worktree_id, path).into();
1196                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1197                        if buffer
1198                            .read(cx)
1199                            .file()
1200                            .map_or(false, |file| file.disk_state().exists())
1201                        {
1202                            save_futures.push(buffer_store.save_buffer(buffer, cx));
1203                        }
1204                    }
1205                }
1206            })
1207        }
1208
1209        cx.spawn(|this, mut cx| async move {
1210            for save_future in save_futures {
1211                save_future.await?;
1212            }
1213
1214            this.update(&mut cx, |this, _| {
1215                this.send_job(|git_repo| async move {
1216                    match git_repo {
1217                        GitRepo::Local(repo) => repo.stage_paths(&entries),
1218                        GitRepo::Remote {
1219                            project_id,
1220                            client,
1221                            worktree_id,
1222                            work_directory_id,
1223                        } => {
1224                            client
1225                                .request(proto::Stage {
1226                                    project_id: project_id.0,
1227                                    worktree_id: worktree_id.to_proto(),
1228                                    work_directory_id: work_directory_id.to_proto(),
1229                                    paths: entries
1230                                        .into_iter()
1231                                        .map(|repo_path| repo_path.as_ref().to_proto())
1232                                        .collect(),
1233                                })
1234                                .await
1235                                .context("sending stage request")?;
1236
1237                            Ok(())
1238                        }
1239                    }
1240                })
1241            })?
1242            .await??;
1243
1244            Ok(())
1245        })
1246    }
1247
1248    pub fn unstage_entries(
1249        &self,
1250        entries: Vec<RepoPath>,
1251        cx: &mut Context<Self>,
1252    ) -> Task<anyhow::Result<()>> {
1253        if entries.is_empty() {
1254            return Task::ready(Ok(()));
1255        }
1256
1257        let mut save_futures = Vec::new();
1258        if let Some(buffer_store) = self.buffer_store(cx) {
1259            buffer_store.update(cx, |buffer_store, cx| {
1260                for path in &entries {
1261                    let Some(path) = self.repository_entry.unrelativize(path) else {
1262                        continue;
1263                    };
1264                    let project_path = (self.worktree_id, path).into();
1265                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1266                        if buffer
1267                            .read(cx)
1268                            .file()
1269                            .map_or(false, |file| file.disk_state().exists())
1270                        {
1271                            save_futures.push(buffer_store.save_buffer(buffer, cx));
1272                        }
1273                    }
1274                }
1275            })
1276        }
1277
1278        cx.spawn(move |this, mut cx| async move {
1279            for save_future in save_futures {
1280                save_future.await?;
1281            }
1282
1283            this.update(&mut cx, |this, _| {
1284                this.send_job(|git_repo| async move {
1285                    match git_repo {
1286                        GitRepo::Local(repo) => repo.unstage_paths(&entries),
1287                        GitRepo::Remote {
1288                            project_id,
1289                            client,
1290                            worktree_id,
1291                            work_directory_id,
1292                        } => {
1293                            client
1294                                .request(proto::Unstage {
1295                                    project_id: project_id.0,
1296                                    worktree_id: worktree_id.to_proto(),
1297                                    work_directory_id: work_directory_id.to_proto(),
1298                                    paths: entries
1299                                        .into_iter()
1300                                        .map(|repo_path| repo_path.as_ref().to_proto())
1301                                        .collect(),
1302                                })
1303                                .await
1304                                .context("sending unstage request")?;
1305
1306                            Ok(())
1307                        }
1308                    }
1309                })
1310            })?
1311            .await??;
1312
1313            Ok(())
1314        })
1315    }
1316
1317    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1318        let to_stage = self
1319            .repository_entry
1320            .status()
1321            .filter(|entry| !entry.status.is_staged().unwrap_or(false))
1322            .map(|entry| entry.repo_path.clone())
1323            .collect();
1324        self.stage_entries(to_stage, cx)
1325    }
1326
1327    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1328        let to_unstage = self
1329            .repository_entry
1330            .status()
1331            .filter(|entry| entry.status.is_staged().unwrap_or(true))
1332            .map(|entry| entry.repo_path.clone())
1333            .collect();
1334        self.unstage_entries(to_unstage, cx)
1335    }
1336
1337    /// Get a count of all entries in the active repository, including
1338    /// untracked files.
1339    pub fn entry_count(&self) -> usize {
1340        self.repository_entry.status_len()
1341    }
1342
1343    pub fn commit(
1344        &self,
1345        message: SharedString,
1346        name_and_email: Option<(SharedString, SharedString)>,
1347    ) -> oneshot::Receiver<Result<()>> {
1348        self.send_job(|git_repo| async move {
1349            match git_repo {
1350                GitRepo::Local(repo) => repo.commit(
1351                    message.as_ref(),
1352                    name_and_email
1353                        .as_ref()
1354                        .map(|(name, email)| (name.as_ref(), email.as_ref())),
1355                ),
1356                GitRepo::Remote {
1357                    project_id,
1358                    client,
1359                    worktree_id,
1360                    work_directory_id,
1361                } => {
1362                    let (name, email) = name_and_email.unzip();
1363                    client
1364                        .request(proto::Commit {
1365                            project_id: project_id.0,
1366                            worktree_id: worktree_id.to_proto(),
1367                            work_directory_id: work_directory_id.to_proto(),
1368                            message: String::from(message),
1369                            name: name.map(String::from),
1370                            email: email.map(String::from),
1371                        })
1372                        .await
1373                        .context("sending commit request")?;
1374
1375                    Ok(())
1376                }
1377            }
1378        })
1379    }
1380
1381    pub fn fetch(
1382        &mut self,
1383        askpass: AskPassDelegate,
1384        cx: &App,
1385    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1386        let executor = cx.background_executor().clone();
1387        let askpass_delegates = self.askpass_delegates.clone();
1388        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1389
1390        self.send_job(move |git_repo| async move {
1391            match git_repo {
1392                GitRepo::Local(git_repository) => {
1393                    let askpass = AskPassSession::new(&executor, askpass).await?;
1394                    git_repository.fetch(askpass)
1395                }
1396                GitRepo::Remote {
1397                    project_id,
1398                    client,
1399                    worktree_id,
1400                    work_directory_id,
1401                } => {
1402                    askpass_delegates.lock().insert(askpass_id, askpass);
1403                    let _defer = util::defer(|| {
1404                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1405                        debug_assert!(askpass_delegate.is_some());
1406                    });
1407
1408                    let response = client
1409                        .request(proto::Fetch {
1410                            project_id: project_id.0,
1411                            worktree_id: worktree_id.to_proto(),
1412                            work_directory_id: work_directory_id.to_proto(),
1413                            askpass_id,
1414                        })
1415                        .await
1416                        .context("sending fetch request")?;
1417
1418                    Ok(RemoteCommandOutput {
1419                        stdout: response.stdout,
1420                        stderr: response.stderr,
1421                    })
1422                }
1423            }
1424        })
1425    }
1426
1427    pub fn push(
1428        &mut self,
1429        branch: SharedString,
1430        remote: SharedString,
1431        options: Option<PushOptions>,
1432        askpass: AskPassDelegate,
1433        cx: &App,
1434    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1435        let executor = cx.background_executor().clone();
1436        let askpass_delegates = self.askpass_delegates.clone();
1437        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1438
1439        self.send_job(move |git_repo| async move {
1440            match git_repo {
1441                GitRepo::Local(git_repository) => {
1442                    let askpass = AskPassSession::new(&executor, askpass).await?;
1443                    git_repository.push(&branch, &remote, options, askpass)
1444                }
1445                GitRepo::Remote {
1446                    project_id,
1447                    client,
1448                    worktree_id,
1449                    work_directory_id,
1450                } => {
1451                    askpass_delegates.lock().insert(askpass_id, askpass);
1452                    let _defer = util::defer(|| {
1453                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1454                        debug_assert!(askpass_delegate.is_some());
1455                    });
1456                    let response = client
1457                        .request(proto::Push {
1458                            project_id: project_id.0,
1459                            worktree_id: worktree_id.to_proto(),
1460                            work_directory_id: work_directory_id.to_proto(),
1461                            askpass_id,
1462                            branch_name: branch.to_string(),
1463                            remote_name: remote.to_string(),
1464                            options: options.map(|options| match options {
1465                                PushOptions::Force => proto::push::PushOptions::Force,
1466                                PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
1467                            } as i32),
1468                        })
1469                        .await
1470                        .context("sending push request")?;
1471
1472                    Ok(RemoteCommandOutput {
1473                        stdout: response.stdout,
1474                        stderr: response.stderr,
1475                    })
1476                }
1477            }
1478        })
1479    }
1480
1481    pub fn pull(
1482        &mut self,
1483        branch: SharedString,
1484        remote: SharedString,
1485        askpass: AskPassDelegate,
1486        cx: &App,
1487    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1488        let executor = cx.background_executor().clone();
1489        let askpass_delegates = self.askpass_delegates.clone();
1490        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1491        self.send_job(move |git_repo| async move {
1492            match git_repo {
1493                GitRepo::Local(git_repository) => {
1494                    let askpass = AskPassSession::new(&executor, askpass).await?;
1495                    git_repository.pull(&branch, &remote, askpass)
1496                }
1497                GitRepo::Remote {
1498                    project_id,
1499                    client,
1500                    worktree_id,
1501                    work_directory_id,
1502                } => {
1503                    askpass_delegates.lock().insert(askpass_id, askpass);
1504                    let _defer = util::defer(|| {
1505                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1506                        debug_assert!(askpass_delegate.is_some());
1507                    });
1508                    let response = client
1509                        .request(proto::Pull {
1510                            project_id: project_id.0,
1511                            worktree_id: worktree_id.to_proto(),
1512                            work_directory_id: work_directory_id.to_proto(),
1513                            askpass_id,
1514                            branch_name: branch.to_string(),
1515                            remote_name: remote.to_string(),
1516                        })
1517                        .await
1518                        .context("sending pull request")?;
1519
1520                    Ok(RemoteCommandOutput {
1521                        stdout: response.stdout,
1522                        stderr: response.stderr,
1523                    })
1524                }
1525            }
1526        })
1527    }
1528
1529    fn set_index_text(
1530        &self,
1531        path: &RepoPath,
1532        content: Option<String>,
1533    ) -> oneshot::Receiver<anyhow::Result<()>> {
1534        let path = path.clone();
1535        self.send_keyed_job(
1536            Some(GitJobKey::WriteIndex(path.clone())),
1537            |git_repo| async move {
1538                match git_repo {
1539                    GitRepo::Local(repo) => repo.set_index_text(&path, content),
1540                    GitRepo::Remote {
1541                        project_id,
1542                        client,
1543                        worktree_id,
1544                        work_directory_id,
1545                    } => {
1546                        client
1547                            .request(proto::SetIndexText {
1548                                project_id: project_id.0,
1549                                worktree_id: worktree_id.to_proto(),
1550                                work_directory_id: work_directory_id.to_proto(),
1551                                path: path.as_ref().to_proto(),
1552                                text: content,
1553                            })
1554                            .await?;
1555                        Ok(())
1556                    }
1557                }
1558            },
1559        )
1560    }
1561
1562    pub fn get_remotes(
1563        &self,
1564        branch_name: Option<String>,
1565    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
1566        self.send_job(|repo| async move {
1567            match repo {
1568                GitRepo::Local(git_repository) => {
1569                    git_repository.get_remotes(branch_name.as_deref())
1570                }
1571                GitRepo::Remote {
1572                    project_id,
1573                    client,
1574                    worktree_id,
1575                    work_directory_id,
1576                } => {
1577                    let response = client
1578                        .request(proto::GetRemotes {
1579                            project_id: project_id.0,
1580                            worktree_id: worktree_id.to_proto(),
1581                            work_directory_id: work_directory_id.to_proto(),
1582                            branch_name,
1583                        })
1584                        .await?;
1585
1586                    let remotes = response
1587                        .remotes
1588                        .into_iter()
1589                        .map(|remotes| git::repository::Remote {
1590                            name: remotes.name.into(),
1591                        })
1592                        .collect();
1593
1594                    Ok(remotes)
1595                }
1596            }
1597        })
1598    }
1599
1600    pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
1601        self.send_job(|repo| async move {
1602            match repo {
1603                GitRepo::Local(git_repository) => git_repository.branches(),
1604                GitRepo::Remote {
1605                    project_id,
1606                    client,
1607                    worktree_id,
1608                    work_directory_id,
1609                } => {
1610                    let response = client
1611                        .request(proto::GitGetBranches {
1612                            project_id: project_id.0,
1613                            worktree_id: worktree_id.to_proto(),
1614                            work_directory_id: work_directory_id.to_proto(),
1615                        })
1616                        .await?;
1617
1618                    let branches = response
1619                        .branches
1620                        .into_iter()
1621                        .map(|branch| worktree::proto_to_branch(&branch))
1622                        .collect();
1623
1624                    Ok(branches)
1625                }
1626            }
1627        })
1628    }
1629
1630    pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
1631        self.send_job(|repo| async move {
1632            match repo {
1633                GitRepo::Local(git_repository) => git_repository.create_branch(&branch_name),
1634                GitRepo::Remote {
1635                    project_id,
1636                    client,
1637                    worktree_id,
1638                    work_directory_id,
1639                } => {
1640                    client
1641                        .request(proto::GitCreateBranch {
1642                            project_id: project_id.0,
1643                            worktree_id: worktree_id.to_proto(),
1644                            work_directory_id: work_directory_id.to_proto(),
1645                            branch_name,
1646                        })
1647                        .await?;
1648
1649                    Ok(())
1650                }
1651            }
1652        })
1653    }
1654
1655    pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
1656        self.send_job(|repo| async move {
1657            match repo {
1658                GitRepo::Local(git_repository) => git_repository.change_branch(&branch_name),
1659                GitRepo::Remote {
1660                    project_id,
1661                    client,
1662                    worktree_id,
1663                    work_directory_id,
1664                } => {
1665                    client
1666                        .request(proto::GitChangeBranch {
1667                            project_id: project_id.0,
1668                            worktree_id: worktree_id.to_proto(),
1669                            work_directory_id: work_directory_id.to_proto(),
1670                            branch_name,
1671                        })
1672                        .await?;
1673
1674                    Ok(())
1675                }
1676            }
1677        })
1678    }
1679
1680    pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
1681        self.send_job(|repo| async move {
1682            match repo {
1683                GitRepo::Local(git_repository) => git_repository.check_for_pushed_commit(),
1684                GitRepo::Remote {
1685                    project_id,
1686                    client,
1687                    worktree_id,
1688                    work_directory_id,
1689                } => {
1690                    let response = client
1691                        .request(proto::CheckForPushedCommits {
1692                            project_id: project_id.0,
1693                            worktree_id: worktree_id.to_proto(),
1694                            work_directory_id: work_directory_id.to_proto(),
1695                        })
1696                        .await?;
1697
1698                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
1699
1700                    Ok(branches)
1701                }
1702            }
1703        })
1704    }
1705}