use anyhow::{Context, Result};
use crate::git::Git;
use crate::issue::IssueUrl;
use crate::progress;
pub(super) struct Reservation {
launch: Git,
reference: String,
token: String,
}
impl Reservation {
pub(super) fn take(launch: &Git, issue: &IssueUrl) -> Result<Option<Self>> {
let reference = format!("refs/heads/thirdshift/pickup/{}", issue.number);
let nonce = tempfile::Builder::new().rand_bytes(32).tempfile()?;
let tree = launch.run(&["mktree"])?;
let message = format!(
"thirdshift Pickup reservation for {}\n\nToken: {}\nLaunch: {}\nProcess: {}\nStarted: {}",
issue.url,
nonce.path().display(),
launch.dir().display(),
std::process::id(),
chrono::Utc::now(),
);
let token = launch.run(&[
"-c",
"commit.gpgSign=false",
"commit-tree",
&tree,
"-m",
&message,
])?;
let pushed = launch.run(&[
"push",
&format!("--force-with-lease={reference}:"),
"origin",
&format!("{token}:{reference}"),
]);
if let Err(error) = pushed {
let remote = launch.run(&["ls-remote", "--refs", "origin", &reference])
.with_context(|| format!(
"{error:#}; could not verify reservation {reference}; check origin by hand (our token {token})"
))?;
if let Some((other, _)) = remote.split_once('\t')
&& other != token
{
return Ok(None);
}
return Err(error).with_context(|| format!(
"could not reserve {reference}; check origin by hand before retrying (our token {token})"
));
}
Ok(Some(Self {
launch: launch.completion(),
reference,
token,
}))
}
}
impl Drop for Reservation {
fn drop(&mut self) {
if let Err(error) = self.launch.run(&[
"push",
&format!("--force-with-lease={}:{}", self.reference, self.token),
"origin",
&format!(":{}", self.reference),
]) {
progress::warn(
&error,
format_args!(
"could not release Pickup reservation {}; check origin by hand (our token {})",
self.reference, self.token
),
);
}
}
}