upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/bin/ngit/sub_commands/list.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/ngit/sub_commands/list.rs')
-rw-r--r--src/bin/ngit/sub_commands/list.rs174
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)]
476fn show_proposal_details( 494fn 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() {