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