1use crate::{
2 buffer_store::{BufferStore, BufferStoreEvent},
3 worktree_store::{WorktreeStore, WorktreeStoreEvent},
4 Project, ProjectEnvironment, ProjectItem, ProjectPath,
5};
6use anyhow::{Context as _, Result};
7use askpass::{AskPassDelegate, AskPassSession};
8use buffer_diff::BufferDiffEvent;
9use client::ProjectId;
10use collections::HashMap;
11use fs::Fs;
12use futures::{
13 channel::{mpsc, oneshot},
14 future::OptionFuture,
15 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, LanguageRegistry};
30use parking_lot::Mutex;
31use rpc::{
32 proto::{self, git_reset, ToProto},
33 AnyProtoClient, TypedEnvelope,
34};
35use settings::WorktreeId;
36use std::{
37 collections::VecDeque,
38 future::Future,
39 path::{Path, PathBuf},
40 sync::Arc,
41};
42
43use text::BufferId;
44use util::{debug_panic, maybe, ResultExt};
45use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry, WorkDirectory};
46
47pub struct GitStore {
48 state: GitStoreState,
49 buffer_store: Entity<BufferStore>,
50 repositories: Vec<Entity<Repository>>,
51 active_index: Option<usize>,
52 update_sender: mpsc::UnboundedSender<GitJob>,
53 _subscriptions: [Subscription; 2],
54}
55
56enum GitStoreState {
57 Local {
58 client: AnyProtoClient,
59 environment: Entity<ProjectEnvironment>,
60 fs: Arc<dyn Fs>,
61 },
62 Ssh {
63 environment: Entity<ProjectEnvironment>,
64 upstream_client: AnyProtoClient,
65 project_id: ProjectId,
66 },
67 Remote {
68 upstream_client: AnyProtoClient,
69 project_id: ProjectId,
70 },
71}
72
73pub struct Repository {
74 commit_message_buffer: Option<Entity<Buffer>>,
75 git_store: WeakEntity<GitStore>,
76 project_environment: Option<WeakEntity<ProjectEnvironment>>,
77 pub worktree_id: WorktreeId,
78 pub repository_entry: RepositoryEntry,
79 pub dot_git_abs_path: PathBuf,
80 pub worktree_abs_path: Arc<Path>,
81 pub is_from_single_file_worktree: bool,
82 pub git_repo: GitRepo,
83 pub merge_message: Option<String>,
84 job_sender: mpsc::UnboundedSender<GitJob>,
85 askpass_delegates: Arc<Mutex<HashMap<u64, AskPassDelegate>>>,
86 latest_askpass_id: u64,
87}
88
89#[derive(Clone)]
90pub enum GitRepo {
91 Local(Arc<dyn GitRepository>),
92 Remote {
93 project_id: ProjectId,
94 client: AnyProtoClient,
95 worktree_id: WorktreeId,
96 work_directory_id: ProjectEntryId,
97 },
98}
99
100#[derive(Debug)]
101pub enum GitEvent {
102 ActiveRepositoryChanged,
103 FileSystemUpdated,
104 GitStateUpdated,
105 IndexWriteError(anyhow::Error),
106}
107
108struct GitJob {
109 job: Box<dyn FnOnce(&mut AsyncApp) -> Task<()>>,
110 key: Option<GitJobKey>,
111}
112
113#[derive(PartialEq, Eq)]
114enum GitJobKey {
115 WriteIndex(RepoPath),
116}
117
118impl EventEmitter<GitEvent> for GitStore {}
119
120impl GitStore {
121 pub fn local(
122 worktree_store: &Entity<WorktreeStore>,
123 buffer_store: Entity<BufferStore>,
124 environment: Entity<ProjectEnvironment>,
125 fs: Arc<dyn Fs>,
126 client: AnyProtoClient,
127 cx: &mut Context<'_, Self>,
128 ) -> Self {
129 let update_sender = Self::spawn_git_worker(cx);
130 let _subscriptions = [
131 cx.subscribe(worktree_store, Self::on_worktree_store_event),
132 cx.subscribe(&buffer_store, Self::on_buffer_store_event),
133 ];
134
135 let state = GitStoreState::Local {
136 client,
137 environment,
138 fs,
139 };
140
141 GitStore {
142 state,
143 buffer_store,
144 repositories: Vec::new(),
145 active_index: None,
146 update_sender,
147 _subscriptions,
148 }
149 }
150
151 pub fn remote(
152 worktree_store: &Entity<WorktreeStore>,
153 buffer_store: Entity<BufferStore>,
154 upstream_client: AnyProtoClient,
155 project_id: ProjectId,
156 cx: &mut Context<'_, Self>,
157 ) -> Self {
158 let update_sender = Self::spawn_git_worker(cx);
159 let _subscriptions = [
160 cx.subscribe(worktree_store, Self::on_worktree_store_event),
161 cx.subscribe(&buffer_store, Self::on_buffer_store_event),
162 ];
163
164 let state = GitStoreState::Remote {
165 upstream_client,
166 project_id,
167 };
168
169 GitStore {
170 state,
171 buffer_store,
172 repositories: Vec::new(),
173 active_index: None,
174 update_sender,
175 _subscriptions,
176 }
177 }
178
179 pub fn ssh(
180 worktree_store: &Entity<WorktreeStore>,
181 buffer_store: Entity<BufferStore>,
182 environment: Entity<ProjectEnvironment>,
183 upstream_client: AnyProtoClient,
184 project_id: ProjectId,
185 cx: &mut Context<'_, Self>,
186 ) -> Self {
187 let update_sender = Self::spawn_git_worker(cx);
188 let _subscriptions = [
189 cx.subscribe(worktree_store, Self::on_worktree_store_event),
190 cx.subscribe(&buffer_store, Self::on_buffer_store_event),
191 ];
192
193 let state = GitStoreState::Ssh {
194 upstream_client,
195 project_id,
196 environment,
197 };
198
199 GitStore {
200 state,
201 buffer_store,
202 repositories: Vec::new(),
203 active_index: None,
204 update_sender,
205 _subscriptions,
206 }
207 }
208
209 pub fn init(client: &AnyProtoClient) {
210 client.add_entity_request_handler(Self::handle_get_remotes);
211 client.add_entity_request_handler(Self::handle_get_branches);
212 client.add_entity_request_handler(Self::handle_change_branch);
213 client.add_entity_request_handler(Self::handle_create_branch);
214 client.add_entity_request_handler(Self::handle_git_init);
215 client.add_entity_request_handler(Self::handle_push);
216 client.add_entity_request_handler(Self::handle_pull);
217 client.add_entity_request_handler(Self::handle_fetch);
218 client.add_entity_request_handler(Self::handle_stage);
219 client.add_entity_request_handler(Self::handle_unstage);
220 client.add_entity_request_handler(Self::handle_commit);
221 client.add_entity_request_handler(Self::handle_reset);
222 client.add_entity_request_handler(Self::handle_show);
223 client.add_entity_request_handler(Self::handle_checkout_files);
224 client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
225 client.add_entity_request_handler(Self::handle_set_index_text);
226 client.add_entity_request_handler(Self::handle_askpass);
227 client.add_entity_request_handler(Self::handle_check_for_pushed_commits);
228 client.add_entity_request_handler(Self::handle_git_diff);
229 }
230
231 pub fn active_repository(&self) -> Option<Entity<Repository>> {
232 self.active_index
233 .map(|index| self.repositories[index].clone())
234 }
235
236 fn client(&self) -> AnyProtoClient {
237 match &self.state {
238 GitStoreState::Local { client, .. } => client.clone(),
239 GitStoreState::Ssh {
240 upstream_client, ..
241 } => upstream_client.clone(),
242 GitStoreState::Remote {
243 upstream_client, ..
244 } => upstream_client.clone(),
245 }
246 }
247
248 fn project_environment(&self) -> Option<Entity<ProjectEnvironment>> {
249 match &self.state {
250 GitStoreState::Local { environment, .. } => Some(environment.clone()),
251 GitStoreState::Ssh { environment, .. } => Some(environment.clone()),
252 GitStoreState::Remote { .. } => None,
253 }
254 }
255
256 fn project_id(&self) -> Option<ProjectId> {
257 match &self.state {
258 GitStoreState::Local { .. } => None,
259 GitStoreState::Ssh { project_id, .. } => Some(*project_id),
260 GitStoreState::Remote { project_id, .. } => Some(*project_id),
261 }
262 }
263
264 fn on_worktree_store_event(
265 &mut self,
266 worktree_store: Entity<WorktreeStore>,
267 event: &WorktreeStoreEvent,
268 cx: &mut Context<'_, Self>,
269 ) {
270 let mut new_repositories = Vec::new();
271 let mut new_active_index = None;
272 let this = cx.weak_entity();
273 let client = self.client();
274 let project_id = self.project_id();
275
276 worktree_store.update(cx, |worktree_store, cx| {
277 for worktree in worktree_store.worktrees() {
278 worktree.update(cx, |worktree, cx| {
279 let snapshot = worktree.snapshot();
280 for repo in snapshot.repositories().iter() {
281 let git_data = worktree
282 .as_local()
283 .and_then(|local_worktree| local_worktree.get_local_repo(repo))
284 .map(|local_repo| {
285 (
286 GitRepo::Local(local_repo.repo().clone()),
287 local_repo.merge_message.clone(),
288 )
289 })
290 .or_else(|| {
291 let client = client.clone();
292 let project_id = project_id?;
293 Some((
294 GitRepo::Remote {
295 project_id,
296 client,
297 worktree_id: worktree.id(),
298 work_directory_id: repo.work_directory_id(),
299 },
300 None,
301 ))
302 });
303 let Some((git_repo, merge_message)) = git_data else {
304 continue;
305 };
306 let worktree_id = worktree.id();
307 let existing =
308 self.repositories
309 .iter()
310 .enumerate()
311 .find(|(_, existing_handle)| {
312 existing_handle.read(cx).id()
313 == (worktree_id, repo.work_directory_id())
314 });
315 let handle = if let Some((index, handle)) = existing {
316 if self.active_index == Some(index) {
317 new_active_index = Some(new_repositories.len());
318 }
319 // Update the statuses and merge message but keep everything else.
320 let existing_handle = handle.clone();
321 existing_handle.update(cx, |existing_handle, cx| {
322 existing_handle.repository_entry = repo.clone();
323 if matches!(git_repo, GitRepo::Local { .. })
324 && existing_handle.merge_message != merge_message
325 {
326 if let (Some(merge_message), Some(buffer)) =
327 (&merge_message, &existing_handle.commit_message_buffer)
328 {
329 buffer.update(cx, |buffer, cx| {
330 if buffer.is_empty() {
331 buffer.set_text(merge_message.as_str(), cx);
332 }
333 })
334 }
335 existing_handle.merge_message = merge_message;
336 }
337 });
338 existing_handle
339 } else {
340 let environment = self.project_environment();
341 cx.new(|_| Repository {
342 project_environment: environment
343 .as_ref()
344 .map(|env| env.downgrade()),
345 git_store: this.clone(),
346 worktree_id,
347 askpass_delegates: Default::default(),
348 latest_askpass_id: 0,
349 repository_entry: repo.clone(),
350 dot_git_abs_path: worktree.dot_git_abs_path(&repo.work_directory),
351 worktree_abs_path: worktree.abs_path(),
352 is_from_single_file_worktree: worktree.is_single_file(),
353 git_repo,
354 job_sender: self.update_sender.clone(),
355 merge_message,
356 commit_message_buffer: None,
357 })
358 };
359 new_repositories.push(handle);
360 }
361 })
362 }
363 });
364
365 if new_active_index == None && new_repositories.len() > 0 {
366 new_active_index = Some(0);
367 }
368
369 self.repositories = new_repositories;
370 self.active_index = new_active_index;
371
372 match event {
373 WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
374 cx.emit(GitEvent::GitStateUpdated);
375 }
376 _ => {
377 cx.emit(GitEvent::FileSystemUpdated);
378 }
379 }
380 }
381
382 fn on_buffer_store_event(
383 &mut self,
384 _: Entity<BufferStore>,
385 event: &BufferStoreEvent,
386 cx: &mut Context<'_, Self>,
387 ) {
388 if let BufferStoreEvent::BufferDiffAdded(diff) = event {
389 cx.subscribe(diff, Self::on_buffer_diff_event).detach();
390 }
391 }
392
393 fn on_buffer_diff_event(
394 this: &mut GitStore,
395 diff: Entity<buffer_diff::BufferDiff>,
396 event: &BufferDiffEvent,
397 cx: &mut Context<'_, GitStore>,
398 ) {
399 if let BufferDiffEvent::HunksStagedOrUnstaged(new_index_text) = event {
400 let buffer_id = diff.read(cx).buffer_id;
401 if let Some((repo, path)) = this.repository_and_path_for_buffer_id(buffer_id, cx) {
402 let recv = repo.update(cx, |repo, cx| {
403 repo.set_index_text(
404 path,
405 new_index_text.as_ref().map(|rope| rope.to_string()),
406 cx,
407 )
408 });
409 let diff = diff.downgrade();
410 cx.spawn(|this, mut cx| async move {
411 if let Some(result) = cx.background_spawn(async move { recv.await.ok() }).await
412 {
413 if let Err(error) = result {
414 diff.update(&mut cx, |diff, cx| {
415 diff.clear_pending_hunks(cx);
416 })
417 .ok();
418 this.update(&mut cx, |_, cx| cx.emit(GitEvent::IndexWriteError(error)))
419 .ok();
420 }
421 }
422 })
423 .detach();
424 }
425 }
426 }
427
428 pub fn all_repositories(&self) -> Vec<Entity<Repository>> {
429 self.repositories.clone()
430 }
431
432 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
433 let (repo, path) = self.repository_and_path_for_buffer_id(buffer_id, cx)?;
434 let status = repo.read(cx).repository_entry.status_for_path(&path)?;
435 Some(status.status)
436 }
437
438 fn repository_and_path_for_buffer_id(
439 &self,
440 buffer_id: BufferId,
441 cx: &App,
442 ) -> Option<(Entity<Repository>, RepoPath)> {
443 let buffer = self.buffer_store.read(cx).get(buffer_id)?;
444 let path = buffer.read(cx).project_path(cx)?;
445 let mut result: Option<(Entity<Repository>, RepoPath)> = None;
446 for repo_handle in &self.repositories {
447 let repo = repo_handle.read(cx);
448 if repo.worktree_id == path.worktree_id {
449 if let Ok(relative_path) = repo.repository_entry.relativize(&path.path) {
450 if result
451 .as_ref()
452 .is_none_or(|(result, _)| !repo.contains_sub_repo(result, cx))
453 {
454 result = Some((repo_handle.clone(), relative_path))
455 }
456 }
457 }
458 }
459 result
460 }
461
462 fn spawn_git_worker(cx: &mut Context<'_, GitStore>) -> mpsc::UnboundedSender<GitJob> {
463 let (job_tx, mut job_rx) = mpsc::unbounded::<GitJob>();
464
465 cx.spawn(|_, mut cx| async move {
466 let mut jobs = VecDeque::new();
467 loop {
468 while let Ok(Some(next_job)) = job_rx.try_next() {
469 jobs.push_back(next_job);
470 }
471
472 if let Some(job) = jobs.pop_front() {
473 if let Some(current_key) = &job.key {
474 if jobs
475 .iter()
476 .any(|other_job| other_job.key.as_ref() == Some(current_key))
477 {
478 continue;
479 }
480 }
481 (job.job)(&mut cx).await;
482 } else if let Some(job) = job_rx.next().await {
483 jobs.push_back(job);
484 } else {
485 break;
486 }
487 }
488 })
489 .detach();
490 job_tx
491 }
492
493 pub fn git_init(
494 &self,
495 path: Arc<Path>,
496 fallback_branch_name: String,
497 cx: &App,
498 ) -> Task<Result<()>> {
499 match &self.state {
500 GitStoreState::Local { fs, .. } => {
501 let fs = fs.clone();
502 cx.background_executor()
503 .spawn(async move { fs.git_init(&path, fallback_branch_name) })
504 }
505 GitStoreState::Ssh {
506 upstream_client,
507 project_id,
508 ..
509 }
510 | GitStoreState::Remote {
511 upstream_client,
512 project_id,
513 } => {
514 let client = upstream_client.clone();
515 let project_id = *project_id;
516 cx.background_executor().spawn(async move {
517 client
518 .request(proto::GitInit {
519 project_id: project_id.0,
520 abs_path: path.to_string_lossy().to_string(),
521 fallback_branch_name,
522 })
523 .await?;
524 Ok(())
525 })
526 }
527 }
528 }
529
530 async fn handle_git_init(
531 this: Entity<Self>,
532 envelope: TypedEnvelope<proto::GitInit>,
533 cx: AsyncApp,
534 ) -> Result<proto::Ack> {
535 let path: Arc<Path> = PathBuf::from(envelope.payload.abs_path).into();
536 let name = envelope.payload.fallback_branch_name;
537 cx.update(|cx| this.read(cx).git_init(path, name, cx))?
538 .await?;
539
540 Ok(proto::Ack {})
541 }
542
543 async fn handle_fetch(
544 this: Entity<Self>,
545 envelope: TypedEnvelope<proto::Fetch>,
546 mut cx: AsyncApp,
547 ) -> Result<proto::RemoteMessageResponse> {
548 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
549 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
550 let repository_handle =
551 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
552 let askpass_id = envelope.payload.askpass_id;
553
554 let askpass = make_remote_delegate(
555 this,
556 envelope.payload.project_id,
557 worktree_id,
558 work_directory_id,
559 askpass_id,
560 &mut cx,
561 );
562
563 let remote_output = repository_handle
564 .update(&mut cx, |repository_handle, cx| {
565 repository_handle.fetch(askpass, cx)
566 })?
567 .await??;
568
569 Ok(proto::RemoteMessageResponse {
570 stdout: remote_output.stdout,
571 stderr: remote_output.stderr,
572 })
573 }
574
575 async fn handle_push(
576 this: Entity<Self>,
577 envelope: TypedEnvelope<proto::Push>,
578 mut cx: AsyncApp,
579 ) -> Result<proto::RemoteMessageResponse> {
580 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
581 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
582 let repository_handle =
583 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
584
585 let askpass_id = envelope.payload.askpass_id;
586 let askpass = make_remote_delegate(
587 this,
588 envelope.payload.project_id,
589 worktree_id,
590 work_directory_id,
591 askpass_id,
592 &mut cx,
593 );
594
595 let options = envelope
596 .payload
597 .options
598 .as_ref()
599 .map(|_| match envelope.payload.options() {
600 proto::push::PushOptions::SetUpstream => git::repository::PushOptions::SetUpstream,
601 proto::push::PushOptions::Force => git::repository::PushOptions::Force,
602 });
603
604 let branch_name = envelope.payload.branch_name.into();
605 let remote_name = envelope.payload.remote_name.into();
606
607 let remote_output = repository_handle
608 .update(&mut cx, |repository_handle, cx| {
609 repository_handle.push(branch_name, remote_name, options, askpass, cx)
610 })?
611 .await??;
612 Ok(proto::RemoteMessageResponse {
613 stdout: remote_output.stdout,
614 stderr: remote_output.stderr,
615 })
616 }
617
618 async fn handle_pull(
619 this: Entity<Self>,
620 envelope: TypedEnvelope<proto::Pull>,
621 mut cx: AsyncApp,
622 ) -> Result<proto::RemoteMessageResponse> {
623 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
624 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
625 let repository_handle =
626 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
627 let askpass_id = envelope.payload.askpass_id;
628 let askpass = make_remote_delegate(
629 this,
630 envelope.payload.project_id,
631 worktree_id,
632 work_directory_id,
633 askpass_id,
634 &mut cx,
635 );
636
637 let branch_name = envelope.payload.branch_name.into();
638 let remote_name = envelope.payload.remote_name.into();
639
640 let remote_message = repository_handle
641 .update(&mut cx, |repository_handle, cx| {
642 repository_handle.pull(branch_name, remote_name, askpass, cx)
643 })?
644 .await??;
645
646 Ok(proto::RemoteMessageResponse {
647 stdout: remote_message.stdout,
648 stderr: remote_message.stderr,
649 })
650 }
651
652 async fn handle_stage(
653 this: Entity<Self>,
654 envelope: TypedEnvelope<proto::Stage>,
655 mut cx: AsyncApp,
656 ) -> Result<proto::Ack> {
657 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
658 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
659 let repository_handle =
660 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
661
662 let entries = envelope
663 .payload
664 .paths
665 .into_iter()
666 .map(PathBuf::from)
667 .map(RepoPath::new)
668 .collect();
669
670 repository_handle
671 .update(&mut cx, |repository_handle, cx| {
672 repository_handle.stage_entries(entries, cx)
673 })?
674 .await?;
675 Ok(proto::Ack {})
676 }
677
678 async fn handle_unstage(
679 this: Entity<Self>,
680 envelope: TypedEnvelope<proto::Unstage>,
681 mut cx: AsyncApp,
682 ) -> Result<proto::Ack> {
683 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
684 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
685 let repository_handle =
686 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
687
688 let entries = envelope
689 .payload
690 .paths
691 .into_iter()
692 .map(PathBuf::from)
693 .map(RepoPath::new)
694 .collect();
695
696 repository_handle
697 .update(&mut cx, |repository_handle, cx| {
698 repository_handle.unstage_entries(entries, cx)
699 })?
700 .await?;
701
702 Ok(proto::Ack {})
703 }
704
705 async fn handle_set_index_text(
706 this: Entity<Self>,
707 envelope: TypedEnvelope<proto::SetIndexText>,
708 mut cx: AsyncApp,
709 ) -> Result<proto::Ack> {
710 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
711 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
712 let repository_handle =
713 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
714
715 repository_handle
716 .update(&mut cx, |repository_handle, cx| {
717 repository_handle.set_index_text(
718 RepoPath::from_str(&envelope.payload.path),
719 envelope.payload.text,
720 cx,
721 )
722 })?
723 .await??;
724 Ok(proto::Ack {})
725 }
726
727 async fn handle_commit(
728 this: Entity<Self>,
729 envelope: TypedEnvelope<proto::Commit>,
730 mut cx: AsyncApp,
731 ) -> Result<proto::Ack> {
732 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
733 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
734 let repository_handle =
735 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
736
737 let message = SharedString::from(envelope.payload.message);
738 let name = envelope.payload.name.map(SharedString::from);
739 let email = envelope.payload.email.map(SharedString::from);
740
741 repository_handle
742 .update(&mut cx, |repository_handle, cx| {
743 repository_handle.commit(message, name.zip(email), cx)
744 })?
745 .await??;
746 Ok(proto::Ack {})
747 }
748
749 async fn handle_get_remotes(
750 this: Entity<Self>,
751 envelope: TypedEnvelope<proto::GetRemotes>,
752 mut cx: AsyncApp,
753 ) -> Result<proto::GetRemotesResponse> {
754 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
755 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
756 let repository_handle =
757 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
758
759 let branch_name = envelope.payload.branch_name;
760
761 let remotes = repository_handle
762 .update(&mut cx, |repository_handle, _| {
763 repository_handle.get_remotes(branch_name)
764 })?
765 .await??;
766
767 Ok(proto::GetRemotesResponse {
768 remotes: remotes
769 .into_iter()
770 .map(|remotes| proto::get_remotes_response::Remote {
771 name: remotes.name.to_string(),
772 })
773 .collect::<Vec<_>>(),
774 })
775 }
776
777 async fn handle_get_branches(
778 this: Entity<Self>,
779 envelope: TypedEnvelope<proto::GitGetBranches>,
780 mut cx: AsyncApp,
781 ) -> Result<proto::GitBranchesResponse> {
782 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
783 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
784 let repository_handle =
785 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
786
787 let branches = repository_handle
788 .update(&mut cx, |repository_handle, _| repository_handle.branches())?
789 .await??;
790
791 Ok(proto::GitBranchesResponse {
792 branches: branches
793 .into_iter()
794 .map(|branch| worktree::branch_to_proto(&branch))
795 .collect::<Vec<_>>(),
796 })
797 }
798 async fn handle_create_branch(
799 this: Entity<Self>,
800 envelope: TypedEnvelope<proto::GitCreateBranch>,
801 mut cx: AsyncApp,
802 ) -> Result<proto::Ack> {
803 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
804 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
805 let repository_handle =
806 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
807 let branch_name = envelope.payload.branch_name;
808
809 repository_handle
810 .update(&mut cx, |repository_handle, _| {
811 repository_handle.create_branch(branch_name)
812 })?
813 .await??;
814
815 Ok(proto::Ack {})
816 }
817
818 async fn handle_change_branch(
819 this: Entity<Self>,
820 envelope: TypedEnvelope<proto::GitChangeBranch>,
821 mut cx: AsyncApp,
822 ) -> Result<proto::Ack> {
823 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
824 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
825 let repository_handle =
826 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
827 let branch_name = envelope.payload.branch_name;
828
829 repository_handle
830 .update(&mut cx, |repository_handle, _| {
831 repository_handle.change_branch(branch_name)
832 })?
833 .await??;
834
835 Ok(proto::Ack {})
836 }
837
838 async fn handle_show(
839 this: Entity<Self>,
840 envelope: TypedEnvelope<proto::GitShow>,
841 mut cx: AsyncApp,
842 ) -> Result<proto::GitCommitDetails> {
843 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
844 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
845 let repository_handle =
846 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
847
848 let commit = repository_handle
849 .update(&mut cx, |repository_handle, _| {
850 repository_handle.show(envelope.payload.commit)
851 })?
852 .await??;
853 Ok(proto::GitCommitDetails {
854 sha: commit.sha.into(),
855 message: commit.message.into(),
856 commit_timestamp: commit.commit_timestamp,
857 committer_email: commit.committer_email.into(),
858 committer_name: commit.committer_name.into(),
859 })
860 }
861
862 async fn handle_reset(
863 this: Entity<Self>,
864 envelope: TypedEnvelope<proto::GitReset>,
865 mut cx: AsyncApp,
866 ) -> Result<proto::Ack> {
867 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
868 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
869 let repository_handle =
870 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
871
872 let mode = match envelope.payload.mode() {
873 git_reset::ResetMode::Soft => ResetMode::Soft,
874 git_reset::ResetMode::Mixed => ResetMode::Mixed,
875 };
876
877 repository_handle
878 .update(&mut cx, |repository_handle, cx| {
879 repository_handle.reset(envelope.payload.commit, mode, cx)
880 })?
881 .await??;
882 Ok(proto::Ack {})
883 }
884
885 async fn handle_checkout_files(
886 this: Entity<Self>,
887 envelope: TypedEnvelope<proto::GitCheckoutFiles>,
888 mut cx: AsyncApp,
889 ) -> Result<proto::Ack> {
890 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
891 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
892 let repository_handle =
893 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
894 let paths = envelope
895 .payload
896 .paths
897 .iter()
898 .map(|s| RepoPath::from_str(s))
899 .collect();
900
901 repository_handle
902 .update(&mut cx, |repository_handle, cx| {
903 repository_handle.checkout_files(&envelope.payload.commit, paths, cx)
904 })?
905 .await??;
906 Ok(proto::Ack {})
907 }
908
909 async fn handle_open_commit_message_buffer(
910 this: Entity<Self>,
911 envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
912 mut cx: AsyncApp,
913 ) -> Result<proto::OpenBufferResponse> {
914 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
915 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
916 let repository =
917 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
918 let buffer = repository
919 .update(&mut cx, |repository, cx| {
920 repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
921 })?
922 .await?;
923
924 let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
925 this.update(&mut cx, |this, cx| {
926 this.buffer_store.update(cx, |buffer_store, cx| {
927 buffer_store
928 .create_buffer_for_peer(
929 &buffer,
930 envelope.original_sender_id.unwrap_or(envelope.sender_id),
931 cx,
932 )
933 .detach_and_log_err(cx);
934 })
935 })?;
936
937 Ok(proto::OpenBufferResponse {
938 buffer_id: buffer_id.to_proto(),
939 })
940 }
941
942 async fn handle_askpass(
943 this: Entity<Self>,
944 envelope: TypedEnvelope<proto::AskPassRequest>,
945 mut cx: AsyncApp,
946 ) -> Result<proto::AskPassResponse> {
947 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
948 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
949 let repository =
950 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
951
952 let delegates = cx.update(|cx| repository.read(cx).askpass_delegates.clone())?;
953 let Some(mut askpass) = delegates.lock().remove(&envelope.payload.askpass_id) else {
954 debug_panic!("no askpass found");
955 return Err(anyhow::anyhow!("no askpass found"));
956 };
957
958 let response = askpass.ask_password(envelope.payload.prompt).await?;
959
960 delegates
961 .lock()
962 .insert(envelope.payload.askpass_id, askpass);
963
964 Ok(proto::AskPassResponse { response })
965 }
966
967 async fn handle_check_for_pushed_commits(
968 this: Entity<Self>,
969 envelope: TypedEnvelope<proto::CheckForPushedCommits>,
970 mut cx: AsyncApp,
971 ) -> Result<proto::CheckForPushedCommitsResponse> {
972 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
973 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
974 let repository_handle =
975 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
976
977 let branches = repository_handle
978 .update(&mut cx, |repository_handle, _| {
979 repository_handle.check_for_pushed_commits()
980 })?
981 .await??;
982 Ok(proto::CheckForPushedCommitsResponse {
983 pushed_to: branches
984 .into_iter()
985 .map(|commit| commit.to_string())
986 .collect(),
987 })
988 }
989
990 async fn handle_git_diff(
991 this: Entity<Self>,
992 envelope: TypedEnvelope<proto::GitDiff>,
993 mut cx: AsyncApp,
994 ) -> Result<proto::GitDiffResponse> {
995 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
996 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
997 let repository_handle =
998 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
999 let diff_type = match envelope.payload.diff_type() {
1000 proto::git_diff::DiffType::HeadToIndex => DiffType::HeadToIndex,
1001 proto::git_diff::DiffType::HeadToWorktree => DiffType::HeadToWorktree,
1002 };
1003
1004 let mut diff = repository_handle
1005 .update(&mut cx, |repository_handle, cx| {
1006 repository_handle.diff(diff_type, cx)
1007 })?
1008 .await??;
1009 const ONE_MB: usize = 1_000_000;
1010 if diff.len() > ONE_MB {
1011 diff = diff.chars().take(ONE_MB).collect()
1012 }
1013
1014 Ok(proto::GitDiffResponse { diff })
1015 }
1016
1017 fn repository_for_request(
1018 this: &Entity<Self>,
1019 worktree_id: WorktreeId,
1020 work_directory_id: ProjectEntryId,
1021 cx: &mut AsyncApp,
1022 ) -> Result<Entity<Repository>> {
1023 this.update(cx, |this, cx| {
1024 this.repositories
1025 .iter()
1026 .find(|repository_handle| {
1027 repository_handle.read(cx).worktree_id == worktree_id
1028 && repository_handle
1029 .read(cx)
1030 .repository_entry
1031 .work_directory_id()
1032 == work_directory_id
1033 })
1034 .context("missing repository handle")
1035 .cloned()
1036 })?
1037 }
1038}
1039
1040fn make_remote_delegate(
1041 this: Entity<GitStore>,
1042 project_id: u64,
1043 worktree_id: WorktreeId,
1044 work_directory_id: ProjectEntryId,
1045 askpass_id: u64,
1046 cx: &mut AsyncApp,
1047) -> AskPassDelegate {
1048 AskPassDelegate::new(cx, move |prompt, tx, cx| {
1049 this.update(cx, |this, cx| {
1050 let response = this.client().request(proto::AskPassRequest {
1051 project_id,
1052 worktree_id: worktree_id.to_proto(),
1053 work_directory_id: work_directory_id.to_proto(),
1054 askpass_id,
1055 prompt,
1056 });
1057 cx.spawn(|_, _| async move {
1058 tx.send(response.await?.response).ok();
1059 anyhow::Ok(())
1060 })
1061 .detach_and_log_err(cx);
1062 })
1063 .log_err();
1064 })
1065}
1066
1067impl GitRepo {}
1068
1069impl Repository {
1070 pub fn git_store(&self) -> Option<Entity<GitStore>> {
1071 self.git_store.upgrade()
1072 }
1073
1074 fn id(&self) -> (WorktreeId, ProjectEntryId) {
1075 (self.worktree_id, self.repository_entry.work_directory_id())
1076 }
1077
1078 pub fn current_branch(&self) -> Option<&Branch> {
1079 self.repository_entry.branch()
1080 }
1081
1082 fn send_job<F, Fut, R>(&self, job: F) -> oneshot::Receiver<R>
1083 where
1084 F: FnOnce(GitRepo, AsyncApp) -> Fut + 'static,
1085 Fut: Future<Output = R> + 'static,
1086 R: Send + 'static,
1087 {
1088 self.send_keyed_job(None, job)
1089 }
1090
1091 fn send_keyed_job<F, Fut, R>(&self, key: Option<GitJobKey>, job: F) -> oneshot::Receiver<R>
1092 where
1093 F: FnOnce(GitRepo, AsyncApp) -> Fut + 'static,
1094 Fut: Future<Output = R> + 'static,
1095 R: Send + 'static,
1096 {
1097 let (result_tx, result_rx) = futures::channel::oneshot::channel();
1098 let git_repo = self.git_repo.clone();
1099 self.job_sender
1100 .unbounded_send(GitJob {
1101 key,
1102 job: Box::new(|cx: &mut AsyncApp| {
1103 let job = job(git_repo, cx.clone());
1104 cx.spawn(|_| async move {
1105 let result = job.await;
1106 result_tx.send(result).ok();
1107 })
1108 }),
1109 })
1110 .ok();
1111 result_rx
1112 }
1113
1114 pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
1115 maybe!({
1116 let project_path = self.repo_path_to_project_path(&"".into())?;
1117 let worktree_name = project
1118 .worktree_for_id(project_path.worktree_id, cx)?
1119 .read(cx)
1120 .root_name();
1121
1122 let mut path = PathBuf::new();
1123 path = path.join(worktree_name);
1124 path = path.join(project_path.path);
1125 Some(path.to_string_lossy().to_string())
1126 })
1127 .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
1128 .into()
1129 }
1130
1131 pub fn activate(&self, cx: &mut Context<Self>) {
1132 let Some(git_store) = self.git_store.upgrade() else {
1133 return;
1134 };
1135 let entity = cx.entity();
1136 git_store.update(cx, |git_store, cx| {
1137 let Some(index) = git_store
1138 .repositories
1139 .iter()
1140 .position(|handle| *handle == entity)
1141 else {
1142 return;
1143 };
1144 git_store.active_index = Some(index);
1145 cx.emit(GitEvent::ActiveRepositoryChanged);
1146 });
1147 }
1148
1149 pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
1150 self.repository_entry.status()
1151 }
1152
1153 pub fn has_conflict(&self, path: &RepoPath) -> bool {
1154 self.repository_entry
1155 .current_merge_conflicts
1156 .contains(&path)
1157 }
1158
1159 pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
1160 let path = self.repository_entry.try_unrelativize(path)?;
1161 Some((self.worktree_id, path).into())
1162 }
1163
1164 pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
1165 self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
1166 }
1167
1168 // note: callers must verify these come from the same worktree
1169 pub fn contains_sub_repo(&self, other: &Entity<Self>, cx: &App) -> bool {
1170 let other_work_dir = &other.read(cx).repository_entry.work_directory;
1171 match (&self.repository_entry.work_directory, other_work_dir) {
1172 (WorkDirectory::InProject { .. }, WorkDirectory::AboveProject { .. }) => false,
1173 (WorkDirectory::AboveProject { .. }, WorkDirectory::InProject { .. }) => true,
1174 (
1175 WorkDirectory::InProject {
1176 relative_path: this_path,
1177 },
1178 WorkDirectory::InProject {
1179 relative_path: other_path,
1180 },
1181 ) => other_path.starts_with(this_path),
1182 (
1183 WorkDirectory::AboveProject {
1184 absolute_path: this_path,
1185 ..
1186 },
1187 WorkDirectory::AboveProject {
1188 absolute_path: other_path,
1189 ..
1190 },
1191 ) => other_path.starts_with(this_path),
1192 }
1193 }
1194
1195 pub fn worktree_id_path_to_repo_path(
1196 &self,
1197 worktree_id: WorktreeId,
1198 path: &Path,
1199 ) -> Option<RepoPath> {
1200 if worktree_id != self.worktree_id {
1201 return None;
1202 }
1203 self.repository_entry.relativize(path).log_err()
1204 }
1205
1206 pub fn open_commit_buffer(
1207 &mut self,
1208 languages: Option<Arc<LanguageRegistry>>,
1209 buffer_store: Entity<BufferStore>,
1210 cx: &mut Context<Self>,
1211 ) -> Task<Result<Entity<Buffer>>> {
1212 if let Some(buffer) = self.commit_message_buffer.clone() {
1213 return Task::ready(Ok(buffer));
1214 }
1215
1216 if let GitRepo::Remote {
1217 project_id,
1218 client,
1219 worktree_id,
1220 work_directory_id,
1221 } = self.git_repo.clone()
1222 {
1223 let client = client.clone();
1224 cx.spawn(|repository, mut cx| async move {
1225 let request = client.request(proto::OpenCommitMessageBuffer {
1226 project_id: project_id.0,
1227 worktree_id: worktree_id.to_proto(),
1228 work_directory_id: work_directory_id.to_proto(),
1229 });
1230 let response = request.await.context("requesting to open commit buffer")?;
1231 let buffer_id = BufferId::new(response.buffer_id)?;
1232 let buffer = buffer_store
1233 .update(&mut cx, |buffer_store, cx| {
1234 buffer_store.wait_for_remote_buffer(buffer_id, cx)
1235 })?
1236 .await?;
1237 if let Some(language_registry) = languages {
1238 let git_commit_language =
1239 language_registry.language_for_name("Git Commit").await?;
1240 buffer.update(&mut cx, |buffer, cx| {
1241 buffer.set_language(Some(git_commit_language), cx);
1242 })?;
1243 }
1244 repository.update(&mut cx, |repository, _| {
1245 repository.commit_message_buffer = Some(buffer.clone());
1246 })?;
1247 Ok(buffer)
1248 })
1249 } else {
1250 self.open_local_commit_buffer(languages, buffer_store, cx)
1251 }
1252 }
1253
1254 fn open_local_commit_buffer(
1255 &mut self,
1256 language_registry: Option<Arc<LanguageRegistry>>,
1257 buffer_store: Entity<BufferStore>,
1258 cx: &mut Context<Self>,
1259 ) -> Task<Result<Entity<Buffer>>> {
1260 let merge_message = self.merge_message.clone();
1261 cx.spawn(|repository, mut cx| async move {
1262 let buffer = buffer_store
1263 .update(&mut cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
1264 .await?;
1265
1266 if let Some(language_registry) = language_registry {
1267 let git_commit_language = language_registry.language_for_name("Git Commit").await?;
1268 buffer.update(&mut cx, |buffer, cx| {
1269 buffer.set_language(Some(git_commit_language), cx);
1270 })?;
1271 }
1272
1273 if let Some(merge_message) = merge_message {
1274 buffer.update(&mut cx, |buffer, cx| {
1275 buffer.set_text(merge_message.as_str(), cx)
1276 })?;
1277 }
1278
1279 repository.update(&mut cx, |repository, _| {
1280 repository.commit_message_buffer = Some(buffer.clone());
1281 })?;
1282 Ok(buffer)
1283 })
1284 }
1285
1286 pub fn checkout_files(
1287 &self,
1288 commit: &str,
1289 paths: Vec<RepoPath>,
1290 cx: &mut App,
1291 ) -> oneshot::Receiver<Result<()>> {
1292 let commit = commit.to_string();
1293 let env = self.worktree_environment(cx);
1294
1295 self.send_job(|git_repo, _| async move {
1296 match git_repo {
1297 GitRepo::Local(repo) => repo.checkout_files(commit, paths, env.await).await,
1298 GitRepo::Remote {
1299 project_id,
1300 client,
1301 worktree_id,
1302 work_directory_id,
1303 } => {
1304 client
1305 .request(proto::GitCheckoutFiles {
1306 project_id: project_id.0,
1307 worktree_id: worktree_id.to_proto(),
1308 work_directory_id: work_directory_id.to_proto(),
1309 commit,
1310 paths: paths
1311 .into_iter()
1312 .map(|p| p.to_string_lossy().to_string())
1313 .collect(),
1314 })
1315 .await?;
1316
1317 Ok(())
1318 }
1319 }
1320 })
1321 }
1322
1323 pub fn reset(
1324 &self,
1325 commit: String,
1326 reset_mode: ResetMode,
1327 cx: &mut App,
1328 ) -> oneshot::Receiver<Result<()>> {
1329 let commit = commit.to_string();
1330 let env = self.worktree_environment(cx);
1331 self.send_job(|git_repo, _| async move {
1332 match git_repo {
1333 GitRepo::Local(git_repo) => {
1334 let env = env.await;
1335 git_repo.reset(commit, reset_mode, env).await
1336 }
1337 GitRepo::Remote {
1338 project_id,
1339 client,
1340 worktree_id,
1341 work_directory_id,
1342 } => {
1343 client
1344 .request(proto::GitReset {
1345 project_id: project_id.0,
1346 worktree_id: worktree_id.to_proto(),
1347 work_directory_id: work_directory_id.to_proto(),
1348 commit,
1349 mode: match reset_mode {
1350 ResetMode::Soft => git_reset::ResetMode::Soft.into(),
1351 ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
1352 },
1353 })
1354 .await?;
1355
1356 Ok(())
1357 }
1358 }
1359 })
1360 }
1361
1362 pub fn show(&self, commit: String) -> oneshot::Receiver<Result<CommitDetails>> {
1363 self.send_job(|git_repo, cx| async move {
1364 match git_repo {
1365 GitRepo::Local(git_repository) => git_repository.show(commit, cx).await,
1366 GitRepo::Remote {
1367 project_id,
1368 client,
1369 worktree_id,
1370 work_directory_id,
1371 } => {
1372 let resp = client
1373 .request(proto::GitShow {
1374 project_id: project_id.0,
1375 worktree_id: worktree_id.to_proto(),
1376 work_directory_id: work_directory_id.to_proto(),
1377 commit,
1378 })
1379 .await?;
1380
1381 Ok(CommitDetails {
1382 sha: resp.sha.into(),
1383 message: resp.message.into(),
1384 commit_timestamp: resp.commit_timestamp,
1385 committer_email: resp.committer_email.into(),
1386 committer_name: resp.committer_name.into(),
1387 })
1388 }
1389 }
1390 })
1391 }
1392
1393 fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
1394 Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
1395 }
1396
1397 pub fn stage_entries(
1398 &self,
1399 entries: Vec<RepoPath>,
1400 cx: &mut Context<Self>,
1401 ) -> Task<anyhow::Result<()>> {
1402 if entries.is_empty() {
1403 return Task::ready(Ok(()));
1404 }
1405 let env = self.worktree_environment(cx);
1406
1407 let mut save_futures = Vec::new();
1408 if let Some(buffer_store) = self.buffer_store(cx) {
1409 buffer_store.update(cx, |buffer_store, cx| {
1410 for path in &entries {
1411 let Some(path) = self.repository_entry.try_unrelativize(path) else {
1412 continue;
1413 };
1414 let project_path = (self.worktree_id, path).into();
1415 if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1416 if buffer
1417 .read(cx)
1418 .file()
1419 .map_or(false, |file| file.disk_state().exists())
1420 {
1421 save_futures.push(buffer_store.save_buffer(buffer, cx));
1422 }
1423 }
1424 }
1425 })
1426 }
1427
1428 cx.spawn(|this, mut cx| async move {
1429 for save_future in save_futures {
1430 save_future.await?;
1431 }
1432 let env = env.await;
1433
1434 this.update(&mut cx, |this, _| {
1435 this.send_job(|git_repo, cx| async move {
1436 match git_repo {
1437 GitRepo::Local(repo) => repo.stage_paths(entries, env, cx).await,
1438 GitRepo::Remote {
1439 project_id,
1440 client,
1441 worktree_id,
1442 work_directory_id,
1443 } => {
1444 client
1445 .request(proto::Stage {
1446 project_id: project_id.0,
1447 worktree_id: worktree_id.to_proto(),
1448 work_directory_id: work_directory_id.to_proto(),
1449 paths: entries
1450 .into_iter()
1451 .map(|repo_path| repo_path.as_ref().to_proto())
1452 .collect(),
1453 })
1454 .await
1455 .context("sending stage request")?;
1456
1457 Ok(())
1458 }
1459 }
1460 })
1461 })?
1462 .await??;
1463
1464 Ok(())
1465 })
1466 }
1467
1468 pub fn unstage_entries(
1469 &self,
1470 entries: Vec<RepoPath>,
1471 cx: &mut Context<Self>,
1472 ) -> Task<anyhow::Result<()>> {
1473 if entries.is_empty() {
1474 return Task::ready(Ok(()));
1475 }
1476 let env = self.worktree_environment(cx);
1477
1478 let mut save_futures = Vec::new();
1479 if let Some(buffer_store) = self.buffer_store(cx) {
1480 buffer_store.update(cx, |buffer_store, cx| {
1481 for path in &entries {
1482 let Some(path) = self.repository_entry.try_unrelativize(path) else {
1483 continue;
1484 };
1485 let project_path = (self.worktree_id, path).into();
1486 if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
1487 if buffer
1488 .read(cx)
1489 .file()
1490 .map_or(false, |file| file.disk_state().exists())
1491 {
1492 save_futures.push(buffer_store.save_buffer(buffer, cx));
1493 }
1494 }
1495 }
1496 })
1497 }
1498
1499 cx.spawn(move |this, mut cx| async move {
1500 for save_future in save_futures {
1501 save_future.await?;
1502 }
1503 let env = env.await;
1504
1505 this.update(&mut cx, |this, _| {
1506 this.send_job(|git_repo, cx| async move {
1507 match git_repo {
1508 GitRepo::Local(repo) => repo.unstage_paths(entries, env, cx).await,
1509 GitRepo::Remote {
1510 project_id,
1511 client,
1512 worktree_id,
1513 work_directory_id,
1514 } => {
1515 client
1516 .request(proto::Unstage {
1517 project_id: project_id.0,
1518 worktree_id: worktree_id.to_proto(),
1519 work_directory_id: work_directory_id.to_proto(),
1520 paths: entries
1521 .into_iter()
1522 .map(|repo_path| repo_path.as_ref().to_proto())
1523 .collect(),
1524 })
1525 .await
1526 .context("sending unstage request")?;
1527
1528 Ok(())
1529 }
1530 }
1531 })
1532 })?
1533 .await??;
1534
1535 Ok(())
1536 })
1537 }
1538
1539 pub fn stage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1540 let to_stage = self
1541 .repository_entry
1542 .status()
1543 .filter(|entry| !entry.status.staging().is_fully_staged())
1544 .map(|entry| entry.repo_path.clone())
1545 .collect();
1546 self.stage_entries(to_stage, cx)
1547 }
1548
1549 pub fn unstage_all(&self, cx: &mut Context<Self>) -> Task<anyhow::Result<()>> {
1550 let to_unstage = self
1551 .repository_entry
1552 .status()
1553 .filter(|entry| entry.status.staging().has_staged())
1554 .map(|entry| entry.repo_path.clone())
1555 .collect();
1556 self.unstage_entries(to_unstage, cx)
1557 }
1558
1559 /// Get a count of all entries in the active repository, including
1560 /// untracked files.
1561 pub fn entry_count(&self) -> usize {
1562 self.repository_entry.status_len()
1563 }
1564
1565 fn worktree_environment(
1566 &self,
1567 cx: &mut App,
1568 ) -> impl Future<Output = HashMap<String, String>> + 'static {
1569 let task = self.project_environment.as_ref().and_then(|env| {
1570 env.update(cx, |env, cx| {
1571 env.get_environment(
1572 Some(self.worktree_id),
1573 Some(self.worktree_abs_path.clone()),
1574 cx,
1575 )
1576 })
1577 .ok()
1578 });
1579 async move { OptionFuture::from(task).await.flatten().unwrap_or_default() }
1580 }
1581
1582 pub fn commit(
1583 &self,
1584 message: SharedString,
1585 name_and_email: Option<(SharedString, SharedString)>,
1586 cx: &mut App,
1587 ) -> oneshot::Receiver<Result<()>> {
1588 let env = self.worktree_environment(cx);
1589 self.send_job(|git_repo, cx| async move {
1590 match git_repo {
1591 GitRepo::Local(repo) => {
1592 let env = env.await;
1593 repo.commit(message, name_and_email, env, cx).await
1594 }
1595 GitRepo::Remote {
1596 project_id,
1597 client,
1598 worktree_id,
1599 work_directory_id,
1600 } => {
1601 let (name, email) = name_and_email.unzip();
1602 client
1603 .request(proto::Commit {
1604 project_id: project_id.0,
1605 worktree_id: worktree_id.to_proto(),
1606 work_directory_id: work_directory_id.to_proto(),
1607 message: String::from(message),
1608 name: name.map(String::from),
1609 email: email.map(String::from),
1610 })
1611 .await
1612 .context("sending commit request")?;
1613
1614 Ok(())
1615 }
1616 }
1617 })
1618 }
1619
1620 pub fn fetch(
1621 &mut self,
1622 askpass: AskPassDelegate,
1623 cx: &mut App,
1624 ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1625 let executor = cx.background_executor().clone();
1626 let askpass_delegates = self.askpass_delegates.clone();
1627 let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1628 let env = self.worktree_environment(cx);
1629
1630 self.send_job(move |git_repo, cx| async move {
1631 match git_repo {
1632 GitRepo::Local(git_repository) => {
1633 let askpass = AskPassSession::new(&executor, askpass).await?;
1634 let env = env.await;
1635 git_repository.fetch(askpass, env, cx).await
1636 }
1637 GitRepo::Remote {
1638 project_id,
1639 client,
1640 worktree_id,
1641 work_directory_id,
1642 } => {
1643 askpass_delegates.lock().insert(askpass_id, askpass);
1644 let _defer = util::defer(|| {
1645 let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1646 debug_assert!(askpass_delegate.is_some());
1647 });
1648
1649 let response = client
1650 .request(proto::Fetch {
1651 project_id: project_id.0,
1652 worktree_id: worktree_id.to_proto(),
1653 work_directory_id: work_directory_id.to_proto(),
1654 askpass_id,
1655 })
1656 .await
1657 .context("sending fetch request")?;
1658
1659 Ok(RemoteCommandOutput {
1660 stdout: response.stdout,
1661 stderr: response.stderr,
1662 })
1663 }
1664 }
1665 })
1666 }
1667
1668 pub fn push(
1669 &mut self,
1670 branch: SharedString,
1671 remote: SharedString,
1672 options: Option<PushOptions>,
1673 askpass: AskPassDelegate,
1674 cx: &mut App,
1675 ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1676 let executor = cx.background_executor().clone();
1677 let askpass_delegates = self.askpass_delegates.clone();
1678 let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1679 let env = self.worktree_environment(cx);
1680
1681 self.send_job(move |git_repo, cx| async move {
1682 match git_repo {
1683 GitRepo::Local(git_repository) => {
1684 let env = env.await;
1685 let askpass = AskPassSession::new(&executor, askpass).await?;
1686 git_repository
1687 .push(
1688 branch.to_string(),
1689 remote.to_string(),
1690 options,
1691 askpass,
1692 env,
1693 cx,
1694 )
1695 .await
1696 }
1697 GitRepo::Remote {
1698 project_id,
1699 client,
1700 worktree_id,
1701 work_directory_id,
1702 } => {
1703 askpass_delegates.lock().insert(askpass_id, askpass);
1704 let _defer = util::defer(|| {
1705 let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1706 debug_assert!(askpass_delegate.is_some());
1707 });
1708 let response = client
1709 .request(proto::Push {
1710 project_id: project_id.0,
1711 worktree_id: worktree_id.to_proto(),
1712 work_directory_id: work_directory_id.to_proto(),
1713 askpass_id,
1714 branch_name: branch.to_string(),
1715 remote_name: remote.to_string(),
1716 options: options.map(|options| match options {
1717 PushOptions::Force => proto::push::PushOptions::Force,
1718 PushOptions::SetUpstream => proto::push::PushOptions::SetUpstream,
1719 } as i32),
1720 })
1721 .await
1722 .context("sending push request")?;
1723
1724 Ok(RemoteCommandOutput {
1725 stdout: response.stdout,
1726 stderr: response.stderr,
1727 })
1728 }
1729 }
1730 })
1731 }
1732
1733 pub fn pull(
1734 &mut self,
1735 branch: SharedString,
1736 remote: SharedString,
1737 askpass: AskPassDelegate,
1738 cx: &mut App,
1739 ) -> oneshot::Receiver<Result<RemoteCommandOutput>> {
1740 let executor = cx.background_executor().clone();
1741 let askpass_delegates = self.askpass_delegates.clone();
1742 let askpass_id = util::post_inc(&mut self.latest_askpass_id);
1743 let env = self.worktree_environment(cx);
1744
1745 self.send_job(move |git_repo, cx| async move {
1746 match git_repo {
1747 GitRepo::Local(git_repository) => {
1748 let askpass = AskPassSession::new(&executor, askpass).await?;
1749 let env = env.await;
1750 git_repository
1751 .pull(branch.to_string(), remote.to_string(), askpass, env, cx)
1752 .await
1753 }
1754 GitRepo::Remote {
1755 project_id,
1756 client,
1757 worktree_id,
1758 work_directory_id,
1759 } => {
1760 askpass_delegates.lock().insert(askpass_id, askpass);
1761 let _defer = util::defer(|| {
1762 let askpass_delegate = askpass_delegates.lock().remove(&askpass_id);
1763 debug_assert!(askpass_delegate.is_some());
1764 });
1765 let response = client
1766 .request(proto::Pull {
1767 project_id: project_id.0,
1768 worktree_id: worktree_id.to_proto(),
1769 work_directory_id: work_directory_id.to_proto(),
1770 askpass_id,
1771 branch_name: branch.to_string(),
1772 remote_name: remote.to_string(),
1773 })
1774 .await
1775 .context("sending pull request")?;
1776
1777 Ok(RemoteCommandOutput {
1778 stdout: response.stdout,
1779 stderr: response.stderr,
1780 })
1781 }
1782 }
1783 })
1784 }
1785
1786 fn set_index_text(
1787 &self,
1788 path: RepoPath,
1789 content: Option<String>,
1790 cx: &mut App,
1791 ) -> oneshot::Receiver<anyhow::Result<()>> {
1792 let env = self.worktree_environment(cx);
1793
1794 self.send_keyed_job(
1795 Some(GitJobKey::WriteIndex(path.clone())),
1796 |git_repo, cx| async move {
1797 match git_repo {
1798 GitRepo::Local(repo) => repo.set_index_text(path, content, env.await, cx).await,
1799 GitRepo::Remote {
1800 project_id,
1801 client,
1802 worktree_id,
1803 work_directory_id,
1804 } => {
1805 client
1806 .request(proto::SetIndexText {
1807 project_id: project_id.0,
1808 worktree_id: worktree_id.to_proto(),
1809 work_directory_id: work_directory_id.to_proto(),
1810 path: path.as_ref().to_proto(),
1811 text: content,
1812 })
1813 .await?;
1814 Ok(())
1815 }
1816 }
1817 },
1818 )
1819 }
1820
1821 pub fn get_remotes(
1822 &self,
1823 branch_name: Option<String>,
1824 ) -> oneshot::Receiver<Result<Vec<Remote>>> {
1825 self.send_job(|repo, cx| async move {
1826 match repo {
1827 GitRepo::Local(git_repository) => git_repository.get_remotes(branch_name, cx).await,
1828 GitRepo::Remote {
1829 project_id,
1830 client,
1831 worktree_id,
1832 work_directory_id,
1833 } => {
1834 let response = client
1835 .request(proto::GetRemotes {
1836 project_id: project_id.0,
1837 worktree_id: worktree_id.to_proto(),
1838 work_directory_id: work_directory_id.to_proto(),
1839 branch_name,
1840 })
1841 .await?;
1842
1843 let remotes = response
1844 .remotes
1845 .into_iter()
1846 .map(|remotes| git::repository::Remote {
1847 name: remotes.name.into(),
1848 })
1849 .collect();
1850
1851 Ok(remotes)
1852 }
1853 }
1854 })
1855 }
1856
1857 pub fn branches(&self) -> oneshot::Receiver<Result<Vec<Branch>>> {
1858 self.send_job(|repo, cx| async move {
1859 match repo {
1860 GitRepo::Local(git_repository) => {
1861 let git_repository = git_repository.clone();
1862 cx.background_spawn(async move { git_repository.branches().await })
1863 .await
1864 }
1865 GitRepo::Remote {
1866 project_id,
1867 client,
1868 worktree_id,
1869 work_directory_id,
1870 } => {
1871 let response = client
1872 .request(proto::GitGetBranches {
1873 project_id: project_id.0,
1874 worktree_id: worktree_id.to_proto(),
1875 work_directory_id: work_directory_id.to_proto(),
1876 })
1877 .await?;
1878
1879 let branches = response
1880 .branches
1881 .into_iter()
1882 .map(|branch| worktree::proto_to_branch(&branch))
1883 .collect();
1884
1885 Ok(branches)
1886 }
1887 }
1888 })
1889 }
1890
1891 pub fn diff(&self, diff_type: DiffType, _cx: &App) -> oneshot::Receiver<Result<String>> {
1892 self.send_job(|repo, cx| async move {
1893 match repo {
1894 GitRepo::Local(git_repository) => git_repository.diff(diff_type, cx).await,
1895 GitRepo::Remote {
1896 project_id,
1897 client,
1898 worktree_id,
1899 work_directory_id,
1900 ..
1901 } => {
1902 let response = client
1903 .request(proto::GitDiff {
1904 project_id: project_id.0,
1905 worktree_id: worktree_id.to_proto(),
1906 work_directory_id: work_directory_id.to_proto(),
1907 diff_type: match diff_type {
1908 DiffType::HeadToIndex => {
1909 proto::git_diff::DiffType::HeadToIndex.into()
1910 }
1911 DiffType::HeadToWorktree => {
1912 proto::git_diff::DiffType::HeadToWorktree.into()
1913 }
1914 },
1915 })
1916 .await?;
1917
1918 Ok(response.diff)
1919 }
1920 }
1921 })
1922 }
1923
1924 pub fn create_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
1925 self.send_job(|repo, cx| async move {
1926 match repo {
1927 GitRepo::Local(git_repository) => {
1928 git_repository.create_branch(branch_name, cx).await
1929 }
1930 GitRepo::Remote {
1931 project_id,
1932 client,
1933 worktree_id,
1934 work_directory_id,
1935 } => {
1936 client
1937 .request(proto::GitCreateBranch {
1938 project_id: project_id.0,
1939 worktree_id: worktree_id.to_proto(),
1940 work_directory_id: work_directory_id.to_proto(),
1941 branch_name,
1942 })
1943 .await?;
1944
1945 Ok(())
1946 }
1947 }
1948 })
1949 }
1950
1951 pub fn change_branch(&self, branch_name: String) -> oneshot::Receiver<Result<()>> {
1952 self.send_job(|repo, cx| async move {
1953 match repo {
1954 GitRepo::Local(git_repository) => {
1955 git_repository.change_branch(branch_name, cx).await
1956 }
1957 GitRepo::Remote {
1958 project_id,
1959 client,
1960 worktree_id,
1961 work_directory_id,
1962 } => {
1963 client
1964 .request(proto::GitChangeBranch {
1965 project_id: project_id.0,
1966 worktree_id: worktree_id.to_proto(),
1967 work_directory_id: work_directory_id.to_proto(),
1968 branch_name,
1969 })
1970 .await?;
1971
1972 Ok(())
1973 }
1974 }
1975 })
1976 }
1977
1978 pub fn check_for_pushed_commits(&self) -> oneshot::Receiver<Result<Vec<SharedString>>> {
1979 self.send_job(|repo, cx| async move {
1980 match repo {
1981 GitRepo::Local(git_repository) => git_repository.check_for_pushed_commit(cx).await,
1982 GitRepo::Remote {
1983 project_id,
1984 client,
1985 worktree_id,
1986 work_directory_id,
1987 } => {
1988 let response = client
1989 .request(proto::CheckForPushedCommits {
1990 project_id: project_id.0,
1991 worktree_id: worktree_id.to_proto(),
1992 work_directory_id: work_directory_id.to_proto(),
1993 })
1994 .await?;
1995
1996 let branches = response.pushed_to.into_iter().map(Into::into).collect();
1997
1998 Ok(branches)
1999 }
2000 }
2001 })
2002 }
2003}