upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/lib/git_events.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-03-05 14:19:49 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-03-05 14:23:07 +0000
commit37244449d6d0d58bb639f181bd15092de1acaaee (patch)
tree7de03867a1a9578e32fdbdbb2be63e863cea57a4 /src/lib/git_events.rs
parent609f3c3db02d437222e2c8e171189179d06c3e9c (diff)
feat(cover-note): add kind-1624 cover notes for PRs, patches, and issues
Implements experimental kind-1624 cover note events: - KIND_COVER_NOTE constant and process_cover_note() in git_events.rs; replaceable semantics (latest created_at, hex-id tiebreak), author or maintainer only - kind-1624 events fetched alongside labels in the fetch pipeline; cover_notes count added to FetchReport display - ngit pr/issue view: cover note displayed in place of description with a clear 'Cover Note:' header; maintainer-authored notes identify the author; original description shown only with --comments; cover_note object included in --json output - ngit pr set-cover-note / ngit issue set-cover-note: publish a kind-1624 event; nostr: mentions in --body converted to q/p tags via tags_from_content (same rules as issue --body) - Fix pre-existing clippy::too_many_lines on repo/mod.rs show_info
Diffstat (limited to 'src/lib/git_events.rs')
-rw-r--r--src/lib/git_events.rs67
1 files changed, 56 insertions, 11 deletions
diff --git a/src/lib/git_events.rs b/src/lib/git_events.rs
index b512e44..a5793a5 100644
--- a/src/lib/git_events.rs
+++ b/src/lib/git_events.rs
@@ -94,6 +94,10 @@ pub const KIND_COMMENT: Kind = Kind::Custom(1111);
94/// NIP-32 label event (kind 1985) — applies hashtag labels to an existing 94/// NIP-32 label event (kind 1985) — applies hashtag labels to an existing
95/// event. Used to add labels to issues, patches and PRs after the fact. 95/// event. Used to add labels to issues, patches and PRs after the fact.
96pub const KIND_LABEL: Kind = Kind::Custom(1985); 96pub const KIND_LABEL: Kind = Kind::Custom(1985);
97/// Cover note event (kind 1624) — a markdown note attached to a PR, patch or
98/// issue by its author or a repository maintainer. Only the latest authorised
99/// event is displayed (replaceable semantics with hex-id tiebreak).
100pub const KIND_COVER_NOTE: Kind = Kind::Custom(1624);
97 101
98pub fn event_is_patch_set_root(event: &Event) -> bool { 102pub fn event_is_patch_set_root(event: &Event) -> bool {
99 event.kind.eq(&Kind::GitPatch) 103 event.kind.eq(&Kind::GitPatch)
@@ -985,19 +989,15 @@ pub fn is_event_proposal_root_for_branch(
985/// 989///
986/// 1. `t` tags on the event itself (self-reported by the event author). 990/// 1. `t` tags on the event itself (self-reported by the event author).
987/// 2. NIP-32 kind-1985 label events in `label_events` that reference `event` 991/// 2. NIP-32 kind-1985 label events in `label_events` that reference `event`
988/// via a lowercase `e` tag and carry `["L", "#t"]` + 992/// via a lowercase `e` tag and carry `["L", "#t"]` + `["l", "<value>",
989/// `["l", "<value>", "#t"]` tags. 993/// "#t"]` tags.
990/// 994///
991/// A label is only applied when the author of the source event is either the 995/// A label is only applied when the author of the source event is either the
992/// author of `event` itself or one of the repository maintainers. 996/// author of `event` itself or one of the repository maintainers.
993/// 997///
994/// Labels are additive — all valid label events contribute; there is no 998/// Labels are additive — all valid label events contribute; there is no
995/// "latest wins" replacement semantics. 999/// "latest wins" replacement semantics.
996pub fn process_labels( 1000pub fn process_labels(event: &Event, repo_ref: &RepoRef, label_events: &[Event]) -> Vec<String> {
997 event: &Event,
998 repo_ref: &RepoRef,
999 label_events: &[Event],
1000) -> Vec<String> {
1001 let is_permitted = |pubkey: &PublicKey| -> bool { 1001 let is_permitted = |pubkey: &PublicKey| -> bool {
1002 pubkey.eq(&event.pubkey) || repo_ref.maintainers.contains(pubkey) 1002 pubkey.eq(&event.pubkey) || repo_ref.maintainers.contains(pubkey)
1003 }; 1003 };
@@ -1162,12 +1162,57 @@ pub fn get_labels_and_subject(
1162/// Compatibility wrapper — returns only the hashtag labels. 1162/// Compatibility wrapper — returns only the hashtag labels.
1163/// 1163///
1164/// Prefer [`get_labels_and_subject`] when the subject override is also needed. 1164/// Prefer [`get_labels_and_subject`] when the subject override is also needed.
1165pub fn get_labels( 1165pub fn get_labels(event: &Event, repo_ref: &RepoRef, label_events: &[Event]) -> Vec<String> {
1166 process_labels(event, repo_ref, label_events)
1167}
1168
1169/// The effective cover note for `event`, selected from a pre-fetched set of
1170/// kind-1624 events.
1171///
1172/// A cover note is a markdown body attached to a PR, patch or issue by its
1173/// author or a repository maintainer. Only the latest authorised event wins
1174/// (replaceable semantics: newest `created_at`, tiebreak by lexicographically
1175/// larger event ID). Events authored by other pubkeys are ignored.
1176///
1177/// Returns `None` when no valid cover note exists.
1178pub fn process_cover_note(
1166 event: &Event, 1179 event: &Event,
1167 repo_ref: &RepoRef, 1180 repo_ref: &RepoRef,
1168 label_events: &[Event], 1181 cover_note_events: &[Event],
1169) -> Vec<String> { 1182) -> Option<(Event, bool)> {
1170 process_labels(event, repo_ref, label_events) 1183 let is_permitted = |pubkey: &PublicKey| -> bool {
1184 pubkey.eq(&event.pubkey) || repo_ref.maintainers.contains(pubkey)
1185 };
1186
1187 let event_id_str = event.id.to_string();
1188
1189 // Find the winning cover note: latest created_at, tiebreak by
1190 // lexicographically larger event ID (NIP-1 replaceable event semantics).
1191 let winner = cover_note_events
1192 .iter()
1193 .filter(|cn| {
1194 if !cn.kind.eq(&KIND_COVER_NOTE) {
1195 return false;
1196 }
1197 if !is_permitted(&cn.pubkey) {
1198 return false;
1199 }
1200 // Must reference our event via a lowercase `e` tag.
1201 cn.tags.iter().any(|t| {
1202 let s = t.as_slice();
1203 s.len() >= 2 && s[0].eq("e") && s[1].eq(&event_id_str)
1204 })
1205 })
1206 .max_by(|a, b| {
1207 a.created_at
1208 .cmp(&b.created_at)
1209 .then_with(|| a.id.to_string().cmp(&b.id.to_string()))
1210 })?;
1211
1212 // True when the cover note author differs from the original event author
1213 // (i.e. a maintainer wrote it, not the PR/issue author).
1214 let by_different_author = winner.pubkey != event.pubkey;
1215 Some((winner.clone(), by_different_author))
1171} 1216}
1172 1217
1173pub fn get_status( 1218pub fn get_status(