diff options
Diffstat (limited to 'src/bin/ngit/sub_commands/list.rs')
| -rw-r--r-- | src/bin/ngit/sub_commands/list.rs | 174 |
1 files changed, 111 insertions, 63 deletions
diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs index ee9840e..f040c63 100644 --- a/src/bin/ngit/sub_commands/list.rs +++ b/src/bin/ngit/sub_commands/list.rs | |||
| @@ -15,9 +15,10 @@ use ngit::{ | |||
| 15 | }, | 15 | }, |
| 16 | fetch::fetch_from_git_server, | 16 | fetch::fetch_from_git_server, |
| 17 | git_events::{ | 17 | git_events::{ |
| 18 | KIND_COMMENT, KIND_LABEL, KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, | 18 | KIND_COMMENT, KIND_COVER_NOTE, KIND_LABEL, KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, |
| 19 | get_commit_id_from_patch, get_labels_and_subject, | 19 | get_commit_id_from_patch, get_labels_and_subject, |
| 20 | get_pr_tip_event_or_most_recent_patch_with_ancestors, get_status, status_kinds, tag_value, | 20 | get_pr_tip_event_or_most_recent_patch_with_ancestors, get_status, process_cover_note, |
| 21 | status_kinds, tag_value, | ||
| 21 | }, | 22 | }, |
| 22 | repo_ref::{RepoRef, is_grasp_server_in_list}, | 23 | repo_ref::{RepoRef, is_grasp_server_in_list}, |
| 23 | }; | 24 | }; |
| @@ -222,7 +223,10 @@ pub async fn launch( | |||
| 222 | if !label_filter.is_empty() { | 223 | if !label_filter.is_empty() { |
| 223 | let proposal_labels_lower: HashSet<String> = | 224 | let proposal_labels_lower: HashSet<String> = |
| 224 | proposal_labels.iter().map(|l| l.to_lowercase()).collect(); | 225 | proposal_labels.iter().map(|l| l.to_lowercase()).collect(); |
| 225 | if !label_filter.iter().any(|l| proposal_labels_lower.contains(l)) { | 226 | if !label_filter |
| 227 | .iter() | ||
| 228 | .any(|l| proposal_labels_lower.contains(l)) | ||
| 229 | { | ||
| 226 | return None; | 230 | return None; |
| 227 | } | 231 | } |
| 228 | } | 232 | } |
| @@ -246,6 +250,16 @@ pub async fn launch( | |||
| 246 | .await? | 250 | .await? |
| 247 | .len() | 251 | .len() |
| 248 | }; | 252 | }; |
| 253 | // Fetch kind-1624 cover note events for this proposal. | ||
| 254 | let cover_note_events = get_events_from_local_cache( | ||
| 255 | git_repo_path, | ||
| 256 | vec![ | ||
| 257 | nostr::Filter::default() | ||
| 258 | .event(target_id) | ||
| 259 | .kind(KIND_COVER_NOTE), | ||
| 260 | ], | ||
| 261 | ) | ||
| 262 | .await?; | ||
| 249 | let relay_hint = repo_ref.relays.first(); | 263 | let relay_hint = repo_ref.relays.first(); |
| 250 | return show_proposal_details( | 264 | return show_proposal_details( |
| 251 | &filtered_proposals, | 265 | &filtered_proposals, |
| @@ -254,6 +268,8 @@ pub async fn launch( | |||
| 254 | show_comments, | 268 | show_comments, |
| 255 | comment_count, | 269 | comment_count, |
| 256 | &comments, | 270 | &comments, |
| 271 | &cover_note_events, | ||
| 272 | &repo_ref, | ||
| 257 | relay_hint, | 273 | relay_hint, |
| 258 | ); | 274 | ); |
| 259 | } | 275 | } |
| @@ -407,38 +423,40 @@ fn output_json( | |||
| 407 | ) -> Result<()> { | 423 | ) -> Result<()> { |
| 408 | let json_output: Vec<serde_json::Value> = proposals | 424 | let json_output: Vec<serde_json::Value> = proposals |
| 409 | .iter() | 425 | .iter() |
| 410 | .map(|(proposal, status_kind, proposal_labels, subject_override)| { | 426 | .map( |
| 411 | let id = event_id_to_nevent(proposal.id, relay_hint); | 427 | |(proposal, status_kind, proposal_labels, subject_override)| { |
| 412 | let status = status_kind_to_str(*status_kind).to_string(); | 428 | let id = event_id_to_nevent(proposal.id, relay_hint); |
| 413 | let (title, author, branch) = if let Ok(cl) = event_to_cover_letter(proposal) { | 429 | let status = status_kind_to_str(*status_kind).to_string(); |
| 414 | ( | 430 | let (title, author, branch) = if let Ok(cl) = event_to_cover_letter(proposal) { |
| 415 | subject_override.clone().unwrap_or(cl.title.clone()), | 431 | ( |
| 416 | proposal.pubkey.to_bech32().unwrap_or_default(), | 432 | subject_override.clone().unwrap_or(cl.title.clone()), |
| 417 | cl.get_branch_name_with_pr_prefix_and_shorthand_id() | 433 | proposal.pubkey.to_bech32().unwrap_or_default(), |
| 418 | .unwrap_or_default(), | 434 | cl.get_branch_name_with_pr_prefix_and_shorthand_id() |
| 419 | ) | 435 | .unwrap_or_default(), |
| 420 | } else { | ||
| 421 | let title = subject_override.clone().unwrap_or_else(|| { | ||
| 422 | tag_value(proposal, "description").map_or_else( | ||
| 423 | |_| proposal.id.to_string(), | ||
| 424 | |d| d.split('\n').collect::<Vec<&str>>()[0].to_string(), | ||
| 425 | ) | 436 | ) |
| 426 | }); | 437 | } else { |
| 427 | ( | 438 | let title = subject_override.clone().unwrap_or_else(|| { |
| 428 | title, | 439 | tag_value(proposal, "description").map_or_else( |
| 429 | proposal.pubkey.to_bech32().unwrap_or_default(), | 440 | |_| proposal.id.to_string(), |
| 430 | String::new(), | 441 | |d| d.split('\n').collect::<Vec<&str>>()[0].to_string(), |
| 431 | ) | 442 | ) |
| 432 | }; | 443 | }); |
| 433 | serde_json::json!({ | 444 | ( |
| 434 | "id": id, | 445 | title, |
| 435 | "status": status, | 446 | proposal.pubkey.to_bech32().unwrap_or_default(), |
| 436 | "subject": title, | 447 | String::new(), |
| 437 | "author": author, | 448 | ) |
| 438 | "branch": branch, | 449 | }; |
| 439 | "labels": proposal_labels, | 450 | serde_json::json!({ |
| 440 | }) | 451 | "id": id, |
| 441 | }) | 452 | "status": status, |
| 453 | "subject": title, | ||
| 454 | "author": author, | ||
| 455 | "branch": branch, | ||
| 456 | "labels": proposal_labels, | ||
| 457 | }) | ||
| 458 | }, | ||
| 459 | ) | ||
| 442 | .collect(); | 460 | .collect(); |
| 443 | 461 | ||
| 444 | println!("{}", serde_json::to_string_pretty(&json_output)?); | 462 | println!("{}", serde_json::to_string_pretty(&json_output)?); |
| @@ -472,7 +490,7 @@ fn comment_reply_to(comment: &nostr::Event) -> Option<nostr::EventId> { | |||
| 472 | }) | 490 | }) |
| 473 | } | 491 | } |
| 474 | 492 | ||
| 475 | #[allow(clippy::too_many_lines)] | 493 | #[allow(clippy::too_many_lines, clippy::too_many_arguments)] |
| 476 | fn show_proposal_details( | 494 | fn show_proposal_details( |
| 477 | proposals: &[(&nostr::Event, Kind, Vec<String>, Option<String>)], | 495 | proposals: &[(&nostr::Event, Kind, Vec<String>, Option<String>)], |
| 478 | event_id_or_nevent: &str, | 496 | event_id_or_nevent: &str, |
| @@ -480,6 +498,8 @@ fn show_proposal_details( | |||
| 480 | show_comments: bool, | 498 | show_comments: bool, |
| 481 | comment_count: usize, | 499 | comment_count: usize, |
| 482 | comments: &[nostr::Event], | 500 | comments: &[nostr::Event], |
| 501 | cover_note_events: &[nostr::Event], | ||
| 502 | repo_ref: &RepoRef, | ||
| 483 | relay_hint: Option<&RelayUrl>, | 503 | relay_hint: Option<&RelayUrl>, |
| 484 | ) -> Result<()> { | 504 | ) -> Result<()> { |
| 485 | use nostr::ToBech32; | 505 | use nostr::ToBech32; |
| @@ -500,13 +520,41 @@ fn show_proposal_details( | |||
| 500 | .unwrap_or(&cover_letter.title) | 520 | .unwrap_or(&cover_letter.title) |
| 501 | .to_string(); | 521 | .to_string(); |
| 502 | 522 | ||
| 523 | // Resolve the effective cover note (kind 1624) for this proposal. | ||
| 524 | let cover_note = process_cover_note(proposal, repo_ref, cover_note_events); | ||
| 525 | |||
| 503 | if json { | 526 | if json { |
| 504 | let json_output = if show_comments { | 527 | let cover_note_json = cover_note.as_ref().map(|(cn, by_different_author)| { |
| 528 | let mut obj = serde_json::json!({ | ||
| 529 | "id": event_id_to_nevent(cn.id, relay_hint), | ||
| 530 | "author": cn.pubkey.to_bech32().unwrap_or_default(), | ||
| 531 | "created_at": cn.created_at.as_secs(), | ||
| 532 | "body": cn.content, | ||
| 533 | }); | ||
| 534 | if *by_different_author { | ||
| 535 | obj["by_maintainer"] = serde_json::Value::Bool(true); | ||
| 536 | } | ||
| 537 | obj | ||
| 538 | }); | ||
| 539 | |||
| 540 | let mut json_obj = serde_json::json!({ | ||
| 541 | "id": event_id_to_nevent(proposal.id, relay_hint), | ||
| 542 | "status": status_kind_to_str(*status_kind), | ||
| 543 | "subject": display_title, | ||
| 544 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), | ||
| 545 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | ||
| 546 | "labels": proposal_labels, | ||
| 547 | "comment_count": comment_count, | ||
| 548 | "description": cover_letter.description, | ||
| 549 | }); | ||
| 550 | if let Some(cn) = cover_note_json { | ||
| 551 | json_obj["cover_note"] = cn; | ||
| 552 | } | ||
| 553 | if show_comments { | ||
| 505 | let comments_json: Vec<serde_json::Value> = comments | 554 | let comments_json: Vec<serde_json::Value> = comments |
| 506 | .iter() | 555 | .iter() |
| 507 | .map(|c| { | 556 | .map(|c| { |
| 508 | let reply_to = | 557 | let reply_to = comment_reply_to(c).map(|id| event_id_to_nevent(id, relay_hint)); |
| 509 | comment_reply_to(c).map(|id| event_id_to_nevent(id, relay_hint)); | ||
| 510 | serde_json::json!({ | 558 | serde_json::json!({ |
| 511 | "id": event_id_to_nevent(c.id, relay_hint), | 559 | "id": event_id_to_nevent(c.id, relay_hint), |
| 512 | "author": c.pubkey.to_bech32().unwrap_or_default(), | 560 | "author": c.pubkey.to_bech32().unwrap_or_default(), |
| @@ -516,30 +564,9 @@ fn show_proposal_details( | |||
| 516 | }) | 564 | }) |
| 517 | }) | 565 | }) |
| 518 | .collect(); | 566 | .collect(); |
| 519 | serde_json::json!({ | 567 | json_obj["comments"] = serde_json::Value::Array(comments_json); |
| 520 | "id": event_id_to_nevent(proposal.id, relay_hint), | 568 | } |
| 521 | "status": status_kind_to_str(*status_kind), | 569 | println!("{}", serde_json::to_string_pretty(&json_obj)?); |
| 522 | "subject": display_title, | ||
| 523 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), | ||
| 524 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | ||
| 525 | "labels": proposal_labels, | ||
| 526 | "comment_count": comment_count, | ||
| 527 | "comments": comments_json, | ||
| 528 | "description": cover_letter.description, | ||
| 529 | }) | ||
| 530 | } else { | ||
| 531 | serde_json::json!({ | ||
| 532 | "id": event_id_to_nevent(proposal.id, relay_hint), | ||
| 533 | "status": status_kind_to_str(*status_kind), | ||
| 534 | "subject": display_title, | ||
| 535 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), | ||
| 536 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | ||
| 537 | "labels": proposal_labels, | ||
| 538 | "comment_count": comment_count, | ||
| 539 | "description": cover_letter.description, | ||
| 540 | }) | ||
| 541 | }; | ||
| 542 | println!("{}", serde_json::to_string_pretty(&json_output)?); | ||
| 543 | return Ok(()); | 570 | return Ok(()); |
| 544 | } | 571 | } |
| 545 | 572 | ||
| @@ -562,7 +589,28 @@ fn show_proposal_details( | |||
| 562 | println!("Labels: {labels_str}"); | 589 | println!("Labels: {labels_str}"); |
| 563 | } | 590 | } |
| 564 | 591 | ||
| 565 | if !cover_letter.description.is_empty() { | 592 | if let Some((cn, by_different_author)) = &cover_note { |
| 593 | println!(); | ||
| 594 | if *by_different_author { | ||
| 595 | println!( | ||
| 596 | "Cover Note (by {}):", | ||
| 597 | cn.pubkey.to_bech32().unwrap_or_default() | ||
| 598 | ); | ||
| 599 | } else { | ||
| 600 | println!("Cover Note:"); | ||
| 601 | } | ||
| 602 | for line in cn.content.lines() { | ||
| 603 | println!(" {line}"); | ||
| 604 | } | ||
| 605 | // Show original description only when --comments is used. | ||
| 606 | if show_comments && !cover_letter.description.is_empty() { | ||
| 607 | println!(); | ||
| 608 | println!("Original Description:"); | ||
| 609 | for line in cover_letter.description.lines() { | ||
| 610 | println!(" {line}"); | ||
| 611 | } | ||
| 612 | } | ||
| 613 | } else if !cover_letter.description.is_empty() { | ||
| 566 | println!(); | 614 | println!(); |
| 567 | println!("Description:"); | 615 | println!("Description:"); |
| 568 | for line in cover_letter.description.lines() { | 616 | for line in cover_letter.description.lines() { |