hive-forge: global -r/--repo flag instead of per-verb [repo] positional (mara@#407)
This commit is contained in:
parent
76cf2ffd36
commit
badf21714b
21 changed files with 46 additions and 85 deletions
|
|
@ -30,10 +30,14 @@ pub struct Client {
|
|||
|
||||
impl Client {
|
||||
/// Build a client from the standard environment variables.
|
||||
pub fn from_env() -> Result<Self> {
|
||||
/// `repo_override` (from the global `-r/--repo` flag) takes
|
||||
/// priority over `HIVE_FORGE_REPO`; the env var is the fallback
|
||||
/// default.
|
||||
pub fn from_env(repo_override: Option<String>) -> Result<Self> {
|
||||
let base = std::env::var("HIVE_FORGE_URL").unwrap_or_else(|_| DEFAULT_URL.to_owned());
|
||||
let default_repo =
|
||||
std::env::var("HIVE_FORGE_REPO").unwrap_or_else(|_| DEFAULT_REPO.to_owned());
|
||||
let default_repo = repo_override
|
||||
.or_else(|| std::env::var("HIVE_FORGE_REPO").ok())
|
||||
.unwrap_or_else(|| DEFAULT_REPO.to_owned());
|
||||
let token = read_token().context("read forge-token")?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
|
|
@ -59,9 +63,11 @@ impl Client {
|
|||
format!("{}/api/v1", self.base)
|
||||
}
|
||||
|
||||
/// Pick the user-supplied repo or fall back to the default.
|
||||
pub fn repo<'a>(&'a self, override_: Option<&'a str>) -> &'a str {
|
||||
override_.unwrap_or(&self.default_repo)
|
||||
/// Return the active repo. `from_env` already folded the
|
||||
/// `-r/--repo` override into `default_repo`, so verbs just read
|
||||
/// it as-is — no per-verb override plumbing.
|
||||
pub fn repo(&self) -> &str {
|
||||
&self.default_repo
|
||||
}
|
||||
|
||||
/// GET `<api>/<path>` and decode JSON.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ use clap::{Parser, Subcommand};
|
|||
disable_help_subcommand = true
|
||||
)]
|
||||
struct Cli {
|
||||
/// Repo override (default from `HIVE_FORGE_REPO`).
|
||||
/// Applies to any verb; replaces the per-verb `[repo]` trailing
|
||||
/// positional the bash helper used.
|
||||
#[arg(short = 'r', long, global = true)]
|
||||
repo: Option<String>,
|
||||
#[command(subcommand)]
|
||||
verb: Verb,
|
||||
}
|
||||
|
|
@ -80,7 +85,7 @@ enum Verb {
|
|||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
let client = client::Client::from_env().context("initialize forge client")?;
|
||||
let client = client::Client::from_env(cli.repo).context("initialize forge client")?;
|
||||
match cli.verb {
|
||||
Verb::View(a) => verbs::view::run(&client, a),
|
||||
Verb::Issue(a) => verbs::issue::run(&client, a),
|
||||
|
|
|
|||
|
|
@ -19,12 +19,10 @@ pub struct Args {
|
|||
/// Remove the user instead of adding.
|
||||
#[arg(long)]
|
||||
remove: bool,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let current = client.get_json(&format!("/repos/{repo}/issues/{}", args.number))?;
|
||||
let mut assignees: Vec<String> = current
|
||||
.get("assignees")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! `attach-issue <number> <file> [repo]` and `attach-comment
|
||||
//! <comment-id> <file> [repo]` — upload a file as an attachment.
|
||||
//! Prints the browser download URL.
|
||||
//! `attach-issue <number> <file>` and `attach-comment <comment-id>
|
||||
//! <file>` — upload a file as an attachment. Prints the browser
|
||||
//! download URL.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -16,8 +16,6 @@ pub struct IssueArgs {
|
|||
number: u64,
|
||||
/// File path to upload.
|
||||
file: PathBuf,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(ClapArgs)]
|
||||
|
|
@ -26,8 +24,6 @@ pub struct CommentArgs {
|
|||
id: u64,
|
||||
/// File path to upload.
|
||||
file: PathBuf,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run_issue(client: &Client, args: IssueArgs) -> Result<()> {
|
||||
|
|
@ -37,7 +33,7 @@ pub fn run_issue(client: &Client, args: IssueArgs) -> Result<()> {
|
|||
args.file.display()
|
||||
);
|
||||
}
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let resp = client.post_multipart_file(
|
||||
&format!("/repos/{repo}/issues/{}/assets", args.number),
|
||||
&args.file,
|
||||
|
|
@ -53,7 +49,7 @@ pub fn run_comment(client: &Client, args: CommentArgs) -> Result<()> {
|
|||
args.file.display()
|
||||
);
|
||||
}
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let resp = client.post_multipart_file(
|
||||
&format!("/repos/{repo}/issues/comments/{}/assets", args.id),
|
||||
&args.file,
|
||||
|
|
|
|||
|
|
@ -10,12 +10,10 @@ use crate::client::Client;
|
|||
pub struct Args {
|
||||
/// Substring pattern to filter branch names.
|
||||
pattern: Option<String>,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let v = client.get_json(&format!("/repos/{repo}/branches?limit=100"))?;
|
||||
let names: Vec<&str> = v
|
||||
.as_array()
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ use crate::verbs::print_json;
|
|||
pub struct Args {
|
||||
/// Issue or PR number.
|
||||
number: u64,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let resp = client.patch_json(
|
||||
&format!("/repos/{repo}/issues/{}", args.number),
|
||||
&json!({ "state": "closed" }),
|
||||
|
|
|
|||
|
|
@ -19,13 +19,11 @@ pub struct Args {
|
|||
/// Read body from a file. `-` means stdin.
|
||||
#[arg(long = "body-file")]
|
||||
body_file: Option<String>,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let body = body::resolve_required(args.body.as_deref(), args.body_file.as_deref(), "comment")?;
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let resp = client.post_json(
|
||||
&format!("/repos/{repo}/issues/{}/comments", args.number),
|
||||
&json!({ "body": body }),
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ pub struct Args {
|
|||
/// Read body from a file. `-` means stdin.
|
||||
#[arg(long = "body-file")]
|
||||
body_file: Option<String>,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
|
|
@ -29,7 +27,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
args.body_file.as_deref(),
|
||||
"comment-edit",
|
||||
)?;
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let resp = client.patch_json(
|
||||
&format!("/repos/{repo}/issues/comments/{}", args.id),
|
||||
&json!({ "body": body }),
|
||||
|
|
|
|||
|
|
@ -15,12 +15,10 @@ pub struct Args {
|
|||
/// Print full JSON envelope instead of just the body text.
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let v = client.get_json(&format!("/repos/{repo}/issues/comments/{}", args.id))?;
|
||||
if args.json {
|
||||
let trimmed = json!({
|
||||
|
|
|
|||
|
|
@ -9,12 +9,10 @@ use crate::client::Client;
|
|||
pub struct Args {
|
||||
/// PR number.
|
||||
number: u64,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let diff = client.get_text(&format!("/repos/{repo}/pulls/{}.diff", args.number), "text/plain")?;
|
||||
print!("{diff}");
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ use crate::verbs::print_json;
|
|||
pub struct Args {
|
||||
/// Issue number.
|
||||
number: u64,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let v = client.get_json(&format!("/repos/{repo}/issues/{}", args.number))?;
|
||||
let trimmed = json!({
|
||||
"number": v.get("number"),
|
||||
|
|
|
|||
|
|
@ -22,13 +22,11 @@ pub struct Args {
|
|||
/// Initial assignee login.
|
||||
#[arg(long)]
|
||||
assignee: Option<String>,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let body = body::resolve(args.body.as_deref(), args.body_file.as_deref())?.unwrap_or_default();
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let mut payload = json!({ "title": args.title, "body": body });
|
||||
if let Some(a) = args.assignee {
|
||||
payload["assignees"] = json!([a]);
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ pub struct Args {
|
|||
/// Milestone id (0 to unset).
|
||||
#[arg(long)]
|
||||
milestone: Option<u64>,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
|
|
@ -73,7 +71,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
payload.insert("milestone".into(), Value::Number(m.into()));
|
||||
}
|
||||
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let resp = client.patch_json(
|
||||
&format!("/repos/{repo}/issues/{}", args.number),
|
||||
&Value::Object(payload),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ enum Action {
|
|||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(None);
|
||||
let repo = client.repo();
|
||||
match args.action.unwrap_or(Action::List) {
|
||||
Action::List => {
|
||||
let labels = client.get_json(&format!("/repos/{repo}/issues/{}/labels", args.number))?;
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@ pub struct Args {
|
|||
#[derive(Subcommand)]
|
||||
enum Action {
|
||||
/// List open milestones as JSON.
|
||||
List {
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
},
|
||||
List,
|
||||
/// Create a milestone, print {id,title}.
|
||||
Create {
|
||||
/// Milestone title.
|
||||
|
|
@ -32,22 +29,18 @@ enum Action {
|
|||
/// Due date YYYY-MM-DD.
|
||||
#[arg(long)]
|
||||
due: Option<String>,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
},
|
||||
/// Close a milestone by id.
|
||||
Close {
|
||||
/// Milestone id.
|
||||
id: u64,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
match args.action.unwrap_or(Action::List { repo: None }) {
|
||||
Action::List { repo } => {
|
||||
let repo = client.repo(repo.as_deref());
|
||||
let repo = client.repo();
|
||||
match args.action.unwrap_or(Action::List) {
|
||||
Action::List => {
|
||||
let v =
|
||||
client.get_json(&format!("/repos/{repo}/milestones?state=open&limit=50"))?;
|
||||
let trimmed: Vec<Value> = v
|
||||
|
|
@ -69,13 +62,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
.unwrap_or_default();
|
||||
print_json(&Value::Array(trimmed))
|
||||
}
|
||||
Action::Create {
|
||||
title,
|
||||
desc,
|
||||
due,
|
||||
repo,
|
||||
} => {
|
||||
let repo = client.repo(repo.as_deref());
|
||||
Action::Create { title, desc, due } => {
|
||||
let mut payload = json!({ "title": title });
|
||||
if let Some(d) = desc.filter(|s| !s.is_empty()) {
|
||||
payload["description"] = Value::String(d);
|
||||
|
|
@ -89,8 +76,7 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
|||
"title": resp.get("title"),
|
||||
}))
|
||||
}
|
||||
Action::Close { id, repo } => {
|
||||
let repo = client.repo(repo.as_deref());
|
||||
Action::Close { id } => {
|
||||
let resp = client.patch_json(
|
||||
&format!("/repos/{repo}/milestones/{id}"),
|
||||
&json!({ "state": "closed" }),
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ use crate::verbs::print_json;
|
|||
pub struct Args {
|
||||
/// PR number.
|
||||
number: u64,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let v = client.get_json(&format!("/repos/{repo}/pulls/{}", args.number))?;
|
||||
let trimmed = json!({
|
||||
"number": v.get("number"),
|
||||
|
|
|
|||
|
|
@ -28,13 +28,11 @@ pub struct Args {
|
|||
/// Open as draft.
|
||||
#[arg(long)]
|
||||
draft: bool,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let body = body::resolve(args.body.as_deref(), args.body_file.as_deref())?.unwrap_or_default();
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let payload = json!({
|
||||
"title": args.title,
|
||||
"head": args.head,
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ use crate::verbs::print_json;
|
|||
pub struct Args {
|
||||
/// PR number.
|
||||
number: u64,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let v = client.get_json(&format!("/repos/{repo}/pulls/{}/reviews", args.number))?;
|
||||
let trimmed: Vec<Value> = v
|
||||
.as_array()
|
||||
|
|
|
|||
|
|
@ -19,12 +19,10 @@ pub struct Args {
|
|||
/// Unsubscribe (clear watch + ignore).
|
||||
#[arg(long, group = "action")]
|
||||
unwatch: bool,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
if args.unwatch {
|
||||
client.delete(&format!("/repos/{repo}/subscription"), None)?;
|
||||
println!("unsubscribed");
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ use crate::client::Client;
|
|||
pub struct Args {
|
||||
/// Branch name or commit SHA.
|
||||
reference: String,
|
||||
/// Repo override.
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
// Try branch first; the bash helper silently treats failure as
|
||||
// "not a branch, use the reference as a commit sha directly".
|
||||
let commit_sha = match client.get_json(&format!("/repos/{repo}/branches/{}", args.reference)) {
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ use crate::client::Client;
|
|||
pub struct Args {
|
||||
/// Issue or PR number.
|
||||
number: u64,
|
||||
/// Repo override (default from `HIVE_FORGE_REPO`).
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||
let repo = client.repo(args.repo.as_deref());
|
||||
let repo = client.repo();
|
||||
let issue = client.get_json(&format!("/repos/{repo}/issues/{}", args.number))?;
|
||||
let title = issue.get("title").and_then(Value::as_str).unwrap_or("");
|
||||
let body = issue.get("body").and_then(Value::as_str).unwrap_or("");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue