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