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