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