vinxen_git/
status.rs

1use crate::Result;
2use git2::{Repository, Status, StatusShow};
3use serde::{Deserialize, Serialize};
4use std::path::Path;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum StatusKind {
8    Modified,
9    Added,
10    Deleted,
11    Renamed,
12    Untracked,
13    Ignored,
14    Conflicted,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct FileStatus {
19    pub path: String,
20    pub kind: StatusKind,
21    pub staged: bool,
22}
23
24#[derive(Debug, Clone, Default, Serialize, Deserialize)]
25pub struct RepoStatus {
26    pub files: Vec<FileStatus>,
27    pub staged_count: usize,
28    pub modified_count: usize,
29    pub untracked_count: usize,
30}
31
32impl RepoStatus {
33    pub fn collect(repo: &Repository, _workdir: &Path) -> Result<Self> {
34        let mut files = Vec::new();
35        let mut staged_count = 0;
36        let mut modified_count = 0;
37        let mut untracked_count = 0;
38
39        let statuses = repo.statuses(Some(
40            git2::StatusOptions::new()
41                .show(StatusShow::IndexAndWorkdir)
42                .include_ignored(false)
43                .recurse_untracked_dirs(false),
44        ))?;
45
46        for status in statuses.iter() {
47            let path = status.path().unwrap_or("").to_string();
48            let status_flags = status.status();
49
50            let is_staged = status_flags.intersects(
51                Status::INDEX_NEW
52                    | Status::INDEX_MODIFIED
53                    | Status::INDEX_DELETED
54                    | Status::INDEX_RENAMED
55                    | Status::INDEX_TYPECHANGE,
56            );
57
58            let kind = if status_flags.intersects(Status::CONFLICTED) {
59                StatusKind::Conflicted
60            } else if status_flags.intersects(Status::WT_NEW) {
61                StatusKind::Untracked
62            } else if status_flags.intersects(Status::WT_MODIFIED) {
63                StatusKind::Modified
64            } else if status_flags.intersects(Status::WT_DELETED) {
65                StatusKind::Deleted
66            } else if status_flags.intersects(Status::WT_RENAMED) {
67                StatusKind::Renamed
68            } else if status_flags.intersects(Status::INDEX_NEW) {
69                StatusKind::Added
70            } else if status_flags.intersects(Status::INDEX_MODIFIED) {
71                StatusKind::Modified
72            } else if status_flags.intersects(Status::INDEX_DELETED) {
73                StatusKind::Deleted
74            } else if status_flags.intersects(Status::INDEX_RENAMED) {
75                StatusKind::Renamed
76            } else if status_flags.intersects(Status::INDEX_TYPECHANGE) {
77                StatusKind::Modified
78            } else {
79                continue;
80            };
81
82            if is_staged {
83                staged_count += 1;
84            }
85            match kind {
86                StatusKind::Modified
87                | StatusKind::Deleted
88                | StatusKind::Renamed
89                | StatusKind::Conflicted => modified_count += 1,
90                StatusKind::Added | StatusKind::Untracked => untracked_count += 1,
91                StatusKind::Ignored => {}
92            }
93
94            files.push(FileStatus {
95                path,
96                kind,
97                staged: is_staged,
98            });
99        }
100
101        Ok(Self {
102            files,
103            staged_count,
104            modified_count,
105            untracked_count,
106        })
107    }
108
109    pub fn is_clean(&self) -> bool {
110        self.files.is_empty()
111    }
112
113    pub fn has_staged(&self) -> bool {
114        self.staged_count > 0
115    }
116
117    pub fn has_unstaged(&self) -> bool {
118        self.modified_count > 0 || self.untracked_count > 0
119    }
120}