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