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