diff options
Diffstat (limited to 'src/branch_refs.rs')
| -rw-r--r-- | src/branch_refs.rs | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/src/branch_refs.rs b/src/branch_refs.rs new file mode 100644 index 0000000..d6f9613 --- /dev/null +++ b/src/branch_refs.rs | |||
| @@ -0,0 +1,274 @@ | |||
| 1 | use std::{path::PathBuf, fs, str::FromStr}; | ||
| 2 | |||
| 3 | use nostr::{Event, Filter, Timestamp, secp256k1::XOnlyPublicKey, EventId}; | ||
| 4 | use nostr_sdk::blocking::Client; | ||
| 5 | |||
| 6 | use crate::{utils::{load_event, save_event}, kind::Kind, repos::repo::Repo, groups::group::Group, repo_config::RepoConfig}; | ||
| 7 | |||
| 8 | |||
| 9 | pub struct BranchRefs { | ||
| 10 | pub branches: Vec<Event>, | ||
| 11 | pub pull_requests: Vec<Event>, | ||
| 12 | pub merges: Vec<Event>, | ||
| 13 | pub groups: Vec<Event>, | ||
| 14 | repo_dir_path: PathBuf, | ||
| 15 | pub most_recent_timestamp: Timestamp, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl BranchRefs { | ||
| 19 | pub fn new (branch_events: Vec<Event>, repo_dir_path: PathBuf) -> Self { | ||
| 20 | let mut refs = Self { | ||
| 21 | branches: vec![], | ||
| 22 | pull_requests: vec![], | ||
| 23 | merges: vec![], | ||
| 24 | groups: vec![], | ||
| 25 | repo_dir_path, | ||
| 26 | most_recent_timestamp: Timestamp::from(0), | ||
| 27 | }; | ||
| 28 | |||
| 29 | // add repo first branch in branches vector | ||
| 30 | refs.update( | ||
| 31 | load_event(refs.repo_dir_path.join(".ngit/repo.json")) | ||
| 32 | .expect("repo.json to be present and load as event") | ||
| 33 | ); | ||
| 34 | |||
| 35 | //load locally | ||
| 36 | for dir_name in [ | ||
| 37 | "groups", | ||
| 38 | "branches", | ||
| 39 | "merges", | ||
| 40 | "prs", | ||
| 41 | ] { | ||
| 42 | let dir_path = refs.repo_dir_path.join(".ngit").join(&dir_name); | ||
| 43 | if dir_path.exists() { | ||
| 44 | let dir = fs::read_dir(&dir_path) | ||
| 45 | .expect("read_dir to produce ReadDir from a path that exists"); | ||
| 46 | for entry in dir { | ||
| 47 | let path = entry | ||
| 48 | .expect("DirEntry to return from ReadDir") | ||
| 49 | .path(); | ||
| 50 | // load each BranchRef event in .ngit and call update | ||
| 51 | refs.update( | ||
| 52 | load_event(path) | ||
| 53 | .expect("every file in .ngit paths is a valid json event") | ||
| 54 | ); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | else { | ||
| 58 | panic!("expected dir to exist in branch_refs"); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | refs.updates(branch_events); | ||
| 62 | refs | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn updates (&mut self, branch_events: Vec<Event>) { | ||
| 66 | for event in branch_events.clone().into_iter() { | ||
| 67 | self.update(event); | ||
| 68 | } | ||
| 69 | let mut repo_config = RepoConfig::open(&self.repo_dir_path); | ||
| 70 | repo_config.set_last_branch_ref_update_time(self.most_recent_timestamp.clone()); | ||
| 71 | } | ||
| 72 | |||
| 73 | pub fn update (&mut self, event: Event) { | ||
| 74 | let event_to_store = event.clone(); | ||
| 75 | // /// check event is for repo | ||
| 76 | // fn event_is_for_repo(event: &Event,branch_refs: &BranchRefs) -> bool { | ||
| 77 | // match event.tags.iter().find(|tag| tag_is_repo(tag)) { | ||
| 78 | // None => false, | ||
| 79 | // Some(tag) => { | ||
| 80 | // match branch_refs.branches.get(0) { | ||
| 81 | // None => true, // current repo unknown | ||
| 82 | // Some(b) => tag_extract_value(tag) == b.id.to_string(), | ||
| 83 | // } | ||
| 84 | // }, | ||
| 85 | // } | ||
| 86 | // } | ||
| 87 | |||
| 88 | // update most_recent_timestamp | ||
| 89 | if event.created_at > self.most_recent_timestamp { | ||
| 90 | self.most_recent_timestamp = event.created_at.clone(); | ||
| 91 | } | ||
| 92 | |||
| 93 | // add events to vectors | ||
| 94 | let dir_name = match Kind::from(event.clone().kind.as_u64()) { | ||
| 95 | Kind::InitializeRepo => { | ||
| 96 | // if !self.branches.iter().any(|e| e.id == event.id) | ||
| 97 | // && event_is_for_repo(&event, &self) { | ||
| 98 | if !self.branches.iter().any(|e| e.id == event.id) { | ||
| 99 | self.branches.push(event); | ||
| 100 | Some("branches") | ||
| 101 | } | ||
| 102 | else { None } | ||
| 103 | }, | ||
| 104 | Kind::InitializeBranch => { | ||
| 105 | // if !self.branches.iter().any(|e| e.id == event.id) | ||
| 106 | // && event_is_for_repo(&event, &self) { | ||
| 107 | if !self.branches.iter().any(|e| e.id == event.id) { | ||
| 108 | self.branches.push(event); | ||
| 109 | Some("branches") | ||
| 110 | } | ||
| 111 | else { None } | ||
| 112 | }, | ||
| 113 | Kind::PullRequest => { | ||
| 114 | // if !self.pull_requests.iter().any(|e| e.id == event.id) | ||
| 115 | // && event_is_for_repo(&event, &self) { | ||
| 116 | if !self.pull_requests.iter().any(|e| e.id == event.id) { | ||
| 117 | self.pull_requests.push(event); | ||
| 118 | Some("prs") | ||
| 119 | } | ||
| 120 | else { None } | ||
| 121 | } | ||
| 122 | Kind::Merge => { | ||
| 123 | // if !self.merges.iter().any(|e| e.id == event.id) | ||
| 124 | // && event_is_for_repo(&event, &self) { | ||
| 125 | if !self.merges.iter().any(|e| e.id == event.id) { | ||
| 126 | self.merges.push(event); | ||
| 127 | Some("merges") | ||
| 128 | } | ||
| 129 | else { None } | ||
| 130 | }, | ||
| 131 | Kind::InitializeGroup => { | ||
| 132 | if !self.groups.iter().any(|e| e.id == event.id) { | ||
| 133 | self.groups.push(event); | ||
| 134 | Some("groups") | ||
| 135 | } | ||
| 136 | else { None } | ||
| 137 | }, | ||
| 138 | _ => None, | ||
| 139 | }; | ||
| 140 | |||
| 141 | // store events in .ngit directory | ||
| 142 | match dir_name { | ||
| 143 | Some(dir_name) => { | ||
| 144 | let path = self.repo_dir_path.join(".ngit").join(format!("{}/{}.json",dir_name, event_to_store.id)); | ||
| 145 | if !path.exists() { | ||
| 146 | save_event(&path, &event_to_store) | ||
| 147 | .expect(format!("save_event will store BranchRefs event in {}",&path.to_string_lossy()).as_str()); | ||
| 148 | } | ||
| 149 | }, | ||
| 150 | None => (), | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | fn branch_event(&self, branch_id: Option<&String>) -> Event { | ||
| 155 | match branch_id { | ||
| 156 | None => self.branches[0].clone(), | ||
| 157 | Some(branch_id) => self.branches.iter().find(|b| b.id.to_string() == *branch_id) | ||
| 158 | .expect("BranchRefs.branch_event() will always be called with a branch_id from a branch in its cache") | ||
| 159 | .clone(), | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | pub fn branch_as_repo(&self, branch_id: Option<&String>) -> Repo { | ||
| 164 | Repo::new_from_event(self.branch_event(branch_id)) | ||
| 165 | .expect("event in BranchRefs.branches to produce Repo") | ||
| 166 | } | ||
| 167 | |||
| 168 | /// assumes the branch_id is in cachse | ||
| 169 | pub fn maintainers_group_id(&self, branch_id: Option<&String>) -> EventId { | ||
| 170 | self.branch_as_repo(branch_id) | ||
| 171 | .maintainers_group.get_first_active_group() | ||
| 172 | .expect("a repo will always have an active maintainers group") | ||
| 173 | .clone() | ||
| 174 | } | ||
| 175 | |||
| 176 | /// assumes the branch_id is in cache. returns None if maintainers group event cannot be found. | ||
| 177 | pub fn maintainers_group(&self, branch_or_group_id: Option<&String>) -> Option<Group> { | ||
| 178 | match self.groups.iter().find(|g| | ||
| 179 | // for branch id | ||
| 180 | g.id == self.maintainers_group_id(branch_or_group_id) | ||
| 181 | // for group id | ||
| 182 | || match branch_or_group_id { | ||
| 183 | None => false, | ||
| 184 | Some(id) => g.id == EventId::from_str(id).expect("id to be valid event id"), | ||
| 185 | }, | ||
| 186 | ) { | ||
| 187 | None => None, | ||
| 188 | Some(event) => Some( | ||
| 189 | Group::new_from_event(event.clone()) | ||
| 190 | .expect("group stored in BranchRefs.groups will always produce Group") | ||
| 191 | ), | ||
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 | /// returns None if maintainers group event cannot be found | ||
| 196 | pub fn is_authorized(&self, branch_id: Option<&String>, pubkey: &XOnlyPublicKey) -> Option<bool> { | ||
| 197 | match self.maintainers_group(branch_id) { | ||
| 198 | None => None, | ||
| 199 | Some(group) => Some( | ||
| 200 | group.is_member(pubkey) | ||
| 201 | // TODO - add support for nested groups so 'is_member' checks for indirect membership | ||
| 202 | // for it will just be members of the branch group or maintainers group | ||
| 203 | || | ||
| 204 | match self.maintainers_group(None) { | ||
| 205 | None => false, | ||
| 206 | Some(group) => group.is_member(pubkey), | ||
| 207 | } | ||
| 208 | ), | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | pub fn group_ids_for_branches_without_cached_groups(&self) -> Vec<EventId> { | ||
| 213 | self.branches.iter() | ||
| 214 | .map(|b| | ||
| 215 | self.maintainers_group_id(Some(&b.id.to_string())) | ||
| 216 | .clone() | ||
| 217 | ) | ||
| 218 | .filter(|id|!self.groups.iter().any(|e|e.id == *id)) | ||
| 219 | .collect() | ||
| 220 | |||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | pub fn get_branch_refs (repo: &Repo, client: &Client, repo_dir_path: &PathBuf) -> BranchRefs { | ||
| 225 | |||
| 226 | let mut refs = BranchRefs::new(vec![],repo_dir_path.clone()); | ||
| 227 | |||
| 228 | let repo_config = RepoConfig::open(repo_dir_path); | ||
| 229 | |||
| 230 | // filter for branches, PRs and Merges | ||
| 231 | let mut tag_filter = Filter::new() | ||
| 232 | .event(repo.id) | ||
| 233 | .kinds(vec![ | ||
| 234 | Kind::InitializeBranch.into_sdk_custom_kind(), | ||
| 235 | Kind::PullRequest.into_sdk_custom_kind(), | ||
| 236 | Kind::Merge.into_sdk_custom_kind(), | ||
| 237 | ]); | ||
| 238 | match repo_config.last_branch_ref_update_time() { | ||
| 239 | None => (), | ||
| 240 | Some(timestamp) => { | ||
| 241 | tag_filter = tag_filter.since(timestamp.clone()); | ||
| 242 | } | ||
| 243 | }; | ||
| 244 | |||
| 245 | let branch_events: Vec<Event> = client.get_events_of( | ||
| 246 | vec![ | ||
| 247 | // branch maintainer groups | ||
| 248 | Filter::new().ids(refs.group_ids_for_branches_without_cached_groups()), | ||
| 249 | tag_filter, | ||
| 250 | ], | ||
| 251 | None, | ||
| 252 | ) | ||
| 253 | .expect("get_events_of to not return an error"); | ||
| 254 | |||
| 255 | refs.updates(branch_events); | ||
| 256 | // refs.merged_branches_ids.push(repo.id.to_string()); | ||
| 257 | |||
| 258 | // for event in refs.merges.iter() { | ||
| 259 | // match &event.tags.iter().find(|t|tag_is_branch(t)) { | ||
| 260 | // None => {}, | ||
| 261 | // Some(t) => { | ||
| 262 | // match &refs.maintainers_group { | ||
| 263 | // None => (), | ||
| 264 | // Some(g) => { | ||
| 265 | // if g.is_member(&event.pubkey) { | ||
| 266 | // refs.merged_branches_ids.push(tag_extract_value(t)); | ||
| 267 | // } | ||
| 268 | // } | ||
| 269 | // } | ||
| 270 | // } | ||
| 271 | // } | ||
| 272 | // } | ||
| 273 | refs | ||
| 274 | } | ||