<feed xmlns='http://www.w3.org/2005/Atom'>
<title>npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/src, branch v1.0.2</title>
<subtitle>Unnamed repository; edit this file 'description' to name the repository.
</subtitle>
<id>https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/atom?h=v1.0.2</id>
<link rel='self' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/atom?h=v1.0.2'/>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/'/>
<updated>2026-04-10T20:29:34+00:00</updated>
<entry>
<title>feat: scan filesystem for orphan git repos with no matching 30617 event</title>
<updated>2026-04-10T20:29:34+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-04-10T20:29:34+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=54636ee239e4f8e25142e99807f246f956d2f003'/>
<id>urn:sha1:54636ee239e4f8e25142e99807f246f956d2f003</id>
<content type='text'>
Extends cleanup-empty-repos with a second scan direction (filesystem → DB).
Bare git repos under the git data path that have no corresponding 30617
announcement event are identified as orphans and cleaned up.

Empty orphans are always removed. Non-empty orphans are flagged in the
report but only deleted when --purge-orphans is also passed, preventing
accidental data loss.
</content>
</entry>
<entry>
<title>fix: pass --git-dir as global git option in check_repo_empty</title>
<updated>2026-04-10T19:47:52+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-04-10T19:47:52+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=368f0556267b32c5478b7fb68b8a30d42942ee6f'/>
<id>urn:sha1:368f0556267b32c5478b7fb68b8a30d42942ee6f</id>
<content type='text'>
--git-dir must precede the subcommand; passing it after for-each-ref
caused git to ignore it and check the CWD instead, making every repo
appear empty.
</content>
</entry>
<entry>
<title>feat: add cleanup-empty-repos subcommand to remove stale events for empty git repos</title>
<updated>2026-04-10T19:26:23+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-04-10T19:26:23+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=8aef478c6b1e9e3f6ebbad6d57f59f1a84a261ea'/>
<id>urn:sha1:8aef478c6b1e9e3f6ebbad6d57f59f1a84a261ea</id>
<content type='text'>
Adds a maintenance subcommand that scans the LMDB database for kind 30617
(repository announcement) events whose bare git repo on disk is empty or
missing, then removes both the 30617 and any matching 30618 (state) events.

A relay should not serve announcement or state events for a repository with
no git data. This was needed to clean up repos leaked by the bug fixed in
2161e3c, and is useful as an ongoing maintenance tool.

Usage (dry-run by default, stop relay before --execute):
  ngit-grasp cleanup-empty-repos [--relay-data-path &lt;path&gt;] [--git-data-path &lt;path&gt;] [--execute]

The relay itself is now invoked as an implicit 'serve' subcommand, preserving
full backward compatibility with existing deployments and env-var configuration.
</content>
</entry>
<entry>
<title>fix: purgatory replacement announcements leaked into DB without git data</title>
<updated>2026-04-10T18:20:23+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-04-10T18:20:23+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=2161e3c0a8169a85111cd6dc01ffe2b0fed1493f'/>
<id>urn:sha1:2161e3c0a8169a85111cd6dc01ffe2b0fed1493f</id>
<content type='text'>
When a replacement 30617 announcement arrived for an entry already in
purgatory (e.g. the same event fetched from a second relay during sync,
or a user re-submitting a slightly updated announcement), the policy
returned Accept instead of AcceptPurgatory. This caused the event to be
saved to the database immediately, bypassing the purgatory gate, without
the corresponding git data or state events ever arriving.

Fix: return AcceptPurgatory when replacing a purgatory entry so the
updated event stays in purgatory until git data arrives. The purgatory
entry is still updated with the newer event via replace_purgatory_announcement
before the return.
</content>
</entry>
<entry>
<title>fix: accept any d-tag identifier; percent-encode in URLs</title>
<updated>2026-04-10T16:42:35+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-04-10T16:42:35+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=dfd20a39a7ddaea07103cac45d4d79bc7e6ce0d7'/>
<id>urn:sha1:dfd20a39a7ddaea07103cac45d4d79bc7e6ce0d7</id>
<content type='text'>
NIP-01 places no restriction on d tag characters and NIP-34 only
recommends kebab-case without mandating it. Rejecting identifiers with
whitespace or other URL-unsafe characters was therefore overly strict.

The correct approach (per NIP-34 PR #2312 and GRASP-01) is to store
identifiers verbatim on disk and percent-encode them when constructing
URLs. The previous commit already handled the incoming direction
(percent-decoding URL paths before filesystem lookup); this commit
handles the outgoing direction and removes the validation restriction.

Changes:
- validate_identifier: drop whitespace rejection; only reject chars
  that are unsafe as filesystem directory names (/, \, null, . / ..)
- git/mod.rs: add percent_encode() alongside percent_decode()
- landing.rs: percent-encode identifier in nostr:// clone URL and
  gitworkshop link (also fixes a pre-existing bug where the clone URL
  displayed literal '{npub}' / '{identifier}' instead of the values)
</content>
</entry>
<entry>
<title>fix: reject identifiers with whitespace and URL-decode path components</title>
<updated>2026-04-09T15:24:17+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-04-09T15:24:17+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=2d74b9ca69b3a1e0b9a2359c12cc2d1979fc6130'/>
<id>urn:sha1:2d74b9ca69b3a1e0b9a2359c12cc2d1979fc6130</id>
<content type='text'>
Two bugs allowed a repository announcement with a space-containing
identifier ('kuboslopp by Shakespeare') to enter purgatory and create
a bare repo on disk, but then fail to serve git data over HTTP.

Bug 1 (serving): parse_git_url and parse_repo_url did not percent-decode
the URL path before resolving the filesystem path. A client requesting
/npub.../kuboslopp%20by%20Shakespeare.git/info/refs had the identifier
extracted as 'kuboslopp%20by%20Shakespeare' (literal %20), which did not
match the on-disk directory 'kuboslopp by Shakespeare.git'.

Fix: add percent_decode() in src/git/mod.rs and apply it to the repo
component in both parse_git_url and parse_repo_url.

Bug 2 (validation): validate_announcement did not check that the
identifier is safe as a filesystem path component and URL segment.
Identifiers containing whitespace, path separators, null bytes, or
reserved names (. / ..) should be rejected at acceptance time.

Fix: add validate_identifier() in src/nostr/events.rs and call it from
validate_announcement before any other policy checks.
</content>
</entry>
<entry>
<title>chore: remove arbitrary default max connections limit</title>
<updated>2026-03-25T07:19:26+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-03-25T07:19:26+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=28168a7701c897a5b6af13bc472d6f5902e0a96d'/>
<id>urn:sha1:28168a7701c897a5b6af13bc472d6f5902e0a96d</id>
<content type='text'>
When NGIT_MAX_CONNECTIONS is unset the relay imposes no connection cap,
deferring to OS fd limits and infrastructure controls. The option remains
available for operators who want an explicit ceiling.
</content>
</entry>
<entry>
<title>chore: apply cargo fmt and fix clippy warnings</title>
<updated>2026-02-26T15:42:09+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-02-26T15:42:09+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=9d86cf15f0275ffeee4519bd054e3b61dc8992ac'/>
<id>urn:sha1:9d86cf15f0275ffeee4519bd054e3b61dc8992ac</id>
<content type='text'>
Fix pre-existing clippy lints:
- &amp;PathBuf -&gt; &amp;Path in audit_cleanup.rs
- too_many_arguments on process_newly_available_git_data,
  process_purgatory_announcements, and HttpService::new
- clone_on_copy for PublicKey (Copy type) in purgatory cleanup loop
</content>
</entry>
<entry>
<title>fix: ignore peeled tag entries (^{}) in state event ref parsing</title>
<updated>2026-02-26T15:38:51+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-02-26T15:38:51+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=a2ecfc5a63311570f0f90c7ee40117e289639cb8'/>
<id>urn:sha1:a2ecfc5a63311570f0f90c7ee40117e289639cb8</id>
<content type='text'>
State events (kind 30618) can include refs/tags/&lt;name&gt;^{} 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.
</content>
</entry>
<entry>
<title>send auth rejection reason to git client via ERR pkt-line</title>
<updated>2026-02-26T12:19:31+00:00</updated>
<author>
<name>DanConwayDev</name>
<email>DanConwayDev@protonmail.com</email>
</author>
<published>2026-02-26T12:19:06+00:00</published>
<link rel='alternate' type='text/html' href='https://upleb.uk/npub1tkq8unhsd5jqx6ueex5lcpsgknrpquxuk44ftpjlpm3ulaake7xs76txrw/ngit-grasp-mirror/commit/?id=b13c6d924f7de5ff34405254b8bb21adf33c78c0'/>
<id>urn:sha1:b13c6d924f7de5ff34405254b8bb21adf33c78c0</id>
<content type='text'>
Previously push auth failures returned HTTP 403 which git clients
display as a generic transport error. Now they return HTTP 200 with
an ERR pkt-line containing the rejection reason (e.g. 'authorisation
failed: No state events in purgatory'), which git displays directly.

Remove GitError::Unauthorized as it is no longer used. GitError
variants now represent only transport/infrastructure failures; app-level
rejections use ERR pkt-line responses.
</content>
</entry>
</feed>
