git_store.rs

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