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