hive-forge: subscription --ignore errors instead of subscribing; warn on --draft; always print agit PR URL
This commit is contained in:
parent
0e5c61d12c
commit
6e32b432ec
3 changed files with 71 additions and 17 deletions
|
|
@ -532,7 +532,7 @@ Create a pull request
|
||||||
Default value: `main`
|
Default value: `main`
|
||||||
* `--body <BODY>` — Inline body text
|
* `--body <BODY>` — Inline body text
|
||||||
* `--body-file <BODY_FILE>` — Read body from a file. `-` means stdin
|
* `--body-file <BODY_FILE>` — Read body from a file. `-` means stdin
|
||||||
* `--draft` — Open as draft. Ignored in `--agit` mode
|
* `--draft` — Not supported by this forge (no draft field): warns on stderr and opens a normal PR
|
||||||
* `--push` — Push the local `--head` branch to `--remote` before creating the PR
|
* `--push` — Push the local `--head` branch to `--remote` before creating the PR
|
||||||
* `--remote <REMOTE>` — Remote to push to
|
* `--remote <REMOTE>` — Remote to push to
|
||||||
|
|
||||||
|
|
@ -1278,7 +1278,7 @@ Get/set this user's watch subscription on a repo, or --list all watched repos
|
||||||
###### **Options:**
|
###### **Options:**
|
||||||
|
|
||||||
* `--watch` — Subscribe (watch the repo)
|
* `--watch` — Subscribe (watch the repo)
|
||||||
* `--ignore` — Mute (mark ignored)
|
* `--ignore` — Not supported: the forge API has no ignore endpoint. Errors; use `--unwatch` to stop notifications
|
||||||
* `--unwatch` — Unsubscribe (clear watch + ignore)
|
* `--unwatch` — Unsubscribe (clear watch + ignore)
|
||||||
* `--list` — List every repo the current user watches (ignores `[repo]`)
|
* `--list` — List every repo the current user watches (ignores `[repo]`)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ pub struct Args {
|
||||||
/// Read body from a file. `-` means stdin.
|
/// Read body from a file. `-` means stdin.
|
||||||
#[arg(long = "body-file")]
|
#[arg(long = "body-file")]
|
||||||
body_file: Option<String>,
|
body_file: Option<String>,
|
||||||
/// Open as draft. Ignored in `--agit` mode.
|
/// Not supported by this forge (no draft field): warns on stderr and
|
||||||
|
/// opens a normal PR.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
draft: bool,
|
draft: bool,
|
||||||
/// Push the local `--head` branch to `--remote` before creating the PR.
|
/// Push the local `--head` branch to `--remote` before creating the PR.
|
||||||
|
|
@ -88,6 +89,9 @@ pub struct Args {
|
||||||
/// token missing/invalid), an unresolved `--label` name, and any I/O
|
/// token missing/invalid), an unresolved `--label` name, and any I/O
|
||||||
/// error from writing the PR URL to stdout.
|
/// error from writing the PR URL to stdout.
|
||||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
|
if args.draft {
|
||||||
|
eprintln!("{DRAFT_WARNING}");
|
||||||
|
}
|
||||||
let body = body::resolve(args.body.as_deref(), args.body_file.as_deref())?.unwrap_or_default();
|
let body = body::resolve(args.body.as_deref(), args.body_file.as_deref())?.unwrap_or_default();
|
||||||
if args.agit {
|
if args.agit {
|
||||||
return agit_create(client, &args, &body);
|
return agit_create(client, &args, &body);
|
||||||
|
|
@ -109,10 +113,9 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
let all = labels::repo_labels(client)?;
|
let all = labels::repo_labels(client)?;
|
||||||
Some(labels::resolve_ids(&all, &args.labels)?)
|
Some(labels::resolve_ids(&all, &args.labels)?)
|
||||||
};
|
};
|
||||||
// Note: Forgejo's CreatePullRequestOption has no `draft` /
|
// Forgejo's CreatePullRequestOption has no `draft` /
|
||||||
// `allow_maintainer_edit` fields (verified against the instance's
|
// `allow_maintainer_edit` fields (verified against the instance's
|
||||||
// swagger) — the raw client used to send both and the server
|
// swagger).
|
||||||
// silently dropped them, so omitting them here changes nothing.
|
|
||||||
let payload = CreatePullRequestOption {
|
let payload = CreatePullRequestOption {
|
||||||
assignee: None,
|
assignee: None,
|
||||||
assignees: None,
|
assignees: None,
|
||||||
|
|
@ -201,6 +204,9 @@ fn agit_create(client: &Client, args: &Args, body: &str) -> Result<()> {
|
||||||
);
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
// The PR exists once the push succeeds; print the URL before any
|
||||||
|
// follow-up so a failing step cannot hide it and prompt a duplicate.
|
||||||
|
println!("{url}");
|
||||||
if deferred_body {
|
if deferred_body {
|
||||||
if let Some(number) = pr_number_from_url(&url) {
|
if let Some(number) = pr_number_from_url(&url) {
|
||||||
let (owner, name) = client.owner_repo()?;
|
let (owner, name) = client.owner_repo()?;
|
||||||
|
|
@ -231,7 +237,6 @@ fn agit_create(client: &Client, args: &Args, body: &str) -> Result<()> {
|
||||||
if !args.labels.is_empty() {
|
if !args.labels.is_empty() {
|
||||||
apply_agit_labels(client, &args.labels, &url)?;
|
apply_agit_labels(client, &args.labels, &url)?;
|
||||||
}
|
}
|
||||||
println!("{url}");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,6 +385,8 @@ fn remote_content(line: &str) -> Option<&str> {
|
||||||
.map(str::trim_start)
|
.map(str::trim_start)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DRAFT_WARNING: &str = "warning: --draft is not supported by this forge; opening a normal PR";
|
||||||
|
|
||||||
fn is_remote_line(line: &str) -> bool {
|
fn is_remote_line(line: &str) -> bool {
|
||||||
remote_content(line).is_some()
|
remote_content(line).is_some()
|
||||||
}
|
}
|
||||||
|
|
@ -395,6 +402,18 @@ fn is_pr_hint_opener(line: &str) -> bool {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn draft_flag_parses() {
|
||||||
|
#[derive(clap::Parser)]
|
||||||
|
struct Cli {
|
||||||
|
#[command(flatten)]
|
||||||
|
args: Args,
|
||||||
|
}
|
||||||
|
use clap::Parser;
|
||||||
|
let cli = Cli::parse_from(["x", "--title", "t", "--head", "h", "--draft"]);
|
||||||
|
assert!(cli.args.draft);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn agit_parse_pr_url_picks_pulls_link() {
|
fn agit_parse_pr_url_picks_pulls_link() {
|
||||||
let stderr = "\
|
let stderr = "\
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
//! repo the user watches (`--list`, for auditing the notification
|
//! repo the user watches (`--list`, for auditing the notification
|
||||||
//! firehose).
|
//! firehose).
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Result, bail};
|
||||||
use clap::Args as ClapArgs;
|
use clap::Args as ClapArgs;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
|
@ -21,7 +21,8 @@ pub struct Args {
|
||||||
/// Subscribe (watch the repo).
|
/// Subscribe (watch the repo).
|
||||||
#[arg(long, group = "action")]
|
#[arg(long, group = "action")]
|
||||||
watch: bool,
|
watch: bool,
|
||||||
/// Mute (mark ignored).
|
/// Not supported: the forge API has no ignore endpoint. Errors; use
|
||||||
|
/// `--unwatch` to stop notifications.
|
||||||
#[arg(long, group = "action")]
|
#[arg(long, group = "action")]
|
||||||
ignore: bool,
|
ignore: bool,
|
||||||
/// Unsubscribe (clear watch + ignore).
|
/// Unsubscribe (clear watch + ignore).
|
||||||
|
|
@ -32,14 +33,26 @@ pub struct Args {
|
||||||
list: bool,
|
list: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `--ignore` has no Forgejo endpoint; fail rather than subscribe.
|
||||||
|
fn reject_ignore(args: &Args) -> Result<()> {
|
||||||
|
if args.ignore {
|
||||||
|
bail!(
|
||||||
|
"--ignore is not supported: the forge API has no ignore endpoint; \
|
||||||
|
use `--unwatch` to stop notifications"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Run the `subscription` verb: `--list` all watched repos, set the
|
/// Run the `subscription` verb: `--list` all watched repos, set the
|
||||||
/// current repo's watch (`--watch` / `--ignore` / `--unwatch`), or get
|
/// current repo's watch (`--watch` / `--ignore` / `--unwatch`), or get
|
||||||
/// its current state.
|
/// its current state.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Propagates transport / non-2xx errors from the Forgejo client calls
|
/// Fails on `--ignore` (unsupported). Propagates transport / non-2xx
|
||||||
/// and from `print_json`.
|
/// errors from the Forgejo client calls and from `print_json`.
|
||||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
|
reject_ignore(&args)?;
|
||||||
if args.list {
|
if args.list {
|
||||||
// List every repo the authed user watches, so an agent can audit
|
// List every repo the authed user watches, so an agent can audit
|
||||||
// its notification firehose and decide what to `--unwatch`. Not
|
// its notification firehose and decide what to `--unwatch`. Not
|
||||||
|
|
@ -72,12 +85,9 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
println!("unsubscribed");
|
println!("unsubscribed");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
if args.watch || args.ignore {
|
if args.watch {
|
||||||
// Forgejo's PUT subscription endpoint takes no body — it always
|
// Forgejo's PUT subscription endpoint takes no body: it always
|
||||||
// subscribes (watch). The old raw client sent a
|
// subscribes (watch).
|
||||||
// `{subscribed, ignored}` body which the server ignored, so
|
|
||||||
// `--ignore` has always behaved like `--watch` server-side; the
|
|
||||||
// typed call just makes that explicit.
|
|
||||||
let resp = client
|
let resp = client
|
||||||
.api()
|
.api()
|
||||||
.user_current_put_subscription(owner, name)
|
.user_current_put_subscription(owner, name)
|
||||||
|
|
@ -102,3 +112,28 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
Err(e) => Err(e.into()),
|
Err(e) => Err(e.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Cli {
|
||||||
|
#[command(flatten)]
|
||||||
|
args: Args,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ignore_is_rejected_pointing_at_unwatch() {
|
||||||
|
let cli = Cli::parse_from(["x", "--ignore"]);
|
||||||
|
let err = reject_ignore(&cli.args).unwrap_err().to_string();
|
||||||
|
assert!(err.contains("--unwatch"), "{err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn watch_is_not_rejected() {
|
||||||
|
let cli = Cli::parse_from(["x", "--watch"]);
|
||||||
|
assert!(reject_ignore(&cli.args).is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue