3f27d9bc
Accept PatchCreate/head_commit as aliases for backward-compatible event deserialization
a73x 2026-03-21 15:50
Worktree agents created events using a different format (PatchCreate tag + head_commit field) than main expects (patch.create tag + branch field). Add serde aliases so both formats deserialize correctly, and make resolve_head try OID parsing before branch lookup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
diff --git a/src/event.rs b/src/event.rs index 515f884..d3e5a49 100644 --- a/src/event.rs +++ b/src/event.rs @@ -54,11 +54,12 @@ pub enum Action { }, #[serde(rename = "issue.reopen")] IssueReopen, #[serde(rename = "patch.create")] #[serde(rename = "patch.create", alias = "PatchCreate")] PatchCreate { title: String, body: String, base_ref: String, #[serde(alias = "head_commit")] branch: String, #[serde(default, skip_serializing_if = "Option::is_none")] fixes: Option<String>, diff --git a/src/state.rs b/src/state.rs index 8028f0b..05ae1a8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -182,6 +182,13 @@ impl IssueState { impl PatchState { /// Resolve the current head commit OID for this patch by looking up `refs/heads/{branch}`. pub fn resolve_head(&self, repo: &Repository) -> Result<Oid, crate::error::Error> { // Try parsing as a hex OID first (for patches created with head_commit) if let Ok(oid) = Oid::from_str(&self.branch) { if repo.find_commit(oid).is_ok() { return Ok(oid); } } // Fall back to branch name lookup let ref_name = format!("refs/heads/{}", self.branch); repo.refname_to_id(&ref_name).map_err(|e| { crate::error::Error::Cmd(format!( diff --git a/tests/collab_test.rs b/tests/collab_test.rs index 1a7461f..3b54981 100644 --- a/tests/collab_test.rs +++ b/tests/collab_test.rs @@ -831,6 +831,87 @@ fn test_patch_show_outdated_staleness() { } // --------------------------------------------------------------------------- // Backward compat: head_commit alias for branch field // --------------------------------------------------------------------------- #[test] fn test_patch_create_with_head_commit_field_deserializes() { // Events created with "head_commit" instead of "branch" should still work let json = r#"{ "timestamp": "2026-03-21T00:00:00+00:00", "author": {"name": "agent", "email": "agent@test"}, "action": { "type": "patch.create", "title": "Agent patch", "body": "from worktree", "base_ref": "main", "head_commit": "abc123def456" } }"#; let event: Event = serde_json::from_str(json).unwrap(); match event.action { Action::PatchCreate { branch, .. } => { assert_eq!(branch, "abc123def456"); } _ => panic!("expected PatchCreate"), } } #[test] fn test_patch_create_with_branch_field_still_works() { let json = r#"{ "timestamp": "2026-03-21T00:00:00+00:00", "author": {"name": "dev", "email": "dev@test"}, "action": { "type": "patch.create", "title": "Normal patch", "body": "", "base_ref": "main", "branch": "feature-branch" } }"#; let event: Event = serde_json::from_str(json).unwrap(); match event.action { Action::PatchCreate { branch, .. } => { assert_eq!(branch, "feature-branch"); } _ => panic!("expected PatchCreate"), } } #[test] fn test_resolve_head_with_oid_string() { // If branch field contains a hex OID, resolve_head should try to parse it as an OID let tmp = TempDir::new().unwrap(); let repo = init_repo(tmp.path(), &alice()); make_initial_commit(&repo, "main"); let tip = add_commit_on_branch(&repo, "main", "f.rs", b"content"); // Create a patch where "branch" is actually a commit OID string let sk = test_signing_key(); let event = Event { timestamp: now(), author: alice(), action: Action::PatchCreate { title: "OID-based patch".to_string(), body: "".to_string(), base_ref: "main".to_string(), branch: tip.to_string(), fixes: None, }, clock: 0, }; let oid = dag::create_root_event(&repo, &event, &sk).unwrap(); let id = oid.to_string(); let ref_name = format!("refs/collab/patches/{}", id); repo.reference(&ref_name, oid, false, "test").unwrap(); let state = PatchState::from_ref(&repo, &ref_name, &id).unwrap(); let resolved = state.resolve_head(&repo).unwrap(); assert_eq!(resolved, tip); } // --------------------------------------------------------------------------- // Phase 5: US3 — Merge (T024-T025) // ---------------------------------------------------------------------------