git_store.rs

   1pub mod git_traversal;
   2
   3use crate::{
   4    buffer_store::{BufferStore, BufferStoreEvent},
   5    worktree_store::{WorktreeStore, WorktreeStoreEvent},
   6    Project, ProjectEnvironment, ProjectItem, ProjectPath,
   7};
   8use anyhow::{anyhow, bail, Context as _, Result};
   9use askpass::{AskPassDelegate, AskPassSession};
  10use buffer_diff::{BufferDiff, BufferDiffEvent};
  11use client::ProjectId;
  12use collections::HashMap;
  13use fs::Fs;
  14use futures::{
  15    channel::{mpsc, oneshot},
  16    future::{self, OptionFuture, Shared},
  17    FutureExt as _, StreamExt as _,
  18};
  19use git::{
  20    blame::Blame,
  21    parse_git_remote_url,
  22    repository::{
  23        Branch, CommitDetails, DiffType, GitRepository, GitRepositoryCheckpoint, PushOptions,
  24        Remote, RemoteCommandOutput, RepoPath, ResetMode,
  25    },
  26    status::FileStatus,
  27    BuildPermalinkParams, GitHostingProviderRegistry,
  28};
  29use gpui::{
  30    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
  31    WeakEntity,
  32};
  33use language::{
  34    proto::{deserialize_version, serialize_version},
  35    Buffer, BufferEvent, Language, LanguageRegistry,
  36};
  37use parking_lot::Mutex;
  38use rpc::{
  39    proto::{self, git_reset, ToProto, SSH_PROJECT_ID},
  40    AnyProtoClient, TypedEnvelope,
  41};
  42use serde::Deserialize;
  43use settings::WorktreeId;
  44use std::{
  45    collections::{hash_map, VecDeque},
  46    future::Future,
  47    ops::Range,
  48    path::{Path, PathBuf},
  49    sync::Arc,
  50};
  51use text::BufferId;
  52use util::{debug_panic, maybe, ResultExt};
  53use worktree::{
  54    File, ProjectEntryId, RepositoryEntry, StatusEntry, UpdatedGitRepositoriesSet, WorkDirectory,
  55    Worktree,
  56};
  57
  58pub struct GitStore {
  59    state: GitStoreState,
  60    buffer_store: Entity<BufferStore>,
  61    _worktree_store: Entity<WorktreeStore>,
  62    repositories: HashMap<ProjectEntryId, Entity<Repository>>,
  63    active_repo_id: Option<ProjectEntryId>,
  64    #[allow(clippy::type_complexity)]
  65    loading_diffs:
  66        HashMap<(BufferId, DiffKind), Shared<Task<Result<Entity<BufferDiff>, Arc<anyhow::Error>>>>>,
  67    diffs: HashMap<BufferId, Entity<BufferDiffState>>,
  68    update_sender: mpsc::UnboundedSender<GitJob>,
  69    shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>,
  70    _subscriptions: [Subscription; 2],
  71}
  72
  73#[derive(Default)]
  74struct SharedDiffs {
  75    unstaged: Option<Entity<BufferDiff>>,
  76    uncommitted: Option<Entity<BufferDiff>>,
  77}
  78
  79#[derive(Default)]
  80struct BufferDiffState {
  81    unstaged_diff: Option<WeakEntity<BufferDiff>>,
  82    uncommitted_diff: Option<WeakEntity<BufferDiff>>,
  83    recalculate_diff_task: Option<Task<Result<()>>>,
  84    language: Option<Arc<Language>>,
  85    language_registry: Option<Arc<LanguageRegistry>>,
  86    diff_updated_futures: Vec<oneshot::Sender<()>>,
  87
  88    head_text: Option<Arc<String>>,
  89    index_text: Option<Arc<String>>,
  90    head_changed: bool,
  91    index_changed: bool,
  92    language_changed: bool,
  93}
  94
  95#[derive(Clone, Debug)]
  96enum DiffBasesChange {
  97    SetIndex(Option<String>),
  98    SetHead(Option<String>),
  99    SetEach {
 100        index: Option<String>,
 101        head: Option<String>,
 102    },
 103    SetBoth(Option<String>),
 104}
 105
 106#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 107enum DiffKind {
 108    Unstaged,
 109    Uncommitted,
 110}
 111
 112enum GitStoreState {
 113    Local {
 114        downstream_client: Option<(AnyProtoClient, ProjectId)>,
 115        environment: Entity<ProjectEnvironment>,
 116        fs: Arc<dyn Fs>,
 117    },
 118    Ssh {
 119        upstream_client: AnyProtoClient,
 120        upstream_project_id: ProjectId,
 121        downstream_client: Option<(AnyProtoClient, ProjectId)>,
 122        environment: Entity<ProjectEnvironment>,
 123    },
 124    Remote {
 125        upstream_client: AnyProtoClient,
 126        project_id: ProjectId,
 127    },
 128}
 129
 130#[derive(Clone)]
 131pub struct GitStoreCheckpoint {
 132    checkpoints_by_dot_git_abs_path: HashMap<PathBuf, GitRepositoryCheckpoint>,
 133}
 134
 135pub struct Repository {
 136    commit_message_buffer: Option<Entity<Buffer>>,
 137    git_store: WeakEntity<GitStore>,
 138    project_environment: Option<WeakEntity<ProjectEnvironment>>,
 139    pub worktree_id: WorktreeId,
 140    pub repository_entry: RepositoryEntry,
 141    pub dot_git_abs_path: PathBuf,
 142    pub worktree_abs_path: Arc<Path>,
 143    pub is_from_single_file_worktree: bool,
 144    pub merge_message: Option<String>,
 145    pub completed_scan_id: usize,
 146    git_repo: RepositoryState,
 147    job_sender: mpsc::UnboundedSender<GitJob>,
 148    askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,
 149    latest_askpass_id: u64,
 150}
 151
 152#[derive(Clone)]
 153enum RepositoryState {
 154    Local(Arc<dyn GitRepository>),
 155    Remote {
 156        project_id: ProjectId,
 157        client: AnyProtoClient,
 158        worktree_id: WorktreeId,
 159        work_directory_id: ProjectEntryId,
 160    },
 161}
 162
 163#[derive(Debug)]
 164pub enum GitEvent {
 165    ActiveRepositoryChanged,
 166    FileSystemUpdated,
 167    GitStateUpdated,
 168    IndexWriteError(anyhow::Error),
 169}
 170
 171struct GitJob {
 172    job: Box<dyn FnOnce(&mut AsyncApp) -> Task<()>>,
 173    key: Option<GitJobKey>,
 174}
 175
 176#[derive(PartialEq, Eq)]
 177enum GitJobKey {
 178    WriteIndex(RepoPath),
 179    BatchReadIndex(ProjectEntryId),
 180}
 181
 182impl EventEmitter<GitEvent> for GitStore {}
 183
 184impl GitStore {
 185    pub fn local(
 186        worktree_store: &Entity<WorktreeStore>,
 187        buffer_store: Entity<BufferStore>,
 188        environment: Entity<ProjectEnvironment>,
 189        fs: Arc<dyn Fs>,
 190        cx: &mut Context<Self>,
 191    ) -> Self {
 192        Self::new(
 193            worktree_store.clone(),
 194            buffer_store,
 195            GitStoreState::Local {
 196                downstream_client: None,
 197                environment,
 198                fs,
 199            },
 200            cx,
 201        )
 202    }
 203
 204    pub fn remote(
 205        worktree_store: &Entity<WorktreeStore>,
 206        buffer_store: Entity<BufferStore>,
 207        upstream_client: AnyProtoClient,
 208        project_id: ProjectId,
 209        cx: &mut Context<Self>,
 210    ) -> Self {
 211        Self::new(
 212            worktree_store.clone(),
 213            buffer_store,
 214            GitStoreState::Remote {
 215                upstream_client,
 216                project_id,
 217            },
 218            cx,
 219        )
 220    }
 221
 222    pub fn ssh(
 223        worktree_store: &Entity<WorktreeStore>,
 224        buffer_store: Entity<BufferStore>,
 225        environment: Entity<ProjectEnvironment>,
 226        upstream_client: AnyProtoClient,
 227        cx: &mut Context<Self>,
 228    ) -> Self {
 229        Self::new(
 230            worktree_store.clone(),
 231            buffer_store,
 232            GitStoreState::Ssh {
 233                upstream_client,
 234                upstream_project_id: ProjectId(SSH_PROJECT_ID),
 235                downstream_client: None,
 236                environment,
 237            },
 238            cx,
 239        )
 240    }
 241
 242    fn new(
 243        worktree_store: Entity<WorktreeStore>,
 244        buffer_store: Entity<BufferStore>,
 245        state: GitStoreState,
 246        cx: &mut Context<Self>,
 247    ) -> Self {
 248        let update_sender = Self::spawn_git_worker(cx);
 249        let _subscriptions = [
 250            cx.subscribe(&worktree_store, Self::on_worktree_store_event),
 251            cx.subscribe(&buffer_store, Self::on_buffer_store_event),
 252        ];
 253
 254        GitStore {
 255            state,
 256            buffer_store,
 257            _worktree_store: worktree_store,
 258            repositories: HashMap::default(),
 259            active_repo_id: None,
 260            update_sender,
 261            _subscriptions,
 262            loading_diffs: HashMap::default(),
 263            shared_diffs: HashMap::default(),
 264            diffs: HashMap::default(),
 265        }
 266    }
 267
 268    pub fn init(client: &AnyProtoClient) {
 269        client.add_entity_request_handler(Self::handle_get_remotes);
 270        client.add_entity_request_handler(Self::handle_get_branches);
 271        client.add_entity_request_handler(Self::handle_change_branch);
 272        client.add_entity_request_handler(Self::handle_create_branch);
 273        client.add_entity_request_handler(Self::handle_git_init);
 274        client.add_entity_request_handler(Self::handle_push);
 275        client.add_entity_request_handler(Self::handle_pull);
 276        client.add_entity_request_handler(Self::handle_fetch);
 277        client.add_entity_request_handler(Self::handle_stage);
 278        client.add_entity_request_handler(Self::handle_unstage);
 279        client.add_entity_request_handler(Self::handle_commit);
 280        client.add_entity_request_handler(Self::handle_reset);
 281        client.add_entity_request_handler(Self::handle_show);
 282        client.add_entity_request_handler(Self::handle_checkout_files);
 283        client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
 284        client.add_entity_request_handler(Self::handle_set_index_text);
 285        client.add_entity_request_handler(Self::handle_askpass);
 286        client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
 287        client.add_entity_request_handler(Self::handle_git_diff);
 288        client.add_entity_request_handler(Self::handle_open_unstaged_diff);
 289        client.add_entity_request_handler(Self::handle_open_uncommitted_diff);
 290        client.add_entity_message_handler(Self::handle_update_diff_bases);
 291        client.add_entity_request_handler(Self::handle_get_permalink_to_line);
 292        client.add_entity_request_handler(Self::handle_blame_buffer);
 293    }
 294
 295    pub fn is_local(&self) -> bool {
 296        matches!(self.state, GitStoreState::Local { .. })
 297    }
 298
 299    pub fn shared(&mut self, remote_id: u64, client: AnyProtoClient, _cx: &mut App) {
 300        match &mut self.state {
 301            GitStoreState::Local {
 302                downstream_client, ..
 303            }
 304            | GitStoreState::Ssh {
 305                downstream_client, ..
 306            } => {
 307                *downstream_client = Some((client, ProjectId(remote_id)));
 308            }
 309            GitStoreState::Remote { .. } => {
 310                debug_panic!("shared called on remote store");
 311            }
 312        }
 313    }
 314
 315    pub fn unshared(&mut self, _cx: &mut Context<Self>) {
 316        match &mut self.state {
 317            GitStoreState::Local {
 318                downstream_client, ..
 319            }
 320            | GitStoreState::Ssh {
 321                downstream_client, ..
 322            } => {
 323                downstream_client.take();
 324            }
 325            GitStoreState::Remote { .. } => {
 326                debug_panic!("unshared called on remote store");
 327            }
 328        }
 329        self.shared_diffs.clear();
 330    }
 331
 332    pub(crate) fn forget_shared_diffs_for(&mut self, peer_id: &proto::PeerId) {
 333        self.shared_diffs.remove(peer_id);
 334    }
 335
 336    pub fn active_repository(&self) -> Option<Entity<Repository>> {
 337        self.active_repo_id
 338            .as_ref()
 339            .map(|id| self.repositories[&id].clone())
 340    }
 341
 342    pub fn open_unstaged_diff(
 343        &mut self,
 344        buffer: Entity<Buffer>,
 345        cx: &mut Context<Self>,
 346    ) -> Task<Result<Entity<BufferDiff>>> {
 347        let buffer_id = buffer.read(cx).remote_id();
 348        if let Some(diff_state) = self.diffs.get(&buffer_id) {
 349            if let Some(unstaged_diff) = diff_state
 350                .read(cx)
 351                .unstaged_diff
 352                .as_ref()
 353                .and_then(|weak| weak.upgrade())
 354            {
 355                if let Some(task) =
 356                    diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
 357                {
 358                    return cx.background_executor().spawn(async move {
 359                        task.await?;
 360                        Ok(unstaged_diff)
 361                    });
 362                }
 363                return Task::ready(Ok(unstaged_diff));
 364            }
 365        }
 366
 367        let task = match self.loading_diffs.entry((buffer_id, DiffKind::Unstaged)) {
 368            hash_map::Entry::Occupied(e) => e.get().clone(),
 369            hash_map::Entry::Vacant(entry) => {
 370                let staged_text = self.state.load_staged_text(&buffer, &self.buffer_store, cx);
 371                entry
 372                    .insert(
 373                        cx.spawn(async move |this, cx| {
 374                            Self::open_diff_internal(
 375                                this,
 376                                DiffKind::Unstaged,
 377                                staged_text.await.map(DiffBasesChange::SetIndex),
 378                                buffer,
 379                                cx,
 380                            )
 381                            .await
 382                            .map_err(Arc::new)
 383                        })
 384                        .shared(),
 385                    )
 386                    .clone()
 387            }
 388        };
 389
 390        cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
 391    }
 392
 393    pub fn open_uncommitted_diff(
 394        &mut self,
 395        buffer: Entity<Buffer>,
 396        cx: &mut Context<Self>,
 397    ) -> Task<Result<Entity<BufferDiff>>> {
 398        let buffer_id = buffer.read(cx).remote_id();
 399
 400        if let Some(diff_state) = self.diffs.get(&buffer_id) {
 401            if let Some(uncommitted_diff) = diff_state
 402                .read(cx)
 403                .uncommitted_diff
 404                .as_ref()
 405                .and_then(|weak| weak.upgrade())
 406            {
 407                if let Some(task) =
 408                    diff_state.update(cx, |diff_state, _| diff_state.wait_for_recalculation())
 409                {
 410                    return cx.background_executor().spawn(async move {
 411                        task.await?;
 412                        Ok(uncommitted_diff)
 413                    });
 414                }
 415                return Task::ready(Ok(uncommitted_diff));
 416            }
 417        }
 418
 419        let task = match self.loading_diffs.entry((buffer_id, DiffKind::Uncommitted)) {
 420            hash_map::Entry::Occupied(e) => e.get().clone(),
 421            hash_map::Entry::Vacant(entry) => {
 422                let changes = self
 423                    .state
 424                    .load_committed_text(&buffer, &self.buffer_store, cx);
 425
 426                entry
 427                    .insert(
 428                        cx.spawn(async move |this, cx| {
 429                            Self::open_diff_internal(
 430                                this,
 431                                DiffKind::Uncommitted,
 432                                changes.await,
 433                                buffer,
 434                                cx,
 435                            )
 436                            .await
 437                            .map_err(Arc::new)
 438                        })
 439                        .shared(),
 440                    )
 441                    .clone()
 442            }
 443        };
 444
 445        cx.background_spawn(async move { task.await.map_err(|e| anyhow!("{e}")) })
 446    }
 447
 448    async fn open_diff_internal(
 449        this: WeakEntity<Self>,
 450        kind: DiffKind,
 451        texts: Result<DiffBasesChange>,
 452        buffer_entity: Entity<Buffer>,
 453        cx: &mut AsyncApp,
 454    ) -> Result<Entity<BufferDiff>> {
 455        let diff_bases_change = match texts {
 456            Err(e) => {
 457                this.update(cx, |this, cx| {
 458                    let buffer = buffer_entity.read(cx);
 459                    let buffer_id = buffer.remote_id();
 460                    this.loading_diffs.remove(&(buffer_id, kind));
 461                })?;
 462                return Err(e);
 463            }
 464            Ok(change) => change,
 465        };
 466
 467        this.update(cx, |this, cx| {
 468            let buffer = buffer_entity.read(cx);
 469            let buffer_id = buffer.remote_id();
 470            let language = buffer.language().cloned();
 471            let language_registry = buffer.language_registry();
 472            let text_snapshot = buffer.text_snapshot();
 473            this.loading_diffs.remove(&(buffer_id, kind));
 474
 475            let diff_state = this
 476                .diffs
 477                .entry(buffer_id)
 478                .or_insert_with(|| cx.new(|_| BufferDiffState::default()));
 479
 480            let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
 481
 482            cx.subscribe(&diff, Self::on_buffer_diff_event).detach();
 483            diff_state.update(cx, |diff_state, cx| {
 484                diff_state.language = language;
 485                diff_state.language_registry = language_registry;
 486
 487                match kind {
 488                    DiffKind::Unstaged => diff_state.unstaged_diff = Some(diff.downgrade()),
 489                    DiffKind::Uncommitted => {
 490                        let unstaged_diff = if let Some(diff) = diff_state.unstaged_diff() {
 491                            diff
 492                        } else {
 493                            let unstaged_diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
 494                            diff_state.unstaged_diff = Some(unstaged_diff.downgrade());
 495                            unstaged_diff
 496                        };
 497
 498                        diff.update(cx, |diff, _| diff.set_secondary_diff(unstaged_diff));
 499                        diff_state.uncommitted_diff = Some(diff.downgrade())
 500                    }
 501                }
 502
 503                let rx = diff_state.diff_bases_changed(text_snapshot, diff_bases_change, cx);
 504
 505                anyhow::Ok(async move {
 506                    rx.await.ok();
 507                    Ok(diff)
 508                })
 509            })
 510        })??
 511        .await
 512    }
 513
 514    pub fn get_unstaged_diff(&self, buffer_id: BufferId, cx: &App) -> Option<Entity<BufferDiff>> {
 515        let diff_state = self.diffs.get(&buffer_id)?;
 516        diff_state.read(cx).unstaged_diff.as_ref()?.upgrade()
 517    }
 518
 519    pub fn get_uncommitted_diff(
 520        &self,
 521        buffer_id: BufferId,
 522        cx: &App,
 523    ) -> Option<Entity<BufferDiff>> {
 524        let diff_state = self.diffs.get(&buffer_id)?;
 525        diff_state.read(cx).uncommitted_diff.as_ref()?.upgrade()
 526    }
 527
 528    pub fn project_path_git_status(
 529        &self,
 530        project_path: &ProjectPath,
 531        cx: &App,
 532    ) -> Option<FileStatus> {
 533        let (repo, repo_path) = self.repository_and_path_for_project_path(project_path, cx)?;
 534        Some(
 535            repo.read(cx)
 536                .repository_entry
 537                .status_for_path(&repo_path)?
 538                .status,
 539        )
 540    }
 541
 542    pub fn checkpoint(&self, cx: &App) -> Task<Result<GitStoreCheckpoint>> {
 543        let mut dot_git_abs_paths = Vec::new();
 544        let mut checkpoints = Vec::new();
 545        for repository in self.repositories.values() {
 546            let repository = repository.read(cx);
 547            dot_git_abs_paths.push(repository.dot_git_abs_path.clone());
 548            checkpoints.push(repository.checkpoint().map(|checkpoint| checkpoint?));
 549        }
 550
 551        cx.background_executor().spawn(async move {
 552            let checkpoints: Vec<GitRepositoryCheckpoint> =
 553                future::try_join_all(checkpoints).await?;
 554            Ok(GitStoreCheckpoint {
 555                checkpoints_by_dot_git_abs_path: dot_git_abs_paths
 556                    .into_iter()
 557                    .zip(checkpoints)
 558                    .collect(),
 559            })
 560        })
 561    }
 562
 563    pub fn restore_checkpoint(&self, checkpoint: GitStoreCheckpoint, cx: &App) -> Task<Result<()>> {
 564        let repositories_by_dot_git_abs_path = self
 565            .repositories
 566            .values()
 567            .map(|repo| (repo.read(cx).dot_git_abs_path.clone(), repo))
 568            .collect::<HashMap<_, _>>();
 569
 570        let mut tasks = Vec::new();
 571        for (dot_git_abs_path, checkpoint) in checkpoint.checkpoints_by_dot_git_abs_path {
 572            if let Some(repository) = repositories_by_dot_git_abs_path.get(&dot_git_abs_path) {
 573                let restore = repository.read(cx).restore_checkpoint(checkpoint);
 574                tasks.push(async move { restore.await? });
 575            }
 576        }
 577        cx.background_spawn(async move {
 578            future::try_join_all(tasks).await?;
 579            Ok(())
 580        })
 581    }
 582
 583    pub fn blame_buffer(
 584        &self,
 585        buffer: &Entity<Buffer>,
 586        version: Option<clock::Global>,
 587        cx: &App,
 588    ) -> Task<Result<Option<Blame>>> {
 589        let buffer = buffer.read(cx);
 590        let Some(file) = File::from_dyn(buffer.file()) else {
 591            return Task::ready(Err(anyhow!("buffer has no file")));
 592        };
 593
 594        match file.worktree.clone().read(cx) {
 595            Worktree::Local(worktree) => {
 596                let worktree = worktree.snapshot();
 597                let blame_params = maybe!({
 598                    let local_repo = match worktree.local_repo_for_path(&file.path) {
 599                        Some(repo_for_path) => repo_for_path,
 600                        None => return Ok(None),
 601                    };
 602
 603                    let relative_path = local_repo
 604                        .relativize(&file.path)
 605                        .context("failed to relativize buffer path")?;
 606
 607                    let repo = local_repo.repo().clone();
 608
 609                    let content = match version {
 610                        Some(version) => buffer.rope_for_version(&version).clone(),
 611                        None => buffer.as_rope().clone(),
 612                    };
 613
 614                    anyhow::Ok(Some((repo, relative_path, content)))
 615                });
 616
 617                cx.spawn(async move |cx| {
 618                    let Some((repo, relative_path, content)) = blame_params? else {
 619                        return Ok(None);
 620                    };
 621                    repo.blame(relative_path.clone(), content, cx)
 622                        .await
 623                        .with_context(|| format!("Failed to blame {:?}", relative_path.0))
 624                        .map(Some)
 625                })
 626            }
 627            Worktree::Remote(worktree) => {
 628                let buffer_id = buffer.remote_id();
 629                let version = buffer.version();
 630                let project_id = worktree.project_id();
 631                let client = worktree.client();
 632                cx.spawn(async move |_| {
 633                    let response = client
 634                        .request(proto::BlameBuffer {
 635                            project_id,
 636                            buffer_id: buffer_id.into(),
 637                            version: serialize_version(&version),
 638                        })
 639                        .await?;
 640                    Ok(deserialize_blame_buffer_response(response))
 641                })
 642            }
 643        }
 644    }
 645
 646    pub fn get_permalink_to_line(
 647        &self,
 648        buffer: &Entity<Buffer>,
 649        selection: Range<u32>,
 650        cx: &App,
 651    ) -> Task<Result<url::Url>> {
 652        let buffer = buffer.read(cx);
 653        let Some(file) = File::from_dyn(buffer.file()) else {
 654            return Task::ready(Err(anyhow!("buffer has no file")));
 655        };
 656
 657        match file.worktree.read(cx) {
 658            Worktree::Local(worktree) => {
 659                let worktree_path = worktree.abs_path().clone();
 660                let Some((repo_entry, repo)) =
 661                    worktree.repository_for_path(&file.path).and_then(|entry| {
 662                        let repo = worktree.get_local_repo(&entry)?.repo().clone();
 663                        Some((entry, repo))
 664                    })
 665                else {
 666                    // If we're not in a Git repo, check whether this is a Rust source
 667                    // file in the Cargo registry (presumably opened with go-to-definition
 668                    // from a normal Rust file). If so, we can put together a permalink
 669                    // using crate metadata.
 670                    if buffer
 671                        .language()
 672                        .is_none_or(|lang| lang.name() != "Rust".into())
 673                    {
 674                        return Task::ready(Err(anyhow!("no permalink available")));
 675                    }
 676                    let file_path = worktree_path.join(&file.path);
 677                    return cx.spawn(async move |cx| {
 678                        let provider_registry =
 679                            cx.update(GitHostingProviderRegistry::default_global)?;
 680                        get_permalink_in_rust_registry_src(provider_registry, file_path, selection)
 681                            .map_err(|_| anyhow!("no permalink available"))
 682                    });
 683                };
 684
 685                let path = match repo_entry.relativize(&file.path) {
 686                    Ok(RepoPath(path)) => path,
 687                    Err(e) => return Task::ready(Err(e)),
 688                };
 689
 690                let remote = repo_entry
 691                    .branch()
 692                    .and_then(|b| b.upstream.as_ref())
 693                    .and_then(|b| b.remote_name())
 694                    .unwrap_or("origin")
 695                    .to_string();
 696
 697                cx.spawn(async move |cx| {
 698                    let origin_url = repo
 699                        .remote_url(&remote)
 700                        .ok_or_else(|| anyhow!("remote \"{remote}\" not found"))?;
 701
 702                    let sha = repo
 703                        .head_sha()
 704                        .ok_or_else(|| anyhow!("failed to read HEAD SHA"))?;
 705
 706                    let provider_registry =
 707                        cx.update(GitHostingProviderRegistry::default_global)?;
 708
 709                    let (provider, remote) =
 710                        parse_git_remote_url(provider_registry, &origin_url)
 711                            .ok_or_else(|| anyhow!("failed to parse Git remote URL"))?;
 712
 713                    let path = path
 714                        .to_str()
 715                        .ok_or_else(|| anyhow!("failed to convert path to string"))?;
 716
 717                    Ok(provider.build_permalink(
 718                        remote,
 719                        BuildPermalinkParams {
 720                            sha: &sha,
 721                            path,
 722                            selection: Some(selection),
 723                        },
 724                    ))
 725                })
 726            }
 727            Worktree::Remote(worktree) => {
 728                let buffer_id = buffer.remote_id();
 729                let project_id = worktree.project_id();
 730                let client = worktree.client();
 731                cx.spawn(async move |_| {
 732                    let response = client
 733                        .request(proto::GetPermalinkToLine {
 734                            project_id,
 735                            buffer_id: buffer_id.into(),
 736                            selection: Some(proto::Range {
 737                                start: selection.start as u64,
 738                                end: selection.end as u64,
 739                            }),
 740                        })
 741                        .await?;
 742
 743                    url::Url::parse(&response.permalink).context("failed to parse permalink")
 744                })
 745            }
 746        }
 747    }
 748
 749    fn downstream_client(&self) -> Option<(AnyProtoClient, ProjectId)> {
 750        match &self.state {
 751            GitStoreState::Local {
 752                downstream_client, ..
 753            }
 754            | GitStoreState::Ssh {
 755                downstream_client, ..
 756            } => downstream_client.clone(),
 757            GitStoreState::Remote { .. } => None,
 758        }
 759    }
 760
 761    fn upstream_client(&self) -> Option<AnyProtoClient> {
 762        match &self.state {
 763            GitStoreState::Local { .. } => None,
 764            GitStoreState::Ssh {
 765                upstream_client, ..
 766            }
 767            | GitStoreState::Remote {
 768                upstream_client, ..
 769            } => Some(upstream_client.clone()),
 770        }
 771    }
 772
 773    fn project_environment(&self) -> Option<Entity<ProjectEnvironment>> {
 774        match &self.state {
 775            GitStoreState::Local { environment, .. } => Some(environment.clone()),
 776            GitStoreState::Ssh { environment, .. } => Some(environment.clone()),
 777            GitStoreState::Remote { .. } => None,
 778        }
 779    }
 780
 781    fn project_id(&self) -> Option<ProjectId> {
 782        match &self.state {
 783            GitStoreState::Local { .. } => None,
 784            GitStoreState::Ssh { .. } => Some(ProjectId(proto::SSH_PROJECT_ID)),
 785            GitStoreState::Remote { project_id, .. } => Some(*project_id),
 786        }
 787    }
 788
 789    fn on_worktree_store_event(
 790        &mut self,
 791        worktree_store: Entity<WorktreeStore>,
 792        event: &WorktreeStoreEvent,
 793        cx: &mut Context<Self>,
 794    ) {
 795        let mut new_repositories = HashMap::default();
 796        let git_store = cx.weak_entity();
 797
 798        worktree_store.update(cx, |worktree_store, cx| {
 799            for worktree in worktree_store.worktrees() {
 800                worktree.update(cx, |worktree, cx| {
 801                    let snapshot = worktree.snapshot();
 802                    for repo_entry in snapshot.repositories().iter() {
 803                        let git_repo_and_merge_message = worktree
 804                            .as_local()
 805                            .and_then(|local_worktree| local_worktree.get_local_repo(repo_entry))
 806                            .map(|local_repo| {
 807                                (
 808                                    RepositoryState::Local(local_repo.repo().clone()),
 809                                    local_repo.merge_message.clone(),
 810                                )
 811                            })
 812                            .or_else(|| {
 813                                let git_repo = RepositoryState::Remote {
 814                                    project_id: self.project_id()?,
 815                                    client: self
 816                                        .upstream_client()
 817                                        .context("no upstream client")
 818                                        .log_err()?
 819                                        .clone(),
 820                                    worktree_id: worktree.id(),
 821                                    work_directory_id: repo_entry.work_directory_id(),
 822                                };
 823                                Some((git_repo, None))
 824                            });
 825
 826                        let Some((git_repo, merge_message)) = git_repo_and_merge_message else {
 827                            continue;
 828                        };
 829
 830                        let existing_repo = self.repositories.values().find(|repo| {
 831                            repo.read(cx).id() == (worktree.id(), repo_entry.work_directory_id())
 832                        });
 833
 834                        let repo = if let Some(existing_repo) = existing_repo {
 835                            // Update the statuses and merge message but keep everything else.
 836                            let existing_repo = existing_repo.clone();
 837                            existing_repo.update(cx, |existing_repo, _| {
 838                                existing_repo.repository_entry = repo_entry.clone();
 839                                if matches!(git_repo, RepositoryState::Local { .. }) {
 840                                    existing_repo.merge_message = merge_message;
 841                                    existing_repo.completed_scan_id = worktree.completed_scan_id();
 842                                }
 843                            });
 844                            existing_repo
 845                        } else {
 846                            cx.new(|_| Repository {
 847                                project_environment: self
 848                                    .project_environment()
 849                                    .as_ref()
 850                                    .map(|env| env.downgrade()),
 851                                git_store: git_store.clone(),
 852                                worktree_id: worktree.id(),
 853                                askpass_delegates: Default::default(),
 854                                latest_askpass_id: 0,
 855                                repository_entry: repo_entry.clone(),
 856                                dot_git_abs_path: worktree
 857                                    .dot_git_abs_path(&repo_entry.work_directory),
 858                                worktree_abs_path: worktree.abs_path(),
 859                                is_from_single_file_worktree: worktree.is_single_file(),
 860                                git_repo,
 861                                job_sender: self.update_sender.clone(),
 862                                merge_message,
 863                                commit_message_buffer: None,
 864                                completed_scan_id: worktree.completed_scan_id(),
 865                            })
 866                        };
 867                        new_repositories.insert(repo_entry.work_directory_id(), repo);
 868                    }
 869                })
 870            }
 871        });
 872
 873        self.repositories = new_repositories;
 874        if let Some(id) = self.active_repo_id.as_ref() {
 875            if !self.repositories.contains_key(id) {
 876                self.active_repo_id = None;
 877            }
 878        } else if let Some(&first_id) = self.repositories.keys().next() {
 879            self.active_repo_id = Some(first_id);
 880        }
 881
 882        match event {
 883            WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
 884                cx.emit(GitEvent::GitStateUpdated);
 885            }
 886            WorktreeStoreEvent::WorktreeAdded(worktree) => {
 887                if self.is_local() {
 888                    cx.subscribe(worktree, Self::on_worktree_event).detach();
 889                }
 890            }
 891            _ => {
 892                cx.emit(GitEvent::FileSystemUpdated);
 893            }
 894        }
 895    }
 896
 897    fn on_worktree_event(
 898        &mut self,
 899        worktree: Entity<Worktree>,
 900        event: &worktree::Event,
 901        cx: &mut Context<Self>,
 902    ) {
 903        if let worktree::Event::UpdatedGitRepositories(changed_repos) = event {
 904            self.local_worktree_git_repos_changed(worktree, changed_repos, cx);
 905        }
 906    }
 907
 908    fn on_buffer_store_event(
 909        &mut self,
 910        _: Entity<BufferStore>,
 911        event: &BufferStoreEvent,
 912        cx: &mut Context<Self>,
 913    ) {
 914        match event {
 915            BufferStoreEvent::BufferAdded(buffer) => {
 916                cx.subscribe(&buffer, |this, buffer, event, cx| {
 917                    if let BufferEvent::LanguageChanged = event {
 918                        let buffer_id = buffer.read(cx).remote_id();
 919                        if let Some(diff_state) = this.diffs.get(&buffer_id) {
 920                            diff_state.update(cx, |diff_state, cx| {
 921                                diff_state.buffer_language_changed(buffer, cx);
 922                            });
 923                        }
 924                    }
 925                })
 926                .detach();
 927            }
 928            BufferStoreEvent::SharedBufferClosed(peer_id, buffer_id) => {
 929                if let Some(diffs) = self.shared_diffs.get_mut(peer_id) {
 930                    diffs.remove(buffer_id);
 931                }
 932            }
 933            BufferStoreEvent::BufferDropped(buffer_id) => {
 934                self.diffs.remove(&buffer_id);
 935                for diffs in self.shared_diffs.values_mut() {
 936                    diffs.remove(buffer_id);
 937                }
 938            }
 939
 940            _ => {}
 941        }
 942    }
 943
 944    pub fn recalculate_buffer_diffs(
 945        &mut self,
 946        buffers: Vec<Entity<Buffer>>,
 947        cx: &mut Context<Self>,
 948    ) -> impl Future<Output = ()> {
 949        let mut futures = Vec::new();
 950        for buffer in buffers {
 951            if let Some(diff_state) = self.diffs.get_mut(&buffer.read(cx).remote_id()) {
 952                let buffer = buffer.read(cx).text_snapshot();
 953                futures.push(diff_state.update(cx, |diff_state, cx| {
 954                    diff_state.recalculate_diffs(buffer, cx)
 955                }));
 956            }
 957        }
 958        async move {
 959            futures::future::join_all(futures).await;
 960        }
 961    }
 962
 963    fn on_buffer_diff_event(
 964        &mut self,
 965        diff: Entity<buffer_diff::BufferDiff>,
 966        event: &BufferDiffEvent,
 967        cx: &mut Context<Self>,
 968    ) {
 969        if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
 970            let buffer_id = diff.read(cx).buffer_id;
 971            if let Some((repo, path)) = self.repository_and_path_for_buffer_id(buffer_id, cx) {
 972                let recv = repo.update(cx, |repo, cx| {
 973                    log::debug!("updating index text for buffer {}", path.display());
 974                    repo.spawn_set_index_text_job(
 975                        path,
 976                        new_index_text.as_ref().map(|rope| rope.to_string()),
 977                        cx,
 978                    )
 979                });
 980                let diff = diff.downgrade();
 981                cx.spawn(async move |this, cx| {
 982                    if let Ok(Err(error)) = cx.background_spawn(recv).await {
 983                        diff.update(cx, |diff, cx| {
 984                            diff.clear_pending_hunks(cx);
 985                        })
 986                        .ok();
 987                        this.update(cx, |_, cx| cx.emit(GitEvent::IndexWriteError(error)))
 988                            .ok();
 989                    }
 990                })
 991                .detach();
 992            }
 993        }
 994    }
 995
 996    fn local_worktree_git_repos_changed(
 997        &mut self,
 998        worktree: Entity<Worktree>,
 999        changed_repos: &UpdatedGitRepositoriesSet,
1000        cx: &mut Context<Self>,
1001    ) {
1002        debug_assert!(worktree.read(cx).is_local());
1003
1004        let Some(active_repo) = self.active_repository() else {
1005            log::error!("local worktree changed but we have no active repository");
1006            return;
1007        };
1008
1009        let mut diff_state_updates = HashMap::<ProjectEntryId, Vec<_>>::default();
1010        for (buffer_id, diff_state) in &self.diffs {
1011            let Some(buffer) = self.buffer_store.read(cx).get(*buffer_id) else {
1012                continue;
1013            };
1014            let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
1015                continue;
1016            };
1017            if file.worktree != worktree {
1018                continue;
1019            }
1020            let Some(repo_id) = changed_repos
1021                .iter()
1022                .map(|(entry, _)| entry.id)
1023                .find(|repo_id| self.repositories().contains_key(&repo_id))
1024            else {
1025                continue;
1026            };
1027
1028            let diff_state = diff_state.read(cx);
1029            let has_unstaged_diff = diff_state
1030                .unstaged_diff
1031                .as_ref()
1032                .is_some_and(|diff| diff.is_upgradable());
1033            let has_uncommitted_diff = diff_state
1034                .uncommitted_diff
1035                .as_ref()
1036                .is_some_and(|set| set.is_upgradable());
1037
1038            let update = (
1039                buffer,
1040                file.path.clone(),
1041                has_unstaged_diff.then(|| diff_state.index_text.clone()),
1042                has_uncommitted_diff.then(|| diff_state.head_text.clone()),
1043            );
1044            diff_state_updates.entry(repo_id).or_default().push(update);
1045        }
1046
1047        if diff_state_updates.is_empty() {
1048            return;
1049        }
1050
1051        for (repo_id, repo_diff_state_updates) in diff_state_updates.into_iter() {
1052            let worktree = worktree.downgrade();
1053            let git_store = cx.weak_entity();
1054
1055            let _ = active_repo.read(cx).send_keyed_job(
1056                Some(GitJobKey::BatchReadIndex(repo_id)),
1057                |_, mut cx| async move {
1058                    let snapshot = worktree.update(&mut cx, |tree, _| {
1059                        tree.as_local().map(|local_tree| local_tree.snapshot())
1060                    });
1061                    let Ok(Some(snapshot)) = snapshot else {
1062                        return;
1063                    };
1064
1065                    let mut diff_bases_changes_by_buffer = Vec::new();
1066                    for (buffer, path, current_index_text, current_head_text) in
1067                        &repo_diff_state_updates
1068                    {
1069                        let Some(local_repo) = snapshot.local_repo_for_path(&path) else {
1070                            continue;
1071                        };
1072                        let Some(relative_path) = local_repo.relativize(&path).ok() else {
1073                            continue;
1074                        };
1075
1076                        log::debug!("reloading git state for buffer {}", path.display());
1077                        let index_text = if current_index_text.is_some() {
1078                            local_repo
1079                                .repo()
1080                                .load_index_text(relative_path.clone(), cx.clone())
1081                                .await
1082                        } else {
1083                            None
1084                        };
1085                        let head_text = if current_head_text.is_some() {
1086                            local_repo
1087                                .repo()
1088                                .load_committed_text(relative_path, cx.clone())
1089                                .await
1090                        } else {
1091                            None
1092                        };
1093
1094                        // Avoid triggering a diff update if the base text has not changed.
1095                        if let Some((current_index, current_head)) =
1096                            current_index_text.as_ref().zip(current_head_text.as_ref())
1097                        {
1098                            if current_index.as_deref() == index_text.as_ref()
1099                                && current_head.as_deref() == head_text.as_ref()
1100                            {
1101                                continue;
1102                            }
1103                        }
1104
1105                        let diff_bases_change =
1106                            match (current_index_text.is_some(), current_head_text.is_some()) {
1107                                (true, true) => Some(if index_text == head_text {
1108                                    DiffBasesChange::SetBoth(head_text)
1109                                } else {
1110                                    DiffBasesChange::SetEach {
1111                                        index: index_text,
1112                                        head: head_text,
1113                                    }
1114                                }),
1115                                (true, false) => Some(DiffBasesChange::SetIndex(index_text)),
1116                                (false, true) => Some(DiffBasesChange::SetHead(head_text)),
1117                                (false, false) => None,
1118                            };
1119
1120                        diff_bases_changes_by_buffer.push((buffer, diff_bases_change))
1121                    }
1122
1123                    git_store
1124                        .update(&mut cx, |git_store, cx| {
1125                            for (buffer, diff_bases_change) in diff_bases_changes_by_buffer {
1126                                let Some(diff_state) =
1127                                    git_store.diffs.get(&buffer.read(cx).remote_id())
1128                                else {
1129                                    continue;
1130                                };
1131                                let Some(diff_bases_change) = diff_bases_change else {
1132                                    continue;
1133                                };
1134
1135                                let downstream_client = git_store.downstream_client();
1136                                diff_state.update(cx, |diff_state, cx| {
1137                                    use proto::update_diff_bases::Mode;
1138
1139                                    let buffer = buffer.read(cx);
1140                                    if let Some((client, project_id)) = downstream_client {
1141                                        let (staged_text, committed_text, mode) =
1142                                            match diff_bases_change.clone() {
1143                                                DiffBasesChange::SetIndex(index) => {
1144                                                    (index, None, Mode::IndexOnly)
1145                                                }
1146                                                DiffBasesChange::SetHead(head) => {
1147                                                    (None, head, Mode::HeadOnly)
1148                                                }
1149                                                DiffBasesChange::SetEach { index, head } => {
1150                                                    (index, head, Mode::IndexAndHead)
1151                                                }
1152                                                DiffBasesChange::SetBoth(text) => {
1153                                                    (None, text, Mode::IndexMatchesHead)
1154                                                }
1155                                            };
1156                                        let message = proto::UpdateDiffBases {
1157                                            project_id: project_id.to_proto(),
1158                                            buffer_id: buffer.remote_id().to_proto(),
1159                                            staged_text,
1160                                            committed_text,
1161                                            mode: mode as i32,
1162                                        };
1163
1164                                        client.send(message).log_err();
1165                                    }
1166
1167                                    let _ = diff_state.diff_bases_changed(
1168                                        buffer.text_snapshot(),
1169                                        diff_bases_change,
1170                                        cx,
1171                                    );
1172                                });
1173                            }
1174                        })
1175                        .ok();
1176                },
1177            );
1178        }
1179    }
1180
1181    pub fn repositories(&self) -> &HashMap<ProjectEntryId, Entity<Repository>> {
1182        &self.repositories
1183    }
1184
1185    pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
1186        let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
1187        let status = repo.read(cx).repository_entry.status_for_path(&path)?;
1188        Some(status.status)
1189    }
1190
1191    pub fn repository_and_path_for_buffer_id(
1192        &self,
1193        buffer_id: BufferId,
1194        cx: &App,
1195    ) -> Option<(Entity<Repository>, RepoPath)> {
1196        let buffer = self.buffer_store.read(cx).get(buffer_id)?;
1197        let project_path = buffer.read(cx).project_path(cx)?;
1198        self.repository_and_path_for_project_path(&project_path, cx)
1199    }
1200
1201    pub fn repository_and_path_for_project_path(
1202        &self,
1203        path: &ProjectPath,
1204        cx: &App,
1205    ) -> Option<(Entity<Repository>, RepoPath)> {
1206        let mut result: Option<(Entity<Repository>, RepoPath)> = None;
1207        for repo_handle in self.repositories.values() {
1208            let repo = repo_handle.read(cx);
1209            if repo.worktree_id == path.worktree_id {
1210                if let Ok(relative_path) = repo.repository_entry.relativize(&path.path) {
1211                    if result
1212                        .as_ref()
1213                        .is_none_or(|(result, _)| !repo.contains_sub_repo(result, cx))
1214                    {
1215                        result = Some((repo_handle.clone(), relative_path))
1216                    }
1217                }
1218            }
1219        }
1220        result
1221    }
1222
1223    fn spawn_git_worker(cx: &mut Context<GitStore>) -> mpsc::UnboundedSender<GitJob> {
1224        let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
1225
1226        cx.spawn(async move |_, cx| {
1227            let mut jobs = VecDeque::new();
1228            loop {
1229                while let Ok(Some(next_job)) = job_rx.try_next() {
1230                    jobs.push_back(next_job);
1231                }
1232
1233                if let Some(job) = jobs.pop_front() {
1234                    if let Some(current_key) = &job.key {
1235                        if jobs
1236                            .iter()
1237                            .any(|other_job| other_job.key.as_ref() == Some(current_key))
1238                        {
1239                            continue;
1240                        }
1241                    }
1242                    (job.job)(cx).await;
1243                } else if let Some(job) = job_rx.next().await {
1244                    jobs.push_back(job);
1245                } else {
1246                    break;
1247                }
1248            }
1249        })
1250        .detach();
1251        job_tx
1252    }
1253
1254    pub fn git_init(
1255        &self,
1256        path: Arc<Path>,
1257        fallback_branch_name: String,
1258        cx: &App,
1259    ) -> Task<Result<()>> {
1260        match &self.state {
1261            GitStoreState::Local { fs, .. } => {
1262                let fs = fs.clone();
1263                cx.background_executor()
1264                    .spawn(async move { fs.git_init(&path, fallback_branch_name) })
1265            }
1266            GitStoreState::Ssh {
1267                upstream_client,
1268                upstream_project_id: project_id,
1269                ..
1270            }
1271            | GitStoreState::Remote {
1272                upstream_client,
1273                project_id,
1274                ..
1275            } => {
1276                let client = upstream_client.clone();
1277                let project_id = *project_id;
1278                cx.background_executor().spawn(async move {
1279                    client
1280                        .request(proto::GitInit {
1281                            project_id: project_id.0,
1282                            abs_path: path.to_string_lossy().to_string(),
1283                            fallback_branch_name,
1284                        })
1285                        .await?;
1286                    Ok(())
1287                })
1288            }
1289        }
1290    }
1291
1292    async fn handle_git_init(
1293        this: Entity<Self>,
1294        envelope: TypedEnvelope<proto::GitInit>,
1295        cx: AsyncApp,
1296    ) -> Result<proto::Ack> {
1297        let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
1298        let name = envelope.payload.fallback_branch_name;
1299        cx.update(|cx| this.read(cx).git_init(path, name, cx))?
1300            .await?;
1301
1302        Ok(proto::Ack {})
1303    }
1304
1305    async fn handle_fetch(
1306        this: Entity<Self>,
1307        envelope: TypedEnvelope<proto::Fetch>,
1308        mut cx: AsyncApp,
1309    ) -> Result<proto::RemoteMessageResponse> {
1310        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1311        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1312        let repository_handle =
1313            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1314        let askpass_id = envelope.payload.askpass_id;
1315
1316        let askpass = make_remote_delegate(
1317            this,
1318            envelope.payload.project_id,
1319            worktree_id,
1320            work_directory_id,
1321            askpass_id,
1322            &mut cx,
1323        );
1324
1325        let remote_output = repository_handle
1326            .update(&mut cx, |repository_handle, cx| {
1327                repository_handle.fetch(askpass, cx)
1328            })?
1329            .await??;
1330
1331        Ok(proto::RemoteMessageResponse {
1332            stdout: remote_output.stdout,
1333            stderr: remote_output.stderr,
1334        })
1335    }
1336
1337    async fn handle_push(
1338        this: Entity<Self>,
1339        envelope: TypedEnvelope<proto::Push>,
1340        mut cx: AsyncApp,
1341    ) -> Result<proto::RemoteMessageResponse> {
1342        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1343        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1344        let repository_handle =
1345            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1346
1347        let askpass_id = envelope.payload.askpass_id;
1348        let askpass = make_remote_delegate(
1349            this,
1350            envelope.payload.project_id,
1351            worktree_id,
1352            work_directory_id,
1353            askpass_id,
1354            &mut cx,
1355        );
1356
1357        let options = envelope
1358            .payload
1359            .options
1360            .as_ref()
1361            .map(|_| match envelope.payload.options() {
1362                proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
1363                proto::push::PushOptions::Force => git::repository::PushOptions::Force,
1364            });
1365
1366        let branch_name = envelope.payload.branch_name.into();
1367        let remote_name = envelope.payload.remote_name.into();
1368
1369        let remote_output = repository_handle
1370            .update(&mut cx, |repository_handle, cx| {
1371                repository_handle.push(branch_name, remote_name, options, askpass, cx)
1372            })?
1373            .await??;
1374        Ok(proto::RemoteMessageResponse {
1375            stdout: remote_output.stdout,
1376            stderr: remote_output.stderr,
1377        })
1378    }
1379
1380    async fn handle_pull(
1381        this: Entity<Self>,
1382        envelope: TypedEnvelope<proto::Pull>,
1383        mut cx: AsyncApp,
1384    ) -> Result<proto::RemoteMessageResponse> {
1385        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1386        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1387        let repository_handle =
1388            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1389        let askpass_id = envelope.payload.askpass_id;
1390        let askpass = make_remote_delegate(
1391            this,
1392            envelope.payload.project_id,
1393            worktree_id,
1394            work_directory_id,
1395            askpass_id,
1396            &mut cx,
1397        );
1398
1399        let branch_name = envelope.payload.branch_name.into();
1400        let remote_name = envelope.payload.remote_name.into();
1401
1402        let remote_message = repository_handle
1403            .update(&mut cx, |repository_handle, cx| {
1404                repository_handle.pull(branch_name, remote_name, askpass, cx)
1405            })?
1406            .await??;
1407
1408        Ok(proto::RemoteMessageResponse {
1409            stdout: remote_message.stdout,
1410            stderr: remote_message.stderr,
1411        })
1412    }
1413
1414    async fn handle_stage(
1415        this: Entity<Self>,
1416        envelope: TypedEnvelope<proto::Stage>,
1417        mut cx: AsyncApp,
1418    ) -> Result<proto::Ack> {
1419        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1420        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1421        let repository_handle =
1422            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1423
1424        let entries = envelope
1425            .payload
1426            .paths
1427            .into_iter()
1428            .map(PathBuf::from)
1429            .map(RepoPath::new)
1430            .collect();
1431
1432        repository_handle
1433            .update(&mut cx, |repository_handle, cx| {
1434                repository_handle.stage_entries(entries, cx)
1435            })?
1436            .await?;
1437        Ok(proto::Ack {})
1438    }
1439
1440    async fn handle_unstage(
1441        this: Entity<Self>,
1442        envelope: TypedEnvelope<proto::Unstage>,
1443        mut cx: AsyncApp,
1444    ) -> Result<proto::Ack> {
1445        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1446        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1447        let repository_handle =
1448            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1449
1450        let entries = envelope
1451            .payload
1452            .paths
1453            .into_iter()
1454            .map(PathBuf::from)
1455            .map(RepoPath::new)
1456            .collect();
1457
1458        repository_handle
1459            .update(&mut cx, |repository_handle, cx| {
1460                repository_handle.unstage_entries(entries, cx)
1461            })?
1462            .await?;
1463
1464        Ok(proto::Ack {})
1465    }
1466
1467    async fn handle_set_index_text(
1468        this: Entity<Self>,
1469        envelope: TypedEnvelope<proto::SetIndexText>,
1470        mut cx: AsyncApp,
1471    ) -> Result<proto::Ack> {
1472        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1473        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1474        let repository_handle =
1475            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1476
1477        repository_handle
1478            .update(&mut cx, |repository_handle, cx| {
1479                repository_handle.spawn_set_index_text_job(
1480                    RepoPath::from_str(&envelope.payload.path),
1481                    envelope.payload.text,
1482                    cx,
1483                )
1484            })?
1485            .await??;
1486        Ok(proto::Ack {})
1487    }
1488
1489    async fn handle_commit(
1490        this: Entity<Self>,
1491        envelope: TypedEnvelope<proto::Commit>,
1492        mut cx: AsyncApp,
1493    ) -> Result<proto::Ack> {
1494        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1495        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1496        let repository_handle =
1497            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1498
1499        let message = SharedString::from(envelope.payload.message);
1500        let name = envelope.payload.name.map(SharedString::from);
1501        let email = envelope.payload.email.map(SharedString::from);
1502
1503        repository_handle
1504            .update(&mut cx, |repository_handle, cx| {
1505                repository_handle.commit(message, name.zip(email), cx)
1506            })?
1507            .await??;
1508        Ok(proto::Ack {})
1509    }
1510
1511    async fn handle_get_remotes(
1512        this: Entity<Self>,
1513        envelope: TypedEnvelope<proto::GetRemotes>,
1514        mut cx: AsyncApp,
1515    ) -> Result<proto::GetRemotesResponse> {
1516        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1517        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1518        let repository_handle =
1519            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1520
1521        let branch_name = envelope.payload.branch_name;
1522
1523        let remotes = repository_handle
1524            .update(&mut cx, |repository_handle, _| {
1525                repository_handle.get_remotes(branch_name)
1526            })?
1527            .await??;
1528
1529        Ok(proto::GetRemotesResponse {
1530            remotes: remotes
1531                .into_iter()
1532                .map(|remotes| proto::get_remotes_response::Remote {
1533                    name: remotes.name.to_string(),
1534                })
1535                .collect::<Vec<_>>(),
1536        })
1537    }
1538
1539    async fn handle_get_branches(
1540        this: Entity<Self>,
1541        envelope: TypedEnvelope<proto::GitGetBranches>,
1542        mut cx: AsyncApp,
1543    ) -> Result<proto::GitBranchesResponse> {
1544        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1545        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1546        let repository_handle =
1547            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1548
1549        let branches = repository_handle
1550            .update(&mut cx, |repository_handle, _| repository_handle.branches())?
1551            .await??;
1552
1553        Ok(proto::GitBranchesResponse {
1554            branches: branches
1555                .into_iter()
1556                .map(|branch| worktree::branch_to_proto(&branch))
1557                .collect::<Vec<_>>(),
1558        })
1559    }
1560    async fn handle_create_branch(
1561        this: Entity<Self>,
1562        envelope: TypedEnvelope<proto::GitCreateBranch>,
1563        mut cx: AsyncApp,
1564    ) -> Result<proto::Ack> {
1565        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1566        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1567        let repository_handle =
1568            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1569        let branch_name = envelope.payload.branch_name;
1570
1571        repository_handle
1572            .update(&mut cx, |repository_handle, _| {
1573                repository_handle.create_branch(branch_name)
1574            })?
1575            .await??;
1576
1577        Ok(proto::Ack {})
1578    }
1579
1580    async fn handle_change_branch(
1581        this: Entity<Self>,
1582        envelope: TypedEnvelope<proto::GitChangeBranch>,
1583        mut cx: AsyncApp,
1584    ) -> Result<proto::Ack> {
1585        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1586        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1587        let repository_handle =
1588            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1589        let branch_name = envelope.payload.branch_name;
1590
1591        repository_handle
1592            .update(&mut cx, |repository_handle, _| {
1593                repository_handle.change_branch(branch_name)
1594            })?
1595            .await??;
1596
1597        Ok(proto::Ack {})
1598    }
1599
1600    async fn handle_show(
1601        this: Entity<Self>,
1602        envelope: TypedEnvelope<proto::GitShow>,
1603        mut cx: AsyncApp,
1604    ) -> Result<proto::GitCommitDetails> {
1605        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1606        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1607        let repository_handle =
1608            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1609
1610        let commit = repository_handle
1611            .update(&mut cx, |repository_handle, _| {
1612                repository_handle.show(envelope.payload.commit)
1613            })?
1614            .await??;
1615        Ok(proto::GitCommitDetails {
1616            sha: commit.sha.into(),
1617            message: commit.message.into(),
1618            commit_timestamp: commit.commit_timestamp,
1619            committer_email: commit.committer_email.into(),
1620            committer_name: commit.committer_name.into(),
1621        })
1622    }
1623
1624    async fn handle_reset(
1625        this: Entity<Self>,
1626        envelope: TypedEnvelope<proto::GitReset>,
1627        mut cx: AsyncApp,
1628    ) -> Result<proto::Ack> {
1629        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1630        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1631        let repository_handle =
1632            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1633
1634        let mode = match envelope.payload.mode() {
1635            git_reset::ResetMode::Soft => ResetMode::Soft,
1636            git_reset::ResetMode::Mixed => ResetMode::Mixed,
1637        };
1638
1639        repository_handle
1640            .update(&mut cx, |repository_handle, cx| {
1641                repository_handle.reset(envelope.payload.commit, mode, cx)
1642            })?
1643            .await??;
1644        Ok(proto::Ack {})
1645    }
1646
1647    async fn handle_checkout_files(
1648        this: Entity<Self>,
1649        envelope: TypedEnvelope<proto::GitCheckoutFiles>,
1650        mut cx: AsyncApp,
1651    ) -> Result<proto::Ack> {
1652        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1653        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1654        let repository_handle =
1655            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1656        let paths = envelope
1657            .payload
1658            .paths
1659            .iter()
1660            .map(|s| RepoPath::from_str(s))
1661            .collect();
1662
1663        repository_handle
1664            .update(&mut cx, |repository_handle, cx| {
1665                repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
1666            })?
1667            .await??;
1668        Ok(proto::Ack {})
1669    }
1670
1671    async fn handle_open_commit_message_buffer(
1672        this: Entity<Self>,
1673        envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
1674        mut cx: AsyncApp,
1675    ) -> Result<proto::OpenBufferResponse> {
1676        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1677        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1678        let repository =
1679            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1680        let buffer = repository
1681            .update(&mut cx, |repository, cx| {
1682                repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
1683            })?
1684            .await?;
1685
1686        let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
1687        this.update(&mut cx, |this, cx| {
1688            this.buffer_store.update(cx, |buffer_store, cx| {
1689                buffer_store
1690                    .create_buffer_for_peer(
1691                        &buffer,
1692                        envelope.original_sender_id.unwrap_or(envelope.sender_id),
1693                        cx,
1694                    )
1695                    .detach_and_log_err(cx);
1696            })
1697        })?;
1698
1699        Ok(proto::OpenBufferResponse {
1700            buffer_id: buffer_id.to_proto(),
1701        })
1702    }
1703
1704    async fn handle_askpass(
1705        this: Entity<Self>,
1706        envelope: TypedEnvelope<proto::AskPassRequest>,
1707        mut cx: AsyncApp,
1708    ) -> Result<proto::AskPassResponse> {
1709        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1710        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1711        let repository =
1712            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1713
1714        let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone())?;
1715        let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
1716            debug_panic!("no askpass found");
1717            return Err(anyhow::anyhow!("no askpass found"));
1718        };
1719
1720        let response = askpass.ask_password(envelope.payload.prompt).await?;
1721
1722        delegates
1723            .lock()
1724            .insert(envelope.payload.askpass_id, askpass);
1725
1726        Ok(proto::AskPassResponse { response })
1727    }
1728
1729    async fn handle_check_for_pushed_commits(
1730        this: Entity<Self>,
1731        envelope: TypedEnvelope<proto::CheckForPushedCommits>,
1732        mut cx: AsyncApp,
1733    ) -> Result<proto::CheckForPushedCommitsResponse> {
1734        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1735        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1736        let repository_handle =
1737            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1738
1739        let branches = repository_handle
1740            .update(&mut cx, |repository_handle, _| {
1741                repository_handle.check_for_pushed_commits()
1742            })?
1743            .await??;
1744        Ok(proto::CheckForPushedCommitsResponse {
1745            pushed_to: branches
1746                .into_iter()
1747                .map(|commit| commit.to_string())
1748                .collect(),
1749        })
1750    }
1751
1752    async fn handle_git_diff(
1753        this: Entity<Self>,
1754        envelope: TypedEnvelope<proto::GitDiff>,
1755        mut cx: AsyncApp,
1756    ) -> Result<proto::GitDiffResponse> {
1757        let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
1758        let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1759        let repository_handle =
1760            Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
1761        let diff_type = match envelope.payload.diff_type() {
1762            proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
1763            proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
1764        };
1765
1766        let mut diff = repository_handle
1767            .update(&mut cx, |repository_handle, cx| {
1768                repository_handle.diff(diff_type, cx)
1769            })?
1770            .await??;
1771        const ONE_MB: usize = 1_000_000;
1772        if diff.len() > ONE_MB {
1773            diff = diff.chars().take(ONE_MB).collect()
1774        }
1775
1776        Ok(proto::GitDiffResponse { diff })
1777    }
1778
1779    async fn handle_open_unstaged_diff(
1780        this: Entity<Self>,
1781        request: TypedEnvelope<proto::OpenUnstagedDiff>,
1782        mut cx: AsyncApp,
1783    ) -> Result<proto::OpenUnstagedDiffResponse> {
1784        let buffer_id = BufferId::new(request.payload.buffer_id)?;
1785        let diff = this
1786            .update(&mut cx, |this, cx| {
1787                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
1788                Some(this.open_unstaged_diff(buffer, cx))
1789            })?
1790            .ok_or_else(|| anyhow!("no such buffer"))?
1791            .await?;
1792        this.update(&mut cx, |this, _| {
1793            let shared_diffs = this
1794                .shared_diffs
1795                .entry(request.original_sender_id.unwrap_or(request.sender_id))
1796                .or_default();
1797            shared_diffs.entry(buffer_id).or_default().unstaged = Some(diff.clone());
1798        })?;
1799        let staged_text = diff.read_with(&cx, |diff, _| diff.base_text_string())?;
1800        Ok(proto::OpenUnstagedDiffResponse { staged_text })
1801    }
1802
1803    async fn handle_open_uncommitted_diff(
1804        this: Entity<Self>,
1805        request: TypedEnvelope<proto::OpenUncommittedDiff>,
1806        mut cx: AsyncApp,
1807    ) -> Result<proto::OpenUncommittedDiffResponse> {
1808        let buffer_id = BufferId::new(request.payload.buffer_id)?;
1809        let diff = this
1810            .update(&mut cx, |this, cx| {
1811                let buffer = this.buffer_store.read(cx).get(buffer_id)?;
1812                Some(this.open_uncommitted_diff(buffer, cx))
1813            })?
1814            .ok_or_else(|| anyhow!("no such buffer"))?
1815            .await?;
1816        this.update(&mut cx, |this, _| {
1817            let shared_diffs = this
1818                .shared_diffs
1819                .entry(request.original_sender_id.unwrap_or(request.sender_id))
1820                .or_default();
1821            shared_diffs.entry(buffer_id).or_default().uncommitted = Some(diff.clone());
1822        })?;
1823        diff.read_with(&cx, |diff, cx| {
1824            use proto::open_uncommitted_diff_response::Mode;
1825
1826            let unstaged_diff = diff.secondary_diff();
1827            let index_snapshot = unstaged_diff.and_then(|diff| {
1828                let diff = diff.read(cx);
1829                diff.base_text_exists().then(|| diff.base_text())
1830            });
1831
1832            let mode;
1833            let staged_text;
1834            let committed_text;
1835            if diff.base_text_exists() {
1836                let committed_snapshot = diff.base_text();
1837                committed_text = Some(committed_snapshot.text());
1838                if let Some(index_text) = index_snapshot {
1839                    if index_text.remote_id() == committed_snapshot.remote_id() {
1840                        mode = Mode::IndexMatchesHead;
1841                        staged_text = None;
1842                    } else {
1843                        mode = Mode::IndexAndHead;
1844                        staged_text = Some(index_text.text());
1845                    }
1846                } else {
1847                    mode = Mode::IndexAndHead;
1848                    staged_text = None;
1849                }
1850            } else {
1851                mode = Mode::IndexAndHead;
1852                committed_text = None;
1853                staged_text = index_snapshot.as_ref().map(|buffer| buffer.text());
1854            }
1855
1856            proto::OpenUncommittedDiffResponse {
1857                committed_text,
1858                staged_text,
1859                mode: mode.into(),
1860            }
1861        })
1862    }
1863
1864    async fn handle_update_diff_bases(
1865        this: Entity<Self>,
1866        request: TypedEnvelope<proto::UpdateDiffBases>,
1867        mut cx: AsyncApp,
1868    ) -> Result<()> {
1869        let buffer_id = BufferId::new(request.payload.buffer_id)?;
1870        this.update(&mut cx, |this, cx| {
1871            if let Some(diff_state) = this.diffs.get_mut(&buffer_id) {
1872                if let Some(buffer) = this.buffer_store.read(cx).get(buffer_id) {
1873                    let buffer = buffer.read(cx).text_snapshot();
1874                    diff_state.update(cx, |diff_state, cx| {
1875                        diff_state.handle_base_texts_updated(buffer, request.payload, cx);
1876                    })
1877                }
1878            }
1879        })
1880    }
1881
1882    async fn handle_blame_buffer(
1883        this: Entity<Self>,
1884        envelope: TypedEnvelope<proto::BlameBuffer>,
1885        mut cx: AsyncApp,
1886    ) -> Result<proto::BlameBufferResponse> {
1887        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
1888        let version = deserialize_version(&envelope.payload.version);
1889        let buffer = this.read_with(&cx, |this, cx| {
1890            this.buffer_store.read(cx).get_existing(buffer_id)
1891        })??;
1892        buffer
1893            .update(&mut cx, |buffer, _| {
1894                buffer.wait_for_version(version.clone())
1895            })?
1896            .await?;
1897        let blame = this
1898            .update(&mut cx, |this, cx| {
1899                this.blame_buffer(&buffer, Some(version), cx)
1900            })?
1901            .await?;
1902        Ok(serialize_blame_buffer_response(blame))
1903    }
1904
1905    async fn handle_get_permalink_to_line(
1906        this: Entity<Self>,
1907        envelope: TypedEnvelope<proto::GetPermalinkToLine>,
1908        mut cx: AsyncApp,
1909    ) -> Result<proto::GetPermalinkToLineResponse> {
1910        let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
1911        // let version = deserialize_version(&envelope.payload.version);
1912        let selection = {
1913            let proto_selection = envelope
1914                .payload
1915                .selection
1916                .context("no selection to get permalink for defined")?;
1917            proto_selection.start as u32..proto_selection.end as u32
1918        };
1919        let buffer = this.read_with(&cx, |this, cx| {
1920            this.buffer_store.read(cx).get_existing(buffer_id)
1921        })??;
1922        let permalink = this
1923            .update(&mut cx, |this, cx| {
1924                this.get_permalink_to_line(&buffer, selection, cx)
1925            })?
1926            .await?;
1927        Ok(proto::GetPermalinkToLineResponse {
1928            permalink: permalink.to_string(),
1929        })
1930    }
1931
1932    fn repository_for_request(
1933        this: &Entity<Self>,
1934        worktree_id: WorktreeId,
1935        work_directory_id: ProjectEntryId,
1936        cx: &mut AsyncApp,
1937    ) -> Result<Entity<Repository>> {
1938        this.update(cx, |this, cx| {
1939            this.repositories
1940                .values()
1941                .find(|repository_handle| {
1942                    repository_handle.read(cx).worktree_id == worktree_id
1943                        && repository_handle
1944                            .read(cx)
1945                            .repository_entry
1946                            .work_directory_id()
1947                            == work_directory_id
1948                })
1949                .context("missing repository handle")
1950                .cloned()
1951        })?
1952    }
1953}
1954
1955impl BufferDiffState {
1956    fn buffer_language_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
1957        self.language = buffer.read(cx).language().cloned();
1958        self.language_changed = true;
1959        let _ = self.recalculate_diffs(buffer.read(cx).text_snapshot(), cx);
1960    }
1961
1962    fn unstaged_diff(&self) -> Option<Entity<BufferDiff>> {
1963        self.unstaged_diff.as_ref().and_then(|set| set.upgrade())
1964    }
1965
1966    fn uncommitted_diff(&self) -> Option<Entity<BufferDiff>> {
1967        self.uncommitted_diff.as_ref().and_then(|set| set.upgrade())
1968    }
1969
1970    fn handle_base_texts_updated(
1971        &mut self,
1972        buffer: text::BufferSnapshot,
1973        message: proto::UpdateDiffBases,
1974        cx: &mut Context<Self>,
1975    ) {
1976        use proto::update_diff_bases::Mode;
1977
1978        let Some(mode) = Mode::from_i32(message.mode) else {
1979            return;
1980        };
1981
1982        let diff_bases_change = match mode {
1983            Mode::HeadOnly => DiffBasesChange::SetHead(message.committed_text),
1984            Mode::IndexOnly => DiffBasesChange::SetIndex(message.staged_text),
1985            Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.committed_text),
1986            Mode::IndexAndHead => DiffBasesChange::SetEach {
1987                index: message.staged_text,
1988                head: message.committed_text,
1989            },
1990        };
1991
1992        let _ = self.diff_bases_changed(buffer, diff_bases_change, cx);
1993    }
1994
1995    pub fn wait_for_recalculation(&mut self) -> Option<oneshot::Receiver<()>> {
1996        if self.diff_updated_futures.is_empty() {
1997            return None;
1998        }
1999        let (tx, rx) = oneshot::channel();
2000        self.diff_updated_futures.push(tx);
2001        Some(rx)
2002    }
2003
2004    fn diff_bases_changed(
2005        &mut self,
2006        buffer: text::BufferSnapshot,
2007        diff_bases_change: DiffBasesChange,
2008        cx: &mut Context<Self>,
2009    ) -> oneshot::Receiver<()> {
2010        match diff_bases_change {
2011            DiffBasesChange::SetIndex(index) => {
2012                self.index_text = index.map(|mut index| {
2013                    text::LineEnding::normalize(&mut index);
2014                    Arc::new(index)
2015                });
2016                self.index_changed = true;
2017            }
2018            DiffBasesChange::SetHead(head) => {
2019                self.head_text = head.map(|mut head| {
2020                    text::LineEnding::normalize(&mut head);
2021                    Arc::new(head)
2022                });
2023                self.head_changed = true;
2024            }
2025            DiffBasesChange::SetBoth(text) => {
2026                let text = text.map(|mut text| {
2027                    text::LineEnding::normalize(&mut text);
2028                    Arc::new(text)
2029                });
2030                self.head_text = text.clone();
2031                self.index_text = text;
2032                self.head_changed = true;
2033                self.index_changed = true;
2034            }
2035            DiffBasesChange::SetEach { index, head } => {
2036                self.index_text = index.map(|mut index| {
2037                    text::LineEnding::normalize(&mut index);
2038                    Arc::new(index)
2039                });
2040                self.index_changed = true;
2041                self.head_text = head.map(|mut head| {
2042                    text::LineEnding::normalize(&mut head);
2043                    Arc::new(head)
2044                });
2045                self.head_changed = true;
2046            }
2047        }
2048
2049        self.recalculate_diffs(buffer, cx)
2050    }
2051
2052    fn recalculate_diffs(
2053        &mut self,
2054        buffer: text::BufferSnapshot,
2055        cx: &mut Context<Self>,
2056    ) -> oneshot::Receiver<()> {
2057        log::debug!("recalculate diffs");
2058        let (tx, rx) = oneshot::channel();
2059        self.diff_updated_futures.push(tx);
2060
2061        let language = self.language.clone();
2062        let language_registry = self.language_registry.clone();
2063        let unstaged_diff = self.unstaged_diff();
2064        let uncommitted_diff = self.uncommitted_diff();
2065        let head = self.head_text.clone();
2066        let index = self.index_text.clone();
2067        let index_changed = self.index_changed;
2068        let head_changed = self.head_changed;
2069        let language_changed = self.language_changed;
2070        let index_matches_head = match (self.index_text.as_ref(), self.head_text.as_ref()) {
2071            (Some(index), Some(head)) => Arc::ptr_eq(index, head),
2072            (None, None) => true,
2073            _ => false,
2074        };
2075        self.recalculate_diff_task = Some(cx.spawn(async move |this, cx| {
2076            let mut new_unstaged_diff = None;
2077            if let Some(unstaged_diff) = &unstaged_diff {
2078                new_unstaged_diff = Some(
2079                    BufferDiff::update_diff(
2080                        unstaged_diff.clone(),
2081                        buffer.clone(),
2082                        index,
2083                        index_changed,
2084                        language_changed,
2085                        language.clone(),
2086                        language_registry.clone(),
2087                        cx,
2088                    )
2089                    .await?,
2090                );
2091            }
2092
2093            let mut new_uncommitted_diff = None;
2094            if let Some(uncommitted_diff) = &uncommitted_diff {
2095                new_uncommitted_diff = if index_matches_head {
2096                    new_unstaged_diff.clone()
2097                } else {
2098                    Some(
2099                        BufferDiff::update_diff(
2100                            uncommitted_diff.clone(),
2101                            buffer.clone(),
2102                            head,
2103                            head_changed,
2104                            language_changed,
2105                            language.clone(),
2106                            language_registry.clone(),
2107                            cx,
2108                        )
2109                        .await?,
2110                    )
2111                }
2112            }
2113
2114            let unstaged_changed_range = if let Some((unstaged_diff, new_unstaged_diff)) =
2115                unstaged_diff.as_ref().zip(new_unstaged_diff.clone())
2116            {
2117                unstaged_diff.update(cx, |diff, cx| {
2118                    diff.set_snapshot(&buffer, new_unstaged_diff, language_changed, None, cx)
2119                })?
2120            } else {
2121                None
2122            };
2123
2124            if let Some((uncommitted_diff, new_uncommitted_diff)) =
2125                uncommitted_diff.as_ref().zip(new_uncommitted_diff.clone())
2126            {
2127                uncommitted_diff.update(cx, |uncommitted_diff, cx| {
2128                    uncommitted_diff.set_snapshot(
2129                        &buffer,
2130                        new_uncommitted_diff,
2131                        language_changed,
2132                        unstaged_changed_range,
2133                        cx,
2134                    );
2135                })?;
2136            }
2137
2138            if let Some(this) = this.upgrade() {
2139                this.update(cx, |this, _| {
2140                    this.index_changed = false;
2141                    this.head_changed = false;
2142                    this.language_changed = false;
2143                    for tx in this.diff_updated_futures.drain(..) {
2144                        tx.send(()).ok();
2145                    }
2146                })?;
2147            }
2148
2149            Ok(())
2150        }));
2151
2152        rx
2153    }
2154}
2155
2156fn make_remote_delegate(
2157    this: Entity<GitStore>,
2158    project_id: u64,
2159    worktree_id: WorktreeId,
2160    work_directory_id: ProjectEntryId,
2161    askpass_id: u64,
2162    cx: &mut AsyncApp,
2163) -> AskPassDelegate {
2164    AskPassDelegate::new(cx, move |prompt, tx, cx| {
2165        this.update(cx, |this, cx| {
2166            let Some((client, _)) = this.downstream_client() else {
2167                return;
2168            };
2169            let response = client.request(proto::AskPassRequest {
2170                project_id,
2171                worktree_id: worktree_id.to_proto(),
2172                work_directory_id: work_directory_id.to_proto(),
2173                askpass_id,
2174                prompt,
2175            });
2176            cx.spawn(async move |_, _| {
2177                tx.send(response.await?.response).ok();
2178                anyhow::Ok(())
2179            })
2180            .detach_and_log_err(cx);
2181        })
2182        .log_err();
2183    })
2184}
2185
2186impl GitStoreState {
2187    fn load_staged_text(
2188        &self,
2189        buffer: &Entity<Buffer>,
2190        buffer_store: &Entity<BufferStore>,
2191        cx: &App,
2192    ) -> Task<Result<Option<String>>> {
2193        match self {
2194            GitStoreState::Local { .. } => {
2195                if let Some((worktree, path)) =
2196                    buffer_store.read(cx).worktree_for_buffer(buffer, cx)
2197                {
2198                    worktree.read(cx).load_staged_file(path.as_ref(), cx)
2199                } else {
2200                    return Task::ready(Err(anyhow!("no such worktree")));
2201                }
2202            }
2203            GitStoreState::Ssh {
2204                upstream_client,
2205                upstream_project_id: project_id,
2206                ..
2207            }
2208            | GitStoreState::Remote {
2209                upstream_client,
2210                project_id,
2211            } => {
2212                let buffer_id = buffer.read(cx).remote_id();
2213                let project_id = *project_id;
2214                let client = upstream_client.clone();
2215                cx.background_spawn(async move {
2216                    let response = client
2217                        .request(proto::OpenUnstagedDiff {
2218                            project_id: project_id.to_proto(),
2219                            buffer_id: buffer_id.to_proto(),
2220                        })
2221                        .await?;
2222                    Ok(response.staged_text)
2223                })
2224            }
2225        }
2226    }
2227
2228    fn load_committed_text(
2229        &self,
2230        buffer: &Entity<Buffer>,
2231        buffer_store: &Entity<BufferStore>,
2232        cx: &App,
2233    ) -> Task<Result<DiffBasesChange>> {
2234        match self {
2235            GitStoreState::Local { .. } => {
2236                if let Some((worktree, path)) =
2237                    buffer_store.read(cx).worktree_for_buffer(buffer, cx)
2238                {
2239                    let worktree = worktree.read(cx);
2240                    let committed_text = worktree.load_committed_file(&path, cx);
2241                    let staged_text = worktree.load_staged_file(&path, cx);
2242                    cx.background_spawn(async move {
2243                        let committed_text = committed_text.await?;
2244                        let staged_text = staged_text.await?;
2245                        let diff_bases_change = if committed_text == staged_text {
2246                            DiffBasesChange::SetBoth(committed_text)
2247                        } else {
2248                            DiffBasesChange::SetEach {
2249                                index: staged_text,
2250                                head: committed_text,
2251                            }
2252                        };
2253                        Ok(diff_bases_change)
2254                    })
2255                } else {
2256                    Task::ready(Err(anyhow!("no such worktree")))
2257                }
2258            }
2259            GitStoreState::Ssh {
2260                upstream_client,
2261                upstream_project_id: project_id,
2262                ..
2263            }
2264            | GitStoreState::Remote {
2265                upstream_client,
2266                project_id,
2267            } => {
2268                use proto::open_uncommitted_diff_response::Mode;
2269
2270                let buffer_id = buffer.read(cx).remote_id();
2271                let project_id = *project_id;
2272                let client = upstream_client.clone();
2273                cx.background_spawn(async move {
2274                    let response = client
2275                        .request(proto::OpenUncommittedDiff {
2276                            project_id: project_id.to_proto(),
2277                            buffer_id: buffer_id.to_proto(),
2278                        })
2279                        .await?;
2280                    let mode =
2281                        Mode::from_i32(response.mode).ok_or_else(|| anyhow!("Invalid mode"))?;
2282                    let bases = match mode {
2283                        Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
2284                        Mode::IndexAndHead => DiffBasesChange::SetEach {
2285                            head: response.committed_text,
2286                            index: response.staged_text,
2287                        },
2288                    };
2289                    Ok(bases)
2290                })
2291            }
2292        }
2293    }
2294}
2295
2296impl Repository {
2297    pub fn git_store(&self) -> Option<Entity<GitStore>> {
2298        self.git_store.upgrade()
2299    }
2300
2301    fn id(&self) -> (WorktreeId, ProjectEntryId) {
2302        (self.worktree_id, self.repository_entry.work_directory_id())
2303    }
2304
2305    pub fn current_branch(&self) -> Option<&Branch> {
2306        self.repository_entry.branch()
2307    }
2308
2309    pub fn status_for_path(&self, path: &RepoPath) -> Option<StatusEntry> {
2310        self.repository_entry.status_for_path(path)
2311    }
2312
2313    fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
2314    where
2315        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
2316        Fut: Future<Output = R> + 'static,
2317        R: Send + 'static,
2318    {
2319        self.send_keyed_job(None, job)
2320    }
2321
2322    fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
2323    where
2324        F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
2325        Fut: Future<Output = R> + 'static,
2326        R: Send + 'static,
2327    {
2328        let (result_tx, result_rx) = futures::channel::oneshot::channel();
2329        let git_repo = self.git_repo.clone();
2330        self.job_sender
2331            .unbounded_send(GitJob {
2332                key,
2333                job: Box::new(|cx: &mut AsyncApp| {
2334                    let job = job(git_repo, cx.clone());
2335                    cx.spawn(async move |_| {
2336                        let result = job.await;
2337                        result_tx.send(result).ok();
2338                    })
2339                }),
2340            })
2341            .ok();
2342        result_rx
2343    }
2344
2345    pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
2346        maybe!({
2347            let project_path = self.repo_path_to_project_path(&"".into())?;
2348            let worktree_name = project
2349                .worktree_for_id(project_path.worktree_id, cx)?
2350                .read(cx)
2351                .root_name();
2352
2353            let mut path = PathBuf::new();
2354            path = path.join(worktree_name);
2355            if project_path.path.components().count() > 0 {
2356                path = path.join(project_path.path);
2357            }
2358            Some(path.to_string_lossy().to_string())
2359        })
2360        .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
2361        .into()
2362    }
2363
2364    pub fn set_as_active_repository(&self, cx: &mut Context<Self>) {
2365        let Some(git_store) = self.git_store.upgrade() else {
2366            return;
2367        };
2368        let entity = cx.entity();
2369        git_store.update(cx, |git_store, cx| {
2370            let Some((&id, _)) = git_store
2371                .repositories
2372                .iter()
2373                .find(|(_, handle)| *handle == &entity)
2374            else {
2375                return;
2376            };
2377            git_store.active_repo_id = Some(id);
2378            cx.emit(GitEvent::ActiveRepositoryChanged);
2379        });
2380    }
2381
2382    pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
2383        self.repository_entry.status()
2384    }
2385
2386    pub fn has_conflict(&self, path: &RepoPath) -> bool {
2387        self.repository_entry
2388            .current_merge_conflicts
2389            .contains(&path)
2390    }
2391
2392    pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
2393        let path = self.repository_entry.try_unrelativize(path)?;
2394        Some((self.worktree_id, path).into())
2395    }
2396
2397    pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
2398        self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
2399    }
2400
2401    // note: callers must verify these come from the same worktree
2402    pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
2403        let other_work_dir = &other.read(cx).repository_entry.work_directory;
2404        match (&self.repository_entry.work_directory, other_work_dir) {
2405            (WorkDirectory::InProject { .. }, WorkDirectory::AboveProject { .. }) => false,
2406            (WorkDirectory::AboveProject { .. }, WorkDirectory::InProject { .. }) => true,
2407            (
2408                WorkDirectory::InProject {
2409                    relative_path: this_path,
2410                },
2411                WorkDirectory::InProject {
2412                    relative_path: other_path,
2413                },
2414            ) => other_path.starts_with(this_path),
2415            (
2416                WorkDirectory::AboveProject {
2417                    absolute_path: this_path,
2418                    ..
2419                },
2420                WorkDirectory::AboveProject {
2421                    absolute_path: other_path,
2422                    ..
2423                },
2424            ) => other_path.starts_with(this_path),
2425        }
2426    }
2427
2428    pub fn worktree_id_path_to_repo_path(
2429        &self,
2430        worktree_id: WorktreeId,
2431        path: &Path,
2432    ) -> Option<RepoPath> {
2433        if worktree_id != self.worktree_id {
2434            return None;
2435        }
2436        self.repository_entry.relativize(path).log_err()
2437    }
2438
2439    pub fn local_repository(&self) -> Option<Arc<dyn GitRepository>> {
2440        match &self.git_repo {
2441            RepositoryState::Local(git_repository) => Some(git_repository.clone()),
2442            RepositoryState::Remote { .. } => None,
2443        }
2444    }
2445
2446    pub fn open_commit_buffer(
2447        &mut self,
2448        languages: Option<Arc<LanguageRegistry>>,
2449        buffer_store: Entity<BufferStore>,
2450        cx: &mut Context<Self>,
2451    ) -> Task<Result<Entity<Buffer>>> {
2452        if let Some(buffer) = self.commit_message_buffer.clone() {
2453            return Task::ready(Ok(buffer));
2454        }
2455
2456        if let RepositoryState::Remote {
2457            project_id,
2458            client,
2459            worktree_id,
2460            work_directory_id,
2461        } = self.git_repo.clone()
2462        {
2463            let client = client.clone();
2464            cx.spawn(async move |repository, cx| {
2465                let request = client.request(proto::OpenCommitMessageBuffer {
2466                    project_id: project_id.0,
2467                    worktree_id: worktree_id.to_proto(),
2468                    work_directory_id: work_directory_id.to_proto(),
2469                });
2470                let response = request.await.context("requesting to open commit buffer")?;
2471                let buffer_id = BufferId::new(response.buffer_id)?;
2472                let buffer = buffer_store
2473                    .update(cx, |buffer_store, cx| {
2474                        buffer_store.wait_for_remote_buffer(buffer_id, cx)
2475                    })?
2476                    .await?;
2477                if let Some(language_registry) = languages {
2478                    let git_commit_language =
2479                        language_registry.language_for_name("Git Commit").await?;
2480                    buffer.update(cx, |buffer, cx| {
2481                        buffer.set_language(Some(git_commit_language), cx);
2482                    })?;
2483                }
2484                repository.update(cx, |repository, _| {
2485                    repository.commit_message_buffer = Some(buffer.clone());
2486                })?;
2487                Ok(buffer)
2488            })
2489        } else {
2490            self.open_local_commit_buffer(languages, buffer_store, cx)
2491        }
2492    }
2493
2494    fn open_local_commit_buffer(
2495        &mut self,
2496        language_registry: Option<Arc<LanguageRegistry>>,
2497        buffer_store: Entity<BufferStore>,
2498        cx: &mut Context<Self>,
2499    ) -> Task<Result<Entity<Buffer>>> {
2500        cx.spawn(async move |repository, cx| {
2501            let buffer = buffer_store
2502                .update(cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
2503                .await?;
2504
2505            if let Some(language_registry) = language_registry {
2506                let git_commit_language = language_registry.language_for_name("Git Commit").await?;
2507                buffer.update(cx, |buffer, cx| {
2508                    buffer.set_language(Some(git_commit_language), cx);
2509                })?;
2510            }
2511
2512            repository.update(cx, |repository, _| {
2513                repository.commit_message_buffer = Some(buffer.clone());
2514            })?;
2515            Ok(buffer)
2516        })
2517    }
2518
2519    pub fn checkout_files(
2520        &self,
2521        commit: &str,
2522        paths: Vec<RepoPath>,
2523        cx: &mut App,
2524    ) -> oneshot::Receiver<Result<()>> {
2525        let commit = commit.to_string();
2526        let env = self.worktree_environment(cx);
2527
2528        self.send_job(|git_repo, _| async move {
2529            match git_repo {
2530                RepositoryState::Local(repo) => repo.checkout_files(commit, paths, env.await).await,
2531                RepositoryState::Remote {
2532                    project_id,
2533                    client,
2534                    worktree_id,
2535                    work_directory_id,
2536                } => {
2537                    client
2538                        .request(proto::GitCheckoutFiles {
2539                            project_id: project_id.0,
2540                            worktree_id: worktree_id.to_proto(),
2541                            work_directory_id: work_directory_id.to_proto(),
2542                            commit,
2543                            paths: paths
2544                                .into_iter()
2545                                .map(|p| p.to_string_lossy().to_string())
2546                                .collect(),
2547                        })
2548                        .await?;
2549
2550                    Ok(())
2551                }
2552            }
2553        })
2554    }
2555
2556    pub fn reset(
2557        &self,
2558        commit: String,
2559        reset_mode: ResetMode,
2560        cx: &mut App,
2561    ) -> oneshot::Receiver<Result<()>> {
2562        let commit = commit.to_string();
2563        let env = self.worktree_environment(cx);
2564        self.send_job(|git_repo, _| async move {
2565            match git_repo {
2566                RepositoryState::Local(git_repo) => {
2567                    let env = env.await;
2568                    git_repo.reset(commit, reset_mode, env).await
2569                }
2570                RepositoryState::Remote {
2571                    project_id,
2572                    client,
2573                    worktree_id,
2574                    work_directory_id,
2575                } => {
2576                    client
2577                        .request(proto::GitReset {
2578                            project_id: project_id.0,
2579                            worktree_id: worktree_id.to_proto(),
2580                            work_directory_id: work_directory_id.to_proto(),
2581                            commit,
2582                            mode: match reset_mode {
2583                                ResetMode::Soft => git_reset::ResetMode::Soft.into(),
2584                                ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
2585                            },
2586                        })
2587                        .await?;
2588
2589                    Ok(())
2590                }
2591            }
2592        })
2593    }
2594
2595    pub fn show(&self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
2596        self.send_job(|git_repo, cx| async move {
2597            match git_repo {
2598                RepositoryState::Local(git_repository) => git_repository.show(commit, cx).await,
2599                RepositoryState::Remote {
2600                    project_id,
2601                    client,
2602                    worktree_id,
2603                    work_directory_id,
2604                } => {
2605                    let resp = client
2606                        .request(proto::GitShow {
2607                            project_id: project_id.0,
2608                            worktree_id: worktree_id.to_proto(),
2609                            work_directory_id: work_directory_id.to_proto(),
2610                            commit,
2611                        })
2612                        .await?;
2613
2614                    Ok(CommitDetails {
2615                        sha: resp.sha.into(),
2616                        message: resp.message.into(),
2617                        commit_timestamp: resp.commit_timestamp,
2618                        committer_email: resp.committer_email.into(),
2619                        committer_name: resp.committer_name.into(),
2620                    })
2621                }
2622            }
2623        })
2624    }
2625
2626    fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
2627        Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
2628    }
2629
2630    pub fn stage_entries(
2631        &self,
2632        entries: Vec<RepoPath>,
2633        cx: &mut Context<Self>,
2634    ) -> Task<anyhow::Result<()>> {
2635        if entries.is_empty() {
2636            return Task::ready(Ok(()));
2637        }
2638        let env = self.worktree_environment(cx);
2639
2640        let mut save_futures = Vec::new();
2641        if let Some(buffer_store) = self.buffer_store(cx) {
2642            buffer_store.update(cx, |buffer_store, cx| {
2643                for path in &entries {
2644                    let Some(path) = self.repository_entry.try_unrelativize(path) else {
2645                        continue;
2646                    };
2647                    let project_path = (self.worktree_id, path).into();
2648                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
2649                        if buffer
2650                            .read(cx)
2651                            .file()
2652                            .map_or(false, |file| file.disk_state().exists())
2653                        {
2654                            save_futures.push(buffer_store.save_buffer(buffer, cx));
2655                        }
2656                    }
2657                }
2658            })
2659        }
2660
2661        cx.spawn(async move |this, cx| {
2662            for save_future in save_futures {
2663                save_future.await?;
2664            }
2665            let env = env.await;
2666
2667            this.update(cx, |this, _| {
2668                this.send_job(|git_repo, cx| async move {
2669                    match git_repo {
2670                        RepositoryState::Local(repo) => repo.stage_paths(entries, env, cx).await,
2671                        RepositoryState::Remote {
2672                            project_id,
2673                            client,
2674                            worktree_id,
2675                            work_directory_id,
2676                        } => {
2677                            client
2678                                .request(proto::Stage {
2679                                    project_id: project_id.0,
2680                                    worktree_id: worktree_id.to_proto(),
2681                                    work_directory_id: work_directory_id.to_proto(),
2682                                    paths: entries
2683                                        .into_iter()
2684                                        .map(|repo_path| repo_path.as_ref().to_proto())
2685                                        .collect(),
2686                                })
2687                                .await
2688                                .context("sending stage request")?;
2689
2690                            Ok(())
2691                        }
2692                    }
2693                })
2694            })?
2695            .await??;
2696
2697            Ok(())
2698        })
2699    }
2700
2701    pub fn unstage_entries(
2702        &self,
2703        entries: Vec<RepoPath>,
2704        cx: &mut Context<Self>,
2705    ) -> Task<anyhow::Result<()>> {
2706        if entries.is_empty() {
2707            return Task::ready(Ok(()));
2708        }
2709        let env = self.worktree_environment(cx);
2710
2711        let mut save_futures = Vec::new();
2712        if let Some(buffer_store) = self.buffer_store(cx) {
2713            buffer_store.update(cx, |buffer_store, cx| {
2714                for path in &entries {
2715                    let Some(path) = self.repository_entry.try_unrelativize(path) else {
2716                        continue;
2717                    };
2718                    let project_path = (self.worktree_id, path).into();
2719                    if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
2720                        if buffer
2721                            .read(cx)
2722                            .file()
2723                            .map_or(false, |file| file.disk_state().exists())
2724                        {
2725                            save_futures.push(buffer_store.save_buffer(buffer, cx));
2726                        }
2727                    }
2728                }
2729            })
2730        }
2731
2732        cx.spawn(async move |this, cx| {
2733            for save_future in save_futures {
2734                save_future.await?;
2735            }
2736            let env = env.await;
2737
2738            this.update(cx, |this, _| {
2739                this.send_job(|git_repo, cx| async move {
2740                    match git_repo {
2741                        RepositoryState::Local(repo) => repo.unstage_paths(entries, env, cx).await,
2742                        RepositoryState::Remote {
2743                            project_id,
2744                            client,
2745                            worktree_id,
2746                            work_directory_id,
2747                        } => {
2748                            client
2749                                .request(proto::Unstage {
2750                                    project_id: project_id.0,
2751                                    worktree_id: worktree_id.to_proto(),
2752                                    work_directory_id: work_directory_id.to_proto(),
2753                                    paths: entries
2754                                        .into_iter()
2755                                        .map(|repo_path| repo_path.as_ref().to_proto())
2756                                        .collect(),
2757                                })
2758                                .await
2759                                .context("sending unstage request")?;
2760
2761                            Ok(())
2762                        }
2763                    }
2764                })
2765            })?
2766            .await??;
2767
2768            Ok(())
2769        })
2770    }
2771
2772    pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
2773        let to_stage = self
2774            .repository_entry
2775            .status()
2776            .filter(|entry| !entry.status.staging().is_fully_staged())
2777            .map(|entry| entry.repo_path.clone())
2778            .collect();
2779        self.stage_entries(to_stage, cx)
2780    }
2781
2782    pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
2783        let to_unstage = self
2784            .repository_entry
2785            .status()
2786            .filter(|entry| entry.status.staging().has_staged())
2787            .map(|entry| entry.repo_path.clone())
2788            .collect();
2789        self.unstage_entries(to_unstage, cx)
2790    }
2791
2792    /// Get a count of all entries in the active repository, including
2793    /// untracked files.
2794    pub fn entry_count(&self) -> usize {
2795        self.repository_entry.status_len()
2796    }
2797
2798    fn worktree_environment(
2799        &self,
2800        cx: &mut App,
2801    ) -> impl Future<Output = HashMap<String, String>> + 'static {
2802        let task = self.project_environment.as_ref().and_then(|env| {
2803            env.update(cx, |env, cx| {
2804                env.get_environment(
2805                    Some(self.worktree_id),
2806                    Some(self.worktree_abs_path.clone()),
2807                    cx,
2808                )
2809            })
2810            .ok()
2811        });
2812        async move { OptionFuture::from(task).await.flatten().unwrap_or_default() }
2813    }
2814
2815    pub fn commit(
2816        &self,
2817        message: SharedString,
2818        name_and_email: Option<(SharedString, SharedString)>,
2819        cx: &mut App,
2820    ) -> oneshot::Receiver<Result<()>> {
2821        let env = self.worktree_environment(cx);
2822        self.send_job(|git_repo, cx| async move {
2823            match git_repo {
2824                RepositoryState::Local(repo) => {
2825                    let env = env.await;
2826                    repo.commit(message, name_and_email, env, cx).await
2827                }
2828                RepositoryState::Remote {
2829                    project_id,
2830                    client,
2831                    worktree_id,
2832                    work_directory_id,
2833                } => {
2834                    let (name, email) = name_and_email.unzip();
2835                    client
2836                        .request(proto::Commit {
2837                            project_id: project_id.0,
2838                            worktree_id: worktree_id.to_proto(),
2839                            work_directory_id: work_directory_id.to_proto(),
2840                            message: String::from(message),
2841                            name: name.map(String::from),
2842                            email: email.map(String::from),
2843                        })
2844                        .await
2845                        .context("sending commit request")?;
2846
2847                    Ok(())
2848                }
2849            }
2850        })
2851    }
2852
2853    pub fn fetch(
2854        &mut self,
2855        askpass: AskPassDelegate,
2856        cx: &mut App,
2857    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
2858        let executor = cx.background_executor().clone();
2859        let askpass_delegates = self.askpass_delegates.clone();
2860        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
2861        let env = self.worktree_environment(cx);
2862
2863        self.send_job(move |git_repo, cx| async move {
2864            match git_repo {
2865                RepositoryState::Local(git_repository) => {
2866                    let askpass = AskPassSession::new(&executor, askpass).await?;
2867                    let env = env.await;
2868                    git_repository.fetch(askpass, env, cx).await
2869                }
2870                RepositoryState::Remote {
2871                    project_id,
2872                    client,
2873                    worktree_id,
2874                    work_directory_id,
2875                } => {
2876                    askpass_delegates.lock().insert(askpass_id, askpass);
2877                    let _defer = util::defer(|| {
2878                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
2879                        debug_assert!(askpass_delegate.is_some());
2880                    });
2881
2882                    let response = client
2883                        .request(proto::Fetch {
2884                            project_id: project_id.0,
2885                            worktree_id: worktree_id.to_proto(),
2886                            work_directory_id: work_directory_id.to_proto(),
2887                            askpass_id,
2888                        })
2889                        .await
2890                        .context("sending fetch request")?;
2891
2892                    Ok(RemoteCommandOutput {
2893                        stdout: response.stdout,
2894                        stderr: response.stderr,
2895                    })
2896                }
2897            }
2898        })
2899    }
2900
2901    pub fn push(
2902        &mut self,
2903        branch: SharedString,
2904        remote: SharedString,
2905        options: Option<PushOptions>,
2906        askpass: AskPassDelegate,
2907        cx: &mut App,
2908    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
2909        let executor = cx.background_executor().clone();
2910        let askpass_delegates = self.askpass_delegates.clone();
2911        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
2912        let env = self.worktree_environment(cx);
2913
2914        self.send_job(move |git_repo, cx| async move {
2915            match git_repo {
2916                RepositoryState::Local(git_repository) => {
2917                    let env = env.await;
2918                    let askpass = AskPassSession::new(&executor, askpass).await?;
2919                    git_repository
2920                        .push(
2921                            branch.to_string(),
2922                            remote.to_string(),
2923                            options,
2924                            askpass,
2925                            env,
2926                            cx,
2927                        )
2928                        .await
2929                }
2930                RepositoryState::Remote {
2931                    project_id,
2932                    client,
2933                    worktree_id,
2934                    work_directory_id,
2935                } => {
2936                    askpass_delegates.lock().insert(askpass_id, askpass);
2937                    let _defer = util::defer(|| {
2938                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
2939                        debug_assert!(askpass_delegate.is_some());
2940                    });
2941                    let response = client
2942                        .request(proto::Push {
2943                            project_id: project_id.0,
2944                            worktree_id: worktree_id.to_proto(),
2945                            work_directory_id: work_directory_id.to_proto(),
2946                            askpass_id,
2947                            branch_name: branch.to_string(),
2948                            remote_name: remote.to_string(),
2949                            options: options.map(|options| match options {
2950                                PushOptions::Force => proto::push::PushOptions::Force,
2951                                PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
2952                            } as i32),
2953                        })
2954                        .await
2955                        .context("sending push request")?;
2956
2957                    Ok(RemoteCommandOutput {
2958                        stdout: response.stdout,
2959                        stderr: response.stderr,
2960                    })
2961                }
2962            }
2963        })
2964    }
2965
2966    pub fn pull(
2967        &mut self,
2968        branch: SharedString,
2969        remote: SharedString,
2970        askpass: AskPassDelegate,
2971        cx: &mut App,
2972    ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
2973        let executor = cx.background_executor().clone();
2974        let askpass_delegates = self.askpass_delegates.clone();
2975        let askpass_id = util::post_inc(&mut self.latest_askpass_id);
2976        let env = self.worktree_environment(cx);
2977
2978        self.send_job(move |git_repo, cx| async move {
2979            match git_repo {
2980                RepositoryState::Local(git_repository) => {
2981                    let askpass = AskPassSession::new(&executor, askpass).await?;
2982                    let env = env.await;
2983                    git_repository
2984                        .pull(branch.to_string(), remote.to_string(), askpass, env, cx)
2985                        .await
2986                }
2987                RepositoryState::Remote {
2988                    project_id,
2989                    client,
2990                    worktree_id,
2991                    work_directory_id,
2992                } => {
2993                    askpass_delegates.lock().insert(askpass_id, askpass);
2994                    let _defer = util::defer(|| {
2995                        let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
2996                        debug_assert!(askpass_delegate.is_some());
2997                    });
2998                    let response = client
2999                        .request(proto::Pull {
3000                            project_id: project_id.0,
3001                            worktree_id: worktree_id.to_proto(),
3002                            work_directory_id: work_directory_id.to_proto(),
3003                            askpass_id,
3004                            branch_name: branch.to_string(),
3005                            remote_name: remote.to_string(),
3006                        })
3007                        .await
3008                        .context("sending pull request")?;
3009
3010                    Ok(RemoteCommandOutput {
3011                        stdout: response.stdout,
3012                        stderr: response.stderr,
3013                    })
3014                }
3015            }
3016        })
3017    }
3018
3019    fn spawn_set_index_text_job(
3020        &self,
3021        path: RepoPath,
3022        content: Option<String>,
3023        cx: &mut App,
3024    ) -> oneshot::Receiver<anyhow::Result<()>> {
3025        let env = self.worktree_environment(cx);
3026
3027        self.send_keyed_job(
3028            Some(GitJobKey::WriteIndex(path.clone())),
3029            |git_repo, cx| async {
3030                match git_repo {
3031                    RepositoryState::Local(repo) => {
3032                        repo.set_index_text(path, content, env.await, cx).await
3033                    }
3034                    RepositoryState::Remote {
3035                        project_id,
3036                        client,
3037                        worktree_id,
3038                        work_directory_id,
3039                    } => {
3040                        client
3041                            .request(proto::SetIndexText {
3042                                project_id: project_id.0,
3043                                worktree_id: worktree_id.to_proto(),
3044                                work_directory_id: work_directory_id.to_proto(),
3045                                path: path.as_ref().to_proto(),
3046                                text: content,
3047                            })
3048                            .await?;
3049                        Ok(())
3050                    }
3051                }
3052            },
3053        )
3054    }
3055
3056    pub fn get_remotes(
3057        &self,
3058        branch_name: Option<String>,
3059    ) -> oneshot::Receiver<Result<Vec<Remote>>> {
3060        self.send_job(|repo, cx| async move {
3061            match repo {
3062                RepositoryState::Local(git_repository) => {
3063                    git_repository.get_remotes(branch_name, cx).await
3064                }
3065                RepositoryState::Remote {
3066                    project_id,
3067                    client,
3068                    worktree_id,
3069                    work_directory_id,
3070                } => {
3071                    let response = client
3072                        .request(proto::GetRemotes {
3073                            project_id: project_id.0,
3074                            worktree_id: worktree_id.to_proto(),
3075                            work_directory_id: work_directory_id.to_proto(),
3076                            branch_name,
3077                        })
3078                        .await?;
3079
3080                    let remotes = response
3081                        .remotes
3082                        .into_iter()
3083                        .map(|remotes| git::repository::Remote {
3084                            name: remotes.name.into(),
3085                        })
3086                        .collect();
3087
3088                    Ok(remotes)
3089                }
3090            }
3091        })
3092    }
3093
3094    pub fn branch(&self) -> Option<&Branch> {
3095        self.repository_entry.branch()
3096    }
3097
3098    pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
3099        self.send_job(|repo, cx| async move {
3100            match repo {
3101                RepositoryState::Local(git_repository) => {
3102                    let git_repository = git_repository.clone();
3103                    cx.background_spawn(async move { git_repository.branches().await })
3104                        .await
3105                }
3106                RepositoryState::Remote {
3107                    project_id,
3108                    client,
3109                    worktree_id,
3110                    work_directory_id,
3111                } => {
3112                    let response = client
3113                        .request(proto::GitGetBranches {
3114                            project_id: project_id.0,
3115                            worktree_id: worktree_id.to_proto(),
3116                            work_directory_id: work_directory_id.to_proto(),
3117                        })
3118                        .await?;
3119
3120                    let branches = response
3121                        .branches
3122                        .into_iter()
3123                        .map(|branch| worktree::proto_to_branch(&branch))
3124                        .collect();
3125
3126                    Ok(branches)
3127                }
3128            }
3129        })
3130    }
3131
3132    pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
3133        self.send_job(|repo, cx| async move {
3134            match repo {
3135                RepositoryState::Local(git_repository) => git_repository.diff(diff_type, cx).await,
3136                RepositoryState::Remote {
3137                    project_id,
3138                    client,
3139                    worktree_id,
3140                    work_directory_id,
3141                    ..
3142                } => {
3143                    let response = client
3144                        .request(proto::GitDiff {
3145                            project_id: project_id.0,
3146                            worktree_id: worktree_id.to_proto(),
3147                            work_directory_id: work_directory_id.to_proto(),
3148                            diff_type: match diff_type {
3149                                DiffType::HeadToIndex => {
3150                                    proto::git_diff::DiffType::HeadToIndex.into()
3151                                }
3152                                DiffType::HeadToWorktree => {
3153                                    proto::git_diff::DiffType::HeadToWorktree.into()
3154                                }
3155                            },
3156                        })
3157                        .await?;
3158
3159                    Ok(response.diff)
3160                }
3161            }
3162        })
3163    }
3164
3165    pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
3166        self.send_job(|repo, cx| async move {
3167            match repo {
3168                RepositoryState::Local(git_repository) => {
3169                    git_repository.create_branch(branch_name, cx).await
3170                }
3171                RepositoryState::Remote {
3172                    project_id,
3173                    client,
3174                    worktree_id,
3175                    work_directory_id,
3176                } => {
3177                    client
3178                        .request(proto::GitCreateBranch {
3179                            project_id: project_id.0,
3180                            worktree_id: worktree_id.to_proto(),
3181                            work_directory_id: work_directory_id.to_proto(),
3182                            branch_name,
3183                        })
3184                        .await?;
3185
3186                    Ok(())
3187                }
3188            }
3189        })
3190    }
3191
3192    pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
3193        self.send_job(|repo, cx| async move {
3194            match repo {
3195                RepositoryState::Local(git_repository) => {
3196                    git_repository.change_branch(branch_name, cx).await
3197                }
3198                RepositoryState::Remote {
3199                    project_id,
3200                    client,
3201                    worktree_id,
3202                    work_directory_id,
3203                } => {
3204                    client
3205                        .request(proto::GitChangeBranch {
3206                            project_id: project_id.0,
3207                            worktree_id: worktree_id.to_proto(),
3208                            work_directory_id: work_directory_id.to_proto(),
3209                            branch_name,
3210                        })
3211                        .await?;
3212
3213                    Ok(())
3214                }
3215            }
3216        })
3217    }
3218
3219    pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
3220        self.send_job(|repo, cx| async move {
3221            match repo {
3222                RepositoryState::Local(git_repository) => {
3223                    git_repository.check_for_pushed_commit(cx).await
3224                }
3225                RepositoryState::Remote {
3226                    project_id,
3227                    client,
3228                    worktree_id,
3229                    work_directory_id,
3230                } => {
3231                    let response = client
3232                        .request(proto::CheckForPushedCommits {
3233                            project_id: project_id.0,
3234                            worktree_id: worktree_id.to_proto(),
3235                            work_directory_id: work_directory_id.to_proto(),
3236                        })
3237                        .await?;
3238
3239                    let branches = response.pushed_to.into_iter().map(Into::into).collect();
3240
3241                    Ok(branches)
3242                }
3243            }
3244        })
3245    }
3246
3247    pub fn checkpoint(&self) -> oneshot::Receiver<Result<GitRepositoryCheckpoint>> {
3248        self.send_job(|repo, cx| async move {
3249            match repo {
3250                RepositoryState::Local(git_repository) => git_repository.checkpoint(cx).await,
3251                RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3252            }
3253        })
3254    }
3255
3256    pub fn restore_checkpoint(
3257        &self,
3258        checkpoint: GitRepositoryCheckpoint,
3259    ) -> oneshot::Receiver<Result<()>> {
3260        self.send_job(move |repo, cx| async move {
3261            match repo {
3262                RepositoryState::Local(git_repository) => {
3263                    git_repository.restore_checkpoint(checkpoint, cx).await
3264                }
3265                RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3266            }
3267        })
3268    }
3269}
3270
3271fn get_permalink_in_rust_registry_src(
3272    provider_registry: Arc<GitHostingProviderRegistry>,
3273    path: PathBuf,
3274    selection: Range<u32>,
3275) -> Result<url::Url> {
3276    #[derive(Deserialize)]
3277    struct CargoVcsGit {
3278        sha1: String,
3279    }
3280
3281    #[derive(Deserialize)]
3282    struct CargoVcsInfo {
3283        git: CargoVcsGit,
3284        path_in_vcs: String,
3285    }
3286
3287    #[derive(Deserialize)]
3288    struct CargoPackage {
3289        repository: String,
3290    }
3291
3292    #[derive(Deserialize)]
3293    struct CargoToml {
3294        package: CargoPackage,
3295    }
3296
3297    let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
3298        let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
3299        Some((dir, json))
3300    }) else {
3301        bail!("No .cargo_vcs_info.json found in parent directories")
3302    };
3303    let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
3304    let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
3305    let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
3306    let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
3307        .ok_or_else(|| anyhow!("Failed to parse package.repository field of manifest"))?;
3308    let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
3309    let permalink = provider.build_permalink(
3310        remote,
3311        BuildPermalinkParams {
3312            sha: &cargo_vcs_info.git.sha1,
3313            path: &path.to_string_lossy(),
3314            selection: Some(selection),
3315        },
3316    );
3317    Ok(permalink)
3318}
3319
3320fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::BlameBufferResponse {
3321    let Some(blame) = blame else {
3322        return proto::BlameBufferResponse {
3323            blame_response: None,
3324        };
3325    };
3326
3327    let entries = blame
3328        .entries
3329        .into_iter()
3330        .map(|entry| proto::BlameEntry {
3331            sha: entry.sha.as_bytes().into(),
3332            start_line: entry.range.start,
3333            end_line: entry.range.end,
3334            original_line_number: entry.original_line_number,
3335            author: entry.author.clone(),
3336            author_mail: entry.author_mail.clone(),
3337            author_time: entry.author_time,
3338            author_tz: entry.author_tz.clone(),
3339            committer: entry.committer_name.clone(),
3340            committer_mail: entry.committer_email.clone(),
3341            committer_time: entry.committer_time,
3342            committer_tz: entry.committer_tz.clone(),
3343            summary: entry.summary.clone(),
3344            previous: entry.previous.clone(),
3345            filename: entry.filename.clone(),
3346        })
3347        .collect::<Vec<_>>();
3348
3349    let messages = blame
3350        .messages
3351        .into_iter()
3352        .map(|(oid, message)| proto::CommitMessage {
3353            oid: oid.as_bytes().into(),
3354            message,
3355        })
3356        .collect::<Vec<_>>();
3357
3358    proto::BlameBufferResponse {
3359        blame_response: Some(proto::blame_buffer_response::BlameResponse {
3360            entries,
3361            messages,
3362            remote_url: blame.remote_url,
3363        }),
3364    }
3365}
3366
3367fn deserialize_blame_buffer_response(
3368    response: proto::BlameBufferResponse,
3369) -> Option<git::blame::Blame> {
3370    let response = response.blame_response?;
3371    let entries = response
3372        .entries
3373        .into_iter()
3374        .filter_map(|entry| {
3375            Some(git::blame::BlameEntry {
3376                sha: git::Oid::from_bytes(&entry.sha).ok()?,
3377                range: entry.start_line..entry.end_line,
3378                original_line_number: entry.original_line_number,
3379                committer_name: entry.committer,
3380                committer_time: entry.committer_time,
3381                committer_tz: entry.committer_tz,
3382                committer_email: entry.committer_mail,
3383                author: entry.author,
3384                author_mail: entry.author_mail,
3385                author_time: entry.author_time,
3386                author_tz: entry.author_tz,
3387                summary: entry.summary,
3388                previous: entry.previous,
3389                filename: entry.filename,
3390            })
3391        })
3392        .collect::<Vec<_>>();
3393
3394    let messages = response
3395        .messages
3396        .into_iter()
3397        .filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
3398        .collect::<HashMap<_, _>>();
3399
3400    Some(Blame {
3401        entries,
3402        messages,
3403        remote_url: response.remote_url,
3404    })
3405}