grass/dev/public/changes.rs
1use crate::dev::{
2 error::GrassError,
3 strategy::{
4 alias::{AliasStrategy, SupportsAlias},
5 discovery::{DiscoveryStrategy, DiscoveryStrategyError, SupportsDiscovery},
6 git::{
7 GitStrategy, GitStrategyError, RepositoryChangeStatus, RepositoryChangeStatusWithError,
8 SupportsGit,
9 },
10 },
11 Api, Category, RepositoryLocation,
12};
13
14#[derive(Debug, PartialEq, Eq, Hash)]
15pub struct ChangeStatusResult {
16 pub location: Option<RepositoryLocation>,
17 pub change_status: RepositoryChangeStatusWithError,
18}
19
20/// Get the change status of a specific repository
21///
22/// # Example
23///
24/// ```rust
25/// # use grass::dev::{Api, use_mock_strategy};
26/// # use grass::dev::strategy::git::RepositoryChangeStatus;
27/// # let api = use_mock_strategy();
28/// let api: Api<_> = api;
29///
30/// let no_changes =
31/// grass::dev::get_repository_change_status(&api, ("with_changes", "first"))
32/// .unwrap();
33/// let no_repository =
34/// grass::dev::get_repository_change_status(&api, ("with_changes", "second"))
35/// .unwrap();
36/// let with_changes =
37/// grass::dev::get_repository_change_status(&api, ("with_changes", "third"))
38/// .unwrap();
39///
40/// assert_eq!(no_changes, RepositoryChangeStatus::UpToDate);
41/// assert_eq!(no_repository, RepositoryChangeStatus::NoRepository);
42/// assert_eq!(
43/// with_changes,
44/// RepositoryChangeStatus::UncommittedChanges { num_changes: 9 }
45/// );
46/// ```
47pub fn get_repository_change_status<T, U>(
48 api: &Api<T>,
49 repository: U,
50) -> Result<RepositoryChangeStatus, GitStrategyError>
51where
52 T: SupportsGit + SupportsAlias,
53 U: Into<RepositoryLocation>,
54{
55 api.get_git_strategy()
56 .get_changes(api.get_alias_strategy().resolve_alias(repository.into())?)
57}
58
59fn location_result_to_change_status_result<T: GitStrategy>(
60 value: (Result<RepositoryLocation, DiscoveryStrategyError>, &T),
61) -> ChangeStatusResult {
62 let (location, git) = value;
63
64 let location = match location {
65 Ok(location) => location.clone(),
66 Err(error) => {
67 return ChangeStatusResult {
68 location: None,
69 change_status: error.into(),
70 }
71 }
72 };
73 ChangeStatusResult {
74 location: Some(location.clone()),
75 change_status: git.get_changes(location).into(),
76 }
77}
78
79fn filter_away_up_to_date_repositories(value: &ChangeStatusResult) -> bool {
80 !matches!(
81 value,
82 ChangeStatusResult {
83 change_status: RepositoryChangeStatusWithError::UpToDate,
84 ..
85 }
86 )
87}
88
89/// List all repositories, including their change status.
90///
91/// This will also list repositories which are up to date.
92///
93/// # Example
94///
95/// ```rust
96/// # use std::collections::HashSet;
97/// #
98/// # use grass::dev::{
99/// # self,
100/// # ChangeStatusResult,
101/// # strategy::{
102/// # api::MockApiStrategy,
103/// # discovery::SupportsDiscovery,
104/// # git::{RepositoryChangeStatusWithError, SupportsGit},
105/// # },
106/// # Api,
107/// # };
108/// #
109/// # let api = Api::from(MockApiStrategy::default());
110/// #
111/// fn test_api<T: SupportsGit + SupportsDiscovery>(api: &Api<T>) {
112/// let repositories: HashSet<_> =
113/// dev::list_repositories_with_change_status_next(api).unwrap();
114///
115/// assert!(repositories.contains(&ChangeStatusResult {
116/// location: Some(("with_changes", "first").into()),
117/// change_status: RepositoryChangeStatusWithError::UpToDate,
118/// }));
119///
120/// assert!(repositories.contains(&ChangeStatusResult {
121/// location: Some(("with_changes", "second").into()),
122/// change_status: RepositoryChangeStatusWithError::NoRepository,
123/// }));
124///
125/// assert!(repositories.contains(&ChangeStatusResult {
126/// location: Some(("with_changes", "third").into()),
127/// change_status: RepositoryChangeStatusWithError::UncommittedChanges {
128/// num_changes: 9
129/// },
130/// }));
131/// }
132///
133/// test_api(&api)
134/// ```
135pub fn list_repositories_with_change_status<T, U>(api: &Api<T>) -> Result<U, GrassError>
136where
137 T: SupportsGit + SupportsDiscovery,
138 U: FromIterator<ChangeStatusResult>,
139{
140 let git = api.get_git_strategy();
141 let discovery = api.get_discovery_strategy();
142
143 let categories: Vec<_> = discovery.list_categories()?;
144 Ok(categories
145 .iter()
146 .filter_map(|category| discovery.list_repositories_in_category(category).ok())
147 .flat_map(|repositories| {
148 repositories
149 .map(|repository| (repository, git))
150 .map(location_result_to_change_status_result)
151 })
152 .collect())
153}
154
155/// List all repositories with uncommitted changes, including their change status.
156///
157/// This won't list repositories that are up to date.
158///
159/// # Example
160///
161/// ```rust
162/// # use std::collections::HashSet;
163/// #
164/// # use grass::dev::{
165/// # self,
166/// # ChangeStatusResult,
167/// # strategy::{
168/// # api::MockApiStrategy,
169/// # discovery::SupportsDiscovery,
170/// # git::{RepositoryChangeStatusWithError, SupportsGit},
171/// # },
172/// # Api,
173/// # };
174/// #
175/// # let api = Api::from(MockApiStrategy::default());
176/// #
177/// fn test_api<T: SupportsGit + SupportsDiscovery>(api: &Api<T>) {
178/// let repositories: HashSet<_> =
179/// dev::list_repositories_with_uncommitted_changes(api).unwrap();
180///
181/// assert!(!repositories.contains(&ChangeStatusResult {
182/// location: Some(("with_changes", "first").into()),
183/// change_status: RepositoryChangeStatusWithError::UpToDate,
184/// }));
185///
186/// assert!(repositories.contains(&ChangeStatusResult {
187/// location: Some(("with_changes", "second").into()),
188/// change_status: RepositoryChangeStatusWithError::NoRepository,
189/// }));
190///
191/// assert!(repositories.contains(&ChangeStatusResult {
192/// location: Some(("with_changes", "third").into()),
193/// change_status: RepositoryChangeStatusWithError::UncommittedChanges {
194/// num_changes: 9
195/// },
196/// }));
197/// }
198///
199/// test_api(&api)
200/// ```
201pub fn list_repositories_with_uncommitted_changes<T, U>(api: &Api<T>) -> Result<U, GrassError>
202where
203 T: SupportsGit + SupportsDiscovery,
204 U: FromIterator<ChangeStatusResult>,
205{
206 let git = api.get_git_strategy();
207 let discovery = api.get_discovery_strategy();
208
209 let categories: Vec<_> = discovery.list_categories()?;
210 Ok(categories
211 .iter()
212 .filter_map(|category| discovery.list_repositories_in_category(category).ok())
213 .flat_map(|repositories| {
214 repositories
215 .map(|repository| (repository, git))
216 .map(location_result_to_change_status_result)
217 .filter(filter_away_up_to_date_repositories)
218 })
219 .collect())
220}
221
222/// List all repositories in a specified category, including their change status.
223///
224/// This will also list repositories which are up to date.
225///
226/// # Example
227///
228/// ```rust
229/// # use std::collections::HashSet;
230/// #
231/// # use grass::dev::{
232/// # self,
233/// # ChangeStatusResult,
234/// # strategy::{
235/// # alias::SupportsAlias,
236/// # api::MockApiStrategy,
237/// # discovery::SupportsDiscovery,
238/// # git::{RepositoryChangeStatusWithError, SupportsGit},
239/// # },
240/// # Api,
241/// # };
242/// #
243/// # let api = Api::from(MockApiStrategy::default());
244/// #
245/// fn test_api<T: SupportsGit + SupportsDiscovery + SupportsAlias>(api: &Api<T>) {
246/// let repositories: HashSet<_> =
247/// dev::list_repositories_with_change_status_in_category(api, "with_changes").unwrap();
248///
249/// assert!(repositories.contains(&ChangeStatusResult {
250/// location: Some(("with_changes", "second").into()),
251/// change_status: RepositoryChangeStatusWithError::NoRepository,
252/// }));
253///
254/// assert!(!repositories.contains(&ChangeStatusResult {
255/// location: Some(("all_good", "second").into()),
256/// change_status: RepositoryChangeStatusWithError::UncommittedChanges {
257/// num_changes: 9
258/// },
259/// }));
260/// }
261///
262/// test_api(&api)
263/// ```
264pub fn list_repositories_with_change_status_in_category<T, U, V>(
265 api: &Api<T>,
266 category: U,
267) -> Result<V, GrassError>
268where
269 T: SupportsGit + SupportsAlias + SupportsDiscovery,
270 U: Into<Category>,
271 V: FromIterator<ChangeStatusResult>,
272{
273 let alias = api.get_alias_strategy();
274 let git = api.get_git_strategy();
275 let discovery = api.get_discovery_strategy();
276
277 let category: Category = alias.resolve_alias(category.into().as_ref())?.into();
278
279 let repositories = discovery.list_repositories_in_category::<Category>(category)?;
280
281 Ok(repositories
282 .map(|repository| (repository, git))
283 .map(location_result_to_change_status_result)
284 .collect())
285}
286
287/// List all repositories with uncommitted changes, in a specific category, including their change status.
288///
289/// This won't list repositories that are up to date.
290///
291/// # Example
292///
293/// ```rust
294/// # use std::collections::HashSet;
295/// #
296/// # use grass::dev::{
297/// # self,
298/// # ChangeStatusResult,
299/// # strategy::{
300/// # alias::SupportsAlias,
301/// # api::MockApiStrategy,
302/// # discovery::SupportsDiscovery,
303/// # git::{RepositoryChangeStatusWithError, SupportsGit},
304/// # },
305/// # Api,
306/// # };
307/// #
308/// # let api = Api::from(MockApiStrategy::default());
309/// #
310/// fn test_api<T: SupportsGit + SupportsDiscovery + SupportsAlias>(api: &Api<T>) {
311/// let repositories: HashSet<_> =
312/// dev::list_repositories_with_uncommitted_changes_in_category(api, "with_changes").unwrap();
313///
314/// assert!(!repositories.contains(&ChangeStatusResult {
315/// location: Some(("with_changes", "first").into()),
316/// change_status: RepositoryChangeStatusWithError::UpToDate,
317/// }));
318///
319/// assert!(repositories.contains(&ChangeStatusResult {
320/// location: Some(("with_changes", "second").into()),
321/// change_status: RepositoryChangeStatusWithError::NoRepository,
322/// }));
323///
324/// assert!(repositories.contains(&ChangeStatusResult {
325/// location: Some(("with_changes", "third").into()),
326/// change_status: RepositoryChangeStatusWithError::UncommittedChanges {
327/// num_changes: 9
328/// },
329/// }));
330/// }
331///
332/// test_api(&api)
333/// ```
334pub fn list_repositories_with_uncommitted_changes_in_category<T, U, V>(
335 api: &Api<T>,
336 category: U,
337) -> Result<V, GrassError>
338where
339 T: SupportsGit + SupportsAlias + SupportsDiscovery,
340 U: Into<Category>,
341 V: FromIterator<ChangeStatusResult>,
342{
343 let alias = api.get_alias_strategy();
344 let git = api.get_git_strategy();
345 let discovery = api.get_discovery_strategy();
346
347 let category: Category = alias.resolve_alias(category.into().as_ref())?.into();
348
349 let repositories = discovery.list_repositories_in_category::<Category>(category)?;
350
351 Ok(repositories
352 .map(|repository| (repository, git))
353 .map(location_result_to_change_status_result)
354 .filter(filter_away_up_to_date_repositories)
355 .collect())
356}