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