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