OXYGENHUB

oh issue comment takes -F FILE bd91edeb44f8

Metal Gear noticed proposal create took a body file but issue comment
only took -b. Issue comment now takes -F FILE (and -F - for stdin) like
every other oh command with a body, and refuses an empty comment.

Closes #260
Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key d99ded2a40e1
When
· 2026-10-18 16:56 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
bd91edeb44f8f318af68521ca74c35ee48f09e1002ce0c5972bc8ac10cea6862
Depends on
5c6e92fd8d71
Depended on by
1c52d940b4a0
In bookmarks
api-unknown-fields cancel-landed-runs diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
In tags
v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
Edits
6 atoms, 0 tree operations, 2 files

Browse the tree at this change

modified crates/oh/src/main.rs +10 −2
…
138 unchanged lines
139 139
    Comment {
140 140
        number: i64,
141 141
        #[arg(long, short)]
142 -
        body: String,
142 +
        body: Option<String>,
143 +
        /// Read the body from a file ("-" for stdin), as proposal comment
144 +
        /// does (#260)
145 +
        #[arg(long, short = 'F', conflicts_with = "body")]
146 +
        body_file: Option<String>,
143 147
    },
144 148
}
145 149
…
250 unchanged lines
396 400
                c.send("POST", &format!("{base}/issues/{number}"), &IssueStateChange { state: "open".into(), reason: String::new() })?;
397 401
            println!("{} reopened #{} {}", ui::ok("✔"), i.number, i.title);
398 402
        }
399 -
        IssueCmd::Comment { number, body } => {
403 +
        IssueCmd::Comment { number, body, body_file } => {
404 +
            let body = body_of(body, body_file)?;
405 +
            if body.trim().is_empty() {
406 +
                bail!("give the comment with --body or --body-file");
407 +
            }
400 408
            c.send_text("POST", &format!("{base}/issues/{number}/comments"), &NewComment { body, path: None, line: None })?;
401 409
            println!("{} commented on #{number}", ui::ok("✔"));
402 410
        }
…
315 unchanged lines
modified crates/oh/tests/cli.rs +8 −1
…
114 unchanged lines
115 115
    assert_eq!((list[0]["number"].as_i64(), list[0]["title"].as_str()), (Some(1), Some("Broken thing")));
116 116
    let (_, out, _) = oh(w, &[], &["issue", "comment", "1", "--body", "Seen it too."]).await;
117 117
    assert!(out.contains("commented on #1"));
118 +
    // From a file, as proposal comment takes it (#260); nothing at all is refused.
119 +
    std::fs::write(w.join("note.md"), "Read from a file.\n").unwrap();
120 +
    let (_, out, _) = oh(w, &[], &["issue", "comment", "1", "-F", "note.md"]).await;
121 +
    assert!(out.contains("commented on #1"), "{out}");
122 +
    std::fs::remove_file(w.join("note.md")).unwrap();
123 +
    let (code, _, err) = oh(w, &[], &["issue", "comment", "1"]).await;
124 +
    assert!(code != 0 && err.contains("--body or --body-file"), "{err}");
118 125
    let (_, out, _) = oh(w, &[], &["issue", "view", "1", "--plain"]).await;
119 -
    assert!(out.contains("It breaks.") && out.contains("Seen it too."), "{out}");
126 +
    assert!(out.contains("It breaks.") && out.contains("Seen it too.") && out.contains("Read from a file."), "{out}");
120 127
    let (_, out, _) = oh(w, &[], &["issue", "close", "1"]).await;
121 128
    assert!(out.contains("closed #1"));
122 129
    let (_, out, _) = oh(w, &[], &["issue", "list", "--plain"]).await;
…
56 unchanged lines