diff --git a/hive-forge/src/main.rs b/hive-forge/src/main.rs index 1aa05923..033a9103 100644 --- a/hive-forge/src/main.rs +++ b/hive-forge/src/main.rs @@ -57,6 +57,10 @@ enum Verb { IssueEdit(verbs::issue_edit::Args), /// Print key fields of a PR as JSON. Pr(verbs::pr::Args), + /// List a PR's commits as JSON (sha, message, author date, author). + /// Survives rebase-rewritten shas — message + author date let a + /// caller match the rows against linear `main` history. + PrCommits(verbs::pr_commits::Args), /// Create a pull request. Prints the PR URL on success. PrCreate(verbs::pr_create::Args), /// Post a comment on an issue or PR. @@ -146,6 +150,7 @@ fn main() -> Result<()> { Verb::IssueCreate(a) => verbs::issue_create::run(&client, a), Verb::IssueEdit(a) => verbs::issue_edit::run(&client, a), Verb::Pr(a) => verbs::pr::run(&client, a), + Verb::PrCommits(a) => verbs::pr_commits::run(&client, a), Verb::PrCreate(a) => verbs::pr_create::run(&client, a), Verb::Comment(a) => verbs::comment::run(&client, a), Verb::Comments(a) => verbs::comments::run(&client, a), diff --git a/hive-forge/src/verbs/mod.rs b/hive-forge/src/verbs/mod.rs index 843fef70..9aaa43b0 100644 --- a/hive-forge/src/verbs/mod.rs +++ b/hive-forge/src/verbs/mod.rs @@ -24,6 +24,7 @@ pub mod lint; pub mod list; pub mod milestone; pub mod pr; +pub mod pr_commits; pub mod pr_create; pub mod pr_merge; pub mod pr_reviews; diff --git a/hive-forge/src/verbs/pr_commits.rs b/hive-forge/src/verbs/pr_commits.rs new file mode 100644 index 00000000..fe22184d --- /dev/null +++ b/hive-forge/src/verbs/pr_commits.rs @@ -0,0 +1,49 @@ +//! `pr-commits [repo]` — list a PR's commits as JSON +//! (sha, message, author date, author login), paginated. +//! +//! The forge stores a PR's commits against its branch ref, so this +//! returns them even for a PR whose shas were rewritten by a rebase +//! merge. The commit message and author date survive a rebase +//! unchanged, so a caller can match these rows against the linear +//! `main` history to recover the merged-final commits when the PR's +//! recorded shas no longer resolve. + +use anyhow::Result; +use clap::Args as ClapArgs; +use serde_json::json; + +use crate::client::Client; +use crate::verbs::print_json; + +/// Page cap for the commit list. Forgejo serves up to 50 commits per +/// page; 40 pages (2000 commits) is far beyond any real PR. +const MAX_PAGES: u32 = 40; + +#[derive(ClapArgs)] +pub struct Args { + /// PR number. + number: u64, +} + +pub fn run(client: &Client, args: Args) -> Result<()> { + let repo = client.repo(); + let commits = client.get_json_all( + &format!("/repos/{repo}/pulls/{}/commits", args.number), + MAX_PAGES, + )?; + let trimmed: Vec<_> = commits + .iter() + .map(|c| { + let commit = c.get("commit"); + json!({ + "sha": c.get("sha"), + "message": commit.and_then(|x| x.get("message")), + "author_date": commit + .and_then(|x| x.get("author")) + .and_then(|a| a.get("date")), + "author": c.get("author").and_then(|u| u.get("login")), + }) + }) + .collect(); + print_json(&json!(trimmed)) +}