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