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