upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/bin/ngit/sub_commands/init.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2025-05-22 17:40:07 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2025-05-22 17:41:22 +0100
commitf382b1307ea1020b7dc8fcd9c7fc02f2f612bef7 (patch)
treee50a0288c34e9a4c6830b99cf93eef301da8ff19 /src/bin/ngit/sub_commands/init.rs
parentb6407944cc8f670d33b828f7b77836ceeed2fcfa (diff)
refactor: cargo fmt
should have done it at 4dc5d0c9fb170981cf4fade5558d7cc8da404aa3
Diffstat (limited to 'src/bin/ngit/sub_commands/init.rs')
-rw-r--r--src/bin/ngit/sub_commands/init.rs158
1 files changed, 104 insertions, 54 deletions
diff --git a/src/bin/ngit/sub_commands/init.rs b/src/bin/ngit/sub_commands/init.rs
index 4f82d5e..70a2cd9 100644
--- a/src/bin/ngit/sub_commands/init.rs
+++ b/src/bin/ngit/sub_commands/init.rs
@@ -1,5 +1,9 @@
1use std::{ 1use std::{
2 collections::HashMap, process::{Command, Stdio}, str::FromStr, thread, time::Duration 2 collections::HashMap,
3 process::{Command, Stdio},
4 str::FromStr,
5 thread,
6 time::Duration,
3}; 7};
4 8
5use anyhow::{Context, Result, bail}; 9use anyhow::{Context, Result, bail};
@@ -7,14 +11,13 @@ use console::Style;
7use dialoguer::theme::{ColorfulTheme, Theme}; 11use dialoguer::theme::{ColorfulTheme, Theme};
8use ngit::{ 12use ngit::{
9 cli_interactor::{PromptChoiceParms, PromptConfirmParms, PromptMultiChoiceParms}, 13 cli_interactor::{PromptChoiceParms, PromptConfirmParms, PromptMultiChoiceParms},
10 client::{send_events, Params}, 14 client::{Params, send_events},
11 git::nostr_url::{CloneUrl, NostrUrlDecoded}, repo_ref::{extract_pks, save_repo_config_to_yaml}, 15 git::nostr_url::{CloneUrl, NostrUrlDecoded},
16 repo_ref::{extract_pks, save_repo_config_to_yaml},
12}; 17};
13use nostr::{ 18use nostr::{
14 nips::{ 19 FromBech32, PublicKey, ToBech32,
15 nip01::Coordinate, 20 nips::{nip01::Coordinate, nip19::Nip19Coordinate},
16 nip19::Nip19Coordinate,
17 }, FromBech32, PublicKey, ToBech32
18}; 21};
19use nostr_sdk::{Kind, RelayUrl, Url}; 22use nostr_sdk::{Kind, RelayUrl, Url};
20 23
@@ -25,8 +28,7 @@ use crate::{
25 git::{Repo, RepoActions, nostr_url::convert_clone_url_to_https}, 28 git::{Repo, RepoActions, nostr_url::convert_clone_url_to_https},
26 login, 29 login,
27 repo_ref::{ 30 repo_ref::{
28 RepoRef, get_repo_config_from_yaml, 31 RepoRef, get_repo_config_from_yaml, try_and_get_repo_coordinates_when_remote_unknown,
29 try_and_get_repo_coordinates_when_remote_unknown,
30 }, 32 },
31}; 33};
32 34
@@ -224,7 +226,6 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
224 args.relays.clone() 226 args.relays.clone()
225 }; 227 };
226 228
227
228 let mut blossoms_defaults = if args.blossoms.is_empty() { 229 let mut blossoms_defaults = if args.blossoms.is_empty() {
229 if let Some(repo_ref) = &repo_ref { 230 if let Some(repo_ref) = &repo_ref {
230 repo_ref 231 repo_ref
@@ -242,7 +243,6 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
242 args.blossoms.clone() 243 args.blossoms.clone()
243 }; 244 };
244 245
245
246 let selected_ngit_relays = if has_server_and_relay_flags { 246 let selected_ngit_relays = if has_server_and_relay_flags {
247 // ignore so a script running `ngit init` can contiue without prompts 247 // ignore so a script running `ngit init` can contiue without prompts
248 vec![] 248 vec![]
@@ -277,7 +277,8 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
277 // ensure ngit relays are added as git server, relay and blossom entries 277 // ensure ngit relays are added as git server, relay and blossom entries
278 for ngit_relay in &selected_ngit_relays { 278 for ngit_relay in &selected_ngit_relays {
279 if args.clone_url.is_empty() { 279 if args.clone_url.is_empty() {
280 let clone_url = format_ngit_relay_url_as_clone_url(ngit_relay, &user_ref.public_key, &identifier)?; 280 let clone_url =
281 format_ngit_relay_url_as_clone_url(ngit_relay, &user_ref.public_key, &identifier)?;
281 if !git_server_defaults.contains(&clone_url) { 282 if !git_server_defaults.contains(&clone_url) {
282 git_server_defaults.push(clone_url); 283 git_server_defaults.push(clone_url);
283 } 284 }
@@ -301,13 +302,19 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
301 } else { 302 } else {
302 false 303 false
303 }; 304 };
304 if no_state && Interactor::default().confirm( 305 if no_state
306 && Interactor::default().confirm(
305 PromptConfirmParms::default() 307 PromptConfirmParms::default()
306 .with_prompt("store state on nostr? required for nostr-permissioned git servers") 308 .with_prompt("store state on nostr? required for nostr-permissioned git servers")
307 .with_default(true), 309 .with_default(true),
308 )?{ 310 )?
311 {
309 // TODO check if ngit-relays in use and if so turn this off: 312 // TODO check if ngit-relays in use and if so turn this off:
310 if git_repo.get_git_config_item("nostr.nostate",Some(true)).unwrap_or(None).is_some() { 313 if git_repo
314 .get_git_config_item("nostr.nostate", Some(true))
315 .unwrap_or(None)
316 .is_some()
317 {
311 git_repo.remove_git_config_item("nostr.nostate", true)?; 318 git_repo.remove_git_config_item("nostr.nostate", true)?;
312 } else { 319 } else {
313 git_repo.remove_git_config_item("nostr.nostate", false)?; 320 git_repo.remove_git_config_item("nostr.nostate", false)?;
@@ -315,8 +322,16 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
315 } 322 }
316 323
317 let git_server = if args.clone_url.is_empty() { 324 let git_server = if args.clone_url.is_empty() {
318 let ngit_relay_git_servers: Vec<String> = git_server_defaults.iter().filter(|s| selected_ngit_relays.iter().any(|r|s.contains(r))).cloned().collect(); 325 let ngit_relay_git_servers: Vec<String> = git_server_defaults
319 let mut additional_server_options: Vec<String> = git_server_defaults.iter().filter(|s| ngit_relay_git_servers.iter().any(|r|s.eq(&r))).cloned().collect(); 326 .iter()
327 .filter(|s| selected_ngit_relays.iter().any(|r| s.contains(r)))
328 .cloned()
329 .collect();
330 let mut additional_server_options: Vec<String> = git_server_defaults
331 .iter()
332 .filter(|s| ngit_relay_git_servers.iter().any(|r| s.eq(&r)))
333 .cloned()
334 .collect();
320 335
321 if simple_mode && !selected_ngit_relays.is_empty() { 336 if simple_mode && !selected_ngit_relays.is_empty() {
322 if additional_server_options.is_empty() { 337 if additional_server_options.is_empty() {
@@ -348,7 +363,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
348 additional_server_options = selected; 363 additional_server_options = selected;
349 continue 364 continue
350 } 365 }
351 break selected 366 break selected;
352 }; 367 };
353 show_multi_input_prompt_success("git servers", &selected); 368 show_multi_input_prompt_success("git servers", &selected);
354 let mut combined = ngit_relay_git_servers; 369 let mut combined = ngit_relay_git_servers;
@@ -381,11 +396,17 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
381 396
382 let relays: Vec<RelayUrl> = { 397 let relays: Vec<RelayUrl> = {
383 if simple_mode { 398 if simple_mode {
384 let formatted_selected_ngit_relays: Vec<String> = selected_ngit_relays.iter() 399 let formatted_selected_ngit_relays: Vec<String> = selected_ngit_relays
400 .iter()
385 .filter_map(|r| format_ngit_relay_url_as_relay_url(r).ok()) 401 .filter_map(|r| format_ngit_relay_url_as_relay_url(r).ok())
386 .collect(); 402 .collect();
387 let mut options: Vec<String> = relay_defaults.iter() 403 let mut options: Vec<String> = relay_defaults
388 .filter(|s| !formatted_selected_ngit_relays.iter().any(|r| s.as_str() == r)) 404 .iter()
405 .filter(|s| {
406 !formatted_selected_ngit_relays
407 .iter()
408 .any(|r| s.as_str() == r)
409 })
389 .cloned() 410 .cloned()
390 .collect(); 411 .collect();
391 412
@@ -393,7 +414,11 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
393 414
394 // add fallback relays as options 415 // add fallback relays as options
395 for relay in client.get_fallback_relays().clone() { 416 for relay in client.get_fallback_relays().clone() {
396 if !options.iter().any(|r|r.contains(&relay)) && !formatted_selected_ngit_relays.iter().any(|r|relay.contains(r)) { 417 if !options.iter().any(|r| r.contains(&relay))
418 && !formatted_selected_ngit_relays
419 .iter()
420 .any(|r| relay.contains(r))
421 {
397 options.push(relay); 422 options.push(relay);
398 selections.push(selections.is_empty()); 423 selections.push(selections.is_empty());
399 } 424 }
@@ -411,11 +436,11 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
411 }, 436 },
412 )?; 437 )?;
413 show_multi_input_prompt_success("additional nostr relays", &selected); 438 show_multi_input_prompt_success("additional nostr relays", &selected);
414 selected.iter() 439 selected
440 .iter()
415 .filter_map(|r| parse_relay_url(r).ok()) 441 .filter_map(|r| parse_relay_url(r).ok())
416 .collect() 442 .collect()
417 } else { 443 } else {
418
419 let selections: Vec<bool> = vec![true; relay_defaults.len()]; 444 let selections: Vec<bool> = vec![true; relay_defaults.len()];
420 if args.relays.is_empty() { 445 if args.relays.is_empty() {
421 let selected = multi_select_with_custom_value( 446 let selected = multi_select_with_custom_value(
@@ -430,7 +455,8 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
430 }, 455 },
431 )?; 456 )?;
432 show_multi_input_prompt_success("nostr relays", &selected); 457 show_multi_input_prompt_success("nostr relays", &selected);
433 selected.iter() 458 selected
459 .iter()
434 .filter_map(|r| parse_relay_url(r).ok()) 460 .filter_map(|r| parse_relay_url(r).ok())
435 .collect() 461 .collect()
436 } else { 462 } else {
@@ -445,7 +471,9 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
445 let blossoms: Vec<Url> = { 471 let blossoms: Vec<Url> = {
446 if simple_mode || has_server_and_relay_flags { 472 if simple_mode || has_server_and_relay_flags {
447 blossoms_defaults 473 blossoms_defaults
448 .iter().filter_map(|b| Url::parse(b).ok()).collect() 474 .iter()
475 .filter_map(|b| Url::parse(b).ok())
476 .collect()
449 } else { 477 } else {
450 let selections: Vec<bool> = vec![true; blossoms_defaults.len()]; 478 let selections: Vec<bool> = vec![true; blossoms_defaults.len()];
451 if args.blossoms.is_empty() { 479 if args.blossoms.is_empty() {
@@ -463,12 +491,14 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
463 show_multi_input_prompt_success("nostr relays", &selected); 491 show_multi_input_prompt_success("nostr relays", &selected);
464 selected.iter().filter_map(|b| Url::parse(b).ok()).collect() 492 selected.iter().filter_map(|b| Url::parse(b).ok()).collect()
465 } else { 493 } else {
466 blossoms_defaults.iter().filter_map(|b| Url::parse(b).ok()).collect() 494 blossoms_defaults
495 .iter()
496 .filter_map(|b| Url::parse(b).ok())
497 .collect()
467 } 498 }
468 } 499 }
469 }; 500 };
470 501
471
472 let default_maintainers = { 502 let default_maintainers = {
473 let mut maintainers = vec![user_ref.public_key]; 503 let mut maintainers = vec![user_ref.public_key];
474 if args.other_maintainers.is_empty() { 504 if args.other_maintainers.is_empty() {
@@ -523,9 +553,10 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
523 }, 553 },
524 )?; 554 )?;
525 show_multi_input_prompt_success("maintainers", &selected); 555 show_multi_input_prompt_success("maintainers", &selected);
526 selected.iter() 556 selected
527 .filter_map(|npub| PublicKey::parse(npub).ok()) 557 .iter()
528 .collect() 558 .filter_map(|npub| PublicKey::parse(npub).ok())
559 .collect()
529 } 560 }
530 } else { 561 } else {
531 default_maintainers 562 default_maintainers
@@ -543,7 +574,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
543 .with_default(true), 574 .with_default(true),
544 )? { 575 )? {
545 git_repo.save_git_config_item("nostr.nostate", "true", false)?; 576 git_repo.save_git_config_item("nostr.nostate", "true", false)?;
546 } 577 }
547 } 578 }
548 579
549 let gitworkshop_url = NostrUrlDecoded { 580 let gitworkshop_url = NostrUrlDecoded {
@@ -564,8 +595,8 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
564 user: None, 595 user: None,
565 nip05: None, 596 nip05: None,
566 } 597 }
567 .to_string() 598 .to_string()
568 .replace("nostr://", "https://gitworkshop.dev/"); 599 .replace("nostr://", "https://gitworkshop.dev/");
569 600
570 let web: Vec<String> = if args.web.is_empty() { 601 let web: Vec<String> = if args.web.is_empty() {
571 let web_default = if let Some(repo_ref) = &repo_ref { 602 let web_default = if let Some(repo_ref) = &repo_ref {
@@ -688,28 +719,31 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> {
688 if git_repo.git_repo.find_remote("origin").is_ok() { 719 if git_repo.git_repo.find_remote("origin").is_ok() {
689 git_repo.git_repo.remote_set_url("origin", &nostr_url)?; 720 git_repo.git_repo.remote_set_url("origin", &nostr_url)?;
690 } else { 721 } else {
691 git_repo.git_repo.remote("origin",&nostr_url)?; 722 git_repo.git_repo.remote("origin", &nostr_url)?;
692 } 723 }
693 thread::sleep(Duration::new(1, 0)); // wait for annoucment event to be receieved and processed by ngit-relays 724 thread::sleep(Duration::new(1, 0)); // wait for annoucment event to be receieved and processed by ngit-relays
694 725
695 if std::env::var("NGITTEST").is_err() { // ignore during tests as git-remote-nostr isn't installed during ngit binary tests 726 if std::env::var("NGITTEST").is_err() {
727 // ignore during tests as git-remote-nostr isn't installed during ngit binary
728 // tests
696 if let Err(err) = push_main_or_master_branch(&git_repo) { 729 if let Err(err) = push_main_or_master_branch(&git_repo) {
697 println!("your repository announcement was published to nostr but git push exited with an error: {err}"); 730 println!(
731 "your repository announcement was published to nostr but git push exited with an error: {err}"
732 );
698 } 733 }
699 } 734 }
700 735
701 // println!( 736 // println!(
702 // "any remote branches beginning with `pr/` are open PRs from contributors. they can submit these by simply pushing a branch with this `pr/` prefix." 737 // "any remote branches beginning with `pr/` are open PRs from contributors.
738 // they can submit these by simply pushing a branch with this `pr/` prefix."
703 // ); 739 // );
704 println!("share your repository: {gitworkshop_url}" ); 740 println!("share your repository: {gitworkshop_url}");
705 println!("clone url: {nostr_url}"); 741 println!("clone url: {nostr_url}");
706 742
707
708 // no longer create a new maintainers.yaml file - its too confusing for users 743 // no longer create a new maintainers.yaml file - its too confusing for users
709 // as it falls out of sync with data in nostr event . update if it already 744 // as it falls out of sync with data in nostr event . update if it already
710 // exists 745 // exists
711 746
712
713 let relays = relays 747 let relays = relays
714 .iter() 748 .iter()
715 .map(std::string::ToString::to_string) 749 .map(std::string::ToString::to_string)
@@ -867,11 +901,12 @@ fn guess_at_existing_ngit_relays(
867 let postfix = format!("/{npub}/{identifier}.git"); 901 let postfix = format!("/{npub}/{identifier}.git");
868 if url.contains(&postfix) { 902 if url.contains(&postfix) {
869 if let Ok(ngit_relay_url) = normalize_ngit_relay_url(url) { 903 if let Ok(ngit_relay_url) = normalize_ngit_relay_url(url) {
870 let is_also_relay = relays.iter() 904 let is_also_relay = relays.iter().any(|r| {
871 .any(|r| normalize_ngit_relay_url(&r.to_string()).is_ok_and(|r| r.eq(&ngit_relay_url))); 905 normalize_ngit_relay_url(&r.to_string())
906 .is_ok_and(|r| r.eq(&ngit_relay_url))
907 });
872 if !existing_ngit_relays.contains(&ngit_relay_url) && is_also_relay { 908 if !existing_ngit_relays.contains(&ngit_relay_url) && is_also_relay {
873 existing_ngit_relays.push(ngit_relay_url); 909 existing_ngit_relays.push(ngit_relay_url);
874
875 } 910 }
876 } 911 }
877 } 912 }
@@ -913,26 +948,36 @@ fn normalize_ngit_relay_url(url: &str) -> Result<String> {
913 Ok(normalized_url.trim_end_matches('/').to_string()) 948 Ok(normalized_url.trim_end_matches('/').to_string())
914} 949}
915 950
916fn format_ngit_relay_url_as_clone_url(url:&str, public_key:&PublicKey, identifier: &str) -> Result<String> { 951fn format_ngit_relay_url_as_clone_url(
952 url: &str,
953 public_key: &PublicKey,
954 identifier: &str,
955) -> Result<String> {
917 let ngit_relay_url = normalize_ngit_relay_url(url)?; 956 let ngit_relay_url = normalize_ngit_relay_url(url)?;
918 if ngit_relay_url.contains("http://") { 957 if ngit_relay_url.contains("http://") {
919 return Ok(format!("{ngit_relay_url}/{}/{identifier}.git", public_key.to_bech32()?)) 958 return Ok(format!(
959 "{ngit_relay_url}/{}/{identifier}.git",
960 public_key.to_bech32()?
961 ));
920 } 962 }
921 Ok(format!("https://{ngit_relay_url}/{}/{identifier}.git", public_key.to_bech32()?)) 963 Ok(format!(
964 "https://{ngit_relay_url}/{}/{identifier}.git",
965 public_key.to_bech32()?
966 ))
922} 967}
923 968
924fn format_ngit_relay_url_as_relay_url(url:&str) -> Result<String> { 969fn format_ngit_relay_url_as_relay_url(url: &str) -> Result<String> {
925 let ngit_relay_url = normalize_ngit_relay_url(url)?; 970 let ngit_relay_url = normalize_ngit_relay_url(url)?;
926 if ngit_relay_url.contains("http://") { 971 if ngit_relay_url.contains("http://") {
927 return Ok(ngit_relay_url.replace("http://", "ws://")) 972 return Ok(ngit_relay_url.replace("http://", "ws://"));
928 } 973 }
929 Ok(format!("wss://{ngit_relay_url}")) 974 Ok(format!("wss://{ngit_relay_url}"))
930} 975}
931 976
932fn format_ngit_blossom_url_as_relay_url(url:&str) -> Result<String> { 977fn format_ngit_blossom_url_as_relay_url(url: &str) -> Result<String> {
933 let ngit_relay_url = normalize_ngit_relay_url(url)?; 978 let ngit_relay_url = normalize_ngit_relay_url(url)?;
934 if ngit_relay_url.contains("http://") { 979 if ngit_relay_url.contains("http://") {
935 return Ok(ngit_relay_url.to_string()) 980 return Ok(ngit_relay_url.to_string());
936 } 981 }
937 Ok(format!("https://{ngit_relay_url}")) 982 Ok(format!("https://{ngit_relay_url}"))
938} 983}
@@ -973,7 +1018,11 @@ pub fn show_multi_input_prompt_success(label: &str, values: &[String]) {
973 let values_str: Vec<&str> = values.iter().map(std::string::String::as_str).collect(); 1018 let values_str: Vec<&str> = values.iter().map(std::string::String::as_str).collect();
974 eprintln!("{}", { 1019 eprintln!("{}", {
975 let mut s = String::new(); 1020 let mut s = String::new();
976 let _ = ColorfulTheme::default().format_multi_select_prompt_selection(&mut s, label, &values_str); 1021 let _ = ColorfulTheme::default().format_multi_select_prompt_selection(
1022 &mut s,
1023 label,
1024 &values_str,
1025 );
977 s 1026 s
978 }); 1027 });
979} 1028}
@@ -988,7 +1037,9 @@ fn push_main_or_master_branch(git_repo: &Repo) -> Result<()> {
988 } else if local_branches.contains(&"master".to_string()) { 1037 } else if local_branches.contains(&"master".to_string()) {
989 "master" 1038 "master"
990 } else { 1039 } else {
991 bail!("set remote origin to nostr url and tried to push main or master branch but they dont exist yet") 1040 bail!(
1041 "set remote origin to nostr url and tried to push main or master branch but they dont exist yet"
1042 )
992 } 1043 }
993 }; 1044 };
994 1045
@@ -1016,7 +1067,6 @@ fn push_main_or_master_branch(git_repo: &Repo) -> Result<()> {
1016 } 1067 }
1017} 1068}
1018 1069
1019
1020#[cfg(test)] 1070#[cfg(test)]
1021mod tests { 1071mod tests {
1022 use anyhow::Result; 1072 use anyhow::Result;