hive-forge: pr reviews names the poster of an inline comment that is not the reviewer's
This commit is contained in:
parent
3a77e869eb
commit
c8c5a19761
3 changed files with 53 additions and 11 deletions
|
|
@ -77,7 +77,7 @@ hive-forge pr edit 42 --base main # retarget a stacked PR onto main
|
|||
hive-forge pr 42 # PR metadata as JSON
|
||||
hive-forge pr create --title "..." --head my-branch --push # also `git push forge my-branch`
|
||||
hive-forge pr create --title "..." --head my-branch --label area/ops # repeatable, same as issue create
|
||||
hive-forge pr reviews 42 # list reviews; each inline comment as `[#id path:line] text`, tagged (resolved by X) / (no position)
|
||||
hive-forge pr reviews 42 # list reviews; each inline comment as `[#id path:line] text`, tagged (by X) when its poster is not the reviewer / (resolved by X) / (no position)
|
||||
hive-forge pr reviews 42 --only unresolved # every inline comment nobody has resolved yet
|
||||
hive-forge pr timeline 42 # review rows list their line comments (id, path:line, state) too
|
||||
hive-forge pr reviews 42 --approve # submit APPROVED review
|
||||
|
|
|
|||
|
|
@ -167,6 +167,13 @@ fn resolved_by(c: &Value) -> Option<&str> {
|
|||
.and_then(Value::as_str)
|
||||
}
|
||||
|
||||
/// Login of whoever posted the comment, if the forge says.
|
||||
fn posted_by(c: &Value) -> Option<&str> {
|
||||
c.get("user")
|
||||
.and_then(|u| u.get("login"))
|
||||
.and_then(Value::as_str)
|
||||
}
|
||||
|
||||
/// Nobody has resolved the conversation yet. Position plays no part: a
|
||||
/// comment without one may be a live finding, and a filter built for a
|
||||
/// re-review gate must not hide it.
|
||||
|
|
@ -175,9 +182,14 @@ fn is_unresolved(c: &Value) -> bool {
|
|||
}
|
||||
|
||||
/// One inline comment as text: `[#id path:line] body`, continuation lines
|
||||
/// indented, `(resolved by X)` / `(no position: ...)` after the body. Shared by
|
||||
/// `pr reviews` and `pr timeline`.
|
||||
pub(crate) fn render_inline_comment(c: &Value) -> String {
|
||||
/// indented, `(by X)` / `(resolved by X)` / `(no position: ...)` after the body.
|
||||
/// Shared by `pr reviews` and `pr timeline`.
|
||||
///
|
||||
/// A reply is stored in the review of the comment it answers, so under
|
||||
/// `reviewer`'s heading it can be someone else's: `(by X)` names the poster
|
||||
/// when it differs from `reviewer`. `pr timeline` shows who commented on its
|
||||
/// own and passes `None`.
|
||||
pub(crate) fn render_inline_comment(c: &Value, reviewer: Option<&str>) -> String {
|
||||
let id = c.get("id").and_then(Value::as_i64).unwrap_or(0);
|
||||
let body = c
|
||||
.get("body")
|
||||
|
|
@ -186,6 +198,11 @@ pub(crate) fn render_inline_comment(c: &Value) -> String {
|
|||
.trim()
|
||||
.replace('\n', "\n ");
|
||||
let mut out = format!("[#{id} {}] {body}", comment_location(c));
|
||||
if let (Some(poster), Some(reviewer)) = (posted_by(c), reviewer)
|
||||
&& poster != reviewer
|
||||
{
|
||||
write!(out, " (by {poster})").ok();
|
||||
}
|
||||
if let Some(who) = resolved_by(c) {
|
||||
write!(out, " (resolved by {who})").ok();
|
||||
}
|
||||
|
|
@ -232,6 +249,7 @@ fn list_reviews_json(
|
|||
"path": c.get("path"),
|
||||
"line": c.get("position"),
|
||||
"body": c.get("body"),
|
||||
"user": posted_by(c),
|
||||
"resolved_by": resolved_by(c),
|
||||
"no_position": has_no_position(c),
|
||||
})
|
||||
|
|
@ -296,7 +314,7 @@ fn list_reviews_text(client: &Client, number: u64, reviews: &[Value], only: Opti
|
|||
println!("{body}");
|
||||
}
|
||||
for c in &inline {
|
||||
println!(" {}", render_inline_comment(c));
|
||||
println!(" {}", render_inline_comment(c, Some(user)));
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
|
@ -365,15 +383,15 @@ mod tests {
|
|||
#[test]
|
||||
fn render_shows_id_location_body_and_state_tags() {
|
||||
assert_eq!(
|
||||
render_inline_comment(&comment(7, 42, None)),
|
||||
render_inline_comment(&comment(7, 42, None), None),
|
||||
"[#7 src/a.rs:42] off by one"
|
||||
);
|
||||
assert_eq!(
|
||||
render_inline_comment(&comment(7, 42, Some("mara"))),
|
||||
render_inline_comment(&comment(7, 42, Some("mara")), None),
|
||||
"[#7 src/a.rs:42] off by one (resolved by mara)"
|
||||
);
|
||||
assert_eq!(
|
||||
render_inline_comment(&comment(7, 0, None)),
|
||||
render_inline_comment(&comment(7, 0, None), None),
|
||||
"[#7 src/a.rs] off by one (no position: outdated or file-level)"
|
||||
);
|
||||
}
|
||||
|
|
@ -381,6 +399,30 @@ mod tests {
|
|||
#[test]
|
||||
fn render_indents_continuation_lines() {
|
||||
let c = json!({"id": 1, "path": "f.rs", "position": 2, "body": "one\ntwo"});
|
||||
assert_eq!(render_inline_comment(&c), "[#1 f.rs:2] one\n two");
|
||||
assert_eq!(render_inline_comment(&c, None), "[#1 f.rs:2] one\n two");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_reply_by_someone_else_names_its_poster_under_the_reviewers_heading() {
|
||||
let mut c = comment(7, 42, None);
|
||||
c["user"] = json!({"login": "damocles"});
|
||||
assert_eq!(
|
||||
render_inline_comment(&c, Some("mara")),
|
||||
"[#7 src/a.rs:42] off by one (by damocles)"
|
||||
);
|
||||
// The reviewer's own comment, a comment with no poster, and a caller
|
||||
// that shows the poster elsewhere get no tag.
|
||||
assert_eq!(
|
||||
render_inline_comment(&c, Some("damocles")),
|
||||
"[#7 src/a.rs:42] off by one"
|
||||
);
|
||||
assert_eq!(
|
||||
render_inline_comment(&comment(7, 42, None), Some("mara")),
|
||||
"[#7 src/a.rs:42] off by one"
|
||||
);
|
||||
assert_eq!(
|
||||
render_inline_comment(&c, None),
|
||||
"[#7 src/a.rs:42] off by one"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -494,7 +494,7 @@ fn format_event(ev: &Value) -> String {
|
|||
let inline = ev.get("inline_comments").and_then(Value::as_array);
|
||||
for c in inline.into_iter().flatten() {
|
||||
out.push_str("\n ");
|
||||
out.push_str(&render_inline_comment(c));
|
||||
out.push_str(&render_inline_comment(c, None));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
|
@ -503,7 +503,7 @@ fn format_event(ev: &Value) -> String {
|
|||
"code" => {
|
||||
let body = ev.get("body").and_then(Value::as_str).unwrap_or("");
|
||||
match ev.get("inline") {
|
||||
Some(c) => format!("commented on a line\n {}", render_inline_comment(c)),
|
||||
Some(c) => format!("commented on a line\n {}", render_inline_comment(c, None)),
|
||||
None => format!("commented on a line: {body}"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue