a73x

4d4efe03

Add Action::IssueCommitLink event variant

alex emery   2026-04-12 06:25

Introduces the new event kind that will be emitted during sync when a
commit's message contains an Issue: trailer. Variant only; no emission
sites or renderers yet.

diff --git a/src/dag.rs b/src/dag.rs
index e1ac63e..6a58345 100644
--- a/src/dag.rs
+++ b/src/dag.rs
@@ -331,6 +331,7 @@ fn commit_message(action: &Action) -> String {
        Action::IssueComment { .. } => "issue: comment".to_string(),
        Action::IssueClose { .. } => "issue: close".to_string(),
        Action::IssueReopen => "issue: reopen".to_string(),
        Action::IssueCommitLink { commit } => format!("issue: commit link {}", &commit[..commit.len().min(7)]),
        Action::PatchCreate { title, .. } => format!("patch: create \"{}\"", title),
        Action::PatchRevision { .. } => "patch: revision".to_string(),
        Action::PatchReview { verdict, .. } => format!("patch: review ({})", verdict),
diff --git a/src/event.rs b/src/event.rs
index a897247..6e2edfb 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -56,6 +56,10 @@ pub enum Action {
    },
    #[serde(rename = "issue.reopen")]
    IssueReopen,
    #[serde(rename = "issue.commit_link")]
    IssueCommitLink {
        commit: String,
    },
    #[serde(rename = "patch.create", alias = "PatchCreate")]
    PatchCreate {
        title: String,
diff --git a/src/log.rs b/src/log.rs
index d92f0cf..d1c6a0b 100644
--- a/src/log.rs
+++ b/src/log.rs
@@ -115,6 +115,7 @@ fn action_type_name(action: &Action) -> String {
        Action::IssueAssign { .. } => "IssueAssign".to_string(),
        Action::IssueUnassign { .. } => "IssueUnassign".to_string(),
        Action::IssueReopen => "IssueReopen".to_string(),
        Action::IssueCommitLink { .. } => "IssueCommitLink".to_string(),
        Action::PatchCreate { .. } => "PatchCreate".to_string(),
        Action::PatchRevision { .. } => "PatchRevision".to_string(),
        Action::PatchReview { .. } => "PatchReview".to_string(),
@@ -149,6 +150,9 @@ fn action_summary(action: &Action) -> String {
        Action::IssueAssign { assignee } => format!("assign \"{}\"", assignee),
        Action::IssueUnassign { assignee } => format!("unassign \"{}\"", assignee),
        Action::IssueReopen => "reopen".to_string(),
        Action::IssueCommitLink { commit } => {
            format!("commit link {}", &commit[..commit.len().min(7)])
        }
        Action::PatchCreate { title, .. } => format!("create \"{}\"", title),
        Action::PatchRevision { body, .. } => match body {
            Some(b) => format!("revision: {}", truncate(b, 50)),
diff --git a/src/tui/widgets.rs b/src/tui/widgets.rs
index 170ebff..7e8bfbb 100644
--- a/src/tui/widgets.rs
+++ b/src/tui/widgets.rs
@@ -26,6 +26,7 @@ pub(crate) fn action_type_label(action: &Action) -> &str {
        Action::IssueUnlabel { .. } => "Issue Unlabel",
        Action::IssueAssign { .. } => "Issue Assign",
        Action::IssueUnassign { .. } => "Issue Unassign",
        Action::IssueCommitLink { .. } => "Issue Commit Link",
    }
}

@@ -111,6 +112,9 @@ pub(crate) fn format_event_detail(oid: &Oid, event: &crate::event::Event) -> Str
        Action::IssueUnassign { assignee } => {
            detail.push_str(&format!("\nRemoved Assignee: {}\n", assignee));
        }
        Action::IssueCommitLink { commit } => {
            detail.push_str(&format!("\nCommit: {}\n", commit));
        }
        Action::IssueReopen | Action::PatchMerge | Action::Merge => {}
    }

diff --git a/tests/commit_link_test.rs b/tests/commit_link_test.rs
new file mode 100644
index 0000000..6197245
--- /dev/null
+++ b/tests/commit_link_test.rs
@@ -0,0 +1,42 @@
//! Unit tests for src/commit_link.rs and the Action::IssueCommitLink variant.

use git_collab::event::{Action, Author, Event};

fn test_author() -> Author {
    Author {
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    }
}

#[test]
fn issue_commit_link_variant_round_trips() {
    let event = Event {
        timestamp: "2026-04-12T12:00:00Z".to_string(),
        author: test_author(),
        action: Action::IssueCommitLink {
            commit: "4b2e1cd0123456789012345678901234567890ab".to_string(),
        },
        clock: 3,
    };

    let json = serde_json::to_string(&event).expect("serialize");
    assert!(
        json.contains("\"type\":\"issue.commit_link\""),
        "expected serde tag issue.commit_link, got: {}",
        json
    );
    assert!(
        json.contains("\"commit\":\"4b2e1cd0123456789012345678901234567890ab\""),
        "expected commit field, got: {}",
        json
    );

    let parsed: Event = serde_json::from_str(&json).expect("deserialize");
    match parsed.action {
        Action::IssueCommitLink { commit } => {
            assert_eq!(commit, "4b2e1cd0123456789012345678901234567890ab");
        }
        other => panic!("expected IssueCommitLink, got {:?}", other),
    }
}