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) -> Fut + 'static,
1085        Fut: Future<Output = R> + Send + '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) -> Fut + 'static,
1094        Fut: Future<Output = R> + Send + '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);
1104                    cx.background_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),
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: &str,
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)
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: &str) -> oneshot::Receiver<Result<CommitDetails>> {
1363        let commit = commit.to_string();
1364        self.send_job(|git_repo| async move {
1365            match git_repo {
1366                GitRepo::Local(git_repository) => git_repository.show(&commit),
1367                GitRepo::Remote {
1368                    project_id,
1369                    client,
1370                    worktree_id,
1371                    work_directory_id,
1372                } => {
1373                    let resp = client
1374                        .request(proto::GitShow {
1375                            project_id: project_id.0,
1376                            worktree_id: worktree_id.to_proto(),
1377                            work_directory_id: work_directory_id.to_proto(),
1378                            commit,
1379                        })
1380                        .await?;
1381
1382                    Ok(CommitDetails {
1383                        sha: resp.sha.into(),
1384                        message: resp.message.into(),
1385                        commit_timestamp: resp.commit_timestamp,
1386                        committer_email: resp.committer_email.into(),
1387                        committer_name: resp.committer_name.into(),
1388                    })
1389                }
1390            }
1391        })
1392    }
1393
1394    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
1395        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
1396    }
1397
1398    pub fn stage_entries(
1399        &self,
1400        entries: Vec<RepoPath>,
1401        cx: &mut Context<Self>,
1402    ) -> Task<anyhow::Result<()>> {
1403        if entries.is_empty() {
1404            return Task::ready(Ok(()));
1405        }
1406        let env = self.worktree_environment(cx);
1407
1408        let mut save_futures = Vec::new();
1409        if let Some(buffer_store) = self.buffer_store(cx) {
1410            buffer_store.update(cx, |buffer_store, cx| {
1411                for path in &entries {
1412                    let Some(path) = self.repository_entry.try_unrelativize(path) else {
1413                        continue;
1414                    };
1415                    let project_path = (self.worktree_id, path).into();
1416                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1417                        if buffer
1418                            .read(cx)
1419                            .file()
1420                            .map_or(false, |file| file.disk_state().exists())
1421                        {
1422                            save_futures.push(buffer_store.save_buffer(buffer, cx));
1423                        }
1424                    }
1425                }
1426            })
1427        }
1428
1429        cx.spawn(|this, mut cx| async move {
1430            for save_future in save_futures {
1431                save_future.await?;
1432            }
1433            let env = env.await;
1434
1435            this.update(&mut cx, |this, _| {
1436                this.send_job(|git_repo| async move {
1437                    match git_repo {
1438                        GitRepo::Local(repo) => repo.stage_paths(&entries, &env),
1439                        GitRepo::Remote {
1440                            project_id,
1441                            client,
1442                            worktree_id,
1443                            work_directory_id,
1444                        } => {
1445                            client
1446                                .request(proto::Stage {
1447                                    project_id: project_id.0,
1448                                    worktree_id: worktree_id.to_proto(),
1449                                    work_directory_id: work_directory_id.to_proto(),
1450                                    paths: entries
1451                                        .into_iter()
1452                                        .map(|repo_path| repo_path.as_ref().to_proto())
1453                                        .collect(),
1454                                })
1455                                .await
1456                                .context("sending stage request")?;
1457
1458                            Ok(())
1459                        }
1460                    }
1461                })
1462            })?
1463            .await??;
1464
1465            Ok(())
1466        })
1467    }
1468
1469    pub fn unstage_entries(
1470        &self,
1471        entries: Vec<RepoPath>,
1472        cx: &mut Context<Self>,
1473    ) -> Task<anyhow::Result<()>> {
1474        if entries.is_empty() {
1475            return Task::ready(Ok(()));
1476        }
1477        let env = self.worktree_environment(cx);
1478
1479        let mut save_futures = Vec::new();
1480        if let Some(buffer_store) = self.buffer_store(cx) {
1481            buffer_store.update(cx, |buffer_store, cx| {
1482                for path in &entries {
1483                    let Some(path) = self.repository_entry.try_unrelativize(path) else {
1484                        continue;
1485                    };
1486                    let project_path = (self.worktree_id, path).into();
1487                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1488                        if buffer
1489                            .read(cx)
1490                            .file()
1491                            .map_or(false, |file| file.disk_state().exists())
1492                        {
1493                            save_futures.push(buffer_store.save_buffer(buffer, cx));
1494                        }
1495                    }
1496                }
1497            })
1498        }
1499
1500        cx.spawn(move |this, mut cx| async move {
1501            for save_future in save_futures {
1502                save_future.await?;
1503            }
1504            let env = env.await;
1505
1506            this.update(&mut cx, |this, _| {
1507                this.send_job(|git_repo| async move {
1508                    match git_repo {
1509                        GitRepo::Local(repo) => repo.unstage_paths(&entries, &env),
1510                        GitRepo::Remote {
1511                            project_id,
1512                            client,
1513                            worktree_id,
1514                            work_directory_id,
1515                        } => {
1516                            client
1517                                .request(proto::Unstage {
1518                                    project_id: project_id.0,
1519                                    worktree_id: worktree_id.to_proto(),
1520                                    work_directory_id: work_directory_id.to_proto(),
1521                                    paths: entries
1522                                        .into_iter()
1523                                        .map(|repo_path| repo_path.as_ref().to_proto())
1524                                        .collect(),
1525                                })
1526                                .await
1527                                .context("sending unstage request")?;
1528
1529                            Ok(())
1530                        }
1531                    }
1532                })
1533            })?
1534            .await??;
1535
1536            Ok(())
1537        })
1538    }
1539
1540    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1541        let to_stage = self
1542            .repository_entry
1543            .status()
1544            .filter(|entry| !entry.status.staging().is_fully_staged())
1545            .map(|entry| entry.repo_path.clone())
1546            .collect();
1547        self.stage_entries(to_stage, cx)
1548    }
1549
1550    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1551        let to_unstage = self
1552            .repository_entry
1553            .status()
1554            .filter(|entry| entry.status.staging().has_staged())
1555            .map(|entry| entry.repo_path.clone())
1556            .collect();
1557        self.unstage_entries(to_unstage, cx)
1558    }
1559
1560    /// Get a count of all entries in the active repository, including
1561    /// untracked files.
1562    pub fn entry_count(&self) -> usize {
1563        self.repository_entry.status_len()
1564    }
1565
1566    fn worktree_environment(
1567        &self,
1568        cx: &mut App,
1569    ) -> impl Future<Output = HashMap<String, String>> + 'static {
1570        let task = self.project_environment.as_ref().and_then(|env| {
1571            env.update(cx, |env, cx| {
1572                env.get_environment(
1573                    Some(self.worktree_id),
1574                    Some(self.worktree_abs_path.clone()),
1575                    cx,
1576                )
1577            })
1578            .ok()
1579        });
1580        async move { OptionFuture::from(task).await.flatten().unwrap_or_default() }
1581    }
1582
1583    pub fn commit(
1584        &self,
1585        message: SharedString,
1586        name_and_email: Option<(SharedString, SharedString)>,
1587        cx: &mut App,
1588    ) -> oneshot::Receiver<Result<()>> {
1589        let env = self.worktree_environment(cx);
1590        self.send_job(|git_repo| async move {
1591            match git_repo {
1592                GitRepo::Local(repo) => {
1593                    let env = env.await;
1594                    repo.commit(
1595                        message.as_ref(),
1596                        name_and_email
1597                            .as_ref()
1598                            .map(|(name, email)| (name.as_ref(), email.as_ref())),
1599                        &env,
1600                    )
1601                }
1602                GitRepo::Remote {
1603                    project_id,
1604                    client,
1605                    worktree_id,
1606                    work_directory_id,
1607                } => {
1608                    let (name, email) = name_and_email.unzip();
1609                    client
1610                        .request(proto::Commit {
1611                            project_id: project_id.0,
1612                            worktree_id: worktree_id.to_proto(),
1613                            work_directory_id: work_directory_id.to_proto(),
1614                            message: String::from(message),
1615                            name: name.map(String::from),
1616                            email: email.map(String::from),
1617                        })
1618                        .await
1619                        .context("sending commit request")?;
1620
1621                    Ok(())
1622                }
1623            }
1624        })
1625    }
1626
1627    pub fn fetch(
1628        &mut self,
1629        askpass: AskPassDelegate,
1630        cx: &mut App,
1631    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1632        let executor = cx.background_executor().clone();
1633        let askpass_delegates = self.askpass_delegates.clone();
1634        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1635        let env = self.worktree_environment(cx);
1636
1637        self.send_job(move |git_repo| async move {
1638            match git_repo {
1639                GitRepo::Local(git_repository) => {
1640                    let askpass = AskPassSession::new(&executor, askpass).await?;
1641                    let env = env.await;
1642                    git_repository.fetch(askpass, &env)
1643                }
1644                GitRepo::Remote {
1645                    project_id,
1646                    client,
1647                    worktree_id,
1648                    work_directory_id,
1649                } => {
1650                    askpass_delegates.lock().insert(askpass_id, askpass);
1651                    let _defer = util::defer(|| {
1652                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1653                        debug_assert!(askpass_delegate.is_some());
1654                    });
1655
1656                    let response = client
1657                        .request(proto::Fetch {
1658                            project_id: project_id.0,
1659                            worktree_id: worktree_id.to_proto(),
1660                            work_directory_id: work_directory_id.to_proto(),
1661                            askpass_id,
1662                        })
1663                        .await
1664                        .context("sending fetch request")?;
1665
1666                    Ok(RemoteCommandOutput {
1667                        stdout: response.stdout,
1668                        stderr: response.stderr,
1669                    })
1670                }
1671            }
1672        })
1673    }
1674
1675    pub fn push(
1676        &mut self,
1677        branch: SharedString,
1678        remote: SharedString,
1679        options: Option<PushOptions>,
1680        askpass: AskPassDelegate,
1681        cx: &mut App,
1682    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1683        let executor = cx.background_executor().clone();
1684        let askpass_delegates = self.askpass_delegates.clone();
1685        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1686        let env = self.worktree_environment(cx);
1687
1688        self.send_job(move |git_repo| async move {
1689            match git_repo {
1690                GitRepo::Local(git_repository) => {
1691                    let env = env.await;
1692                    let askpass = AskPassSession::new(&executor, askpass).await?;
1693                    git_repository.push(&branch, &remote, options, askpass, &env)
1694                }
1695                GitRepo::Remote {
1696                    project_id,
1697                    client,
1698                    worktree_id,
1699                    work_directory_id,
1700                } => {
1701                    askpass_delegates.lock().insert(askpass_id, askpass);
1702                    let _defer = util::defer(|| {
1703                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1704                        debug_assert!(askpass_delegate.is_some());
1705                    });
1706                    let response = client
1707                        .request(proto::Push {
1708                            project_id: project_id.0,
1709                            worktree_id: worktree_id.to_proto(),
1710                            work_directory_id: work_directory_id.to_proto(),
1711                            askpass_id,
1712                            branch_name: branch.to_string(),
1713                            remote_name: remote.to_string(),
1714                            options: options.map(|options| match options {
1715                                PushOptions::Force => proto::push::PushOptions::Force,
1716                                PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
1717                            } as i32),
1718                        })
1719                        .await
1720                        .context("sending push request")?;
1721
1722                    Ok(RemoteCommandOutput {
1723                        stdout: response.stdout,
1724                        stderr: response.stderr,
1725                    })
1726                }
1727            }
1728        })
1729    }
1730
1731    pub fn pull(
1732        &mut self,
1733        branch: SharedString,
1734        remote: SharedString,
1735        askpass: AskPassDelegate,
1736        cx: &mut App,
1737    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1738        let executor = cx.background_executor().clone();
1739        let askpass_delegates = self.askpass_delegates.clone();
1740        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1741        let env = self.worktree_environment(cx);
1742
1743        self.send_job(move |git_repo| async move {
1744            match git_repo {
1745                GitRepo::Local(git_repository) => {
1746                    let askpass = AskPassSession::new(&executor, askpass).await?;
1747                    let env = env.await;
1748                    git_repository.pull(&branch, &remote, askpass, &env)
1749                }
1750                GitRepo::Remote {
1751                    project_id,
1752                    client,
1753                    worktree_id,
1754                    work_directory_id,
1755                } => {
1756                    askpass_delegates.lock().insert(askpass_id, askpass);
1757                    let _defer = util::defer(|| {
1758                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1759                        debug_assert!(askpass_delegate.is_some());
1760                    });
1761                    let response = client
1762                        .request(proto::Pull {
1763                            project_id: project_id.0,
1764                            worktree_id: worktree_id.to_proto(),
1765                            work_directory_id: work_directory_id.to_proto(),
1766                            askpass_id,
1767                            branch_name: branch.to_string(),
1768                            remote_name: remote.to_string(),
1769                        })
1770                        .await
1771                        .context("sending pull request")?;
1772
1773                    Ok(RemoteCommandOutput {
1774                        stdout: response.stdout,
1775                        stderr: response.stderr,
1776                    })
1777                }
1778            }
1779        })
1780    }
1781
1782    fn set_index_text(
1783        &self,
1784        path: &RepoPath,
1785        content: Option<String>,
1786        cx: &mut App,
1787    ) -> oneshot::Receiver<anyhow::Result<()>> {
1788        let path = path.clone();
1789        let env = self.worktree_environment(cx);
1790
1791        self.send_keyed_job(
1792            Some(GitJobKey::WriteIndex(path.clone())),
1793            |git_repo| async move {
1794                match git_repo {
1795                    GitRepo::Local(repo) => repo.set_index_text(&path, content, &env.await),
1796                    GitRepo::Remote {
1797                        project_id,
1798                        client,
1799                        worktree_id,
1800                        work_directory_id,
1801                    } => {
1802                        client
1803                            .request(proto::SetIndexText {
1804                                project_id: project_id.0,
1805                                worktree_id: worktree_id.to_proto(),
1806                                work_directory_id: work_directory_id.to_proto(),
1807                                path: path.as_ref().to_proto(),
1808                                text: content,
1809                            })
1810                            .await?;
1811                        Ok(())
1812                    }
1813                }
1814            },
1815        )
1816    }
1817
1818    pub fn get_remotes(
1819        &self,
1820        branch_name: Option<String>,
1821    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
1822        self.send_job(|repo| async move {
1823            match repo {
1824                GitRepo::Local(git_repository) => {
1825                    git_repository.get_remotes(branch_name.as_deref())
1826                }
1827                GitRepo::Remote {
1828                    project_id,
1829                    client,
1830                    worktree_id,
1831                    work_directory_id,
1832                } => {
1833                    let response = client
1834                        .request(proto::GetRemotes {
1835                            project_id: project_id.0,
1836                            worktree_id: worktree_id.to_proto(),
1837                            work_directory_id: work_directory_id.to_proto(),
1838                            branch_name,
1839                        })
1840                        .await?;
1841
1842                    let remotes = response
1843                        .remotes
1844                        .into_iter()
1845                        .map(|remotes| git::repository::Remote {
1846                            name: remotes.name.into(),
1847                        })
1848                        .collect();
1849
1850                    Ok(remotes)
1851                }
1852            }
1853        })
1854    }
1855
1856    pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
1857        self.send_job(|repo| async move {
1858            match repo {
1859                GitRepo::Local(git_repository) => git_repository.branches(),
1860                GitRepo::Remote {
1861                    project_id,
1862                    client,
1863                    worktree_id,
1864                    work_directory_id,
1865                } => {
1866                    let response = client
1867                        .request(proto::GitGetBranches {
1868                            project_id: project_id.0,
1869                            worktree_id: worktree_id.to_proto(),
1870                            work_directory_id: work_directory_id.to_proto(),
1871                        })
1872                        .await?;
1873
1874                    let branches = response
1875                        .branches
1876                        .into_iter()
1877                        .map(|branch| worktree::proto_to_branch(&branch))
1878                        .collect();
1879
1880                    Ok(branches)
1881                }
1882            }
1883        })
1884    }
1885
1886    pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
1887        self.send_job(|repo| async move {
1888            match repo {
1889                GitRepo::Local(git_repository) => git_repository.diff(diff_type),
1890                GitRepo::Remote {
1891                    project_id,
1892                    client,
1893                    worktree_id,
1894                    work_directory_id,
1895                    ..
1896                } => {
1897                    let response = client
1898                        .request(proto::GitDiff {
1899                            project_id: project_id.0,
1900                            worktree_id: worktree_id.to_proto(),
1901                            work_directory_id: work_directory_id.to_proto(),
1902                            diff_type: match diff_type {
1903                                DiffType::HeadToIndex => {
1904                                    proto::git_diff::DiffType::HeadToIndex.into()
1905                                }
1906                                DiffType::HeadToWorktree => {
1907                                    proto::git_diff::DiffType::HeadToWorktree.into()
1908                                }
1909                            },
1910                        })
1911                        .await?;
1912
1913                    Ok(response.diff)
1914                }
1915            }
1916        })
1917    }
1918
1919    pub fn create_branch(&self, branch_name: &str) -> oneshot::Receiver<Result<()>> {
1920        let branch_name = branch_name.to_owned();
1921        self.send_job(|repo| async move {
1922            match repo {
1923                GitRepo::Local(git_repository) => git_repository.create_branch(&branch_name),
1924                GitRepo::Remote {
1925                    project_id,
1926                    client,
1927                    worktree_id,
1928                    work_directory_id,
1929                } => {
1930                    client
1931                        .request(proto::GitCreateBranch {
1932                            project_id: project_id.0,
1933                            worktree_id: worktree_id.to_proto(),
1934                            work_directory_id: work_directory_id.to_proto(),
1935                            branch_name,
1936                        })
1937                        .await?;
1938
1939                    Ok(())
1940                }
1941            }
1942        })
1943    }
1944
1945    pub fn change_branch(&self, branch_name: &str) -> oneshot::Receiver<Result<()>> {
1946        let branch_name = branch_name.to_owned();
1947        self.send_job(|repo| async move {
1948            match repo {
1949                GitRepo::Local(git_repository) => git_repository.change_branch(&branch_name),
1950                GitRepo::Remote {
1951                    project_id,
1952                    client,
1953                    worktree_id,
1954                    work_directory_id,
1955                } => {
1956                    client
1957                        .request(proto::GitChangeBranch {
1958                            project_id: project_id.0,
1959                            worktree_id: worktree_id.to_proto(),
1960                            work_directory_id: work_directory_id.to_proto(),
1961                            branch_name,
1962                        })
1963                        .await?;
1964
1965                    Ok(())
1966                }
1967            }
1968        })
1969    }
1970
1971    pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
1972        self.send_job(|repo| async move {
1973            match repo {
1974                GitRepo::Local(git_repository) => git_repository.check_for_pushed_commit(),
1975                GitRepo::Remote {
1976                    project_id,
1977                    client,
1978                    worktree_id,
1979                    work_directory_id,
1980                } => {
1981                    let response = client
1982                        .request(proto::CheckForPushedCommits {
1983                            project_id: project_id.0,
1984                            worktree_id: worktree_id.to_proto(),
1985                            work_directory_id: work_directory_id.to_proto(),
1986                        })
1987                        .await?;
1988
1989                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
1990
1991                    Ok(branches)
1992                }
1993            }
1994        })
1995    }
1996}