diff options
Diffstat (limited to 'src/fetch_pull_push.rs')
| -rw-r--r-- | src/fetch_pull_push.rs | 369 |
1 files changed, 369 insertions, 0 deletions
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 @@ | |||
| 1 | use std::{env::current_dir, path::PathBuf, vec}; | ||
| 2 | |||
| 3 | use dialoguer::{Confirm, theme::ColorfulTheme}; | ||
| 4 | use git2::{BranchType}; | ||
| 5 | use nostr::{Keys}; | ||
| 6 | use nostr_sdk::blocking::Client; | ||
| 7 | |||
| 8 | 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}}; | ||
| 9 | |||
| 10 | /// will only pull if no rebase required or push if no downstream conflicts detected | ||
| 11 | pub fn fetch_pull_push( | ||
| 12 | keys: Option<&Keys>, | ||
| 13 | pull: bool, | ||
| 14 | push: bool, | ||
| 15 | proposed_branch_to_pull: Option<String>, | ||
| 16 | clone: bool, | ||
| 17 | repo_dir_path: Option<PathBuf>, | ||
| 18 | client: Option<Client>, | ||
| 19 | ) -> BranchRefs { | ||
| 20 | |||
| 21 | let repo_dir_path = match repo_dir_path { | ||
| 22 | None => current_dir().unwrap(), | ||
| 23 | Some(p) => p, | ||
| 24 | }; | ||
| 25 | |||
| 26 | let git_repo = git2::Repository::open(&repo_dir_path) | ||
| 27 | .expect("git repo not initialized. run ngit init first"); | ||
| 28 | |||
| 29 | if !repo_dir_path.join(".ngit").is_dir() { | ||
| 30 | panic!("ngit not initialised. Run 'ngit init' first..."); | ||
| 31 | } | ||
| 32 | |||
| 33 | let repo_has_no_commits = git_repo.branches(Some(BranchType::Local)) | ||
| 34 | .expect("git_repo.branches to not error even if its a blank repo") | ||
| 35 | .count() == 0; | ||
| 36 | |||
| 37 | // check whether we already have a local branch mapped to proposed_branch_to_pull | ||
| 38 | let proposed_branch_to_pull = match proposed_branch_to_pull { | ||
| 39 | // it was never specified | ||
| 40 | None => None, | ||
| 41 | Some(id) => { | ||
| 42 | let branch_id = get_branch_event_from_user_input( | ||
| 43 | &Some(id), | ||
| 44 | &BranchRefs::new( | ||
| 45 | vec![], | ||
| 46 | repo_dir_path.clone(), | ||
| 47 | ), | ||
| 48 | &repo_dir_path, | ||
| 49 | ).id.to_string(); | ||
| 50 | match RepoConfig::open(&repo_dir_path).branch_name_from_id(&branch_id) { | ||
| 51 | // we dont have a mapped local branch | ||
| 52 | None => Some(branch_id), | ||
| 53 | // we have a branch mapping. check it out and do a normal pull | ||
| 54 | Some(name) => { | ||
| 55 | checkout_branch_from_name( | ||
| 56 | &git_repo, | ||
| 57 | name, | ||
| 58 | ); | ||
| 59 | None | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | }; | ||
| 64 | |||
| 65 | let branch_name = if clone || repo_has_no_commits || proposed_branch_to_pull.is_some() { | ||
| 66 | None | ||
| 67 | } else { | ||
| 68 | // no commits | ||
| 69 | if git_repo.branches(Some(BranchType::Local)) | ||
| 70 | .expect("git_repo.branches to not error even if its a blank repo") | ||
| 71 | .count() == 0 { | ||
| 72 | panic!("There are no branches. you should use clone instead") | ||
| 73 | } | ||
| 74 | let head = git_repo.head() | ||
| 75 | .expect("git_repo returns head"); | ||
| 76 | if !head.is_branch() { | ||
| 77 | // TODO: fetch should still work here? | ||
| 78 | panic!("check out a branch to continue. you have an another object checked out such as a tag or commit."); | ||
| 79 | } | ||
| 80 | Some( | ||
| 81 | head.shorthand() | ||
| 82 | .expect("head is branch so head.shortand() should retunr branch name") | ||
| 83 | .to_string() | ||
| 84 | ) | ||
| 85 | }; | ||
| 86 | |||
| 87 | let repo = Repo::open(&repo_dir_path); | ||
| 88 | |||
| 89 | let new_commits_to_push = | ||
| 90 | if clone || repo_has_no_commits || proposed_branch_to_pull.is_some() { vec![] } | ||
| 91 | else { | ||
| 92 | find_commits_ahead( | ||
| 93 | &git_repo, | ||
| 94 | &repo_dir_path, | ||
| 95 | &branch_name.clone() | ||
| 96 | .expect("branch_name to always be defined (clone and pull_new_branch do not reach here)"), | ||
| 97 | ) | ||
| 98 | } | ||
| 99 | ; | ||
| 100 | |||
| 101 | let mut cfg = load_config(); | ||
| 102 | |||
| 103 | let keys = match keys { | ||
| 104 | None => { | ||
| 105 | match get_stored_keys(&mut cfg) { | ||
| 106 | None => { | ||
| 107 | if push { get_or_generate_keys(&mut cfg) } | ||
| 108 | else {Keys::generate() } | ||
| 109 | } | ||
| 110 | Some(k) => k.clone(), | ||
| 111 | } | ||
| 112 | }, | ||
| 113 | Some(k) => k.clone(), | ||
| 114 | }; | ||
| 115 | |||
| 116 | let client = match client { | ||
| 117 | None => create_client(&keys, repo.relays.clone()) | ||
| 118 | .expect("create_client returns client"), | ||
| 119 | Some(client) => client, | ||
| 120 | }; | ||
| 121 | |||
| 122 | let mut branch_refs = get_branch_refs(&repo, &client, &repo_dir_path); | ||
| 123 | |||
| 124 | let branch_id:String = | ||
| 125 | if clone || repo_has_no_commits { repo.id.to_string() } | ||
| 126 | else if proposed_branch_to_pull.is_some() { | ||
| 127 | get_unmapped_branch_event_from_user_input( | ||
| 128 | &proposed_branch_to_pull, | ||
| 129 | &branch_refs, | ||
| 130 | &repo_dir_path, | ||
| 131 | ).id.to_string() | ||
| 132 | } | ||
| 133 | else { | ||
| 134 | let name = &branch_name.clone() | ||
| 135 | .expect("branch_name to always be defined (clone and pull_new_branch do not reach here)"); | ||
| 136 | |||
| 137 | match RepoConfig::open(&repo_dir_path) | ||
| 138 | .branch_id_from_name( | ||
| 139 | &name, | ||
| 140 | ) { | ||
| 141 | None => { | ||
| 142 | if push { | ||
| 143 | create_branch_and_pr( | ||
| 144 | &name, | ||
| 145 | new_commits_to_push.len(), | ||
| 146 | &repo_dir_path, | ||
| 147 | &repo, | ||
| 148 | &mut branch_refs, | ||
| 149 | &keys, | ||
| 150 | &mut cfg, | ||
| 151 | &client, | ||
| 152 | ) | ||
| 153 | } else { | ||
| 154 | println!("branch '{}' hasn't been pushed!",&name); | ||
| 155 | return branch_refs; | ||
| 156 | } | ||
| 157 | }, | ||
| 158 | Some(id) => id.clone(), | ||
| 159 | } | ||
| 160 | } | ||
| 161 | ; | ||
| 162 | |||
| 163 | let mut patches = get_updates_of_patches( | ||
| 164 | &client, | ||
| 165 | &mut branch_refs, | ||
| 166 | &git_repo, | ||
| 167 | &repo_dir_path, | ||
| 168 | &branch_id, | ||
| 169 | &branch_name, | ||
| 170 | proposed_branch_to_pull.is_some(), | ||
| 171 | ); | ||
| 172 | |||
| 173 | let mut confirmed_branch_name = | ||
| 174 | if clone || repo_has_no_commits { "master/main".to_string() } | ||
| 175 | else if proposed_branch_to_pull.is_some() { | ||
| 176 | match patches.get(0) { | ||
| 177 | None => { | ||
| 178 | match RepoConfig::open(&repo_dir_path) | ||
| 179 | .branch_name_from_id( | ||
| 180 | &branch_id, | ||
| 181 | ) { | ||
| 182 | None => { | ||
| 183 | // 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. | ||
| 184 | println!("You are pulling a branch that has been merged or you have delete. ngit doesnt currently support this."); | ||
| 185 | }, | ||
| 186 | Some(name) => { | ||
| 187 | println!("exists as a local branch named '{}'. check it out and then fetch /pull.",name); | ||
| 188 | }, | ||
| 189 | } | ||
| 190 | return branch_refs; | ||
| 191 | } | ||
| 192 | Some(earliest_patch_event) => { | ||
| 193 | // create local branch from off of parent commit | ||
| 194 | create_local_branch_from_user_input( | ||
| 195 | &repo_dir_path, | ||
| 196 | &git_repo, | ||
| 197 | &branch_refs.branch_as_repo(Some(&branch_id)).name, | ||
| 198 | &tag_extract_value( | ||
| 199 | earliest_patch_event.tags.iter() | ||
| 200 | .find(|t|tag_is_commit_parent(t)) | ||
| 201 | .expect("patch event to have parent commit"), | ||
| 202 | ), | ||
| 203 | &branch_id | ||
| 204 | ) | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | else { | ||
| 209 | branch_name.clone() | ||
| 210 | .expect("branch_name to exists. clone and pull_new_branch don't reach here") | ||
| 211 | } | ||
| 212 | ; | ||
| 213 | |||
| 214 | // no patches or new commits | ||
| 215 | if patches.is_empty() | ||
| 216 | && new_commits_to_push.is_empty() | ||
| 217 | { | ||
| 218 | println!( | ||
| 219 | "branch '{}' is up-to-date{}", | ||
| 220 | &confirmed_branch_name, | ||
| 221 | if push { ". no changes to push." } | ||
| 222 | else { "!"} | ||
| 223 | ); | ||
| 224 | } | ||
| 225 | // patches with no new commits | ||
| 226 | else if new_commits_to_push.is_empty() | ||
| 227 | { | ||
| 228 | println!( | ||
| 229 | "branch '{}' {} behind{}", | ||
| 230 | &confirmed_branch_name, | ||
| 231 | &patches.len(), | ||
| 232 | if push { ". no changes to push." } | ||
| 233 | else { "!"} | ||
| 234 | ); | ||
| 235 | if pull || proposed_branch_to_pull.is_some() { | ||
| 236 | // apply patches | ||
| 237 | apply_patches( | ||
| 238 | &git_repo, | ||
| 239 | &repo_dir_path, | ||
| 240 | &mut patches, | ||
| 241 | ); | ||
| 242 | |||
| 243 | // update repo_config | ||
| 244 | let mut repo_config = RepoConfig::open(&repo_dir_path); | ||
| 245 | // update branch mapping | ||
| 246 | if clone || repo_has_no_commits { | ||
| 247 | confirmed_branch_name = git_repo.head() | ||
| 248 | .expect("we have just cloned and therefore commited to main branch so git_repo.head should not error") | ||
| 249 | .shorthand() | ||
| 250 | .expect("shorthand to be moast / main") | ||
| 251 | .to_string(); | ||
| 252 | repo_config.set_mapping(&confirmed_branch_name, &repo.id.to_string()); | ||
| 253 | } | ||
| 254 | // update branch update timestamp | ||
| 255 | match patches.last() { | ||
| 256 | Some(p) => { | ||
| 257 | repo_config.set_last_patch_update_time( | ||
| 258 | branch_id.clone(), | ||
| 259 | p.created_at.clone(), | ||
| 260 | ); | ||
| 261 | } | ||
| 262 | None => (), | ||
| 263 | }; | ||
| 264 | |||
| 265 | println!( | ||
| 266 | "branch '{}' is up-to-date!", | ||
| 267 | &confirmed_branch_name | ||
| 268 | ); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | else { | ||
| 272 | let update = format!( | ||
| 273 | "branch '{}' {} behind and {} ahead", | ||
| 274 | &confirmed_branch_name, | ||
| 275 | &patches.len(), | ||
| 276 | &new_commits_to_push.len(), | ||
| 277 | ); | ||
| 278 | // new commits and new patches | ||
| 279 | if !patches.is_empty() { | ||
| 280 | if pull { println!("{update}. TODO enable rebase option... pull to branch?"); } | ||
| 281 | else if push { println!("{update} TODO enable for push option. TODO enable rebase option... pull to branch?"); } | ||
| 282 | else { println!("{update}"); } | ||
| 283 | // there have been 3 more commits on the main branch. would you like to rebase before pushing your new branch? | ||
| 284 | // there has been 1 commit(s) the branch you are pushing 'feat:add-stuff'. how would you like to proceed? | ||
| 285 | // [ ] rebase my commits | ||
| 286 | // [ ] ignore commit(s) | ||
| 287 | |||
| 288 | } | ||
| 289 | // new commits with no patches | ||
| 290 | else { | ||
| 291 | println!("{update}"); | ||
| 292 | if push { | ||
| 293 | if Confirm::with_theme(&ColorfulTheme::default()) | ||
| 294 | .with_prompt(format!( | ||
| 295 | "push {} commits on the '{}' branch?", | ||
| 296 | &new_commits_to_push.len(), | ||
| 297 | &confirmed_branch_name | ||
| 298 | )) | ||
| 299 | .default(true) | ||
| 300 | .interact() | ||
| 301 | .unwrap() | ||
| 302 | { | ||
| 303 | // get keys | ||
| 304 | let mut cfg = load_config(); | ||
| 305 | let keys = get_or_generate_keys(&mut cfg); | ||
| 306 | |||
| 307 | // check permission | ||
| 308 | let groups = Groups::new(); | ||
| 309 | let maintainers = groups.by_event_id( | ||
| 310 | repo.maintainers_group.get_first_active_group() | ||
| 311 | .expect("maintainers_group will never be null") | ||
| 312 | ) | ||
| 313 | .expect("always will have the maintainers_group initialisaiton event cached") | ||
| 314 | .members(); | ||
| 315 | if maintainers.iter().any(|k| keys.public_key() == **k) { | ||
| 316 | println!( | ||
| 317 | "you are a repo maintainer and have the permission to push to '{}'!", | ||
| 318 | &confirmed_branch_name, | ||
| 319 | ) | ||
| 320 | } else { | ||
| 321 | panic!( | ||
| 322 | "You are not a repo maintainer so you don't have permission to push to '{}' branch :(", | ||
| 323 | &confirmed_branch_name, | ||
| 324 | ); | ||
| 325 | } | ||
| 326 | create_and_broadcast_patches_from_oid( | ||
| 327 | new_commits_to_push, | ||
| 328 | &git_repo, | ||
| 329 | &repo_dir_path, | ||
| 330 | &repo, | ||
| 331 | &branch_id, | ||
| 332 | &keys, | ||
| 333 | ); | ||
| 334 | } | ||
| 335 | } | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | // there have been 3 more commits on the main branch. would you like to rebase before pushing your new branch? | ||
| 340 | // there has been 1 commit(s) the branch you are pushing 'feat:add-stuff'. how would you like to proceed? | ||
| 341 | // [ ] rebase my commits | ||
| 342 | // [ ] ignore commit(s) | ||
| 343 | |||
| 344 | |||
| 345 | // let ngit_path = repo_dir_path.join(".ngit"); | ||
| 346 | // // CURRENTLY UNUSED identify new merges | ||
| 347 | // let new_merge_ids: Vec<&String> = branch_refs.merged_branches_ids | ||
| 348 | // .iter() | ||
| 349 | // .filter(|id| | ||
| 350 | // ngit_path.join(format!("merges/{}.json",id)).exists() | ||
| 351 | // ) | ||
| 352 | // .collect(); | ||
| 353 | // // TODO: identify new PullRequests to report | ||
| 354 | |||
| 355 | // Non closed PRs and branches | ||
| 356 | // TODO add a status-update custom tag for so PRs can be marked as closed or reopened. | ||
| 357 | // then we can gather status updates and filter out closed branches and build open one. | ||
| 358 | // merge - commit, from-branch, to-branch | ||
| 359 | |||
| 360 | // find patches | ||
| 361 | // get latest chain of patches on main | ||
| 362 | |||
| 363 | // identify merged branches | ||
| 364 | // will there always be a pull request for a branch? | ||
| 365 | |||
| 366 | // get patches from maitainers or branches merged by maintainers and permission groups for these branches | ||
| 367 | |||
| 368 | branch_refs | ||
| 369 | } | ||