feat(#1877): restructure hive-forge into pr/issue sub-verbs
Group issue/PR operations under `pr` and `issue` parent commands (`hive-forge pr close 42`, `issue create …`, `pr status --pr 42`) per the operator decision — kind-namespaced verbs replace the flat surface. - new `verbs::pr_cmd` / `verbs::issue_cmd` parent commands wrap the existing per-verb modules (reuse their Args + run fns) under `#[command(subcommand)]`. - kind-validation (the win over the old generic verbs): the generics that work on both (view/comment/comments/close/labels/assign/timeline) call `assert_kind` first, so `pr close <issue>` / `issue close <pr>` are rejected with a 'use the other command' message. PR-only / issue-only verbs are kind-correct by construction. `number` exposed `pub(crate)` on the shared verbs so the wrappers can probe it. - every flat kind verb (`close`, `pr-create`, `pr-status`, `issue-edit`, …) kept as a `#[command(hide = true)]` back-compat alias — still parses, dropped from --help; removed in a later sweep once usage migrates. (`pr`/`issue` bare-show become `pr show` / `issue show` — the names are now parents.) - docs/tools/forge.md documents the new surface + the deprecated aliases. cargo build/clippy/fmt clean, 54 tests pass; --help surface + alias parsing smoke-tested.
This commit is contained in:
parent
fe974c580d
commit
dc4c5460d5
12 changed files with 257 additions and 13 deletions
|
|
@ -12,7 +12,25 @@ as a proper Rust binary). Use it instead of ad-hoc curl pipelines.
|
||||||
|
|
||||||
## Verbs
|
## Verbs
|
||||||
|
|
||||||
|
**Kind-namespaced commands (preferred):** issue/PR operations are grouped
|
||||||
|
under `issue` and `pr` parent commands — `hive-forge pr close 42`,
|
||||||
|
`hive-forge issue create --title …`, `hive-forge pr status --pr 42`. The
|
||||||
|
`pr <verb>` / `issue <verb>` forms validate the number's kind (e.g. `pr close`
|
||||||
|
refuses an issue number, which the old generic `close` couldn't). Run
|
||||||
|
`hive-forge pr --help` / `hive-forge issue --help` for the full subcommand
|
||||||
|
list (show/create/edit/status/merge/reviews/commits/diff/view/comment/
|
||||||
|
comments/close/labels/assign/timeline as applicable).
|
||||||
|
|
||||||
|
The flat forms below (`close 42`, `pr-create …`, `pr-status …`, …) still work
|
||||||
|
as **hidden back-compat aliases** during the transition and are dropped from
|
||||||
|
`--help`; prefer the namespaced form. They'll be removed in a later sweep.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
hive-forge pr close 42 # close a PR (kind-validated)
|
||||||
|
hive-forge issue close 42 # close an issue (kind-validated)
|
||||||
|
hive-forge pr status --pr 42 # PR health (mergeable / CI / reviews)
|
||||||
|
hive-forge issue create --title "..." --body "..."
|
||||||
|
# --- flat aliases below remain valid (hidden) ---
|
||||||
hive-forge view 42 # title + body + comments
|
hive-forge view 42 # title + body + comments
|
||||||
hive-forge comments 42 # list all comments (human-readable)
|
hive-forge comments 42 # list all comments (human-readable)
|
||||||
hive-forge comments 42 --tail 10 # last 10 comments (count-then-page; efficient on long threads)
|
hive-forge comments 42 --tail 10 # last 10 comments (count-then-page; efficient on long threads)
|
||||||
|
|
|
||||||
|
|
@ -47,39 +47,54 @@ struct Cli {
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum Verb {
|
enum Verb {
|
||||||
|
// The kind verbs below are hidden back-compat aliases of the new
|
||||||
|
// `pr <verb>` / `issue <verb>` forms (`pr-close` -> `pr close`, bare
|
||||||
|
// `close` -> `pr close` / `issue close`, etc.). They still parse but are
|
||||||
|
// dropped from `--help`; a later change removes them once usage migrates.
|
||||||
/// Dump title + body + all comments for an issue or PR.
|
/// Dump title + body + all comments for an issue or PR.
|
||||||
|
#[command(hide = true)]
|
||||||
View(verbs::view::Args),
|
View(verbs::view::Args),
|
||||||
/// Print key fields of an issue as JSON.
|
/// Issue-scoped commands: `issue <show|create|edit|view|comment|comments|close|labels|assign|timeline> …`.
|
||||||
Issue(verbs::issue::Args),
|
Issue(verbs::issue_cmd::Args),
|
||||||
/// Create an issue. Prints the issue URL on success.
|
/// Create an issue. Prints the issue URL on success.
|
||||||
|
#[command(hide = true)]
|
||||||
IssueCreate(verbs::issue_create::Args),
|
IssueCreate(verbs::issue_create::Args),
|
||||||
/// Edit an issue's title, body, state, or milestone.
|
/// Edit an issue's title, body, state, or milestone.
|
||||||
|
#[command(hide = true)]
|
||||||
IssueEdit(verbs::issue_edit::Args),
|
IssueEdit(verbs::issue_edit::Args),
|
||||||
/// Print key fields of a PR as JSON.
|
/// PR-scoped commands: `pr <show|status|create|merge|reviews|commits|diff|view|comment|comments|close|labels|assign|timeline> …`.
|
||||||
Pr(verbs::pr::Args),
|
Pr(verbs::pr_cmd::Args),
|
||||||
/// List a PR's commits as JSON (sha, message, author date, author).
|
/// List a PR's commits as JSON (sha, message, author date, author).
|
||||||
/// Survives rebase-rewritten shas — message + author date let a
|
/// Survives rebase-rewritten shas — message + author date let a
|
||||||
/// caller match the rows against linear `main` history.
|
/// caller match the rows against linear `main` history.
|
||||||
|
#[command(hide = true)]
|
||||||
PrCommits(verbs::pr_commits::Args),
|
PrCommits(verbs::pr_commits::Args),
|
||||||
/// Create a pull request. Prints the PR URL on success.
|
/// Create a pull request. Prints the PR URL on success.
|
||||||
|
#[command(hide = true)]
|
||||||
PrCreate(verbs::pr_create::Args),
|
PrCreate(verbs::pr_create::Args),
|
||||||
/// Post a comment on an issue or PR.
|
/// Post a comment on an issue or PR.
|
||||||
|
#[command(hide = true)]
|
||||||
Comment(verbs::comment::Args),
|
Comment(verbs::comment::Args),
|
||||||
/// List all comments on an issue or PR.
|
/// List all comments on an issue or PR.
|
||||||
|
#[command(hide = true)]
|
||||||
Comments(verbs::comments::Args),
|
Comments(verbs::comments::Args),
|
||||||
/// Print the body (or full JSON) of a single comment by id.
|
/// Print the body (or full JSON) of a single comment by id.
|
||||||
CommentShow(verbs::comment_show::Args),
|
CommentShow(verbs::comment_show::Args),
|
||||||
/// Edit an existing comment by id.
|
/// Edit an existing comment by id.
|
||||||
CommentEdit(verbs::comment_edit::Args),
|
CommentEdit(verbs::comment_edit::Args),
|
||||||
/// Assign or unassign a user on an issue or PR.
|
/// Assign or unassign a user on an issue or PR.
|
||||||
|
#[command(hide = true)]
|
||||||
Assign(verbs::assign::Args),
|
Assign(verbs::assign::Args),
|
||||||
/// Close an issue or PR.
|
/// Close an issue or PR.
|
||||||
|
#[command(hide = true)]
|
||||||
Close(verbs::close::Args),
|
Close(verbs::close::Args),
|
||||||
/// List, add, or remove labels on an issue or PR.
|
/// List, add, or remove labels on an issue or PR.
|
||||||
|
#[command(hide = true)]
|
||||||
Labels(verbs::labels::Args),
|
Labels(verbs::labels::Args),
|
||||||
/// PR health view: mergeable state, CI checks, requested reviewers +
|
/// PR health view: mergeable state, CI checks, requested reviewers +
|
||||||
/// review verdicts, last-comment time (`--pr <n>`). `--sha` is a
|
/// review verdicts, last-comment time (`--pr <n>`). `--sha` is a
|
||||||
/// CI-only fast path. Exit code is a merge-readiness verdict.
|
/// CI-only fast path. Exit code is a merge-readiness verdict.
|
||||||
|
#[command(hide = true)]
|
||||||
PrStatus(verbs::pr_status::Args),
|
PrStatus(verbs::pr_status::Args),
|
||||||
/// Clone a forge repo (default `-r`/`HIVE_FORGE_REPO`) with
|
/// Clone a forge repo (default `-r`/`HIVE_FORGE_REPO`) with
|
||||||
/// credentials auto-injected. Pairs with `pr-create --agit`.
|
/// credentials auto-injected. Pairs with `pr-create --agit`.
|
||||||
|
|
@ -113,21 +128,25 @@ enum Verb {
|
||||||
/// Merge a PR (`--method merge|rebase`, default merge). Refuses unless
|
/// Merge a PR (`--method merge|rebase`, default merge). Refuses unless
|
||||||
/// mergeable + CI not red + no changes requested (`--force` overrides).
|
/// mergeable + CI not red + no changes requested (`--force` overrides).
|
||||||
/// Deletes the head branch unless `--keep-branch`. No squash option.
|
/// Deletes the head branch unless `--keep-branch`. No squash option.
|
||||||
|
#[command(hide = true)]
|
||||||
PrMerge(verbs::pr_merge::Args),
|
PrMerge(verbs::pr_merge::Args),
|
||||||
/// List a PR's reviews, or submit one: `--approve` /
|
/// List a PR's reviews, or submit one: `--approve` /
|
||||||
/// `--request-changes` / `--comment` (with `-m` for the body).
|
/// `--request-changes` / `--comment` (with `-m` for the body).
|
||||||
|
#[command(hide = true)]
|
||||||
PrReviews(verbs::pr_reviews::Args),
|
PrReviews(verbs::pr_reviews::Args),
|
||||||
/// List branches, optionally filtered.
|
/// List branches, optionally filtered.
|
||||||
Branches(verbs::branches::Args),
|
Branches(verbs::branches::Args),
|
||||||
/// Print the tree SHA at a branch or commit.
|
/// Print the tree SHA at a branch or commit.
|
||||||
TreeSha(verbs::tree_sha::Args),
|
TreeSha(verbs::tree_sha::Args),
|
||||||
/// Print the unified diff for a PR.
|
/// Print the unified diff for a PR.
|
||||||
|
#[command(hide = true)]
|
||||||
Diff(verbs::diff::Args),
|
Diff(verbs::diff::Args),
|
||||||
/// Get or set this user's watch subscription on a repo.
|
/// Get or set this user's watch subscription on a repo.
|
||||||
Subscription(verbs::subscription::Args),
|
Subscription(verbs::subscription::Args),
|
||||||
/// List timeline events on an issue or PR (closes, label adds,
|
/// List timeline events on an issue or PR (closes, label adds,
|
||||||
/// assignments, commit refs, pushes, etc.) — the audit trail
|
/// assignments, commit refs, pushes, etc.) — the audit trail
|
||||||
/// `view` + `comments` don't surface.
|
/// `view` + `comments` don't surface.
|
||||||
|
#[command(hide = true)]
|
||||||
Timeline(verbs::timeline::Args),
|
Timeline(verbs::timeline::Args),
|
||||||
/// Upload a file as an attachment to an issue.
|
/// Upload a file as an attachment to an issue.
|
||||||
AttachIssue(verbs::attach::IssueArgs),
|
AttachIssue(verbs::attach::IssueArgs),
|
||||||
|
|
@ -152,10 +171,10 @@ fn main() -> Result<()> {
|
||||||
let client = client::Client::from_env(cli.repo, cli.json).context("initialize forge client")?;
|
let client = client::Client::from_env(cli.repo, cli.json).context("initialize forge client")?;
|
||||||
match cli.verb {
|
match cli.verb {
|
||||||
Verb::View(a) => verbs::view::run(&client, a),
|
Verb::View(a) => verbs::view::run(&client, a),
|
||||||
Verb::Issue(a) => verbs::issue::run(&client, a),
|
Verb::Issue(a) => verbs::issue_cmd::run(&client, a),
|
||||||
Verb::IssueCreate(a) => verbs::issue_create::run(&client, a),
|
Verb::IssueCreate(a) => verbs::issue_create::run(&client, a),
|
||||||
Verb::IssueEdit(a) => verbs::issue_edit::run(&client, a),
|
Verb::IssueEdit(a) => verbs::issue_edit::run(&client, a),
|
||||||
Verb::Pr(a) => verbs::pr::run(&client, a),
|
Verb::Pr(a) => verbs::pr_cmd::run(&client, a),
|
||||||
Verb::PrCommits(a) => verbs::pr_commits::run(&client, a),
|
Verb::PrCommits(a) => verbs::pr_commits::run(&client, a),
|
||||||
Verb::PrCreate(a) => verbs::pr_create::run(&client, a),
|
Verb::PrCreate(a) => verbs::pr_create::run(&client, a),
|
||||||
Verb::Comment(a) => verbs::comment::run(&client, a),
|
Verb::Comment(a) => verbs::comment::run(&client, a),
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use crate::verbs::print_json;
|
||||||
#[derive(ClapArgs)]
|
#[derive(ClapArgs)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Issue or PR number.
|
/// Issue or PR number.
|
||||||
number: u64,
|
pub(crate) number: u64,
|
||||||
/// User login to assign (or unassign with `--remove`).
|
/// User login to assign (or unassign with `--remove`).
|
||||||
user: String,
|
user: String,
|
||||||
/// Remove the user instead of adding.
|
/// Remove the user instead of adding.
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use crate::verbs::print_json;
|
||||||
#[derive(ClapArgs)]
|
#[derive(ClapArgs)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Issue or PR number.
|
/// Issue or PR number.
|
||||||
number: u64,
|
pub(crate) number: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::verbs::print_json;
|
||||||
#[derive(ClapArgs)]
|
#[derive(ClapArgs)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Issue or PR number.
|
/// Issue or PR number.
|
||||||
number: u64,
|
pub(crate) number: u64,
|
||||||
/// Inline body text.
|
/// Inline body text.
|
||||||
#[arg(long, conflicts_with = "body_file")]
|
#[arg(long, conflicts_with = "body_file")]
|
||||||
body: Option<String>,
|
body: Option<String>,
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ const PAGE_SIZE: usize = 50;
|
||||||
#[derive(ClapArgs)]
|
#[derive(ClapArgs)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Issue or PR number.
|
/// Issue or PR number.
|
||||||
number: u64,
|
pub(crate) number: u64,
|
||||||
/// Page size for the head-of-thread shape (Forgejo caps at 50).
|
/// Page size for the head-of-thread shape (Forgejo caps at 50).
|
||||||
/// Mutually exclusive with `--tail`.
|
/// Mutually exclusive with `--tail`.
|
||||||
#[arg(long, default_value_t = 50, conflicts_with = "tail")]
|
#[arg(long, default_value_t = 50, conflicts_with = "tail")]
|
||||||
|
|
|
||||||
81
hive-forge/src/verbs/issue_cmd.rs
Normal file
81
hive-forge/src/verbs/issue_cmd.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
//! `issue <verb>` — issue-scoped sub-commands. Wraps the per-verb modules
|
||||||
|
//! under an `issue` parent so `hive-forge issue close 42`, `issue create …`,
|
||||||
|
//! etc. read as kind-namespaced commands. The generic verbs that also work on
|
||||||
|
//! PRs (view/comment/comments/close/labels/assign/timeline) kind-check the
|
||||||
|
//! number is an issue first (`assert_kind`); the issue-only verbs are
|
||||||
|
//! kind-correct by construction. The flat `issue-*` + bare generic verbs stay
|
||||||
|
//! as hidden back-compat aliases (see `main.rs`).
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::{Args as ClapArgs, Subcommand};
|
||||||
|
|
||||||
|
use crate::client::Client;
|
||||||
|
use crate::verbs::{self, Kind, assert_kind};
|
||||||
|
|
||||||
|
#[derive(ClapArgs)]
|
||||||
|
pub struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
cmd: Cmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Cmd {
|
||||||
|
/// Show issue metadata as JSON.
|
||||||
|
Show(verbs::issue::Args),
|
||||||
|
/// Create an issue.
|
||||||
|
Create(verbs::issue_create::Args),
|
||||||
|
/// Edit an issue's title / body / state / milestone.
|
||||||
|
Edit(verbs::issue_edit::Args),
|
||||||
|
/// Show title + body + comments.
|
||||||
|
View(verbs::view::Args),
|
||||||
|
/// Post a comment on the issue.
|
||||||
|
Comment(verbs::comment::Args),
|
||||||
|
/// List comments on the issue.
|
||||||
|
Comments(verbs::comments::Args),
|
||||||
|
/// Close the issue.
|
||||||
|
Close(verbs::close::Args),
|
||||||
|
/// List / add / remove labels.
|
||||||
|
Labels(verbs::labels::Args),
|
||||||
|
/// Assign or unassign a user.
|
||||||
|
Assign(verbs::assign::Args),
|
||||||
|
/// List timeline events.
|
||||||
|
Timeline(verbs::timeline::Args),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
|
match args.cmd {
|
||||||
|
// Issue-only verbs.
|
||||||
|
Cmd::Show(a) => verbs::issue::run(client, a),
|
||||||
|
Cmd::Create(a) => verbs::issue_create::run(client, a),
|
||||||
|
Cmd::Edit(a) => verbs::issue_edit::run(client, a),
|
||||||
|
// Generics shared with `pr` — verify the number is an issue first.
|
||||||
|
Cmd::View(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Issue)?;
|
||||||
|
verbs::view::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Comment(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Issue)?;
|
||||||
|
verbs::comment::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Comments(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Issue)?;
|
||||||
|
verbs::comments::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Close(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Issue)?;
|
||||||
|
verbs::close::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Labels(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Issue)?;
|
||||||
|
verbs::labels::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Assign(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Issue)?;
|
||||||
|
verbs::assign::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Timeline(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Issue)?;
|
||||||
|
verbs::timeline::run(client, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::verbs::print_json;
|
||||||
#[derive(ClapArgs)]
|
#[derive(ClapArgs)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Issue or PR number.
|
/// Issue or PR number.
|
||||||
number: u64,
|
pub(crate) number: u64,
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
action: Option<Action>,
|
action: Option<Action>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ pub mod comment_show;
|
||||||
pub mod comments;
|
pub mod comments;
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
pub mod issue;
|
pub mod issue;
|
||||||
|
pub mod issue_cmd;
|
||||||
pub mod issue_create;
|
pub mod issue_create;
|
||||||
pub mod issue_edit;
|
pub mod issue_edit;
|
||||||
pub mod labels;
|
pub mod labels;
|
||||||
|
|
@ -24,6 +25,7 @@ pub mod lint;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod milestone;
|
pub mod milestone;
|
||||||
pub mod pr;
|
pub mod pr;
|
||||||
|
pub mod pr_cmd;
|
||||||
pub mod pr_commits;
|
pub mod pr_commits;
|
||||||
pub mod pr_create;
|
pub mod pr_create;
|
||||||
pub mod pr_merge;
|
pub mod pr_merge;
|
||||||
|
|
@ -52,6 +54,37 @@ pub(crate) fn print_json(v: &Value) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Issue-vs-PR kind, for the `pr <verb>` / `issue <verb>` sub-command
|
||||||
|
/// validation.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) enum Kind {
|
||||||
|
Pr,
|
||||||
|
Issue,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify `number` is the expected kind before a kind-namespaced verb (one
|
||||||
|
/// of the generics that work on both — close/comment/labels/…) acts on it —
|
||||||
|
/// the validation win the `pr <verb>` / `issue <verb>` split buys over the
|
||||||
|
/// old generic verbs. Forgejo's `/issues/{n}` endpoint serves both issues and
|
||||||
|
/// PRs and marks PRs with a non-null `pull_request` field, so one GET
|
||||||
|
/// classifies it. Errors with a "use the other command" message on mismatch.
|
||||||
|
pub(crate) fn assert_kind(client: &Client, number: u64, expected: Kind) -> Result<()> {
|
||||||
|
let repo = client.repo();
|
||||||
|
let v = client.get_json(&format!("/repos/{repo}/issues/{number}"))?;
|
||||||
|
let is_pr = v.get("pull_request").is_some_and(|p| !p.is_null());
|
||||||
|
match (expected, is_pr) {
|
||||||
|
(Kind::Pr, false) => {
|
||||||
|
anyhow::bail!(
|
||||||
|
"#{number} is an issue, not a PR — use `hive-forge issue <verb> {number}`"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(Kind::Issue, true) => {
|
||||||
|
anyhow::bail!("#{number} is a PR, not an issue — use `hive-forge pr <verb> {number}`")
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Minimal RFC 3986 unreserved-set percent encoder. Covers the subset of
|
/// Minimal RFC 3986 unreserved-set percent encoder. Covers the subset of
|
||||||
/// characters that show up in the values we splice into request paths —
|
/// characters that show up in the values we splice into request paths —
|
||||||
/// usernames, label names, artifact names — without pulling in a fresh
|
/// usernames, label names, artifact names — without pulling in a fresh
|
||||||
|
|
|
||||||
93
hive-forge/src/verbs/pr_cmd.rs
Normal file
93
hive-forge/src/verbs/pr_cmd.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
//! `pr <verb>` — PR-scoped sub-commands. Wraps the per-verb modules under a
|
||||||
|
//! `pr` parent so `hive-forge pr close 42`, `pr status --pr 42`, etc. read as
|
||||||
|
//! kind-namespaced commands. The generic verbs that also work on issues
|
||||||
|
//! (view/comment/comments/close/labels/assign/timeline) kind-check the number
|
||||||
|
//! is a PR first (`assert_kind`); the PR-only verbs hit `/pulls/…` and are
|
||||||
|
//! kind-correct by construction. The flat `pr-*` + bare generic verbs stay as
|
||||||
|
//! hidden back-compat aliases (see `main.rs`).
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::{Args as ClapArgs, Subcommand};
|
||||||
|
|
||||||
|
use crate::client::Client;
|
||||||
|
use crate::verbs::{self, Kind, assert_kind};
|
||||||
|
|
||||||
|
#[derive(ClapArgs)]
|
||||||
|
pub struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
cmd: Cmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Cmd {
|
||||||
|
/// Show PR metadata as JSON.
|
||||||
|
Show(verbs::pr::Args),
|
||||||
|
/// List the PR's commits as JSON.
|
||||||
|
Commits(verbs::pr_commits::Args),
|
||||||
|
/// Create a pull request.
|
||||||
|
Create(verbs::pr_create::Args),
|
||||||
|
/// PR health view: mergeable / CI / reviews.
|
||||||
|
Status(verbs::pr_status::Args),
|
||||||
|
/// Merge the PR.
|
||||||
|
Merge(verbs::pr_merge::Args),
|
||||||
|
/// List a PR's reviews, or submit one.
|
||||||
|
Reviews(verbs::pr_reviews::Args),
|
||||||
|
/// Print the PR's unified diff.
|
||||||
|
Diff(verbs::diff::Args),
|
||||||
|
/// Show title + body + comments.
|
||||||
|
View(verbs::view::Args),
|
||||||
|
/// Post a comment on the PR.
|
||||||
|
Comment(verbs::comment::Args),
|
||||||
|
/// List comments on the PR.
|
||||||
|
Comments(verbs::comments::Args),
|
||||||
|
/// Close the PR.
|
||||||
|
Close(verbs::close::Args),
|
||||||
|
/// List / add / remove labels.
|
||||||
|
Labels(verbs::labels::Args),
|
||||||
|
/// Assign or unassign a user.
|
||||||
|
Assign(verbs::assign::Args),
|
||||||
|
/// List timeline events.
|
||||||
|
Timeline(verbs::timeline::Args),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
|
match args.cmd {
|
||||||
|
// PR-only verbs — kind-correct by construction (hit `/pulls/…`).
|
||||||
|
Cmd::Show(a) => verbs::pr::run(client, a),
|
||||||
|
Cmd::Commits(a) => verbs::pr_commits::run(client, a),
|
||||||
|
Cmd::Create(a) => verbs::pr_create::run(client, a),
|
||||||
|
Cmd::Status(a) => verbs::pr_status::run(client, a),
|
||||||
|
Cmd::Merge(a) => verbs::pr_merge::run(client, a),
|
||||||
|
Cmd::Reviews(a) => verbs::pr_reviews::run(client, a),
|
||||||
|
Cmd::Diff(a) => verbs::diff::run(client, a),
|
||||||
|
// Generics shared with `issue` — verify the number is a PR first.
|
||||||
|
Cmd::View(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Pr)?;
|
||||||
|
verbs::view::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Comment(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Pr)?;
|
||||||
|
verbs::comment::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Comments(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Pr)?;
|
||||||
|
verbs::comments::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Close(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Pr)?;
|
||||||
|
verbs::close::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Labels(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Pr)?;
|
||||||
|
verbs::labels::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Assign(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Pr)?;
|
||||||
|
verbs::assign::run(client, a)
|
||||||
|
}
|
||||||
|
Cmd::Timeline(a) => {
|
||||||
|
assert_kind(client, a.number, Kind::Pr)?;
|
||||||
|
verbs::timeline::run(client, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ use crate::verbs::print_json;
|
||||||
#[derive(ClapArgs)]
|
#[derive(ClapArgs)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Issue or PR number.
|
/// Issue or PR number.
|
||||||
number: u64,
|
pub(crate) number: u64,
|
||||||
/// Page size (Forgejo caps at 50). Returns the first `N` events.
|
/// Page size (Forgejo caps at 50). Returns the first `N` events.
|
||||||
#[arg(long, default_value_t = 50)]
|
#[arg(long, default_value_t = 50)]
|
||||||
limit: u64,
|
limit: u64,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use crate::client::Client;
|
||||||
#[derive(ClapArgs)]
|
#[derive(ClapArgs)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Issue or PR number.
|
/// Issue or PR number.
|
||||||
number: u64,
|
pub(crate) number: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue