gnostr_asyncgit/sync/
status.rs1use 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#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
16pub enum StatusItemType {
17 New,
19 Modified,
21 Deleted,
23 Renamed,
25 Typechange,
27 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#[derive(Clone, Hash, PartialEq, Eq, Debug)]
63pub struct StatusItem {
64 pub path: String,
66 pub status: StatusItemType,
68}
69
70#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
72pub enum StatusType {
73 WorkingDir,
75 Stage,
77 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
97pub 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
127pub 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}