From 8d211bad54d15208caeea093329acb7f8273944c Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Sun, 21 May 2023 11:21:36 +0000 Subject: fech_pull_push --- src/fetch_pull_push.rs | 369 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 src/fetch_pull_push.rs (limited to 'src') diff --git a/src/fetch_pull_push.rs b/src/fetch_pull_push.rs new file mode 100644 index 0000000..a1faece --- /dev/null +++ b/src/fetch_pull_push.rs @@ -0,0 +1,369 @@ +use std::{env::current_dir, path::PathBuf, vec}; + +use dialoguer::{Confirm, theme::ColorfulTheme}; +use git2::{BranchType}; +use nostr::{Keys}; +use nostr_sdk::blocking::Client; + +use crate::{groups::groups::Groups, repos::repo::Repo, utils::{create_client, get_stored_keys, get_or_generate_keys}, config::load_config, repo_config::RepoConfig, funcs::{find_commits_ahead::find_commits_ahead, apply_patches::apply_patches, get_updates_of_patches::get_updates_of_patches, create_patches::create_and_broadcast_patches_from_oid, create_branch_and_pr::create_branch_and_pr, get_branch_event_from_user_input::{get_unmapped_branch_event_from_user_input,get_branch_event_from_user_input}, create_local_branch_from_user_input::create_local_branch_from_user_input, checkout_branch::{checkout_branch_from_name}}, branch_refs::{get_branch_refs, BranchRefs}, ngit_tag::{tag_is_commit_parent, tag_extract_value}}; + +/// will only pull if no rebase required or push if no downstream conflicts detected +pub fn fetch_pull_push( + keys: Option<&Keys>, + pull: bool, + push: bool, + proposed_branch_to_pull: Option, + clone: bool, + repo_dir_path: Option, + client: Option, +) -> BranchRefs { + + let repo_dir_path = match repo_dir_path { + None => current_dir().unwrap(), + Some(p) => p, + }; + + let git_repo = git2::Repository::open(&repo_dir_path) + .expect("git repo not initialized. run ngit init first"); + + if !repo_dir_path.join(".ngit").is_dir() { + panic!("ngit not initialised. Run 'ngit init' first..."); + } + + let repo_has_no_commits = git_repo.branches(Some(BranchType::Local)) + .expect("git_repo.branches to not error even if its a blank repo") + .count() == 0; + + // check whether we already have a local branch mapped to proposed_branch_to_pull + let proposed_branch_to_pull = match proposed_branch_to_pull { + // it was never specified + None => None, + Some(id) => { + let branch_id = get_branch_event_from_user_input( + &Some(id), + &BranchRefs::new( + vec![], + repo_dir_path.clone(), + ), + &repo_dir_path, + ).id.to_string(); + match RepoConfig::open(&repo_dir_path).branch_name_from_id(&branch_id) { + // we dont have a mapped local branch + None => Some(branch_id), + // we have a branch mapping. check it out and do a normal pull + Some(name) => { + checkout_branch_from_name( + &git_repo, + name, + ); + None + } + } + } + }; + + let branch_name = if clone || repo_has_no_commits || proposed_branch_to_pull.is_some() { + None + } else { + // no commits + if git_repo.branches(Some(BranchType::Local)) + .expect("git_repo.branches to not error even if its a blank repo") + .count() == 0 { + panic!("There are no branches. you should use clone instead") + } + let head = git_repo.head() + .expect("git_repo returns head"); + if !head.is_branch() { + // TODO: fetch should still work here? + panic!("check out a branch to continue. you have an another object checked out such as a tag or commit."); + } + Some( + head.shorthand() + .expect("head is branch so head.shortand() should retunr branch name") + .to_string() + ) + }; + + let repo = Repo::open(&repo_dir_path); + + let new_commits_to_push = + if clone || repo_has_no_commits || proposed_branch_to_pull.is_some() { vec![] } + else { + find_commits_ahead( + &git_repo, + &repo_dir_path, + &branch_name.clone() + .expect("branch_name to always be defined (clone and pull_new_branch do not reach here)"), + ) + } + ; + + let mut cfg = load_config(); + + let keys = match keys { + None => { + match get_stored_keys(&mut cfg) { + None => { + if push { get_or_generate_keys(&mut cfg) } + else {Keys::generate() } + } + Some(k) => k.clone(), + } + }, + Some(k) => k.clone(), + }; + + let client = match client { + None => create_client(&keys, repo.relays.clone()) + .expect("create_client returns client"), + Some(client) => client, + }; + + let mut branch_refs = get_branch_refs(&repo, &client, &repo_dir_path); + + let branch_id:String = + if clone || repo_has_no_commits { repo.id.to_string() } + else if proposed_branch_to_pull.is_some() { + get_unmapped_branch_event_from_user_input( + &proposed_branch_to_pull, + &branch_refs, + &repo_dir_path, + ).id.to_string() + } + else { + let name = &branch_name.clone() + .expect("branch_name to always be defined (clone and pull_new_branch do not reach here)"); + + match RepoConfig::open(&repo_dir_path) + .branch_id_from_name( + &name, + ) { + None => { + if push { + create_branch_and_pr( + &name, + new_commits_to_push.len(), + &repo_dir_path, + &repo, + &mut branch_refs, + &keys, + &mut cfg, + &client, + ) + } else { + println!("branch '{}' hasn't been pushed!",&name); + return branch_refs; + } + }, + Some(id) => id.clone(), + } + } + ; + + let mut patches = get_updates_of_patches( + &client, + &mut branch_refs, + &git_repo, + &repo_dir_path, + &branch_id, + &branch_name, + proposed_branch_to_pull.is_some(), + ); + + let mut confirmed_branch_name = + if clone || repo_has_no_commits { "master/main".to_string() } + else if proposed_branch_to_pull.is_some() { + match patches.get(0) { + None => { + match RepoConfig::open(&repo_dir_path) + .branch_name_from_id( + &branch_id, + ) { + None => { + // TODO you should still be able to check it out - find the easliest commit, check it out as branch, find the latest commit and set the branch to that commit. + println!("You are pulling a branch that has been merged or you have delete. ngit doesnt currently support this."); + }, + Some(name) => { + println!("exists as a local branch named '{}'. check it out and then fetch /pull.",name); + }, + } + return branch_refs; + } + Some(earliest_patch_event) => { + // create local branch from off of parent commit + create_local_branch_from_user_input( + &repo_dir_path, + &git_repo, + &branch_refs.branch_as_repo(Some(&branch_id)).name, + &tag_extract_value( + earliest_patch_event.tags.iter() + .find(|t|tag_is_commit_parent(t)) + .expect("patch event to have parent commit"), + ), + &branch_id + ) + } + } + } + else { + branch_name.clone() + .expect("branch_name to exists. clone and pull_new_branch don't reach here") + } + ; + + // no patches or new commits + if patches.is_empty() + && new_commits_to_push.is_empty() + { + println!( + "branch '{}' is up-to-date{}", + &confirmed_branch_name, + if push { ". no changes to push." } + else { "!"} + ); + } + // patches with no new commits + else if new_commits_to_push.is_empty() + { + println!( + "branch '{}' {} behind{}", + &confirmed_branch_name, + &patches.len(), + if push { ". no changes to push." } + else { "!"} + ); + if pull || proposed_branch_to_pull.is_some() { + // apply patches + apply_patches( + &git_repo, + &repo_dir_path, + &mut patches, + ); + + // update repo_config + let mut repo_config = RepoConfig::open(&repo_dir_path); + // update branch mapping + if clone || repo_has_no_commits { + confirmed_branch_name = git_repo.head() + .expect("we have just cloned and therefore commited to main branch so git_repo.head should not error") + .shorthand() + .expect("shorthand to be moast / main") + .to_string(); + repo_config.set_mapping(&confirmed_branch_name, &repo.id.to_string()); + } + // update branch update timestamp + match patches.last() { + Some(p) => { + repo_config.set_last_patch_update_time( + branch_id.clone(), + p.created_at.clone(), + ); + } + None => (), + }; + + println!( + "branch '{}' is up-to-date!", + &confirmed_branch_name + ); + } + } + else { + let update = format!( + "branch '{}' {} behind and {} ahead", + &confirmed_branch_name, + &patches.len(), + &new_commits_to_push.len(), + ); + // new commits and new patches + if !patches.is_empty() { + if pull { println!("{update}. TODO enable rebase option... pull to branch?"); } + else if push { println!("{update} TODO enable for push option. TODO enable rebase option... pull to branch?"); } + else { println!("{update}"); } + // there have been 3 more commits on the main branch. would you like to rebase before pushing your new branch? + // there has been 1 commit(s) the branch you are pushing 'feat:add-stuff'. how would you like to proceed? + // [ ] rebase my commits + // [ ] ignore commit(s) + + } + // new commits with no patches + else { + println!("{update}"); + if push { + if Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(format!( + "push {} commits on the '{}' branch?", + &new_commits_to_push.len(), + &confirmed_branch_name + )) + .default(true) + .interact() + .unwrap() + { + // get keys + let mut cfg = load_config(); + let keys = get_or_generate_keys(&mut cfg); + + // check permission + let groups = Groups::new(); + let maintainers = groups.by_event_id( + repo.maintainers_group.get_first_active_group() + .expect("maintainers_group will never be null") + ) + .expect("always will have the maintainers_group initialisaiton event cached") + .members(); + if maintainers.iter().any(|k| keys.public_key() == **k) { + println!( + "you are a repo maintainer and have the permission to push to '{}'!", + &confirmed_branch_name, + ) + } else { + panic!( + "You are not a repo maintainer so you don't have permission to push to '{}' branch :(", + &confirmed_branch_name, + ); + } + create_and_broadcast_patches_from_oid( + new_commits_to_push, + &git_repo, + &repo_dir_path, + &repo, + &branch_id, + &keys, + ); + } + } + } + } + + // there have been 3 more commits on the main branch. would you like to rebase before pushing your new branch? + // there has been 1 commit(s) the branch you are pushing 'feat:add-stuff'. how would you like to proceed? + // [ ] rebase my commits + // [ ] ignore commit(s) + + + // let ngit_path = repo_dir_path.join(".ngit"); + // // CURRENTLY UNUSED identify new merges + // let new_merge_ids: Vec<&String> = branch_refs.merged_branches_ids + // .iter() + // .filter(|id| + // ngit_path.join(format!("merges/{}.json",id)).exists() + // ) + // .collect(); + // // TODO: identify new PullRequests to report + + // Non closed PRs and branches + // TODO add a status-update custom tag for so PRs can be marked as closed or reopened. + // then we can gather status updates and filter out closed branches and build open one. + // merge - commit, from-branch, to-branch + + // find patches + // get latest chain of patches on main + + // identify merged branches + // will there always be a pull request for a branch? + + // get patches from maitainers or branches merged by maintainers and permission groups for these branches + + branch_refs +} -- cgit v1.2.3