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