1use std::{collections::BTreeMap, num::NonZero, path::PathBuf, str::FromStr, sync::Arc};
5
6use anyhow::Result;
7use itertools::Itertools;
8use run_git::git::GitWorkingDir;
9
10use crate::{
11 git::GitHash,
12 run::{
13 working_directory::{
14 REMOTE_NAME, WorkingDirectoryAutoCleanOpts, WorkingDirectoryWithPoolMut,
15 },
16 working_directory_pool::WorkingDirectoryPoolContext,
17 },
18 serde_types::{
19 date_and_time::DateTimeWithOffset, git_branch_name::GitBranchName,
20 git_reference::GitReference, git_url::GitUrl,
21 },
22 utillib::arc::CloneArc,
23};
24
25use super::{
26 config::JobTemplate,
27 working_directory_pool::{
28 WorkingDirectoryId, WorkingDirectoryPool, WorkingDirectoryPoolBaseDir,
29 },
30};
31
32fn check_exists(git_working_dir: &GitWorkingDir, commit: &GitHash) -> Result<bool> {
33 let commit_str = commit.to_string();
34 git_working_dir.contains_reference(&commit_str)
35}
36
37pub struct PollingPool {
38 pool: WorkingDirectoryPool,
39}
40
41impl PollingPool {
42 pub fn open(remote_repository_url: GitUrl, polling_pool_base: PathBuf) -> Result<Self> {
43 let base_dir = Arc::new(WorkingDirectoryPoolBaseDir::new(
44 Some(polling_pool_base),
45 &|| unreachable!("no fallback needed as path is always given"),
46 )?);
47 let pool = WorkingDirectoryPool::open(
48 WorkingDirectoryPoolContext {
49 capacity: NonZero::try_from(1).unwrap(),
50 auto_clean: {
55 let min_age_days = 60; Some(WorkingDirectoryAutoCleanOpts {
57 min_age_days,
58 min_num_runs: 3 * 60 * 24 * usize::from(min_age_days),
59 wait_until_commit_done: false,
61 })
62 },
63 remote_repository_url,
64 base_dir,
65 signal_change: None,
66 },
67 true,
68 false,
69 )?
70 .into_inner();
71 Ok(Self { pool })
72 }
73
74 pub fn commit_is_valid(&mut self, commit: &GitHash) -> Result<bool> {
77 let working_directory_id = {
78 let mut pool = self.pool.lock_mut("PollingPool.commit_is_valid")?;
79 pool.clear_current_working_directory()?;
80 pool.get_first()?
81 };
82 let (res, cleanup) = self.pool.process_in_working_directory(
83 working_directory_id,
84 &DateTimeWithOffset::now(None),
85 |mut working_directory| {
86 let working_directory = working_directory.get().expect("still there");
87 let git_working_dir = &working_directory.git_working_dir;
91 Ok(check_exists(git_working_dir, commit)? || {
92 let _ = working_directory.fetch(Some(commit))?;
93 check_exists(git_working_dir, commit)?
94 })
95 },
96 None,
97 &format!("verifying commit {commit}"),
98 None,
99 )?;
100 self.pool.working_directory_cleanup(cleanup)?;
101 Ok(res)
102 }
103
104 pub fn updated_working_dir(&mut self) -> Result<WorkingDirectoryId> {
107 let working_directory_id = {
108 let mut pool = self.pool.lock_mut("PollingPool.updated_working_dir")?;
109 pool.clear_current_working_directory()?;
110 pool.get_first()?
111 };
112 let (res, cleanup) = self.pool.process_in_working_directory(
113 working_directory_id,
114 &DateTimeWithOffset::now(None),
115 |mut working_directory| {
116 let working_directory = working_directory.get().expect("still there");
117 _ = working_directory.fetch(None)?;
118 Ok(working_directory_id)
119 },
120 None,
121 "updated_working_dir()",
122 None,
123 )?;
124 self.pool.working_directory_cleanup(cleanup)?;
125 Ok(res)
126 }
127
128 pub fn process_in_working_directory<R>(
132 &mut self,
133 working_directory_id: WorkingDirectoryId,
134 timestamp: &DateTimeWithOffset,
135 action: impl FnOnce(WorkingDirectoryWithPoolMut) -> Result<R>,
136 context: &str,
137 ) -> Result<R> {
138 {
139 let pool = self
140 .pool
141 .lock_mut("PollingPool.process_in_working_directory")?;
142 pool.clear_current_working_directory()?;
146 }
147 let (r, token) = self.pool.process_in_working_directory(
148 working_directory_id,
149 timestamp,
150 action,
151 None,
152 context,
153 None,
154 )?;
155 self.pool.working_directory_cleanup(token)?;
156 Ok(r)
157 }
158
159 pub fn resolve_branch_names<'b>(
162 &mut self,
163 working_directory_id: WorkingDirectoryId,
164 branch_names: &'b BTreeMap<GitBranchName, Arc<[JobTemplate]>>,
165 ) -> Result<(
166 Vec<(&'b GitBranchName, GitHash, Arc<[JobTemplate]>)>,
167 Vec<String>,
168 )> {
169 self.process_in_working_directory(
170 working_directory_id,
171 &DateTimeWithOffset::now(None),
172 |mut working_directory| {
173 let working_directory = working_directory.get().expect("still there");
174 let git_working_dir = &working_directory.git_working_dir;
175 let mut non_resolving = Vec::new();
176 let mut ids = Vec::new();
177 for (name, job_templates) in branch_names {
178 let ref_string = name.to_ref_string_in_remote(REMOTE_NAME);
179 if let Some(id) = git_working_dir.git_rev_parse(&ref_string, true)? {
180 ids.push((name, GitHash::from_str(&id)?, job_templates.clone_arc()))
181 } else {
182 non_resolving.push(ref_string);
183 }
184 }
185 Ok((ids, non_resolving))
186 },
187 &format!("resolving branch names {}", branch_names.keys().join(", ")),
188 )
189 }
190
191 pub fn resolve_references<R: AsRef<GitReference>>(
195 &mut self,
196 working_directory_id: WorkingDirectoryId,
197 remote_name: Option<&str>,
198 references: impl IntoIterator<Item = R>,
199 ) -> Result<Vec<Option<GitHash>>> {
200 self.process_in_working_directory(
201 working_directory_id,
202 &DateTimeWithOffset::now(None),
203 |mut working_directory| {
204 let working_directory = working_directory.get().expect("still there");
205 let git_working_dir = &working_directory.git_working_dir;
206
207 references
208 .into_iter()
209 .map(|reference| -> Result<Option<GitHash>> {
210 let reference = reference.as_ref();
211 let reference = reference.as_str();
212 let tmp;
213 let full_reference = if let Some(remote_name) = remote_name {
214 tmp = format!("{remote_name}/{reference}");
215 &*tmp
216 } else {
217 reference
218 };
219 Ok(git_working_dir
220 .git_rev_parse(full_reference, true)?
221 .map(|s| GitHash::from_str(&s).expect("git always returns git hashes")))
222 })
223 .try_collect()
224 },
225 "resolving references",
226 )
227 }
228}