grass/dev/strategy/git/
local.rs1use git2::{build::RepoBuilder, ErrorCode, Repository, Status};
2use itertools::Itertools;
3use tracing::trace;
4
5use crate::dev::strategy::path::{PathStrategy, PathStrategyError};
6
7use super::{GitStrategy, GitStrategyError, RepositoryChangeStatus, RepositoryLocation, Result};
8
9pub struct LocalGitStrategy<'a, T: PathStrategy> {
10 path_strategy: &'a T,
11}
12
13impl<'a, T: PathStrategy> LocalGitStrategy<'a, T> {
14 pub fn new(path_strategy: &'a T) -> Self {
15 Self { path_strategy }
16 }
17}
18
19impl<'a, T: PathStrategy> GitStrategy for LocalGitStrategy<'a, T> {
20 fn clean<U>(&self, repository: U) -> Result<()>
21 where
22 U: Into<RepositoryLocation>,
23 {
24 let repository_path = self.path_strategy.get_directory(repository)?;
25
26 let repository = Repository::open(&repository_path)?;
27
28 let statuses = repository.statuses(None)?;
29
30 let results: Vec<_> = statuses
31 .iter()
32 .filter_map(|entry| match entry.path() {
33 Some(file_path) if entry.status().is_ignored() => Some(file_path.to_owned()),
34 _ => None,
35 })
36 .map(|file| {
37 let path = repository_path.join(file);
38 if path.is_dir() {
39 std::fs::remove_dir_all(path)
40 } else {
41 std::fs::remove_file(path)
42 }
43 })
44 .filter_map(std::result::Result::err)
45 .map(|error| error.to_string())
46 .collect();
47
48 match results.len() {
49 0 => Ok(()),
50 _ => Err(GitStrategyError::FileSystemError {
51 message: "Something went wrong when trying to clean a repository".into(),
52 reason: results.iter().map(ToString::to_string).join("\n"),
53 reasons: results,
54 }),
55 }
56 }
57
58 fn clone<U, V>(&self, repository: U, remote: V) -> Result<()>
59 where
60 U: Into<RepositoryLocation>,
61 V: AsRef<str>,
62 {
63 let repo_path = self.path_strategy.get_directory(repository)?;
64
65 RepoBuilder::new().clone(remote.as_ref(), &repo_path)?;
66
67 Ok(())
68 }
69
70 fn get_changes<U>(&self, repository: U) -> Result<RepositoryChangeStatus>
71 where
72 U: Into<RepositoryLocation>,
73 {
74 let repository_location = repository.into();
75 let repository_path = self
76 .path_strategy
77 .get_directory(repository_location.clone())?;
78
79 let repository = match Repository::open(repository_path) {
80 Ok(repository) => Ok(repository),
81 Err(error) => {
82 if error.code() == ErrorCode::NotFound {
83 trace!(
84 "Repository '{}' not found, assumed up to date",
85 repository_location
86 );
87 return Ok(RepositoryChangeStatus::UpToDate);
88 }
89
90 Err(error)
91 }
92 };
93
94 let repository = repository.map_err(|error| {
95 GitStrategyError::from(error).with_message("There was a problem opening the repository")
96 })?;
97
98 let statuses = repository.statuses(None).map_err(|error| {
99 GitStrategyError::from(error)
100 .with_message("There was a problem retrieving the repository status")
101 })?;
102
103 let changes: Vec<_> = statuses
104 .iter()
105 .filter(|status| status.status().contains(Status::IGNORED))
106 .collect();
107
108 if changes.is_empty() {
109 trace!("Repository '{}' has no changes", repository_location);
110 return Ok(RepositoryChangeStatus::UpToDate);
111 }
112
113 trace!(
114 "Repository '{}' has ({}) changes",
115 repository_location,
116 changes.len()
117 );
118 Ok(RepositoryChangeStatus::UncommittedChanges {
119 num_changes: changes.len(),
120 })
121 }
122}
123
124impl From<PathStrategyError> for GitStrategyError {
125 fn from(value: PathStrategyError) -> Self {
126 match value {
127 PathStrategyError::RepositoryNotFound { context, reason } => {
128 GitStrategyError::RepositoryNotFound {
129 message: context,
130 reason,
131 }
132 }
133 PathStrategyError::FileDoesNotExist { context, reason } => {
134 GitStrategyError::FileSystemError {
135 message: context,
136 reason,
137 reasons: Vec::new(),
138 }
139 }
140 PathStrategyError::Unknown { context, reason } => GitStrategyError::UnknownError {
141 message: context,
142 reason,
143 },
144 }
145 }
146}
147
148impl From<git2::Error> for GitStrategyError {
149 fn from(value: git2::Error) -> Self {
150 let reason = value.message().to_string();
151 match value.code() {
152 git2::ErrorCode::NotFound => GitStrategyError::RepositoryNotFound {
153 message: "There was a problem running an unspecified git2 command".into(),
154 reason,
155 },
156
157 git2::ErrorCode::Exists => GitStrategyError::RepositryExists {
158 message: "There was a problem running an unspecified git2 command".into(),
159 reason,
160 },
161
162 git2::ErrorCode::Ambiguous
163 | git2::ErrorCode::BufSize
164 | git2::ErrorCode::User
165 | git2::ErrorCode::BareRepo
166 | git2::ErrorCode::UnbornBranch
167 | git2::ErrorCode::Unmerged
168 | git2::ErrorCode::NotFastForward
169 | git2::ErrorCode::InvalidSpec
170 | git2::ErrorCode::Conflict
171 | git2::ErrorCode::Locked
172 | git2::ErrorCode::Modified
173 | git2::ErrorCode::Applied
174 | git2::ErrorCode::Peel
175 | git2::ErrorCode::Eof
176 | git2::ErrorCode::Invalid
177 | git2::ErrorCode::Uncommitted
178 | git2::ErrorCode::Directory
179 | git2::ErrorCode::MergeConflict
180 | git2::ErrorCode::HashsumMismatch
181 | git2::ErrorCode::IndexDirty
182 | git2::ErrorCode::ApplyFail
183 | git2::ErrorCode::Owner => GitStrategyError::RepositoryError {
184 message: "There was a problem running an unspecified git2 command".into(),
185 reason,
186 },
187
188 git2::ErrorCode::Auth | git2::ErrorCode::Certificate => {
189 GitStrategyError::RemoteAuthenticationError {
190 message: "There was a problem running an unspecified git2 command".into(),
191 reason,
192 }
193 }
194
195 _ => GitStrategyError::UnknownError {
196 message: "There was a problem running an unspecified git2 command".into(),
197 reason,
198 },
199 }
200 }
201}