1use std::sync::Arc;
2
3use futures::channel::mpsc;
4use futures::StreamExt as _;
5use git::repository::{GitRepository, RepoPath};
6use gpui::{AppContext, SharedString};
7use settings::WorktreeId;
8use util::ResultExt as _;
9use worktree::RepositoryEntry;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum StatusAction {
13 Stage,
14 Unstage,
15}
16
17pub struct GitState {
18 /// The current commit message being composed.
19 pub commit_message: Option<SharedString>,
20
21 /// When a git repository is selected, this is used to track which repository's changes
22 /// are currently being viewed or modified in the UI.
23 pub active_repository: Option<(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)>,
24
25 pub update_sender: mpsc::UnboundedSender<(Arc<dyn GitRepository>, Vec<RepoPath>, StatusAction)>,
26}
27
28impl GitState {
29 pub fn new(cx: &AppContext) -> Self {
30 let (tx, mut rx) =
31 mpsc::unbounded::<(Arc<dyn GitRepository>, Vec<RepoPath>, StatusAction)>();
32 cx.spawn(|cx| async move {
33 while let Some((git_repo, paths, action)) = rx.next().await {
34 cx.background_executor()
35 .spawn(async move {
36 match action {
37 StatusAction::Stage => git_repo.stage_paths(&paths),
38 StatusAction::Unstage => git_repo.unstage_paths(&paths),
39 }
40 })
41 .await
42 .log_err();
43 }
44 })
45 .detach();
46 GitState {
47 commit_message: None,
48 active_repository: None,
49 update_sender: tx,
50 }
51 }
52
53 pub fn activate_repository(
54 &mut self,
55 worktree_id: WorktreeId,
56 active_repository: RepositoryEntry,
57 git_repo: Arc<dyn GitRepository>,
58 ) {
59 self.active_repository = Some((worktree_id, active_repository, git_repo));
60 }
61
62 pub fn active_repository(
63 &self,
64 ) -> Option<&(WorktreeId, RepositoryEntry, Arc<dyn GitRepository>)> {
65 self.active_repository.as_ref()
66 }
67
68 pub fn commit_message(&mut self, message: Option<SharedString>) {
69 self.commit_message = message;
70 }
71
72 pub fn clear_commit_message(&mut self) {
73 self.commit_message = None;
74 }
75
76 pub fn stage_entry(&mut self, repo_path: RepoPath) {
77 if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
78 let _ = self.update_sender.unbounded_send((
79 git_repo.clone(),
80 vec![repo_path],
81 StatusAction::Stage,
82 ));
83 }
84 }
85
86 pub fn unstage_entry(&mut self, repo_path: RepoPath) {
87 if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
88 let _ = self.update_sender.unbounded_send((
89 git_repo.clone(),
90 vec![repo_path],
91 StatusAction::Unstage,
92 ));
93 }
94 }
95
96 pub fn stage_entries(&mut self, entries: Vec<RepoPath>) {
97 if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
98 let _ =
99 self.update_sender
100 .unbounded_send((git_repo.clone(), entries, StatusAction::Stage));
101 }
102 }
103
104 fn act_on_all(&mut self, action: StatusAction) {
105 if let Some((_, active_repository, git_repo)) = self.active_repository.as_ref() {
106 let _ = self.update_sender.unbounded_send((
107 git_repo.clone(),
108 active_repository
109 .status()
110 .map(|entry| entry.repo_path)
111 .collect(),
112 action,
113 ));
114 }
115 }
116
117 pub fn stage_all(&mut self) {
118 self.act_on_all(StatusAction::Stage);
119 }
120
121 pub fn unstage_all(&mut self) {
122 self.act_on_all(StatusAction::Unstage);
123 }
124}