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, ResetMode};
9use git::{
10 repository::{GitRepository, RepoPath},
11 status::{GitSummary, TrackedSummary},
12};
13use gpui::{
14 App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Subscription, Task,
15 WeakEntity,
16};
17use language::{Buffer, LanguageRegistry};
18use rpc::proto::{git_reset, ToProto};
19use rpc::{proto, AnyProtoClient, TypedEnvelope};
20use settings::WorktreeId;
21use std::path::{Path, PathBuf};
22use std::sync::Arc;
23use text::BufferId;
24use util::{maybe, ResultExt};
25use worktree::{ProjectEntryId, RepositoryEntry, StatusEntry};
26
27pub struct GitStore {
28 buffer_store: Entity<BufferStore>,
29 pub(super) project_id: Option<ProjectId>,
30 pub(super) client: Option<AnyProtoClient>,
31 repositories: Vec<Entity<Repository>>,
32 active_index: Option<usize>,
33 update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>,
34 _subscription: Subscription,
35}
36
37pub struct Repository {
38 commit_message_buffer: Option<Entity<Buffer>>,
39 git_store: WeakEntity<GitStore>,
40 pub worktree_id: WorktreeId,
41 pub repository_entry: RepositoryEntry,
42 pub git_repo: GitRepo,
43 update_sender: mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)>,
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 Message {
58 Commit {
59 git_repo: GitRepo,
60 message: SharedString,
61 name_and_email: Option<(SharedString, SharedString)>,
62 },
63 Reset {
64 repo: GitRepo,
65 commit: SharedString,
66 reset_mode: ResetMode,
67 },
68 CheckoutFiles {
69 repo: GitRepo,
70 commit: SharedString,
71 paths: Vec<RepoPath>,
72 },
73 Stage(GitRepo, Vec<RepoPath>),
74 Unstage(GitRepo, Vec<RepoPath>),
75 SetIndexText(GitRepo, RepoPath, Option<String>),
76}
77
78pub enum GitEvent {
79 ActiveRepositoryChanged,
80 FileSystemUpdated,
81 GitStateUpdated,
82}
83
84impl EventEmitter<GitEvent> for GitStore {}
85
86impl GitStore {
87 pub fn new(
88 worktree_store: &Entity<WorktreeStore>,
89 buffer_store: Entity<BufferStore>,
90 client: Option<AnyProtoClient>,
91 project_id: Option<ProjectId>,
92 cx: &mut Context<'_, Self>,
93 ) -> Self {
94 let update_sender = Self::spawn_git_worker(cx);
95 let _subscription = cx.subscribe(worktree_store, Self::on_worktree_store_event);
96
97 GitStore {
98 project_id,
99 client,
100 buffer_store,
101 repositories: Vec::new(),
102 active_index: None,
103 update_sender,
104 _subscription,
105 }
106 }
107
108 pub fn init(client: &AnyProtoClient) {
109 client.add_entity_request_handler(Self::handle_stage);
110 client.add_entity_request_handler(Self::handle_unstage);
111 client.add_entity_request_handler(Self::handle_commit);
112 client.add_entity_request_handler(Self::handle_reset);
113 client.add_entity_request_handler(Self::handle_show);
114 client.add_entity_request_handler(Self::handle_checkout_files);
115 client.add_entity_request_handler(Self::handle_open_commit_message_buffer);
116 client.add_entity_request_handler(Self::handle_set_index_text);
117 }
118
119 pub fn active_repository(&self) -> Option<Entity<Repository>> {
120 self.active_index
121 .map(|index| self.repositories[index].clone())
122 }
123
124 fn on_worktree_store_event(
125 &mut self,
126 worktree_store: Entity<WorktreeStore>,
127 event: &WorktreeStoreEvent,
128 cx: &mut Context<'_, Self>,
129 ) {
130 let mut new_repositories = Vec::new();
131 let mut new_active_index = None;
132 let this = cx.weak_entity();
133 let client = self.client.clone();
134 let project_id = self.project_id;
135
136 worktree_store.update(cx, |worktree_store, cx| {
137 for worktree in worktree_store.worktrees() {
138 worktree.update(cx, |worktree, cx| {
139 let snapshot = worktree.snapshot();
140 for repo in snapshot.repositories().iter() {
141 let git_repo = worktree
142 .as_local()
143 .and_then(|local_worktree| local_worktree.get_local_repo(repo))
144 .map(|local_repo| local_repo.repo().clone())
145 .map(GitRepo::Local)
146 .or_else(|| {
147 let client = client.clone()?;
148 let project_id = project_id?;
149 Some(GitRepo::Remote {
150 project_id,
151 client,
152 worktree_id: worktree.id(),
153 work_directory_id: repo.work_directory_id(),
154 })
155 });
156 let Some(git_repo) = git_repo else {
157 continue;
158 };
159 let worktree_id = worktree.id();
160 let existing =
161 self.repositories
162 .iter()
163 .enumerate()
164 .find(|(_, existing_handle)| {
165 existing_handle.read(cx).id()
166 == (worktree_id, repo.work_directory_id())
167 });
168 let handle = if let Some((index, handle)) = existing {
169 if self.active_index == Some(index) {
170 new_active_index = Some(new_repositories.len());
171 }
172 // Update the statuses but keep everything else.
173 let existing_handle = handle.clone();
174 existing_handle.update(cx, |existing_handle, _| {
175 existing_handle.repository_entry = repo.clone();
176 });
177 existing_handle
178 } else {
179 cx.new(|_| Repository {
180 git_store: this.clone(),
181 worktree_id,
182 repository_entry: repo.clone(),
183 git_repo,
184 update_sender: self.update_sender.clone(),
185 commit_message_buffer: None,
186 })
187 };
188 new_repositories.push(handle);
189 }
190 })
191 }
192 });
193
194 if new_active_index == None && new_repositories.len() > 0 {
195 new_active_index = Some(0);
196 }
197
198 self.repositories = new_repositories;
199 self.active_index = new_active_index;
200
201 match event {
202 WorktreeStoreEvent::WorktreeUpdatedGitRepositories(_) => {
203 cx.emit(GitEvent::GitStateUpdated);
204 }
205 _ => {
206 cx.emit(GitEvent::FileSystemUpdated);
207 }
208 }
209 }
210
211 pub fn all_repositories(&self) -> Vec<Entity<Repository>> {
212 self.repositories.clone()
213 }
214
215 fn spawn_git_worker(
216 cx: &mut Context<'_, GitStore>,
217 ) -> mpsc::UnboundedSender<(Message, oneshot::Sender<Result<()>>)> {
218 let (update_sender, mut update_receiver) =
219 mpsc::unbounded::<(Message, oneshot::Sender<Result<()>>)>();
220 cx.spawn(|_, cx| async move {
221 while let Some((msg, respond)) = update_receiver.next().await {
222 let result = cx
223 .background_executor()
224 .spawn(Self::process_git_msg(msg))
225 .await;
226 respond.send(result).ok();
227 }
228 })
229 .detach();
230 update_sender
231 }
232
233 async fn process_git_msg(msg: Message) -> Result<()> {
234 match msg {
235 Message::Stage(repo, paths) => {
236 match repo {
237 GitRepo::Local(repo) => repo.stage_paths(&paths)?,
238 GitRepo::Remote {
239 project_id,
240 client,
241 worktree_id,
242 work_directory_id,
243 } => {
244 client
245 .request(proto::Stage {
246 project_id: project_id.0,
247 worktree_id: worktree_id.to_proto(),
248 work_directory_id: work_directory_id.to_proto(),
249 paths: paths
250 .into_iter()
251 .map(|repo_path| repo_path.as_ref().to_proto())
252 .collect(),
253 })
254 .await
255 .context("sending stage request")?;
256 }
257 }
258 Ok(())
259 }
260 Message::Reset {
261 repo,
262 commit,
263 reset_mode,
264 } => {
265 match repo {
266 GitRepo::Local(repo) => repo.reset(&commit, reset_mode)?,
267 GitRepo::Remote {
268 project_id,
269 client,
270 worktree_id,
271 work_directory_id,
272 } => {
273 client
274 .request(proto::GitReset {
275 project_id: project_id.0,
276 worktree_id: worktree_id.to_proto(),
277 work_directory_id: work_directory_id.to_proto(),
278 commit: commit.into(),
279 mode: match reset_mode {
280 ResetMode::Soft => git_reset::ResetMode::Soft.into(),
281 ResetMode::Mixed => git_reset::ResetMode::Mixed.into(),
282 },
283 })
284 .await?;
285 }
286 }
287 Ok(())
288 }
289
290 Message::CheckoutFiles {
291 repo,
292 commit,
293 paths,
294 } => {
295 match repo {
296 GitRepo::Local(repo) => repo.checkout_files(&commit, &paths)?,
297 GitRepo::Remote {
298 project_id,
299 client,
300 worktree_id,
301 work_directory_id,
302 } => {
303 client
304 .request(proto::GitCheckoutFiles {
305 project_id: project_id.0,
306 worktree_id: worktree_id.to_proto(),
307 work_directory_id: work_directory_id.to_proto(),
308 commit: commit.into(),
309 paths: paths
310 .into_iter()
311 .map(|p| p.to_string_lossy().to_string())
312 .collect(),
313 })
314 .await?;
315 }
316 }
317 Ok(())
318 }
319 Message::Unstage(repo, paths) => {
320 match repo {
321 GitRepo::Local(repo) => repo.unstage_paths(&paths)?,
322 GitRepo::Remote {
323 project_id,
324 client,
325 worktree_id,
326 work_directory_id,
327 } => {
328 client
329 .request(proto::Unstage {
330 project_id: project_id.0,
331 worktree_id: worktree_id.to_proto(),
332 work_directory_id: work_directory_id.to_proto(),
333 paths: paths
334 .into_iter()
335 .map(|repo_path| repo_path.as_ref().to_proto())
336 .collect(),
337 })
338 .await
339 .context("sending unstage request")?;
340 }
341 }
342 Ok(())
343 }
344 Message::Commit {
345 git_repo,
346 message,
347 name_and_email,
348 } => {
349 match git_repo {
350 GitRepo::Local(repo) => repo.commit(
351 message.as_ref(),
352 name_and_email
353 .as_ref()
354 .map(|(name, email)| (name.as_ref(), email.as_ref())),
355 )?,
356 GitRepo::Remote {
357 project_id,
358 client,
359 worktree_id,
360 work_directory_id,
361 } => {
362 let (name, email) = name_and_email.unzip();
363 client
364 .request(proto::Commit {
365 project_id: project_id.0,
366 worktree_id: worktree_id.to_proto(),
367 work_directory_id: work_directory_id.to_proto(),
368 message: String::from(message),
369 name: name.map(String::from),
370 email: email.map(String::from),
371 })
372 .await
373 .context("sending commit request")?;
374 }
375 }
376 Ok(())
377 }
378 Message::SetIndexText(git_repo, path, text) => match git_repo {
379 GitRepo::Local(repo) => repo.set_index_text(&path, text),
380 GitRepo::Remote {
381 project_id,
382 client,
383 worktree_id,
384 work_directory_id,
385 } => client.send(proto::SetIndexText {
386 project_id: project_id.0,
387 worktree_id: worktree_id.to_proto(),
388 work_directory_id: work_directory_id.to_proto(),
389 path: path.as_ref().to_proto(),
390 text,
391 }),
392 },
393 }
394 }
395
396 async fn handle_stage(
397 this: Entity<Self>,
398 envelope: TypedEnvelope<proto::Stage>,
399 mut cx: AsyncApp,
400 ) -> Result<proto::Ack> {
401 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
402 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
403 let repository_handle =
404 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
405
406 let entries = envelope
407 .payload
408 .paths
409 .into_iter()
410 .map(PathBuf::from)
411 .map(RepoPath::new)
412 .collect();
413
414 repository_handle
415 .update(&mut cx, |repository_handle, cx| {
416 repository_handle.stage_entries(entries, cx)
417 })?
418 .await?;
419 Ok(proto::Ack {})
420 }
421
422 async fn handle_unstage(
423 this: Entity<Self>,
424 envelope: TypedEnvelope<proto::Unstage>,
425 mut cx: AsyncApp,
426 ) -> Result<proto::Ack> {
427 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
428 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
429 let repository_handle =
430 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
431
432 let entries = envelope
433 .payload
434 .paths
435 .into_iter()
436 .map(PathBuf::from)
437 .map(RepoPath::new)
438 .collect();
439
440 repository_handle
441 .update(&mut cx, |repository_handle, cx| {
442 repository_handle.unstage_entries(entries, cx)
443 })?
444 .await?;
445
446 Ok(proto::Ack {})
447 }
448
449 async fn handle_set_index_text(
450 this: Entity<Self>,
451 envelope: TypedEnvelope<proto::SetIndexText>,
452 mut cx: AsyncApp,
453 ) -> Result<proto::Ack> {
454 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
455 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
456 let repository_handle =
457 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
458
459 repository_handle
460 .update(&mut cx, |repository_handle, _| {
461 repository_handle.set_index_text(
462 &RepoPath::from_str(&envelope.payload.path),
463 envelope.payload.text,
464 )
465 })?
466 .await??;
467 Ok(proto::Ack {})
468 }
469
470 async fn handle_commit(
471 this: Entity<Self>,
472 envelope: TypedEnvelope<proto::Commit>,
473 mut cx: AsyncApp,
474 ) -> Result<proto::Ack> {
475 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
476 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
477 let repository_handle =
478 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
479
480 let message = SharedString::from(envelope.payload.message);
481 let name = envelope.payload.name.map(SharedString::from);
482 let email = envelope.payload.email.map(SharedString::from);
483
484 repository_handle
485 .update(&mut cx, |repository_handle, _| {
486 repository_handle.commit(message, name.zip(email))
487 })?
488 .await??;
489 Ok(proto::Ack {})
490 }
491
492 async fn handle_show(
493 this: Entity<Self>,
494 envelope: TypedEnvelope<proto::GitShow>,
495 mut cx: AsyncApp,
496 ) -> Result<proto::GitCommitDetails> {
497 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
498 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
499 let repository_handle =
500 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
501
502 let commit = repository_handle
503 .update(&mut cx, |repository_handle, cx| {
504 repository_handle.show(&envelope.payload.commit, cx)
505 })?
506 .await?;
507 Ok(proto::GitCommitDetails {
508 sha: commit.sha.into(),
509 message: commit.message.into(),
510 commit_timestamp: commit.commit_timestamp,
511 committer_email: commit.committer_email.into(),
512 committer_name: commit.committer_name.into(),
513 })
514 }
515
516 async fn handle_reset(
517 this: Entity<Self>,
518 envelope: TypedEnvelope<proto::GitReset>,
519 mut cx: AsyncApp,
520 ) -> Result<proto::Ack> {
521 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
522 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
523 let repository_handle =
524 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
525
526 let mode = match envelope.payload.mode() {
527 git_reset::ResetMode::Soft => ResetMode::Soft,
528 git_reset::ResetMode::Mixed => ResetMode::Mixed,
529 };
530
531 repository_handle
532 .update(&mut cx, |repository_handle, _| {
533 repository_handle.reset(&envelope.payload.commit, mode)
534 })?
535 .await??;
536 Ok(proto::Ack {})
537 }
538
539 async fn handle_checkout_files(
540 this: Entity<Self>,
541 envelope: TypedEnvelope<proto::GitCheckoutFiles>,
542 mut cx: AsyncApp,
543 ) -> Result<proto::Ack> {
544 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
545 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
546 let repository_handle =
547 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
548 let paths = envelope
549 .payload
550 .paths
551 .iter()
552 .map(|s| RepoPath::from_str(s))
553 .collect();
554
555 repository_handle
556 .update(&mut cx, |repository_handle, _| {
557 repository_handle.checkout_files(&envelope.payload.commit, paths)
558 })?
559 .await??;
560 Ok(proto::Ack {})
561 }
562
563 async fn handle_open_commit_message_buffer(
564 this: Entity<Self>,
565 envelope: TypedEnvelope<proto::OpenCommitMessageBuffer>,
566 mut cx: AsyncApp,
567 ) -> Result<proto::OpenBufferResponse> {
568 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
569 let work_directory_id = ProjectEntryId::from_proto(envelope.payload.work_directory_id);
570 let repository =
571 Self::repository_for_request(&this, worktree_id, work_directory_id, &mut cx)?;
572 let buffer = repository
573 .update(&mut cx, |repository, cx| {
574 repository.open_commit_buffer(None, this.read(cx).buffer_store.clone(), cx)
575 })?
576 .await?;
577
578 let buffer_id = buffer.read_with(&cx, |buffer, _| buffer.remote_id())?;
579 this.update(&mut cx, |this, cx| {
580 this.buffer_store.update(cx, |buffer_store, cx| {
581 buffer_store
582 .create_buffer_for_peer(
583 &buffer,
584 envelope.original_sender_id.unwrap_or(envelope.sender_id),
585 cx,
586 )
587 .detach_and_log_err(cx);
588 })
589 })?;
590
591 Ok(proto::OpenBufferResponse {
592 buffer_id: buffer_id.to_proto(),
593 })
594 }
595
596 fn repository_for_request(
597 this: &Entity<Self>,
598 worktree_id: WorktreeId,
599 work_directory_id: ProjectEntryId,
600 cx: &mut AsyncApp,
601 ) -> Result<Entity<Repository>> {
602 this.update(cx, |this, cx| {
603 let repository_handle = this
604 .all_repositories()
605 .into_iter()
606 .find(|repository_handle| {
607 repository_handle.read(cx).worktree_id == worktree_id
608 && repository_handle
609 .read(cx)
610 .repository_entry
611 .work_directory_id()
612 == work_directory_id
613 })
614 .context("missing repository handle")?;
615 anyhow::Ok(repository_handle)
616 })?
617 }
618}
619
620impl GitRepo {}
621
622impl Repository {
623 pub fn git_store(&self) -> Option<Entity<GitStore>> {
624 self.git_store.upgrade()
625 }
626
627 fn id(&self) -> (WorktreeId, ProjectEntryId) {
628 (self.worktree_id, self.repository_entry.work_directory_id())
629 }
630
631 pub fn branch(&self) -> Option<&Branch> {
632 self.repository_entry.branch()
633 }
634
635 pub fn display_name(&self, project: &Project, cx: &App) -> SharedString {
636 maybe!({
637 let project_path = self.repo_path_to_project_path(&"".into())?;
638 let worktree_name = project
639 .worktree_for_id(project_path.worktree_id, cx)?
640 .read(cx)
641 .root_name();
642
643 let mut path = PathBuf::new();
644 path = path.join(worktree_name);
645 path = path.join(project_path.path);
646 Some(path.to_string_lossy().to_string())
647 })
648 .unwrap_or_else(|| self.repository_entry.work_directory.display_name())
649 .into()
650 }
651
652 pub fn activate(&self, cx: &mut Context<Self>) {
653 let Some(git_store) = self.git_store.upgrade() else {
654 return;
655 };
656 let entity = cx.entity();
657 git_store.update(cx, |git_store, cx| {
658 let Some(index) = git_store
659 .repositories
660 .iter()
661 .position(|handle| *handle == entity)
662 else {
663 return;
664 };
665 git_store.active_index = Some(index);
666 cx.emit(GitEvent::ActiveRepositoryChanged);
667 });
668 }
669
670 pub fn status(&self) -> impl '_ + Iterator<Item = StatusEntry> {
671 self.repository_entry.status()
672 }
673
674 pub fn has_conflict(&self, path: &RepoPath) -> bool {
675 self.repository_entry
676 .current_merge_conflicts
677 .contains(&path)
678 }
679
680 pub fn repo_path_to_project_path(&self, path: &RepoPath) -> Option<ProjectPath> {
681 let path = self.repository_entry.unrelativize(path)?;
682 Some((self.worktree_id, path).into())
683 }
684
685 pub fn project_path_to_repo_path(&self, path: &ProjectPath) -> Option<RepoPath> {
686 self.worktree_id_path_to_repo_path(path.worktree_id, &path.path)
687 }
688
689 pub fn worktree_id_path_to_repo_path(
690 &self,
691 worktree_id: WorktreeId,
692 path: &Path,
693 ) -> Option<RepoPath> {
694 if worktree_id != self.worktree_id {
695 return None;
696 }
697 self.repository_entry.relativize(path).log_err()
698 }
699
700 pub fn open_commit_buffer(
701 &mut self,
702 languages: Option<Arc<LanguageRegistry>>,
703 buffer_store: Entity<BufferStore>,
704 cx: &mut Context<Self>,
705 ) -> Task<Result<Entity<Buffer>>> {
706 if let Some(buffer) = self.commit_message_buffer.clone() {
707 return Task::ready(Ok(buffer));
708 }
709
710 if let GitRepo::Remote {
711 project_id,
712 client,
713 worktree_id,
714 work_directory_id,
715 } = self.git_repo.clone()
716 {
717 let client = client.clone();
718 cx.spawn(|repository, mut cx| async move {
719 let request = client.request(proto::OpenCommitMessageBuffer {
720 project_id: project_id.0,
721 worktree_id: worktree_id.to_proto(),
722 work_directory_id: work_directory_id.to_proto(),
723 });
724 let response = request.await.context("requesting to open commit buffer")?;
725 let buffer_id = BufferId::new(response.buffer_id)?;
726 let buffer = buffer_store
727 .update(&mut cx, |buffer_store, cx| {
728 buffer_store.wait_for_remote_buffer(buffer_id, cx)
729 })?
730 .await?;
731 if let Some(language_registry) = languages {
732 let git_commit_language =
733 language_registry.language_for_name("Git Commit").await?;
734 buffer.update(&mut cx, |buffer, cx| {
735 buffer.set_language(Some(git_commit_language), cx);
736 })?;
737 }
738 repository.update(&mut cx, |repository, _| {
739 repository.commit_message_buffer = Some(buffer.clone());
740 })?;
741 Ok(buffer)
742 })
743 } else {
744 self.open_local_commit_buffer(languages, buffer_store, cx)
745 }
746 }
747
748 fn open_local_commit_buffer(
749 &mut self,
750 language_registry: Option<Arc<LanguageRegistry>>,
751 buffer_store: Entity<BufferStore>,
752 cx: &mut Context<Self>,
753 ) -> Task<Result<Entity<Buffer>>> {
754 cx.spawn(|repository, mut cx| async move {
755 let buffer = buffer_store
756 .update(&mut cx, |buffer_store, cx| buffer_store.create_buffer(cx))?
757 .await?;
758
759 if let Some(language_registry) = language_registry {
760 let git_commit_language = language_registry.language_for_name("Git Commit").await?;
761 buffer.update(&mut cx, |buffer, cx| {
762 buffer.set_language(Some(git_commit_language), cx);
763 })?;
764 }
765
766 repository.update(&mut cx, |repository, _| {
767 repository.commit_message_buffer = Some(buffer.clone());
768 })?;
769 Ok(buffer)
770 })
771 }
772
773 pub fn checkout_files(
774 &self,
775 commit: &str,
776 paths: Vec<RepoPath>,
777 ) -> oneshot::Receiver<Result<()>> {
778 let (result_tx, result_rx) = futures::channel::oneshot::channel();
779 let commit = commit.to_string().into();
780 self.update_sender
781 .unbounded_send((
782 Message::CheckoutFiles {
783 repo: self.git_repo.clone(),
784 commit,
785 paths,
786 },
787 result_tx,
788 ))
789 .ok();
790 result_rx
791 }
792
793 pub fn reset(&self, commit: &str, reset_mode: ResetMode) -> oneshot::Receiver<Result<()>> {
794 let (result_tx, result_rx) = futures::channel::oneshot::channel();
795 let commit = commit.to_string().into();
796 self.update_sender
797 .unbounded_send((
798 Message::Reset {
799 repo: self.git_repo.clone(),
800 commit,
801 reset_mode,
802 },
803 result_tx,
804 ))
805 .ok();
806 result_rx
807 }
808
809 pub fn show(&self, commit: &str, cx: &Context<Self>) -> Task<Result<CommitDetails>> {
810 let commit = commit.to_string();
811 match self.git_repo.clone() {
812 GitRepo::Local(git_repository) => {
813 let commit = commit.to_string();
814 cx.background_executor()
815 .spawn(async move { git_repository.show(&commit) })
816 }
817 GitRepo::Remote {
818 project_id,
819 client,
820 worktree_id,
821 work_directory_id,
822 } => cx.background_executor().spawn(async move {
823 let resp = client
824 .request(proto::GitShow {
825 project_id: project_id.0,
826 worktree_id: worktree_id.to_proto(),
827 work_directory_id: work_directory_id.to_proto(),
828 commit,
829 })
830 .await?;
831
832 Ok(CommitDetails {
833 sha: resp.sha.into(),
834 message: resp.message.into(),
835 commit_timestamp: resp.commit_timestamp,
836 committer_email: resp.committer_email.into(),
837 committer_name: resp.committer_name.into(),
838 })
839 }),
840 }
841 }
842
843 fn buffer_store(&self, cx: &App) -> Option<Entity<BufferStore>> {
844 Some(self.git_store.upgrade()?.read(cx).buffer_store.clone())
845 }
846
847 pub fn stage_entries(&self, entries: Vec<RepoPath>, cx: &mut App) -> Task<anyhow::Result<()>> {
848 let (result_tx, result_rx) = futures::channel::oneshot::channel();
849 if entries.is_empty() {
850 return Task::ready(Ok(()));
851 }
852
853 let mut save_futures = Vec::new();
854 if let Some(buffer_store) = self.buffer_store(cx) {
855 buffer_store.update(cx, |buffer_store, cx| {
856 for path in &entries {
857 let Some(path) = self.repository_entry.unrelativize(path) else {
858 continue;
859 };
860 let project_path = (self.worktree_id, path).into();
861 if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
862 save_futures.push(buffer_store.save_buffer(buffer, cx));
863 }
864 }
865 })
866 }
867
868 let update_sender = self.update_sender.clone();
869 let git_repo = self.git_repo.clone();
870 cx.spawn(|_| async move {
871 for save_future in save_futures {
872 save_future.await?;
873 }
874 update_sender
875 .unbounded_send((Message::Stage(git_repo, entries), result_tx))
876 .ok();
877 result_rx.await.anyhow()??;
878 Ok(())
879 })
880 }
881
882 pub fn unstage_entries(
883 &self,
884 entries: Vec<RepoPath>,
885 cx: &mut App,
886 ) -> Task<anyhow::Result<()>> {
887 let (result_tx, result_rx) = futures::channel::oneshot::channel();
888 if entries.is_empty() {
889 return Task::ready(Ok(()));
890 }
891
892 let mut save_futures = Vec::new();
893 if let Some(buffer_store) = self.buffer_store(cx) {
894 buffer_store.update(cx, |buffer_store, cx| {
895 for path in &entries {
896 let Some(path) = self.repository_entry.unrelativize(path) else {
897 continue;
898 };
899 let project_path = (self.worktree_id, path).into();
900 if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
901 save_futures.push(buffer_store.save_buffer(buffer, cx));
902 }
903 }
904 })
905 }
906
907 let update_sender = self.update_sender.clone();
908 let git_repo = self.git_repo.clone();
909 cx.spawn(|_| async move {
910 for save_future in save_futures {
911 save_future.await?;
912 }
913 update_sender
914 .unbounded_send((Message::Unstage(git_repo, entries), result_tx))
915 .ok();
916 result_rx.await.anyhow()??;
917 Ok(())
918 })
919 }
920
921 pub fn stage_all(&self, cx: &mut App) -> Task<anyhow::Result<()>> {
922 let to_stage = self
923 .repository_entry
924 .status()
925 .filter(|entry| !entry.status.is_staged().unwrap_or(false))
926 .map(|entry| entry.repo_path.clone())
927 .collect();
928 self.stage_entries(to_stage, cx)
929 }
930
931 pub fn unstage_all(&self, cx: &mut App) -> Task<anyhow::Result<()>> {
932 let to_unstage = self
933 .repository_entry
934 .status()
935 .filter(|entry| entry.status.is_staged().unwrap_or(true))
936 .map(|entry| entry.repo_path.clone())
937 .collect();
938 self.unstage_entries(to_unstage, cx)
939 }
940
941 /// Get a count of all entries in the active repository, including
942 /// untracked files.
943 pub fn entry_count(&self) -> usize {
944 self.repository_entry.status_len()
945 }
946
947 fn have_changes(&self) -> bool {
948 self.repository_entry.status_summary() != GitSummary::UNCHANGED
949 }
950
951 fn have_staged_changes(&self) -> bool {
952 self.repository_entry.status_summary().index != TrackedSummary::UNCHANGED
953 }
954
955 pub fn can_commit(&self, commit_all: bool) -> bool {
956 return self.have_changes() && (commit_all || self.have_staged_changes());
957 }
958
959 pub fn commit(
960 &self,
961 message: SharedString,
962 name_and_email: Option<(SharedString, SharedString)>,
963 ) -> oneshot::Receiver<Result<()>> {
964 let (result_tx, result_rx) = futures::channel::oneshot::channel();
965 self.update_sender
966 .unbounded_send((
967 Message::Commit {
968 git_repo: self.git_repo.clone(),
969 message,
970 name_and_email,
971 },
972 result_tx,
973 ))
974 .ok();
975 result_rx
976 }
977
978 pub fn set_index_text(
979 &self,
980 path: &RepoPath,
981 content: Option<String>,
982 ) -> oneshot::Receiver<anyhow::Result<()>> {
983 let (result_tx, result_rx) = futures::channel::oneshot::channel();
984 self.update_sender
985 .unbounded_send((
986 Message::SetIndexText(self.git_repo.clone(), path.clone(), content),
987 result_tx,
988 ))
989 .ok();
990 result_rx
991 }
992}