From 0067804cc00e94ce2b7043e67f9ff50968525479 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Sun, 21 May 2023 11:14:47 +0000 Subject: v0.0.1-alpha funcs --- src/funcs/get_updates_of_patches.rs | 263 ++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 src/funcs/get_updates_of_patches.rs (limited to 'src/funcs/get_updates_of_patches.rs') 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 @@ +use std::{path::PathBuf, str::FromStr}; + +use git2::Repository; +use nostr::{Event, Filter, EventId}; +use nostr_sdk::blocking::Client; + +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}; + + +/// ancessor patch events first +pub fn get_updates_of_patches ( + client: &Client, + branch_refs: &mut BranchRefs, + git_repo: &Repository, + repo_dir_path: &PathBuf, + branch_id: &String, + branch_name: &Option, + pull_new_branch: bool, +) -> Vec { + + let repo_config = RepoConfig::open(repo_dir_path); + let last_patch_timestamp = repo_config.last_patch_update_time(branch_id.clone()); + + // create direct patches filter + let direct_patches_filter = Filter::new() + .event( + EventId::from_str(branch_id) + .expect("branch_id to render as EventId") + ) + .kinds(vec![Kind::Patch.into_sdk_custom_kind()]); + + let mut filters = vec![ + match &last_patch_timestamp { + None => direct_patches_filter, + Some(timestamp) => { + direct_patches_filter.since(timestamp.clone()) + } + } + ]; + + // get maintainers group + if branch_refs.maintainers_group(Some(&branch_id)).is_none() { + // fetch branch mantainers group and check again + client.add_relays( + branch_refs.branch_as_repo(Some(branch_id)) + .relays + .clone().iter().map(|url| (url, None)).collect() + ) + .expect("branch relays to be added to client"); + let mut group_events = client.get_events_of( + vec![ + // use the opportunity to get all the remaining groups + Filter::new().ids(branch_refs.group_ids_for_branches_without_cached_groups()), + ], + None, + ) + .expect("get_events_of to not return an error"); + group_events.sort(); + group_events.dedup(); + branch_refs.updates(group_events); + } + + // create indirect pacthes filter + let merges_into_branch: Vec = branch_refs.merges.iter().filter(|event| + // merged into branch + event.tags.iter().any(|t| + tag_is_branch(t) + && tag_extract_value(t) == branch_id.clone() + ) + // merge timestamp is after last_patch_timestamp - we already have patches before this date + && match &last_patch_timestamp { + None => true, + Some(timestamp) => timestamp < &event.created_at + } + // author is member of branch maintainers group + && branch_refs.is_authorized(Some(branch_id), &event.pubkey) + .expect("found group event for branch after checking on speficied relays") + ).map(|e|e.clone()) + .collect(); + + if !merges_into_branch.is_empty() { + filters.push( + // ids for all patches referenced in merges + Filter::new() + .ids( + merges_into_branch.iter().flat_map(|event| + event.tags.iter() + .filter(|t| tag_is_patch(t)) + .map(|t| tag_extract_value(t).clone()) + .collect::>() + ) + .collect::>() + ) + // .kinds(vec![Kind::Patch.into_sdk_custom_kind()]) + ) + } + + // find patch events + let mut patch_events: Vec = client.get_events_of( + filters, + None, + ) + .expect("get_events_of to not return an error when looking for patches"); + + patch_events.sort(); + patch_events.dedup(); + + // find patch tip on branch + let latest_patch_on_branch = match find_latest_patch( + &branch_id, + &patch_events, + &merges_into_branch, + &branch_refs, + &repo_dir_path, + ) { + // no patches return empty vector + None => { return vec![] }, // for pull_new_branch do we set the branch to the latest commit referneced even if we have it? + Some(event) => event, + }; + + let mut new_patches_on_branch = vec![]; + // for pull_new_branch - cycle through patch parents until we find any patch that exists in our commit history + if pull_new_branch { + let mut patch_event_id = latest_patch_on_branch.id.to_string(); + let mut patch_commit_id = tag_extract_value( + latest_patch_on_branch.tags.iter().find(|t|tag_is_commit(t)) + .expect("all patch events to have a commit tag") + ); + + loop { + let patch = match patch_events.iter().find(|p| p.id.to_string() == patch_event_id.clone()) { + // patch event found in patch_events + Some(patch) => patch, + None => { + // loop for parent locally + if repo_dir_path.join(format!( + ".ngit/patches/{}.json", + patch_commit_id, + )).exists() { + // break out of loop when we identify the commit where the branch begins + break + } + else { + panic!("cannot find parent patch locally or in patch_events. This will fail if the branch does not share a commit with main / master") + } + } + }; + // add patch to list of patches to apply to new branch + new_patches_on_branch.push(patch.clone()); + // prepare loop for next patch - set patch_event_id to current patches parent + patch_event_id = tag_extract_value( + patch.tags.iter().find(|t|tag_is_patch_parent(t)) + .expect("patch to always have a patch parent.") + ); + patch_commit_id = tag_extract_value( + patch.tags.iter().find(|t|tag_is_commit_parent(t)) + .expect("patch to always have a commit parent. This will fail if the branch does not share a commit with main / master") + ); + }; + } + + // 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) + else { + // revwalk through branch to identify forced push + let mut revwalk = git_repo.revwalk() + .expect("revwalk to not error on git_repo"); + match &branch_name { + Some(name) => { + revwalk.push( + git_repo.find_branch( + name.as_str(), + git2::BranchType::Local + ) + .expect("branch found from the branch_name") + .get() + .peel_to_commit() + .expect("branch reference to peel back to a commit") + .id() + ) + .expect("revwalk push_glob(branch_name) not to error if branch name is not None"); + } + None => (), + } + let commit_ids_in_branch: Vec = if branch_name.is_none() { vec![] } else { + revwalk.map(|oid| + oid + .expect("revwalk to produce oids without error") + .to_string() + ).collect() + }; + + let latest_commit: Option<&String> = match commit_ids_in_branch.get(0) { + None => None, + Some(latest_commit) => { + // return empty if latest patch is in current chain + if commit_ids_in_branch.iter().any(|id| + patch_commit_id(&latest_patch_on_branch) == id.to_string() + ) { return vec![]; } + Some(latest_commit) + }, + }; + + // work back thorugh commit chain until we reach a commit in our branch history (tip or ealier for rebase) + new_patches_on_branch = vec![latest_patch_on_branch.clone()]; + loop { + let next_parent_patch = new_patches_on_branch.last() + .expect("chain to contain at least latest_patch_on_main") + .clone(); + match next_parent_patch.tags.iter().find(|t|tag_is_patch_parent(t)) { + None => { + // found root patch or error + next_parent_patch.tags.iter().find(|t|tag_is_initial_commit(t)) + // tag_is_initial_commit is false when it should be true. is it always false or just the oposite? + .expect( + &format!( + "reach a patch which doesn't contain a either a tag_is_patch_parent or tag_is_initial_commit{:#?}", + &next_parent_patch + ) + ); + break; + }, + Some(t) => { + let next_patch = match patch_events.iter().find(|event|event.id.to_string() == tag_extract_value(t)) { + None => { + let patch_path = repo_dir_path.join(format!( + ".ngit/patches/{}.json", + tag_extract_value( + next_parent_patch.tags.iter().find(|t|tag_is_commit_parent(t)) + .expect("patch to always have a commit parent if it has a patch parent") + ), + )); + if patch_path.exists() { + load_event(patch_path) + .expect("patch json at location that exists loads into event") + } + else { + panic!("cannot find parent patch id {} from patch {:#?}",tag_extract_value(t), next_parent_patch); + } + }, + Some(event) => event.clone(), + }; + // if reached current tip - break + if latest_commit.is_some() && patch_is_commit( + &next_patch, + latest_commit.unwrap(), + ) { break; } + // detect rebase + if commit_ids_in_branch.iter().any(|id| + patch_commit_id(&next_patch) == id.to_string() + ) { + panic!("force push detected. This branch has been force pushed since you last pulled. ngit doesnt handle this yet"); + } + // new patch + new_patches_on_branch.push(next_patch.clone()); + + }, + } + } + } + // oldest first + new_patches_on_branch.reverse(); + new_patches_on_branch +} -- cgit v1.2.3