comment once per pr and agent while red, clear the marker on a green build
This commit is contained in:
parent
0bc5a7e475
commit
9fc0e3bf90
1 changed files with 234 additions and 49 deletions
|
|
@ -1,28 +1,17 @@
|
|||
//! Comments on a pull request, posted once per marker.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use forgejo_api::structs::{CreateIssueCommentOption, IssueGetCommentsQuery};
|
||||
use forgejo_api::structs::{
|
||||
Comment, CreateIssueCommentOption, EditIssueCommentOption, IssueGetCommentsQuery,
|
||||
};
|
||||
|
||||
use super::Client;
|
||||
|
||||
impl Client {
|
||||
/// Comment `body` on pull request `pr` of `owner/repo` unless a comment
|
||||
/// there already contains `marker`. Whether a comment was posted.
|
||||
///
|
||||
/// The marker is what the comment carries, so the check holds across a
|
||||
/// restart and across every caller. It reads the comments first, so two
|
||||
/// concurrent calls with one marker can both post.
|
||||
pub(crate) async fn comment_once(
|
||||
&self,
|
||||
owner: &str,
|
||||
repo: &str,
|
||||
pr: u64,
|
||||
marker: &str,
|
||||
body: &str,
|
||||
) -> Result<bool> {
|
||||
/// Every comment on pull request `pr` of `owner/repo`.
|
||||
async fn comments(&self, owner: &str, repo: &str, pr: u64) -> Result<Vec<Comment>> {
|
||||
let index = i64::try_from(pr).context("PR number out of range")?;
|
||||
let existing = self
|
||||
.api
|
||||
self.api
|
||||
.call(|api| {
|
||||
Box::pin(
|
||||
api.issue_get_comments(
|
||||
|
|
@ -38,7 +27,25 @@ impl Client {
|
|||
)
|
||||
})
|
||||
.await
|
||||
.with_context(|| format!("listing the comments on {owner}/{repo}#{pr}"))?;
|
||||
.with_context(|| format!("listing the comments on {owner}/{repo}#{pr}"))
|
||||
}
|
||||
|
||||
/// Comment `body` on pull request `pr` of `owner/repo` unless a comment
|
||||
/// there already contains `marker`. Whether a comment was posted.
|
||||
///
|
||||
/// The marker is what the comment carries, so the check holds across a
|
||||
/// restart and across every caller. It reads the comments first, so two
|
||||
/// concurrent calls with one marker can both post.
|
||||
pub(crate) async fn comment_once(
|
||||
&self,
|
||||
owner: &str,
|
||||
repo: &str,
|
||||
pr: u64,
|
||||
marker: &str,
|
||||
body: &str,
|
||||
) -> Result<bool> {
|
||||
let index = i64::try_from(pr).context("PR number out of range")?;
|
||||
let existing = self.comments(owner, repo, pr).await?;
|
||||
if existing
|
||||
.iter()
|
||||
.any(|c| c.body.as_deref().is_some_and(|b| b.contains(marker)))
|
||||
|
|
@ -58,6 +65,42 @@ impl Client {
|
|||
.with_context(|| format!("commenting on {owner}/{repo}#{pr}"))?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Rewrite every comment on pull request `pr` of `owner/repo` that
|
||||
/// contains `marker` so it contains `cleared` instead, which lets
|
||||
/// [`Self::comment_once`] post that marker again. How many comments were
|
||||
/// rewritten.
|
||||
pub(crate) async fn clear_marker(
|
||||
&self,
|
||||
owner: &str,
|
||||
repo: &str,
|
||||
pr: u64,
|
||||
marker: &str,
|
||||
cleared: &str,
|
||||
) -> Result<usize> {
|
||||
let mut rewritten = 0;
|
||||
for comment in self.comments(owner, repo, pr).await? {
|
||||
let (Some(id), Some(body)) = (comment.id, comment.body.as_deref()) else {
|
||||
continue;
|
||||
};
|
||||
if !body.contains(marker) {
|
||||
continue;
|
||||
}
|
||||
let option = EditIssueCommentOption {
|
||||
body: body.replace(marker, cleared),
|
||||
updated_at: None,
|
||||
};
|
||||
self.api
|
||||
.call(|api| {
|
||||
api.issue_edit_comment(owner, repo, id, option.clone())
|
||||
.into_future()
|
||||
})
|
||||
.await
|
||||
.with_context(|| format!("clearing comment {id} on {owner}/{repo}#{pr}"))?;
|
||||
rewritten += 1;
|
||||
}
|
||||
Ok(rewritten)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -87,8 +130,8 @@ pub(crate) mod tests {
|
|||
"GET" => {
|
||||
let list: Vec<_> = held
|
||||
.iter()
|
||||
.filter(|(p, _)| *p == path)
|
||||
.enumerate()
|
||||
.filter(|(_, (p, _))| *p == path)
|
||||
.map(
|
||||
|(id, (_, body))| serde_json::json!({ "id": id, "body": body }),
|
||||
)
|
||||
|
|
@ -100,6 +143,16 @@ pub(crate) mod tests {
|
|||
serde_json::Value::from(list).to_string(),
|
||||
)
|
||||
}
|
||||
"PATCH" => {
|
||||
let sent: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
let id: usize = path.rsplit('/').next().unwrap().parse().unwrap();
|
||||
held[id].1 = sent["body"].as_str().unwrap().to_owned();
|
||||
(
|
||||
StatusCode::OK,
|
||||
[("x-total-count", String::new())],
|
||||
serde_json::json!({ "id": id, "body": held[id].1 }).to_string(),
|
||||
)
|
||||
}
|
||||
"POST" => {
|
||||
let sent: serde_json::Value = serde_json::from_slice(&body).unwrap();
|
||||
let text = sent["body"].as_str().unwrap().to_owned();
|
||||
|
|
@ -107,7 +160,8 @@ pub(crate) mod tests {
|
|||
(
|
||||
StatusCode::CREATED,
|
||||
[("x-total-count", String::new())],
|
||||
serde_json::json!({ "id": held.len(), "body": text }).to_string(),
|
||||
serde_json::json!({ "id": held.len() - 1, "body": text })
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
_ => (
|
||||
|
|
@ -147,4 +201,39 @@ pub(crate) mod tests {
|
|||
format!("/api/v1/repos/o/r/{}/7/comments", "issues")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_cleared_marker_can_be_posted_again() {
|
||||
let (client, held) = stand_in().await;
|
||||
let (marker, cleared) = ("<!-- a -->", "<!-- a cleared -->");
|
||||
assert!(
|
||||
client
|
||||
.comment_once("o", "r", 7, marker, marker)
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
client
|
||||
.clear_marker("o", "r", 7, marker, cleared)
|
||||
.await
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
client
|
||||
.clear_marker("o", "r", 7, marker, cleared)
|
||||
.await
|
||||
.unwrap(),
|
||||
0
|
||||
);
|
||||
assert!(
|
||||
client
|
||||
.comment_once("o", "r", 7, marker, marker)
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
let held = held.lock().unwrap();
|
||||
assert_eq!(held[0].1, cleared);
|
||||
assert_eq!(held[1].1, marker);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue