git.rs

  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    fn act_on_entries(&self, entries: Vec<RepoPath>, action: StatusAction) {
 77        if entries.is_empty() {
 78            return;
 79        }
 80        if let Some((_, _, git_repo)) = self.active_repository.as_ref() {
 81            let _ = self
 82                .update_sender
 83                .unbounded_send((git_repo.clone(), entries, action));
 84        }
 85    }
 86
 87    pub fn stage_entries(&self, entries: Vec<RepoPath>) {
 88        self.act_on_entries(entries, StatusAction::Stage);
 89    }
 90
 91    pub fn unstage_entries(&self, entries: Vec<RepoPath>) {
 92        self.act_on_entries(entries, StatusAction::Unstage);
 93    }
 94
 95    pub fn stage_all(&self) {
 96        let Some((_, entry, _)) = self.active_repository.as_ref() else {
 97            return;
 98        };
 99        let to_stage = entry
100            .status()
101            .filter(|entry| !entry.status.is_staged().unwrap_or(false))
102            .map(|entry| entry.repo_path.clone())
103            .collect();
104        self.stage_entries(to_stage);
105    }
106
107    pub fn unstage_all(&self) {
108        let Some((_, entry, _)) = self.active_repository.as_ref() else {
109            return;
110        };
111        let to_unstage = entry
112            .status()
113            .filter(|entry| entry.status.is_staged().unwrap_or(true))
114            .map(|entry| entry.repo_path.clone())
115            .collect();
116        self.unstage_entries(to_unstage);
117    }
118
119    /// Get a count of all entries in the active repository, including
120    /// untracked files.
121    pub fn entry_count(&self) -> usize {
122        self.active_repository
123            .as_ref()
124            .map_or(0, |(_, entry, _)| entry.status_len())
125    }
126}