gnostr_asyncgit/sync/
status.rs

1//! sync git api for fetching a status
2
3use std::path::Path;
4
5use git2::{Delta, Status, StatusOptions, StatusShow};
6use scopetime::scope_time;
7
8use super::{RepoPath, ShowUntrackedFilesConfig};
9use crate::{
10    error::{Error, Result},
11    sync::{config::untracked_files_config_repo, repository::repo},
12};
13
14///
15#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
16pub enum StatusItemType {
17    ///
18    New,
19    ///
20    Modified,
21    ///
22    Deleted,
23    ///
24    Renamed,
25    ///
26    Typechange,
27    ///
28    Conflicted,
29}
30
31impl From<Status> for StatusItemType {
32    fn from(s: Status) -> Self {
33        if s.is_index_new() || s.is_wt_new() {
34            Self::New
35        } else if s.is_index_deleted() || s.is_wt_deleted() {
36            Self::Deleted
37        } else if s.is_index_renamed() || s.is_wt_renamed() {
38            Self::Renamed
39        } else if s.is_index_typechange() || s.is_wt_typechange() {
40            Self::Typechange
41        } else if s.is_conflicted() {
42            Self::Conflicted
43        } else {
44            Self::Modified
45        }
46    }
47}
48
49impl From<Delta> for StatusItemType {
50    fn from(d: Delta) -> Self {
51        match d {
52            Delta::Added => Self::New,
53            Delta::Deleted => Self::Deleted,
54            Delta::Renamed => Self::Renamed,
55            Delta::Typechange => Self::Typechange,
56            _ => Self::Modified,
57        }
58    }
59}
60
61///
62#[derive(Clone, Hash, PartialEq, Eq, Debug)]
63pub struct StatusItem {
64    ///
65    pub path: String,
66    ///
67    pub status: StatusItemType,
68}
69
70///
71#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
72pub enum StatusType {
73    ///
74    WorkingDir,
75    ///
76    Stage,
77    ///
78    Both,
79}
80
81impl Default for StatusType {
82    fn default() -> Self {
83        Self::WorkingDir
84    }
85}
86
87impl From<StatusType> for StatusShow {
88    fn from(s: StatusType) -> Self {
89        match s {
90            StatusType::WorkingDir => Self::Workdir,
91            StatusType::Stage => Self::Index,
92            StatusType::Both => Self::IndexAndWorkdir,
93        }
94    }
95}
96
97///
98pub fn is_workdir_clean(
99    repo_path: &RepoPath,
100    show_untracked: Option<ShowUntrackedFilesConfig>,
101) -> Result<bool> {
102    let repo = repo(repo_path)?;
103
104    if repo.is_bare() && !repo.is_worktree() {
105        return Ok(true);
106    }
107
108    let show_untracked = if let Some(config) = show_untracked {
109        config
110    } else {
111        untracked_files_config_repo(&repo)?
112    };
113
114    let mut options = StatusOptions::default();
115    options
116        .show(StatusShow::Workdir)
117        .update_index(true)
118        .include_untracked(show_untracked.include_untracked())
119        .renames_head_to_index(true)
120        .recurse_untracked_dirs(show_untracked.recurse_untracked_dirs());
121
122    let statuses = repo.statuses(Some(&mut options))?;
123
124    Ok(statuses.is_empty())
125}
126
127/// guarantees sorting
128pub fn get_status(
129    repo_path: &RepoPath,
130    status_type: StatusType,
131    show_untracked: Option<ShowUntrackedFilesConfig>,
132) -> Result<Vec<StatusItem>> {
133    scope_time!("get_status");
134
135    let repo = repo(repo_path)?;
136
137    if repo.is_bare() && !repo.is_worktree() {
138        return Ok(Vec::new());
139    }
140
141    let show_untracked = if let Some(config) = show_untracked {
142        config
143    } else {
144        untracked_files_config_repo(&repo)?
145    };
146
147    let mut options = StatusOptions::default();
148    options
149        .show(status_type.into())
150        .update_index(true)
151        .include_untracked(show_untracked.include_untracked())
152        .renames_head_to_index(true)
153        .recurse_untracked_dirs(show_untracked.recurse_untracked_dirs());
154
155    let statuses = repo.statuses(Some(&mut options))?;
156
157    let mut res = Vec::with_capacity(statuses.len());
158
159    for e in statuses.iter() {
160        let status: Status = e.status();
161
162        let path = match e.head_to_index() {
163            Some(diff) => diff
164                .new_file()
165                .path()
166                .and_then(Path::to_str)
167                .map(String::from)
168                .ok_or_else(|| {
169                    Error::Generic("failed to get path to diff's new file.".to_string())
170                })?,
171            None => e.path().map(String::from).ok_or_else(|| {
172                Error::Generic("failed to get the path to indexed file.".to_string())
173            })?,
174        };
175
176        res.push(StatusItem {
177            path,
178            status: StatusItemType::from(status),
179        });
180    }
181
182    res.sort_by(|a, b| Path::new(a.path.as_str()).cmp(Path::new(b.path.as_str())));
183
184    Ok(res)
185}