diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 09:19:06 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 09:19:06 +0000 |
| commit | 4da51a8adb94f2979c0a911157f26596c1ee2cb5 (patch) | |
| tree | 72c7e2d7347ae39fb6e7db06771a01beeddc37d3 /src/nostr/events.rs | |
| parent | e41126732fb75ec66a979c09544076ba92373680 (diff) | |
sync HEAD on state event and git data push
Diffstat (limited to 'src/nostr/events.rs')
| -rw-r--r-- | src/nostr/events.rs | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/src/nostr/events.rs b/src/nostr/events.rs index 643269a..97688b1 100644 --- a/src/nostr/events.rs +++ b/src/nostr/events.rs | |||
| @@ -167,6 +167,8 @@ pub struct RepositoryState { | |||
| 167 | pub identifier: String, | 167 | pub identifier: String, |
| 168 | pub branches: Vec<BranchState>, | 168 | pub branches: Vec<BranchState>, |
| 169 | pub tags: Vec<TagState>, | 169 | pub tags: Vec<TagState>, |
| 170 | /// HEAD reference (e.g., "refs/heads/main") if specified | ||
| 171 | pub head: Option<String>, | ||
| 170 | } | 172 | } |
| 171 | 173 | ||
| 172 | /// Branch state (ref with commit hash) | 174 | /// Branch state (ref with commit hash) |
| @@ -255,11 +257,39 @@ impl RepositoryState { | |||
| 255 | }) | 257 | }) |
| 256 | .collect(); | 258 | .collect(); |
| 257 | 259 | ||
| 260 | // Extract HEAD reference per NIP-34 | ||
| 261 | // Tag format: ["HEAD", "ref: refs/heads/main"] or ["HEAD", "refs/heads/main"] | ||
| 262 | let head = event | ||
| 263 | .tags | ||
| 264 | .iter() | ||
| 265 | .find(|t| { | ||
| 266 | if let TagKind::Custom(s) = t.kind() { | ||
| 267 | s.as_ref() == "HEAD" | ||
| 268 | } else { | ||
| 269 | false | ||
| 270 | } | ||
| 271 | }) | ||
| 272 | .and_then(|t| { | ||
| 273 | let parts = t.clone().to_vec(); | ||
| 274 | if parts.len() >= 2 { | ||
| 275 | let head_value = &parts[1]; | ||
| 276 | // Handle both "ref: refs/heads/main" and "refs/heads/main" formats | ||
| 277 | if let Some(stripped) = head_value.strip_prefix("ref: ") { | ||
| 278 | Some(stripped.to_string()) | ||
| 279 | } else { | ||
| 280 | Some(head_value.clone()) | ||
| 281 | } | ||
| 282 | } else { | ||
| 283 | None | ||
| 284 | } | ||
| 285 | }); | ||
| 286 | |||
| 258 | Ok(RepositoryState { | 287 | Ok(RepositoryState { |
| 259 | event, | 288 | event, |
| 260 | identifier, | 289 | identifier, |
| 261 | branches, | 290 | branches, |
| 262 | tags, | 291 | tags, |
| 292 | head, | ||
| 263 | }) | 293 | }) |
| 264 | } | 294 | } |
| 265 | 295 | ||
| @@ -283,6 +313,23 @@ impl RepositoryState { | |||
| 283 | pub fn owner_npub(&self) -> String { | 313 | pub fn owner_npub(&self) -> String { |
| 284 | self.event.pubkey.to_bech32().unwrap_or_default() | 314 | self.event.pubkey.to_bech32().unwrap_or_default() |
| 285 | } | 315 | } |
| 316 | |||
| 317 | /// Get the HEAD branch name (without refs/heads/ prefix) | ||
| 318 | pub fn get_head_branch(&self) -> Option<&str> { | ||
| 319 | self.head.as_ref().and_then(|h| { | ||
| 320 | h.strip_prefix("refs/heads/") | ||
| 321 | }) | ||
| 322 | } | ||
| 323 | |||
| 324 | /// Check if the HEAD commit is available in the git repository | ||
| 325 | /// Returns true if we have the git data for the HEAD branch | ||
| 326 | pub fn head_commit_available(&self) -> bool { | ||
| 327 | if let Some(head_branch) = self.get_head_branch() { | ||
| 328 | self.get_branch_commit(head_branch).is_some() | ||
| 329 | } else { | ||
| 330 | false | ||
| 331 | } | ||
| 332 | } | ||
| 286 | } | 333 | } |
| 287 | 334 | ||
| 288 | /// Validate a repository announcement according to GRASP-01 | 335 | /// Validate a repository announcement according to GRASP-01 |
| @@ -601,4 +648,131 @@ mod tests { | |||
| 601 | assert_eq!(state.get_branch_commit("main"), Some("a1b2c3d4")); | 648 | assert_eq!(state.get_branch_commit("main"), Some("a1b2c3d4")); |
| 602 | assert_eq!(state.get_tag_commit("v1.0.0"), Some("e5f6g7h8")); | 649 | assert_eq!(state.get_tag_commit("v1.0.0"), Some("e5f6g7h8")); |
| 603 | } | 650 | } |
| 651 | |||
| 652 | #[test] | ||
| 653 | fn test_state_with_head_ref_prefix() { | ||
| 654 | use nostr_sdk::Tag; | ||
| 655 | |||
| 656 | let keys = create_test_keys(); | ||
| 657 | let mut tags = vec![Tag::custom( | ||
| 658 | nostr_sdk::TagKind::d(), | ||
| 659 | vec!["test-repo".to_string()], | ||
| 660 | )]; | ||
| 661 | |||
| 662 | // Add branch | ||
| 663 | tags.push(Tag::custom( | ||
| 664 | nostr_sdk::TagKind::Custom("refs/heads/main".into()), | ||
| 665 | vec!["a1b2c3d4e5f6g7h8".to_string()], | ||
| 666 | )); | ||
| 667 | |||
| 668 | // Add HEAD with "ref: " prefix (common NIP-34 format) | ||
| 669 | tags.push(Tag::custom( | ||
| 670 | nostr_sdk::TagKind::Custom("HEAD".into()), | ||
| 671 | vec!["ref: refs/heads/main".to_string()], | ||
| 672 | )); | ||
| 673 | |||
| 674 | let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_STATE), "") | ||
| 675 | .tags(tags) | ||
| 676 | .sign_with_keys(&keys) | ||
| 677 | .unwrap(); | ||
| 678 | |||
| 679 | let state = RepositoryState::from_event(event).unwrap(); | ||
| 680 | assert_eq!(state.head, Some("refs/heads/main".to_string())); | ||
| 681 | assert_eq!(state.get_head_branch(), Some("main")); | ||
| 682 | assert!(state.head_commit_available()); | ||
| 683 | } | ||
| 684 | |||
| 685 | #[test] | ||
| 686 | fn test_state_with_head_no_prefix() { | ||
| 687 | use nostr_sdk::Tag; | ||
| 688 | |||
| 689 | let keys = create_test_keys(); | ||
| 690 | let mut tags = vec![Tag::custom( | ||
| 691 | nostr_sdk::TagKind::d(), | ||
| 692 | vec!["test-repo".to_string()], | ||
| 693 | )]; | ||
| 694 | |||
| 695 | // Add branch | ||
| 696 | tags.push(Tag::custom( | ||
| 697 | nostr_sdk::TagKind::Custom("refs/heads/develop".into()), | ||
| 698 | vec!["deadbeefcafe".to_string()], | ||
| 699 | )); | ||
| 700 | |||
| 701 | // Add HEAD without "ref: " prefix (also valid) | ||
| 702 | tags.push(Tag::custom( | ||
| 703 | nostr_sdk::TagKind::Custom("HEAD".into()), | ||
| 704 | vec!["refs/heads/develop".to_string()], | ||
| 705 | )); | ||
| 706 | |||
| 707 | let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_STATE), "") | ||
| 708 | .tags(tags) | ||
| 709 | .sign_with_keys(&keys) | ||
| 710 | .unwrap(); | ||
| 711 | |||
| 712 | let state = RepositoryState::from_event(event).unwrap(); | ||
| 713 | assert_eq!(state.head, Some("refs/heads/develop".to_string())); | ||
| 714 | assert_eq!(state.get_head_branch(), Some("develop")); | ||
| 715 | assert!(state.head_commit_available()); | ||
| 716 | } | ||
| 717 | |||
| 718 | #[test] | ||
| 719 | fn test_state_without_head() { | ||
| 720 | use nostr_sdk::Tag; | ||
| 721 | |||
| 722 | let keys = create_test_keys(); | ||
| 723 | let tags = vec![ | ||
| 724 | Tag::custom( | ||
| 725 | nostr_sdk::TagKind::d(), | ||
| 726 | vec!["test-repo".to_string()], | ||
| 727 | ), | ||
| 728 | Tag::custom( | ||
| 729 | nostr_sdk::TagKind::Custom("refs/heads/main".into()), | ||
| 730 | vec!["a1b2c3d4".to_string()], | ||
| 731 | ), | ||
| 732 | ]; | ||
| 733 | |||
| 734 | let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_STATE), "") | ||
| 735 | .tags(tags) | ||
| 736 | .sign_with_keys(&keys) | ||
| 737 | .unwrap(); | ||
| 738 | |||
| 739 | let state = RepositoryState::from_event(event).unwrap(); | ||
| 740 | assert_eq!(state.head, None); | ||
| 741 | assert_eq!(state.get_head_branch(), None); | ||
| 742 | assert!(!state.head_commit_available()); | ||
| 743 | } | ||
| 744 | |||
| 745 | #[test] | ||
| 746 | fn test_state_head_commit_not_available() { | ||
| 747 | use nostr_sdk::Tag; | ||
| 748 | |||
| 749 | let keys = create_test_keys(); | ||
| 750 | let mut tags = vec![Tag::custom( | ||
| 751 | nostr_sdk::TagKind::d(), | ||
| 752 | vec!["test-repo".to_string()], | ||
| 753 | )]; | ||
| 754 | |||
| 755 | // Add branch for "main" | ||
| 756 | tags.push(Tag::custom( | ||
| 757 | nostr_sdk::TagKind::Custom("refs/heads/main".into()), | ||
| 758 | vec!["a1b2c3d4".to_string()], | ||
| 759 | )); | ||
| 760 | |||
| 761 | // HEAD points to "develop" which doesn't exist in branches | ||
| 762 | tags.push(Tag::custom( | ||
| 763 | nostr_sdk::TagKind::Custom("HEAD".into()), | ||
| 764 | vec!["refs/heads/develop".to_string()], | ||
| 765 | )); | ||
| 766 | |||
| 767 | let event = EventBuilder::new(Kind::from(KIND_REPOSITORY_STATE), "") | ||
| 768 | .tags(tags) | ||
| 769 | .sign_with_keys(&keys) | ||
| 770 | .unwrap(); | ||
| 771 | |||
| 772 | let state = RepositoryState::from_event(event).unwrap(); | ||
| 773 | assert_eq!(state.head, Some("refs/heads/develop".to_string())); | ||
| 774 | assert_eq!(state.get_head_branch(), Some("develop")); | ||
| 775 | // HEAD points to develop but only main branch exists in state | ||
| 776 | assert!(!state.head_commit_available()); | ||
| 777 | } | ||
| 604 | } | 778 | } |