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