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(
121			show_untracked.recurse_untracked_dirs(),
122		);
123
124	let statuses = repo.statuses(Some(&mut options))?;
125
126	Ok(statuses.is_empty())
127}
128
129/// guarantees sorting
130pub fn get_status(
131	repo_path: &RepoPath,
132	status_type: StatusType,
133	show_untracked: Option<ShowUntrackedFilesConfig>,
134) -> Result<Vec<StatusItem>> {
135	scope_time!("get_status");
136
137	let repo = repo(repo_path)?;
138
139	if repo.is_bare() && !repo.is_worktree() {
140		return Ok(Vec::new());
141	}
142
143	let show_untracked = if let Some(config) = show_untracked {
144		config
145	} else {
146		untracked_files_config_repo(&repo)?
147	};
148
149	let mut options = StatusOptions::default();
150	options
151		.show(status_type.into())
152		.update_index(true)
153		.include_untracked(show_untracked.include_untracked())
154		.renames_head_to_index(true)
155		.recurse_untracked_dirs(
156			show_untracked.recurse_untracked_dirs(),
157		);
158
159	let statuses = repo.statuses(Some(&mut options))?;
160
161	let mut res = Vec::with_capacity(statuses.len());
162
163	for e in statuses.iter() {
164		let status: Status = e.status();
165
166		let path = match e.head_to_index() {
167			Some(diff) => diff
168				.new_file()
169				.path()
170				.and_then(Path::to_str)
171				.map(String::from)
172				.ok_or_else(|| {
173					Error::Generic(
174						"failed to get path to diff's new file."
175							.to_string(),
176					)
177				})?,
178			None => e.path().map(String::from).ok_or_else(|| {
179				Error::Generic(
180					"failed to get the path to indexed file."
181						.to_string(),
182				)
183			})?,
184		};
185
186		res.push(StatusItem {
187			path,
188			status: StatusItemType::from(status),
189		});
190	}
191
192	res.sort_by(|a, b| {
193		Path::new(a.path.as_str()).cmp(Path::new(b.path.as_str()))
194	});
195
196	Ok(res)
197}