git.rs

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