git_store.rs

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