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