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, 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::Ack> {
 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        repository_handle
 275            .update(&mut cx, |repository_handle, _cx| repository_handle.fetch())?
 276            .await??;
 277        Ok(proto::Ack {})
 278    }
 279
 280    async fn handle_push(
 281        this: Entity<Self>,
 282        envelope: TypedEnvelope<proto::Push>,
 283        mut cx: AsyncApp,
 284    ) -> Result<proto::Ack> {
 285        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 286        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 287        let repository_handle =
 288            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 289
 290        let options = envelope
 291            .payload
 292            .options
 293            .as_ref()
 294            .map(|_| match envelope.payload.options() {
 295                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
 296                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
 297            });
 298
 299        let branch_name = envelope.payload.branch_name.into();
 300        let remote_name = envelope.payload.remote_name.into();
 301
 302        repository_handle
 303            .update(&mut cx, |repository_handle, _cx| {
 304                repository_handle.push(branch_name, remote_name, options)
 305            })?
 306            .await??;
 307        Ok(proto::Ack {})
 308    }
 309
 310    async fn handle_pull(
 311        this: Entity<Self>,
 312        envelope: TypedEnvelope<proto::Pull>,
 313        mut cx: AsyncApp,
 314    ) -> Result<proto::Ack> {
 315        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 316        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 317        let repository_handle =
 318            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 319
 320        let branch_name = envelope.payload.branch_name.into();
 321        let remote_name = envelope.payload.remote_name.into();
 322
 323        repository_handle
 324            .update(&mut cx, |repository_handle, _cx| {
 325                repository_handle.pull(branch_name, remote_name)
 326            })?
 327            .await??;
 328        Ok(proto::Ack {})
 329    }
 330
 331    async fn handle_stage(
 332        this: Entity<Self>,
 333        envelope: TypedEnvelope<proto::Stage>,
 334        mut cx: AsyncApp,
 335    ) -> Result<proto::Ack> {
 336        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 337        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 338        let repository_handle =
 339            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 340
 341        let entries = envelope
 342            .payload
 343            .paths
 344            .into_iter()
 345            .map(PathBuf::from)
 346            .map(RepoPath::new)
 347            .collect();
 348
 349        repository_handle
 350            .update(&mut cx, |repository_handle, cx| {
 351                repository_handle.stage_entries(entries, cx)
 352            })?
 353            .await?;
 354        Ok(proto::Ack {})
 355    }
 356
 357    async fn handle_unstage(
 358        this: Entity<Self>,
 359        envelope: TypedEnvelope<proto::Unstage>,
 360        mut cx: AsyncApp,
 361    ) -> Result<proto::Ack> {
 362        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 363        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 364        let repository_handle =
 365            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 366
 367        let entries = envelope
 368            .payload
 369            .paths
 370            .into_iter()
 371            .map(PathBuf::from)
 372            .map(RepoPath::new)
 373            .collect();
 374
 375        repository_handle
 376            .update(&mut cx, |repository_handle, cx| {
 377                repository_handle.unstage_entries(entries, cx)
 378            })?
 379            .await?;
 380
 381        Ok(proto::Ack {})
 382    }
 383
 384    async fn handle_set_index_text(
 385        this: Entity<Self>,
 386        envelope: TypedEnvelope<proto::SetIndexText>,
 387        mut cx: AsyncApp,
 388    ) -> Result<proto::Ack> {
 389        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 390        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 391        let repository_handle =
 392            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 393
 394        repository_handle
 395            .update(&mut cx, |repository_handle, _| {
 396                repository_handle.set_index_text(
 397                    &RepoPath::from_str(&envelope.payload.path),
 398                    envelope.payload.text,
 399                )
 400            })?
 401            .await??;
 402        Ok(proto::Ack {})
 403    }
 404
 405    async fn handle_commit(
 406        this: Entity<Self>,
 407        envelope: TypedEnvelope<proto::Commit>,
 408        mut cx: AsyncApp,
 409    ) -> Result<proto::Ack> {
 410        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 411        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 412        let repository_handle =
 413            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 414
 415        let message = SharedString::from(envelope.payload.message);
 416        let name = envelope.payload.name.map(SharedString::from);
 417        let email = envelope.payload.email.map(SharedString::from);
 418
 419        repository_handle
 420            .update(&mut cx, |repository_handle, _| {
 421                repository_handle.commit(message, name.zip(email))
 422            })?
 423            .await??;
 424        Ok(proto::Ack {})
 425    }
 426
 427    async fn handle_get_remotes(
 428        this: Entity<Self>,
 429        envelope: TypedEnvelope<proto::GetRemotes>,
 430        mut cx: AsyncApp,
 431    ) -> Result<proto::GetRemotesResponse> {
 432        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 433        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 434        let repository_handle =
 435            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 436
 437        let branch_name = envelope.payload.branch_name;
 438
 439        let remotes = repository_handle
 440            .update(&mut cx, |repository_handle, _| {
 441                repository_handle.get_remotes(branch_name)
 442            })?
 443            .await??;
 444
 445        Ok(proto::GetRemotesResponse {
 446            remotes: remotes
 447                .into_iter()
 448                .map(|remotes| proto::get_remotes_response::Remote {
 449                    name: remotes.name.to_string(),
 450                })
 451                .collect::<Vec<_>>(),
 452        })
 453    }
 454
 455    async fn handle_show(
 456        this: Entity<Self>,
 457        envelope: TypedEnvelope<proto::GitShow>,
 458        mut cx: AsyncApp,
 459    ) -> Result<proto::GitCommitDetails> {
 460        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 461        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 462        let repository_handle =
 463            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 464
 465        let commit = repository_handle
 466            .update(&mut cx, |repository_handle, _| {
 467                repository_handle.show(&envelope.payload.commit)
 468            })?
 469            .await??;
 470        Ok(proto::GitCommitDetails {
 471            sha: commit.sha.into(),
 472            message: commit.message.into(),
 473            commit_timestamp: commit.commit_timestamp,
 474            committer_email: commit.committer_email.into(),
 475            committer_name: commit.committer_name.into(),
 476        })
 477    }
 478
 479    async fn handle_reset(
 480        this: Entity<Self>,
 481        envelope: TypedEnvelope<proto::GitReset>,
 482        mut cx: AsyncApp,
 483    ) -> Result<proto::Ack> {
 484        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 485        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 486        let repository_handle =
 487            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 488
 489        let mode = match envelope.payload.mode() {
 490            git_reset::ResetMode::Soft => ResetMode::Soft,
 491            git_reset::ResetMode::Mixed => ResetMode::Mixed,
 492        };
 493
 494        repository_handle
 495            .update(&mut cx, |repository_handle, _| {
 496                repository_handle.reset(&envelope.payload.commit, mode)
 497            })?
 498            .await??;
 499        Ok(proto::Ack {})
 500    }
 501
 502    async fn handle_checkout_files(
 503        this: Entity<Self>,
 504        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
 505        mut cx: AsyncApp,
 506    ) -> Result<proto::Ack> {
 507        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 508        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 509        let repository_handle =
 510            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 511        let paths = envelope
 512            .payload
 513            .paths
 514            .iter()
 515            .map(|s| RepoPath::from_str(s))
 516            .collect();
 517
 518        repository_handle
 519            .update(&mut cx, |repository_handle, _| {
 520                repository_handle.checkout_files(&envelope.payload.commit, paths)
 521            })?
 522            .await??;
 523        Ok(proto::Ack {})
 524    }
 525
 526    async fn handle_open_commit_message_buffer(
 527        this: Entity<Self>,
 528        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
 529        mut cx: AsyncApp,
 530    ) -> Result<proto::OpenBufferResponse> {
 531        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 532        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 533        let repository =
 534            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 535        let buffer = repository
 536            .update(&mut cx, |repository, cx| {
 537                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
 538            })?
 539            .await?;
 540
 541        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
 542        this.update(&mut cx, |this, cx| {
 543            this.buffer_store.update(cx, |buffer_store, cx| {
 544                buffer_store
 545                    .create_buffer_for_peer(
 546                        &buffer,
 547                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
 548                        cx,
 549                    )
 550                    .detach_and_log_err(cx);
 551            })
 552        })?;
 553
 554        Ok(proto::OpenBufferResponse {
 555            buffer_id: buffer_id.to_proto(),
 556        })
 557    }
 558
 559    fn repository_for_request(
 560        this: &Entity<Self>,
 561        worktree_id: WorktreeId,
 562        work_directory_id: ProjectEntryId,
 563        cx: &mut AsyncApp,
 564    ) -> Result<Entity<Repository>> {
 565        this.update(cx, |this, cx| {
 566            let repository_handle = this
 567                .all_repositories()
 568                .into_iter()
 569                .find(|repository_handle| {
 570                    repository_handle.read(cx).worktree_id == worktree_id
 571                        && repository_handle
 572                            .read(cx)
 573                            .repository_entry
 574                            .work_directory_id()
 575                            == work_directory_id
 576                })
 577                .context("missing repository handle")?;
 578            anyhow::Ok(repository_handle)
 579        })?
 580    }
 581}
 582
 583impl GitRepo {}
 584
 585impl Repository {
 586    pub fn git_store(&self) -> Option<Entity<GitStore>> {
 587        self.git_store.upgrade()
 588    }
 589
 590    fn id(&self) -> (WorktreeId, ProjectEntryId) {
 591        (self.worktree_id, self.repository_entry.work_directory_id())
 592    }
 593
 594    pub fn current_branch(&self) -> Option<&Branch> {
 595        self.repository_entry.branch()
 596    }
 597
 598    fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
 599    where
 600        F: FnOnce(GitRepo) -> Fut + 'static,
 601        Fut: Future<Output = R> + Send + 'static,
 602        R: Send + 'static,
 603    {
 604        self.send_keyed_job(None, job)
 605    }
 606
 607    fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
 608    where
 609        F: FnOnce(GitRepo) -> Fut + 'static,
 610        Fut: Future<Output = R> + Send + 'static,
 611        R: Send + 'static,
 612    {
 613        let (result_tx, result_rx) = futures::channel::oneshot::channel();
 614        let git_repo = self.git_repo.clone();
 615        self.job_sender
 616            .unbounded_send(GitJob {
 617                key,
 618                job: Box::new(|cx: &mut AsyncApp| {
 619                    let job = job(git_repo);
 620                    cx.background_spawn(async move {
 621                        let result = job.await;
 622                        result_tx.send(result).ok();
 623                    })
 624                }),
 625            })
 626            .ok();
 627        result_rx
 628    }
 629
 630    pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
 631        maybe!({
 632            let project_path = self.repo_path_to_project_path(&"".into())?;
 633            let worktree_name = project
 634                .worktree_for_id(project_path.worktree_id, cx)?
 635                .read(cx)
 636                .root_name();
 637
 638            let mut path = PathBuf::new();
 639            path = path.join(worktree_name);
 640            path = path.join(project_path.path);
 641            Some(path.to_string_lossy().to_string())
 642        })
 643        .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
 644        .into()
 645    }
 646
 647    pub fn activate(&self, cx: &mut Context<Self>) {
 648        let Some(git_store) = self.git_store.upgrade() else {
 649            return;
 650        };
 651        let entity = cx.entity();
 652        git_store.update(cx, |git_store, cx| {
 653            let Some(index) = git_store
 654                .repositories
 655                .iter()
 656                .position(|handle| *handle == entity)
 657            else {
 658                return;
 659            };
 660            git_store.active_index = Some(index);
 661            cx.emit(GitEvent::ActiveRepositoryChanged);
 662        });
 663    }
 664
 665    pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
 666        self.repository_entry.status()
 667    }
 668
 669    pub fn has_conflict(&self, path: &RepoPath) -> bool {
 670        self.repository_entry
 671            .current_merge_conflicts
 672            .contains(&path)
 673    }
 674
 675    pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
 676        let path = self.repository_entry.unrelativize(path)?;
 677        Some((self.worktree_id, path).into())
 678    }
 679
 680    pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
 681        self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
 682    }
 683
 684    pub fn worktree_id_path_to_repo_path(
 685        &self,
 686        worktree_id: WorktreeId,
 687        path: &Path,
 688    ) -> Option<RepoPath> {
 689        if worktree_id != self.worktree_id {
 690            return None;
 691        }
 692        self.repository_entry.relativize(path).log_err()
 693    }
 694
 695    pub fn open_commit_buffer(
 696        &mut self,
 697        languages: Option<Arc<LanguageRegistry>>,
 698        buffer_store: Entity<BufferStore>,
 699        cx: &mut Context<Self>,
 700    ) -> Task<Result<Entity<Buffer>>> {
 701        if let Some(buffer) = self.commit_message_buffer.clone() {
 702            return Task::ready(Ok(buffer));
 703        }
 704
 705        if let GitRepo::Remote {
 706            project_id,
 707            client,
 708            worktree_id,
 709            work_directory_id,
 710        } = self.git_repo.clone()
 711        {
 712            let client = client.clone();
 713            cx.spawn(|repository, mut cx| async move {
 714                let request = client.request(proto::OpenCommitMessageBuffer {
 715                    project_id: project_id.0,
 716                    worktree_id: worktree_id.to_proto(),
 717                    work_directory_id: work_directory_id.to_proto(),
 718                });
 719                let response = request.await.context("requesting to open commit buffer")?;
 720                let buffer_id = BufferId::new(response.buffer_id)?;
 721                let buffer = buffer_store
 722                    .update(&mut cx, |buffer_store, cx| {
 723                        buffer_store.wait_for_remote_buffer(buffer_id, cx)
 724                    })?
 725                    .await?;
 726                if let Some(language_registry) = languages {
 727                    let git_commit_language =
 728                        language_registry.language_for_name("Git Commit").await?;
 729                    buffer.update(&mut cx, |buffer, cx| {
 730                        buffer.set_language(Some(git_commit_language), cx);
 731                    })?;
 732                }
 733                repository.update(&mut cx, |repository, _| {
 734                    repository.commit_message_buffer = Some(buffer.clone());
 735                })?;
 736                Ok(buffer)
 737            })
 738        } else {
 739            self.open_local_commit_buffer(languages, buffer_store, cx)
 740        }
 741    }
 742
 743    fn open_local_commit_buffer(
 744        &mut self,
 745        language_registry: Option<Arc<LanguageRegistry>>,
 746        buffer_store: Entity<BufferStore>,
 747        cx: &mut Context<Self>,
 748    ) -> Task<Result<Entity<Buffer>>> {
 749        let merge_message = self.merge_message.clone();
 750        cx.spawn(|repository, mut cx| async move {
 751            let buffer = buffer_store
 752                .update(&mut cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
 753                .await?;
 754
 755            if let Some(language_registry) = language_registry {
 756                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
 757                buffer.update(&mut cx, |buffer, cx| {
 758                    buffer.set_language(Some(git_commit_language), cx);
 759                })?;
 760            }
 761
 762            if let Some(merge_message) = merge_message {
 763                buffer.update(&mut cx, |buffer, cx| {
 764                    buffer.set_text(merge_message.as_str(), cx)
 765                })?;
 766            }
 767
 768            repository.update(&mut cx, |repository, _| {
 769                repository.commit_message_buffer = Some(buffer.clone());
 770            })?;
 771            Ok(buffer)
 772        })
 773    }
 774
 775    pub fn checkout_files(
 776        &self,
 777        commit: &str,
 778        paths: Vec<RepoPath>,
 779    ) -> oneshot::Receiver<Result<()>> {
 780        let commit = commit.to_string();
 781        self.send_job(|git_repo| async move {
 782            match git_repo {
 783                GitRepo::Local(repo) => repo.checkout_files(&commit, &paths),
 784                GitRepo::Remote {
 785                    project_id,
 786                    client,
 787                    worktree_id,
 788                    work_directory_id,
 789                } => {
 790                    client
 791                        .request(proto::GitCheckoutFiles {
 792                            project_id: project_id.0,
 793                            worktree_id: worktree_id.to_proto(),
 794                            work_directory_id: work_directory_id.to_proto(),
 795                            commit,
 796                            paths: paths
 797                                .into_iter()
 798                                .map(|p| p.to_string_lossy().to_string())
 799                                .collect(),
 800                        })
 801                        .await?;
 802
 803                    Ok(())
 804                }
 805            }
 806        })
 807    }
 808
 809    pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> {
 810        let commit = commit.to_string();
 811        self.send_job(|git_repo| async move {
 812            match git_repo {
 813                GitRepo::Local(git_repo) => git_repo.reset(&commit, reset_mode),
 814                GitRepo::Remote {
 815                    project_id,
 816                    client,
 817                    worktree_id,
 818                    work_directory_id,
 819                } => {
 820                    client
 821                        .request(proto::GitReset {
 822                            project_id: project_id.0,
 823                            worktree_id: worktree_id.to_proto(),
 824                            work_directory_id: work_directory_id.to_proto(),
 825                            commit,
 826                            mode: match reset_mode {
 827                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
 828                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
 829                            },
 830                        })
 831                        .await?;
 832
 833                    Ok(())
 834                }
 835            }
 836        })
 837    }
 838
 839    pub fn show(&self, commit: &str) -> oneshot::Receiver<Result<CommitDetails>> {
 840        let commit = commit.to_string();
 841        self.send_job(|git_repo| async move {
 842            match git_repo {
 843                GitRepo::Local(git_repository) => git_repository.show(&commit),
 844                GitRepo::Remote {
 845                    project_id,
 846                    client,
 847                    worktree_id,
 848                    work_directory_id,
 849                } => {
 850                    let resp = client
 851                        .request(proto::GitShow {
 852                            project_id: project_id.0,
 853                            worktree_id: worktree_id.to_proto(),
 854                            work_directory_id: work_directory_id.to_proto(),
 855                            commit,
 856                        })
 857                        .await?;
 858
 859                    Ok(CommitDetails {
 860                        sha: resp.sha.into(),
 861                        message: resp.message.into(),
 862                        commit_timestamp: resp.commit_timestamp,
 863                        committer_email: resp.committer_email.into(),
 864                        committer_name: resp.committer_name.into(),
 865                    })
 866                }
 867            }
 868        })
 869    }
 870
 871    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
 872        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
 873    }
 874
 875    pub fn stage_entries(
 876        &self,
 877        entries: Vec<RepoPath>,
 878        cx: &mut Context<Self>,
 879    ) -> Task<anyhow::Result<()>> {
 880        if entries.is_empty() {
 881            return Task::ready(Ok(()));
 882        }
 883
 884        let mut save_futures = Vec::new();
 885        if let Some(buffer_store) = self.buffer_store(cx) {
 886            buffer_store.update(cx, |buffer_store, cx| {
 887                for path in &entries {
 888                    let Some(path) = self.repository_entry.unrelativize(path) else {
 889                        continue;
 890                    };
 891                    let project_path = (self.worktree_id, path).into();
 892                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
 893                        if buffer
 894                            .read(cx)
 895                            .file()
 896                            .map_or(false, |file| file.disk_state().exists())
 897                        {
 898                            save_futures.push(buffer_store.save_buffer(buffer, cx));
 899                        }
 900                    }
 901                }
 902            })
 903        }
 904
 905        cx.spawn(|this, mut cx| async move {
 906            for save_future in save_futures {
 907                save_future.await?;
 908            }
 909
 910            this.update(&mut cx, |this, _| {
 911                this.send_job(|git_repo| async move {
 912                    match git_repo {
 913                        GitRepo::Local(repo) => repo.stage_paths(&entries),
 914                        GitRepo::Remote {
 915                            project_id,
 916                            client,
 917                            worktree_id,
 918                            work_directory_id,
 919                        } => {
 920                            client
 921                                .request(proto::Stage {
 922                                    project_id: project_id.0,
 923                                    worktree_id: worktree_id.to_proto(),
 924                                    work_directory_id: work_directory_id.to_proto(),
 925                                    paths: entries
 926                                        .into_iter()
 927                                        .map(|repo_path| repo_path.as_ref().to_proto())
 928                                        .collect(),
 929                                })
 930                                .await
 931                                .context("sending stage request")?;
 932
 933                            Ok(())
 934                        }
 935                    }
 936                })
 937            })?
 938            .await??;
 939
 940            Ok(())
 941        })
 942    }
 943
 944    pub fn unstage_entries(
 945        &self,
 946        entries: Vec<RepoPath>,
 947        cx: &mut Context<Self>,
 948    ) -> Task<anyhow::Result<()>> {
 949        if entries.is_empty() {
 950            return Task::ready(Ok(()));
 951        }
 952
 953        let mut save_futures = Vec::new();
 954        if let Some(buffer_store) = self.buffer_store(cx) {
 955            buffer_store.update(cx, |buffer_store, cx| {
 956                for path in &entries {
 957                    let Some(path) = self.repository_entry.unrelativize(path) else {
 958                        continue;
 959                    };
 960                    let project_path = (self.worktree_id, path).into();
 961                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
 962                        if buffer
 963                            .read(cx)
 964                            .file()
 965                            .map_or(false, |file| file.disk_state().exists())
 966                        {
 967                            save_futures.push(buffer_store.save_buffer(buffer, cx));
 968                        }
 969                    }
 970                }
 971            })
 972        }
 973
 974        cx.spawn(move |this, mut cx| async move {
 975            for save_future in save_futures {
 976                save_future.await?;
 977            }
 978
 979            this.update(&mut cx, |this, _| {
 980                this.send_job(|git_repo| async move {
 981                    match git_repo {
 982                        GitRepo::Local(repo) => repo.unstage_paths(&entries),
 983                        GitRepo::Remote {
 984                            project_id,
 985                            client,
 986                            worktree_id,
 987                            work_directory_id,
 988                        } => {
 989                            client
 990                                .request(proto::Unstage {
 991                                    project_id: project_id.0,
 992                                    worktree_id: worktree_id.to_proto(),
 993                                    work_directory_id: work_directory_id.to_proto(),
 994                                    paths: entries
 995                                        .into_iter()
 996                                        .map(|repo_path| repo_path.as_ref().to_proto())
 997                                        .collect(),
 998                                })
 999                                .await
1000                                .context("sending unstage request")?;
1001
1002                            Ok(())
1003                        }
1004                    }
1005                })
1006            })?
1007            .await??;
1008
1009            Ok(())
1010        })
1011    }
1012
1013    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1014        let to_stage = self
1015            .repository_entry
1016            .status()
1017            .filter(|entry| !entry.status.is_staged().unwrap_or(false))
1018            .map(|entry| entry.repo_path.clone())
1019            .collect();
1020        self.stage_entries(to_stage, cx)
1021    }
1022
1023    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1024        let to_unstage = self
1025            .repository_entry
1026            .status()
1027            .filter(|entry| entry.status.is_staged().unwrap_or(true))
1028            .map(|entry| entry.repo_path.clone())
1029            .collect();
1030        self.unstage_entries(to_unstage, cx)
1031    }
1032
1033    /// Get a count of all entries in the active repository, including
1034    /// untracked files.
1035    pub fn entry_count(&self) -> usize {
1036        self.repository_entry.status_len()
1037    }
1038
1039    fn have_changes(&self) -> bool {
1040        self.repository_entry.status_summary() != GitSummary::UNCHANGED
1041    }
1042
1043    fn have_staged_changes(&self) -> bool {
1044        self.repository_entry.status_summary().index != TrackedSummary::UNCHANGED
1045    }
1046
1047    pub fn can_commit(&self, commit_all: bool) -> bool {
1048        return self.have_changes() && (commit_all || self.have_staged_changes());
1049    }
1050
1051    pub fn commit(
1052        &self,
1053        message: SharedString,
1054        name_and_email: Option<(SharedString, SharedString)>,
1055    ) -> oneshot::Receiver<Result<()>> {
1056        self.send_job(|git_repo| async move {
1057            match git_repo {
1058                GitRepo::Local(repo) => repo.commit(
1059                    message.as_ref(),
1060                    name_and_email
1061                        .as_ref()
1062                        .map(|(name, email)| (name.as_ref(), email.as_ref())),
1063                ),
1064                GitRepo::Remote {
1065                    project_id,
1066                    client,
1067                    worktree_id,
1068                    work_directory_id,
1069                } => {
1070                    let (name, email) = name_and_email.unzip();
1071                    client
1072                        .request(proto::Commit {
1073                            project_id: project_id.0,
1074                            worktree_id: worktree_id.to_proto(),
1075                            work_directory_id: work_directory_id.to_proto(),
1076                            message: String::from(message),
1077                            name: name.map(String::from),
1078                            email: email.map(String::from),
1079                        })
1080                        .await
1081                        .context("sending commit request")?;
1082
1083                    Ok(())
1084                }
1085            }
1086        })
1087    }
1088
1089    pub fn fetch(&self) -> oneshot::Receiver<Result<()>> {
1090        self.send_job(|git_repo| async move {
1091            match git_repo {
1092                GitRepo::Local(git_repository) => git_repository.fetch(),
1093                GitRepo::Remote {
1094                    project_id,
1095                    client,
1096                    worktree_id,
1097                    work_directory_id,
1098                } => {
1099                    client
1100                        .request(proto::Fetch {
1101                            project_id: project_id.0,
1102                            worktree_id: worktree_id.to_proto(),
1103                            work_directory_id: work_directory_id.to_proto(),
1104                        })
1105                        .await
1106                        .context("sending fetch request")?;
1107
1108                    Ok(())
1109                }
1110            }
1111        })
1112    }
1113
1114    pub fn push(
1115        &self,
1116        branch: SharedString,
1117        remote: SharedString,
1118        options: Option<PushOptions>,
1119    ) -> oneshot::Receiver<Result<()>> {
1120        self.send_job(move |git_repo| async move {
1121            match git_repo {
1122                GitRepo::Local(git_repository) => git_repository.push(&branch, &remote, options),
1123                GitRepo::Remote {
1124                    project_id,
1125                    client,
1126                    worktree_id,
1127                    work_directory_id,
1128                } => {
1129                    client
1130                        .request(proto::Push {
1131                            project_id: project_id.0,
1132                            worktree_id: worktree_id.to_proto(),
1133                            work_directory_id: work_directory_id.to_proto(),
1134                            branch_name: branch.to_string(),
1135                            remote_name: remote.to_string(),
1136                            options: options.map(|options| match options {
1137                                PushOptions::Force => proto::push::PushOptions::Force,
1138                                PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
1139                            } as i32),
1140                        })
1141                        .await
1142                        .context("sending push request")?;
1143
1144                    Ok(())
1145                }
1146            }
1147        })
1148    }
1149
1150    pub fn pull(
1151        &self,
1152        branch: SharedString,
1153        remote: SharedString,
1154    ) -> oneshot::Receiver<Result<()>> {
1155        self.send_job(|git_repo| async move {
1156            match git_repo {
1157                GitRepo::Local(git_repository) => git_repository.pull(&branch, &remote),
1158                GitRepo::Remote {
1159                    project_id,
1160                    client,
1161                    worktree_id,
1162                    work_directory_id,
1163                } => {
1164                    client
1165                        .request(proto::Pull {
1166                            project_id: project_id.0,
1167                            worktree_id: worktree_id.to_proto(),
1168                            work_directory_id: work_directory_id.to_proto(),
1169                            branch_name: branch.to_string(),
1170                            remote_name: remote.to_string(),
1171                        })
1172                        .await
1173                        .context("sending pull request")?;
1174
1175                    // TODO: wire through remote
1176                    Ok(())
1177                }
1178            }
1179        })
1180    }
1181
1182    pub fn set_index_text(
1183        &self,
1184        path: &RepoPath,
1185        content: Option<String>,
1186    ) -> oneshot::Receiver<anyhow::Result<()>> {
1187        let path = path.clone();
1188        self.send_keyed_job(
1189            Some(GitJobKey::WriteIndex(path.clone())),
1190            |git_repo| async move {
1191                match git_repo {
1192                    GitRepo::Local(repo) => repo.set_index_text(&path, content),
1193                    GitRepo::Remote {
1194                        project_id,
1195                        client,
1196                        worktree_id,
1197                        work_directory_id,
1198                    } => {
1199                        client
1200                            .request(proto::SetIndexText {
1201                                project_id: project_id.0,
1202                                worktree_id: worktree_id.to_proto(),
1203                                work_directory_id: work_directory_id.to_proto(),
1204                                path: path.as_ref().to_proto(),
1205                                text: content,
1206                            })
1207                            .await?;
1208                        Ok(())
1209                    }
1210                }
1211            },
1212        )
1213    }
1214
1215    pub fn get_remotes(
1216        &self,
1217        branch_name: Option<String>,
1218    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
1219        self.send_job(|repo| async move {
1220            match repo {
1221                GitRepo::Local(git_repository) => {
1222                    git_repository.get_remotes(branch_name.as_deref())
1223                }
1224                GitRepo::Remote {
1225                    project_id,
1226                    client,
1227                    worktree_id,
1228                    work_directory_id,
1229                } => {
1230                    let response = client
1231                        .request(proto::GetRemotes {
1232                            project_id: project_id.0,
1233                            worktree_id: worktree_id.to_proto(),
1234                            work_directory_id: work_directory_id.to_proto(),
1235                            branch_name,
1236                        })
1237                        .await?;
1238
1239                    let remotes = response
1240                        .remotes
1241                        .into_iter()
1242                        .map(|remotes| git::repository::Remote {
1243                            name: remotes.name.into(),
1244                        })
1245                        .collect();
1246
1247                    Ok(remotes)
1248                }
1249            }
1250        })
1251    }
1252}