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