From 6e7e7bd3497d2a77fda34e27f65955b8ac09b3be Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 6 Sep 2024 10:45:21 +0100 Subject: fix(remote): `list` apply protocols selection used in fetch and tweak the error reporting --- src/bin/git_remote_nostr/list.rs | 138 ++++++++++++++++++++++++------- src/bin/git_remote_nostr/main.rs | 18 ++++- src/bin/git_remote_nostr/push.rs | 16 ++-- src/lib/git/nostr_url.rs | 171 +++++++++++++++++++++------------------ 4 files changed, 229 insertions(+), 114 deletions(-) diff --git a/src/bin/git_remote_nostr/list.rs b/src/bin/git_remote_nostr/list.rs index 097f070..26f699d 100644 --- a/src/bin/git_remote_nostr/list.rs +++ b/src/bin/git_remote_nostr/list.rs @@ -1,25 +1,35 @@ use core::str; use std::collections::HashMap; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use auth_git2::GitAuthenticator; use client::get_state_from_cache; use git::RepoActions; use git_events::{event_to_cover_letter, get_commit_id_from_patch}; -use ngit::{client, git, git_events, login::get_curent_user, repo_ref}; +use ngit::{ + client, + git::{ + self, + nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, + }, + git_events, + login::get_curent_user, + repo_ref, +}; use nostr_sdk::hashes::sha1::Hash as Sha1Hash; use repo_ref::RepoRef; use crate::{ git::Repo, utils::{ - get_open_proposals, get_short_git_server_name, switch_clone_url_between_ssh_and_https, + get_open_proposals, get_read_protocols_to_try, get_short_git_server_name, join_with_and, }, }; pub async fn run_list( git_repo: &Repo, repo_ref: &RepoRef, + decoded_nostr_url: &NostrUrlDecoded, for_push: bool, ) -> Result>> { let nostr_state = @@ -31,7 +41,7 @@ pub async fn run_list( let term = console::Term::stderr(); - let remote_states = list_from_remotes(&term, git_repo, &repo_ref.git_server)?; + let remote_states = list_from_remotes(&term, git_repo, &repo_ref.git_server, decoded_nostr_url); let mut state = if let Some(nostr_state) = nostr_state { for (name, value) in &nostr_state.state { @@ -122,41 +132,115 @@ pub fn list_from_remotes( term: &console::Term, git_repo: &Repo, git_servers: &Vec, -) -> Result>> { + decoded_nostr_url: &NostrUrlDecoded, // Add this parameter +) -> HashMap> { let mut remote_states = HashMap::new(); + let mut errors = HashMap::new(); for url in git_servers { - let short_name = get_short_git_server_name(git_repo, url); - term.write_line(format!("fetching refs list: {short_name}...").as_str())?; - match list_from_remote(git_repo, url) { - Ok(remote_state) => { - remote_states.insert(url.clone(), remote_state); + match list_from_remote(term, git_repo, url, decoded_nostr_url) { + Err(error) => { + errors.insert(url, error); } - Err(error1) => { - if let Ok(alternative_url) = switch_clone_url_between_ssh_and_https(url) { - match list_from_remote(git_repo, &alternative_url) { - Ok(remote_state) => { - remote_states.insert(url.clone(), remote_state); - } - Err(error2) => { - term.write_line( - format!("WARNING: {short_name} failed to list refs error: {error1}\r\nand alternative protocol {alternative_url}: {error2}").as_str(), - )?; - } - } - } else { + Ok(state) => { + remote_states.insert(url.to_string(), state); + } + } + } + remote_states +} + +pub fn list_from_remote( + term: &console::Term, + git_repo: &Repo, + git_server_url: &str, + decoded_nostr_url: &NostrUrlDecoded, // Add this parameter +) -> Result> { + let server_url = git_server_url.parse::()?; + let protocols_to_attempt = get_read_protocols_to_try(&server_url, decoded_nostr_url); + + let mut failed_protocols = vec![]; + let mut remote_state: Option> = None; + + for protocol in &protocols_to_attempt { + term.write_line( + format!( + "fetching ref list from {} over {protocol}...", + server_url.domain(), + ) + .as_str(), + )?; + + let formatted_url = server_url.format_as(protocol, &decoded_nostr_url.user)?; + let res = if [ServerProtocol::UnauthHttps, ServerProtocol::UnauthHttp].contains(protocol) { + list_from_remote_url_unauthenticated(git_repo, &formatted_url) + } else { + list_from_remote_url(git_repo, &formatted_url) + }; + + match res { + Ok(state) => { + remote_state = Some(state); + if !failed_protocols.is_empty() { term.write_line( - format!("WARNING: {short_name} failed to list refs error: {error1}",) - .as_str(), + format!( + "list: succeeded over {protocol} for {}", + server_url.domain(), + ) + .as_str(), )?; } + break; + } + Err(error) => { + term.write_line( + format!("list: {formatted_url} failed over {protocol}: {error}").as_str(), + )?; + failed_protocols.push(protocol); } } term.clear_last_lines(1)?; } - Ok(remote_states) + if let Some(remote_state) = remote_state { + Ok(remote_state) + } else { + let error = anyhow!( + "{} failed over {}{}", + server_url.domain(), + join_with_and(&failed_protocols), + if decoded_nostr_url.protocol.is_some() { + " and nostr url contains protocol override so no other protocols were attempted" + } else { + "" + }, + ); + term.write_line(format!("list: {error}").as_str())?; + Err(error) + } +} + +fn list_from_remote_url_unauthenticated( + git_repo: &Repo, + git_server_remote_url: &str, +) -> Result> { + let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_remote_url)?; + let remote_callbacks = git2::RemoteCallbacks::new(); + git_server_remote.connect_auth(git2::Direction::Fetch, Some(remote_callbacks), None)?; + let mut state = HashMap::new(); + for head in git_server_remote.list()? { + if let Some(symbolic_reference) = head.symref_target() { + state.insert( + head.name().to_string(), + format!("ref: {symbolic_reference}"), + ); + } else { + state.insert(head.name().to_string(), head.oid().to_string()); + } + } + git_server_remote.disconnect()?; + Ok(state) } -fn list_from_remote( +fn list_from_remote_url( git_repo: &Repo, git_server_remote_url: &str, ) -> Result> { diff --git a/src/bin/git_remote_nostr/main.rs b/src/bin/git_remote_nostr/main.rs index a3fc5f8..e2738d2 100644 --- a/src/bin/git_remote_nostr/main.rs +++ b/src/bin/git_remote_nostr/main.rs @@ -75,13 +75,21 @@ async fn main() -> Result<()> { println!("unsupported"); } ["fetch", oid, refstr] => { - fetch::run_fetch(&git_repo, &repo_ref, &decoded_nostr_url, &stdin, oid, refstr).await?; + fetch::run_fetch( + &git_repo, + &repo_ref, + &decoded_nostr_url, + &stdin, + oid, + refstr, + ) + .await?; } ["push", refspec] => { push::run_push( &git_repo, &repo_ref, - nostr_remote_url, + &decoded_nostr_url, &stdin, refspec, &client, @@ -90,10 +98,12 @@ async fn main() -> Result<()> { .await?; } ["list"] => { - list_outputs = Some(list::run_list(&git_repo, &repo_ref, false).await?); + list_outputs = + Some(list::run_list(&git_repo, &repo_ref, &decoded_nostr_url, false).await?); } ["list", "for-push"] => { - list_outputs = Some(list::run_list(&git_repo, &repo_ref, true).await?); + list_outputs = + Some(list::run_list(&git_repo, &repo_ref, &decoded_nostr_url, true).await?); } [] => { return Ok(()); diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs index 441c341..2c9c7e1 100644 --- a/src/bin/git_remote_nostr/push.rs +++ b/src/bin/git_remote_nostr/push.rs @@ -15,7 +15,7 @@ use git_events::{ }; use ngit::{ client::{self, get_event_from_cache_by_id}, - git, + git::{self, nostr_url::NostrUrlDecoded}, git_events::{self, get_event_root}, login::{self, get_curent_user}, repo_ref, repo_state, @@ -42,7 +42,7 @@ use crate::{ pub async fn run_push( git_repo: &Repo, repo_ref: &RepoRef, - nostr_remote_url: &str, + decoded_nostr_url: &NostrUrlDecoded, stdin: &Stdin, initial_refspec: &str, client: &Client, @@ -66,7 +66,7 @@ pub async fn run_push( let list_outputs = match list_outputs { Some(outputs) => outputs, - _ => list_from_remotes(&term, git_repo, &repo_ref.git_server)?, + _ => list_from_remotes(&term, git_repo, &repo_ref.git_server, decoded_nostr_url), }; let nostr_state = get_state_from_cache(git_repo.get_path()?, repo_ref).await; @@ -154,7 +154,7 @@ pub async fn run_push( &term, repo_ref, git_repo, - nostr_remote_url, + &decoded_nostr_url.original_string, &signer, &git_server_refspecs, ) @@ -306,8 +306,12 @@ pub async fn run_push( } let (_, to) = refspec_to_from_to(refspec)?; println!("ok {to}"); - update_remote_refs_pushed(&git_repo.git_repo, refspec, nostr_remote_url) - .context("could not update remote_ref locally")?; + update_remote_refs_pushed( + &git_repo.git_repo, + refspec, + &decoded_nostr_url.original_string, + ) + .context("could not update remote_ref locally")?; } // TODO make async - check gitlib2 callbacks work async diff --git a/src/lib/git/nostr_url.rs b/src/lib/git/nostr_url.rs index ea8d221..2717916 100644 --- a/src/lib/git/nostr_url.rs +++ b/src/lib/git/nostr_url.rs @@ -36,6 +36,7 @@ impl fmt::Display for ServerProtocol { #[derive(Debug, PartialEq)] pub struct NostrUrlDecoded { + pub original_string: String, pub coordinates: HashSet, pub protocol: Option, pub user: Option, @@ -145,6 +146,7 @@ impl std::str::FromStr for NostrUrlDecoded { } Ok(Self { + original_string: url.to_string(), coordinates, protocol, user, @@ -831,11 +833,11 @@ mod tests { #[test] fn from_naddr() -> Result<()> { + let url = "nostr://naddr1qqzxuemfwsqs6amnwvaz7tmwdaejumr0dspzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqvzqqqrhnym0k2qj".to_string(); assert_eq!( - NostrUrlDecoded::from_str( - "nostr://naddr1qqzxuemfwsqs6amnwvaz7tmwdaejumr0dspzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqvzqqqrhnym0k2qj" - )?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([Coordinate { identifier: "ngit".to_string(), public_key: PublicKey::parse( @@ -851,16 +853,19 @@ mod tests { ); Ok(()) } + mod from_npub_slash_identifier { use super::*; #[test] fn without_relay() -> Result<()> { + let url = + "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit" + .to_string(); assert_eq!( - NostrUrlDecoded::from_str( - "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit" - )?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([get_model_coordinate(false)]), protocol: None, user: None, @@ -870,16 +875,15 @@ mod tests { } mod with_url_parameters { - use super::*; #[test] fn with_relay_without_scheme_defaults_to_wss() -> Result<()> { + let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay=nos.lol".to_string(); assert_eq!( - NostrUrlDecoded::from_str( - "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay=nos.lol" - )?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([get_model_coordinate(true)]), protocol: None, user: None, @@ -890,12 +894,14 @@ mod tests { #[test] fn with_encoded_relay() -> Result<()> { + let url = format!( + "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay={}", + urlencoding::encode("wss://nos.lol") + ); assert_eq!( - NostrUrlDecoded::from_str(&format!( - "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay={}", - urlencoding::encode("wss://nos.lol") - ))?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([get_model_coordinate(true)]), protocol: None, user: None, @@ -903,41 +909,44 @@ mod tests { ); Ok(()) } + #[test] fn with_multiple_encoded_relays() -> Result<()> { - assert_eq!( - NostrUrlDecoded::from_str(&format!( - "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay={}&relay1={}", - urlencoding::encode("wss://nos.lol"), - urlencoding::encode("wss://relay.damus.io"), - ))?, - NostrUrlDecoded { - coordinates: HashSet::from([Coordinate { - identifier: "ngit".to_string(), - public_key: PublicKey::parse( - "npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr", - ) - .unwrap(), - kind: nostr_sdk::Kind::GitRepoAnnouncement, - relays: vec![ - "wss://nos.lol/".to_string(), - "wss://relay.damus.io/".to_string(), - ], - }]), - protocol: None, - user: None, - }, + let url = format!( + "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?relay={}&relay1={}", + urlencoding::encode("wss://nos.lol"), + urlencoding::encode("wss://relay.damus.io"), ); + assert_eq!( + NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded { + original_string: url.clone(), + coordinates: HashSet::from([Coordinate { + identifier: "ngit".to_string(), + public_key: PublicKey::parse( + "npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr", + ) + .unwrap(), + kind: nostr_sdk::Kind::GitRepoAnnouncement, + relays: vec![ + "wss://nos.lol/".to_string(), + "wss://relay.damus.io/".to_string(), + ], + }]), + protocol: None, + user: None, + }, + ); Ok(()) } #[test] fn with_server_protocol() -> Result<()> { + let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?protocol=ssh".to_string(); assert_eq!( - NostrUrlDecoded::from_str( - "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?protocol=ssh" - )?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([get_model_coordinate(false)]), protocol: Some(ServerProtocol::Ssh), user: None, @@ -945,13 +954,14 @@ mod tests { ); Ok(()) } + #[test] fn with_server_protocol_and_user() -> Result<()> { + let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?protocol=ssh&user=fred".to_string(); assert_eq!( - NostrUrlDecoded::from_str( - "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit?protocol=ssh&user=fred" - )?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([get_model_coordinate(false)]), protocol: Some(ServerProtocol::Ssh), user: Some("fred".to_string()), @@ -960,16 +970,17 @@ mod tests { Ok(()) } } + mod with_parameters_embedded_with_slashes { use super::*; #[test] fn with_relay_without_scheme_defaults_to_wss() -> Result<()> { + let url = "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/nos.lol/ngit".to_string(); assert_eq!( - NostrUrlDecoded::from_str( - "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/nos.lol/ngit" - )?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([get_model_coordinate(true)]), protocol: None, user: None, @@ -980,12 +991,14 @@ mod tests { #[test] fn with_encoded_relay() -> Result<()> { + let url = format!( + "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/{}/ngit", + urlencoding::encode("wss://nos.lol") + ); assert_eq!( - NostrUrlDecoded::from_str(&format!( - "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/{}/ngit", - urlencoding::encode("wss://nos.lol") - ))?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([get_model_coordinate(true)]), protocol: None, user: None, @@ -993,41 +1006,44 @@ mod tests { ); Ok(()) } + #[test] fn with_multiple_encoded_relays() -> Result<()> { - assert_eq!( - NostrUrlDecoded::from_str(&format!( - "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/{}/{}/ngit", - urlencoding::encode("wss://nos.lol"), - urlencoding::encode("wss://relay.damus.io"), - ))?, - NostrUrlDecoded { - coordinates: HashSet::from([Coordinate { - identifier: "ngit".to_string(), - public_key: PublicKey::parse( - "npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr", - ) - .unwrap(), - kind: nostr_sdk::Kind::GitRepoAnnouncement, - relays: vec![ - "wss://nos.lol/".to_string(), - "wss://relay.damus.io/".to_string(), - ], - }]), - protocol: None, - user: None, - }, + let url = format!( + "nostr://npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/{}/{}/ngit", + urlencoding::encode("wss://nos.lol"), + urlencoding::encode("wss://relay.damus.io"), ); + assert_eq!( + NostrUrlDecoded::from_str(&url)?, + NostrUrlDecoded { + original_string: url.clone(), + coordinates: HashSet::from([Coordinate { + identifier: "ngit".to_string(), + public_key: PublicKey::parse( + "npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr", + ) + .unwrap(), + kind: nostr_sdk::Kind::GitRepoAnnouncement, + relays: vec![ + "wss://nos.lol/".to_string(), + "wss://relay.damus.io/".to_string(), + ], + }]), + protocol: None, + user: None, + }, + ); Ok(()) } #[test] fn with_server_protocol() -> Result<()> { + let url = "nostr://ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit".to_string(); assert_eq!( - NostrUrlDecoded::from_str( - "nostr://ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit" - )?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([get_model_coordinate(false)]), protocol: Some(ServerProtocol::Ssh), user: None, @@ -1035,13 +1051,14 @@ mod tests { ); Ok(()) } + #[test] fn with_server_protocol_and_user() -> Result<()> { + let url = "nostr://fred@ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit".to_string(); assert_eq!( - NostrUrlDecoded::from_str( - "nostr://fred@ssh/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit" - )?, + NostrUrlDecoded::from_str(&url)?, NostrUrlDecoded { + original_string: url.clone(), coordinates: HashSet::from([get_model_coordinate(false)]), protocol: Some(ServerProtocol::Ssh), user: Some("fred".to_string()), -- cgit v1.2.3