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