upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/nostr/events.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-11-28 09:19:06 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2025-11-28 09:19:06 +0000
commit4da51a8adb94f2979c0a911157f26596c1ee2cb5 (patch)
tree72c7e2d7347ae39fb6e7db06771a01beeddc37d3 /src/nostr/events.rs
parente41126732fb75ec66a979c09544076ba92373680 (diff)
sync HEAD on state event and git data push
Diffstat (limited to 'src/nostr/events.rs')
-rw-r--r--src/nostr/events.rs174
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}