upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/bin/ngit/sub_commands/send.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/ngit/sub_commands/send.rs')
-rw-r--r--src/bin/ngit/sub_commands/send.rs275
1 files changed, 244 insertions, 31 deletions
diff --git a/src/bin/ngit/sub_commands/send.rs b/src/bin/ngit/sub_commands/send.rs
index 609812b..835153e 100644
--- a/src/bin/ngit/sub_commands/send.rs
+++ b/src/bin/ngit/sub_commands/send.rs
@@ -1,16 +1,27 @@
1use std::{path::Path, str::FromStr}; 1use std::{path::Path, str::FromStr, thread, time::Duration};
2 2
3use anyhow::{Context, Result, bail}; 3use anyhow::{Context, Result, bail};
4use console::Style; 4use console::Style;
5use ngit::{ 5use ngit::{
6 cli_interactor::{PromptChoiceParms, multi_select_with_custom_value},
6 client::{Params, send_events}, 7 client::{Params, send_events},
7 git::nostr_url::CloneUrl, 8 git::nostr_url::CloneUrl,
8 git_events::{EventRefType, KIND_PULL_REQUEST, generate_cover_letter_and_patch_events}, 9 git_events::{EventRefType, KIND_PULL_REQUEST, generate_cover_letter_and_patch_events},
9 push::push_refs_and_generate_pr_or_pr_update_event, 10 push::push_refs_and_generate_pr_or_pr_update_event,
10 repo_ref::is_grasp_server, 11 repo_ref::{
12 format_grasp_server_url_as_clone_url, format_grasp_server_url_as_relay_url,
13 is_grasp_server, normalize_grasp_server_url,
14 },
11 utils::proposal_tip_is_pr_or_pr_update, 15 utils::proposal_tip_is_pr_or_pr_update,
12}; 16};
13use nostr::{ToBech32, event::Event, nips::nip19::Nip19Event}; 17use nostr::{
18 ToBech32,
19 event::Event,
20 nips::{
21 nip01::Coordinate,
22 nip19::{Nip19Coordinate, Nip19Event},
23 },
24};
14use nostr_sdk::hashes::sha1::Hash as Sha1Hash; 25use nostr_sdk::hashes::sha1::Hash as Sha1Hash;
15 26
16use crate::{ 27use crate::{
@@ -179,7 +190,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re
179 None 190 None
180 }; 191 };
181 192
182 let (signer, user_ref, _) = login::login_or_signup( 193 let (signer, mut user_ref, _) = login::login_or_signup(
183 &Some(&git_repo), 194 &Some(&git_repo),
184 &extract_signer_cli_arguments(cli_args).unwrap_or(None), 195 &extract_signer_cli_arguments(cli_args).unwrap_or(None),
185 &cli_args.password, 196 &cli_args.password,
@@ -194,20 +205,55 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re
194 commits.reverse(); 205 commits.reverse();
195 206
196 let events = if as_pr { 207 let events = if as_pr {
208 let mut to_try = vec![];
209 let mut tried = vec![];
197 let repo_grasps = repo_ref.grasp_servers(); 210 let repo_grasps = repo_ref.grasp_servers();
198 let repo_grasp_clone_urls: Vec<String> = repo_ref 211 // if the user already has a fork, or is a maintainer, use those git servers
199 .git_server 212 let mut user_repo_ref = get_repo_ref_from_cache(
200 .iter() 213 Some(git_repo_path),
201 .filter(|s| is_grasp_server(s, &repo_grasps)) 214 &Nip19Coordinate {
202 .cloned() 215 coordinate: Coordinate {
203 .collect(); 216 kind: nostr::event::Kind::GitRepoAnnouncement,
204 if repo_grasp_clone_urls.is_empty() { 217 public_key: user_ref.public_key,
218 identifier: repo_ref.identifier.clone(),
219 },
220 relays: vec![],
221 },
222 )
223 .await
224 .ok();
225 if let Some(user_repo_ref) = &user_repo_ref {
226 for url in &user_repo_ref.git_server {
227 if CloneUrl::from_str(url).is_ok() {
228 to_try.push(url.clone());
229 }
230 }
231 }
232 if !to_try.is_empty() || !repo_grasps.is_empty() {
233 println!(
234 "pushing proposal refs to {}",
235 if repo_ref.maintainers.contains(&user_ref.public_key) {
236 "repository git servers"
237 } else if to_try.is_empty() {
238 "repository grasp servers"
239 } else if repo_grasps.is_empty() {
240 "the git servers listed in your fork"
241 } else {
242 "the git servers listed in your fork and repository grasp servers"
243 }
244 );
245 } else {
205 println!( 246 println!(
206 "The repository doesn't list a grasp server which would otherwise be used to submit your proposal as nostr Pull Request." 247 "The repository doesn't list a grasp server which would otherwise be used to submit your proposal as nostr Pull Request."
207 ); 248 );
208 } 249 }
209 let mut to_try = repo_grasp_clone_urls.clone(); 250 // also use repo grasp servers
210 let mut tried = vec![]; 251 for url in &repo_ref.git_server {
252 if is_grasp_server(url, &repo_grasps) && !to_try.contains(url) {
253 to_try.push(url.clone());
254 }
255 }
256
211 let mut git_ref = None; 257 let mut git_ref = None;
212 loop { 258 loop {
213 let (events, _server_responses) = push_refs_and_generate_pr_or_pr_update_event( 259 let (events, _server_responses) = push_refs_and_generate_pr_or_pr_update_event(
@@ -217,7 +263,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re
217 &user_ref, 263 &user_ref,
218 root_proposal.as_ref(), 264 root_proposal.as_ref(),
219 &cover_letter_title_description, 265 &cover_letter_title_description,
220 &repo_grasp_clone_urls, 266 &to_try,
221 git_ref.clone(), 267 git_ref.clone(),
222 &signer, 268 &signer,
223 &console::Term::stdout(), 269 &console::Term::stdout(),
@@ -230,27 +276,194 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs, no_fetch: bool) -> Re
230 if let Some(events) = events { 276 if let Some(events) = events {
231 break events; 277 break events;
232 } 278 }
233 let clone_url = Interactor::default() 279 // fallback to creating user personal-fork on their grasp servers
234 .input( 280 let untried_user_grasp_servers: Vec<String> = user_ref
235 PromptInputParms::default().with_prompt("git repo url with write permission"), 281 .grasp_list
236 )? 282 .urls
237 .clone(); 283 .iter()
238 if CloneUrl::from_str(&clone_url).is_ok() { 284 .map(std::string::ToString::to_string)
239 to_try.push(clone_url); 285 .filter(|g| {
240 let mut git_ref_or_branch_name = Interactor::default() 286 // is a grasp server not in list of tried
241 .input( 287 !is_grasp_server(g, &tried)
242 PromptInputParms::default() 288 })
243 .with_prompt("ref / branch name") 289 .collect();
244 .with_default(git_ref.unwrap_or("refs/nostr/<event-id>".to_string())), 290
291 if untried_user_grasp_servers.is_empty()
292 && Interactor::default().choice(
293 PromptChoiceParms::default()
294 .with_prompt("choose alternative git server")
295 .dont_report()
296 .with_choices(vec![
297 "choose grasp server(s)".to_string(),
298 "enter a git repo url with write permission".to_string(),
299 ])
300 .with_default(0),
301 )? == 1
302 {
303 loop {
304 let clone_url = Interactor::default()
305 .input(
306 PromptInputParms::default()
307 .with_prompt("git repo url with write permission"),
308 )?
309 .clone();
310 if CloneUrl::from_str(&clone_url).is_ok() {
311 to_try.push(clone_url);
312 let mut git_ref_or_branch_name = Interactor::default()
313 .input(
314 PromptInputParms::default()
315 .with_prompt("ref / branch name")
316 .with_default(
317 git_ref.unwrap_or("refs/nostr/<event-id>".to_string()),
318 ),
319 )?
320 .clone();
321 if !git_ref_or_branch_name.starts_with("refs/") {
322 git_ref_or_branch_name = format!("refs/heads/{git_ref_or_branch_name}");
323 }
324 git_ref = Some(git_ref_or_branch_name);
325 break;
326 }
327 println!("invalid clone url");
328 }
329 continue;
330 }
331
332 let mut new_grasp_server_events: Vec<Event> = vec![];
333
334 let grasp_servers = if untried_user_grasp_servers.is_empty() {
335 let default_choices: Vec<String> = client
336 .get_grasp_default_set()
337 .iter()
338 .filter(|g| !is_grasp_server(g, &tried))
339 .cloned()
340 .collect();
341 let selections = vec![true; default_choices.len()]; // all selected by default
342 let grasp_servers = multi_select_with_custom_value(
343 "grasp server(s)",
344 "grasp server",
345 default_choices,
346 selections,
347 normalize_grasp_server_url,
348 )?;
349 if grasp_servers.is_empty() {
350 // ask again
351 continue;
352 }
353 let normalised_grasp_servers: Vec<String> = grasp_servers
354 .iter()
355 .filter_map(|g| normalize_grasp_server_url(g).ok())
356 .collect();
357 // if any grasp servers not listed in user grasp list prompt to update
358 let grasp_servers_not_in_user_prefs: Vec<String> = normalised_grasp_servers
359 .iter()
360 .filter(|g| {
361 !user_ref.grasp_list.urls.contains(
362 // unwrap is safe as we constructed g
363 &nostr::Url::parse(&format_grasp_server_url_as_relay_url(g).unwrap())
364 .unwrap(),
365 )
366 })
367 .cloned()
368 .collect();
369 if !grasp_servers_not_in_user_prefs.is_empty()
370 && Interactor::default().confirm(
371 PromptConfirmParms::default()
372 .with_prompt(
373 "add these to your list of prefered grasp servers?".to_string(),
374 )
375 .with_default(true),
245 )? 376 )?
246 .clone(); 377 {
247 if !git_ref_or_branch_name.starts_with("refs/") { 378 for g in &normalised_grasp_servers {
248 git_ref_or_branch_name = format!("refs/heads/{git_ref_or_branch_name}"); 379 let as_url = nostr::Url::parse(&format_grasp_server_url_as_relay_url(g)?)?;
380 if !user_ref.grasp_list.urls.contains(&as_url) {
381 user_ref.grasp_list.urls.push(as_url);
382 }
383 }
384 new_grasp_server_events.push(user_ref.grasp_list.to_event(&signer).await?);
249 } 385 }
250 git_ref = Some(git_ref_or_branch_name); 386 normalised_grasp_servers
251 } else { 387 } else {
252 println!("invalid clone url"); 388 println!(
389 "{} personal-fork so we can push commits to your prefered grasp servers",
390 if user_repo_ref.is_some() {
391 "Updating"
392 } else {
393 "Creating a"
394 },
395 );
396 untried_user_grasp_servers
397 };
398
399 let grasp_servers_as_personal_clone_url: Vec<String> = grasp_servers
400 .iter()
401 .filter_map(|g| {
402 format_grasp_server_url_as_clone_url(
403 g,
404 &user_ref.public_key,
405 &repo_ref.identifier,
406 )
407 .ok()
408 })
409 .collect();
410
411 // create personal-fork / update existing user repo and add these grasp servers
412 let updated_user_repo_ref = {
413 if let Some(mut user_repo_ref) = user_repo_ref {
414 for g in &grasp_servers_as_personal_clone_url {
415 let _ = user_repo_ref.add_grasp_server(g);
416 }
417 user_repo_ref
418 } else {
419 // clone repo_ref and reset as personal-fork
420 let mut user_repo_ref = repo_ref.clone();
421 user_repo_ref.trusted_maintainer = user_ref.public_key;
422 user_repo_ref.maintainers = vec![user_ref.public_key];
423 user_repo_ref.git_server = vec![];
424 user_repo_ref.relays = vec![];
425 if !user_repo_ref
426 .hashtags
427 .contains(&"personal-fork".to_string())
428 {
429 user_repo_ref.hashtags.push("personal-fork".to_string());
430 }
431 user_repo_ref
432 }
433 };
434 // pubish event to my-relays and my-fork-relays
435 new_grasp_server_events.push(updated_user_repo_ref.to_event(&signer).await?);
436 send_events(
437 &client,
438 Some(git_repo_path),
439 new_grasp_server_events,
440 user_ref.relays.write(),
441 updated_user_repo_ref.relays.clone(),
442 !cli_args.disable_cli_spinners,
443 false,
444 )
445 .await?;
446 user_repo_ref = Some(updated_user_repo_ref);
447 // wait a few seconds
448 let countdown_start = 5;
449 let term = console::Term::stdout();
450 for i in (1..=countdown_start).rev() {
451 term.write_line(
452 format!(
453 "waiting {i}s grasp servers to create your repo before we push your data"
454 )
455 .as_str(),
456 )?;
457 thread::sleep(Duration::new(1, 0)); // Sleep for 1 second
458 term.clear_last_lines(1)?;
459 }
460 term.flush().unwrap(); // Ensure the output is flushed to the terminal
461
462 // add grasp servers to to_try
463 for url in grasp_servers_as_personal_clone_url {
464 to_try.push(url);
253 } 465 }
466 // the loop with continue with the grasp servers
254 } 467 }
255 } else { 468 } else {
256 let events = generate_cover_letter_and_patch_events( 469 let events = generate_cover_letter_and_patch_events(