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