diff options
Diffstat (limited to 'src/bin/git_remote_nostr')
| -rw-r--r-- | src/bin/git_remote_nostr/fetch.rs | 6 | ||||
| -rw-r--r-- | src/bin/git_remote_nostr/list.rs | 8 | ||||
| -rw-r--r-- | src/bin/git_remote_nostr/push.rs | 92 | ||||
| -rw-r--r-- | src/bin/git_remote_nostr/utils.rs | 76 |
4 files changed, 117 insertions, 65 deletions
diff --git a/src/bin/git_remote_nostr/fetch.rs b/src/bin/git_remote_nostr/fetch.rs index c48da85..83b94b8 100644 --- a/src/bin/git_remote_nostr/fetch.rs +++ b/src/bin/git_remote_nostr/fetch.rs | |||
| @@ -15,7 +15,7 @@ use ngit::{ | |||
| 15 | }; | 15 | }; |
| 16 | 16 | ||
| 17 | use crate::utils::{ | 17 | use crate::utils::{ |
| 18 | error_is_not_authentication_failure, find_proposal_and_patches_by_branch_name, | 18 | fetch_or_list_error_is_not_authentication_failure, find_proposal_and_patches_by_branch_name, |
| 19 | get_oids_from_fetch_batch, get_open_proposals, get_read_protocols_to_try, join_with_and, | 19 | get_oids_from_fetch_batch, get_open_proposals, get_read_protocols_to_try, join_with_and, |
| 20 | }; | 20 | }; |
| 21 | 21 | ||
| @@ -140,7 +140,9 @@ fn fetch_from_git_server( | |||
| 140 | format!("fetch: {formatted_url} failed over {protocol}: {error}").as_str(), | 140 | format!("fetch: {formatted_url} failed over {protocol}: {error}").as_str(), |
| 141 | )?; | 141 | )?; |
| 142 | failed_protocols.push(protocol); | 142 | failed_protocols.push(protocol); |
| 143 | if protocol == &ServerProtocol::Ssh && error_is_not_authentication_failure(&error) { | 143 | if protocol == &ServerProtocol::Ssh |
| 144 | && fetch_or_list_error_is_not_authentication_failure(&error) | ||
| 145 | { | ||
| 144 | // authenticated by failed to complete request | 146 | // authenticated by failed to complete request |
| 145 | break; | 147 | break; |
| 146 | } | 148 | } |
diff --git a/src/bin/git_remote_nostr/list.rs b/src/bin/git_remote_nostr/list.rs index 3614626..c8843e3 100644 --- a/src/bin/git_remote_nostr/list.rs +++ b/src/bin/git_remote_nostr/list.rs | |||
| @@ -22,8 +22,8 @@ use repo_ref::RepoRef; | |||
| 22 | use crate::{ | 22 | use crate::{ |
| 23 | git::Repo, | 23 | git::Repo, |
| 24 | utils::{ | 24 | utils::{ |
| 25 | error_is_not_authentication_failure, get_open_proposals, get_read_protocols_to_try, | 25 | fetch_or_list_error_is_not_authentication_failure, get_open_proposals, |
| 26 | get_short_git_server_name, join_with_and, | 26 | get_read_protocols_to_try, get_short_git_server_name, join_with_and, |
| 27 | }, | 27 | }, |
| 28 | }; | 28 | }; |
| 29 | 29 | ||
| @@ -197,7 +197,9 @@ pub fn list_from_remote( | |||
| 197 | format!("list: {formatted_url} failed over {protocol}: {error}").as_str(), | 197 | format!("list: {formatted_url} failed over {protocol}: {error}").as_str(), |
| 198 | )?; | 198 | )?; |
| 199 | failed_protocols.push(protocol); | 199 | failed_protocols.push(protocol); |
| 200 | if protocol == &ServerProtocol::Ssh && error_is_not_authentication_failure(&error) { | 200 | if protocol == &ServerProtocol::Ssh |
| 201 | && fetch_or_list_error_is_not_authentication_failure(&error) | ||
| 202 | { | ||
| 201 | // authenticated by failed to complete request | 203 | // authenticated by failed to complete request |
| 202 | break; | 204 | break; |
| 203 | } | 205 | } |
diff --git a/src/bin/git_remote_nostr/push.rs b/src/bin/git_remote_nostr/push.rs index 2c9c7e1..364555a 100644 --- a/src/bin/git_remote_nostr/push.rs +++ b/src/bin/git_remote_nostr/push.rs | |||
| @@ -4,7 +4,7 @@ use std::{ | |||
| 4 | io::Stdin, | 4 | io::Stdin, |
| 5 | }; | 5 | }; |
| 6 | 6 | ||
| 7 | use anyhow::{bail, Context, Result}; | 7 | use anyhow::{anyhow, bail, Context, Result}; |
| 8 | use auth_git2::GitAuthenticator; | 8 | use auth_git2::GitAuthenticator; |
| 9 | use client::{get_events_from_cache, get_state_from_cache, send_events, sign_event, STATE_KIND}; | 9 | use client::{get_events_from_cache, get_state_from_cache, send_events, sign_event, STATE_KIND}; |
| 10 | use console::Term; | 10 | use console::Term; |
| @@ -15,7 +15,10 @@ use git_events::{ | |||
| 15 | }; | 15 | }; |
| 16 | use ngit::{ | 16 | use ngit::{ |
| 17 | client::{self, get_event_from_cache_by_id}, | 17 | client::{self, get_event_from_cache_by_id}, |
| 18 | git::{self, nostr_url::NostrUrlDecoded}, | 18 | git::{ |
| 19 | self, | ||
| 20 | nostr_url::{CloneUrl, NostrUrlDecoded}, | ||
| 21 | }, | ||
| 19 | git_events::{self, get_event_root}, | 22 | git_events::{self, get_event_root}, |
| 20 | login::{self, get_curent_user}, | 23 | login::{self, get_curent_user}, |
| 21 | repo_ref, repo_state, | 24 | repo_ref, repo_state, |
| @@ -34,7 +37,8 @@ use crate::{ | |||
| 34 | list::list_from_remotes, | 37 | list::list_from_remotes, |
| 35 | utils::{ | 38 | utils::{ |
| 36 | find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url, | 39 | find_proposal_and_patches_by_branch_name, get_all_proposals, get_remote_name_by_url, |
| 37 | get_short_git_server_name, read_line, switch_clone_url_between_ssh_and_https, | 40 | get_short_git_server_name, get_write_protocols_to_try, join_with_and, |
| 41 | push_error_is_not_authentication_failure, read_line, | ||
| 38 | }, | 42 | }, |
| 39 | }; | 43 | }; |
| 40 | 44 | ||
| @@ -315,27 +319,21 @@ pub async fn run_push( | |||
| 315 | } | 319 | } |
| 316 | 320 | ||
| 317 | // TODO make async - check gitlib2 callbacks work async | 321 | // TODO make async - check gitlib2 callbacks work async |
| 322 | |||
| 318 | for (git_server_url, remote_refspecs) in remote_refspecs { | 323 | for (git_server_url, remote_refspecs) in remote_refspecs { |
| 319 | let remote_refspecs = remote_refspecs | 324 | let remote_refspecs = remote_refspecs |
| 320 | .iter() | 325 | .iter() |
| 321 | .filter(|refspec| git_server_refspecs.contains(refspec)) | 326 | .filter(|refspec| git_server_refspecs.contains(refspec)) |
| 322 | .cloned() | 327 | .cloned() |
| 323 | .collect::<Vec<String>>(); | 328 | .collect::<Vec<String>>(); |
| 324 | if !refspecs.is_empty() | 329 | if !refspecs.is_empty() { |
| 325 | && push_to_remote(git_repo, &git_server_url, &remote_refspecs, &term).is_err() | 330 | let _ = push_to_remote( |
| 326 | { | 331 | git_repo, |
| 327 | if let Ok(alternative_url) = switch_clone_url_between_ssh_and_https(&git_server_url) { | 332 | &git_server_url, |
| 328 | if push_to_remote(git_repo, &alternative_url, &remote_refspecs, &term).is_err() { | 333 | decoded_nostr_url, |
| 329 | // errors get printed as part of callback | 334 | &remote_refspecs, |
| 330 | // TODO prevent 2 warning messages and instead use one | 335 | &term, |
| 331 | // to say it didnt work over either https or ssh | 336 | ); |
| 332 | } else { | ||
| 333 | term.write_line( | ||
| 334 | format!("but succeed over alterantive protocol {alternative_url}",) | ||
| 335 | .as_str(), | ||
| 336 | )?; | ||
| 337 | } | ||
| 338 | } | ||
| 339 | } | 337 | } |
| 340 | } | 338 | } |
| 341 | println!(); | 339 | println!(); |
| @@ -345,6 +343,64 @@ pub async fn run_push( | |||
| 345 | fn push_to_remote( | 343 | fn push_to_remote( |
| 346 | git_repo: &Repo, | 344 | git_repo: &Repo, |
| 347 | git_server_url: &str, | 345 | git_server_url: &str, |
| 346 | decoded_nostr_url: &NostrUrlDecoded, | ||
| 347 | remote_refspecs: &[String], | ||
| 348 | term: &Term, | ||
| 349 | ) -> Result<()> { | ||
| 350 | let server_url = git_server_url.parse::<CloneUrl>()?; | ||
| 351 | let protocols_to_attempt = get_write_protocols_to_try(&server_url, decoded_nostr_url); | ||
| 352 | |||
| 353 | let mut failed_protocols = vec![]; | ||
| 354 | let mut success = false; | ||
| 355 | |||
| 356 | for protocol in &protocols_to_attempt { | ||
| 357 | term.write_line( | ||
| 358 | format!( | ||
| 359 | "fetching ref list over {protocol} from {}...", | ||
| 360 | server_url.short_name(), | ||
| 361 | ) | ||
| 362 | .as_str(), | ||
| 363 | )?; | ||
| 364 | |||
| 365 | let formatted_url = server_url.format_as(protocol, &decoded_nostr_url.user)?; | ||
| 366 | |||
| 367 | if let Err(error) = push_to_remote_url(git_repo, &formatted_url, remote_refspecs, term) { | ||
| 368 | term.write_line( | ||
| 369 | format!("push: {formatted_url} failed over {protocol}: {error}").as_str(), | ||
| 370 | )?; | ||
| 371 | failed_protocols.push(protocol); | ||
| 372 | if push_error_is_not_authentication_failure(&error) { | ||
| 373 | break; | ||
| 374 | } | ||
| 375 | } else { | ||
| 376 | success = true; | ||
| 377 | if !failed_protocols.is_empty() { | ||
| 378 | term.write_line(format!("fetch: succeeded over {protocol}").as_str())?; | ||
| 379 | } | ||
| 380 | } | ||
| 381 | term.clear_last_lines(1)?; | ||
| 382 | } | ||
| 383 | if success { | ||
| 384 | Ok(()) | ||
| 385 | } else { | ||
| 386 | let error = anyhow!( | ||
| 387 | "{} failed over {}{}", | ||
| 388 | server_url.short_name(), | ||
| 389 | join_with_and(&failed_protocols), | ||
| 390 | if decoded_nostr_url.protocol.is_some() { | ||
| 391 | " and nostr url contains protocol override so no other protocols were attempted" | ||
| 392 | } else { | ||
| 393 | "" | ||
| 394 | }, | ||
| 395 | ); | ||
| 396 | term.write_line(format!("fetch: {error}").as_str())?; | ||
| 397 | Err(error) | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | fn push_to_remote_url( | ||
| 402 | git_repo: &Repo, | ||
| 403 | git_server_url: &str, | ||
| 348 | remote_refspecs: &[String], | 404 | remote_refspecs: &[String], |
| 349 | term: &Term, | 405 | term: &Term, |
| 350 | ) -> Result<()> { | 406 | ) -> Result<()> { |
diff --git a/src/bin/git_remote_nostr/utils.rs b/src/bin/git_remote_nostr/utils.rs index 90ea848..3039fe3 100644 --- a/src/bin/git_remote_nostr/utils.rs +++ b/src/bin/git_remote_nostr/utils.rs | |||
| @@ -91,47 +91,6 @@ pub fn read_line<'a>(stdin: &io::Stdin, line: &'a mut String) -> io::Result<Vec< | |||
| 91 | Ok(tokens) | 91 | Ok(tokens) |
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | pub fn switch_clone_url_between_ssh_and_https(url: &str) -> Result<String> { | ||
| 95 | if url.starts_with("https://") { | ||
| 96 | // Convert HTTPS to git@ syntax | ||
| 97 | let parts: Vec<&str> = url.trim_start_matches("https://").split('/').collect(); | ||
| 98 | if parts.len() >= 2 { | ||
| 99 | // Construct the git@ URL | ||
| 100 | Ok(format!("git@{}:{}", parts[0], parts[1..].join("/"))) | ||
| 101 | } else { | ||
| 102 | // If the format is unexpected, return an error | ||
| 103 | bail!("Invalid HTTPS URL format: {}", url); | ||
| 104 | } | ||
| 105 | } else if url.starts_with("ssh://") { | ||
| 106 | // Convert SSH to git@ syntax | ||
| 107 | let parts: Vec<&str> = url.trim_start_matches("ssh://").split('/').collect(); | ||
| 108 | if parts.len() >= 2 { | ||
| 109 | // Construct the git@ URL | ||
| 110 | Ok(format!("git@{}:{}", parts[0], parts[1..].join("/"))) | ||
| 111 | } else { | ||
| 112 | // If the format is unexpected, return an error | ||
| 113 | bail!("Invalid SSH URL format: {}", url); | ||
| 114 | } | ||
| 115 | } else if url.starts_with("git@") { | ||
| 116 | // Convert git@ syntax to HTTPS | ||
| 117 | let parts: Vec<&str> = url.split(':').collect(); | ||
| 118 | if parts.len() == 2 { | ||
| 119 | // Construct the HTTPS URL | ||
| 120 | Ok(format!( | ||
| 121 | "https://{}/{}", | ||
| 122 | parts[0].trim_end_matches('@'), | ||
| 123 | parts[1] | ||
| 124 | )) | ||
| 125 | } else { | ||
| 126 | // If the format is unexpected, return an error | ||
| 127 | bail!("Invalid git@ URL format: {}", url); | ||
| 128 | } | ||
| 129 | } else { | ||
| 130 | // If the URL is neither HTTPS, SSH, nor git@, return an error | ||
| 131 | bail!("Unsupported URL protocol: {}", url); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | pub async fn get_open_proposals( | 94 | pub async fn get_open_proposals( |
| 136 | git_repo: &Repo, | 95 | git_repo: &Repo, |
| 137 | repo_ref: &RepoRef, | 96 | repo_ref: &RepoRef, |
| @@ -297,7 +256,40 @@ pub fn get_read_protocols_to_try( | |||
| 297 | } | 256 | } |
| 298 | } | 257 | } |
| 299 | 258 | ||
| 300 | pub fn error_is_not_authentication_failure(error: &anyhow::Error) -> bool { | 259 | /// get an ordered vector of server protocols to attempt |
| 260 | pub fn get_write_protocols_to_try( | ||
| 261 | server_url: &CloneUrl, | ||
| 262 | decoded_nostr_url: &NostrUrlDecoded, | ||
| 263 | ) -> Vec<ServerProtocol> { | ||
| 264 | if server_url.protocol() == ServerProtocol::Filesystem { | ||
| 265 | vec![(ServerProtocol::Filesystem)] | ||
| 266 | } else if let Some(protocol) = &decoded_nostr_url.protocol { | ||
| 267 | vec![protocol.clone()] | ||
| 268 | } else if server_url.protocol() == ServerProtocol::Http { | ||
| 269 | vec![ | ||
| 270 | ServerProtocol::Ssh, | ||
| 271 | // note: list and fetch stop here if ssh was authenticated | ||
| 272 | ServerProtocol::Http, | ||
| 273 | ] | ||
| 274 | } else if server_url.protocol() == ServerProtocol::Ftp { | ||
| 275 | vec![ServerProtocol::Ssh, ServerProtocol::Ftp] | ||
| 276 | } else { | ||
| 277 | vec![ | ||
| 278 | ServerProtocol::Ssh, | ||
| 279 | // note: list and fetch stop here if ssh was authenticated | ||
| 280 | ServerProtocol::Https, | ||
| 281 | ] | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | /// to understand whether to try over another protocol | ||
| 286 | pub fn fetch_or_list_error_is_not_authentication_failure(error: &anyhow::Error) -> bool { | ||
| 287 | let error_str = error.to_string(); | ||
| 288 | error_str.contains("Permission to") || error_str.contains("Repository not found") | ||
| 289 | } | ||
| 290 | |||
| 291 | /// to understand whether to try over another protocol | ||
| 292 | pub fn push_error_is_not_authentication_failure(error: &anyhow::Error) -> bool { | ||
| 301 | let error_str = error.to_string(); | 293 | let error_str = error.to_string(); |
| 302 | error_str.contains("Permission to") || error_str.contains("Repository not found") | 294 | error_str.contains("Permission to") || error_str.contains("Repository not found") |
| 303 | } | 295 | } |