diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-26 15:38:51 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-26 15:38:51 +0000 |
| commit | a2ecfc5a63311570f0f90c7ee40117e289639cb8 (patch) | |
| tree | 0c5208cb7d606be4b431cda813b09dcc64a50e23 | |
| parent | b13c6d924f7de5ff34405254b8bb21adf33c78c0 (diff) | |
fix: ignore peeled tag entries (^{}) in state event ref parsing
State events (kind 30618) can include refs/tags/<name>^{} entries which
are git's notation for the dereferenced commit behind an annotated tag.
These are not real git refs and are never sent as part of a push.
extract_refs_from_state and RepositoryState::from_event were treating
them as real refs, causing can_satisfy_state to reject valid annotated
tag pushes: the would-be state after the push lacked the spurious ^{}
entry, so the exact-equality check always failed.
| -rw-r--r-- | CHANGELOG.md | 4 | ||||
| -rw-r--r-- | src/nostr/events.rs | 4 | ||||
| -rw-r--r-- | src/purgatory/helpers.rs | 6 |
3 files changed, 12 insertions, 2 deletions
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 | |||
| 7 | 7 | ||
| 8 | ## [Unreleased] | 8 | ## [Unreleased] |
| 9 | 9 | ||
| 10 | ### Fixed | ||
| 11 | |||
| 12 | - Push authorization now correctly ignores `refs/tags/<name>^{}` 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. | ||
| 13 | |||
| 10 | ### Changed | 14 | ### Changed |
| 11 | 15 | ||
| 12 | - 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 | 16 | - 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 { | |||
| 260 | 260 | ||
| 261 | // Extract tags (refs/tags/*) | 261 | // Extract tags (refs/tags/*) |
| 262 | // Tag format: ["refs/tags/v1.0", "commit_hash"] | 262 | // Tag format: ["refs/tags/v1.0", "commit_hash"] |
| 263 | // Exclude peeled tag notation ("refs/tags/v1.0^{}") — these are git's internal | ||
| 264 | // dereference markers pointing to the underlying commit, not real refs. | ||
| 263 | let tags = event | 265 | let tags = event |
| 264 | .tags | 266 | .tags |
| 265 | .iter() | 267 | .iter() |
| 266 | .filter_map(|t| { | 268 | .filter_map(|t| { |
| 267 | if let TagKind::Custom(s) = t.kind() { | 269 | if let TagKind::Custom(s) = t.kind() { |
| 268 | if s.as_ref().starts_with("refs/tags/") { | 270 | if s.as_ref().starts_with("refs/tags/") && !s.as_ref().ends_with("^{}") { |
| 269 | let parts = t.clone().to_vec(); | 271 | let parts = t.clone().to_vec(); |
| 270 | if parts.len() >= 2 { | 272 | if parts.len() >= 2 { |
| 271 | Some(TagState { | 273 | 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<RefPair> { | |||
| 58 | let ref_str = ref_name.as_ref(); | 58 | let ref_str = ref_name.as_ref(); |
| 59 | 59 | ||
| 60 | // Only process refs/heads/* and refs/tags/* | 60 | // Only process refs/heads/* and refs/tags/* |
| 61 | if ref_str.starts_with("refs/heads/") || ref_str.starts_with("refs/tags/") { | 61 | // Exclude peeled tag notation (e.g. "refs/tags/v1.0.0^{}") — these are |
| 62 | // git's internal dereference markers, not real refs that get pushed. | ||
| 63 | if (ref_str.starts_with("refs/heads/") || ref_str.starts_with("refs/tags/")) | ||
| 64 | && !ref_str.ends_with("^{}") | ||
| 65 | { | ||
| 62 | // Get the object SHA (first value in tag) | 66 | // Get the object SHA (first value in tag) |
| 63 | let parts = tag.clone().to_vec(); | 67 | let parts = tag.clone().to_vec(); |
| 64 | if parts.len() >= 2 { | 68 | if parts.len() >= 2 { |