diff --git a/CHANGELOG.md b/CHANGELOG.md index b613097..a4610da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Push authorization now correctly ignores `refs/tags/^{}` peeled-tag entries in state events (kind 30618). These entries are git's internal notation for the dereferenced commit behind an annotated tag and are never sent as part of a push. Previously, their presence in the state event caused `can_satisfy_state` to reject valid annotated-tag pushes because the would-be ref state after the push did not include the spurious `^{}` entry, making the exact-equality check fail. + ### Changed - Push auth rejections now send the reason to the git client via ERR pkt-line (e.g. "authorisation failed: No state events in purgatory") instead of a generic HTTP 403, so users see actionable error messages directly in their terminal diff --git a/src/nostr/events.rs b/src/nostr/events.rs index a441742..00e4486 100644 --- a/src/nostr/events.rs +++ b/src/nostr/events.rs @@ -260,12 +260,14 @@ impl RepositoryState { // Extract tags (refs/tags/*) // Tag format: ["refs/tags/v1.0", "commit_hash"] + // Exclude peeled tag notation ("refs/tags/v1.0^{}") — these are git's internal + // dereference markers pointing to the underlying commit, not real refs. let tags = event .tags .iter() .filter_map(|t| { if let TagKind::Custom(s) = t.kind() { - if s.as_ref().starts_with("refs/tags/") { + if s.as_ref().starts_with("refs/tags/") && !s.as_ref().ends_with("^{}") { let parts = t.clone().to_vec(); if parts.len() >= 2 { Some(TagState { diff --git a/src/purgatory/helpers.rs b/src/purgatory/helpers.rs index a9f6e66..319711b 100644 --- a/src/purgatory/helpers.rs +++ b/src/purgatory/helpers.rs @@ -58,7 +58,11 @@ pub fn extract_refs_from_state(event: &Event) -> Vec { let ref_str = ref_name.as_ref(); // Only process refs/heads/* and refs/tags/* - if ref_str.starts_with("refs/heads/") || ref_str.starts_with("refs/tags/") { + // Exclude peeled tag notation (e.g. "refs/tags/v1.0.0^{}") — these are + // git's internal dereference markers, not real refs that get pushed. + if (ref_str.starts_with("refs/heads/") || ref_str.starts_with("refs/tags/")) + && !ref_str.ends_with("^{}") + { // Get the object SHA (first value in tag) let parts = tag.clone().to_vec(); if parts.len() >= 2 {