Watch
0
0
Fork
You've already forked hyperhive
0

swarm-controller: read a repo's review rules from its default branch

`GET /api/review-classifier/rules?repo=owner/name` reads
`.hyperhive/review-classifier.yml` or `.json` from the repo's default
branch with the controller's forge token, on every request, and answers
the classifications in the try-out POST's shape, plus the file, the
branch and the commit it was read at. The swarm UI's try-out page loads
them into its editor (#5134).

The file is one `classifications` list of the try-out's `Classification`.
A missing file, both files, a parse error, any unknown key, an empty list
or more than ten entries is a problem+json error, never a pass. The
checks the POST makes on its classifications move into one function both
paths call.

The doc names the file and its schema, replacing the placeholders, and
documents the route.

Part of #5096.
This commit is contained in:
atlas 2026-10-12 08:12:38 +02:00 • committed by mara
commit 16c7c249ef
6 changed files with 624 additions and 55 deletions

View file

@ -25,8 +25,8 @@ use forgejo_api::structs::{
CreateHookOption, CreateHookOptionConfig, CreateHookOptionType, CreateRepoOption,
CreateUserOption, EditUserOption, IssueListIssuesQuery, IssueListIssuesQueryState,
IssueListIssuesQueryType, RepoDownloadPullDiffOrPatchQuery, RepoGetContentsQuery,
RepoGetPullRequestCommitsQuery, RepoListPullRequestsQuery, RepoListPullRequestsQueryState,
RepoSearchQuery, StateType,
RepoGetPullRequestCommitsQuery, RepoGetRawFileQuery, RepoListPullRequestsQuery,
RepoListPullRequestsQueryState, RepoSearchQuery, StateType,
};
use forgejo_api::{ApiErrorKind, Auth, Forgejo, ForgejoError};
use futures_util::{StreamExt as _, TryStreamExt as _};
@ -65,6 +65,18 @@ pub struct PullForReview {
pub diff: String,
}
/// Files on a repo's default branch, as [`Client::default_branch_files`] reads
/// them.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DefaultBranchFiles {
pub branch: String,
/// The commit the files were read at.
pub commit: String,
/// Each asked-for path that exists there, with its bytes, in the order
/// asked.
pub files: Vec<(String, Vec<u8>)>,
}
/// One row of [`Client::issue_report`] — an open issue plus the two facts
/// derived from its dependency graph that swarm-ui's issue-report page
/// sorts/filters on. See that function's doc comment for how both are
@ -1102,6 +1114,59 @@ impl Client {
})
}
/// The `paths` of `owner/repo` that exist on its default branch, all
/// read at the branch's head commit. `None` for a repo with no commit.
///
/// # Errors
/// A forge failure, with the [`ForgejoError`] underneath; a 404 on the
/// first call means the repo does not exist ([`is_repo_not_found`]).
pub async fn default_branch_files(
&self,
owner: &str,
repo: &str,
paths: &[&str],
) -> Result<Option<DefaultBranchFiles>> {
let info = self
.api
.repo_get(owner, repo)
.await
.with_context(|| format!("get repo {owner}/{repo}"))?;
if info.empty.unwrap_or(false) {
return Ok(None);
}
let branch = info
.default_branch
.with_context(|| format!("{owner}/{repo} names no default branch"))?;
let commit = self
.api
.repo_get_branch(owner, repo, &branch)
.await
.with_context(|| format!("read branch {branch} of {owner}/{repo}"))?
.commit
.and_then(|c| c.id)
.with_context(|| format!("branch {branch} of {owner}/{repo} names no commit"))?;
let mut files = Vec::new();
for path in paths {
let query = RepoGetRawFileQuery {
r#ref: Some(commit.clone()),
};
match self.api.repo_get_raw_file(owner, repo, path, query).await {
Ok(bytes) => files.push(((*path).to_owned(), bytes.to_vec())),
Err(e) if is_repo_not_found(&e) => {}
Err(e) => {
return Err(e).with_context(|| {
format!("read {path} of {owner}/{repo} at {branch} ({commit})")
});
}
}
}
Ok(Some(DefaultBranchFiles {
branch,
commit,
files,
}))
}
/// Register the swarm-wide hooks against this controller, so a real
/// forge event reaches [`crate::webhook`] instead of the endpoint only
/// being reachable by hand.