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