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}