diff options
Diffstat (limited to 'src/lib/git_events.rs')
| -rw-r--r-- | src/lib/git_events.rs | 67 |
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. |
| 96 | pub const KIND_LABEL: Kind = Kind::Custom(1985); | 96 | pub 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). | ||
| 100 | pub const KIND_COVER_NOTE: Kind = Kind::Custom(1624); | ||
| 97 | 101 | ||
| 98 | pub fn event_is_patch_set_root(event: &Event) -> bool { | 102 | pub 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. |
| 996 | pub fn process_labels( | 1000 | pub 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. |
| 1165 | pub fn get_labels( | 1165 | pub 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. | ||
| 1178 | pub 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 | ||
| 1173 | pub fn get_status( | 1218 | pub fn get_status( |