upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/fetch_pull_push.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2023-05-21 11:21:36 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2023-05-21 11:21:36 +0000
commit8d211bad54d15208caeea093329acb7f8273944c (patch)
tree1675a08650adf6b3d510c0a7a00d843c43ba3822 /src/fetch_pull_push.rs
parent6f44c872a347907737cc1d2f9e49da201142044e (diff)
fech_pull_push
Diffstat (limited to 'src/fetch_pull_push.rs')
-rw-r--r--src/fetch_pull_push.rs369
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 @@
1use std::{env::current_dir, path::PathBuf, vec};
2
3use dialoguer::{Confirm, theme::ColorfulTheme};
4use git2::{BranchType};
5use nostr::{Keys};
6use nostr_sdk::blocking::Client;
7
8use 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
11pub 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}