refactor(hive-c0re): port forge module + knowledge hooks to forgejo-api
This commit is contained in:
parent
4468e86e2d
commit
b8a3927c43
5 changed files with 651 additions and 426 deletions
|
|
@ -4,10 +4,12 @@
|
|||
//! boundary; moved verbatim from the `forge` module root.
|
||||
|
||||
use anyhow::Context;
|
||||
use forgejo_api::ForgejoError;
|
||||
use forgejo_api::structs::{MergePullRequestOption, MergePullRequestOptionDo};
|
||||
|
||||
use crate::coordinator::Coordinator;
|
||||
|
||||
use super::{CONFIG_ORG, FORGE_HTTP, core_token, forge_http};
|
||||
use super::{CONFIG_ORG, api, core_token};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PR-based config-flow merge primitives (part of the
|
||||
|
|
@ -242,35 +244,57 @@ pub async fn ff_push_to_main(repo: &str, sha: &str) -> Result<(), ForgeMergeErro
|
|||
}
|
||||
|
||||
/// Mark PR `pr` as **manually merged** at `sha` (Forgejo
|
||||
/// `POST …/pulls/{pr}/merge` with `Do=manually-merged`, `MergeCommitID=sha`).
|
||||
/// `repo_merge_pull_request` with `Do=manually-merged`, `MergeCommitID=sha`).
|
||||
/// `ff_push_to_main` must have already set `main` to `sha` (Forgejo requires
|
||||
/// the branch already be at the merge commit). On a non-2xx, re-reads the PR
|
||||
/// head to distinguish drift (`HeadDrift`) from a generic failure (`Other`) —
|
||||
/// best-effort, since the handler's pre-merge head re-read is the real gate.
|
||||
/// the branch already be at the merge commit). On an API rejection, re-reads
|
||||
/// the PR head to distinguish drift (`HeadDrift`) from a generic failure
|
||||
/// (`Other`) — best-effort, since the handler's pre-merge head re-read is
|
||||
/// the real gate. A transport failure skips the drift re-read (it couldn't
|
||||
/// reach the forge either) and surfaces as `Other` directly.
|
||||
///
|
||||
/// # Errors
|
||||
/// `HeadDrift` if the PR head no longer matches `sha`; `Other` otherwise.
|
||||
pub async fn mark_pr_merged(repo: &str, pr: u64, sha: &str) -> Result<(), ForgeMergeError> {
|
||||
let token = core_token()
|
||||
.ok_or_else(|| ForgeMergeError::Other(anyhow::anyhow!("forge core token absent")))?;
|
||||
let url = format!("{FORGE_HTTP}/api/v1/repos/{repo}/pulls/{pr}/merge");
|
||||
let body = format!(r#"{{"Do":"manually-merged","MergeCommitID":"{sha}"}}"#);
|
||||
let (status, _) = forge_http(reqwest::Method::POST, &url, &token, &body)
|
||||
let (owner, name) = repo.split_once('/').ok_or_else(|| {
|
||||
ForgeMergeError::Other(anyhow::anyhow!("forge repo `{repo}` is not owner/name"))
|
||||
})?;
|
||||
let index = i64::try_from(pr)
|
||||
.map_err(|_| ForgeMergeError::Other(anyhow::anyhow!("PR index {pr} overflows i64")))?;
|
||||
let body = MergePullRequestOption {
|
||||
r#do: MergePullRequestOptionDo::ManuallyMerged,
|
||||
merge_commit_id: Some(sha.to_owned()),
|
||||
merge_message_field: None,
|
||||
merge_title_field: None,
|
||||
delete_branch_after_merge: None,
|
||||
force_merge: None,
|
||||
head_commit_id: None,
|
||||
merge_when_checks_succeed: None,
|
||||
};
|
||||
let client = api(&token).map_err(ForgeMergeError::Other)?;
|
||||
match client
|
||||
.repo_merge_pull_request(owner, name, index, body)
|
||||
.await
|
||||
.context("POST pulls/<pr>/merge (manually-merged)")?;
|
||||
if status.is_success() {
|
||||
return Ok(());
|
||||
}
|
||||
// Best-effort drift detection: if the live head no longer matches `sha`,
|
||||
// that's a head-drift race; otherwise surface as a hard failure.
|
||||
match pr_head_sha(repo, pr).await {
|
||||
Ok(actual) if actual != sha => Err(ForgeMergeError::HeadDrift {
|
||||
expected: sha.to_string(),
|
||||
actual,
|
||||
}),
|
||||
_ => Err(ForgeMergeError::Other(anyhow::anyhow!(
|
||||
"mark PR #{pr} in {repo} manually-merged at {sha} failed: HTTP {status}"
|
||||
))),
|
||||
{
|
||||
Ok(()) => Ok(()),
|
||||
Err(ForgejoError::ReqwestError(e)) => Err(ForgeMergeError::Other(
|
||||
anyhow::Error::from(e).context("POST pulls/<pr>/merge (manually-merged)"),
|
||||
)),
|
||||
Err(e) => {
|
||||
// Best-effort drift detection: if the live head no longer matches
|
||||
// `sha`, that's a head-drift race; otherwise surface as a hard
|
||||
// failure.
|
||||
match pr_head_sha(repo, pr).await {
|
||||
Ok(actual) if actual != sha => Err(ForgeMergeError::HeadDrift {
|
||||
expected: sha.to_string(),
|
||||
actual,
|
||||
}),
|
||||
_ => Err(ForgeMergeError::Other(anyhow::Error::from(e).context(
|
||||
format!("mark PR #{pr} in {repo} manually-merged at {sha}"),
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue