use anyhow::{Context, Result};
use crate::branch;
use crate::git::Git;
use crate::github::{self, ListedIssue};
use crate::interrupt;
use crate::issue::{IssueUrl, Repo};
use crate::labels::{Label, Labels, READY_FOR_AGENT};
use crate::progress;
pub const IN_PROGRESS: Label =
Label::new("in-progress", "A Claim: thirdshift has taken this issue");
pub struct Claim<'a> {
issue: &'a IssueUrl,
added_in_progress: bool,
removed_ready_for_agent: bool,
}
pub fn is_on(labels: &Labels) -> bool {
labels.has(IN_PROGRESS)
}
pub fn make(issue: &IssueUrl) -> Result<Claim<'_>> {
label_in_progress(issue)
.with_context(|| format!("could not make the Claim on #{}", issue.number))
}
fn label_in_progress(issue: &IssueUrl) -> Result<Claim<'_>> {
let labels = github::issue_labels(issue)?;
let (claimed, ready) = (is_on(&labels), labels.has(READY_FOR_AGENT));
let claim = Claim {
issue,
added_in_progress: !claimed,
removed_ready_for_agent: ready,
};
if claimed && !ready {
return Ok(claim);
}
if ready {
progress::step(format_args!(
"labelling #{} {IN_PROGRESS}, in place of {READY_FOR_AGENT}",
issue.number
));
} else {
progress::step(format_args!("labelling #{} {IN_PROGRESS}", issue.number));
}
if !claimed {
github::ensure_labels(&issue.repo_slug(), &[IN_PROGRESS])?;
}
github::set_labels(issue, &labels.swapped(&[READY_FOR_AGENT], &[IN_PROGRESS]))?;
Ok(claim)
}
impl Claim<'_> {
pub fn release_if_nothing_on_origin(&self, launch: &Git) {
if !self.added_in_progress && !self.removed_ready_for_agent {
return;
}
if let Err(error) =
interrupt::retry_if_interrupted(|| self.put_labels_back_unless_on_origin(launch))
{
progress::warn(
&error,
format_args!(
"could not release the Claim on #{}, so if nothing of the run is on origin, \
release it by hand: {}",
self.issue.number,
self.release_by_hand()
),
);
}
}
fn put_labels_back_unless_on_origin(&self, launch: &Git) -> Result<()> {
let issue = self.issue;
if branch::started(launch, issue)?.is_some() {
return Ok(());
}
let labels = github::issue_labels(issue)?;
let Some(in_progress) = labels.spelled(IN_PROGRESS) else {
return Ok(());
};
let number = issue.number;
if !self.removed_ready_for_agent {
progress::step(format_args!(
"releasing the Claim on #{number}: removing {IN_PROGRESS}"
));
return github::remove_label(issue, in_progress);
}
let off: &[Label] = if self.added_in_progress {
progress::step(format_args!(
"releasing the Claim on #{number}: labelling it {READY_FOR_AGENT}, \
in place of {IN_PROGRESS}"
));
&[IN_PROGRESS]
} else {
progress::step(format_args!(
"releasing the Claim on #{number}: labelling it {READY_FOR_AGENT} again"
));
&[]
};
github::set_labels(issue, &labels.swapped(off, &[READY_FOR_AGENT]))
}
pub fn remove_if_closed(&self) {
if let Err(error) = interrupt::retry_if_interrupted(|| self.unlabel_if_closed()) {
progress::warn(
&error,
format_args!(
"could not remove {IN_PROGRESS} from issue #{}, so remove it by hand: {}",
self.issue.number,
github::remove_label_command(self.issue, IN_PROGRESS)
),
);
}
}
fn unlabel_if_closed(&self) -> Result<()> {
let issue = github::issue(self.issue)?;
if issue.is_open {
return Ok(());
}
let Some(in_progress) = issue.labels.spelled(IN_PROGRESS) else {
return Ok(());
};
progress::step(format_args!(
"removing {IN_PROGRESS} from issue #{}",
self.issue.number
));
github::remove_label(self.issue, in_progress)
}
fn release_by_hand(&self) -> String {
let mut commands = Vec::new();
if self.added_in_progress {
commands.push(github::remove_label_command(self.issue, IN_PROGRESS));
}
if self.removed_ready_for_agent {
commands.push(github::add_label_command(self.issue, READY_FOR_AGENT));
}
commands.join(" && ")
}
}
pub fn open_count(repo: &Repo) -> Result<usize> {
Ok(github::open_issues_labelled(&repo.slug(), IN_PROGRESS)?.len())
}
pub fn sweep(repo: &Repo) {
let closed = match github::closed_issues_labelled(&repo.slug(), IN_PROGRESS) {
Ok(closed) => closed,
Err(error) => {
progress::step(format_args!(
"warning: could not list the closed issues labelled {IN_PROGRESS}: {error:#}"
));
return;
}
};
for ListedIssue { issue, labels, .. } in closed {
let Some(label) = labels.spelled(IN_PROGRESS) else {
continue;
};
progress::step(format_args!(
"taking {IN_PROGRESS} off #{}, which is closed",
issue.number
));
if let Err(error) = github::remove_label(&issue, label) {
progress::step(format_args!(
"warning: could not take {IN_PROGRESS} off #{}: {error:#}",
issue.number
));
}
}
}