git.rs

   1use crate::{
   2    buffer_store::{BufferStore, BufferStoreEvent},
   3    worktree_store::{WorktreeStore, WorktreeStoreEvent},
   4    Project, ProjectEnvironment, ProjectItem, ProjectPath,
   5};
   6use anyhow::{Context as _, Result};
   7use askpass::{AskPassDelegate, AskPassSession};
   8use buffer_diff::BufferDiffEvent;
   9use client::ProjectId;
  10use collections::HashMap;
  11use fs::Fs;
  12use futures::{
  13    channel::{mpsc, oneshot},
  14    future::OptionFuture,
  15    StreamExt as _,
  16};
  17use git::repository::DiffType;
  18use git::{
  19    repository::{
  20        Branch, CommitDetails, GitRepository, PushOptions, Remote, RemoteCommandOutput, RepoPath,
  21        ResetMode,
  22    },
  23    status::FileStatus,
  24};
  25use gpui::{
  26    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
  27    WeakEntity,
  28};
  29use language::{Buffer, LanguageRegistry};
  30use parking_lot::Mutex;
  31use rpc::{
  32    proto::{self, git_reset, ToProto},
  33    AnyProtoClient, TypedEnvelope,
  34};
  35use settings::WorktreeId;
  36use std::{
  37    collections::VecDeque,
  38    future::Future,
  39    path::{Path, PathBuf},
  40    sync::Arc,
  41};
  42
  43use text::BufferId;
  44use util::{debug_panic, maybe, ResultExt};
  45use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory};
  46
  47pub struct GitStore {
  48    state: GitStoreState,
  49    buffer_store: Entity<BufferStore>,
  50    repositories: Vec<Entity<Repository>>,
  51    active_index: Option<usize>,
  52    update_sender: mpsc::UnboundedSender<GitJob>,
  53    _subscriptions: [Subscription; 2],
  54}
  55
  56enum GitStoreState {
  57    Local {
  58        client: AnyProtoClient,
  59        environment: Entity<ProjectEnvironment>,
  60        fs: Arc<dyn Fs>,
  61    },
  62    Ssh {
  63        environment: Entity<ProjectEnvironment>,
  64        upstream_client: AnyProtoClient,
  65        project_id: ProjectId,
  66    },
  67    Remote {
  68        upstream_client: AnyProtoClient,
  69        project_id: ProjectId,
  70    },
  71}
  72
  73pub struct Repository {
  74    commit_message_buffer: Option<Entity<Buffer>>,
  75    git_store: WeakEntity<GitStore>,
  76    project_environment: Option<WeakEntity<ProjectEnvironment>>,
  77    pub worktree_id: WorktreeId,
  78    pub repository_entry: RepositoryEntry,
  79    pub dot_git_abs_path: PathBuf,
  80    pub worktree_abs_path: Arc<Path>,
  81    pub is_from_single_file_worktree: bool,
  82    pub git_repo: GitRepo,
  83    pub merge_message: Option<String>,
  84    job_sender: mpsc::UnboundedSender<GitJob>,
  85    askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,
  86    latest_askpass_id: u64,
  87}
  88
  89#[derive(Clone)]
  90pub enum GitRepo {
  91    Local(Arc<dyn GitRepository>),
  92    Remote {
  93        project_id: ProjectId,
  94        client: AnyProtoClient,
  95        worktree_id: WorktreeId,
  96        work_directory_id: ProjectEntryId,
  97    },
  98}
  99
 100#[derive(Debug)]
 101pub enum GitEvent {
 102    ActiveRepositoryChanged,
 103    FileSystemUpdated,
 104    GitStateUpdated,
 105    IndexWriteError(anyhow::Error),
 106}
 107
 108struct GitJob {
 109    job: Box<dyn FnOnce(&mut AsyncApp) -> Task<()>>,
 110    key: Option<GitJobKey>,
 111}
 112
 113#[derive(PartialEq, Eq)]
 114enum GitJobKey {
 115    WriteIndex(RepoPath),
 116}
 117
 118impl EventEmitter<GitEvent> for GitStore {}
 119
 120impl GitStore {
 121    pub fn local(
 122        worktree_store: &Entity<WorktreeStore>,
 123        buffer_store: Entity<BufferStore>,
 124        environment: Entity<ProjectEnvironment>,
 125        fs: Arc<dyn Fs>,
 126        client: AnyProtoClient,
 127        cx: &mut Context<'_, Self>,
 128    ) -> Self {
 129        let update_sender = Self::spawn_git_worker(cx);
 130        let _subscriptions = [
 131            cx.subscribe(worktree_store, Self::on_worktree_store_event),
 132            cx.subscribe(&buffer_store, Self::on_buffer_store_event),
 133        ];
 134
 135        let state = GitStoreState::Local {
 136            client,
 137            environment,
 138            fs,
 139        };
 140
 141        GitStore {
 142            state,
 143            buffer_store,
 144            repositories: Vec::new(),
 145            active_index: None,
 146            update_sender,
 147            _subscriptions,
 148        }
 149    }
 150
 151    pub fn remote(
 152        worktree_store: &Entity<WorktreeStore>,
 153        buffer_store: Entity<BufferStore>,
 154        upstream_client: AnyProtoClient,
 155        project_id: ProjectId,
 156        cx: &mut Context<'_, Self>,
 157    ) -> Self {
 158        let update_sender = Self::spawn_git_worker(cx);
 159        let _subscriptions = [
 160            cx.subscribe(worktree_store, Self::on_worktree_store_event),
 161            cx.subscribe(&buffer_store, Self::on_buffer_store_event),
 162        ];
 163
 164        let state = GitStoreState::Remote {
 165            upstream_client,
 166            project_id,
 167        };
 168
 169        GitStore {
 170            state,
 171            buffer_store,
 172            repositories: Vec::new(),
 173            active_index: None,
 174            update_sender,
 175            _subscriptions,
 176        }
 177    }
 178
 179    pub fn ssh(
 180        worktree_store: &Entity<WorktreeStore>,
 181        buffer_store: Entity<BufferStore>,
 182        environment: Entity<ProjectEnvironment>,
 183        upstream_client: AnyProtoClient,
 184        project_id: ProjectId,
 185        cx: &mut Context<'_, Self>,
 186    ) -> Self {
 187        let update_sender = Self::spawn_git_worker(cx);
 188        let _subscriptions = [
 189            cx.subscribe(worktree_store, Self::on_worktree_store_event),
 190            cx.subscribe(&buffer_store, Self::on_buffer_store_event),
 191        ];
 192
 193        let state = GitStoreState::Ssh {
 194            upstream_client,
 195            project_id,
 196            environment,
 197        };
 198
 199        GitStore {
 200            state,
 201            buffer_store,
 202            repositories: Vec::new(),
 203            active_index: None,
 204            update_sender,
 205            _subscriptions,
 206        }
 207    }
 208
 209    pub fn init(client: &AnyProtoClient) {
 210        client.add_entity_request_handler(Self::handle_get_remotes);
 211        client.add_entity_request_handler(Self::handle_get_branches);
 212        client.add_entity_request_handler(Self::handle_change_branch);
 213        client.add_entity_request_handler(Self::handle_create_branch);
 214        client.add_entity_request_handler(Self::handle_git_init);
 215        client.add_entity_request_handler(Self::handle_push);
 216        client.add_entity_request_handler(Self::handle_pull);
 217        client.add_entity_request_handler(Self::handle_fetch);
 218        client.add_entity_request_handler(Self::handle_stage);
 219        client.add_entity_request_handler(Self::handle_unstage);
 220        client.add_entity_request_handler(Self::handle_commit);
 221        client.add_entity_request_handler(Self::handle_reset);
 222        client.add_entity_request_handler(Self::handle_show);
 223        client.add_entity_request_handler(Self::handle_checkout_files);
 224        client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
 225        client.add_entity_request_handler(Self::handle_set_index_text);
 226        client.add_entity_request_handler(Self::handle_askpass);
 227        client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
 228        client.add_entity_request_handler(Self::handle_git_diff);
 229    }
 230
 231    pub fn active_repository(&self) -> Option<Entity<Repository>> {
 232        self.active_index
 233            .map(|index| self.repositories[index].clone())
 234    }
 235
 236    fn client(&self) -> AnyProtoClient {
 237        match &self.state {
 238            GitStoreState::Local { client, .. } => client.clone(),
 239            GitStoreState::Ssh {
 240                upstream_client, ..
 241            } => upstream_client.clone(),
 242            GitStoreState::Remote {
 243                upstream_client, ..
 244            } => upstream_client.clone(),
 245        }
 246    }
 247
 248    fn project_environment(&self) -> Option<Entity<ProjectEnvironment>> {
 249        match &self.state {
 250            GitStoreState::Local { environment, .. } => Some(environment.clone()),
 251            GitStoreState::Ssh { environment, .. } => Some(environment.clone()),
 252            GitStoreState::Remote { .. } => None,
 253        }
 254    }
 255
 256    fn project_id(&self) -> Option<ProjectId> {
 257        match &self.state {
 258            GitStoreState::Local { .. } => None,
 259            GitStoreState::Ssh { project_id, .. } => Some(*project_id),
 260            GitStoreState::Remote { project_id, .. } => Some(*project_id),
 261        }
 262    }
 263
 264    fn on_worktree_store_event(
 265        &mut self,
 266        worktree_store: Entity<WorktreeStore>,
 267        event: &WorktreeStoreEvent,
 268        cx: &mut Context<'_, Self>,
 269    ) {
 270        let mut new_repositories = Vec::new();
 271        let mut new_active_index = None;
 272        let this = cx.weak_entity();
 273        let client = self.client();
 274        let project_id = self.project_id();
 275
 276        worktree_store.update(cx, |worktree_store, cx| {
 277            for worktree in worktree_store.worktrees() {
 278                worktree.update(cx, |worktree, cx| {
 279                    let snapshot = worktree.snapshot();
 280                    for repo in snapshot.repositories().iter() {
 281                        let git_data = worktree
 282                            .as_local()
 283                            .and_then(|local_worktree| local_worktree.get_local_repo(repo))
 284                            .map(|local_repo| {
 285                                (
 286                                    GitRepo::Local(local_repo.repo().clone()),
 287                                    local_repo.merge_message.clone(),
 288                                )
 289                            })
 290                            .or_else(|| {
 291                                let client = client.clone();
 292                                let project_id = project_id?;
 293                                Some((
 294                                    GitRepo::Remote {
 295                                        project_id,
 296                                        client,
 297                                        worktree_id: worktree.id(),
 298                                        work_directory_id: repo.work_directory_id(),
 299                                    },
 300                                    None,
 301                                ))
 302                            });
 303                        let Some((git_repo, merge_message)) = git_data else {
 304                            continue;
 305                        };
 306                        let worktree_id = worktree.id();
 307                        let existing =
 308                            self.repositories
 309                                .iter()
 310                                .enumerate()
 311                                .find(|(_, existing_handle)| {
 312                                    existing_handle.read(cx).id()
 313                                        == (worktree_id, repo.work_directory_id())
 314                                });
 315                        let handle = if let Some((index, handle)) = existing {
 316                            if self.active_index == Some(index) {
 317                                new_active_index = Some(new_repositories.len());
 318                            }
 319                            // Update the statuses and merge message but keep everything else.
 320                            let existing_handle = handle.clone();
 321                            existing_handle.update(cx, |existing_handle, _| {
 322                                existing_handle.repository_entry = repo.clone();
 323                                if matches!(git_repo, GitRepo::Local { .. }) {
 324                                    existing_handle.merge_message = merge_message;
 325                                }
 326                            });
 327                            existing_handle
 328                        } else {
 329                            let environment = self.project_environment();
 330                            cx.new(|_| Repository {
 331                                project_environment: environment
 332                                    .as_ref()
 333                                    .map(|env| env.downgrade()),
 334                                git_store: this.clone(),
 335                                worktree_id,
 336                                askpass_delegates: Default::default(),
 337                                latest_askpass_id: 0,
 338                                repository_entry: repo.clone(),
 339                                dot_git_abs_path: worktree.dot_git_abs_path(&repo.work_directory),
 340                                worktree_abs_path: worktree.abs_path(),
 341                                is_from_single_file_worktree: worktree.is_single_file(),
 342                                git_repo,
 343                                job_sender: self.update_sender.clone(),
 344                                merge_message,
 345                                commit_message_buffer: None,
 346                            })
 347                        };
 348                        new_repositories.push(handle);
 349                    }
 350                })
 351            }
 352        });
 353
 354        if new_active_index == None && new_repositories.len() > 0 {
 355            new_active_index = Some(0);
 356        }
 357
 358        self.repositories = new_repositories;
 359        self.active_index = new_active_index;
 360
 361        match event {
 362            WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
 363                cx.emit(GitEvent::GitStateUpdated);
 364            }
 365            _ => {
 366                cx.emit(GitEvent::FileSystemUpdated);
 367            }
 368        }
 369    }
 370
 371    fn on_buffer_store_event(
 372        &mut self,
 373        _: Entity<BufferStore>,
 374        event: &BufferStoreEvent,
 375        cx: &mut Context<'_, Self>,
 376    ) {
 377        if let BufferStoreEvent::BufferDiffAdded(diff) = event {
 378            cx.subscribe(diff, Self::on_buffer_diff_event).detach();
 379        }
 380    }
 381
 382    fn on_buffer_diff_event(
 383        this: &mut GitStore,
 384        diff: Entity<buffer_diff::BufferDiff>,
 385        event: &BufferDiffEvent,
 386        cx: &mut Context<'_, GitStore>,
 387    ) {
 388        if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
 389            let buffer_id = diff.read(cx).buffer_id;
 390            if let Some((repo, path)) = this.repository_and_path_for_buffer_id(buffer_id, cx) {
 391                let recv = repo.update(cx, |repo, cx| {
 392                    repo.set_index_text(
 393                        path,
 394                        new_index_text.as_ref().map(|rope| rope.to_string()),
 395                        cx,
 396                    )
 397                });
 398                let diff = diff.downgrade();
 399                cx.spawn(|this, mut cx| async move {
 400                    if let Some(result) = cx.background_spawn(async move { recv.await.ok() }).await
 401                    {
 402                        if let Err(error) = result {
 403                            diff.update(&mut cx, |diff, cx| {
 404                                diff.clear_pending_hunks(cx);
 405                            })
 406                            .ok();
 407                            this.update(&mut cx, |_, cx| cx.emit(GitEvent::IndexWriteError(error)))
 408                                .ok();
 409                        }
 410                    }
 411                })
 412                .detach();
 413            }
 414        }
 415    }
 416
 417    pub fn all_repositories(&self) -> Vec<Entity<Repository>> {
 418        self.repositories.clone()
 419    }
 420
 421    pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
 422        let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
 423        let status = repo.read(cx).repository_entry.status_for_path(&path)?;
 424        Some(status.status)
 425    }
 426
 427    fn repository_and_path_for_buffer_id(
 428        &self,
 429        buffer_id: BufferId,
 430        cx: &App,
 431    ) -> Option<(Entity<Repository>, RepoPath)> {
 432        let buffer = self.buffer_store.read(cx).get(buffer_id)?;
 433        let path = buffer.read(cx).project_path(cx)?;
 434        let mut result: Option<(Entity<Repository>, RepoPath)> = None;
 435        for repo_handle in &self.repositories {
 436            let repo = repo_handle.read(cx);
 437            if repo.worktree_id == path.worktree_id {
 438                if let Ok(relative_path) = repo.repository_entry.relativize(&path.path) {
 439                    if result
 440                        .as_ref()
 441                        .is_none_or(|(result, _)| !repo.contains_sub_repo(result, cx))
 442                    {
 443                        result = Some((repo_handle.clone(), relative_path))
 444                    }
 445                }
 446            }
 447        }
 448        result
 449    }
 450
 451    fn spawn_git_worker(cx: &mut Context<'_, GitStore>) -> mpsc::UnboundedSender<GitJob> {
 452        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
 453
 454        cx.spawn(|_, mut cx| async move {
 455            let mut jobs = VecDeque::new();
 456            loop {
 457                while let Ok(Some(next_job)) = job_rx.try_next() {
 458                    jobs.push_back(next_job);
 459                }
 460
 461                if let Some(job) = jobs.pop_front() {
 462                    if let Some(current_key) = &job.key {
 463                        if jobs
 464                            .iter()
 465                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
 466                        {
 467                            continue;
 468                        }
 469                    }
 470                    (job.job)(&mut cx).await;
 471                } else if let Some(job) = job_rx.next().await {
 472                    jobs.push_back(job);
 473                } else {
 474                    break;
 475                }
 476            }
 477        })
 478        .detach();
 479        job_tx
 480    }
 481
 482    pub fn git_init(
 483        &self,
 484        path: Arc<Path>,
 485        fallback_branch_name: String,
 486        cx: &App,
 487    ) -> Task<Result<()>> {
 488        match &self.state {
 489            GitStoreState::Local { fs, .. } => {
 490                let fs = fs.clone();
 491                cx.background_executor()
 492                    .spawn(async move { fs.git_init(&path, fallback_branch_name) })
 493            }
 494            GitStoreState::Ssh {
 495                upstream_client,
 496                project_id,
 497                ..
 498            }
 499            | GitStoreState::Remote {
 500                upstream_client,
 501                project_id,
 502            } => {
 503                let client = upstream_client.clone();
 504                let project_id = *project_id;
 505                cx.background_executor().spawn(async move {
 506                    client
 507                        .request(proto::GitInit {
 508                            project_id: project_id.0,
 509                            abs_path: path.to_string_lossy().to_string(),
 510                            fallback_branch_name,
 511                        })
 512                        .await?;
 513                    Ok(())
 514                })
 515            }
 516        }
 517    }
 518
 519    async fn handle_git_init(
 520        this: Entity<Self>,
 521        envelope: TypedEnvelope<proto::GitInit>,
 522        cx: AsyncApp,
 523    ) -> Result<proto::Ack> {
 524        let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
 525        let name = envelope.payload.fallback_branch_name;
 526        cx.update(|cx| this.read(cx).git_init(path, name, cx))?
 527            .await?;
 528
 529        Ok(proto::Ack {})
 530    }
 531
 532    async fn handle_fetch(
 533        this: Entity<Self>,
 534        envelope: TypedEnvelope<proto::Fetch>,
 535        mut cx: AsyncApp,
 536    ) -> Result<proto::RemoteMessageResponse> {
 537        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 538        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 539        let repository_handle =
 540            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 541        let askpass_id = envelope.payload.askpass_id;
 542
 543        let askpass = make_remote_delegate(
 544            this,
 545            envelope.payload.project_id,
 546            worktree_id,
 547            work_directory_id,
 548            askpass_id,
 549            &mut cx,
 550        );
 551
 552        let remote_output = repository_handle
 553            .update(&mut cx, |repository_handle, cx| {
 554                repository_handle.fetch(askpass, cx)
 555            })?
 556            .await??;
 557
 558        Ok(proto::RemoteMessageResponse {
 559            stdout: remote_output.stdout,
 560            stderr: remote_output.stderr,
 561        })
 562    }
 563
 564    async fn handle_push(
 565        this: Entity<Self>,
 566        envelope: TypedEnvelope<proto::Push>,
 567        mut cx: AsyncApp,
 568    ) -> Result<proto::RemoteMessageResponse> {
 569        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 570        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 571        let repository_handle =
 572            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 573
 574        let askpass_id = envelope.payload.askpass_id;
 575        let askpass = make_remote_delegate(
 576            this,
 577            envelope.payload.project_id,
 578            worktree_id,
 579            work_directory_id,
 580            askpass_id,
 581            &mut cx,
 582        );
 583
 584        let options = envelope
 585            .payload
 586            .options
 587            .as_ref()
 588            .map(|_| match envelope.payload.options() {
 589                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
 590                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
 591            });
 592
 593        let branch_name = envelope.payload.branch_name.into();
 594        let remote_name = envelope.payload.remote_name.into();
 595
 596        let remote_output = repository_handle
 597            .update(&mut cx, |repository_handle, cx| {
 598                repository_handle.push(branch_name, remote_name, options, askpass, cx)
 599            })?
 600            .await??;
 601        Ok(proto::RemoteMessageResponse {
 602            stdout: remote_output.stdout,
 603            stderr: remote_output.stderr,
 604        })
 605    }
 606
 607    async fn handle_pull(
 608        this: Entity<Self>,
 609        envelope: TypedEnvelope<proto::Pull>,
 610        mut cx: AsyncApp,
 611    ) -> Result<proto::RemoteMessageResponse> {
 612        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 613        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 614        let repository_handle =
 615            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 616        let askpass_id = envelope.payload.askpass_id;
 617        let askpass = make_remote_delegate(
 618            this,
 619            envelope.payload.project_id,
 620            worktree_id,
 621            work_directory_id,
 622            askpass_id,
 623            &mut cx,
 624        );
 625
 626        let branch_name = envelope.payload.branch_name.into();
 627        let remote_name = envelope.payload.remote_name.into();
 628
 629        let remote_message = repository_handle
 630            .update(&mut cx, |repository_handle, cx| {
 631                repository_handle.pull(branch_name, remote_name, askpass, cx)
 632            })?
 633            .await??;
 634
 635        Ok(proto::RemoteMessageResponse {
 636            stdout: remote_message.stdout,
 637            stderr: remote_message.stderr,
 638        })
 639    }
 640
 641    async fn handle_stage(
 642        this: Entity<Self>,
 643        envelope: TypedEnvelope<proto::Stage>,
 644        mut cx: AsyncApp,
 645    ) -> Result<proto::Ack> {
 646        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 647        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 648        let repository_handle =
 649            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 650
 651        let entries = envelope
 652            .payload
 653            .paths
 654            .into_iter()
 655            .map(PathBuf::from)
 656            .map(RepoPath::new)
 657            .collect();
 658
 659        repository_handle
 660            .update(&mut cx, |repository_handle, cx| {
 661                repository_handle.stage_entries(entries, cx)
 662            })?
 663            .await?;
 664        Ok(proto::Ack {})
 665    }
 666
 667    async fn handle_unstage(
 668        this: Entity<Self>,
 669        envelope: TypedEnvelope<proto::Unstage>,
 670        mut cx: AsyncApp,
 671    ) -> Result<proto::Ack> {
 672        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 673        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 674        let repository_handle =
 675            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 676
 677        let entries = envelope
 678            .payload
 679            .paths
 680            .into_iter()
 681            .map(PathBuf::from)
 682            .map(RepoPath::new)
 683            .collect();
 684
 685        repository_handle
 686            .update(&mut cx, |repository_handle, cx| {
 687                repository_handle.unstage_entries(entries, cx)
 688            })?
 689            .await?;
 690
 691        Ok(proto::Ack {})
 692    }
 693
 694    async fn handle_set_index_text(
 695        this: Entity<Self>,
 696        envelope: TypedEnvelope<proto::SetIndexText>,
 697        mut cx: AsyncApp,
 698    ) -> Result<proto::Ack> {
 699        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 700        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 701        let repository_handle =
 702            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 703
 704        repository_handle
 705            .update(&mut cx, |repository_handle, cx| {
 706                repository_handle.set_index_text(
 707                    RepoPath::from_str(&envelope.payload.path),
 708                    envelope.payload.text,
 709                    cx,
 710                )
 711            })?
 712            .await??;
 713        Ok(proto::Ack {})
 714    }
 715
 716    async fn handle_commit(
 717        this: Entity<Self>,
 718        envelope: TypedEnvelope<proto::Commit>,
 719        mut cx: AsyncApp,
 720    ) -> Result<proto::Ack> {
 721        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 722        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 723        let repository_handle =
 724            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 725
 726        let message = SharedString::from(envelope.payload.message);
 727        let name = envelope.payload.name.map(SharedString::from);
 728        let email = envelope.payload.email.map(SharedString::from);
 729
 730        repository_handle
 731            .update(&mut cx, |repository_handle, cx| {
 732                repository_handle.commit(message, name.zip(email), cx)
 733            })?
 734            .await??;
 735        Ok(proto::Ack {})
 736    }
 737
 738    async fn handle_get_remotes(
 739        this: Entity<Self>,
 740        envelope: TypedEnvelope<proto::GetRemotes>,
 741        mut cx: AsyncApp,
 742    ) -> Result<proto::GetRemotesResponse> {
 743        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 744        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 745        let repository_handle =
 746            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 747
 748        let branch_name = envelope.payload.branch_name;
 749
 750        let remotes = repository_handle
 751            .update(&mut cx, |repository_handle, _| {
 752                repository_handle.get_remotes(branch_name)
 753            })?
 754            .await??;
 755
 756        Ok(proto::GetRemotesResponse {
 757            remotes: remotes
 758                .into_iter()
 759                .map(|remotes| proto::get_remotes_response::Remote {
 760                    name: remotes.name.to_string(),
 761                })
 762                .collect::<Vec<_>>(),
 763        })
 764    }
 765
 766    async fn handle_get_branches(
 767        this: Entity<Self>,
 768        envelope: TypedEnvelope<proto::GitGetBranches>,
 769        mut cx: AsyncApp,
 770    ) -> Result<proto::GitBranchesResponse> {
 771        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 772        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 773        let repository_handle =
 774            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 775
 776        let branches = repository_handle
 777            .update(&mut cx, |repository_handle, _| repository_handle.branches())?
 778            .await??;
 779
 780        Ok(proto::GitBranchesResponse {
 781            branches: branches
 782                .into_iter()
 783                .map(|branch| worktree::branch_to_proto(&branch))
 784                .collect::<Vec<_>>(),
 785        })
 786    }
 787    async fn handle_create_branch(
 788        this: Entity<Self>,
 789        envelope: TypedEnvelope<proto::GitCreateBranch>,
 790        mut cx: AsyncApp,
 791    ) -> Result<proto::Ack> {
 792        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 793        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 794        let repository_handle =
 795            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 796        let branch_name = envelope.payload.branch_name;
 797
 798        repository_handle
 799            .update(&mut cx, |repository_handle, _| {
 800                repository_handle.create_branch(branch_name)
 801            })?
 802            .await??;
 803
 804        Ok(proto::Ack {})
 805    }
 806
 807    async fn handle_change_branch(
 808        this: Entity<Self>,
 809        envelope: TypedEnvelope<proto::GitChangeBranch>,
 810        mut cx: AsyncApp,
 811    ) -> Result<proto::Ack> {
 812        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 813        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 814        let repository_handle =
 815            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 816        let branch_name = envelope.payload.branch_name;
 817
 818        repository_handle
 819            .update(&mut cx, |repository_handle, _| {
 820                repository_handle.change_branch(branch_name)
 821            })?
 822            .await??;
 823
 824        Ok(proto::Ack {})
 825    }
 826
 827    async fn handle_show(
 828        this: Entity<Self>,
 829        envelope: TypedEnvelope<proto::GitShow>,
 830        mut cx: AsyncApp,
 831    ) -> Result<proto::GitCommitDetails> {
 832        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 833        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 834        let repository_handle =
 835            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 836
 837        let commit = repository_handle
 838            .update(&mut cx, |repository_handle, _| {
 839                repository_handle.show(envelope.payload.commit)
 840            })?
 841            .await??;
 842        Ok(proto::GitCommitDetails {
 843            sha: commit.sha.into(),
 844            message: commit.message.into(),
 845            commit_timestamp: commit.commit_timestamp,
 846            committer_email: commit.committer_email.into(),
 847            committer_name: commit.committer_name.into(),
 848        })
 849    }
 850
 851    async fn handle_reset(
 852        this: Entity<Self>,
 853        envelope: TypedEnvelope<proto::GitReset>,
 854        mut cx: AsyncApp,
 855    ) -> Result<proto::Ack> {
 856        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 857        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 858        let repository_handle =
 859            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 860
 861        let mode = match envelope.payload.mode() {
 862            git_reset::ResetMode::Soft => ResetMode::Soft,
 863            git_reset::ResetMode::Mixed => ResetMode::Mixed,
 864        };
 865
 866        repository_handle
 867            .update(&mut cx, |repository_handle, cx| {
 868                repository_handle.reset(envelope.payload.commit, mode, cx)
 869            })?
 870            .await??;
 871        Ok(proto::Ack {})
 872    }
 873
 874    async fn handle_checkout_files(
 875        this: Entity<Self>,
 876        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
 877        mut cx: AsyncApp,
 878    ) -> Result<proto::Ack> {
 879        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 880        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 881        let repository_handle =
 882            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 883        let paths = envelope
 884            .payload
 885            .paths
 886            .iter()
 887            .map(|s| RepoPath::from_str(s))
 888            .collect();
 889
 890        repository_handle
 891            .update(&mut cx, |repository_handle, cx| {
 892                repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
 893            })?
 894            .await??;
 895        Ok(proto::Ack {})
 896    }
 897
 898    async fn handle_open_commit_message_buffer(
 899        this: Entity<Self>,
 900        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
 901        mut cx: AsyncApp,
 902    ) -> Result<proto::OpenBufferResponse> {
 903        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 904        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 905        let repository =
 906            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 907        let buffer = repository
 908            .update(&mut cx, |repository, cx| {
 909                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
 910            })?
 911            .await?;
 912
 913        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
 914        this.update(&mut cx, |this, cx| {
 915            this.buffer_store.update(cx, |buffer_store, cx| {
 916                buffer_store
 917                    .create_buffer_for_peer(
 918                        &buffer,
 919                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
 920                        cx,
 921                    )
 922                    .detach_and_log_err(cx);
 923            })
 924        })?;
 925
 926        Ok(proto::OpenBufferResponse {
 927            buffer_id: buffer_id.to_proto(),
 928        })
 929    }
 930
 931    async fn handle_askpass(
 932        this: Entity<Self>,
 933        envelope: TypedEnvelope<proto::AskPassRequest>,
 934        mut cx: AsyncApp,
 935    ) -> Result<proto::AskPassResponse> {
 936        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 937        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 938        let repository =
 939            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 940
 941        let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone())?;
 942        let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
 943            debug_panic!("no askpass found");
 944            return Err(anyhow::anyhow!("no askpass found"));
 945        };
 946
 947        let response = askpass.ask_password(envelope.payload.prompt).await?;
 948
 949        delegates
 950            .lock()
 951            .insert(envelope.payload.askpass_id, askpass);
 952
 953        Ok(proto::AskPassResponse { response })
 954    }
 955
 956    async fn handle_check_for_pushed_commits(
 957        this: Entity<Self>,
 958        envelope: TypedEnvelope<proto::CheckForPushedCommits>,
 959        mut cx: AsyncApp,
 960    ) -> Result<proto::CheckForPushedCommitsResponse> {
 961        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 962        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 963        let repository_handle =
 964            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 965
 966        let branches = repository_handle
 967            .update(&mut cx, |repository_handle, _| {
 968                repository_handle.check_for_pushed_commits()
 969            })?
 970            .await??;
 971        Ok(proto::CheckForPushedCommitsResponse {
 972            pushed_to: branches
 973                .into_iter()
 974                .map(|commit| commit.to_string())
 975                .collect(),
 976        })
 977    }
 978
 979    async fn handle_git_diff(
 980        this: Entity<Self>,
 981        envelope: TypedEnvelope<proto::GitDiff>,
 982        mut cx: AsyncApp,
 983    ) -> Result<proto::GitDiffResponse> {
 984        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 985        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
 986        let repository_handle =
 987            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
 988        let diff_type = match envelope.payload.diff_type() {
 989            proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
 990            proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
 991        };
 992
 993        let mut diff = repository_handle
 994            .update(&mut cx, |repository_handle, cx| {
 995                repository_handle.diff(diff_type, cx)
 996            })?
 997            .await??;
 998        const ONE_MB: usize = 1_000_000;
 999        if diff.len() > ONE_MB {
1000            diff = diff.chars().take(ONE_MB).collect()
1001        }
1002
1003        Ok(proto::GitDiffResponse { diff })
1004    }
1005
1006    fn repository_for_request(
1007        this: &Entity<Self>,
1008        worktree_id: WorktreeId,
1009        work_directory_id: ProjectEntryId,
1010        cx: &mut AsyncApp,
1011    ) -> Result<Entity<Repository>> {
1012        this.update(cx, |this, cx| {
1013            this.repositories
1014                .iter()
1015                .find(|repository_handle| {
1016                    repository_handle.read(cx).worktree_id == worktree_id
1017                        && repository_handle
1018                            .read(cx)
1019                            .repository_entry
1020                            .work_directory_id()
1021                            == work_directory_id
1022                })
1023                .context("missing repository handle")
1024                .cloned()
1025        })?
1026    }
1027}
1028
1029fn make_remote_delegate(
1030    this: Entity<GitStore>,
1031    project_id: u64,
1032    worktree_id: WorktreeId,
1033    work_directory_id: ProjectEntryId,
1034    askpass_id: u64,
1035    cx: &mut AsyncApp,
1036) -> AskPassDelegate {
1037    AskPassDelegate::new(cx, move |prompt, tx, cx| {
1038        this.update(cx, |this, cx| {
1039            let response = this.client().request(proto::AskPassRequest {
1040                project_id,
1041                worktree_id: worktree_id.to_proto(),
1042                work_directory_id: work_directory_id.to_proto(),
1043                askpass_id,
1044                prompt,
1045            });
1046            cx.spawn(|_, _| async move {
1047                tx.send(response.await?.response).ok();
1048                anyhow::Ok(())
1049            })
1050            .detach_and_log_err(cx);
1051        })
1052        .log_err();
1053    })
1054}
1055
1056impl GitRepo {}
1057
1058impl Repository {
1059    pub fn git_store(&self) -> Option<Entity<GitStore>> {
1060        self.git_store.upgrade()
1061    }
1062
1063    fn id(&self) -> (WorktreeId, ProjectEntryId) {
1064        (self.worktree_id, self.repository_entry.work_directory_id())
1065    }
1066
1067    pub fn current_branch(&self) -> Option<&Branch> {
1068        self.repository_entry.branch()
1069    }
1070
1071    fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
1072    where
1073        F: FnOnce(GitRepo, AsyncApp) -> Fut + 'static,
1074        Fut: Future<Output = R> + 'static,
1075        R: Send + 'static,
1076    {
1077        self.send_keyed_job(None, job)
1078    }
1079
1080    fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
1081    where
1082        F: FnOnce(GitRepo, AsyncApp) -> Fut + 'static,
1083        Fut: Future<Output = R> + 'static,
1084        R: Send + 'static,
1085    {
1086        let (result_tx, result_rx) = futures::channel::oneshot::channel();
1087        let git_repo = self.git_repo.clone();
1088        self.job_sender
1089            .unbounded_send(GitJob {
1090                key,
1091                job: Box::new(|cx: &mut AsyncApp| {
1092                    let job = job(git_repo, cx.clone());
1093                    cx.spawn(|_| async move {
1094                        let result = job.await;
1095                        result_tx.send(result).ok();
1096                    })
1097                }),
1098            })
1099            .ok();
1100        result_rx
1101    }
1102
1103    pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
1104        maybe!({
1105            let project_path = self.repo_path_to_project_path(&"".into())?;
1106            let worktree_name = project
1107                .worktree_for_id(project_path.worktree_id, cx)?
1108                .read(cx)
1109                .root_name();
1110
1111            let mut path = PathBuf::new();
1112            path = path.join(worktree_name);
1113            path = path.join(project_path.path);
1114            Some(path.to_string_lossy().to_string())
1115        })
1116        .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
1117        .into()
1118    }
1119
1120    pub fn activate(&self, cx: &mut Context<Self>) {
1121        let Some(git_store) = self.git_store.upgrade() else {
1122            return;
1123        };
1124        let entity = cx.entity();
1125        git_store.update(cx, |git_store, cx| {
1126            let Some(index) = git_store
1127                .repositories
1128                .iter()
1129                .position(|handle| *handle == entity)
1130            else {
1131                return;
1132            };
1133            git_store.active_index = Some(index);
1134            cx.emit(GitEvent::ActiveRepositoryChanged);
1135        });
1136    }
1137
1138    pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
1139        self.repository_entry.status()
1140    }
1141
1142    pub fn has_conflict(&self, path: &RepoPath) -> bool {
1143        self.repository_entry
1144            .current_merge_conflicts
1145            .contains(&path)
1146    }
1147
1148    pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
1149        let path = self.repository_entry.try_unrelativize(path)?;
1150        Some((self.worktree_id, path).into())
1151    }
1152
1153    pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
1154        self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
1155    }
1156
1157    // note: callers must verify these come from the same worktree
1158    pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
1159        let other_work_dir = &other.read(cx).repository_entry.work_directory;
1160        match (&self.repository_entry.work_directory, other_work_dir) {
1161            (WorkDirectory::InProject { .. }, WorkDirectory::AboveProject { .. }) => false,
1162            (WorkDirectory::AboveProject { .. }, WorkDirectory::InProject { .. }) => true,
1163            (
1164                WorkDirectory::InProject {
1165                    relative_path: this_path,
1166                },
1167                WorkDirectory::InProject {
1168                    relative_path: other_path,
1169                },
1170            ) => other_path.starts_with(this_path),
1171            (
1172                WorkDirectory::AboveProject {
1173                    absolute_path: this_path,
1174                    ..
1175                },
1176                WorkDirectory::AboveProject {
1177                    absolute_path: other_path,
1178                    ..
1179                },
1180            ) => other_path.starts_with(this_path),
1181        }
1182    }
1183
1184    pub fn worktree_id_path_to_repo_path(
1185        &self,
1186        worktree_id: WorktreeId,
1187        path: &Path,
1188    ) -> Option<RepoPath> {
1189        if worktree_id != self.worktree_id {
1190            return None;
1191        }
1192        self.repository_entry.relativize(path).log_err()
1193    }
1194
1195    pub fn open_commit_buffer(
1196        &mut self,
1197        languages: Option<Arc<LanguageRegistry>>,
1198        buffer_store: Entity<BufferStore>,
1199        cx: &mut Context<Self>,
1200    ) -> Task<Result<Entity<Buffer>>> {
1201        if let Some(buffer) = self.commit_message_buffer.clone() {
1202            return Task::ready(Ok(buffer));
1203        }
1204
1205        if let GitRepo::Remote {
1206            project_id,
1207            client,
1208            worktree_id,
1209            work_directory_id,
1210        } = self.git_repo.clone()
1211        {
1212            let client = client.clone();
1213            cx.spawn(|repository, mut cx| async move {
1214                let request = client.request(proto::OpenCommitMessageBuffer {
1215                    project_id: project_id.0,
1216                    worktree_id: worktree_id.to_proto(),
1217                    work_directory_id: work_directory_id.to_proto(),
1218                });
1219                let response = request.await.context("requesting to open commit buffer")?;
1220                let buffer_id = BufferId::new(response.buffer_id)?;
1221                let buffer = buffer_store
1222                    .update(&mut cx, |buffer_store, cx| {
1223                        buffer_store.wait_for_remote_buffer(buffer_id, cx)
1224                    })?
1225                    .await?;
1226                if let Some(language_registry) = languages {
1227                    let git_commit_language =
1228                        language_registry.language_for_name("Git Commit").await?;
1229                    buffer.update(&mut cx, |buffer, cx| {
1230                        buffer.set_language(Some(git_commit_language), cx);
1231                    })?;
1232                }
1233                repository.update(&mut cx, |repository, _| {
1234                    repository.commit_message_buffer = Some(buffer.clone());
1235                })?;
1236                Ok(buffer)
1237            })
1238        } else {
1239            self.open_local_commit_buffer(languages, buffer_store, cx)
1240        }
1241    }
1242
1243    fn open_local_commit_buffer(
1244        &mut self,
1245        language_registry: Option<Arc<LanguageRegistry>>,
1246        buffer_store: Entity<BufferStore>,
1247        cx: &mut Context<Self>,
1248    ) -> Task<Result<Entity<Buffer>>> {
1249        cx.spawn(|repository, mut cx| async move {
1250            let buffer = buffer_store
1251                .update(&mut cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
1252                .await?;
1253
1254            if let Some(language_registry) = language_registry {
1255                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
1256                buffer.update(&mut cx, |buffer, cx| {
1257                    buffer.set_language(Some(git_commit_language), cx);
1258                })?;
1259            }
1260
1261            repository.update(&mut cx, |repository, _| {
1262                repository.commit_message_buffer = Some(buffer.clone());
1263            })?;
1264            Ok(buffer)
1265        })
1266    }
1267
1268    pub fn checkout_files(
1269        &self,
1270        commit: &str,
1271        paths: Vec<RepoPath>,
1272        cx: &mut App,
1273    ) -> oneshot::Receiver<Result<()>> {
1274        let commit = commit.to_string();
1275        let env = self.worktree_environment(cx);
1276
1277        self.send_job(|git_repo, _| async move {
1278            match git_repo {
1279                GitRepo::Local(repo) => repo.checkout_files(commit, paths, env.await).await,
1280                GitRepo::Remote {
1281                    project_id,
1282                    client,
1283                    worktree_id,
1284                    work_directory_id,
1285                } => {
1286                    client
1287                        .request(proto::GitCheckoutFiles {
1288                            project_id: project_id.0,
1289                            worktree_id: worktree_id.to_proto(),
1290                            work_directory_id: work_directory_id.to_proto(),
1291                            commit,
1292                            paths: paths
1293                                .into_iter()
1294                                .map(|p| p.to_string_lossy().to_string())
1295                                .collect(),
1296                        })
1297                        .await?;
1298
1299                    Ok(())
1300                }
1301            }
1302        })
1303    }
1304
1305    pub fn reset(
1306        &self,
1307        commit: String,
1308        reset_mode: ResetMode,
1309        cx: &mut App,
1310    ) -> oneshot::Receiver<Result<()>> {
1311        let commit = commit.to_string();
1312        let env = self.worktree_environment(cx);
1313        self.send_job(|git_repo, _| async move {
1314            match git_repo {
1315                GitRepo::Local(git_repo) => {
1316                    let env = env.await;
1317                    git_repo.reset(commit, reset_mode, env).await
1318                }
1319                GitRepo::Remote {
1320                    project_id,
1321                    client,
1322                    worktree_id,
1323                    work_directory_id,
1324                } => {
1325                    client
1326                        .request(proto::GitReset {
1327                            project_id: project_id.0,
1328                            worktree_id: worktree_id.to_proto(),
1329                            work_directory_id: work_directory_id.to_proto(),
1330                            commit,
1331                            mode: match reset_mode {
1332                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
1333                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
1334                            },
1335                        })
1336                        .await?;
1337
1338                    Ok(())
1339                }
1340            }
1341        })
1342    }
1343
1344    pub fn show(&self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
1345        self.send_job(|git_repo, cx| async move {
1346            match git_repo {
1347                GitRepo::Local(git_repository) => git_repository.show(commit, cx).await,
1348                GitRepo::Remote {
1349                    project_id,
1350                    client,
1351                    worktree_id,
1352                    work_directory_id,
1353                } => {
1354                    let resp = client
1355                        .request(proto::GitShow {
1356                            project_id: project_id.0,
1357                            worktree_id: worktree_id.to_proto(),
1358                            work_directory_id: work_directory_id.to_proto(),
1359                            commit,
1360                        })
1361                        .await?;
1362
1363                    Ok(CommitDetails {
1364                        sha: resp.sha.into(),
1365                        message: resp.message.into(),
1366                        commit_timestamp: resp.commit_timestamp,
1367                        committer_email: resp.committer_email.into(),
1368                        committer_name: resp.committer_name.into(),
1369                    })
1370                }
1371            }
1372        })
1373    }
1374
1375    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
1376        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
1377    }
1378
1379    pub fn stage_entries(
1380        &self,
1381        entries: Vec<RepoPath>,
1382        cx: &mut Context<Self>,
1383    ) -> Task<anyhow::Result<()>> {
1384        if entries.is_empty() {
1385            return Task::ready(Ok(()));
1386        }
1387        let env = self.worktree_environment(cx);
1388
1389        let mut save_futures = Vec::new();
1390        if let Some(buffer_store) = self.buffer_store(cx) {
1391            buffer_store.update(cx, |buffer_store, cx| {
1392                for path in &entries {
1393                    let Some(path) = self.repository_entry.try_unrelativize(path) else {
1394                        continue;
1395                    };
1396                    let project_path = (self.worktree_id, path).into();
1397                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1398                        if buffer
1399                            .read(cx)
1400                            .file()
1401                            .map_or(false, |file| file.disk_state().exists())
1402                        {
1403                            save_futures.push(buffer_store.save_buffer(buffer, cx));
1404                        }
1405                    }
1406                }
1407            })
1408        }
1409
1410        cx.spawn(|this, mut cx| async move {
1411            for save_future in save_futures {
1412                save_future.await?;
1413            }
1414            let env = env.await;
1415
1416            this.update(&mut cx, |this, _| {
1417                this.send_job(|git_repo, cx| async move {
1418                    match git_repo {
1419                        GitRepo::Local(repo) => repo.stage_paths(entries, env, cx).await,
1420                        GitRepo::Remote {
1421                            project_id,
1422                            client,
1423                            worktree_id,
1424                            work_directory_id,
1425                        } => {
1426                            client
1427                                .request(proto::Stage {
1428                                    project_id: project_id.0,
1429                                    worktree_id: worktree_id.to_proto(),
1430                                    work_directory_id: work_directory_id.to_proto(),
1431                                    paths: entries
1432                                        .into_iter()
1433                                        .map(|repo_path| repo_path.as_ref().to_proto())
1434                                        .collect(),
1435                                })
1436                                .await
1437                                .context("sending stage request")?;
1438
1439                            Ok(())
1440                        }
1441                    }
1442                })
1443            })?
1444            .await??;
1445
1446            Ok(())
1447        })
1448    }
1449
1450    pub fn unstage_entries(
1451        &self,
1452        entries: Vec<RepoPath>,
1453        cx: &mut Context<Self>,
1454    ) -> Task<anyhow::Result<()>> {
1455        if entries.is_empty() {
1456            return Task::ready(Ok(()));
1457        }
1458        let env = self.worktree_environment(cx);
1459
1460        let mut save_futures = Vec::new();
1461        if let Some(buffer_store) = self.buffer_store(cx) {
1462            buffer_store.update(cx, |buffer_store, cx| {
1463                for path in &entries {
1464                    let Some(path) = self.repository_entry.try_unrelativize(path) else {
1465                        continue;
1466                    };
1467                    let project_path = (self.worktree_id, path).into();
1468                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1469                        if buffer
1470                            .read(cx)
1471                            .file()
1472                            .map_or(false, |file| file.disk_state().exists())
1473                        {
1474                            save_futures.push(buffer_store.save_buffer(buffer, cx));
1475                        }
1476                    }
1477                }
1478            })
1479        }
1480
1481        cx.spawn(move |this, mut cx| async move {
1482            for save_future in save_futures {
1483                save_future.await?;
1484            }
1485            let env = env.await;
1486
1487            this.update(&mut cx, |this, _| {
1488                this.send_job(|git_repo, cx| async move {
1489                    match git_repo {
1490                        GitRepo::Local(repo) => repo.unstage_paths(entries, env, cx).await,
1491                        GitRepo::Remote {
1492                            project_id,
1493                            client,
1494                            worktree_id,
1495                            work_directory_id,
1496                        } => {
1497                            client
1498                                .request(proto::Unstage {
1499                                    project_id: project_id.0,
1500                                    worktree_id: worktree_id.to_proto(),
1501                                    work_directory_id: work_directory_id.to_proto(),
1502                                    paths: entries
1503                                        .into_iter()
1504                                        .map(|repo_path| repo_path.as_ref().to_proto())
1505                                        .collect(),
1506                                })
1507                                .await
1508                                .context("sending unstage request")?;
1509
1510                            Ok(())
1511                        }
1512                    }
1513                })
1514            })?
1515            .await??;
1516
1517            Ok(())
1518        })
1519    }
1520
1521    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1522        let to_stage = self
1523            .repository_entry
1524            .status()
1525            .filter(|entry| !entry.status.staging().is_fully_staged())
1526            .map(|entry| entry.repo_path.clone())
1527            .collect();
1528        self.stage_entries(to_stage, cx)
1529    }
1530
1531    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1532        let to_unstage = self
1533            .repository_entry
1534            .status()
1535            .filter(|entry| entry.status.staging().has_staged())
1536            .map(|entry| entry.repo_path.clone())
1537            .collect();
1538        self.unstage_entries(to_unstage, cx)
1539    }
1540
1541    /// Get a count of all entries in the active repository, including
1542    /// untracked files.
1543    pub fn entry_count(&self) -> usize {
1544        self.repository_entry.status_len()
1545    }
1546
1547    fn worktree_environment(
1548        &self,
1549        cx: &mut App,
1550    ) -> impl Future<Output = HashMap<String, String>> + 'static {
1551        let task = self.project_environment.as_ref().and_then(|env| {
1552            env.update(cx, |env, cx| {
1553                env.get_environment(
1554                    Some(self.worktree_id),
1555                    Some(self.worktree_abs_path.clone()),
1556                    cx,
1557                )
1558            })
1559            .ok()
1560        });
1561        async move { OptionFuture::from(task).await.flatten().unwrap_or_default() }
1562    }
1563
1564    pub fn commit(
1565        &self,
1566        message: SharedString,
1567        name_and_email: Option<(SharedString, SharedString)>,
1568        cx: &mut App,
1569    ) -> oneshot::Receiver<Result<()>> {
1570        let env = self.worktree_environment(cx);
1571        self.send_job(|git_repo, cx| async move {
1572            match git_repo {
1573                GitRepo::Local(repo) => {
1574                    let env = env.await;
1575                    repo.commit(message, name_and_email, env, cx).await
1576                }
1577                GitRepo::Remote {
1578                    project_id,
1579                    client,
1580                    worktree_id,
1581                    work_directory_id,
1582                } => {
1583                    let (name, email) = name_and_email.unzip();
1584                    client
1585                        .request(proto::Commit {
1586                            project_id: project_id.0,
1587                            worktree_id: worktree_id.to_proto(),
1588                            work_directory_id: work_directory_id.to_proto(),
1589                            message: String::from(message),
1590                            name: name.map(String::from),
1591                            email: email.map(String::from),
1592                        })
1593                        .await
1594                        .context("sending commit request")?;
1595
1596                    Ok(())
1597                }
1598            }
1599        })
1600    }
1601
1602    pub fn fetch(
1603        &mut self,
1604        askpass: AskPassDelegate,
1605        cx: &mut App,
1606    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1607        let executor = cx.background_executor().clone();
1608        let askpass_delegates = self.askpass_delegates.clone();
1609        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1610        let env = self.worktree_environment(cx);
1611
1612        self.send_job(move |git_repo, cx| async move {
1613            match git_repo {
1614                GitRepo::Local(git_repository) => {
1615                    let askpass = AskPassSession::new(&executor, askpass).await?;
1616                    let env = env.await;
1617                    git_repository.fetch(askpass, env, cx).await
1618                }
1619                GitRepo::Remote {
1620                    project_id,
1621                    client,
1622                    worktree_id,
1623                    work_directory_id,
1624                } => {
1625                    askpass_delegates.lock().insert(askpass_id, askpass);
1626                    let _defer = util::defer(|| {
1627                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1628                        debug_assert!(askpass_delegate.is_some());
1629                    });
1630
1631                    let response = client
1632                        .request(proto::Fetch {
1633                            project_id: project_id.0,
1634                            worktree_id: worktree_id.to_proto(),
1635                            work_directory_id: work_directory_id.to_proto(),
1636                            askpass_id,
1637                        })
1638                        .await
1639                        .context("sending fetch request")?;
1640
1641                    Ok(RemoteCommandOutput {
1642                        stdout: response.stdout,
1643                        stderr: response.stderr,
1644                    })
1645                }
1646            }
1647        })
1648    }
1649
1650    pub fn push(
1651        &mut self,
1652        branch: SharedString,
1653        remote: SharedString,
1654        options: Option<PushOptions>,
1655        askpass: AskPassDelegate,
1656        cx: &mut App,
1657    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1658        let executor = cx.background_executor().clone();
1659        let askpass_delegates = self.askpass_delegates.clone();
1660        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1661        let env = self.worktree_environment(cx);
1662
1663        self.send_job(move |git_repo, cx| async move {
1664            match git_repo {
1665                GitRepo::Local(git_repository) => {
1666                    let env = env.await;
1667                    let askpass = AskPassSession::new(&executor, askpass).await?;
1668                    git_repository
1669                        .push(
1670                            branch.to_string(),
1671                            remote.to_string(),
1672                            options,
1673                            askpass,
1674                            env,
1675                            cx,
1676                        )
1677                        .await
1678                }
1679                GitRepo::Remote {
1680                    project_id,
1681                    client,
1682                    worktree_id,
1683                    work_directory_id,
1684                } => {
1685                    askpass_delegates.lock().insert(askpass_id, askpass);
1686                    let _defer = util::defer(|| {
1687                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1688                        debug_assert!(askpass_delegate.is_some());
1689                    });
1690                    let response = client
1691                        .request(proto::Push {
1692                            project_id: project_id.0,
1693                            worktree_id: worktree_id.to_proto(),
1694                            work_directory_id: work_directory_id.to_proto(),
1695                            askpass_id,
1696                            branch_name: branch.to_string(),
1697                            remote_name: remote.to_string(),
1698                            options: options.map(|options| match options {
1699                                PushOptions::Force => proto::push::PushOptions::Force,
1700                                PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
1701                            } as i32),
1702                        })
1703                        .await
1704                        .context("sending push request")?;
1705
1706                    Ok(RemoteCommandOutput {
1707                        stdout: response.stdout,
1708                        stderr: response.stderr,
1709                    })
1710                }
1711            }
1712        })
1713    }
1714
1715    pub fn pull(
1716        &mut self,
1717        branch: SharedString,
1718        remote: SharedString,
1719        askpass: AskPassDelegate,
1720        cx: &mut App,
1721    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1722        let executor = cx.background_executor().clone();
1723        let askpass_delegates = self.askpass_delegates.clone();
1724        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1725        let env = self.worktree_environment(cx);
1726
1727        self.send_job(move |git_repo, cx| async move {
1728            match git_repo {
1729                GitRepo::Local(git_repository) => {
1730                    let askpass = AskPassSession::new(&executor, askpass).await?;
1731                    let env = env.await;
1732                    git_repository
1733                        .pull(branch.to_string(), remote.to_string(), askpass, env, cx)
1734                        .await
1735                }
1736                GitRepo::Remote {
1737                    project_id,
1738                    client,
1739                    worktree_id,
1740                    work_directory_id,
1741                } => {
1742                    askpass_delegates.lock().insert(askpass_id, askpass);
1743                    let _defer = util::defer(|| {
1744                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1745                        debug_assert!(askpass_delegate.is_some());
1746                    });
1747                    let response = client
1748                        .request(proto::Pull {
1749                            project_id: project_id.0,
1750                            worktree_id: worktree_id.to_proto(),
1751                            work_directory_id: work_directory_id.to_proto(),
1752                            askpass_id,
1753                            branch_name: branch.to_string(),
1754                            remote_name: remote.to_string(),
1755                        })
1756                        .await
1757                        .context("sending pull request")?;
1758
1759                    Ok(RemoteCommandOutput {
1760                        stdout: response.stdout,
1761                        stderr: response.stderr,
1762                    })
1763                }
1764            }
1765        })
1766    }
1767
1768    fn set_index_text(
1769        &self,
1770        path: RepoPath,
1771        content: Option<String>,
1772        cx: &mut App,
1773    ) -> oneshot::Receiver<anyhow::Result<()>> {
1774        let env = self.worktree_environment(cx);
1775
1776        self.send_keyed_job(
1777            Some(GitJobKey::WriteIndex(path.clone())),
1778            |git_repo, cx| async move {
1779                match git_repo {
1780                    GitRepo::Local(repo) => repo.set_index_text(path, content, env.await, cx).await,
1781                    GitRepo::Remote {
1782                        project_id,
1783                        client,
1784                        worktree_id,
1785                        work_directory_id,
1786                    } => {
1787                        client
1788                            .request(proto::SetIndexText {
1789                                project_id: project_id.0,
1790                                worktree_id: worktree_id.to_proto(),
1791                                work_directory_id: work_directory_id.to_proto(),
1792                                path: path.as_ref().to_proto(),
1793                                text: content,
1794                            })
1795                            .await?;
1796                        Ok(())
1797                    }
1798                }
1799            },
1800        )
1801    }
1802
1803    pub fn get_remotes(
1804        &self,
1805        branch_name: Option<String>,
1806    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
1807        self.send_job(|repo, cx| async move {
1808            match repo {
1809                GitRepo::Local(git_repository) => git_repository.get_remotes(branch_name, cx).await,
1810                GitRepo::Remote {
1811                    project_id,
1812                    client,
1813                    worktree_id,
1814                    work_directory_id,
1815                } => {
1816                    let response = client
1817                        .request(proto::GetRemotes {
1818                            project_id: project_id.0,
1819                            worktree_id: worktree_id.to_proto(),
1820                            work_directory_id: work_directory_id.to_proto(),
1821                            branch_name,
1822                        })
1823                        .await?;
1824
1825                    let remotes = response
1826                        .remotes
1827                        .into_iter()
1828                        .map(|remotes| git::repository::Remote {
1829                            name: remotes.name.into(),
1830                        })
1831                        .collect();
1832
1833                    Ok(remotes)
1834                }
1835            }
1836        })
1837    }
1838
1839    pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
1840        self.send_job(|repo, cx| async move {
1841            match repo {
1842                GitRepo::Local(git_repository) => {
1843                    let git_repository = git_repository.clone();
1844                    cx.background_spawn(async move { git_repository.branches().await })
1845                        .await
1846                }
1847                GitRepo::Remote {
1848                    project_id,
1849                    client,
1850                    worktree_id,
1851                    work_directory_id,
1852                } => {
1853                    let response = client
1854                        .request(proto::GitGetBranches {
1855                            project_id: project_id.0,
1856                            worktree_id: worktree_id.to_proto(),
1857                            work_directory_id: work_directory_id.to_proto(),
1858                        })
1859                        .await?;
1860
1861                    let branches = response
1862                        .branches
1863                        .into_iter()
1864                        .map(|branch| worktree::proto_to_branch(&branch))
1865                        .collect();
1866
1867                    Ok(branches)
1868                }
1869            }
1870        })
1871    }
1872
1873    pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
1874        self.send_job(|repo, cx| async move {
1875            match repo {
1876                GitRepo::Local(git_repository) => git_repository.diff(diff_type, cx).await,
1877                GitRepo::Remote {
1878                    project_id,
1879                    client,
1880                    worktree_id,
1881                    work_directory_id,
1882                    ..
1883                } => {
1884                    let response = client
1885                        .request(proto::GitDiff {
1886                            project_id: project_id.0,
1887                            worktree_id: worktree_id.to_proto(),
1888                            work_directory_id: work_directory_id.to_proto(),
1889                            diff_type: match diff_type {
1890                                DiffType::HeadToIndex => {
1891                                    proto::git_diff::DiffType::HeadToIndex.into()
1892                                }
1893                                DiffType::HeadToWorktree => {
1894                                    proto::git_diff::DiffType::HeadToWorktree.into()
1895                                }
1896                            },
1897                        })
1898                        .await?;
1899
1900                    Ok(response.diff)
1901                }
1902            }
1903        })
1904    }
1905
1906    pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
1907        self.send_job(|repo, cx| async move {
1908            match repo {
1909                GitRepo::Local(git_repository) => {
1910                    git_repository.create_branch(branch_name, cx).await
1911                }
1912                GitRepo::Remote {
1913                    project_id,
1914                    client,
1915                    worktree_id,
1916                    work_directory_id,
1917                } => {
1918                    client
1919                        .request(proto::GitCreateBranch {
1920                            project_id: project_id.0,
1921                            worktree_id: worktree_id.to_proto(),
1922                            work_directory_id: work_directory_id.to_proto(),
1923                            branch_name,
1924                        })
1925                        .await?;
1926
1927                    Ok(())
1928                }
1929            }
1930        })
1931    }
1932
1933    pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
1934        self.send_job(|repo, cx| async move {
1935            match repo {
1936                GitRepo::Local(git_repository) => {
1937                    git_repository.change_branch(branch_name, cx).await
1938                }
1939                GitRepo::Remote {
1940                    project_id,
1941                    client,
1942                    worktree_id,
1943                    work_directory_id,
1944                } => {
1945                    client
1946                        .request(proto::GitChangeBranch {
1947                            project_id: project_id.0,
1948                            worktree_id: worktree_id.to_proto(),
1949                            work_directory_id: work_directory_id.to_proto(),
1950                            branch_name,
1951                        })
1952                        .await?;
1953
1954                    Ok(())
1955                }
1956            }
1957        })
1958    }
1959
1960    pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
1961        self.send_job(|repo, cx| async move {
1962            match repo {
1963                GitRepo::Local(git_repository) => git_repository.check_for_pushed_commit(cx).await,
1964                GitRepo::Remote {
1965                    project_id,
1966                    client,
1967                    worktree_id,
1968                    work_directory_id,
1969                } => {
1970                    let response = client
1971                        .request(proto::CheckForPushedCommits {
1972                            project_id: project_id.0,
1973                            worktree_id: worktree_id.to_proto(),
1974                            work_directory_id: work_directory_id.to_proto(),
1975                        })
1976                        .await?;
1977
1978                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
1979
1980                    Ok(branches)
1981                }
1982            }
1983        })
1984    }
1985}