refactor(hive-forge): port CLI verbs to forgejo-api
This commit is contained in:
parent
b8a3927c43
commit
4636987469
36 changed files with 1463 additions and 1153 deletions
|
|
@ -14,9 +14,11 @@
|
|||
|
||||
use anyhow::{Result, bail};
|
||||
use clap::{Args as ClapArgs, ValueEnum};
|
||||
use serde_json::{Value, json};
|
||||
use forgejo_api::structs::{
|
||||
CommitStatusState, MergePullRequestOption, MergePullRequestOptionDo, PullRequest, StateType,
|
||||
};
|
||||
|
||||
use crate::client::Client;
|
||||
use crate::client::{Client, index, split_repo};
|
||||
|
||||
/// Merge strategy. Squash is deliberately omitted (hive convention: keep the
|
||||
/// per-commit history, so a squash option isn't exposed).
|
||||
|
|
@ -29,13 +31,22 @@ pub enum Method {
|
|||
}
|
||||
|
||||
impl Method {
|
||||
/// The Forgejo `Do` field value for this strategy.
|
||||
/// The Forgejo `Do` field value for this strategy (used for the
|
||||
/// confirmation message).
|
||||
fn forgejo_do(self) -> &'static str {
|
||||
match self {
|
||||
Method::Merge => "merge",
|
||||
Method::Rebase => "rebase",
|
||||
}
|
||||
}
|
||||
|
||||
/// The typed `Do` enum variant for the merge request body.
|
||||
fn as_option_do(self) -> MergePullRequestOptionDo {
|
||||
match self {
|
||||
Method::Merge => MergePullRequestOptionDo::Merge,
|
||||
Method::Rebase => MergePullRequestOptionDo::Rebase,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ClapArgs)]
|
||||
|
|
@ -65,12 +76,17 @@ pub struct Args {
|
|||
/// be merged).
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo();
|
||||
let pull = client.get_json(&format!("/repos/{repo}/pulls/{}", args.number))?;
|
||||
let (owner, name) = client.owner_repo()?;
|
||||
let idx = index(args.number)?;
|
||||
let pull = client
|
||||
.api()
|
||||
.repo_get_pull_request(owner, name, idx)
|
||||
.send()?;
|
||||
|
||||
if pull.get("merged").and_then(Value::as_bool).unwrap_or(false) {
|
||||
if pull.merged.unwrap_or(false) {
|
||||
bail!("pr-merge: PR #{} is already merged", args.number);
|
||||
}
|
||||
if pull.get("state").and_then(Value::as_str) == Some("closed") {
|
||||
if pull.state == Some(StateType::Closed) {
|
||||
bail!("pr-merge: PR #{} is closed", args.number);
|
||||
}
|
||||
|
||||
|
|
@ -78,15 +94,20 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
check_ready(client, repo, args.number, &pull)?;
|
||||
}
|
||||
|
||||
let payload = json!({
|
||||
"Do": args.method.forgejo_do(),
|
||||
"delete_branch_after_merge": !args.keep_branch,
|
||||
"force_merge": args.force,
|
||||
});
|
||||
client.post_no_content(
|
||||
&format!("/repos/{repo}/pulls/{}/merge", args.number),
|
||||
&payload,
|
||||
)?;
|
||||
let payload = MergePullRequestOption {
|
||||
r#do: args.method.as_option_do(),
|
||||
merge_commit_id: None,
|
||||
merge_message_field: None,
|
||||
merge_title_field: None,
|
||||
delete_branch_after_merge: Some(!args.keep_branch),
|
||||
force_merge: Some(args.force),
|
||||
head_commit_id: None,
|
||||
merge_when_checks_succeed: None,
|
||||
};
|
||||
client
|
||||
.api()
|
||||
.repo_merge_pull_request(owner, name, idx, payload)
|
||||
.send()?;
|
||||
|
||||
let deleted = if args.keep_branch {
|
||||
""
|
||||
|
|
@ -105,8 +126,8 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
/// mergeable, CI must not be red/pending, and no review may request changes.
|
||||
/// Bails with an actionable message (pointing at `--force`) on the first
|
||||
/// failure.
|
||||
fn check_ready(client: &Client, repo: &str, number: u64, pull: &Value) -> Result<()> {
|
||||
match pull.get("mergeable").and_then(Value::as_bool) {
|
||||
fn check_ready(client: &Client, repo: &str, number: u64, pull: &PullRequest) -> Result<()> {
|
||||
match pull.mergeable {
|
||||
Some(true) => {}
|
||||
Some(false) => bail!(
|
||||
"pr-merge: PR #{number} is not mergeable (conflicts). Rebase it, or pass --force."
|
||||
|
|
@ -116,20 +137,18 @@ fn check_ready(client: &Client, repo: &str, number: u64, pull: &Value) -> Result
|
|||
),
|
||||
}
|
||||
|
||||
if let Some(sha) = pull
|
||||
.get("head")
|
||||
.and_then(|h| h.get("sha"))
|
||||
.and_then(Value::as_str)
|
||||
{
|
||||
let combined = client.get_json(&format!("/repos/{repo}/commits/{sha}/status"))?;
|
||||
let state = combined.get("state").and_then(Value::as_str).unwrap_or("");
|
||||
let has_statuses = combined
|
||||
.get("statuses")
|
||||
.and_then(Value::as_array)
|
||||
.is_some_and(|a| !a.is_empty());
|
||||
if let Some(sha) = pull.head.as_ref().and_then(|h| h.sha.as_deref()) {
|
||||
let (owner, name) = split_repo(repo)?;
|
||||
let (_, combined) = client
|
||||
.api()
|
||||
.repo_get_combined_status_by_ref(owner, name, sha)
|
||||
.send()?;
|
||||
let state = combined.state;
|
||||
let has_statuses = combined.statuses.as_ref().is_some_and(|a| !a.is_empty());
|
||||
// An empty status set means no CI is configured — not a blocker.
|
||||
// Anything other than success once CI exists blocks the merge.
|
||||
if has_statuses && state != "success" {
|
||||
if has_statuses && state != Some(CommitStatusState::Success) {
|
||||
let state = super::pr_status::status_state_str(state);
|
||||
bail!(
|
||||
"pr-merge: PR #{number} CI is not green (state: {state}). Wait for green, or pass --force."
|
||||
);
|
||||
|
|
@ -167,13 +186,20 @@ mod tests {
|
|||
#[test]
|
||||
fn merge_payload_shape() {
|
||||
// delete-by-default: keep_branch=false → delete_branch_after_merge=true.
|
||||
let payload = json!({
|
||||
"Do": Method::Merge.forgejo_do(),
|
||||
"delete_branch_after_merge": true,
|
||||
"force_merge": false,
|
||||
});
|
||||
assert_eq!(payload["Do"], "merge");
|
||||
assert_eq!(payload["delete_branch_after_merge"], true);
|
||||
assert_eq!(payload["force_merge"], false);
|
||||
// Pin the wire shape of the typed body — `Do` casing included.
|
||||
let payload = MergePullRequestOption {
|
||||
r#do: Method::Merge.as_option_do(),
|
||||
merge_commit_id: None,
|
||||
merge_message_field: None,
|
||||
merge_title_field: None,
|
||||
delete_branch_after_merge: Some(true),
|
||||
force_merge: Some(false),
|
||||
head_commit_id: None,
|
||||
merge_when_checks_succeed: None,
|
||||
};
|
||||
let wire = serde_json::to_value(&payload).unwrap();
|
||||
assert_eq!(wire["Do"], "merge");
|
||||
assert_eq!(wire["delete_branch_after_merge"], true);
|
||||
assert_eq!(wire["force_merge"], false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue