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 jid = job.jid;
70 let git_dir = format!("{task_tmpdir}/package.git");
71
72 fs::create_dir(&git_dir)
73 .with_context(|| git_dir.clone()).context("mkdir")
74 .map_err(PE::Local)?;
75
76 let acq = TagDataAcquirer {
77 git_dir: &git_dir,
78 job,
79 };
80
81 acq.run_git("init", &["init", "--bare"])
82 .await?
83 .map_err(PE::Local)?;
84
85 let url = &*job.data.repo_git_url;
86
87 test_hook_url!(url);
88
89 let refname = format!("refs/tags/{}", job.data.tag_name);
90 let refspec = format!("+{}:{}", refname, refname);
91
92 debug!(jid=%jid, url=?url, "git fetch...");
93
94 tokio::time::timeout(
95 *globals().config.timeouts.git_clone,
96 acq.run_git(
97 "fetch",
98 &["fetch", "--no-tags", "--progress", "--depth", "1",
99 &url, &refspec],
100 )
101 )
102 .await
103 .context("clone, to inspect tag").map_err(PE::Forge)?
104 ?
105 .map_err(PE::Forge)?;
106
107 let tag_objectid: GitObjectId = acq.run_git(
108 "rev-parse",
109 &["rev-parse", &refname],
110 )
111 .await?
112 .map_err(PE::Local)?
113 .stdout
114 .trim_end().parse()
115 .context("parse output of git-rev-parse").map_err(PE::Local)?;
116
117 MismatchError::check(
118 "git object id",
119 &job.data.tag_objectid,
120 &tag_objectid,
121 )?;
122
123 let obj_type = acq.run_git(
124 "cat-file -t",
125 &["cat-file", "-t", &refname],
126 )
127 .await?
128 .map_err(PE::Local)?
129 .stdout;
130
131 let obj_type = obj_type.trim();
132 if obj_type != "tag" {
133 Err(PE::Forge(anyhow!(
134 "ref {refname:?} referenced {obj_type}, not a tag"
135 )))?;
136 }
137
138 let tag_data = acq.run_git(
139 "cat-file tag",
140 &["cat-file", "tag", &refname],
141 )
142 .await?
143 .map_err(PE::Local)?
144 .stdout;
145
146 let tag_data: TagObjectData = tag_data.try_into()
147 .context("tag data obtained via git clone")
148 .map_err(PE::Forge)?;
149
150 let tagger_date = {
151 let tagger_line = acq.run_git(
152 "for-each-ref ...taggerdate...",
153 &["for-each-ref", "--format=%(taggerdate:raw)",
154 &format!("[r]efs/tags/{}", job.data.tag_name)]
159 )
160 .await?
161 .map_err(PE::Local)?
162 .stdout;
163
164 let time_t = tagger_line
165 .split_once(' ').map(|(lhs, _)| lhs).unwrap_or(&tagger_line)
166 .parse::<u64>().context("parse time_t from git tagger line")
167 .map_err(PE::Forge)?;
168
169 let time_t = SystemTime::UNIX_EPOCH
170 .checked_add(Duration::from_secs(time_t))
171 .ok_or_else(|| PE::Forge(anyhow!(
172 "tagger date out of plausible range"
173 )))?;
174
175 time_t
176 };
177
178 let is_recent_enough = globals().check_tag_recency(tagger_date)?;
179
180 Ok(ValidTagObjectData {
181 tag_data,
182 is_recent_enough,
183 })
184}