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)
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())
1286 .await
1287 } else {
1288 None
1289 };
1290 let head_text = if current_head_text.is_some() {
1291 local_repo.repo().load_committed_text(relative_path).await
1292 } else {
1293 None
1294 };
1295
1296 // Avoid triggering a diff update if the base text has not changed.
1297 if let Some((current_index, current_head)) =
1298 current_index_text.as_ref().zip(current_head_text.as_ref())
1299 {
1300 if current_index.as_deref() == index_text.as_ref()
1301 && current_head.as_deref() == head_text.as_ref()
1302 {
1303 continue;
1304 }
1305 }
1306
1307 let diff_bases_change =
1308 match (current_index_text.is_some(), current_head_text.is_some()) {
1309 (true, true) => Some(if index_text == head_text {
1310 DiffBasesChange::SetBoth(head_text)
1311 } else {
1312 DiffBasesChange::SetEach {
1313 index: index_text,
1314 head: head_text,
1315 }
1316 }),
1317 (true, false) => Some(DiffBasesChange::SetIndex(index_text)),
1318 (false, true) => Some(DiffBasesChange::SetHead(head_text)),
1319 (false, false) => None,
1320 };
1321
1322 diff_bases_changes_by_buffer.push((
1323 buffer,
1324 diff_bases_change,
1325 *hunk_staging_operation_count,
1326 ))
1327 }
1328
1329 git_store
1330 .update(&mut cx, |git_store, cx| {
1331 for (buffer, diff_bases_change, hunk_staging_operation_count) in
1332 diff_bases_changes_by_buffer
1333 {
1334 let Some(diff_state) =
1335 git_store.diffs.get(&buffer.read(cx).remote_id())
1336 else {
1337 continue;
1338 };
1339 let Some(diff_bases_change) = diff_bases_change else {
1340 continue;
1341 };
1342
1343 let downstream_client = git_store.downstream_client();
1344 diff_state.update(cx, |diff_state, cx| {
1345 use proto::update_diff_bases::Mode;
1346
1347 let buffer = buffer.read(cx);
1348 if let Some((client, project_id)) = downstream_client {
1349 let (staged_text, committed_text, mode) =
1350 match diff_bases_change.clone() {
1351 DiffBasesChange::SetIndex(index) => {
1352 (index, None, Mode::IndexOnly)
1353 }
1354 DiffBasesChange::SetHead(head) => {
1355 (None, head, Mode::HeadOnly)
1356 }
1357 DiffBasesChange::SetEach { index, head } => {
1358 (index, head, Mode::IndexAndHead)
1359 }
1360 DiffBasesChange::SetBoth(text) => {
1361 (None, text, Mode::IndexMatchesHead)
1362 }
1363 };
1364 let message = proto::UpdateDiffBases {
1365 project_id: project_id.to_proto(),
1366 buffer_id: buffer.remote_id().to_proto(),
1367 staged_text,
1368 committed_text,
1369 mode: mode as i32,
1370 };
1371
1372 client.send(message).log_err();
1373 }
1374
1375 let _ = diff_state.diff_bases_changed(
1376 buffer.text_snapshot(),
1377 diff_bases_change,
1378 hunk_staging_operation_count,
1379 cx,
1380 );
1381 });
1382 }
1383 })
1384 .ok();
1385 },
1386 );
1387 }
1388 }
1389
1390 pub fn repositories(&self) -> &HashMap<ProjectEntryId, Entity<Repository>> {
1391 &self.repositories
1392 }
1393
1394 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
1395 let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
1396 let status = repo.read(cx).repository_entry.status_for_path(&path)?;
1397 Some(status.status)
1398 }
1399
1400 pub fn repository_and_path_for_buffer_id(
1401 &self,
1402 buffer_id: BufferId,
1403 cx: &App,
1404 ) -> Option<(Entity<Repository>, RepoPath)> {
1405 let buffer = self.buffer_store.read(cx).get(buffer_id)?;
1406 let project_path = buffer.read(cx).project_path(cx)?;
1407 self.repository_and_path_for_project_path(&project_path, cx)
1408 }
1409
1410 pub fn repository_and_path_for_project_path(
1411 &self,
1412 path: &ProjectPath,
1413 cx: &App,
1414 ) -> Option<(Entity<Repository>, RepoPath)> {
1415 let abs_path = self.worktree_store.read(cx).absolutize(path, cx)?;
1416 self.repositories
1417 .values()
1418 .filter_map(|repo_handle| {
1419 let repo = repo_handle.read(cx);
1420 let relative_path = repo.repository_entry.relativize_abs_path(&abs_path)?;
1421 Some((repo_handle.clone(), relative_path))
1422 })
1423 .max_by_key(|(repo, _)| {
1424 repo.read(cx)
1425 .repository_entry
1426 .work_directory_abs_path
1427 .clone()
1428 })
1429 }
1430
1431 fn spawn_git_worker(cx: &mut Context<GitStore>) -> mpsc::UnboundedSender<GitJob> {
1432 let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
1433
1434 cx.spawn(async move |_, cx| {
1435 let mut jobs = VecDeque::new();
1436 loop {
1437 while let Ok(Some(next_job)) = job_rx.try_next() {
1438 jobs.push_back(next_job);
1439 }
1440
1441 if let Some(job) = jobs.pop_front() {
1442 if let Some(current_key) = &job.key {
1443 if jobs
1444 .iter()
1445 .any(|other_job| other_job.key.as_ref() == Some(current_key))
1446 {
1447 continue;
1448 }
1449 }
1450 (job.job)(cx).await;
1451 } else if let Some(job) = job_rx.next().await {
1452 jobs.push_back(job);
1453 } else {
1454 break;
1455 }
1456 }
1457 })
1458 .detach();
1459 job_tx
1460 }
1461
1462 pub fn git_init(
1463 &self,
1464 path: Arc<Path>,
1465 fallback_branch_name: String,
1466 cx: &App,
1467 ) -> Task<Result<()>> {
1468 match &self.state {
1469 GitStoreState::Local { fs, .. } => {
1470 let fs = fs.clone();
1471 cx.background_executor()
1472 .spawn(async move { fs.git_init(&path, fallback_branch_name) })
1473 }
1474 GitStoreState::Ssh {
1475 upstream_client,
1476 upstream_project_id: project_id,
1477 ..
1478 }
1479 | GitStoreState::Remote {
1480 upstream_client,
1481 project_id,
1482 ..
1483 } => {
1484 let client = upstream_client.clone();
1485 let project_id = *project_id;
1486 cx.background_executor().spawn(async move {
1487 client
1488 .request(proto::GitInit {
1489 project_id: project_id.0,
1490 abs_path: path.to_string_lossy().to_string(),
1491 fallback_branch_name,
1492 })
1493 .await?;
1494 Ok(())
1495 })
1496 }
1497 }
1498 }
1499
1500 async fn handle_update_repository(
1501 this: Entity<Self>,
1502 envelope: TypedEnvelope<proto::UpdateRepository>,
1503 mut cx: AsyncApp,
1504 ) -> Result<()> {
1505 this.update(&mut cx, |this, cx| {
1506 let mut update = envelope.payload;
1507
1508 let work_directory_id = ProjectEntryId::from_proto(update.id);
1509 let client = this
1510 .upstream_client()
1511 .context("no upstream client")?
1512 .clone();
1513
1514 let repo = this
1515 .repositories
1516 .entry(work_directory_id)
1517 .or_insert_with(|| {
1518 let git_store = cx.weak_entity();
1519
1520 cx.new(|_| Repository {
1521 commit_message_buffer: None,
1522 git_store,
1523 project_environment: None,
1524 worktree_id: None,
1525 repository_entry: RepositoryEntry {
1526 work_directory_id,
1527 current_branch: None,
1528 statuses_by_path: Default::default(),
1529 current_merge_conflicts: Default::default(),
1530 work_directory_abs_path: update.abs_path.clone().into(),
1531 worktree_scan_id: update.scan_id as usize,
1532 },
1533 merge_message: None,
1534 completed_scan_id: update.scan_id as usize,
1535 state: RepositoryState::Remote {
1536 project_id: ProjectId(update.project_id),
1537 client,
1538 work_directory_id,
1539 },
1540 job_sender: this.update_sender.clone(),
1541 askpass_delegates: Default::default(),
1542 latest_askpass_id: 0,
1543 })
1544 });
1545
1546 repo.update(cx, |repo, _cx| repo.apply_remote_update(update.clone()))?;
1547 cx.emit(GitEvent::GitStateUpdated);
1548 this.active_repo_id.get_or_insert_with(|| {
1549 cx.emit(GitEvent::ActiveRepositoryChanged);
1550 work_directory_id
1551 });
1552
1553 if let Some((client, project_id)) = this.downstream_client() {
1554 update.project_id = project_id.to_proto();
1555 client.send(update).log_err();
1556 }
1557 Ok(())
1558 })?
1559 }
1560
1561 async fn handle_remove_repository(
1562 this: Entity<Self>,
1563 envelope: TypedEnvelope<proto::RemoveRepository>,
1564 mut cx: AsyncApp,
1565 ) -> Result<()> {
1566 this.update(&mut cx, |this, cx| {
1567 let mut update = envelope.payload;
1568 let id = ProjectEntryId::from_proto(update.id);
1569 this.repositories.remove(&id);
1570 if let Some((client, project_id)) = this.downstream_client() {
1571 update.project_id = project_id.to_proto();
1572 client.send(update).log_err();
1573 }
1574 if this.active_repo_id == Some(id) {
1575 this.active_repo_id = None;
1576 cx.emit(GitEvent::ActiveRepositoryChanged);
1577 }
1578 cx.emit(GitEvent::GitStateUpdated);
1579 })
1580 }
1581
1582 async fn handle_git_init(
1583 this: Entity<Self>,
1584 envelope: TypedEnvelope<proto::GitInit>,
1585 cx: AsyncApp,
1586 ) -> Result<proto::Ack> {
1587 let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
1588 let name = envelope.payload.fallback_branch_name;
1589 cx.update(|cx| this.read(cx).git_init(path, name, cx))?
1590 .await?;
1591
1592 Ok(proto::Ack {})
1593 }
1594
1595 async fn handle_fetch(
1596 this: Entity<Self>,
1597 envelope: TypedEnvelope<proto::Fetch>,
1598 mut cx: AsyncApp,
1599 ) -> Result<proto::RemoteMessageResponse> {
1600 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1601 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1602 let askpass_id = envelope.payload.askpass_id;
1603
1604 let askpass = make_remote_delegate(
1605 this,
1606 envelope.payload.project_id,
1607 work_directory_id,
1608 askpass_id,
1609 &mut cx,
1610 );
1611
1612 let remote_output = repository_handle
1613 .update(&mut cx, |repository_handle, cx| {
1614 repository_handle.fetch(askpass, cx)
1615 })?
1616 .await??;
1617
1618 Ok(proto::RemoteMessageResponse {
1619 stdout: remote_output.stdout,
1620 stderr: remote_output.stderr,
1621 })
1622 }
1623
1624 async fn handle_push(
1625 this: Entity<Self>,
1626 envelope: TypedEnvelope<proto::Push>,
1627 mut cx: AsyncApp,
1628 ) -> Result<proto::RemoteMessageResponse> {
1629 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1630 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1631
1632 let askpass_id = envelope.payload.askpass_id;
1633 let askpass = make_remote_delegate(
1634 this,
1635 envelope.payload.project_id,
1636 work_directory_id,
1637 askpass_id,
1638 &mut cx,
1639 );
1640
1641 let options = envelope
1642 .payload
1643 .options
1644 .as_ref()
1645 .map(|_| match envelope.payload.options() {
1646 proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
1647 proto::push::PushOptions::Force => git::repository::PushOptions::Force,
1648 });
1649
1650 let branch_name = envelope.payload.branch_name.into();
1651 let remote_name = envelope.payload.remote_name.into();
1652
1653 let remote_output = repository_handle
1654 .update(&mut cx, |repository_handle, cx| {
1655 repository_handle.push(branch_name, remote_name, options, askpass, cx)
1656 })?
1657 .await??;
1658 Ok(proto::RemoteMessageResponse {
1659 stdout: remote_output.stdout,
1660 stderr: remote_output.stderr,
1661 })
1662 }
1663
1664 async fn handle_pull(
1665 this: Entity<Self>,
1666 envelope: TypedEnvelope<proto::Pull>,
1667 mut cx: AsyncApp,
1668 ) -> Result<proto::RemoteMessageResponse> {
1669 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1670 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1671 let askpass_id = envelope.payload.askpass_id;
1672 let askpass = make_remote_delegate(
1673 this,
1674 envelope.payload.project_id,
1675 work_directory_id,
1676 askpass_id,
1677 &mut cx,
1678 );
1679
1680 let branch_name = envelope.payload.branch_name.into();
1681 let remote_name = envelope.payload.remote_name.into();
1682
1683 let remote_message = repository_handle
1684 .update(&mut cx, |repository_handle, cx| {
1685 repository_handle.pull(branch_name, remote_name, askpass, cx)
1686 })?
1687 .await??;
1688
1689 Ok(proto::RemoteMessageResponse {
1690 stdout: remote_message.stdout,
1691 stderr: remote_message.stderr,
1692 })
1693 }
1694
1695 async fn handle_stage(
1696 this: Entity<Self>,
1697 envelope: TypedEnvelope<proto::Stage>,
1698 mut cx: AsyncApp,
1699 ) -> Result<proto::Ack> {
1700 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1701 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1702
1703 let entries = envelope
1704 .payload
1705 .paths
1706 .into_iter()
1707 .map(PathBuf::from)
1708 .map(RepoPath::new)
1709 .collect();
1710
1711 repository_handle
1712 .update(&mut cx, |repository_handle, cx| {
1713 repository_handle.stage_entries(entries, cx)
1714 })?
1715 .await?;
1716 Ok(proto::Ack {})
1717 }
1718
1719 async fn handle_unstage(
1720 this: Entity<Self>,
1721 envelope: TypedEnvelope<proto::Unstage>,
1722 mut cx: AsyncApp,
1723 ) -> Result<proto::Ack> {
1724 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1725 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1726
1727 let entries = envelope
1728 .payload
1729 .paths
1730 .into_iter()
1731 .map(PathBuf::from)
1732 .map(RepoPath::new)
1733 .collect();
1734
1735 repository_handle
1736 .update(&mut cx, |repository_handle, cx| {
1737 repository_handle.unstage_entries(entries, cx)
1738 })?
1739 .await?;
1740
1741 Ok(proto::Ack {})
1742 }
1743
1744 async fn handle_set_index_text(
1745 this: Entity<Self>,
1746 envelope: TypedEnvelope<proto::SetIndexText>,
1747 mut cx: AsyncApp,
1748 ) -> Result<proto::Ack> {
1749 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1750 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1751
1752 repository_handle
1753 .update(&mut cx, |repository_handle, cx| {
1754 repository_handle.spawn_set_index_text_job(
1755 RepoPath::from_str(&envelope.payload.path),
1756 envelope.payload.text,
1757 cx,
1758 )
1759 })?
1760 .await??;
1761 Ok(proto::Ack {})
1762 }
1763
1764 async fn handle_commit(
1765 this: Entity<Self>,
1766 envelope: TypedEnvelope<proto::Commit>,
1767 mut cx: AsyncApp,
1768 ) -> Result<proto::Ack> {
1769 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1770 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1771
1772 let message = SharedString::from(envelope.payload.message);
1773 let name = envelope.payload.name.map(SharedString::from);
1774 let email = envelope.payload.email.map(SharedString::from);
1775
1776 repository_handle
1777 .update(&mut cx, |repository_handle, cx| {
1778 repository_handle.commit(message, name.zip(email), cx)
1779 })?
1780 .await??;
1781 Ok(proto::Ack {})
1782 }
1783
1784 async fn handle_get_remotes(
1785 this: Entity<Self>,
1786 envelope: TypedEnvelope<proto::GetRemotes>,
1787 mut cx: AsyncApp,
1788 ) -> Result<proto::GetRemotesResponse> {
1789 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1790 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1791
1792 let branch_name = envelope.payload.branch_name;
1793
1794 let remotes = repository_handle
1795 .update(&mut cx, |repository_handle, _| {
1796 repository_handle.get_remotes(branch_name)
1797 })?
1798 .await??;
1799
1800 Ok(proto::GetRemotesResponse {
1801 remotes: remotes
1802 .into_iter()
1803 .map(|remotes| proto::get_remotes_response::Remote {
1804 name: remotes.name.to_string(),
1805 })
1806 .collect::<Vec<_>>(),
1807 })
1808 }
1809
1810 async fn handle_get_branches(
1811 this: Entity<Self>,
1812 envelope: TypedEnvelope<proto::GitGetBranches>,
1813 mut cx: AsyncApp,
1814 ) -> Result<proto::GitBranchesResponse> {
1815 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1816 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1817
1818 let branches = repository_handle
1819 .update(&mut cx, |repository_handle, _| repository_handle.branches())?
1820 .await??;
1821
1822 Ok(proto::GitBranchesResponse {
1823 branches: branches
1824 .into_iter()
1825 .map(|branch| worktree::branch_to_proto(&branch))
1826 .collect::<Vec<_>>(),
1827 })
1828 }
1829 async fn handle_create_branch(
1830 this: Entity<Self>,
1831 envelope: TypedEnvelope<proto::GitCreateBranch>,
1832 mut cx: AsyncApp,
1833 ) -> Result<proto::Ack> {
1834 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1835 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1836 let branch_name = envelope.payload.branch_name;
1837
1838 repository_handle
1839 .update(&mut cx, |repository_handle, _| {
1840 repository_handle.create_branch(branch_name)
1841 })?
1842 .await??;
1843
1844 Ok(proto::Ack {})
1845 }
1846
1847 async fn handle_change_branch(
1848 this: Entity<Self>,
1849 envelope: TypedEnvelope<proto::GitChangeBranch>,
1850 mut cx: AsyncApp,
1851 ) -> Result<proto::Ack> {
1852 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1853 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1854 let branch_name = envelope.payload.branch_name;
1855
1856 repository_handle
1857 .update(&mut cx, |repository_handle, _| {
1858 repository_handle.change_branch(branch_name)
1859 })?
1860 .await??;
1861
1862 Ok(proto::Ack {})
1863 }
1864
1865 async fn handle_show(
1866 this: Entity<Self>,
1867 envelope: TypedEnvelope<proto::GitShow>,
1868 mut cx: AsyncApp,
1869 ) -> Result<proto::GitCommitDetails> {
1870 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1871 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1872
1873 let commit = repository_handle
1874 .update(&mut cx, |repository_handle, _| {
1875 repository_handle.show(envelope.payload.commit)
1876 })?
1877 .await??;
1878 Ok(proto::GitCommitDetails {
1879 sha: commit.sha.into(),
1880 message: commit.message.into(),
1881 commit_timestamp: commit.commit_timestamp,
1882 committer_email: commit.committer_email.into(),
1883 committer_name: commit.committer_name.into(),
1884 })
1885 }
1886
1887 async fn handle_reset(
1888 this: Entity<Self>,
1889 envelope: TypedEnvelope<proto::GitReset>,
1890 mut cx: AsyncApp,
1891 ) -> Result<proto::Ack> {
1892 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1893 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1894
1895 let mode = match envelope.payload.mode() {
1896 git_reset::ResetMode::Soft => ResetMode::Soft,
1897 git_reset::ResetMode::Mixed => ResetMode::Mixed,
1898 };
1899
1900 repository_handle
1901 .update(&mut cx, |repository_handle, cx| {
1902 repository_handle.reset(envelope.payload.commit, mode, cx)
1903 })?
1904 .await??;
1905 Ok(proto::Ack {})
1906 }
1907
1908 async fn handle_checkout_files(
1909 this: Entity<Self>,
1910 envelope: TypedEnvelope<proto::GitCheckoutFiles>,
1911 mut cx: AsyncApp,
1912 ) -> Result<proto::Ack> {
1913 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1914 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1915 let paths = envelope
1916 .payload
1917 .paths
1918 .iter()
1919 .map(|s| RepoPath::from_str(s))
1920 .collect();
1921
1922 repository_handle
1923 .update(&mut cx, |repository_handle, cx| {
1924 repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
1925 })?
1926 .await??;
1927 Ok(proto::Ack {})
1928 }
1929
1930 async fn handle_open_commit_message_buffer(
1931 this: Entity<Self>,
1932 envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
1933 mut cx: AsyncApp,
1934 ) -> Result<proto::OpenBufferResponse> {
1935 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1936 let repository = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1937 let buffer = repository
1938 .update(&mut cx, |repository, cx| {
1939 repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
1940 })?
1941 .await?;
1942
1943 let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
1944 this.update(&mut cx, |this, cx| {
1945 this.buffer_store.update(cx, |buffer_store, cx| {
1946 buffer_store
1947 .create_buffer_for_peer(
1948 &buffer,
1949 envelope.original_sender_id.unwrap_or(envelope.sender_id),
1950 cx,
1951 )
1952 .detach_and_log_err(cx);
1953 })
1954 })?;
1955
1956 Ok(proto::OpenBufferResponse {
1957 buffer_id: buffer_id.to_proto(),
1958 })
1959 }
1960
1961 async fn handle_askpass(
1962 this: Entity<Self>,
1963 envelope: TypedEnvelope<proto::AskPassRequest>,
1964 mut cx: AsyncApp,
1965 ) -> Result<proto::AskPassResponse> {
1966 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1967 let repository = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1968
1969 let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone())?;
1970 let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
1971 debug_panic!("no askpass found");
1972 return Err(anyhow::anyhow!("no askpass found"));
1973 };
1974
1975 let response = askpass.ask_password(envelope.payload.prompt).await?;
1976
1977 delegates
1978 .lock()
1979 .insert(envelope.payload.askpass_id, askpass);
1980
1981 Ok(proto::AskPassResponse { response })
1982 }
1983
1984 async fn handle_check_for_pushed_commits(
1985 this: Entity<Self>,
1986 envelope: TypedEnvelope<proto::CheckForPushedCommits>,
1987 mut cx: AsyncApp,
1988 ) -> Result<proto::CheckForPushedCommitsResponse> {
1989 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
1990 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
1991
1992 let branches = repository_handle
1993 .update(&mut cx, |repository_handle, _| {
1994 repository_handle.check_for_pushed_commits()
1995 })?
1996 .await??;
1997 Ok(proto::CheckForPushedCommitsResponse {
1998 pushed_to: branches
1999 .into_iter()
2000 .map(|commit| commit.to_string())
2001 .collect(),
2002 })
2003 }
2004
2005 async fn handle_git_diff(
2006 this: Entity<Self>,
2007 envelope: TypedEnvelope<proto::GitDiff>,
2008 mut cx: AsyncApp,
2009 ) -> Result<proto::GitDiffResponse> {
2010 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
2011 let repository_handle = Self::repository_for_request(&this, work_directory_id, &mut cx)?;
2012 let diff_type = match envelope.payload.diff_type() {
2013 proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
2014 proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
2015 };
2016
2017 let mut diff = repository_handle
2018 .update(&mut cx, |repository_handle, cx| {
2019 repository_handle.diff(diff_type, cx)
2020 })?
2021 .await??;
2022 const ONE_MB: usize = 1_000_000;
2023 if diff.len() > ONE_MB {
2024 diff = diff.chars().take(ONE_MB).collect()
2025 }
2026
2027 Ok(proto::GitDiffResponse { diff })
2028 }
2029
2030 async fn handle_open_unstaged_diff(
2031 this: Entity<Self>,
2032 request: TypedEnvelope<proto::OpenUnstagedDiff>,
2033 mut cx: AsyncApp,
2034 ) -> Result<proto::OpenUnstagedDiffResponse> {
2035 let buffer_id = BufferId::new(request.payload.buffer_id)?;
2036 let diff = this
2037 .update(&mut cx, |this, cx| {
2038 let buffer = this.buffer_store.read(cx).get(buffer_id)?;
2039 Some(this.open_unstaged_diff(buffer, cx))
2040 })?
2041 .ok_or_else(|| anyhow!("no such buffer"))?
2042 .await?;
2043 this.update(&mut cx, |this, _| {
2044 let shared_diffs = this
2045 .shared_diffs
2046 .entry(request.original_sender_id.unwrap_or(request.sender_id))
2047 .or_default();
2048 shared_diffs.entry(buffer_id).or_default().unstaged = Some(diff.clone());
2049 })?;
2050 let staged_text = diff.read_with(&cx, |diff, _| diff.base_text_string())?;
2051 Ok(proto::OpenUnstagedDiffResponse { staged_text })
2052 }
2053
2054 async fn handle_open_uncommitted_diff(
2055 this: Entity<Self>,
2056 request: TypedEnvelope<proto::OpenUncommittedDiff>,
2057 mut cx: AsyncApp,
2058 ) -> Result<proto::OpenUncommittedDiffResponse> {
2059 let buffer_id = BufferId::new(request.payload.buffer_id)?;
2060 let diff = this
2061 .update(&mut cx, |this, cx| {
2062 let buffer = this.buffer_store.read(cx).get(buffer_id)?;
2063 Some(this.open_uncommitted_diff(buffer, cx))
2064 })?
2065 .ok_or_else(|| anyhow!("no such buffer"))?
2066 .await?;
2067 this.update(&mut cx, |this, _| {
2068 let shared_diffs = this
2069 .shared_diffs
2070 .entry(request.original_sender_id.unwrap_or(request.sender_id))
2071 .or_default();
2072 shared_diffs.entry(buffer_id).or_default().uncommitted = Some(diff.clone());
2073 })?;
2074 diff.read_with(&cx, |diff, cx| {
2075 use proto::open_uncommitted_diff_response::Mode;
2076
2077 let unstaged_diff = diff.secondary_diff();
2078 let index_snapshot = unstaged_diff.and_then(|diff| {
2079 let diff = diff.read(cx);
2080 diff.base_text_exists().then(|| diff.base_text())
2081 });
2082
2083 let mode;
2084 let staged_text;
2085 let committed_text;
2086 if diff.base_text_exists() {
2087 let committed_snapshot = diff.base_text();
2088 committed_text = Some(committed_snapshot.text());
2089 if let Some(index_text) = index_snapshot {
2090 if index_text.remote_id() == committed_snapshot.remote_id() {
2091 mode = Mode::IndexMatchesHead;
2092 staged_text = None;
2093 } else {
2094 mode = Mode::IndexAndHead;
2095 staged_text = Some(index_text.text());
2096 }
2097 } else {
2098 mode = Mode::IndexAndHead;
2099 staged_text = None;
2100 }
2101 } else {
2102 mode = Mode::IndexAndHead;
2103 committed_text = None;
2104 staged_text = index_snapshot.as_ref().map(|buffer| buffer.text());
2105 }
2106
2107 proto::OpenUncommittedDiffResponse {
2108 committed_text,
2109 staged_text,
2110 mode: mode.into(),
2111 }
2112 })
2113 }
2114
2115 async fn handle_update_diff_bases(
2116 this: Entity<Self>,
2117 request: TypedEnvelope<proto::UpdateDiffBases>,
2118 mut cx: AsyncApp,
2119 ) -> Result<()> {
2120 let buffer_id = BufferId::new(request.payload.buffer_id)?;
2121 this.update(&mut cx, |this, cx| {
2122 if let Some(diff_state) = this.diffs.get_mut(&buffer_id) {
2123 if let Some(buffer) = this.buffer_store.read(cx).get(buffer_id) {
2124 let buffer = buffer.read(cx).text_snapshot();
2125 diff_state.update(cx, |diff_state, cx| {
2126 diff_state.handle_base_texts_updated(buffer, request.payload, cx);
2127 })
2128 }
2129 }
2130 })
2131 }
2132
2133 async fn handle_blame_buffer(
2134 this: Entity<Self>,
2135 envelope: TypedEnvelope<proto::BlameBuffer>,
2136 mut cx: AsyncApp,
2137 ) -> Result<proto::BlameBufferResponse> {
2138 let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
2139 let version = deserialize_version(&envelope.payload.version);
2140 let buffer = this.read_with(&cx, |this, cx| {
2141 this.buffer_store.read(cx).get_existing(buffer_id)
2142 })??;
2143 buffer
2144 .update(&mut cx, |buffer, _| {
2145 buffer.wait_for_version(version.clone())
2146 })?
2147 .await?;
2148 let blame = this
2149 .update(&mut cx, |this, cx| {
2150 this.blame_buffer(&buffer, Some(version), cx)
2151 })?
2152 .await?;
2153 Ok(serialize_blame_buffer_response(blame))
2154 }
2155
2156 async fn handle_get_permalink_to_line(
2157 this: Entity<Self>,
2158 envelope: TypedEnvelope<proto::GetPermalinkToLine>,
2159 mut cx: AsyncApp,
2160 ) -> Result<proto::GetPermalinkToLineResponse> {
2161 let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
2162 // let version = deserialize_version(&envelope.payload.version);
2163 let selection = {
2164 let proto_selection = envelope
2165 .payload
2166 .selection
2167 .context("no selection to get permalink for defined")?;
2168 proto_selection.start as u32..proto_selection.end as u32
2169 };
2170 let buffer = this.read_with(&cx, |this, cx| {
2171 this.buffer_store.read(cx).get_existing(buffer_id)
2172 })??;
2173 let permalink = this
2174 .update(&mut cx, |this, cx| {
2175 this.get_permalink_to_line(&buffer, selection, cx)
2176 })?
2177 .await?;
2178 Ok(proto::GetPermalinkToLineResponse {
2179 permalink: permalink.to_string(),
2180 })
2181 }
2182
2183 fn repository_for_request(
2184 this: &Entity<Self>,
2185 work_directory_id: ProjectEntryId,
2186 cx: &mut AsyncApp,
2187 ) -> Result<Entity<Repository>> {
2188 this.update(cx, |this, cx| {
2189 this.repositories
2190 .values()
2191 .find(|repository_handle| {
2192 repository_handle
2193 .read(cx)
2194 .repository_entry
2195 .work_directory_id()
2196 == work_directory_id
2197 })
2198 .context("missing repository handle")
2199 .cloned()
2200 })?
2201 }
2202
2203 pub fn repo_snapshots(&self, cx: &App) -> HashMap<ProjectEntryId, RepositoryEntry> {
2204 self.repositories
2205 .iter()
2206 .map(|(id, repo)| (*id, repo.read(cx).repository_entry.clone()))
2207 .collect()
2208 }
2209}
2210
2211impl BufferDiffState {
2212 fn buffer_language_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
2213 self.language = buffer.read(cx).language().cloned();
2214 self.language_changed = true;
2215 let _ = self.recalculate_diffs(
2216 buffer.read(cx).text_snapshot(),
2217 self.hunk_staging_operation_count,
2218 cx,
2219 );
2220 }
2221
2222 fn unstaged_diff(&self) -> Option<Entity<BufferDiff>> {
2223 self.unstaged_diff.as_ref().and_then(|set| set.upgrade())
2224 }
2225
2226 fn uncommitted_diff(&self) -> Option<Entity<BufferDiff>> {
2227 self.uncommitted_diff.as_ref().and_then(|set| set.upgrade())
2228 }
2229
2230 fn handle_base_texts_updated(
2231 &mut self,
2232 buffer: text::BufferSnapshot,
2233 message: proto::UpdateDiffBases,
2234 cx: &mut Context<Self>,
2235 ) {
2236 use proto::update_diff_bases::Mode;
2237
2238 let Some(mode) = Mode::from_i32(message.mode) else {
2239 return;
2240 };
2241
2242 let diff_bases_change = match mode {
2243 Mode::HeadOnly => DiffBasesChange::SetHead(message.committed_text),
2244 Mode::IndexOnly => DiffBasesChange::SetIndex(message.staged_text),
2245 Mode::IndexMatchesHead => DiffBasesChange::SetBoth(message.committed_text),
2246 Mode::IndexAndHead => DiffBasesChange::SetEach {
2247 index: message.staged_text,
2248 head: message.committed_text,
2249 },
2250 };
2251
2252 let _ = self.diff_bases_changed(
2253 buffer,
2254 diff_bases_change,
2255 self.hunk_staging_operation_count,
2256 cx,
2257 );
2258 }
2259
2260 pub fn wait_for_recalculation(&mut self) -> Option<oneshot::Receiver<()>> {
2261 if self.diff_updated_futures.is_empty() {
2262 return None;
2263 }
2264 let (tx, rx) = oneshot::channel();
2265 self.diff_updated_futures.push(tx);
2266 Some(rx)
2267 }
2268
2269 fn diff_bases_changed(
2270 &mut self,
2271 buffer: text::BufferSnapshot,
2272 diff_bases_change: DiffBasesChange,
2273 prev_hunk_staging_operation_count: usize,
2274 cx: &mut Context<Self>,
2275 ) -> oneshot::Receiver<()> {
2276 match diff_bases_change {
2277 DiffBasesChange::SetIndex(index) => {
2278 self.index_text = index.map(|mut index| {
2279 text::LineEnding::normalize(&mut index);
2280 Arc::new(index)
2281 });
2282 self.index_changed = true;
2283 }
2284 DiffBasesChange::SetHead(head) => {
2285 self.head_text = head.map(|mut head| {
2286 text::LineEnding::normalize(&mut head);
2287 Arc::new(head)
2288 });
2289 self.head_changed = true;
2290 }
2291 DiffBasesChange::SetBoth(text) => {
2292 let text = text.map(|mut text| {
2293 text::LineEnding::normalize(&mut text);
2294 Arc::new(text)
2295 });
2296 self.head_text = text.clone();
2297 self.index_text = text;
2298 self.head_changed = true;
2299 self.index_changed = true;
2300 }
2301 DiffBasesChange::SetEach { index, head } => {
2302 self.index_text = index.map(|mut index| {
2303 text::LineEnding::normalize(&mut index);
2304 Arc::new(index)
2305 });
2306 self.index_changed = true;
2307 self.head_text = head.map(|mut head| {
2308 text::LineEnding::normalize(&mut head);
2309 Arc::new(head)
2310 });
2311 self.head_changed = true;
2312 }
2313 }
2314
2315 self.recalculate_diffs(buffer, prev_hunk_staging_operation_count, cx)
2316 }
2317
2318 fn recalculate_diffs(
2319 &mut self,
2320 buffer: text::BufferSnapshot,
2321 prev_hunk_staging_operation_count: usize,
2322 cx: &mut Context<Self>,
2323 ) -> oneshot::Receiver<()> {
2324 log::debug!("recalculate diffs");
2325 let (tx, rx) = oneshot::channel();
2326 self.diff_updated_futures.push(tx);
2327
2328 let language = self.language.clone();
2329 let language_registry = self.language_registry.clone();
2330 let unstaged_diff = self.unstaged_diff();
2331 let uncommitted_diff = self.uncommitted_diff();
2332 let head = self.head_text.clone();
2333 let index = self.index_text.clone();
2334 let index_changed = self.index_changed;
2335 let head_changed = self.head_changed;
2336 let language_changed = self.language_changed;
2337 let index_matches_head = match (self.index_text.as_ref(), self.head_text.as_ref()) {
2338 (Some(index), Some(head)) => Arc::ptr_eq(index, head),
2339 (None, None) => true,
2340 _ => false,
2341 };
2342 self.recalculate_diff_task = Some(cx.spawn(async move |this, cx| {
2343 let mut new_unstaged_diff = None;
2344 if let Some(unstaged_diff) = &unstaged_diff {
2345 new_unstaged_diff = Some(
2346 BufferDiff::update_diff(
2347 unstaged_diff.clone(),
2348 buffer.clone(),
2349 index,
2350 index_changed,
2351 language_changed,
2352 language.clone(),
2353 language_registry.clone(),
2354 cx,
2355 )
2356 .await?,
2357 );
2358 }
2359
2360 let mut new_uncommitted_diff = None;
2361 if let Some(uncommitted_diff) = &uncommitted_diff {
2362 new_uncommitted_diff = if index_matches_head {
2363 new_unstaged_diff.clone()
2364 } else {
2365 Some(
2366 BufferDiff::update_diff(
2367 uncommitted_diff.clone(),
2368 buffer.clone(),
2369 head,
2370 head_changed,
2371 language_changed,
2372 language.clone(),
2373 language_registry.clone(),
2374 cx,
2375 )
2376 .await?,
2377 )
2378 }
2379 }
2380
2381 if this.update(cx, |this, _| {
2382 this.hunk_staging_operation_count > prev_hunk_staging_operation_count
2383 })? {
2384 return Ok(());
2385 }
2386
2387 let unstaged_changed_range = if let Some((unstaged_diff, new_unstaged_diff)) =
2388 unstaged_diff.as_ref().zip(new_unstaged_diff.clone())
2389 {
2390 unstaged_diff.update(cx, |diff, cx| {
2391 diff.set_snapshot(&buffer, new_unstaged_diff, language_changed, None, cx)
2392 })?
2393 } else {
2394 None
2395 };
2396
2397 if let Some((uncommitted_diff, new_uncommitted_diff)) =
2398 uncommitted_diff.as_ref().zip(new_uncommitted_diff.clone())
2399 {
2400 uncommitted_diff.update(cx, |uncommitted_diff, cx| {
2401 uncommitted_diff.set_snapshot(
2402 &buffer,
2403 new_uncommitted_diff,
2404 language_changed,
2405 unstaged_changed_range,
2406 cx,
2407 );
2408 })?;
2409 }
2410
2411 if let Some(this) = this.upgrade() {
2412 this.update(cx, |this, _| {
2413 this.index_changed = false;
2414 this.head_changed = false;
2415 this.language_changed = false;
2416 for tx in this.diff_updated_futures.drain(..) {
2417 tx.send(()).ok();
2418 }
2419 })?;
2420 }
2421
2422 Ok(())
2423 }));
2424
2425 rx
2426 }
2427}
2428
2429fn make_remote_delegate(
2430 this: Entity<GitStore>,
2431 project_id: u64,
2432 work_directory_id: ProjectEntryId,
2433 askpass_id: u64,
2434 cx: &mut AsyncApp,
2435) -> AskPassDelegate {
2436 AskPassDelegate::new(cx, move |prompt, tx, cx| {
2437 this.update(cx, |this, cx| {
2438 let Some((client, _)) = this.downstream_client() else {
2439 return;
2440 };
2441 let response = client.request(proto::AskPassRequest {
2442 project_id,
2443 work_directory_id: work_directory_id.to_proto(),
2444 askpass_id,
2445 prompt,
2446 });
2447 cx.spawn(async move |_, _| {
2448 tx.send(response.await?.response).ok();
2449 anyhow::Ok(())
2450 })
2451 .detach_and_log_err(cx);
2452 })
2453 .log_err();
2454 })
2455}
2456
2457impl GitStoreState {
2458 fn load_staged_text(
2459 &self,
2460 buffer: &Entity<Buffer>,
2461 buffer_store: &Entity<BufferStore>,
2462 cx: &App,
2463 ) -> Task<Result<Option<String>>> {
2464 match self {
2465 GitStoreState::Local { .. } => {
2466 if let Some((worktree, path)) =
2467 buffer_store.read(cx).worktree_for_buffer(buffer, cx)
2468 {
2469 worktree.read(cx).load_staged_file(path.as_ref(), cx)
2470 } else {
2471 return Task::ready(Err(anyhow!("no such worktree")));
2472 }
2473 }
2474 GitStoreState::Ssh {
2475 upstream_client,
2476 upstream_project_id: project_id,
2477 ..
2478 }
2479 | GitStoreState::Remote {
2480 upstream_client,
2481 project_id,
2482 } => {
2483 let buffer_id = buffer.read(cx).remote_id();
2484 let project_id = *project_id;
2485 let client = upstream_client.clone();
2486 cx.background_spawn(async move {
2487 let response = client
2488 .request(proto::OpenUnstagedDiff {
2489 project_id: project_id.to_proto(),
2490 buffer_id: buffer_id.to_proto(),
2491 })
2492 .await?;
2493 Ok(response.staged_text)
2494 })
2495 }
2496 }
2497 }
2498
2499 fn load_committed_text(
2500 &self,
2501 buffer: &Entity<Buffer>,
2502 buffer_store: &Entity<BufferStore>,
2503 cx: &App,
2504 ) -> Task<Result<DiffBasesChange>> {
2505 match self {
2506 GitStoreState::Local { .. } => {
2507 if let Some((worktree, path)) =
2508 buffer_store.read(cx).worktree_for_buffer(buffer, cx)
2509 {
2510 let worktree = worktree.read(cx);
2511 let committed_text = worktree.load_committed_file(&path, cx);
2512 let staged_text = worktree.load_staged_file(&path, cx);
2513 cx.background_spawn(async move {
2514 let committed_text = committed_text.await?;
2515 let staged_text = staged_text.await?;
2516 let diff_bases_change = if committed_text == staged_text {
2517 DiffBasesChange::SetBoth(committed_text)
2518 } else {
2519 DiffBasesChange::SetEach {
2520 index: staged_text,
2521 head: committed_text,
2522 }
2523 };
2524 Ok(diff_bases_change)
2525 })
2526 } else {
2527 Task::ready(Err(anyhow!("no such worktree")))
2528 }
2529 }
2530 GitStoreState::Ssh {
2531 upstream_client,
2532 upstream_project_id: project_id,
2533 ..
2534 }
2535 | GitStoreState::Remote {
2536 upstream_client,
2537 project_id,
2538 } => {
2539 use proto::open_uncommitted_diff_response::Mode;
2540
2541 let buffer_id = buffer.read(cx).remote_id();
2542 let project_id = *project_id;
2543 let client = upstream_client.clone();
2544 cx.background_spawn(async move {
2545 let response = client
2546 .request(proto::OpenUncommittedDiff {
2547 project_id: project_id.to_proto(),
2548 buffer_id: buffer_id.to_proto(),
2549 })
2550 .await?;
2551 let mode =
2552 Mode::from_i32(response.mode).ok_or_else(|| anyhow!("Invalid mode"))?;
2553 let bases = match mode {
2554 Mode::IndexMatchesHead => DiffBasesChange::SetBoth(response.committed_text),
2555 Mode::IndexAndHead => DiffBasesChange::SetEach {
2556 head: response.committed_text,
2557 index: response.staged_text,
2558 },
2559 };
2560 Ok(bases)
2561 })
2562 }
2563 }
2564 }
2565}
2566
2567impl Repository {
2568 pub fn git_store(&self) -> Option<Entity<GitStore>> {
2569 self.git_store.upgrade()
2570 }
2571
2572 fn id(&self) -> ProjectEntryId {
2573 self.repository_entry.work_directory_id()
2574 }
2575
2576 pub fn current_branch(&self) -> Option<&Branch> {
2577 self.repository_entry.branch()
2578 }
2579
2580 pub fn status_for_path(&self, path: &RepoPath) -> Option<StatusEntry> {
2581 self.repository_entry.status_for_path(path)
2582 }
2583
2584 fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
2585 where
2586 F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
2587 Fut: Future<Output = R> + 'static,
2588 R: Send + 'static,
2589 {
2590 self.send_keyed_job(None, job)
2591 }
2592
2593 fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
2594 where
2595 F: FnOnce(RepositoryState, AsyncApp) -> Fut + 'static,
2596 Fut: Future<Output = R> + 'static,
2597 R: Send + 'static,
2598 {
2599 let (result_tx, result_rx) = futures::channel::oneshot::channel();
2600 let git_repo = self.state.clone();
2601 self.job_sender
2602 .unbounded_send(GitJob {
2603 key,
2604 job: Box::new(|cx: &mut AsyncApp| {
2605 let job = job(git_repo, cx.clone());
2606 cx.spawn(async move |_| {
2607 let result = job.await;
2608 result_tx.send(result).ok();
2609 })
2610 }),
2611 })
2612 .ok();
2613 result_rx
2614 }
2615
2616 /// This is the name that will be displayed in the repository selector for this repository.
2617 pub fn display_name(&self) -> SharedString {
2618 self.repository_entry
2619 .work_directory_abs_path
2620 .file_name()
2621 .unwrap_or_default()
2622 .to_string_lossy()
2623 .to_string()
2624 .into()
2625 }
2626
2627 pub fn set_as_active_repository(&self, cx: &mut Context<Self>) {
2628 let Some(git_store) = self.git_store.upgrade() else {
2629 return;
2630 };
2631 let entity = cx.entity();
2632 git_store.update(cx, |git_store, cx| {
2633 let Some((&id, _)) = git_store
2634 .repositories
2635 .iter()
2636 .find(|(_, handle)| *handle == &entity)
2637 else {
2638 return;
2639 };
2640 git_store.active_repo_id = Some(id);
2641 cx.emit(GitEvent::ActiveRepositoryChanged);
2642 });
2643 }
2644
2645 pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
2646 self.repository_entry.status()
2647 }
2648
2649 pub fn has_conflict(&self, path: &RepoPath) -> bool {
2650 self.repository_entry
2651 .current_merge_conflicts
2652 .contains(&path)
2653 }
2654
2655 pub fn repo_path_to_project_path(&self, path: &RepoPath, cx: &App) -> Option<ProjectPath> {
2656 let git_store = self.git_store.upgrade()?;
2657 let worktree_store = git_store.read(cx).worktree_store.read(cx);
2658 let abs_path = self.repository_entry.work_directory_abs_path.join(&path.0);
2659 let (worktree, relative_path) = worktree_store.find_worktree(abs_path, cx)?;
2660 Some(ProjectPath {
2661 worktree_id: worktree.read(cx).id(),
2662 path: relative_path.into(),
2663 })
2664 }
2665
2666 pub fn project_path_to_repo_path(&self, path: &ProjectPath, cx: &App) -> Option<RepoPath> {
2667 let git_store = self.git_store.upgrade()?;
2668 let worktree_store = git_store.read(cx).worktree_store.read(cx);
2669 let abs_path = worktree_store.absolutize(path, cx)?;
2670 self.repository_entry.relativize_abs_path(&abs_path)
2671 }
2672
2673 pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
2674 other
2675 .read(cx)
2676 .repository_entry
2677 .work_directory_abs_path
2678 .starts_with(&self.repository_entry.work_directory_abs_path)
2679 }
2680
2681 pub fn local_repository(&self) -> Option<Arc<dyn GitRepository>> {
2682 match &self.state {
2683 RepositoryState::Local(git_repository) => Some(git_repository.clone()),
2684 RepositoryState::Remote { .. } => None,
2685 }
2686 }
2687
2688 pub fn open_commit_buffer(
2689 &mut self,
2690 languages: Option<Arc<LanguageRegistry>>,
2691 buffer_store: Entity<BufferStore>,
2692 cx: &mut Context<Self>,
2693 ) -> Task<Result<Entity<Buffer>>> {
2694 if let Some(buffer) = self.commit_message_buffer.clone() {
2695 return Task::ready(Ok(buffer));
2696 }
2697
2698 if let RepositoryState::Remote {
2699 project_id,
2700 client,
2701 work_directory_id,
2702 } = self.state.clone()
2703 {
2704 let client = client.clone();
2705 cx.spawn(async move |repository, cx| {
2706 let request = client.request(proto::OpenCommitMessageBuffer {
2707 project_id: project_id.0,
2708 work_directory_id: work_directory_id.to_proto(),
2709 });
2710 let response = request.await.context("requesting to open commit buffer")?;
2711 let buffer_id = BufferId::new(response.buffer_id)?;
2712 let buffer = buffer_store
2713 .update(cx, |buffer_store, cx| {
2714 buffer_store.wait_for_remote_buffer(buffer_id, cx)
2715 })?
2716 .await?;
2717 if let Some(language_registry) = languages {
2718 let git_commit_language =
2719 language_registry.language_for_name("Git Commit").await?;
2720 buffer.update(cx, |buffer, cx| {
2721 buffer.set_language(Some(git_commit_language), cx);
2722 })?;
2723 }
2724 repository.update(cx, |repository, _| {
2725 repository.commit_message_buffer = Some(buffer.clone());
2726 })?;
2727 Ok(buffer)
2728 })
2729 } else {
2730 self.open_local_commit_buffer(languages, buffer_store, cx)
2731 }
2732 }
2733
2734 fn open_local_commit_buffer(
2735 &mut self,
2736 language_registry: Option<Arc<LanguageRegistry>>,
2737 buffer_store: Entity<BufferStore>,
2738 cx: &mut Context<Self>,
2739 ) -> Task<Result<Entity<Buffer>>> {
2740 cx.spawn(async move |repository, cx| {
2741 let buffer = buffer_store
2742 .update(cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
2743 .await?;
2744
2745 if let Some(language_registry) = language_registry {
2746 let git_commit_language = language_registry.language_for_name("Git Commit").await?;
2747 buffer.update(cx, |buffer, cx| {
2748 buffer.set_language(Some(git_commit_language), cx);
2749 })?;
2750 }
2751
2752 repository.update(cx, |repository, _| {
2753 repository.commit_message_buffer = Some(buffer.clone());
2754 })?;
2755 Ok(buffer)
2756 })
2757 }
2758
2759 pub fn checkout_files(
2760 &self,
2761 commit: &str,
2762 paths: Vec<RepoPath>,
2763 cx: &mut App,
2764 ) -> oneshot::Receiver<Result<()>> {
2765 let commit = commit.to_string();
2766 let env = self.worktree_environment(cx);
2767
2768 self.send_job(|git_repo, _| async move {
2769 match git_repo {
2770 RepositoryState::Local(repo) => repo.checkout_files(commit, paths, env.await).await,
2771 RepositoryState::Remote {
2772 project_id,
2773 client,
2774 work_directory_id,
2775 } => {
2776 client
2777 .request(proto::GitCheckoutFiles {
2778 project_id: project_id.0,
2779 work_directory_id: work_directory_id.to_proto(),
2780 commit,
2781 paths: paths
2782 .into_iter()
2783 .map(|p| p.to_string_lossy().to_string())
2784 .collect(),
2785 })
2786 .await?;
2787
2788 Ok(())
2789 }
2790 }
2791 })
2792 }
2793
2794 pub fn reset(
2795 &self,
2796 commit: String,
2797 reset_mode: ResetMode,
2798 cx: &mut App,
2799 ) -> oneshot::Receiver<Result<()>> {
2800 let commit = commit.to_string();
2801 let env = self.worktree_environment(cx);
2802 self.send_job(|git_repo, _| async move {
2803 match git_repo {
2804 RepositoryState::Local(git_repo) => {
2805 let env = env.await;
2806 git_repo.reset(commit, reset_mode, env).await
2807 }
2808 RepositoryState::Remote {
2809 project_id,
2810 client,
2811 work_directory_id,
2812 } => {
2813 client
2814 .request(proto::GitReset {
2815 project_id: project_id.0,
2816 work_directory_id: work_directory_id.to_proto(),
2817 commit,
2818 mode: match reset_mode {
2819 ResetMode::Soft => git_reset::ResetMode::Soft.into(),
2820 ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
2821 },
2822 })
2823 .await?;
2824
2825 Ok(())
2826 }
2827 }
2828 })
2829 }
2830
2831 pub fn show(&self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
2832 self.send_job(|git_repo, _cx| async move {
2833 match git_repo {
2834 RepositoryState::Local(git_repository) => git_repository.show(commit).await,
2835 RepositoryState::Remote {
2836 project_id,
2837 client,
2838 work_directory_id,
2839 } => {
2840 let resp = client
2841 .request(proto::GitShow {
2842 project_id: project_id.0,
2843 work_directory_id: work_directory_id.to_proto(),
2844 commit,
2845 })
2846 .await?;
2847
2848 Ok(CommitDetails {
2849 sha: resp.sha.into(),
2850 message: resp.message.into(),
2851 commit_timestamp: resp.commit_timestamp,
2852 committer_email: resp.committer_email.into(),
2853 committer_name: resp.committer_name.into(),
2854 })
2855 }
2856 }
2857 })
2858 }
2859
2860 fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
2861 Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
2862 }
2863
2864 pub fn stage_entries(
2865 &self,
2866 entries: Vec<RepoPath>,
2867 cx: &mut Context<Self>,
2868 ) -> Task<anyhow::Result<()>> {
2869 if entries.is_empty() {
2870 return Task::ready(Ok(()));
2871 }
2872 let env = self.worktree_environment(cx);
2873
2874 let mut save_futures = Vec::new();
2875 if let Some(buffer_store) = self.buffer_store(cx) {
2876 buffer_store.update(cx, |buffer_store, cx| {
2877 for path in &entries {
2878 let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
2879 continue;
2880 };
2881 if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
2882 if buffer
2883 .read(cx)
2884 .file()
2885 .map_or(false, |file| file.disk_state().exists())
2886 {
2887 save_futures.push(buffer_store.save_buffer(buffer, cx));
2888 }
2889 }
2890 }
2891 })
2892 }
2893
2894 cx.spawn(async move |this, cx| {
2895 for save_future in save_futures {
2896 save_future.await?;
2897 }
2898 let env = env.await;
2899
2900 this.update(cx, |this, _| {
2901 this.send_job(|git_repo, _cx| async move {
2902 match git_repo {
2903 RepositoryState::Local(repo) => repo.stage_paths(entries, env).await,
2904 RepositoryState::Remote {
2905 project_id,
2906 client,
2907 work_directory_id,
2908 } => {
2909 client
2910 .request(proto::Stage {
2911 project_id: project_id.0,
2912 work_directory_id: work_directory_id.to_proto(),
2913 paths: entries
2914 .into_iter()
2915 .map(|repo_path| repo_path.as_ref().to_proto())
2916 .collect(),
2917 })
2918 .await
2919 .context("sending stage request")?;
2920
2921 Ok(())
2922 }
2923 }
2924 })
2925 })?
2926 .await??;
2927
2928 Ok(())
2929 })
2930 }
2931
2932 pub fn unstage_entries(
2933 &self,
2934 entries: Vec<RepoPath>,
2935 cx: &mut Context<Self>,
2936 ) -> Task<anyhow::Result<()>> {
2937 if entries.is_empty() {
2938 return Task::ready(Ok(()));
2939 }
2940 let env = self.worktree_environment(cx);
2941
2942 let mut save_futures = Vec::new();
2943 if let Some(buffer_store) = self.buffer_store(cx) {
2944 buffer_store.update(cx, |buffer_store, cx| {
2945 for path in &entries {
2946 let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
2947 continue;
2948 };
2949 if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
2950 if buffer
2951 .read(cx)
2952 .file()
2953 .map_or(false, |file| file.disk_state().exists())
2954 {
2955 save_futures.push(buffer_store.save_buffer(buffer, cx));
2956 }
2957 }
2958 }
2959 })
2960 }
2961
2962 cx.spawn(async move |this, cx| {
2963 for save_future in save_futures {
2964 save_future.await?;
2965 }
2966 let env = env.await;
2967
2968 this.update(cx, |this, _| {
2969 this.send_job(|git_repo, _cx| async move {
2970 match git_repo {
2971 RepositoryState::Local(repo) => repo.unstage_paths(entries, env).await,
2972 RepositoryState::Remote {
2973 project_id,
2974 client,
2975 work_directory_id,
2976 } => {
2977 client
2978 .request(proto::Unstage {
2979 project_id: project_id.0,
2980 work_directory_id: work_directory_id.to_proto(),
2981 paths: entries
2982 .into_iter()
2983 .map(|repo_path| repo_path.as_ref().to_proto())
2984 .collect(),
2985 })
2986 .await
2987 .context("sending unstage request")?;
2988
2989 Ok(())
2990 }
2991 }
2992 })
2993 })?
2994 .await??;
2995
2996 Ok(())
2997 })
2998 }
2999
3000 pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
3001 let to_stage = self
3002 .repository_entry
3003 .status()
3004 .filter(|entry| !entry.status.staging().is_fully_staged())
3005 .map(|entry| entry.repo_path.clone())
3006 .collect();
3007 self.stage_entries(to_stage, cx)
3008 }
3009
3010 pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
3011 let to_unstage = self
3012 .repository_entry
3013 .status()
3014 .filter(|entry| entry.status.staging().has_staged())
3015 .map(|entry| entry.repo_path.clone())
3016 .collect();
3017 self.unstage_entries(to_unstage, cx)
3018 }
3019
3020 /// Get a count of all entries in the active repository, including
3021 /// untracked files.
3022 pub fn entry_count(&self) -> usize {
3023 self.repository_entry.status_len()
3024 }
3025
3026 fn worktree_environment(
3027 &self,
3028 cx: &mut App,
3029 ) -> impl Future<Output = HashMap<String, String>> + 'static {
3030 let task = self.project_environment.as_ref().and_then(|env| {
3031 env.update(cx, |env, cx| {
3032 env.get_environment(
3033 self.worktree_id,
3034 Some(
3035 self.repository_entry
3036 .work_directory_abs_path
3037 .as_path()
3038 .into(),
3039 ),
3040 cx,
3041 )
3042 })
3043 .ok()
3044 });
3045 async move { OptionFuture::from(task).await.flatten().unwrap_or_default() }
3046 }
3047
3048 pub fn commit(
3049 &self,
3050 message: SharedString,
3051 name_and_email: Option<(SharedString, SharedString)>,
3052 cx: &mut App,
3053 ) -> oneshot::Receiver<Result<()>> {
3054 let env = self.worktree_environment(cx);
3055 self.send_job(|git_repo, _cx| async move {
3056 match git_repo {
3057 RepositoryState::Local(repo) => {
3058 let env = env.await;
3059 repo.commit(message, name_and_email, env).await
3060 }
3061 RepositoryState::Remote {
3062 project_id,
3063 client,
3064 work_directory_id,
3065 } => {
3066 let (name, email) = name_and_email.unzip();
3067 client
3068 .request(proto::Commit {
3069 project_id: project_id.0,
3070 work_directory_id: work_directory_id.to_proto(),
3071 message: String::from(message),
3072 name: name.map(String::from),
3073 email: email.map(String::from),
3074 })
3075 .await
3076 .context("sending commit request")?;
3077
3078 Ok(())
3079 }
3080 }
3081 })
3082 }
3083
3084 pub fn fetch(
3085 &mut self,
3086 askpass: AskPassDelegate,
3087 cx: &mut App,
3088 ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
3089 let executor = cx.background_executor().clone();
3090 let askpass_delegates = self.askpass_delegates.clone();
3091 let askpass_id = util::post_inc(&mut self.latest_askpass_id);
3092 let env = self.worktree_environment(cx);
3093
3094 self.send_job(move |git_repo, cx| async move {
3095 match git_repo {
3096 RepositoryState::Local(git_repository) => {
3097 let askpass = AskPassSession::new(&executor, askpass).await?;
3098 let env = env.await;
3099 git_repository.fetch(askpass, env, cx).await
3100 }
3101 RepositoryState::Remote {
3102 project_id,
3103 client,
3104 work_directory_id,
3105 } => {
3106 askpass_delegates.lock().insert(askpass_id, askpass);
3107 let _defer = util::defer(|| {
3108 let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
3109 debug_assert!(askpass_delegate.is_some());
3110 });
3111
3112 let response = client
3113 .request(proto::Fetch {
3114 project_id: project_id.0,
3115 work_directory_id: work_directory_id.to_proto(),
3116 askpass_id,
3117 })
3118 .await
3119 .context("sending fetch request")?;
3120
3121 Ok(RemoteCommandOutput {
3122 stdout: response.stdout,
3123 stderr: response.stderr,
3124 })
3125 }
3126 }
3127 })
3128 }
3129
3130 pub fn push(
3131 &mut self,
3132 branch: SharedString,
3133 remote: SharedString,
3134 options: Option<PushOptions>,
3135 askpass: AskPassDelegate,
3136 cx: &mut App,
3137 ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
3138 let executor = cx.background_executor().clone();
3139 let askpass_delegates = self.askpass_delegates.clone();
3140 let askpass_id = util::post_inc(&mut self.latest_askpass_id);
3141 let env = self.worktree_environment(cx);
3142
3143 self.send_job(move |git_repo, cx| async move {
3144 match git_repo {
3145 RepositoryState::Local(git_repository) => {
3146 let env = env.await;
3147 let askpass = AskPassSession::new(&executor, askpass).await?;
3148 git_repository
3149 .push(
3150 branch.to_string(),
3151 remote.to_string(),
3152 options,
3153 askpass,
3154 env,
3155 cx,
3156 )
3157 .await
3158 }
3159 RepositoryState::Remote {
3160 project_id,
3161 client,
3162 work_directory_id,
3163 } => {
3164 askpass_delegates.lock().insert(askpass_id, askpass);
3165 let _defer = util::defer(|| {
3166 let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
3167 debug_assert!(askpass_delegate.is_some());
3168 });
3169 let response = client
3170 .request(proto::Push {
3171 project_id: project_id.0,
3172 work_directory_id: work_directory_id.to_proto(),
3173 askpass_id,
3174 branch_name: branch.to_string(),
3175 remote_name: remote.to_string(),
3176 options: options.map(|options| match options {
3177 PushOptions::Force => proto::push::PushOptions::Force,
3178 PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
3179 } as i32),
3180 })
3181 .await
3182 .context("sending push request")?;
3183
3184 Ok(RemoteCommandOutput {
3185 stdout: response.stdout,
3186 stderr: response.stderr,
3187 })
3188 }
3189 }
3190 })
3191 }
3192
3193 pub fn pull(
3194 &mut self,
3195 branch: SharedString,
3196 remote: SharedString,
3197 askpass: AskPassDelegate,
3198 cx: &mut App,
3199 ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
3200 let executor = cx.background_executor().clone();
3201 let askpass_delegates = self.askpass_delegates.clone();
3202 let askpass_id = util::post_inc(&mut self.latest_askpass_id);
3203 let env = self.worktree_environment(cx);
3204
3205 self.send_job(move |git_repo, cx| async move {
3206 match git_repo {
3207 RepositoryState::Local(git_repository) => {
3208 let askpass = AskPassSession::new(&executor, askpass).await?;
3209 let env = env.await;
3210 git_repository
3211 .pull(branch.to_string(), remote.to_string(), askpass, env, cx)
3212 .await
3213 }
3214 RepositoryState::Remote {
3215 project_id,
3216 client,
3217 work_directory_id,
3218 } => {
3219 askpass_delegates.lock().insert(askpass_id, askpass);
3220 let _defer = util::defer(|| {
3221 let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
3222 debug_assert!(askpass_delegate.is_some());
3223 });
3224 let response = client
3225 .request(proto::Pull {
3226 project_id: project_id.0,
3227 work_directory_id: work_directory_id.to_proto(),
3228 askpass_id,
3229 branch_name: branch.to_string(),
3230 remote_name: remote.to_string(),
3231 })
3232 .await
3233 .context("sending pull request")?;
3234
3235 Ok(RemoteCommandOutput {
3236 stdout: response.stdout,
3237 stderr: response.stderr,
3238 })
3239 }
3240 }
3241 })
3242 }
3243
3244 fn spawn_set_index_text_job(
3245 &self,
3246 path: RepoPath,
3247 content: Option<String>,
3248 cx: &mut App,
3249 ) -> oneshot::Receiver<anyhow::Result<()>> {
3250 let env = self.worktree_environment(cx);
3251
3252 self.send_keyed_job(
3253 Some(GitJobKey::WriteIndex(path.clone())),
3254 |git_repo, _cx| async {
3255 match git_repo {
3256 RepositoryState::Local(repo) => {
3257 repo.set_index_text(path, content, env.await).await
3258 }
3259 RepositoryState::Remote {
3260 project_id,
3261 client,
3262 work_directory_id,
3263 } => {
3264 client
3265 .request(proto::SetIndexText {
3266 project_id: project_id.0,
3267 work_directory_id: work_directory_id.to_proto(),
3268 path: path.as_ref().to_proto(),
3269 text: content,
3270 })
3271 .await?;
3272 Ok(())
3273 }
3274 }
3275 },
3276 )
3277 }
3278
3279 pub fn get_remotes(
3280 &self,
3281 branch_name: Option<String>,
3282 ) -> oneshot::Receiver<Result<Vec<Remote>>> {
3283 self.send_job(|repo, _cx| async move {
3284 match repo {
3285 RepositoryState::Local(git_repository) => {
3286 git_repository.get_remotes(branch_name).await
3287 }
3288 RepositoryState::Remote {
3289 project_id,
3290 client,
3291 work_directory_id,
3292 } => {
3293 let response = client
3294 .request(proto::GetRemotes {
3295 project_id: project_id.0,
3296 work_directory_id: work_directory_id.to_proto(),
3297 branch_name,
3298 })
3299 .await?;
3300
3301 let remotes = response
3302 .remotes
3303 .into_iter()
3304 .map(|remotes| git::repository::Remote {
3305 name: remotes.name.into(),
3306 })
3307 .collect();
3308
3309 Ok(remotes)
3310 }
3311 }
3312 })
3313 }
3314
3315 pub fn branch(&self) -> Option<&Branch> {
3316 self.repository_entry.branch()
3317 }
3318
3319 pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
3320 self.send_job(|repo, cx| async move {
3321 match repo {
3322 RepositoryState::Local(git_repository) => {
3323 let git_repository = git_repository.clone();
3324 cx.background_spawn(async move { git_repository.branches().await })
3325 .await
3326 }
3327 RepositoryState::Remote {
3328 project_id,
3329 client,
3330 work_directory_id,
3331 } => {
3332 let response = client
3333 .request(proto::GitGetBranches {
3334 project_id: project_id.0,
3335 work_directory_id: work_directory_id.to_proto(),
3336 })
3337 .await?;
3338
3339 let branches = response
3340 .branches
3341 .into_iter()
3342 .map(|branch| worktree::proto_to_branch(&branch))
3343 .collect();
3344
3345 Ok(branches)
3346 }
3347 }
3348 })
3349 }
3350
3351 pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
3352 self.send_job(|repo, _cx| async move {
3353 match repo {
3354 RepositoryState::Local(git_repository) => git_repository.diff(diff_type).await,
3355 RepositoryState::Remote {
3356 project_id,
3357 client,
3358 work_directory_id,
3359 ..
3360 } => {
3361 let response = client
3362 .request(proto::GitDiff {
3363 project_id: project_id.0,
3364 work_directory_id: work_directory_id.to_proto(),
3365 diff_type: match diff_type {
3366 DiffType::HeadToIndex => {
3367 proto::git_diff::DiffType::HeadToIndex.into()
3368 }
3369 DiffType::HeadToWorktree => {
3370 proto::git_diff::DiffType::HeadToWorktree.into()
3371 }
3372 },
3373 })
3374 .await?;
3375
3376 Ok(response.diff)
3377 }
3378 }
3379 })
3380 }
3381
3382 pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
3383 self.send_job(|repo, _cx| async move {
3384 match repo {
3385 RepositoryState::Local(git_repository) => {
3386 git_repository.create_branch(branch_name).await
3387 }
3388 RepositoryState::Remote {
3389 project_id,
3390 client,
3391 work_directory_id,
3392 } => {
3393 client
3394 .request(proto::GitCreateBranch {
3395 project_id: project_id.0,
3396 work_directory_id: work_directory_id.to_proto(),
3397 branch_name,
3398 })
3399 .await?;
3400
3401 Ok(())
3402 }
3403 }
3404 })
3405 }
3406
3407 pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
3408 self.send_job(|repo, _cx| async move {
3409 match repo {
3410 RepositoryState::Local(git_repository) => {
3411 git_repository.change_branch(branch_name).await
3412 }
3413 RepositoryState::Remote {
3414 project_id,
3415 client,
3416 work_directory_id,
3417 } => {
3418 client
3419 .request(proto::GitChangeBranch {
3420 project_id: project_id.0,
3421 work_directory_id: work_directory_id.to_proto(),
3422 branch_name,
3423 })
3424 .await?;
3425
3426 Ok(())
3427 }
3428 }
3429 })
3430 }
3431
3432 pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
3433 self.send_job(|repo, _cx| async move {
3434 match repo {
3435 RepositoryState::Local(git_repository) => {
3436 git_repository.check_for_pushed_commit().await
3437 }
3438 RepositoryState::Remote {
3439 project_id,
3440 client,
3441 work_directory_id,
3442 } => {
3443 let response = client
3444 .request(proto::CheckForPushedCommits {
3445 project_id: project_id.0,
3446 work_directory_id: work_directory_id.to_proto(),
3447 })
3448 .await?;
3449
3450 let branches = response.pushed_to.into_iter().map(Into::into).collect();
3451
3452 Ok(branches)
3453 }
3454 }
3455 })
3456 }
3457
3458 pub fn checkpoint(&self) -> oneshot::Receiver<Result<GitRepositoryCheckpoint>> {
3459 self.send_job(|repo, _cx| async move {
3460 match repo {
3461 RepositoryState::Local(git_repository) => git_repository.checkpoint().await,
3462 RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3463 }
3464 })
3465 }
3466
3467 pub fn restore_checkpoint(
3468 &self,
3469 checkpoint: GitRepositoryCheckpoint,
3470 ) -> oneshot::Receiver<Result<()>> {
3471 self.send_job(move |repo, _cx| async move {
3472 match repo {
3473 RepositoryState::Local(git_repository) => {
3474 git_repository.restore_checkpoint(checkpoint).await
3475 }
3476 RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3477 }
3478 })
3479 }
3480
3481 pub(crate) fn apply_remote_update(&mut self, update: proto::UpdateRepository) -> Result<()> {
3482 let conflicted_paths = TreeSet::from_ordered_entries(
3483 update
3484 .current_merge_conflicts
3485 .into_iter()
3486 .map(|path| RepoPath(Path::new(&path).into())),
3487 );
3488 self.repository_entry.current_branch = update.branch_summary.as_ref().map(proto_to_branch);
3489 self.repository_entry.current_merge_conflicts = conflicted_paths;
3490
3491 let edits = update
3492 .removed_statuses
3493 .into_iter()
3494 .map(|path| sum_tree::Edit::Remove(PathKey(FromProto::from_proto(path))))
3495 .chain(
3496 update
3497 .updated_statuses
3498 .into_iter()
3499 .filter_map(|updated_status| {
3500 Some(sum_tree::Edit::Insert(updated_status.try_into().log_err()?))
3501 }),
3502 )
3503 .collect::<Vec<_>>();
3504 self.repository_entry.statuses_by_path.edit(edits, &());
3505 Ok(())
3506 }
3507
3508 pub fn compare_checkpoints(
3509 &self,
3510 left: GitRepositoryCheckpoint,
3511 right: GitRepositoryCheckpoint,
3512 ) -> oneshot::Receiver<Result<bool>> {
3513 self.send_job(move |repo, _cx| async move {
3514 match repo {
3515 RepositoryState::Local(git_repository) => {
3516 git_repository.compare_checkpoints(left, right).await
3517 }
3518 RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3519 }
3520 })
3521 }
3522
3523 pub fn delete_checkpoint(
3524 &self,
3525 checkpoint: GitRepositoryCheckpoint,
3526 ) -> oneshot::Receiver<Result<()>> {
3527 self.send_job(move |repo, _cx| async move {
3528 match repo {
3529 RepositoryState::Local(git_repository) => {
3530 git_repository.delete_checkpoint(checkpoint).await
3531 }
3532 RepositoryState::Remote { .. } => Err(anyhow!("not implemented yet")),
3533 }
3534 })
3535 }
3536}
3537
3538fn get_permalink_in_rust_registry_src(
3539 provider_registry: Arc<GitHostingProviderRegistry>,
3540 path: PathBuf,
3541 selection: Range<u32>,
3542) -> Result<url::Url> {
3543 #[derive(Deserialize)]
3544 struct CargoVcsGit {
3545 sha1: String,
3546 }
3547
3548 #[derive(Deserialize)]
3549 struct CargoVcsInfo {
3550 git: CargoVcsGit,
3551 path_in_vcs: String,
3552 }
3553
3554 #[derive(Deserialize)]
3555 struct CargoPackage {
3556 repository: String,
3557 }
3558
3559 #[derive(Deserialize)]
3560 struct CargoToml {
3561 package: CargoPackage,
3562 }
3563
3564 let Some((dir, cargo_vcs_info_json)) = path.ancestors().skip(1).find_map(|dir| {
3565 let json = std::fs::read_to_string(dir.join(".cargo_vcs_info.json")).ok()?;
3566 Some((dir, json))
3567 }) else {
3568 bail!("No .cargo_vcs_info.json found in parent directories")
3569 };
3570 let cargo_vcs_info = serde_json::from_str::<CargoVcsInfo>(&cargo_vcs_info_json)?;
3571 let cargo_toml = std::fs::read_to_string(dir.join("Cargo.toml"))?;
3572 let manifest = toml::from_str::<CargoToml>(&cargo_toml)?;
3573 let (provider, remote) = parse_git_remote_url(provider_registry, &manifest.package.repository)
3574 .ok_or_else(|| anyhow!("Failed to parse package.repository field of manifest"))?;
3575 let path = PathBuf::from(cargo_vcs_info.path_in_vcs).join(path.strip_prefix(dir).unwrap());
3576 let permalink = provider.build_permalink(
3577 remote,
3578 BuildPermalinkParams {
3579 sha: &cargo_vcs_info.git.sha1,
3580 path: &path.to_string_lossy(),
3581 selection: Some(selection),
3582 },
3583 );
3584 Ok(permalink)
3585}
3586
3587fn serialize_blame_buffer_response(blame: Option<git::blame::Blame>) -> proto::BlameBufferResponse {
3588 let Some(blame) = blame else {
3589 return proto::BlameBufferResponse {
3590 blame_response: None,
3591 };
3592 };
3593
3594 let entries = blame
3595 .entries
3596 .into_iter()
3597 .map(|entry| proto::BlameEntry {
3598 sha: entry.sha.as_bytes().into(),
3599 start_line: entry.range.start,
3600 end_line: entry.range.end,
3601 original_line_number: entry.original_line_number,
3602 author: entry.author.clone(),
3603 author_mail: entry.author_mail.clone(),
3604 author_time: entry.author_time,
3605 author_tz: entry.author_tz.clone(),
3606 committer: entry.committer_name.clone(),
3607 committer_mail: entry.committer_email.clone(),
3608 committer_time: entry.committer_time,
3609 committer_tz: entry.committer_tz.clone(),
3610 summary: entry.summary.clone(),
3611 previous: entry.previous.clone(),
3612 filename: entry.filename.clone(),
3613 })
3614 .collect::<Vec<_>>();
3615
3616 let messages = blame
3617 .messages
3618 .into_iter()
3619 .map(|(oid, message)| proto::CommitMessage {
3620 oid: oid.as_bytes().into(),
3621 message,
3622 })
3623 .collect::<Vec<_>>();
3624
3625 proto::BlameBufferResponse {
3626 blame_response: Some(proto::blame_buffer_response::BlameResponse {
3627 entries,
3628 messages,
3629 remote_url: blame.remote_url,
3630 }),
3631 }
3632}
3633
3634fn deserialize_blame_buffer_response(
3635 response: proto::BlameBufferResponse,
3636) -> Option<git::blame::Blame> {
3637 let response = response.blame_response?;
3638 let entries = response
3639 .entries
3640 .into_iter()
3641 .filter_map(|entry| {
3642 Some(git::blame::BlameEntry {
3643 sha: git::Oid::from_bytes(&entry.sha).ok()?,
3644 range: entry.start_line..entry.end_line,
3645 original_line_number: entry.original_line_number,
3646 committer_name: entry.committer,
3647 committer_time: entry.committer_time,
3648 committer_tz: entry.committer_tz,
3649 committer_email: entry.committer_mail,
3650 author: entry.author,
3651 author_mail: entry.author_mail,
3652 author_time: entry.author_time,
3653 author_tz: entry.author_tz,
3654 summary: entry.summary,
3655 previous: entry.previous,
3656 filename: entry.filename,
3657 })
3658 })
3659 .collect::<Vec<_>>();
3660
3661 let messages = response
3662 .messages
3663 .into_iter()
3664 .filter_map(|message| Some((git::Oid::from_bytes(&message.oid).ok()?, message.message)))
3665 .collect::<HashMap<_, _>>();
3666
3667 Some(Blame {
3668 entries,
3669 messages,
3670 remote_url: response.remote_url,
3671 })
3672}