diff --git a/docs/tools/forge-cli.md b/docs/tools/forge-cli.md
index 0cc936c9..d26344c3 100644
--- a/docs/tools/forge-cli.md
+++ b/docs/tools/forge-cli.md
@@ -532,7 +532,7 @@ Create a pull request
Default value: `main`
* `--body
` — Inline body text
* `--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
* `--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:**
* `--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)
* `--list` — List every repo the current user watches (ignores `[repo]`)
diff --git a/hive-forge/src/verbs/pr_create.rs b/hive-forge/src/verbs/pr_create.rs
index 8885fca9..f427febc 100644
--- a/hive-forge/src/verbs/pr_create.rs
+++ b/hive-forge/src/verbs/pr_create.rs
@@ -50,7 +50,8 @@ pub struct Args {
/// Read body from a file. `-` means stdin.
#[arg(long = "body-file")]
body_file: Option,
- /// 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)]
draft: bool,
/// 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
/// error from writing the PR URL to stdout.
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();
if args.agit {
return agit_create(client, &args, &body);
@@ -109,10 +113,9 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
let all = labels::repo_labels(client)?;
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
- // swagger) — the raw client used to send both and the server
- // silently dropped them, so omitting them here changes nothing.
+ // swagger).
let payload = CreatePullRequestOption {
assignee: None,
assignees: None,
@@ -201,6 +204,9 @@ fn agit_create(client: &Client, args: &Args, body: &str) -> Result<()> {
);
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 let Some(number) = pr_number_from_url(&url) {
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() {
apply_agit_labels(client, &args.labels, &url)?;
}
- println!("{url}");
Ok(())
}
@@ -380,6 +385,8 @@ fn remote_content(line: &str) -> Option<&str> {
.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 {
remote_content(line).is_some()
}
@@ -395,6 +402,18 @@ fn is_pr_hint_opener(line: &str) -> bool {
mod tests {
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]
fn agit_parse_pr_url_picks_pulls_link() {
let stderr = "\
diff --git a/hive-forge/src/verbs/subscription.rs b/hive-forge/src/verbs/subscription.rs
index 621a4c48..4e7b1129 100644
--- a/hive-forge/src/verbs/subscription.rs
+++ b/hive-forge/src/verbs/subscription.rs
@@ -3,7 +3,7 @@
//! repo the user watches (`--list`, for auditing the notification
//! firehose).
-use anyhow::Result;
+use anyhow::{Result, bail};
use clap::Args as ClapArgs;
use serde_json::json;
@@ -21,7 +21,8 @@ pub struct Args {
/// Subscribe (watch the repo).
#[arg(long, group = "action")]
watch: bool,
- /// Mute (mark ignored).
+ /// Not supported: the forge API has no ignore endpoint. Errors; use
+ /// `--unwatch` to stop notifications.
#[arg(long, group = "action")]
ignore: bool,
/// Unsubscribe (clear watch + ignore).
@@ -32,14 +33,26 @@ pub struct Args {
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
/// current repo's watch (`--watch` / `--ignore` / `--unwatch`), or get
/// its current state.
///
/// # Errors
-/// Propagates transport / non-2xx errors from the Forgejo client calls
-/// and from `print_json`.
+/// Fails on `--ignore` (unsupported). Propagates transport / non-2xx
+/// errors from the Forgejo client calls and from `print_json`.
pub fn run(client: &Client, args: Args) -> Result<()> {
+ reject_ignore(&args)?;
if args.list {
// List every repo the authed user watches, so an agent can audit
// its notification firehose and decide what to `--unwatch`. Not
@@ -72,12 +85,9 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
println!("unsubscribed");
return Ok(());
}
- if args.watch || args.ignore {
- // Forgejo's PUT subscription endpoint takes no body — it always
- // subscribes (watch). The old raw client sent a
- // `{subscribed, ignored}` body which the server ignored, so
- // `--ignore` has always behaved like `--watch` server-side; the
- // typed call just makes that explicit.
+ if args.watch {
+ // Forgejo's PUT subscription endpoint takes no body: it always
+ // subscribes (watch).
let resp = client
.api()
.user_current_put_subscription(owner, name)
@@ -102,3 +112,28 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
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());
+ }
+}