tag2upload_service_manager/
gitclone.rs
use crate::prelude::*;
use tokio::process::Command;
pub struct TagDataAcquirer<'a> {
git_dir: &'a str,
job: &'a JobInWorkflow,
}
struct GitOutput {
stdout: String,
#[allow(dead_code)] stderr: String,
}
impl TagDataAcquirer<'_> {
async fn run_git(
&self,
what: &str,
args: &[&str],
) -> Result<Result<GitOutput, AE>, ProcessingError> {
let jid = &self.job.jid;
trace!(%jid, ?args, "git ...");
let mut cmd = Command::new("git");
cmd
.current_dir(&self.git_dir)
.kill_on_drop(true)
.args(args);
let std::process::Output { stdout, stderr, status } =
cmd.output().await
.context("run git").map_err(PE::Local)?;
let stderr = String::from_utf8(stderr)
.unwrap_or_else(|e| {
format!("Invalid UTF-8 error; approximation follows: {}",
String::from_utf8_lossy(e.as_bytes()))
});
if !status.success() {
let err = anyhow!(
"git {what} failed, {status}; stderr: {stderr:?}"
);
trace!(%jid, ?args, %err, "git failed");
return Ok(Err(err));
}
let Ok(stdout) = String::from_utf8(stdout)
else {
let err = anyhow!(
"git {what} produced invalid UTF-8 on stdout; \
on stderr it printed: {stderr:?}"
);
trace!(%jid, ?args, %err, "git misbehaved");
return Ok(Err(err));
};
trace!(%jid, ?args, ?stdout, "git succeeded");
Ok(Ok(GitOutput { stdout, stderr }))
}
}
pub async fn fetch_tags_via_clone(
job: &mut JobInWorkflow,
task_tmpdir: &str,
) -> Result<ValidTagObjectData, FetchError> {
let jid = job.jid;
let git_dir = format!("{task_tmpdir}/package.git");
fs::create_dir(&git_dir)
.with_context(|| git_dir.clone()).context("mkdir")
.map_err(PE::Local)?;
let acq = TagDataAcquirer {
git_dir: &git_dir,
job,
};
acq.run_git("init", &["init", "--bare"])
.await?
.map_err(PE::Local)?;
let url = &*job.data.repo_git_url;
test_hook_url!(url);
let refname = format!("refs/tags/{}", job.data.tag_name);
let refspec = format!("+{}:{}", refname, refname);
debug!(jid=%jid, url=?url, "git fetch...");
tokio::time::timeout(
*globals().config.timeouts.git_clone,
acq.run_git(
"fetch",
&["fetch", "--progress", "--depth", "1", &url, &refspec],
)
)
.await
.context("clone, to inspect tag").map_err(PE::Forge)?
?
.map_err(PE::Forge)?;
let tag_objectid: GitObjectId = acq.run_git(
"rev-parse",
&["rev-parse", &refname],
)
.await?
.map_err(PE::Local)?
.stdout
.trim_end().parse()
.context("parse output of git-rev-parse").map_err(PE::Local)?;
MismatchError::check(
"git object id",
&job.data.tag_objectid,
&tag_objectid,
)?;
let obj_type = acq.run_git(
"cat-file -t",
&["cat-file", "-t", &refname],
)
.await?
.map_err(PE::Local)?
.stdout;
let obj_type = obj_type.trim();
if obj_type != "tag" {
Err(PE::Forge(anyhow!(
"ref {refname:?} referenced {obj_type}, not a tag"
)))?;
}
let tag_data = acq.run_git(
"cat-file tag",
&["cat-file", "tag", &refname],
)
.await?
.map_err(PE::Local)?
.stdout;
let tag_data: TagObjectData = tag_data.try_into()
.context("tag data obtained via git clone")
.map_err(PE::Forge)?;
let tagger_date = {
let tagger_line = acq.run_git(
"for-each-ref ...taggerdate...",
&["for-each-ref", "--format=%(taggerdate:raw)",
&format!("[r]efs/tags/{}", job.data.tag_name)]
)
.await?
.map_err(PE::Local)?
.stdout;
let time_t = tagger_line
.split_once(' ').map(|(lhs, _)| lhs).unwrap_or(&tagger_line)
.parse::<u64>().context("parse time_t from git tagger line")
.map_err(PE::Forge)?;
let time_t = SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(time_t))
.ok_or_else(|| PE::Forge(anyhow!(
"tagger date out of plausible range"
)))?;
time_t
};
let is_recent_enough = globals().check_tag_recency(tagger_date)?;
Ok(ValidTagObjectData {
tag_data,
is_recent_enough,
})
}