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, WorkDirectory};
  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    // note: callers must verify these come from the same worktree
 692    pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
 693        let other_work_dir = &other.read(cx).repository_entry.work_directory;
 694        match (&self.repository_entry.work_directory, other_work_dir) {
 695            (WorkDirectory::InProject { .. }, WorkDirectory::AboveProject { .. }) => false,
 696            (WorkDirectory::AboveProject { .. }, WorkDirectory::InProject { .. }) => true,
 697            (
 698                WorkDirectory::InProject {
 699                    relative_path: this_path,
 700                },
 701                WorkDirectory::InProject {
 702                    relative_path: other_path,
 703                },
 704            ) => other_path.starts_with(this_path),
 705            (
 706                WorkDirectory::AboveProject {
 707                    absolute_path: this_path,
 708                    ..
 709                },
 710                WorkDirectory::AboveProject {
 711                    absolute_path: other_path,
 712                    ..
 713                },
 714            ) => other_path.starts_with(this_path),
 715        }
 716    }
 717
 718    pub fn worktree_id_path_to_repo_path(
 719        &self,
 720        worktree_id: WorktreeId,
 721        path: &Path,
 722    ) -> Option<RepoPath> {
 723        if worktree_id != self.worktree_id {
 724            return None;
 725        }
 726        self.repository_entry.relativize(path).log_err()
 727    }
 728
 729    pub fn open_commit_buffer(
 730        &mut self,
 731        languages: Option<Arc<LanguageRegistry>>,
 732        buffer_store: Entity<BufferStore>,
 733        cx: &mut Context<Self>,
 734    ) -> Task<Result<Entity<Buffer>>> {
 735        if let Some(buffer) = self.commit_message_buffer.clone() {
 736            return Task::ready(Ok(buffer));
 737        }
 738
 739        if let GitRepo::Remote {
 740            project_id,
 741            client,
 742            worktree_id,
 743            work_directory_id,
 744        } = self.git_repo.clone()
 745        {
 746            let client = client.clone();
 747            cx.spawn(|repository, mut cx| async move {
 748                let request = client.request(proto::OpenCommitMessageBuffer {
 749                    project_id: project_id.0,
 750                    worktree_id: worktree_id.to_proto(),
 751                    work_directory_id: work_directory_id.to_proto(),
 752                });
 753                let response = request.await.context("requesting to open commit buffer")?;
 754                let buffer_id = BufferId::new(response.buffer_id)?;
 755                let buffer = buffer_store
 756                    .update(&mut cx, |buffer_store, cx| {
 757                        buffer_store.wait_for_remote_buffer(buffer_id, cx)
 758                    })?
 759                    .await?;
 760                if let Some(language_registry) = languages {
 761                    let git_commit_language =
 762                        language_registry.language_for_name("Git Commit").await?;
 763                    buffer.update(&mut cx, |buffer, cx| {
 764                        buffer.set_language(Some(git_commit_language), cx);
 765                    })?;
 766                }
 767                repository.update(&mut cx, |repository, _| {
 768                    repository.commit_message_buffer = Some(buffer.clone());
 769                })?;
 770                Ok(buffer)
 771            })
 772        } else {
 773            self.open_local_commit_buffer(languages, buffer_store, cx)
 774        }
 775    }
 776
 777    fn open_local_commit_buffer(
 778        &mut self,
 779        language_registry: Option<Arc<LanguageRegistry>>,
 780        buffer_store: Entity<BufferStore>,
 781        cx: &mut Context<Self>,
 782    ) -> Task<Result<Entity<Buffer>>> {
 783        let merge_message = self.merge_message.clone();
 784        cx.spawn(|repository, mut cx| async move {
 785            let buffer = buffer_store
 786                .update(&mut cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
 787                .await?;
 788
 789            if let Some(language_registry) = language_registry {
 790                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
 791                buffer.update(&mut cx, |buffer, cx| {
 792                    buffer.set_language(Some(git_commit_language), cx);
 793                })?;
 794            }
 795
 796            if let Some(merge_message) = merge_message {
 797                buffer.update(&mut cx, |buffer, cx| {
 798                    buffer.set_text(merge_message.as_str(), cx)
 799                })?;
 800            }
 801
 802            repository.update(&mut cx, |repository, _| {
 803                repository.commit_message_buffer = Some(buffer.clone());
 804            })?;
 805            Ok(buffer)
 806        })
 807    }
 808
 809    pub fn checkout_files(
 810        &self,
 811        commit: &str,
 812        paths: Vec<RepoPath>,
 813    ) -> oneshot::Receiver<Result<()>> {
 814        let commit = commit.to_string();
 815        self.send_job(|git_repo| async move {
 816            match git_repo {
 817                GitRepo::Local(repo) => repo.checkout_files(&commit, &paths),
 818                GitRepo::Remote {
 819                    project_id,
 820                    client,
 821                    worktree_id,
 822                    work_directory_id,
 823                } => {
 824                    client
 825                        .request(proto::GitCheckoutFiles {
 826                            project_id: project_id.0,
 827                            worktree_id: worktree_id.to_proto(),
 828                            work_directory_id: work_directory_id.to_proto(),
 829                            commit,
 830                            paths: paths
 831                                .into_iter()
 832                                .map(|p| p.to_string_lossy().to_string())
 833                                .collect(),
 834                        })
 835                        .await?;
 836
 837                    Ok(())
 838                }
 839            }
 840        })
 841    }
 842
 843    pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> {
 844        let commit = commit.to_string();
 845        self.send_job(|git_repo| async move {
 846            match git_repo {
 847                GitRepo::Local(git_repo) => git_repo.reset(&commit, reset_mode),
 848                GitRepo::Remote {
 849                    project_id,
 850                    client,
 851                    worktree_id,
 852                    work_directory_id,
 853                } => {
 854                    client
 855                        .request(proto::GitReset {
 856                            project_id: project_id.0,
 857                            worktree_id: worktree_id.to_proto(),
 858                            work_directory_id: work_directory_id.to_proto(),
 859                            commit,
 860                            mode: match reset_mode {
 861                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
 862                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
 863                            },
 864                        })
 865                        .await?;
 866
 867                    Ok(())
 868                }
 869            }
 870        })
 871    }
 872
 873    pub fn show(&self, commit: &str) -> oneshot::Receiver<Result<CommitDetails>> {
 874        let commit = commit.to_string();
 875        self.send_job(|git_repo| async move {
 876            match git_repo {
 877                GitRepo::Local(git_repository) => git_repository.show(&commit),
 878                GitRepo::Remote {
 879                    project_id,
 880                    client,
 881                    worktree_id,
 882                    work_directory_id,
 883                } => {
 884                    let resp = client
 885                        .request(proto::GitShow {
 886                            project_id: project_id.0,
 887                            worktree_id: worktree_id.to_proto(),
 888                            work_directory_id: work_directory_id.to_proto(),
 889                            commit,
 890                        })
 891                        .await?;
 892
 893                    Ok(CommitDetails {
 894                        sha: resp.sha.into(),
 895                        message: resp.message.into(),
 896                        commit_timestamp: resp.commit_timestamp,
 897                        committer_email: resp.committer_email.into(),
 898                        committer_name: resp.committer_name.into(),
 899                    })
 900                }
 901            }
 902        })
 903    }
 904
 905    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
 906        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
 907    }
 908
 909    pub fn stage_entries(
 910        &self,
 911        entries: Vec<RepoPath>,
 912        cx: &mut Context<Self>,
 913    ) -> Task<anyhow::Result<()>> {
 914        if entries.is_empty() {
 915            return Task::ready(Ok(()));
 916        }
 917
 918        let mut save_futures = Vec::new();
 919        if let Some(buffer_store) = self.buffer_store(cx) {
 920            buffer_store.update(cx, |buffer_store, cx| {
 921                for path in &entries {
 922                    let Some(path) = self.repository_entry.unrelativize(path) else {
 923                        continue;
 924                    };
 925                    let project_path = (self.worktree_id, path).into();
 926                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
 927                        if buffer
 928                            .read(cx)
 929                            .file()
 930                            .map_or(false, |file| file.disk_state().exists())
 931                        {
 932                            save_futures.push(buffer_store.save_buffer(buffer, cx));
 933                        }
 934                    }
 935                }
 936            })
 937        }
 938
 939        cx.spawn(|this, mut cx| async move {
 940            for save_future in save_futures {
 941                save_future.await?;
 942            }
 943
 944            this.update(&mut cx, |this, _| {
 945                this.send_job(|git_repo| async move {
 946                    match git_repo {
 947                        GitRepo::Local(repo) => repo.stage_paths(&entries),
 948                        GitRepo::Remote {
 949                            project_id,
 950                            client,
 951                            worktree_id,
 952                            work_directory_id,
 953                        } => {
 954                            client
 955                                .request(proto::Stage {
 956                                    project_id: project_id.0,
 957                                    worktree_id: worktree_id.to_proto(),
 958                                    work_directory_id: work_directory_id.to_proto(),
 959                                    paths: entries
 960                                        .into_iter()
 961                                        .map(|repo_path| repo_path.as_ref().to_proto())
 962                                        .collect(),
 963                                })
 964                                .await
 965                                .context("sending stage request")?;
 966
 967                            Ok(())
 968                        }
 969                    }
 970                })
 971            })?
 972            .await??;
 973
 974            Ok(())
 975        })
 976    }
 977
 978    pub fn unstage_entries(
 979        &self,
 980        entries: Vec<RepoPath>,
 981        cx: &mut Context<Self>,
 982    ) -> Task<anyhow::Result<()>> {
 983        if entries.is_empty() {
 984            return Task::ready(Ok(()));
 985        }
 986
 987        let mut save_futures = Vec::new();
 988        if let Some(buffer_store) = self.buffer_store(cx) {
 989            buffer_store.update(cx, |buffer_store, cx| {
 990                for path in &entries {
 991                    let Some(path) = self.repository_entry.unrelativize(path) else {
 992                        continue;
 993                    };
 994                    let project_path = (self.worktree_id, path).into();
 995                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
 996                        if buffer
 997                            .read(cx)
 998                            .file()
 999                            .map_or(false, |file| file.disk_state().exists())
1000                        {
1001                            save_futures.push(buffer_store.save_buffer(buffer, cx));
1002                        }
1003                    }
1004                }
1005            })
1006        }
1007
1008        cx.spawn(move |this, mut cx| async move {
1009            for save_future in save_futures {
1010                save_future.await?;
1011            }
1012
1013            this.update(&mut cx, |this, _| {
1014                this.send_job(|git_repo| async move {
1015                    match git_repo {
1016                        GitRepo::Local(repo) => repo.unstage_paths(&entries),
1017                        GitRepo::Remote {
1018                            project_id,
1019                            client,
1020                            worktree_id,
1021                            work_directory_id,
1022                        } => {
1023                            client
1024                                .request(proto::Unstage {
1025                                    project_id: project_id.0,
1026                                    worktree_id: worktree_id.to_proto(),
1027                                    work_directory_id: work_directory_id.to_proto(),
1028                                    paths: entries
1029                                        .into_iter()
1030                                        .map(|repo_path| repo_path.as_ref().to_proto())
1031                                        .collect(),
1032                                })
1033                                .await
1034                                .context("sending unstage request")?;
1035
1036                            Ok(())
1037                        }
1038                    }
1039                })
1040            })?
1041            .await??;
1042
1043            Ok(())
1044        })
1045    }
1046
1047    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1048        let to_stage = self
1049            .repository_entry
1050            .status()
1051            .filter(|entry| !entry.status.is_staged().unwrap_or(false))
1052            .map(|entry| entry.repo_path.clone())
1053            .collect();
1054        self.stage_entries(to_stage, cx)
1055    }
1056
1057    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1058        let to_unstage = self
1059            .repository_entry
1060            .status()
1061            .filter(|entry| entry.status.is_staged().unwrap_or(true))
1062            .map(|entry| entry.repo_path.clone())
1063            .collect();
1064        self.unstage_entries(to_unstage, cx)
1065    }
1066
1067    /// Get a count of all entries in the active repository, including
1068    /// untracked files.
1069    pub fn entry_count(&self) -> usize {
1070        self.repository_entry.status_len()
1071    }
1072
1073    pub fn commit(
1074        &self,
1075        message: SharedString,
1076        name_and_email: Option<(SharedString, SharedString)>,
1077    ) -> oneshot::Receiver<Result<()>> {
1078        self.send_job(|git_repo| async move {
1079            match git_repo {
1080                GitRepo::Local(repo) => repo.commit(
1081                    message.as_ref(),
1082                    name_and_email
1083                        .as_ref()
1084                        .map(|(name, email)| (name.as_ref(), email.as_ref())),
1085                ),
1086                GitRepo::Remote {
1087                    project_id,
1088                    client,
1089                    worktree_id,
1090                    work_directory_id,
1091                } => {
1092                    let (name, email) = name_and_email.unzip();
1093                    client
1094                        .request(proto::Commit {
1095                            project_id: project_id.0,
1096                            worktree_id: worktree_id.to_proto(),
1097                            work_directory_id: work_directory_id.to_proto(),
1098                            message: String::from(message),
1099                            name: name.map(String::from),
1100                            email: email.map(String::from),
1101                        })
1102                        .await
1103                        .context("sending commit request")?;
1104
1105                    Ok(())
1106                }
1107            }
1108        })
1109    }
1110
1111    pub fn fetch(&self) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1112        self.send_job(|git_repo| async move {
1113            match git_repo {
1114                GitRepo::Local(git_repository) => git_repository.fetch(),
1115                GitRepo::Remote {
1116                    project_id,
1117                    client,
1118                    worktree_id,
1119                    work_directory_id,
1120                } => {
1121                    let response = client
1122                        .request(proto::Fetch {
1123                            project_id: project_id.0,
1124                            worktree_id: worktree_id.to_proto(),
1125                            work_directory_id: work_directory_id.to_proto(),
1126                        })
1127                        .await
1128                        .context("sending fetch request")?;
1129
1130                    Ok(RemoteCommandOutput {
1131                        stdout: response.stdout,
1132                        stderr: response.stderr,
1133                    })
1134                }
1135            }
1136        })
1137    }
1138
1139    pub fn push(
1140        &self,
1141        branch: SharedString,
1142        remote: SharedString,
1143        options: Option<PushOptions>,
1144    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1145        self.send_job(move |git_repo| async move {
1146            match git_repo {
1147                GitRepo::Local(git_repository) => git_repository.push(&branch, &remote, options),
1148                GitRepo::Remote {
1149                    project_id,
1150                    client,
1151                    worktree_id,
1152                    work_directory_id,
1153                } => {
1154                    let response = client
1155                        .request(proto::Push {
1156                            project_id: project_id.0,
1157                            worktree_id: worktree_id.to_proto(),
1158                            work_directory_id: work_directory_id.to_proto(),
1159                            branch_name: branch.to_string(),
1160                            remote_name: remote.to_string(),
1161                            options: options.map(|options| match options {
1162                                PushOptions::Force => proto::push::PushOptions::Force,
1163                                PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
1164                            } as i32),
1165                        })
1166                        .await
1167                        .context("sending push request")?;
1168
1169                    Ok(RemoteCommandOutput {
1170                        stdout: response.stdout,
1171                        stderr: response.stderr,
1172                    })
1173                }
1174            }
1175        })
1176    }
1177
1178    pub fn pull(
1179        &self,
1180        branch: SharedString,
1181        remote: SharedString,
1182    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1183        self.send_job(|git_repo| async move {
1184            match git_repo {
1185                GitRepo::Local(git_repository) => git_repository.pull(&branch, &remote),
1186                GitRepo::Remote {
1187                    project_id,
1188                    client,
1189                    worktree_id,
1190                    work_directory_id,
1191                } => {
1192                    let response = client
1193                        .request(proto::Pull {
1194                            project_id: project_id.0,
1195                            worktree_id: worktree_id.to_proto(),
1196                            work_directory_id: work_directory_id.to_proto(),
1197                            branch_name: branch.to_string(),
1198                            remote_name: remote.to_string(),
1199                        })
1200                        .await
1201                        .context("sending pull request")?;
1202
1203                    Ok(RemoteCommandOutput {
1204                        stdout: response.stdout,
1205                        stderr: response.stderr,
1206                    })
1207                }
1208            }
1209        })
1210    }
1211
1212    pub fn set_index_text(
1213        &self,
1214        path: &RepoPath,
1215        content: Option<String>,
1216    ) -> oneshot::Receiver<anyhow::Result<()>> {
1217        let path = path.clone();
1218        self.send_keyed_job(
1219            Some(GitJobKey::WriteIndex(path.clone())),
1220            |git_repo| async move {
1221                match git_repo {
1222                    GitRepo::Local(repo) => repo.set_index_text(&path, content),
1223                    GitRepo::Remote {
1224                        project_id,
1225                        client,
1226                        worktree_id,
1227                        work_directory_id,
1228                    } => {
1229                        client
1230                            .request(proto::SetIndexText {
1231                                project_id: project_id.0,
1232                                worktree_id: worktree_id.to_proto(),
1233                                work_directory_id: work_directory_id.to_proto(),
1234                                path: path.as_ref().to_proto(),
1235                                text: content,
1236                            })
1237                            .await?;
1238                        Ok(())
1239                    }
1240                }
1241            },
1242        )
1243    }
1244
1245    pub fn get_remotes(
1246        &self,
1247        branch_name: Option<String>,
1248    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
1249        self.send_job(|repo| async move {
1250            match repo {
1251                GitRepo::Local(git_repository) => {
1252                    git_repository.get_remotes(branch_name.as_deref())
1253                }
1254                GitRepo::Remote {
1255                    project_id,
1256                    client,
1257                    worktree_id,
1258                    work_directory_id,
1259                } => {
1260                    let response = client
1261                        .request(proto::GetRemotes {
1262                            project_id: project_id.0,
1263                            worktree_id: worktree_id.to_proto(),
1264                            work_directory_id: work_directory_id.to_proto(),
1265                            branch_name,
1266                        })
1267                        .await?;
1268
1269                    let remotes = response
1270                        .remotes
1271                        .into_iter()
1272                        .map(|remotes| git::repository::Remote {
1273                            name: remotes.name.into(),
1274                        })
1275                        .collect();
1276
1277                    Ok(remotes)
1278                }
1279            }
1280        })
1281    }
1282}