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