2cc57a53
Extract event-creation boilerplate into dag helpers
a73x 2026-03-22 08:29
Add build_event, append_action, and create_root_action convenience wrappers in dag.rs. Replace 10 boilerplate blocks in issue.rs and 2 in patch.rs that manually loaded signing keys, built Events, and appended. Closes issue cb052012. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
diff --git a/src/dag.rs b/src/dag.rs index e9e42be..179f370 100644 --- a/src/dag.rs +++ b/src/dag.rs @@ -2,8 +2,8 @@ use git2::{ObjectType, Oid, Repository, Sort}; use crate::error::Error; use crate::event::{Action, Event}; use crate::identity::author_signature; use crate::signing::sign_event; use crate::identity::{author_signature, get_author}; use crate::signing::{self, sign_event}; /// The manifest blob content included in every event commit tree. const MANIFEST_JSON: &[u8] = br#"{"version":1,"format":"git-collab"}"#; @@ -132,6 +132,42 @@ pub fn append_event( Ok(oid) } /// Build an Event from the given action, filling in timestamp and author /// automatically. The clock field is set to 0 (callers like `create_root_event` /// and `append_event` overwrite it). pub fn build_event(repo: &Repository, action: Action) -> Result<Event, Error> { let author = get_author(repo)?; Ok(Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action, clock: 0, }) } /// Convenience wrapper: load signing key, build event, and append it to an /// existing DAG ref in one call. pub fn append_action( repo: &Repository, ref_name: &str, action: Action, ) -> Result<Oid, Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let event = build_event(repo, action)?; append_event(repo, ref_name, &event, &sk) } /// Convenience wrapper: load signing key, build event, and create a root /// (orphan) DAG commit. Returns the new commit OID (entity ID). pub fn create_root_action( repo: &Repository, action: Action, ) -> Result<Oid, Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let event = build_event(repo, action)?; create_root_event(repo, &event, &sk) } /// Walk the DAG from the given ref in topological order (oldest first). /// Returns (commit_oid, event) pairs. pub fn walk_events(repo: &Repository, ref_name: &str) -> Result<Vec<(Oid, Event)>, Error> { diff --git a/src/issue.rs b/src/issue.rs index fbc2b3b..0385825 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -2,9 +2,7 @@ use git2::Repository; use crate::cli::{self, SortMode}; use crate::dag; use crate::event::{Action, Event}; use crate::identity::get_author; use crate::signing; use crate::event::Action; use crate::state::{self, IssueState}; pub fn open( @@ -13,19 +11,14 @@ pub fn open( body: &str, relates_to: Option<&str>, ) -> Result<String, crate::error::Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::IssueOpen { let oid = dag::create_root_action( repo, Action::IssueOpen { title: title.to_string(), body: body.to_string(), relates_to: relates_to.map(|s| s.to_string()), }, clock: 0, }; let oid = dag::create_root_event(repo, &event, &sk)?; )?; let id = oid.to_string(); let ref_name = format!("refs/collab/issues/{}", id); repo.reference(&ref_name, oid, false, "issue open")?; @@ -144,34 +137,18 @@ pub fn show(repo: &Repository, id_prefix: &str) -> Result<IssueState, crate::err } pub fn label(repo: &Repository, id_prefix: &str, label: &str) -> Result<(), crate::error::Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let (ref_name, _id) = state::resolve_issue_ref(repo, id_prefix)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::IssueLabel { label: label.to_string(), }, clock: 0, }; dag::append_event(repo, &ref_name, &event, &sk)?; dag::append_action(repo, &ref_name, Action::IssueLabel { label: label.to_string(), })?; Ok(()) } pub fn unlabel(repo: &Repository, id_prefix: &str, label: &str) -> Result<(), crate::error::Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let (ref_name, _id) = state::resolve_issue_ref(repo, id_prefix)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::IssueUnlabel { label: label.to_string(), }, clock: 0, }; dag::append_event(repo, &ref_name, &event, &sk)?; dag::append_action(repo, &ref_name, Action::IssueUnlabel { label: label.to_string(), })?; Ok(()) } @@ -180,18 +157,10 @@ pub fn assign( id_prefix: &str, assignee: &str, ) -> Result<(), crate::error::Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let (ref_name, _id) = state::resolve_issue_ref(repo, id_prefix)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::IssueAssign { assignee: assignee.to_string(), }, clock: 0, }; dag::append_event(repo, &ref_name, &event, &sk)?; dag::append_action(repo, &ref_name, Action::IssueAssign { assignee: assignee.to_string(), })?; Ok(()) } @@ -200,18 +169,10 @@ pub fn unassign( id_prefix: &str, assignee: &str, ) -> Result<(), crate::error::Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let (ref_name, _id) = state::resolve_issue_ref(repo, id_prefix)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::IssueUnassign { assignee: assignee.to_string(), }, clock: 0, }; dag::append_event(repo, &ref_name, &event, &sk)?; dag::append_action(repo, &ref_name, Action::IssueUnassign { assignee: assignee.to_string(), })?; Ok(()) } @@ -226,35 +187,19 @@ pub fn edit( git2::Error::from_str("at least one of --title or --body must be provided").into(), ); } let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let (ref_name, _id) = state::resolve_issue_ref(repo, id_prefix)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::IssueEdit { title: title.map(|s| s.to_string()), body: body.map(|s| s.to_string()), }, clock: 0, }; dag::append_event(repo, &ref_name, &event, &sk)?; dag::append_action(repo, &ref_name, Action::IssueEdit { title: title.map(|s| s.to_string()), body: body.map(|s| s.to_string()), })?; Ok(()) } pub fn comment(repo: &Repository, id_prefix: &str, body: &str) -> Result<(), crate::error::Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let (ref_name, _id) = state::resolve_issue_ref(repo, id_prefix)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::IssueComment { body: body.to_string(), }, clock: 0, }; dag::append_event(repo, &ref_name, &event, &sk)?; dag::append_action(repo, &ref_name, Action::IssueComment { body: body.to_string(), })?; Ok(()) } @@ -263,18 +208,10 @@ pub fn close( id_prefix: &str, reason: Option<&str>, ) -> Result<(), crate::error::Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let (ref_name, id) = state::resolve_issue_ref(repo, id_prefix)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::IssueClose { reason: reason.map(|s| s.to_string()), }, clock: 0, }; dag::append_event(repo, &ref_name, &event, &sk)?; dag::append_action(repo, &ref_name, Action::IssueClose { reason: reason.map(|s| s.to_string()), })?; // Archive the ref (move to refs/collab/archive/issues/) if ref_name.starts_with("refs/collab/issues/") { state::archive_issue_ref(repo, &id)?; @@ -289,15 +226,7 @@ pub fn delete(repo: &Repository, id_prefix: &str) -> Result<String, crate::error } pub fn reopen(repo: &Repository, id_prefix: &str) -> Result<(), crate::error::Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let (ref_name, _id) = state::resolve_issue_ref(repo, id_prefix)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::IssueReopen, clock: 0, }; dag::append_event(repo, &ref_name, &event, &sk)?; dag::append_action(repo, &ref_name, Action::IssueReopen)?; Ok(()) } diff --git a/src/patch.rs b/src/patch.rs index d5bbf65..8846408 100644 --- a/src/patch.rs +++ b/src/patch.rs @@ -105,12 +105,9 @@ pub fn create( let commit = repo.find_commit(tip_oid)?; let tree_oid = commit.tree()?.id(); let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::PatchCreate { let oid = dag::create_root_action( repo, Action::PatchCreate { title: title.to_string(), body: body.to_string(), base_ref: base_ref.to_string(), @@ -119,9 +116,7 @@ pub fn create( commit: tip_oid.to_string(), tree: tree_oid.to_string(), }, clock: 0, }; let oid = dag::create_root_event(repo, &event, &sk)?; )?; let id = oid.to_string(); let ref_name = format!("refs/collab/patches/{}", id); repo.reference(&ref_name, oid, false, "patch create")?; @@ -709,18 +704,10 @@ pub fn close( id_prefix: &str, reason: Option<&str>, ) -> Result<(), crate::error::Error> { let sk = signing::load_signing_key(&signing::signing_key_dir()?)?; let (ref_name, id) = state::resolve_patch_ref(repo, id_prefix)?; let author = get_author(repo)?; let event = Event { timestamp: chrono::Utc::now().to_rfc3339(), author, action: Action::PatchClose { reason: reason.map(|s| s.to_string()), }, clock: 0, }; dag::append_event(repo, &ref_name, &event, &sk)?; dag::append_action(repo, &ref_name, Action::PatchClose { reason: reason.map(|s| s.to_string()), })?; // Archive the ref (move to refs/collab/archive/patches/) if ref_name.starts_with("refs/collab/patches/") { state::archive_patch_ref(repo, &id)?;