git.rs

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