diff options
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | src/lib/client.rs | 46 |
2 files changed, 45 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index d4b8083..1c5b583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
| @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 11 | 11 | ||
| 12 | - git server push option passthrough, enabling `-o secret-scanning.skip` for grasp servers | 12 | - git server push option passthrough, enabling `-o secret-scanning.skip` for grasp servers |
| 13 | - `ngit sync` now publishes the current state event to grasp server relays that are missing it or have a stale version before attempting git pushes, preventing rejections; per-relay state visibility is captured during the nostr fetch and surfaced via `FetchReport::state_per_relay` | 13 | - `ngit sync` now publishes the current state event to grasp server relays that are missing it or have a stale version before attempting git pushes, preventing rejections; per-relay state visibility is captured during the nostr fetch and surfaced via `FetchReport::state_per_relay` |
| 14 | - Fetch filters now request kind-5 deletion events for cached state and repo announcement events by `#e` tag (NIP-09), in addition to the existing `#a`-tagged filter; ensures deletions of these events are received even from clients that do not embed a repo coordinate in their deletion event | ||
| 14 | 15 | ||
| 15 | ### Fixed | 16 | ### Fixed |
| 16 | 17 | ||
diff --git a/src/lib/client.rs b/src/lib/client.rs index e657f53..95dd78d 100644 --- a/src/lib/client.rs +++ b/src/lib/client.rs | |||
| @@ -798,6 +798,9 @@ impl Connect for Client { | |||
| 798 | ) | 798 | ) |
| 799 | .copied() | 799 | .copied() |
| 800 | .collect(); | 800 | .collect(); |
| 801 | // Only request non-proposal event deletions on the first loop iteration; | ||
| 802 | // cleared after first use so subsequent iterations don't re-request them. | ||
| 803 | let mut fresh_non_proposal_event_ids = request.non_proposal_event_ids.clone(); | ||
| 801 | 804 | ||
| 802 | let mut report = FetchReport::default(); | 805 | let mut report = FetchReport::default(); |
| 803 | 806 | ||
| @@ -813,8 +816,13 @@ impl Connect for Client { | |||
| 813 | let dim = Style::new().color256(247); | 816 | let dim = Style::new().color256(247); |
| 814 | 817 | ||
| 815 | loop { | 818 | loop { |
| 816 | let filters = | 819 | let filters = get_fetch_filters( |
| 817 | get_fetch_filters(&fresh_coordinates, &fresh_proposal_roots, &fresh_profiles); | 820 | &fresh_coordinates, |
| 821 | &fresh_proposal_roots, | ||
| 822 | &fresh_non_proposal_event_ids, | ||
| 823 | &fresh_profiles, | ||
| 824 | ); | ||
| 825 | fresh_non_proposal_event_ids = HashSet::new(); | ||
| 818 | 826 | ||
| 819 | if let Some(pb) = &pb { | 827 | if let Some(pb) = &pb { |
| 820 | pb.set_prefix( | 828 | pb.set_prefix( |
| @@ -1727,6 +1735,8 @@ async fn create_relays_request( | |||
| 1727 | for filter in get_fetch_filters( | 1735 | for filter in get_fetch_filters( |
| 1728 | &repo_coordinates_without_relays, | 1736 | &repo_coordinates_without_relays, |
| 1729 | &proposals, | 1737 | &proposals, |
| 1738 | &HashSet::new(), /* non_proposal_event_ids not yet computed; deletion events are not | ||
| 1739 | * cached locally */ | ||
| 1730 | &missing_contributor_profiles | 1740 | &missing_contributor_profiles |
| 1731 | .union( | 1741 | .union( |
| 1732 | &profiles_to_fetch_from_user_relays | 1742 | &profiles_to_fetch_from_user_relays |
| @@ -1828,6 +1838,21 @@ async fn create_relays_request( | |||
| 1828 | } else { | 1838 | } else { |
| 1829 | None | 1839 | None |
| 1830 | }, | 1840 | }, |
| 1841 | non_proposal_event_ids: { | ||
| 1842 | let mut ids: HashSet<EventId> = HashSet::new(); | ||
| 1843 | // Include repo announcement event IDs so we can request kind-5 | ||
| 1844 | // deletions for them by #e tag (NIP-09 style). | ||
| 1845 | if let Some(repo_ref) = &repo_ref { | ||
| 1846 | for event in repo_ref.events.values() { | ||
| 1847 | ids.insert(event.id); | ||
| 1848 | } | ||
| 1849 | // Also include the state event ID if we have one. | ||
| 1850 | if let Ok(existing_state) = get_state_from_cache(git_repo_path, repo_ref).await { | ||
| 1851 | ids.insert(existing_state.event.id); | ||
| 1852 | } | ||
| 1853 | } | ||
| 1854 | ids | ||
| 1855 | }, | ||
| 1831 | proposals, | 1856 | proposals, |
| 1832 | contributors, | 1857 | contributors, |
| 1833 | missing_contributor_profiles, | 1858 | missing_contributor_profiles, |
| @@ -2075,6 +2100,7 @@ pub fn consolidate_fetch_reports(reports: Vec<Result<FetchReport>>) -> FetchRepo | |||
| 2075 | pub fn get_fetch_filters( | 2100 | pub fn get_fetch_filters( |
| 2076 | repo_coordinates: &HashSet<Nip19Coordinate>, | 2101 | repo_coordinates: &HashSet<Nip19Coordinate>, |
| 2077 | proposal_ids: &HashSet<EventId>, | 2102 | proposal_ids: &HashSet<EventId>, |
| 2103 | non_proposal_event_ids: &HashSet<EventId>, | ||
| 2078 | required_profiles: &HashSet<PublicKey>, | 2104 | required_profiles: &HashSet<PublicKey>, |
| 2079 | ) -> Vec<nostr::Filter> { | 2105 | ) -> Vec<nostr::Filter> { |
| 2080 | [ | 2106 | [ |
| @@ -2124,6 +2150,19 @@ pub fn get_fetch_filters( | |||
| 2124 | ), | 2150 | ), |
| 2125 | ] | 2151 | ] |
| 2126 | }, | 2152 | }, |
| 2153 | // Request kind-5 deletions for state events and repo announcements by | ||
| 2154 | // their event ID (#e tag), as per NIP-09. The #a-tagged filter above | ||
| 2155 | // covers addressable-event deletions; this covers the specific event IDs | ||
| 2156 | // of the state and announcement events we already have cached. | ||
| 2157 | if non_proposal_event_ids.is_empty() { | ||
| 2158 | vec![] | ||
| 2159 | } else { | ||
| 2160 | vec![ | ||
| 2161 | nostr::Filter::default() | ||
| 2162 | .kind(Kind::EventDeletion) | ||
| 2163 | .events(non_proposal_event_ids.clone()), | ||
| 2164 | ] | ||
| 2165 | }, | ||
| 2127 | if required_profiles.is_empty() { | 2166 | if required_profiles.is_empty() { |
| 2128 | vec![] | 2167 | vec![] |
| 2129 | } else { | 2168 | } else { |
| @@ -2290,6 +2329,9 @@ pub struct FetchRequest { | |||
| 2290 | repo_coordinates_without_relays: Vec<(Nip19Coordinate, Option<Timestamp>)>, | 2329 | repo_coordinates_without_relays: Vec<(Nip19Coordinate, Option<Timestamp>)>, |
| 2291 | state: Option<(Timestamp, EventId)>, | 2330 | state: Option<(Timestamp, EventId)>, |
| 2292 | proposals: HashSet<EventId>, | 2331 | proposals: HashSet<EventId>, |
| 2332 | /// Event IDs of non-proposal events (state events, repo announcements) for | ||
| 2333 | /// which we should also request kind-5 deletion events by `#e` tag. | ||
| 2334 | non_proposal_event_ids: HashSet<EventId>, | ||
| 2293 | contributors: HashSet<PublicKey>, | 2335 | contributors: HashSet<PublicKey>, |
| 2294 | missing_contributor_profiles: HashSet<PublicKey>, | 2336 | missing_contributor_profiles: HashSet<PublicKey>, |
| 2295 | existing_events: HashSet<EventId>, | 2337 | existing_events: HashSet<EventId>, |