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