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