5bccfd16
Render --- Linked Commits --- section in CLI issue show
alex emery 2026-04-12 07:19
Each line shows the short SHA, commit subject (truncated to 60 chars), commit author, and then (linked by <event-author>, <timestamp>). When the commit isn't locally reachable (GC'd, shallow), falls back to a no-subject variant that still shows the linked-by metadata.
diff --git a/src/lib.rs b/src/lib.rs index 399889c..b6be19b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,6 +145,51 @@ pub fn run(cli: cli::Cli, repo: &Repository) -> Result<(), error::Error> { println!("\n{} ({}):\n{}", c.author.name, c.timestamp, c.body); } } if !i.linked_commits.is_empty() { println!("\n--- Linked Commits ---"); for lc in &i.linked_commits { let short_sha = if lc.commit.len() >= 7 { &lc.commit[..7] } else { &lc.commit }; let (subject, commit_author) = match git2::Oid::from_str(&lc.commit) .ok() .and_then(|oid| repo.find_commit(oid).ok()) { Some(commit) => { let subject = commit .summary() .map(|s| truncate_summary(s, 60)) .unwrap_or_default(); let author = commit .author() .name() .unwrap_or("unknown") .to_string(); (Some(subject), Some(author)) } None => (None, None), }; match (subject, commit_author) { (Some(subject), Some(author)) => { println!( "· linked {} \"{}\" by {} (linked by {}, {})", short_sha, subject, author, lc.event_author.name, lc.event_timestamp, ); } _ => { println!( "· linked {} (commit {} not in local repo) (linked by {}, {})", short_sha, short_sha, lc.event_author.name, lc.event_timestamp, ); } } } } Ok(()) } IssueCmd::Label { id, label } => { @@ -663,3 +708,15 @@ fn search(repo: &Repository, query: &str) -> Result<(), error::Error> { Ok(()) } fn truncate_summary(s: &str, max_chars: usize) -> String { let mut out = String::new(); for (count, c) in s.chars().enumerate() { if count + 1 > max_chars { out.push('…'); return out; } out.push(c); } out } diff --git a/tests/sync_test.rs b/tests/sync_test.rs index 571cb1d..b7a7729 100644 --- a/tests/sync_test.rs +++ b/tests/sync_test.rs @@ -1499,3 +1499,49 @@ fn commit_link_scan_dedups_against_remote_originated_events() { assert_eq!(bob_issue.linked_commits.len(), 1); assert_eq!(bob_issue.linked_commits[0].commit, linked_commit.to_string()); } #[test] fn cli_issue_show_renders_linked_commits_section() { use common::TestRepo; let repo = TestRepo::new("Alice", "alice@example.com"); let issue_id = repo.issue_open("fix the thing"); // Resolve the full id via --json so we can format a trailer prefix. let full_id = { let out = repo.run_ok(&["issue", "show", &issue_id, "--json"]); let v: serde_json::Value = serde_json::from_str(&out).unwrap(); v["id"].as_str().unwrap().to_string() }; let msg = format!("Fix a thing\n\nIssue: {}", &full_id[..8]); repo.git(&["commit", "--allow-empty", "-m", &msg]); // Set up a bare remote so sync has something to push to. let bare = TempDir::new().unwrap(); Command::new("git") .args(["init", "--bare"]) .current_dir(bare.path()) .status() .unwrap(); repo.git(&["remote", "add", "origin", bare.path().to_str().unwrap()]); repo.git(&["push", "-u", "origin", "main"]); repo.run_ok(&["init"]); repo.run_ok(&["sync"]); let show = repo.run_ok(&["issue", "show", &issue_id]); assert!( show.contains("--- Linked Commits ---"), "expected linked commits section, got:\n{}", show ); assert!( show.contains("by Alice"), "expected commit author rendered, got:\n{}", show ); assert!( show.contains("(linked by Alice"), "expected event author rendered, got:\n{}", show ); }