tag2upload_service_manager/
gitclone.rs1
2use crate::prelude::*;
3use tokio::process::Command;
4
5pub struct TagDataAcquirer<'a> {
6 git_dir: &'a str,
7 job: &'a JobInWorkflow,
8}
9
10struct GitOutput {
11 stdout: String,
12 #[allow(dead_code)] stderr: String,
14}
15
16impl TagDataAcquirer<'_> {
17 async fn run_git(
18 &self,
19 what: &str,
20 args: &[&str],
21 ) -> Result<Result<GitOutput, AE>, ProcessingError> {
22 let jid = &self.job.jid;
23 trace!(%jid, ?args, "git ...");
24
25 let mut cmd = Command::new("git");
26 cmd
27 .current_dir(&self.git_dir)
28 .kill_on_drop(true)
29 .args(args);
30
31 let std::process::Output { stdout, stderr, status } =
32 cmd.output().await
33 .context("run git").map_err(PE::Local)?;
34
35 let stderr = String::from_utf8(stderr)
36 .unwrap_or_else(|e| {
37 format!("Invalid UTF-8 error; approximation follows: {}",
38 String::from_utf8_lossy(e.as_bytes()))
39 });
40
41 if !status.success() {
42 let err = anyhow!(
43 "git {what} failed, {status}; stderr: {stderr:?}"
44 );
45 trace!(%jid, ?args, %err, "git failed");
46 return Ok(Err(err));
47 }
48
49 let Ok(stdout) = String::from_utf8(stdout)
50 else {
51 let err = anyhow!(
52 "git {what} produced invalid UTF-8 on stdout; \
53 on stderr it printed: {stderr:?}"
54 );
55 trace!(%jid, ?args, %err, "git misbehaved");
56 return Ok(Err(err));
57 };
58
59 trace!(%jid, ?args, ?stdout, "git succeeded");
60
61 Ok(Ok(GitOutput { stdout, stderr }))
62 }
63}
64
65pub async fn fetch_tags_via_clone(
66 job: &mut JobInWorkflow,
67 task_tmpdir: &str,
68) -> Result<ValidTagObjectData, FetchError> {
69 let gl = globals();
70 let jid = job.jid;
71 let git_dir = format!("{task_tmpdir}/package.git");
72
73 fs::create_dir(&git_dir)
74 .with_context(|| git_dir.clone()).context("mkdir")
75 .map_err(PE::Local)?;
76
77 let acq = TagDataAcquirer {
78 git_dir: &git_dir,
79 job,
80 };
81
82 acq.run_git("init", &["init", "--bare"])
83 .await?
84 .map_err(PE::Local)?;
85
86 let url = &*job.data.repo_git_url;
87
88 test_hook_url!(url);
89
90 let refname = format!("refs/tags/{}", job.data.tag_name);
91 let refspec = format!("+{}:{}", refname, refname);
92
93 debug!(jid=%jid, url=?url, "git fetch...");
94
95 match gl.http_fetch_raw(
98 Url::parse(&url)
99 .context("parse url")
100 .map_err(PE::ForgePerm)
101 ?,
102 )
103 .await.context("http fetch").map_err(PE::ForgeTemp)?
104 {
105 HttpFetchedRaw::Success { .. } |
106 HttpFetchedRaw::SuccessFile { .. } => {},
107 HttpFetchedRaw::NotFound(e) => Err(PE::ForgePerm(e.into()))?,
108 }
109
110 let () = tokio::time::timeout(
113 *globals().config.timeouts.git_query,
114 acq.run_git(
115 "ls-remote",
116 &["ls-remote", "--refs", &url, &refname],
117 )
118 )
119 .await
120 .context("ls-remote timeout").map_err(PE::ForgeTemp)?
121 ?
122 .map_err(PE::ForgeTemp)?
123 .stdout
124 .lines()
125 .find_map(|l| {
126 let found_ref = l
127 .split_once('\t')?.1
128 .trim_end();
129 (found_ref == refname).then_some(())
130 })
131 .ok_or_else(|| anyhow!("tag does not exist at repository"))
132 .map_err(PE::ForgePerm)?;
133
134 tokio::time::timeout(
137 *gl.config.timeouts.git_clone,
138 acq.run_git(
139 "fetch",
140 &["fetch", "--no-tags", "--progress", "--depth", "1",
141 &url, &refspec],
142 )
143 )
144 .await
145 .context("clone, to inspect tag").map_err(PE::ForgeTemp)?
146 ?
147 .map_err(PE::ForgeTemp)?;
148
149 let tag_objectid = acq.run_git(
150 "rev-parse",
151 &["rev-parse", &refname],
152 )
153 .await?
154 .map_err(PE::Local)?
155 .stdout
156 .trim_end().parse::<GitObjectId>()
157 .context("parse output of git-rev-parse").map_err(PE::Local)?;
158
159 MismatchError::check(
160 "git object id",
161 &job.data.tag_objectid,
162 &tag_objectid,
163 )?;
164
165 let obj_type = acq.run_git(
166 "cat-file -t",
167 &["cat-file", "-t", &refname],
168 )
169 .await?
170 .map_err(PE::Local)?
171 .stdout;
172
173 let obj_type = obj_type.trim();
174 if obj_type != "tag" {
175 Err(PE::ForgePerm(anyhow!(
176 "ref {refname:?} referenced {obj_type}, not a tag"
177 )))?;
178 }
179
180 let tag_data = acq.run_git(
181 "cat-file tag",
182 &["cat-file", "tag", &refname],
183 )
184 .await?
185 .map_err(PE::Local)?
186 .stdout;
187
188 let tag_data = TagObjectData::try_from(tag_data)
189 .context("tag data obtained via git clone")
190 .map_err(PE::ForgePerm)?;
191
192 let tagger_date = {
193 let tagger_line = acq.run_git(
194 "for-each-ref ...taggerdate...",
195 &["for-each-ref", "--format=%(taggerdate:raw)",
196 &format!("[r]efs/tags/{}", job.data.tag_name)]
201 )
202 .await?
203 .map_err(PE::Local)?
204 .stdout;
205
206 let time_t = tagger_line
207 .split_once(' ').map(|(lhs, _)| lhs).unwrap_or(&tagger_line)
208 .parse::<u64>().context("parse time_t from git tagger line")
209 .map_err(PE::ForgePerm)?;
210
211 let time_t = SystemTime::UNIX_EPOCH
212 .checked_add(Duration::from_secs(time_t))
213 .ok_or_else(|| PE::ForgePerm(anyhow!(
214 "tagger date out of plausible range"
215 )))?;
216
217 time_t
218 };
219
220 let is_recent_enough = gl.check_tag_recency(tagger_date)?;
221
222 Ok(ValidTagObjectData {
223 tag_data,
224 is_recent_enough,
225 })
226}