git.rs

   1use crate::buffer_store::BufferStore;
   2use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent};
   3use crate::{Project, ProjectPath};
   4use anyhow::{Context as _, Result};
   5use client::ProjectId;
   6use futures::channel::{mpsc, oneshot};
   7use futures::StreamExt as _;
   8use git::repository::{Branch, CommitDetails, PushOptions, Remote, RemoteCommandOutput, ResetMode};
   9use git::repository::{GitRepository, RepoPath};
  10use gpui::{
  11    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
  12    WeakEntity,
  13};
  14use language::{Buffer, LanguageRegistry};
  15use rpc::proto::{git_reset, ToProto};
  16use rpc::{proto, AnyProtoClient, TypedEnvelope};
  17use settings::WorktreeId;
  18use std::collections::VecDeque;
  19use std::future::Future;
  20use std::path::{Path, PathBuf};
  21use std::sync::Arc;
  22use text::BufferId;
  23use util::{maybe, ResultExt};
  24use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry};
  25
  26pub struct GitStore {
  27    buffer_store: Entity<BufferStore>,
  28    pub(super) project_id: Option<ProjectId>,
  29    pub(super) client: Option<AnyProtoClient>,
  30    repositories: Vec<Entity<Repository>>,
  31    active_index: Option<usize>,
  32    update_sender: mpsc::UnboundedSender<GitJob>,
  33    _subscription: Subscription,
  34}
  35
  36pub struct Repository {
  37    commit_message_buffer: Option<Entity<Buffer>>,
  38    git_store: WeakEntity<GitStore>,
  39    pub worktree_id: WorktreeId,
  40    pub repository_entry: RepositoryEntry,
  41    pub git_repo: GitRepo,
  42    pub merge_message: Option<String>,
  43    job_sender: mpsc::UnboundedSender<GitJob>,
  44}
  45
  46#[derive(Clone)]
  47pub enum GitRepo {
  48    Local(Arc<dyn GitRepository>),
  49    Remote {
  50        project_id: ProjectId,
  51        client: AnyProtoClient,
  52        worktree_id: WorktreeId,
  53        work_directory_id: ProjectEntryId,
  54    },
  55}
  56
  57pub enum GitEvent {
  58    ActiveRepositoryChanged,
  59    FileSystemUpdated,
  60    GitStateUpdated,
  61}
  62
  63struct GitJob {
  64    job: Box<dyn FnOnce(&mut AsyncApp) -> Task<()>>,
  65    key: Option<GitJobKey>,
  66}
  67
  68#[derive(PartialEq, Eq)]
  69enum GitJobKey {
  70    WriteIndex(RepoPath),
  71}
  72
  73impl EventEmitter<GitEvent> for GitStore {}
  74
  75impl GitStore {
  76    pub fn new(
  77        worktree_store: &Entity<WorktreeStore>,
  78        buffer_store: Entity<BufferStore>,
  79        client: Option<AnyProtoClient>,
  80        project_id: Option<ProjectId>,
  81        cx: &mut Context<'_, Self>,
  82    ) -> Self {
  83        let update_sender = Self::spawn_git_worker(cx);
  84        let _subscription = cx.subscribe(worktree_store, Self::on_worktree_store_event);
  85
  86        GitStore {
  87            project_id,
  88            client,
  89            buffer_store,
  90            repositories: Vec::new(),
  91            active_index: None,
  92            update_sender,
  93            _subscription,
  94        }
  95    }
  96
  97    pub fn init(client: &AnyProtoClient) {
  98        client.add_entity_request_handler(Self::handle_get_remotes);
  99        client.add_entity_request_handler(Self::handle_push);
 100        client.add_entity_request_handler(Self::handle_pull);
 101        client.add_entity_request_handler(Self::handle_fetch);
 102        client.add_entity_request_handler(Self::handle_stage);
 103        client.add_entity_request_handler(Self::handle_unstage);
 104        client.add_entity_request_handler(Self::handle_commit);
 105        client.add_entity_request_handler(Self::handle_reset);
 106        client.add_entity_request_handler(Self::handle_show);
 107        client.add_entity_request_handler(Self::handle_checkout_files);
 108        client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
 109        client.add_entity_request_handler(Self::handle_set_index_text);
 110    }
 111
 112    pub fn active_repository(&self) -> Option<Entity<Repository>> {
 113        self.active_index
 114            .map(|index| self.repositories[index].clone())
 115    }
 116
 117    fn on_worktree_store_event(
 118        &mut self,
 119        worktree_store: Entity<WorktreeStore>,
 120        event: &WorktreeStoreEvent,
 121        cx: &mut Context<'_, Self>,
 122    ) {
 123        let mut new_repositories = Vec::new();
 124        let mut new_active_index = None;
 125        let this = cx.weak_entity();
 126        let client = self.client.clone();
 127        let project_id = self.project_id;
 128
 129        worktree_store.update(cx, |worktree_store, cx| {
 130            for worktree in worktree_store.worktrees() {
 131                worktree.update(cx, |worktree, cx| {
 132                    let snapshot = worktree.snapshot();
 133                    for repo in snapshot.repositories().iter() {
 134                        let git_data = worktree
 135                            .as_local()
 136                            .and_then(|local_worktree| local_worktree.get_local_repo(repo))
 137                            .map(|local_repo| {
 138                                (
 139                                    GitRepo::Local(local_repo.repo().clone()),
 140                                    local_repo.merge_message.clone(),
 141                                )
 142                            })
 143                            .or_else(|| {
 144                                let client = client.clone()?;
 145                                let project_id = project_id?;
 146                                Some((
 147                                    GitRepo::Remote {
 148                                        project_id,
 149                                        client,
 150                                        worktree_id: worktree.id(),
 151                                        work_directory_id: repo.work_directory_id(),
 152                                    },
 153                                    None,
 154                                ))
 155                            });
 156                        let Some((git_repo, merge_message)) = git_data else {
 157                            continue;
 158                        };
 159                        let worktree_id = worktree.id();
 160                        let existing =
 161                            self.repositories
 162                                .iter()
 163                                .enumerate()
 164                                .find(|(_, existing_handle)| {
 165                                    existing_handle.read(cx).id()
 166                                        == (worktree_id, repo.work_directory_id())
 167                                });
 168                        let handle = if let Some((index, handle)) = existing {
 169                            if self.active_index == Some(index) {
 170                                new_active_index = Some(new_repositories.len());
 171                            }
 172                            // Update the statuses and merge message but keep everything else.
 173                            let existing_handle = handle.clone();
 174                            existing_handle.update(cx, |existing_handle, cx| {
 175                                existing_handle.repository_entry = repo.clone();
 176                                if matches!(git_repo, GitRepo::Local { .. })
 177                                    && existing_handle.merge_message != merge_message
 178                                {
 179                                    if let (Some(merge_message), Some(buffer)) =
 180                                        (&merge_message, &existing_handle.commit_message_buffer)
 181                                    {
 182                                        buffer.update(cx, |buffer, cx| {
 183                                            if buffer.is_empty() {
 184                                                buffer.set_text(merge_message.as_str(), cx);
 185                                            }
 186                                        })
 187                                    }
 188                                    existing_handle.merge_message = merge_message;
 189                                }
 190                            });
 191                            existing_handle
 192                        } else {
 193                            cx.new(|_| Repository {
 194                                git_store: this.clone(),
 195                                worktree_id,
 196                                repository_entry: repo.clone(),
 197                                git_repo,
 198                                job_sender: self.update_sender.clone(),
 199                                merge_message,
 200                                commit_message_buffer: None,
 201                            })
 202                        };
 203                        new_repositories.push(handle);
 204                    }
 205                })
 206            }
 207        });
 208
 209        if new_active_index == None && new_repositories.len() > 0 {
 210            new_active_index = Some(0);
 211        }
 212
 213        self.repositories = new_repositories;
 214        self.active_index = new_active_index;
 215
 216        match event {
 217            WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
 218                cx.emit(GitEvent::GitStateUpdated);
 219            }
 220            _ => {
 221                cx.emit(GitEvent::FileSystemUpdated);
 222            }
 223        }
 224    }
 225
 226    pub fn all_repositories(&self) -> Vec<Entity<Repository>> {
 227        self.repositories.clone()
 228    }
 229
 230    fn spawn_git_worker(cx: &mut Context<'_, GitStore>) -> mpsc::UnboundedSender<GitJob> {
 231        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
 232
 233        cx.spawn(|_, mut cx| async move {
 234            let mut jobs = VecDeque::new();
 235            loop {
 236                while let Ok(Some(next_job)) = job_rx.try_next() {
 237                    jobs.push_back(next_job);
 238                }
 239
 240                if let Some(job) = jobs.pop_front() {
 241                    if let Some(current_key) = &job.key {
 242                        if jobs
 243                            .iter()
 244                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
 245                        {
 246                            continue;
 247                        }
 248                    }
 249                    (job.job)(&mut cx).await;
 250                } else if let Some(job) = job_rx.next().await {
 251                    jobs.push_back(job);
 252                } else {
 253                    break;
 254                }
 255            }
 256        })
 257        .detach();
 258        job_tx
 259    }
 260
 261    async fn handle_fetch(
 262        this: Entity<Self>,
 263        envelope: TypedEnvelope<proto::Fetch>,
 264        mut cx: AsyncApp,
 265    ) -> Result<proto::RemoteMessageResponse> {
 266        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 267        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 268        let repository_handle =
 269            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 270
 271        let remote_output = repository_handle
 272            .update(&mut cx, |repository_handle, _cx| repository_handle.fetch())?
 273            .await??;
 274
 275        Ok(proto::RemoteMessageResponse {
 276            stdout: remote_output.stdout,
 277            stderr: remote_output.stderr,
 278        })
 279    }
 280
 281    async fn handle_push(
 282        this: Entity<Self>,
 283        envelope: TypedEnvelope<proto::Push>,
 284        mut cx: AsyncApp,
 285    ) -> Result<proto::RemoteMessageResponse> {
 286        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 287        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 288        let repository_handle =
 289            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 290
 291        let options = envelope
 292            .payload
 293            .options
 294            .as_ref()
 295            .map(|_| match envelope.payload.options() {
 296                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
 297                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
 298            });
 299
 300        let branch_name = envelope.payload.branch_name.into();
 301        let remote_name = envelope.payload.remote_name.into();
 302
 303        let remote_output = repository_handle
 304            .update(&mut cx, |repository_handle, _cx| {
 305                repository_handle.push(branch_name, remote_name, options)
 306            })?
 307            .await??;
 308        Ok(proto::RemoteMessageResponse {
 309            stdout: remote_output.stdout,
 310            stderr: remote_output.stderr,
 311        })
 312    }
 313
 314    async fn handle_pull(
 315        this: Entity<Self>,
 316        envelope: TypedEnvelope<proto::Pull>,
 317        mut cx: AsyncApp,
 318    ) -> Result<proto::RemoteMessageResponse> {
 319        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 320        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 321        let repository_handle =
 322            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 323
 324        let branch_name = envelope.payload.branch_name.into();
 325        let remote_name = envelope.payload.remote_name.into();
 326
 327        let remote_message = repository_handle
 328            .update(&mut cx, |repository_handle, _cx| {
 329                repository_handle.pull(branch_name, remote_name)
 330            })?
 331            .await??;
 332        Ok(proto::RemoteMessageResponse {
 333            stdout: remote_message.stdout,
 334            stderr: remote_message.stderr,
 335        })
 336    }
 337
 338    async fn handle_stage(
 339        this: Entity<Self>,
 340        envelope: TypedEnvelope<proto::Stage>,
 341        mut cx: AsyncApp,
 342    ) -> Result<proto::Ack> {
 343        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 344        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 345        let repository_handle =
 346            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 347
 348        let entries = envelope
 349            .payload
 350            .paths
 351            .into_iter()
 352            .map(PathBuf::from)
 353            .map(RepoPath::new)
 354            .collect();
 355
 356        repository_handle
 357            .update(&mut cx, |repository_handle, cx| {
 358                repository_handle.stage_entries(entries, cx)
 359            })?
 360            .await?;
 361        Ok(proto::Ack {})
 362    }
 363
 364    async fn handle_unstage(
 365        this: Entity<Self>,
 366        envelope: TypedEnvelope<proto::Unstage>,
 367        mut cx: AsyncApp,
 368    ) -> Result<proto::Ack> {
 369        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 370        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 371        let repository_handle =
 372            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 373
 374        let entries = envelope
 375            .payload
 376            .paths
 377            .into_iter()
 378            .map(PathBuf::from)
 379            .map(RepoPath::new)
 380            .collect();
 381
 382        repository_handle
 383            .update(&mut cx, |repository_handle, cx| {
 384                repository_handle.unstage_entries(entries, cx)
 385            })?
 386            .await?;
 387
 388        Ok(proto::Ack {})
 389    }
 390
 391    async fn handle_set_index_text(
 392        this: Entity<Self>,
 393        envelope: TypedEnvelope<proto::SetIndexText>,
 394        mut cx: AsyncApp,
 395    ) -> Result<proto::Ack> {
 396        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 397        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 398        let repository_handle =
 399            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 400
 401        repository_handle
 402            .update(&mut cx, |repository_handle, _| {
 403                repository_handle.set_index_text(
 404                    &RepoPath::from_str(&envelope.payload.path),
 405                    envelope.payload.text,
 406                )
 407            })?
 408            .await??;
 409        Ok(proto::Ack {})
 410    }
 411
 412    async fn handle_commit(
 413        this: Entity<Self>,
 414        envelope: TypedEnvelope<proto::Commit>,
 415        mut cx: AsyncApp,
 416    ) -> Result<proto::Ack> {
 417        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 418        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 419        let repository_handle =
 420            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 421
 422        let message = SharedString::from(envelope.payload.message);
 423        let name = envelope.payload.name.map(SharedString::from);
 424        let email = envelope.payload.email.map(SharedString::from);
 425
 426        repository_handle
 427            .update(&mut cx, |repository_handle, _| {
 428                repository_handle.commit(message, name.zip(email))
 429            })?
 430            .await??;
 431        Ok(proto::Ack {})
 432    }
 433
 434    async fn handle_get_remotes(
 435        this: Entity<Self>,
 436        envelope: TypedEnvelope<proto::GetRemotes>,
 437        mut cx: AsyncApp,
 438    ) -> Result<proto::GetRemotesResponse> {
 439        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 440        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 441        let repository_handle =
 442            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 443
 444        let branch_name = envelope.payload.branch_name;
 445
 446        let remotes = repository_handle
 447            .update(&mut cx, |repository_handle, _| {
 448                repository_handle.get_remotes(branch_name)
 449            })?
 450            .await??;
 451
 452        Ok(proto::GetRemotesResponse {
 453            remotes: remotes
 454                .into_iter()
 455                .map(|remotes| proto::get_remotes_response::Remote {
 456                    name: remotes.name.to_string(),
 457                })
 458                .collect::<Vec<_>>(),
 459        })
 460    }
 461
 462    async fn handle_show(
 463        this: Entity<Self>,
 464        envelope: TypedEnvelope<proto::GitShow>,
 465        mut cx: AsyncApp,
 466    ) -> Result<proto::GitCommitDetails> {
 467        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 468        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 469        let repository_handle =
 470            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 471
 472        let commit = repository_handle
 473            .update(&mut cx, |repository_handle, _| {
 474                repository_handle.show(&envelope.payload.commit)
 475            })?
 476            .await??;
 477        Ok(proto::GitCommitDetails {
 478            sha: commit.sha.into(),
 479            message: commit.message.into(),
 480            commit_timestamp: commit.commit_timestamp,
 481            committer_email: commit.committer_email.into(),
 482            committer_name: commit.committer_name.into(),
 483        })
 484    }
 485
 486    async fn handle_reset(
 487        this: Entity<Self>,
 488        envelope: TypedEnvelope<proto::GitReset>,
 489        mut cx: AsyncApp,
 490    ) -> Result<proto::Ack> {
 491        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 492        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 493        let repository_handle =
 494            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 495
 496        let mode = match envelope.payload.mode() {
 497            git_reset::ResetMode::Soft => ResetMode::Soft,
 498            git_reset::ResetMode::Mixed => ResetMode::Mixed,
 499        };
 500
 501        repository_handle
 502            .update(&mut cx, |repository_handle, _| {
 503                repository_handle.reset(&envelope.payload.commit, mode)
 504            })?
 505            .await??;
 506        Ok(proto::Ack {})
 507    }
 508
 509    async fn handle_checkout_files(
 510        this: Entity<Self>,
 511        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
 512        mut cx: AsyncApp,
 513    ) -> Result<proto::Ack> {
 514        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 515        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 516        let repository_handle =
 517            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 518        let paths = envelope
 519            .payload
 520            .paths
 521            .iter()
 522            .map(|s| RepoPath::from_str(s))
 523            .collect();
 524
 525        repository_handle
 526            .update(&mut cx, |repository_handle, _| {
 527                repository_handle.checkout_files(&envelope.payload.commit, paths)
 528            })?
 529            .await??;
 530        Ok(proto::Ack {})
 531    }
 532
 533    async fn handle_open_commit_message_buffer(
 534        this: Entity<Self>,
 535        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
 536        mut cx: AsyncApp,
 537    ) -> Result<proto::OpenBufferResponse> {
 538        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 539        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 540        let repository =
 541            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 542        let buffer = repository
 543            .update(&mut cx, |repository, cx| {
 544                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
 545            })?
 546            .await?;
 547
 548        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
 549        this.update(&mut cx, |this, cx| {
 550            this.buffer_store.update(cx, |buffer_store, cx| {
 551                buffer_store
 552                    .create_buffer_for_peer(
 553                        &buffer,
 554                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
 555                        cx,
 556                    )
 557                    .detach_and_log_err(cx);
 558            })
 559        })?;
 560
 561        Ok(proto::OpenBufferResponse {
 562            buffer_id: buffer_id.to_proto(),
 563        })
 564    }
 565
 566    fn repository_for_request(
 567        this: &Entity<Self>,
 568        worktree_id: WorktreeId,
 569        work_directory_id: ProjectEntryId,
 570        cx: &mut AsyncApp,
 571    ) -> Result<Entity<Repository>> {
 572        this.update(cx, |this, cx| {
 573            let repository_handle = this
 574                .all_repositories()
 575                .into_iter()
 576                .find(|repository_handle| {
 577                    repository_handle.read(cx).worktree_id == worktree_id
 578                        && repository_handle
 579                            .read(cx)
 580                            .repository_entry
 581                            .work_directory_id()
 582                            == work_directory_id
 583                })
 584                .context("missing repository handle")?;
 585            anyhow::Ok(repository_handle)
 586        })?
 587    }
 588}
 589
 590impl GitRepo {}
 591
 592impl Repository {
 593    pub fn git_store(&self) -> Option<Entity<GitStore>> {
 594        self.git_store.upgrade()
 595    }
 596
 597    fn id(&self) -> (WorktreeId, ProjectEntryId) {
 598        (self.worktree_id, self.repository_entry.work_directory_id())
 599    }
 600
 601    pub fn current_branch(&self) -> Option<&Branch> {
 602        self.repository_entry.branch()
 603    }
 604
 605    fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
 606    where
 607        F: FnOnce(GitRepo) -> Fut + 'static,
 608        Fut: Future<Output = R> + Send + 'static,
 609        R: Send + 'static,
 610    {
 611        self.send_keyed_job(None, job)
 612    }
 613
 614    fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
 615    where
 616        F: FnOnce(GitRepo) -> Fut + 'static,
 617        Fut: Future<Output = R> + Send + 'static,
 618        R: Send + 'static,
 619    {
 620        let (result_tx, result_rx) = futures::channel::oneshot::channel();
 621        let git_repo = self.git_repo.clone();
 622        self.job_sender
 623            .unbounded_send(GitJob {
 624                key,
 625                job: Box::new(|cx: &mut AsyncApp| {
 626                    let job = job(git_repo);
 627                    cx.background_spawn(async move {
 628                        let result = job.await;
 629                        result_tx.send(result).ok();
 630                    })
 631                }),
 632            })
 633            .ok();
 634        result_rx
 635    }
 636
 637    pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
 638        maybe!({
 639            let project_path = self.repo_path_to_project_path(&"".into())?;
 640            let worktree_name = project
 641                .worktree_for_id(project_path.worktree_id, cx)?
 642                .read(cx)
 643                .root_name();
 644
 645            let mut path = PathBuf::new();
 646            path = path.join(worktree_name);
 647            path = path.join(project_path.path);
 648            Some(path.to_string_lossy().to_string())
 649        })
 650        .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
 651        .into()
 652    }
 653
 654    pub fn activate(&self, cx: &mut Context<Self>) {
 655        let Some(git_store) = self.git_store.upgrade() else {
 656            return;
 657        };
 658        let entity = cx.entity();
 659        git_store.update(cx, |git_store, cx| {
 660            let Some(index) = git_store
 661                .repositories
 662                .iter()
 663                .position(|handle| *handle == entity)
 664            else {
 665                return;
 666            };
 667            git_store.active_index = Some(index);
 668            cx.emit(GitEvent::ActiveRepositoryChanged);
 669        });
 670    }
 671
 672    pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
 673        self.repository_entry.status()
 674    }
 675
 676    pub fn has_conflict(&self, path: &RepoPath) -> bool {
 677        self.repository_entry
 678            .current_merge_conflicts
 679            .contains(&path)
 680    }
 681
 682    pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
 683        let path = self.repository_entry.unrelativize(path)?;
 684        Some((self.worktree_id, path).into())
 685    }
 686
 687    pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
 688        self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
 689    }
 690
 691    pub fn worktree_id_path_to_repo_path(
 692        &self,
 693        worktree_id: WorktreeId,
 694        path: &Path,
 695    ) -> Option<RepoPath> {
 696        if worktree_id != self.worktree_id {
 697            return None;
 698        }
 699        self.repository_entry.relativize(path).log_err()
 700    }
 701
 702    pub fn open_commit_buffer(
 703        &mut self,
 704        languages: Option<Arc<LanguageRegistry>>,
 705        buffer_store: Entity<BufferStore>,
 706        cx: &mut Context<Self>,
 707    ) -> Task<Result<Entity<Buffer>>> {
 708        if let Some(buffer) = self.commit_message_buffer.clone() {
 709            return Task::ready(Ok(buffer));
 710        }
 711
 712        if let GitRepo::Remote {
 713            project_id,
 714            client,
 715            worktree_id,
 716            work_directory_id,
 717        } = self.git_repo.clone()
 718        {
 719            let client = client.clone();
 720            cx.spawn(|repository, mut cx| async move {
 721                let request = client.request(proto::OpenCommitMessageBuffer {
 722                    project_id: project_id.0,
 723                    worktree_id: worktree_id.to_proto(),
 724                    work_directory_id: work_directory_id.to_proto(),
 725                });
 726                let response = request.await.context("requesting to open commit buffer")?;
 727                let buffer_id = BufferId::new(response.buffer_id)?;
 728                let buffer = buffer_store
 729                    .update(&mut cx, |buffer_store, cx| {
 730                        buffer_store.wait_for_remote_buffer(buffer_id, cx)
 731                    })?
 732                    .await?;
 733                if let Some(language_registry) = languages {
 734                    let git_commit_language =
 735                        language_registry.language_for_name("Git Commit").await?;
 736                    buffer.update(&mut cx, |buffer, cx| {
 737                        buffer.set_language(Some(git_commit_language), cx);
 738                    })?;
 739                }
 740                repository.update(&mut cx, |repository, _| {
 741                    repository.commit_message_buffer = Some(buffer.clone());
 742                })?;
 743                Ok(buffer)
 744            })
 745        } else {
 746            self.open_local_commit_buffer(languages, buffer_store, cx)
 747        }
 748    }
 749
 750    fn open_local_commit_buffer(
 751        &mut self,
 752        language_registry: Option<Arc<LanguageRegistry>>,
 753        buffer_store: Entity<BufferStore>,
 754        cx: &mut Context<Self>,
 755    ) -> Task<Result<Entity<Buffer>>> {
 756        let merge_message = self.merge_message.clone();
 757        cx.spawn(|repository, mut cx| async move {
 758            let buffer = buffer_store
 759                .update(&mut cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
 760                .await?;
 761
 762            if let Some(language_registry) = language_registry {
 763                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
 764                buffer.update(&mut cx, |buffer, cx| {
 765                    buffer.set_language(Some(git_commit_language), cx);
 766                })?;
 767            }
 768
 769            if let Some(merge_message) = merge_message {
 770                buffer.update(&mut cx, |buffer, cx| {
 771                    buffer.set_text(merge_message.as_str(), cx)
 772                })?;
 773            }
 774
 775            repository.update(&mut cx, |repository, _| {
 776                repository.commit_message_buffer = Some(buffer.clone());
 777            })?;
 778            Ok(buffer)
 779        })
 780    }
 781
 782    pub fn checkout_files(
 783        &self,
 784        commit: &str,
 785        paths: Vec<RepoPath>,
 786    ) -> oneshot::Receiver<Result<()>> {
 787        let commit = commit.to_string();
 788        self.send_job(|git_repo| async move {
 789            match git_repo {
 790                GitRepo::Local(repo) => repo.checkout_files(&commit, &paths),
 791                GitRepo::Remote {
 792                    project_id,
 793                    client,
 794                    worktree_id,
 795                    work_directory_id,
 796                } => {
 797                    client
 798                        .request(proto::GitCheckoutFiles {
 799                            project_id: project_id.0,
 800                            worktree_id: worktree_id.to_proto(),
 801                            work_directory_id: work_directory_id.to_proto(),
 802                            commit,
 803                            paths: paths
 804                                .into_iter()
 805                                .map(|p| p.to_string_lossy().to_string())
 806                                .collect(),
 807                        })
 808                        .await?;
 809
 810                    Ok(())
 811                }
 812            }
 813        })
 814    }
 815
 816    pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> {
 817        let commit = commit.to_string();
 818        self.send_job(|git_repo| async move {
 819            match git_repo {
 820                GitRepo::Local(git_repo) => git_repo.reset(&commit, reset_mode),
 821                GitRepo::Remote {
 822                    project_id,
 823                    client,
 824                    worktree_id,
 825                    work_directory_id,
 826                } => {
 827                    client
 828                        .request(proto::GitReset {
 829                            project_id: project_id.0,
 830                            worktree_id: worktree_id.to_proto(),
 831                            work_directory_id: work_directory_id.to_proto(),
 832                            commit,
 833                            mode: match reset_mode {
 834                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
 835                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
 836                            },
 837                        })
 838                        .await?;
 839
 840                    Ok(())
 841                }
 842            }
 843        })
 844    }
 845
 846    pub fn show(&self, commit: &str) -> oneshot::Receiver<Result<CommitDetails>> {
 847        let commit = commit.to_string();
 848        self.send_job(|git_repo| async move {
 849            match git_repo {
 850                GitRepo::Local(git_repository) => git_repository.show(&commit),
 851                GitRepo::Remote {
 852                    project_id,
 853                    client,
 854                    worktree_id,
 855                    work_directory_id,
 856                } => {
 857                    let resp = client
 858                        .request(proto::GitShow {
 859                            project_id: project_id.0,
 860                            worktree_id: worktree_id.to_proto(),
 861                            work_directory_id: work_directory_id.to_proto(),
 862                            commit,
 863                        })
 864                        .await?;
 865
 866                    Ok(CommitDetails {
 867                        sha: resp.sha.into(),
 868                        message: resp.message.into(),
 869                        commit_timestamp: resp.commit_timestamp,
 870                        committer_email: resp.committer_email.into(),
 871                        committer_name: resp.committer_name.into(),
 872                    })
 873                }
 874            }
 875        })
 876    }
 877
 878    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
 879        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
 880    }
 881
 882    pub fn stage_entries(
 883        &self,
 884        entries: Vec<RepoPath>,
 885        cx: &mut Context<Self>,
 886    ) -> Task<anyhow::Result<()>> {
 887        if entries.is_empty() {
 888            return Task::ready(Ok(()));
 889        }
 890
 891        let mut save_futures = Vec::new();
 892        if let Some(buffer_store) = self.buffer_store(cx) {
 893            buffer_store.update(cx, |buffer_store, cx| {
 894                for path in &entries {
 895                    let Some(path) = self.repository_entry.unrelativize(path) else {
 896                        continue;
 897                    };
 898                    let project_path = (self.worktree_id, path).into();
 899                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
 900                        if buffer
 901                            .read(cx)
 902                            .file()
 903                            .map_or(false, |file| file.disk_state().exists())
 904                        {
 905                            save_futures.push(buffer_store.save_buffer(buffer, cx));
 906                        }
 907                    }
 908                }
 909            })
 910        }
 911
 912        cx.spawn(|this, mut cx| async move {
 913            for save_future in save_futures {
 914                save_future.await?;
 915            }
 916
 917            this.update(&mut cx, |this, _| {
 918                this.send_job(|git_repo| async move {
 919                    match git_repo {
 920                        GitRepo::Local(repo) => repo.stage_paths(&entries),
 921                        GitRepo::Remote {
 922                            project_id,
 923                            client,
 924                            worktree_id,
 925                            work_directory_id,
 926                        } => {
 927                            client
 928                                .request(proto::Stage {
 929                                    project_id: project_id.0,
 930                                    worktree_id: worktree_id.to_proto(),
 931                                    work_directory_id: work_directory_id.to_proto(),
 932                                    paths: entries
 933                                        .into_iter()
 934                                        .map(|repo_path| repo_path.as_ref().to_proto())
 935                                        .collect(),
 936                                })
 937                                .await
 938                                .context("sending stage request")?;
 939
 940                            Ok(())
 941                        }
 942                    }
 943                })
 944            })?
 945            .await??;
 946
 947            Ok(())
 948        })
 949    }
 950
 951    pub fn unstage_entries(
 952        &self,
 953        entries: Vec<RepoPath>,
 954        cx: &mut Context<Self>,
 955    ) -> Task<anyhow::Result<()>> {
 956        if entries.is_empty() {
 957            return Task::ready(Ok(()));
 958        }
 959
 960        let mut save_futures = Vec::new();
 961        if let Some(buffer_store) = self.buffer_store(cx) {
 962            buffer_store.update(cx, |buffer_store, cx| {
 963                for path in &entries {
 964                    let Some(path) = self.repository_entry.unrelativize(path) else {
 965                        continue;
 966                    };
 967                    let project_path = (self.worktree_id, path).into();
 968                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
 969                        if buffer
 970                            .read(cx)
 971                            .file()
 972                            .map_or(false, |file| file.disk_state().exists())
 973                        {
 974                            save_futures.push(buffer_store.save_buffer(buffer, cx));
 975                        }
 976                    }
 977                }
 978            })
 979        }
 980
 981        cx.spawn(move |this, mut cx| async move {
 982            for save_future in save_futures {
 983                save_future.await?;
 984            }
 985
 986            this.update(&mut cx, |this, _| {
 987                this.send_job(|git_repo| async move {
 988                    match git_repo {
 989                        GitRepo::Local(repo) => repo.unstage_paths(&entries),
 990                        GitRepo::Remote {
 991                            project_id,
 992                            client,
 993                            worktree_id,
 994                            work_directory_id,
 995                        } => {
 996                            client
 997                                .request(proto::Unstage {
 998                                    project_id: project_id.0,
 999                                    worktree_id: worktree_id.to_proto(),
1000                                    work_directory_id: work_directory_id.to_proto(),
1001                                    paths: entries
1002                                        .into_iter()
1003                                        .map(|repo_path| repo_path.as_ref().to_proto())
1004                                        .collect(),
1005                                })
1006                                .await
1007                                .context("sending unstage request")?;
1008
1009                            Ok(())
1010                        }
1011                    }
1012                })
1013            })?
1014            .await??;
1015
1016            Ok(())
1017        })
1018    }
1019
1020    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1021        let to_stage = self
1022            .repository_entry
1023            .status()
1024            .filter(|entry| !entry.status.is_staged().unwrap_or(false))
1025            .map(|entry| entry.repo_path.clone())
1026            .collect();
1027        self.stage_entries(to_stage, cx)
1028    }
1029
1030    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1031        let to_unstage = self
1032            .repository_entry
1033            .status()
1034            .filter(|entry| entry.status.is_staged().unwrap_or(true))
1035            .map(|entry| entry.repo_path.clone())
1036            .collect();
1037        self.unstage_entries(to_unstage, cx)
1038    }
1039
1040    /// Get a count of all entries in the active repository, including
1041    /// untracked files.
1042    pub fn entry_count(&self) -> usize {
1043        self.repository_entry.status_len()
1044    }
1045
1046    pub fn commit(
1047        &self,
1048        message: SharedString,
1049        name_and_email: Option<(SharedString, SharedString)>,
1050    ) -> oneshot::Receiver<Result<()>> {
1051        self.send_job(|git_repo| async move {
1052            match git_repo {
1053                GitRepo::Local(repo) => repo.commit(
1054                    message.as_ref(),
1055                    name_and_email
1056                        .as_ref()
1057                        .map(|(name, email)| (name.as_ref(), email.as_ref())),
1058                ),
1059                GitRepo::Remote {
1060                    project_id,
1061                    client,
1062                    worktree_id,
1063                    work_directory_id,
1064                } => {
1065                    let (name, email) = name_and_email.unzip();
1066                    client
1067                        .request(proto::Commit {
1068                            project_id: project_id.0,
1069                            worktree_id: worktree_id.to_proto(),
1070                            work_directory_id: work_directory_id.to_proto(),
1071                            message: String::from(message),
1072                            name: name.map(String::from),
1073                            email: email.map(String::from),
1074                        })
1075                        .await
1076                        .context("sending commit request")?;
1077
1078                    Ok(())
1079                }
1080            }
1081        })
1082    }
1083
1084    pub fn fetch(&self) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1085        self.send_job(|git_repo| async move {
1086            match git_repo {
1087                GitRepo::Local(git_repository) => git_repository.fetch(),
1088                GitRepo::Remote {
1089                    project_id,
1090                    client,
1091                    worktree_id,
1092                    work_directory_id,
1093                } => {
1094                    let response = client
1095                        .request(proto::Fetch {
1096                            project_id: project_id.0,
1097                            worktree_id: worktree_id.to_proto(),
1098                            work_directory_id: work_directory_id.to_proto(),
1099                        })
1100                        .await
1101                        .context("sending fetch request")?;
1102
1103                    Ok(RemoteCommandOutput {
1104                        stdout: response.stdout,
1105                        stderr: response.stderr,
1106                    })
1107                }
1108            }
1109        })
1110    }
1111
1112    pub fn push(
1113        &self,
1114        branch: SharedString,
1115        remote: SharedString,
1116        options: Option<PushOptions>,
1117    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1118        self.send_job(move |git_repo| async move {
1119            match git_repo {
1120                GitRepo::Local(git_repository) => git_repository.push(&branch, &remote, options),
1121                GitRepo::Remote {
1122                    project_id,
1123                    client,
1124                    worktree_id,
1125                    work_directory_id,
1126                } => {
1127                    let response = client
1128                        .request(proto::Push {
1129                            project_id: project_id.0,
1130                            worktree_id: worktree_id.to_proto(),
1131                            work_directory_id: work_directory_id.to_proto(),
1132                            branch_name: branch.to_string(),
1133                            remote_name: remote.to_string(),
1134                            options: options.map(|options| match options {
1135                                PushOptions::Force => proto::push::PushOptions::Force,
1136                                PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
1137                            } as i32),
1138                        })
1139                        .await
1140                        .context("sending push request")?;
1141
1142                    Ok(RemoteCommandOutput {
1143                        stdout: response.stdout,
1144                        stderr: response.stderr,
1145                    })
1146                }
1147            }
1148        })
1149    }
1150
1151    pub fn pull(
1152        &self,
1153        branch: SharedString,
1154        remote: SharedString,
1155    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1156        self.send_job(|git_repo| async move {
1157            match git_repo {
1158                GitRepo::Local(git_repository) => git_repository.pull(&branch, &remote),
1159                GitRepo::Remote {
1160                    project_id,
1161                    client,
1162                    worktree_id,
1163                    work_directory_id,
1164                } => {
1165                    let response = client
1166                        .request(proto::Pull {
1167                            project_id: project_id.0,
1168                            worktree_id: worktree_id.to_proto(),
1169                            work_directory_id: work_directory_id.to_proto(),
1170                            branch_name: branch.to_string(),
1171                            remote_name: remote.to_string(),
1172                        })
1173                        .await
1174                        .context("sending pull request")?;
1175
1176                    Ok(RemoteCommandOutput {
1177                        stdout: response.stdout,
1178                        stderr: response.stderr,
1179                    })
1180                }
1181            }
1182        })
1183    }
1184
1185    pub fn set_index_text(
1186        &self,
1187        path: &RepoPath,
1188        content: Option<String>,
1189    ) -> oneshot::Receiver<anyhow::Result<()>> {
1190        let path = path.clone();
1191        self.send_keyed_job(
1192            Some(GitJobKey::WriteIndex(path.clone())),
1193            |git_repo| async move {
1194                match git_repo {
1195                    GitRepo::Local(repo) => repo.set_index_text(&path, content),
1196                    GitRepo::Remote {
1197                        project_id,
1198                        client,
1199                        worktree_id,
1200                        work_directory_id,
1201                    } => {
1202                        client
1203                            .request(proto::SetIndexText {
1204                                project_id: project_id.0,
1205                                worktree_id: worktree_id.to_proto(),
1206                                work_directory_id: work_directory_id.to_proto(),
1207                                path: path.as_ref().to_proto(),
1208                                text: content,
1209                            })
1210                            .await?;
1211                        Ok(())
1212                    }
1213                }
1214            },
1215        )
1216    }
1217
1218    pub fn get_remotes(
1219        &self,
1220        branch_name: Option<String>,
1221    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
1222        self.send_job(|repo| async move {
1223            match repo {
1224                GitRepo::Local(git_repository) => {
1225                    git_repository.get_remotes(branch_name.as_deref())
1226                }
1227                GitRepo::Remote {
1228                    project_id,
1229                    client,
1230                    worktree_id,
1231                    work_directory_id,
1232                } => {
1233                    let response = client
1234                        .request(proto::GetRemotes {
1235                            project_id: project_id.0,
1236                            worktree_id: worktree_id.to_proto(),
1237                            work_directory_id: work_directory_id.to_proto(),
1238                            branch_name,
1239                        })
1240                        .await?;
1241
1242                    let remotes = response
1243                        .remotes
1244                        .into_iter()
1245                        .map(|remotes| git::repository::Remote {
1246                            name: remotes.name.into(),
1247                        })
1248                        .collect();
1249
1250                    Ok(remotes)
1251                }
1252            }
1253        })
1254    }
1255}