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