diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-09-09 07:36:47 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-09-09 07:36:47 +0100 |
| commit | d2d0eeb72912809a00f09fafdae4e827a34d0e26 (patch) | |
| tree | 5f87f87ce8f068d4fa22a9f7f12701ea467bf0ff /src/bin | |
| parent | ad28bd8db1a6e953dce999eea85becda1d90beae (diff) | |
feat(remote): push protocol selection / fallback
enable override from nostr url
clone url is filesystem use filesystem
otherwise try ssh, then https authenticated
unless clone url is http, then try ssh then http as we assume,
we are on a local trusted network.
Diffstat (limited to 'src/bin')
| -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 | } |