diff options
Diffstat (limited to 'src/bin/ngit/sub_commands/list.rs')
| -rw-r--r-- | src/bin/ngit/sub_commands/list.rs | 68 |
1 files changed, 42 insertions, 26 deletions
diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs index 404b25e..ab4f0f7 100644 --- a/src/bin/ngit/sub_commands/list.rs +++ b/src/bin/ngit/sub_commands/list.rs | |||
| @@ -16,7 +16,7 @@ use ngit::{ | |||
| 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_LABEL, KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, |
| 19 | get_commit_id_from_patch, get_labels, | 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, status_kinds, tag_value, |
| 21 | }, | 21 | }, |
| 22 | repo_ref::{RepoRef, is_grasp_server_in_list}, | 22 | repo_ref::{RepoRef, is_grasp_server_in_list}, |
| @@ -203,7 +203,7 @@ pub async fn launch( | |||
| 203 | // OR filter: proposal must have at least one of the requested labels. | 203 | // OR filter: proposal must have at least one of the requested labels. |
| 204 | let label_filter: HashSet<String> = labels.iter().map(|l| l.trim().to_lowercase()).collect(); | 204 | let label_filter: HashSet<String> = labels.iter().map(|l| l.trim().to_lowercase()).collect(); |
| 205 | 205 | ||
| 206 | let filtered_proposals: Vec<(&nostr::Event, Kind, Vec<String>)> = proposals | 206 | let filtered_proposals: Vec<(&nostr::Event, Kind, Vec<String>, Option<String>)> = proposals |
| 207 | .iter() | 207 | .iter() |
| 208 | .filter_map(|p| { | 208 | .filter_map(|p| { |
| 209 | let status_kind = get_status(p, &repo_ref, &statuses, &proposals); | 209 | let status_kind = get_status(p, &repo_ref, &statuses, &proposals); |
| @@ -217,7 +217,8 @@ pub async fn launch( | |||
| 217 | if !status_filter.contains(status_str) && !status_filter.contains("unknown") { | 217 | if !status_filter.contains(status_str) && !status_filter.contains("unknown") { |
| 218 | return None; | 218 | return None; |
| 219 | } | 219 | } |
| 220 | let proposal_labels = get_labels(p, &repo_ref, &label_events); | 220 | let (proposal_labels, subject_override) = |
| 221 | get_labels_and_subject(p, &repo_ref, &label_events); | ||
| 221 | if !label_filter.is_empty() { | 222 | if !label_filter.is_empty() { |
| 222 | let proposal_labels_lower: HashSet<String> = | 223 | let proposal_labels_lower: HashSet<String> = |
| 223 | proposal_labels.iter().map(|l| l.to_lowercase()).collect(); | 224 | proposal_labels.iter().map(|l| l.to_lowercase()).collect(); |
| @@ -225,7 +226,7 @@ pub async fn launch( | |||
| 225 | return None; | 226 | return None; |
| 226 | } | 227 | } |
| 227 | } | 228 | } |
| 228 | Some((p, status_kind, proposal_labels)) | 229 | Some((p, status_kind, proposal_labels, subject_override)) |
| 229 | }) | 230 | }) |
| 230 | .collect(); | 231 | .collect(); |
| 231 | 232 | ||
| @@ -321,8 +322,21 @@ fn status_kind_to_str(kind: Kind) -> &'static str { | |||
| 321 | } | 322 | } |
| 322 | } | 323 | } |
| 323 | 324 | ||
| 325 | fn proposal_title(proposal: &nostr::Event, subject_override: Option<&str>) -> String { | ||
| 326 | if let Some(s) = subject_override { | ||
| 327 | return s.to_string(); | ||
| 328 | } | ||
| 329 | if let Ok(cl) = event_to_cover_letter(proposal) { | ||
| 330 | cl.title | ||
| 331 | } else if let Ok(msg) = tag_value(proposal, "description") { | ||
| 332 | msg.split('\n').collect::<Vec<&str>>()[0].to_string() | ||
| 333 | } else { | ||
| 334 | proposal.id.to_string() | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 324 | fn output_table( | 338 | fn output_table( |
| 325 | proposals: &[(&nostr::Event, Kind, Vec<String>)], | 339 | proposals: &[(&nostr::Event, Kind, Vec<String>, Option<String>)], |
| 326 | status_filter: &str, | 340 | status_filter: &str, |
| 327 | label_filter: &HashSet<String>, | 341 | label_filter: &HashSet<String>, |
| 328 | ) { | 342 | ) { |
| @@ -332,16 +346,10 @@ fn output_table( | |||
| 332 | } | 346 | } |
| 333 | 347 | ||
| 334 | println!("{:<66} {:<8} TITLE LABELS", "ID", "STATUS"); | 348 | println!("{:<66} {:<8} TITLE LABELS", "ID", "STATUS"); |
| 335 | for (proposal, status_kind, proposal_labels) in proposals { | 349 | for (proposal, status_kind, proposal_labels, subject_override) in proposals { |
| 336 | let id = proposal.id.to_string(); | 350 | let id = proposal.id.to_string(); |
| 337 | let status = status_kind_to_str(*status_kind); | 351 | let status = status_kind_to_str(*status_kind); |
| 338 | let title = if let Ok(cl) = event_to_cover_letter(proposal) { | 352 | let title = proposal_title(proposal, subject_override.as_deref()); |
| 339 | cl.title | ||
| 340 | } else if let Ok(msg) = tag_value(proposal, "description") { | ||
| 341 | msg.split('\n').collect::<Vec<&str>>()[0].to_string() | ||
| 342 | } else { | ||
| 343 | proposal.id.to_string() | ||
| 344 | }; | ||
| 345 | let labels_str: String = proposal_labels | 353 | let labels_str: String = proposal_labels |
| 346 | .iter() | 354 | .iter() |
| 347 | .map(|l| format!("#{l}")) | 355 | .map(|l| format!("#{l}")) |
| @@ -376,24 +384,26 @@ fn output_table( | |||
| 376 | ); | 384 | ); |
| 377 | } | 385 | } |
| 378 | 386 | ||
| 379 | fn output_json(proposals: &[(&nostr::Event, Kind, Vec<String>)]) -> Result<()> { | 387 | fn output_json(proposals: &[(&nostr::Event, Kind, Vec<String>, Option<String>)]) -> Result<()> { |
| 380 | let json_output: Vec<serde_json::Value> = proposals | 388 | let json_output: Vec<serde_json::Value> = proposals |
| 381 | .iter() | 389 | .iter() |
| 382 | .map(|(proposal, status_kind, proposal_labels)| { | 390 | .map(|(proposal, status_kind, proposal_labels, subject_override)| { |
| 383 | let id = proposal.id.to_string(); | 391 | let id = proposal.id.to_string(); |
| 384 | let status = status_kind_to_str(*status_kind).to_string(); | 392 | let status = status_kind_to_str(*status_kind).to_string(); |
| 385 | let (title, author, branch) = if let Ok(cl) = event_to_cover_letter(proposal) { | 393 | let (title, author, branch) = if let Ok(cl) = event_to_cover_letter(proposal) { |
| 386 | ( | 394 | ( |
| 387 | cl.title.clone(), | 395 | subject_override.clone().unwrap_or(cl.title.clone()), |
| 388 | proposal.pubkey.to_bech32().unwrap_or_default(), | 396 | proposal.pubkey.to_bech32().unwrap_or_default(), |
| 389 | cl.get_branch_name_with_pr_prefix_and_shorthand_id() | 397 | cl.get_branch_name_with_pr_prefix_and_shorthand_id() |
| 390 | .unwrap_or_default(), | 398 | .unwrap_or_default(), |
| 391 | ) | 399 | ) |
| 392 | } else { | 400 | } else { |
| 393 | let title = tag_value(proposal, "description").map_or_else( | 401 | let title = subject_override.clone().unwrap_or_else(|| { |
| 394 | |_| proposal.id.to_string(), | 402 | tag_value(proposal, "description").map_or_else( |
| 395 | |d| d.split('\n').collect::<Vec<&str>>()[0].to_string(), | 403 | |_| proposal.id.to_string(), |
| 396 | ); | 404 | |d| d.split('\n').collect::<Vec<&str>>()[0].to_string(), |
| 405 | ) | ||
| 406 | }); | ||
| 397 | ( | 407 | ( |
| 398 | title, | 408 | title, |
| 399 | proposal.pubkey.to_bech32().unwrap_or_default(), | 409 | proposal.pubkey.to_bech32().unwrap_or_default(), |
| @@ -444,7 +454,7 @@ fn comment_reply_to(comment: &nostr::Event) -> Option<nostr::EventId> { | |||
| 444 | 454 | ||
| 445 | #[allow(clippy::too_many_lines)] | 455 | #[allow(clippy::too_many_lines)] |
| 446 | fn show_proposal_details( | 456 | fn show_proposal_details( |
| 447 | proposals: &[(&nostr::Event, Kind, Vec<String>)], | 457 | proposals: &[(&nostr::Event, Kind, Vec<String>, Option<String>)], |
| 448 | event_id_or_nevent: &str, | 458 | event_id_or_nevent: &str, |
| 449 | json: bool, | 459 | json: bool, |
| 450 | show_comments: bool, | 460 | show_comments: bool, |
| @@ -455,14 +465,20 @@ fn show_proposal_details( | |||
| 455 | 465 | ||
| 456 | let target_id = resolve_event_id(event_id_or_nevent)?; | 466 | let target_id = resolve_event_id(event_id_or_nevent)?; |
| 457 | 467 | ||
| 458 | let (proposal, status_kind, proposal_labels) = proposals | 468 | let (proposal, status_kind, proposal_labels, subject_override) = proposals |
| 459 | .iter() | 469 | .iter() |
| 460 | .find(|(p, _, _)| p.id == target_id) | 470 | .find(|(p, _, _, _)| p.id == target_id) |
| 461 | .context("proposal not found")?; | 471 | .context("proposal not found")?; |
| 462 | 472 | ||
| 463 | let cover_letter = event_to_cover_letter(proposal) | 473 | let cover_letter = event_to_cover_letter(proposal) |
| 464 | .context("failed to extract proposal details from proposal root event")?; | 474 | .context("failed to extract proposal details from proposal root event")?; |
| 465 | 475 | ||
| 476 | // Use subject override if present, otherwise fall back to the original title. | ||
| 477 | let display_title = subject_override | ||
| 478 | .as_deref() | ||
| 479 | .unwrap_or(&cover_letter.title) | ||
| 480 | .to_string(); | ||
| 481 | |||
| 466 | if json { | 482 | if json { |
| 467 | let json_output = if show_comments { | 483 | let json_output = if show_comments { |
| 468 | let comments_json: Vec<serde_json::Value> = comments | 484 | let comments_json: Vec<serde_json::Value> = comments |
| @@ -481,7 +497,7 @@ fn show_proposal_details( | |||
| 481 | serde_json::json!({ | 497 | serde_json::json!({ |
| 482 | "id": proposal.id.to_string(), | 498 | "id": proposal.id.to_string(), |
| 483 | "status": status_kind_to_str(*status_kind), | 499 | "status": status_kind_to_str(*status_kind), |
| 484 | "title": cover_letter.title, | 500 | "title": display_title, |
| 485 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), | 501 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), |
| 486 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 502 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 487 | "labels": proposal_labels, | 503 | "labels": proposal_labels, |
| @@ -493,7 +509,7 @@ fn show_proposal_details( | |||
| 493 | serde_json::json!({ | 509 | serde_json::json!({ |
| 494 | "id": proposal.id.to_string(), | 510 | "id": proposal.id.to_string(), |
| 495 | "status": status_kind_to_str(*status_kind), | 511 | "status": status_kind_to_str(*status_kind), |
| 496 | "title": cover_letter.title, | 512 | "title": display_title, |
| 497 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), | 513 | "author": proposal.pubkey.to_bech32().unwrap_or_default(), |
| 498 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, | 514 | "branch": cover_letter.get_branch_name_with_pr_prefix_and_shorthand_id()?, |
| 499 | "labels": proposal_labels, | 515 | "labels": proposal_labels, |
| @@ -505,7 +521,7 @@ fn show_proposal_details( | |||
| 505 | return Ok(()); | 521 | return Ok(()); |
| 506 | } | 522 | } |
| 507 | 523 | ||
| 508 | println!("Title: {}", cover_letter.title); | 524 | println!("Title: {display_title}"); |
| 509 | println!( | 525 | println!( |
| 510 | "Author: {}", | 526 | "Author: {}", |
| 511 | proposal.pubkey.to_bech32().unwrap_or_default() | 527 | proposal.pubkey.to_bech32().unwrap_or_default() |