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