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