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