diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2023-05-21 11:14:47 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2023-05-21 11:14:47 +0000 |
| commit | 0067804cc00e94ce2b7043e67f9ff50968525479 (patch) | |
| tree | 2accdc6d4e9b73df4f20499238ec24f24a52a1b8 /src/funcs/get_updates_of_patches.rs | |
| parent | 5c5feaa732363e32e2a980a887fa42b4394b1a5e (diff) | |
v0.0.1-alpha funcs
Diffstat (limited to 'src/funcs/get_updates_of_patches.rs')
| -rw-r--r-- | src/funcs/get_updates_of_patches.rs | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/src/funcs/get_updates_of_patches.rs b/src/funcs/get_updates_of_patches.rs new file mode 100644 index 0000000..ad28774 --- /dev/null +++ b/src/funcs/get_updates_of_patches.rs | |||
| @@ -0,0 +1,263 @@ | |||
| 1 | use std::{path::PathBuf, str::FromStr}; | ||
| 2 | |||
| 3 | use git2::Repository; | ||
| 4 | use nostr::{Event, Filter, EventId}; | ||
| 5 | use nostr_sdk::blocking::Client; | ||
| 6 | |||
| 7 | use crate::{ngit_tag::{tag_is_patch_parent, tag_is_initial_commit, tag_extract_value, tag_is_patch, tag_is_branch, tag_is_commit_parent, tag_is_commit}, utils::{load_event}, funcs::find_latest_patch::find_latest_patch, patch::{patch_commit_id, patch_is_commit}, branch_refs::BranchRefs, repo_config::RepoConfig, kind::Kind}; | ||
| 8 | |||
| 9 | |||
| 10 | /// ancessor patch events first | ||
| 11 | pub fn get_updates_of_patches ( | ||
| 12 | client: &Client, | ||
| 13 | branch_refs: &mut BranchRefs, | ||
| 14 | git_repo: &Repository, | ||
| 15 | repo_dir_path: &PathBuf, | ||
| 16 | branch_id: &String, | ||
| 17 | branch_name: &Option<String>, | ||
| 18 | pull_new_branch: bool, | ||
| 19 | ) -> Vec<Event> { | ||
| 20 | |||
| 21 | let repo_config = RepoConfig::open(repo_dir_path); | ||
| 22 | let last_patch_timestamp = repo_config.last_patch_update_time(branch_id.clone()); | ||
| 23 | |||
| 24 | // create direct patches filter | ||
| 25 | let direct_patches_filter = Filter::new() | ||
| 26 | .event( | ||
| 27 | EventId::from_str(branch_id) | ||
| 28 | .expect("branch_id to render as EventId") | ||
| 29 | ) | ||
| 30 | .kinds(vec![Kind::Patch.into_sdk_custom_kind()]); | ||
| 31 | |||
| 32 | let mut filters = vec![ | ||
| 33 | match &last_patch_timestamp { | ||
| 34 | None => direct_patches_filter, | ||
| 35 | Some(timestamp) => { | ||
| 36 | direct_patches_filter.since(timestamp.clone()) | ||
| 37 | } | ||
| 38 | } | ||
| 39 | ]; | ||
| 40 | |||
| 41 | // get maintainers group | ||
| 42 | if branch_refs.maintainers_group(Some(&branch_id)).is_none() { | ||
| 43 | // fetch branch mantainers group and check again | ||
| 44 | client.add_relays( | ||
| 45 | branch_refs.branch_as_repo(Some(branch_id)) | ||
| 46 | .relays | ||
| 47 | .clone().iter().map(|url| (url, None)).collect() | ||
| 48 | ) | ||
| 49 | .expect("branch relays to be added to client"); | ||
| 50 | let mut group_events = client.get_events_of( | ||
| 51 | vec![ | ||
| 52 | // use the opportunity to get all the remaining groups | ||
| 53 | Filter::new().ids(branch_refs.group_ids_for_branches_without_cached_groups()), | ||
| 54 | ], | ||
| 55 | None, | ||
| 56 | ) | ||
| 57 | .expect("get_events_of to not return an error"); | ||
| 58 | group_events.sort(); | ||
| 59 | group_events.dedup(); | ||
| 60 | branch_refs.updates(group_events); | ||
| 61 | } | ||
| 62 | |||
| 63 | // create indirect pacthes filter | ||
| 64 | let merges_into_branch: Vec<Event> = branch_refs.merges.iter().filter(|event| | ||
| 65 | // merged into branch | ||
| 66 | event.tags.iter().any(|t| | ||
| 67 | tag_is_branch(t) | ||
| 68 | && tag_extract_value(t) == branch_id.clone() | ||
| 69 | ) | ||
| 70 | // merge timestamp is after last_patch_timestamp - we already have patches before this date | ||
| 71 | && match &last_patch_timestamp { | ||
| 72 | None => true, | ||
| 73 | Some(timestamp) => timestamp < &event.created_at | ||
| 74 | } | ||
| 75 | // author is member of branch maintainers group | ||
| 76 | && branch_refs.is_authorized(Some(branch_id), &event.pubkey) | ||
| 77 | .expect("found group event for branch after checking on speficied relays") | ||
| 78 | ).map(|e|e.clone()) | ||
| 79 | .collect(); | ||
| 80 | |||
| 81 | if !merges_into_branch.is_empty() { | ||
| 82 | filters.push( | ||
| 83 | // ids for all patches referenced in merges | ||
| 84 | Filter::new() | ||
| 85 | .ids( | ||
| 86 | merges_into_branch.iter().flat_map(|event| | ||
| 87 | event.tags.iter() | ||
| 88 | .filter(|t| tag_is_patch(t)) | ||
| 89 | .map(|t| tag_extract_value(t).clone()) | ||
| 90 | .collect::<Vec<String>>() | ||
| 91 | ) | ||
| 92 | .collect::<Vec<String>>() | ||
| 93 | ) | ||
| 94 | // .kinds(vec![Kind::Patch.into_sdk_custom_kind()]) | ||
| 95 | ) | ||
| 96 | } | ||
| 97 | |||
| 98 | // find patch events | ||
| 99 | let mut patch_events: Vec<Event> = client.get_events_of( | ||
| 100 | filters, | ||
| 101 | None, | ||
| 102 | ) | ||
| 103 | .expect("get_events_of to not return an error when looking for patches"); | ||
| 104 | |||
| 105 | patch_events.sort(); | ||
| 106 | patch_events.dedup(); | ||
| 107 | |||
| 108 | // find patch tip on branch | ||
| 109 | let latest_patch_on_branch = match find_latest_patch( | ||
| 110 | &branch_id, | ||
| 111 | &patch_events, | ||
| 112 | &merges_into_branch, | ||
| 113 | &branch_refs, | ||
| 114 | &repo_dir_path, | ||
| 115 | ) { | ||
| 116 | // no patches return empty vector | ||
| 117 | None => { return vec![] }, // for pull_new_branch do we set the branch to the latest commit referneced even if we have it? | ||
| 118 | Some(event) => event, | ||
| 119 | }; | ||
| 120 | |||
| 121 | let mut new_patches_on_branch = vec![]; | ||
| 122 | // for pull_new_branch - cycle through patch parents until we find any patch that exists in our commit history | ||
| 123 | if pull_new_branch { | ||
| 124 | let mut patch_event_id = latest_patch_on_branch.id.to_string(); | ||
| 125 | let mut patch_commit_id = tag_extract_value( | ||
| 126 | latest_patch_on_branch.tags.iter().find(|t|tag_is_commit(t)) | ||
| 127 | .expect("all patch events to have a commit tag") | ||
| 128 | ); | ||
| 129 | |||
| 130 | loop { | ||
| 131 | let patch = match patch_events.iter().find(|p| p.id.to_string() == patch_event_id.clone()) { | ||
| 132 | // patch event found in patch_events | ||
| 133 | Some(patch) => patch, | ||
| 134 | None => { | ||
| 135 | // loop for parent locally | ||
| 136 | if repo_dir_path.join(format!( | ||
| 137 | ".ngit/patches/{}.json", | ||
| 138 | patch_commit_id, | ||
| 139 | )).exists() { | ||
| 140 | // break out of loop when we identify the commit where the branch begins | ||
| 141 | break | ||
| 142 | } | ||
| 143 | else { | ||
| 144 | panic!("cannot find parent patch locally or in patch_events. This will fail if the branch does not share a commit with main / master") | ||
| 145 | } | ||
| 146 | } | ||
| 147 | }; | ||
| 148 | // add patch to list of patches to apply to new branch | ||
| 149 | new_patches_on_branch.push(patch.clone()); | ||
| 150 | // prepare loop for next patch - set patch_event_id to current patches parent | ||
| 151 | patch_event_id = tag_extract_value( | ||
| 152 | patch.tags.iter().find(|t|tag_is_patch_parent(t)) | ||
| 153 | .expect("patch to always have a patch parent.") | ||
| 154 | ); | ||
| 155 | patch_commit_id = tag_extract_value( | ||
| 156 | patch.tags.iter().find(|t|tag_is_commit_parent(t)) | ||
| 157 | .expect("patch to always have a commit parent. This will fail if the branch does not share a commit with main / master") | ||
| 158 | ); | ||
| 159 | }; | ||
| 160 | } | ||
| 161 | |||
| 162 | // cycle through patch parents until we the latest commit in our local branch, or error if detects a rebase (it exists in our branch history) | ||
| 163 | else { | ||
| 164 | // revwalk through branch to identify forced push | ||
| 165 | let mut revwalk = git_repo.revwalk() | ||
| 166 | .expect("revwalk to not error on git_repo"); | ||
| 167 | match &branch_name { | ||
| 168 | Some(name) => { | ||
| 169 | revwalk.push( | ||
| 170 | git_repo.find_branch( | ||
| 171 | name.as_str(), | ||
| 172 | git2::BranchType::Local | ||
| 173 | ) | ||
| 174 | .expect("branch found from the branch_name") | ||
| 175 | .get() | ||
| 176 | .peel_to_commit() | ||
| 177 | .expect("branch reference to peel back to a commit") | ||
| 178 | .id() | ||
| 179 | ) | ||
| 180 | .expect("revwalk push_glob(branch_name) not to error if branch name is not None"); | ||
| 181 | } | ||
| 182 | None => (), | ||
| 183 | } | ||
| 184 | let commit_ids_in_branch: Vec<String> = if branch_name.is_none() { vec![] } else { | ||
| 185 | revwalk.map(|oid| | ||
| 186 | oid | ||
| 187 | .expect("revwalk to produce oids without error") | ||
| 188 | .to_string() | ||
| 189 | ).collect() | ||
| 190 | }; | ||
| 191 | |||
| 192 | let latest_commit: Option<&String> = match commit_ids_in_branch.get(0) { | ||
| 193 | None => None, | ||
| 194 | Some(latest_commit) => { | ||
| 195 | // return empty if latest patch is in current chain | ||
| 196 | if commit_ids_in_branch.iter().any(|id| | ||
| 197 | patch_commit_id(&latest_patch_on_branch) == id.to_string() | ||
| 198 | ) { return vec![]; } | ||
| 199 | Some(latest_commit) | ||
| 200 | }, | ||
| 201 | }; | ||
| 202 | |||
| 203 | // work back thorugh commit chain until we reach a commit in our branch history (tip or ealier for rebase) | ||
| 204 | new_patches_on_branch = vec![latest_patch_on_branch.clone()]; | ||
| 205 | loop { | ||
| 206 | let next_parent_patch = new_patches_on_branch.last() | ||
| 207 | .expect("chain to contain at least latest_patch_on_main") | ||
| 208 | .clone(); | ||
| 209 | match next_parent_patch.tags.iter().find(|t|tag_is_patch_parent(t)) { | ||
| 210 | None => { | ||
| 211 | // found root patch or error | ||
| 212 | next_parent_patch.tags.iter().find(|t|tag_is_initial_commit(t)) | ||
| 213 | // tag_is_initial_commit is false when it should be true. is it always false or just the oposite? | ||
| 214 | .expect( | ||
| 215 | &format!( | ||
| 216 | "reach a patch which doesn't contain a either a tag_is_patch_parent or tag_is_initial_commit{:#?}", | ||
| 217 | &next_parent_patch | ||
| 218 | ) | ||
| 219 | ); | ||
| 220 | break; | ||
| 221 | }, | ||
| 222 | Some(t) => { | ||
| 223 | let next_patch = match patch_events.iter().find(|event|event.id.to_string() == tag_extract_value(t)) { | ||
| 224 | None => { | ||
| 225 | let patch_path = repo_dir_path.join(format!( | ||
| 226 | ".ngit/patches/{}.json", | ||
| 227 | tag_extract_value( | ||
| 228 | next_parent_patch.tags.iter().find(|t|tag_is_commit_parent(t)) | ||
| 229 | .expect("patch to always have a commit parent if it has a patch parent") | ||
| 230 | ), | ||
| 231 | )); | ||
| 232 | if patch_path.exists() { | ||
| 233 | load_event(patch_path) | ||
| 234 | .expect("patch json at location that exists loads into event") | ||
| 235 | } | ||
| 236 | else { | ||
| 237 | panic!("cannot find parent patch id {} from patch {:#?}",tag_extract_value(t), next_parent_patch); | ||
| 238 | } | ||
| 239 | }, | ||
| 240 | Some(event) => event.clone(), | ||
| 241 | }; | ||
| 242 | // if reached current tip - break | ||
| 243 | if latest_commit.is_some() && patch_is_commit( | ||
| 244 | &next_patch, | ||
| 245 | latest_commit.unwrap(), | ||
| 246 | ) { break; } | ||
| 247 | // detect rebase | ||
| 248 | if commit_ids_in_branch.iter().any(|id| | ||
| 249 | patch_commit_id(&next_patch) == id.to_string() | ||
| 250 | ) { | ||
| 251 | panic!("force push detected. This branch has been force pushed since you last pulled. ngit doesnt handle this yet"); | ||
| 252 | } | ||
| 253 | // new patch | ||
| 254 | new_patches_on_branch.push(next_patch.clone()); | ||
| 255 | |||
| 256 | }, | ||
| 257 | } | ||
| 258 | } | ||
| 259 | } | ||
| 260 | // oldest first | ||
| 261 | new_patches_on_branch.reverse(); | ||
| 262 | new_patches_on_branch | ||
| 263 | } | ||