The inline cleanup paths (receive handler, PR-event policy, purgatory
expiry) only fire while the process is running. They do not handle
directories left zero-ref by a previous run:
* A crash between writing a ref and the end-of-push cleanup.
* A crash between `delete_ref` and `remove_dir_all` in one of the
off-push cleanup paths.
* A clean shutdown with a scoped placeholder still in memory whose
matching event then never arrives in the next run, after the
purgatory state has been dropped or aged out.
Without recovery the bare directory and any dangling refs persist
indefinitely — the standalone `cleanup-empty-repos` CLI tool
explicitly skips `/prs/` because its event-driven model does not
apply, so there is no operational lifeline either.
Add `src/grasp06/cleanup.rs::scan_on_startup` that walks
`<git_data_path>/prs/<hex>/<id>.git` once and removes any bare repo
with zero refs. Empty submitter directories left behind are removed
too via `remove_dir` (which fails non-empty, so no explicit check is
needed). Wired into `run_relay` in `src/main.rs` immediately after the
shared `repo_init_locks` is constructed and before
`nostr::builder::create_relay` is called — at that point no request
handler has run, no `repo_init_locks` entries exist, and nothing else
is touching `/prs/`, so the scan needs no locking.
Gated on `config.grasp06_enable` so an operator who has turned the
feature off does not have their existing `/prs/` data scanned and
potentially trimmed on the next restart.
Entries whose first-level name is not valid 64-char hex, or whose
second-level name does not end in `.git`, are left alone — these
shouldn't exist under `/prs/`, but the scan should never delete
something it doesn't recognise.
Docs:
* docs/explanation/grasp-06-contributor-pr-submission.md — add a
fourth bullet to the Zero-ref `/prs/` cleanup section covering
startup recovery.
* docs/explanation/architecture.md — add the new `cleanup.rs` to
the module layout.
* docs/how-to/enable-grasp-06.md — mention the startup scan in the
storage-cost section's bullet list.
* CHANGELOG.md — extend the GRASP-06 feature entry to call out the
startup scan alongside the three runtime cleanup sites.
4.7 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Added
- GRASP-06 contributor PR submission endpoint (
NGIT_GRASP06_ENABLE, default off). When enabled, the relay accepts unauthenticatedgit pushofrefs/nostr/<event-id>to/prs/<npub>/<identifier>.gitfrom any contributor, even for repositories this relay has no accepted announcement for. The corresponding PR (kind 1618) or PR Update (kind 1619) event is accepted into purgatory when itsclonetag names this relay's/prs/<signer>/<d>.gitendpoint and itsatag's d-tag matches the URL identifier. When the event and the push match (signer, d-tag, c-tag commit) the event is released from purgatory and the ref is mirrored into any accepted-announcement repos on this relay. Empty/prs/repos (probe pushes, mismatched events) are garbage-collected inline at the three runtime sites that can leave them empty (receive handler at end of push, PR-event policy when discarding a mismatched scoped placeholder, purgatory sweep when a scoped placeholder expires without a matching event) plus a one-shot startup scan that removes any zero-ref/prs/bare repos left behind by a previous run (crash, mid-cleanup failure, or shutdown with unresolved scoped placeholders). GRASP-06 is advertised in NIP-11supported_graspswhen enabled. See how-to/enable-grasp-06.md and explanation/grasp-06-contributor-pr-submission.md.
1.0.2 - 2026-04-10
Fixed
-
Replacement announcements (kind 30617) for a purgatory entry were being saved to the database immediately, bypassing the purgatory gate. When a second copy of the same announcement arrived (e.g. via sync from another relay) while the original was still in purgatory awaiting git data, the policy returned
Acceptinstead ofAcceptPurgatory, causing the event to be stored without the corresponding git data or state events ever arriving. The fix returnsAcceptPurgatoryfor replacements of purgatory entries so the updated event is held in purgatory until git data arrives. -
Repository identifiers containing characters that require percent-encoding in URLs (e.g. spaces, emoji) are now accepted and served correctly. NIP-01 places no restriction on
dtag values and NIP-34 only recommends kebab-case without mandating it, so rejecting non-kebab identifiers was overly strict. Identifiers are stored verbatim on disk and percent-encoded when used in URLs, per thenostr://clone URL spec formalised in NIP-34 PR #2312 and the GRASP-01 HTTP path spec. The landing page clone URL now also correctly percent-encodes the identifier. -
--git-diris now passed as a global git option (before the subcommand) incheck_repo_empty, fixing compatibility with git versions that require global options to precede the subcommand.
Changed
-
Remove arbitrary default max connections limit; when
NGIT_MAX_CONNECTIONSis unset the relay imposes no connection cap, deferring to OS fd limits and infrastructure controls -
Added
cleanup-empty-repossubcommand to remove stale events for empty git repositories
1.0.1 - 2026-02-27
Fixed
- 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 causedcan_satisfy_stateto 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
1.0.0 - 2026-02-26
Initial release of ngit-grasp, a GRASP relay implementation in Rust.