upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-08-30 13:39:05 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-08-30 13:39:05 +0100
commit8a9fc6336ae52ed73160719feeae96db9889feb0 (patch)
treeff488f991aa49ffa29d2ea773e4d78c1d00457f4 /src
parentb4459e98d816f6f278a4e9ab76883008d0b946fe (diff)
feat(remote): fallback to ssh or https
ssh clone urls are problematic as it requires authentication for fetching if users don't ave ssh keys setup for authentication with the git server. so cloning / fetching will fail. some users only authenticate using https and some only using ssh so we cant just use https for everything. this commit falls back to the other protocol if the first one failed for list, fetch or push
Diffstat (limited to 'src')
-rw-r--r--src/git_remote_helper.rs169
1 files changed, 133 insertions, 36 deletions
diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs
index e2de494..d00ad0e 100644
--- a/src/git_remote_helper.rs
+++ b/src/git_remote_helper.rs
@@ -12,12 +12,13 @@ use std::{
12 path::{Path, PathBuf}, 12 path::{Path, PathBuf},
13}; 13};
14 14
15use anyhow::{bail, Context, Result}; 15use anyhow::{anyhow, bail, Context, Result};
16use auth_git2::GitAuthenticator; 16use auth_git2::GitAuthenticator;
17use client::{ 17use client::{
18 consolidate_fetch_reports, get_events_from_cache, get_repo_ref_from_cache, 18 consolidate_fetch_reports, get_events_from_cache, get_repo_ref_from_cache,
19 get_state_from_cache, sign_event, Connect, STATE_KIND, 19 get_state_from_cache, sign_event, Connect, STATE_KIND,
20}; 20};
21use console::Term;
21use git::{nostr_git_url_to_repo_coordinates, sha1_to_oid, RepoActions}; 22use git::{nostr_git_url_to_repo_coordinates, sha1_to_oid, RepoActions};
22use git2::{Oid, Repository}; 23use git2::{Oid, Repository};
23use nostr::nips::{nip01::Coordinate, nip10::Marker}; 24use nostr::nips::{nip01::Coordinate, nip10::Marker};
@@ -290,10 +291,24 @@ fn list_from_remotes(
290 Ok(remote_state) => { 291 Ok(remote_state) => {
291 remote_states.insert(url.clone(), remote_state); 292 remote_states.insert(url.clone(), remote_state);
292 } 293 }
293 Err(error) => { 294 Err(error1) => {
294 term.write_line( 295 if let Ok(alternative_url) = switch_clone_url_between_ssh_and_https(url) {
295 format!("WARNING: {short_name} failed to list refs error: {error}",).as_str(), 296 match list_from_remote(git_repo, &alternative_url) {
296 )?; 297 Ok(remote_state) => {
298 remote_states.insert(url.clone(), remote_state);
299 }
300 Err(error2) => {
301 term.write_line(
302 format!("WARNING: {short_name} failed to list refs error: {error1}\r\nand alternative protocol {alternative_url}: {error2}").as_str(),
303 )?;
304 }
305 }
306 } else {
307 term.write_line(
308 format!("WARNING: {short_name} failed to list refs error: {error1}",)
309 .as_str(),
310 )?;
311 }
297 } 312 }
298 } 313 }
299 term.clear_last_lines(1)?; 314 term.clear_last_lines(1)?;
@@ -301,6 +316,47 @@ fn list_from_remotes(
301 Ok(remote_states) 316 Ok(remote_states)
302} 317}
303 318
319fn switch_clone_url_between_ssh_and_https(url: &str) -> Result<String> {
320 if url.starts_with("https://") {
321 // Convert HTTPS to git@ syntax
322 let parts: Vec<&str> = url.trim_start_matches("https://").split('/').collect();
323 if parts.len() >= 2 {
324 // Construct the git@ URL
325 Ok(format!("git@{}:{}", parts[0], parts[1..].join("/")))
326 } else {
327 // If the format is unexpected, return an error
328 bail!("Invalid HTTPS URL format: {}", url);
329 }
330 } else if url.starts_with("ssh://") {
331 // Convert SSH to git@ syntax
332 let parts: Vec<&str> = url.trim_start_matches("ssh://").split('/').collect();
333 if parts.len() >= 2 {
334 // Construct the git@ URL
335 Ok(format!("git@{}:{}", parts[0], parts[1..].join("/")))
336 } else {
337 // If the format is unexpected, return an error
338 bail!("Invalid SSH URL format: {}", url);
339 }
340 } else if url.starts_with("git@") {
341 // Convert git@ syntax to HTTPS
342 let parts: Vec<&str> = url.split(':').collect();
343 if parts.len() == 2 {
344 // Construct the HTTPS URL
345 Ok(format!(
346 "https://{}/{}",
347 parts[0].trim_end_matches('@'),
348 parts[1]
349 ))
350 } else {
351 // If the format is unexpected, return an error
352 bail!("Invalid git@ URL format: {}", url);
353 }
354 } else {
355 // If the URL is neither HTTPS, SSH, nor git@, return an error
356 bail!("Unsupported URL protocol: {}", url);
357 }
358}
359
304fn list_from_remote( 360fn list_from_remote(
305 git_repo: &Repo, 361 git_repo: &Repo,
306 git_server_remote_url: &str, 362 git_server_remote_url: &str,
@@ -467,15 +523,34 @@ async fn fetch(
467 term.write_line(format!("fetching from {short_name}...").as_str())?; 523 term.write_line(format!("fetching from {short_name}...").as_str())?;
468 let res = fetch_from_git_server(&git_repo.git_repo, &oids_from_git_servers, git_server_url); 524 let res = fetch_from_git_server(&git_repo.git_repo, &oids_from_git_servers, git_server_url);
469 term.clear_last_lines(1)?; 525 term.clear_last_lines(1)?;
470 if let Err(e) = res { 526 if let Err(error1) = res {
471 term.write_line( 527 if let Ok(alternative_url) = switch_clone_url_between_ssh_and_https(git_server_url) {
472 format!( 528 let res2 = fetch_from_git_server(
473 "WARNING: failed to fetch from {short_name} error: 529 &git_repo.git_repo,
474 {e}" 530 &oids_from_git_servers,
475 ) 531 &alternative_url,
476 .as_str(), 532 );
477 )?; 533 if let Err(error2) = res2 {
478 errors.insert(short_name.to_string(), e); 534 term.write_line(
535 format!(
536 "WARNING: failed to fetch from {short_name} error:{error1}\r\nand using alternative protocol {alternative_url}: {error2}"
537 ).as_str()
538 )?;
539 errors.insert(
540 short_name.to_string(),
541 anyhow!(
542 "{error1} and using alternative protocol {alternative_url}: {error2}"
543 ),
544 );
545 } else {
546 break;
547 }
548 } else {
549 term.write_line(
550 format!("WARNING: failed to fetch from {short_name} error:{error1}").as_str(),
551 )?;
552 errors.insert(short_name.to_string(), error1);
553 }
479 } else { 554 } else {
480 break; 555 break;
481 } 556 }
@@ -854,35 +929,26 @@ async fn push(
854 } 929 }
855 930
856 // TODO make async - check gitlib2 callbacks work async 931 // TODO make async - check gitlib2 callbacks work async
857 let git_config = git_repo.git_repo.config()?;
858 for (git_server_url, remote_refspecs) in remote_refspecs { 932 for (git_server_url, remote_refspecs) in remote_refspecs {
859 let remote_refspecs = remote_refspecs 933 let remote_refspecs = remote_refspecs
860 .iter() 934 .iter()
861 .filter(|refspec| git_server_refspecs.contains(refspec)) 935 .filter(|refspec| git_server_refspecs.contains(refspec))
862 .cloned() 936 .cloned()
863 .collect::<Vec<String>>(); 937 .collect::<Vec<String>>();
864 if !refspecs.is_empty() { 938 if !refspecs.is_empty()
865 if let Ok(mut git_server_remote) = git_repo.git_repo.remote_anonymous(&git_server_url) { 939 && push_to_remote(git_repo, &git_server_url, &remote_refspecs, &term).is_err()
866 let auth = GitAuthenticator::default(); 940 {
867 let mut push_options = git2::PushOptions::new(); 941 if let Ok(alternative_url) = switch_clone_url_between_ssh_and_https(&git_server_url) {
868 let mut remote_callbacks = git2::RemoteCallbacks::new(); 942 if push_to_remote(git_repo, &alternative_url, &remote_refspecs, &term).is_err() {
869 remote_callbacks.credentials(auth.credentials(&git_config)); 943 // errors get printed as part of callback
870 remote_callbacks.push_update_reference(|name, error| { 944 // TODO prevent 2 warning messages and instead use one
871 if let Some(error) = error { 945 // to say it didnt work over either https or ssh
872 term.write_line( 946 } else {
873 format!( 947 term.write_line(
874 "WARNING: {} failed to push {name} error: {error}", 948 format!("but succeed over alterantive protocol {alternative_url}",)
875 get_short_git_server_name(git_repo, &git_server_url),
876 )
877 .as_str(), 949 .as_str(),
878 ) 950 )?;
879 .unwrap(); 951 }
880 }
881 Ok(())
882 });
883 push_options.remote_callbacks(remote_callbacks);
884 let _ = git_server_remote.push(&remote_refspecs, Some(&mut push_options));
885 let _ = git_server_remote.disconnect();
886 } 952 }
887 } 953 }
888 } 954 }
@@ -890,6 +956,37 @@ async fn push(
890 Ok(()) 956 Ok(())
891} 957}
892 958
959fn push_to_remote(
960 git_repo: &Repo,
961 git_server_url: &str,
962 remote_refspecs: &[String],
963 term: &Term,
964) -> Result<()> {
965 let git_config = git_repo.git_repo.config()?;
966 let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_url)?;
967 let auth = GitAuthenticator::default();
968 let mut push_options = git2::PushOptions::new();
969 let mut remote_callbacks = git2::RemoteCallbacks::new();
970 remote_callbacks.credentials(auth.credentials(&git_config));
971 remote_callbacks.push_update_reference(|name, error| {
972 if let Some(error) = error {
973 term.write_line(
974 format!(
975 "WARNING: {} failed to push {name} error: {error}",
976 get_short_git_server_name(git_repo, git_server_url),
977 )
978 .as_str(),
979 )
980 .unwrap();
981 }
982 Ok(())
983 });
984 push_options.remote_callbacks(remote_callbacks);
985 git_server_remote.push(remote_refspecs, Some(&mut push_options))?;
986 let _ = git_server_remote.disconnect();
987 Ok(())
988}
989
893fn get_event_root(event: &nostr::Event) -> Result<EventId> { 990fn get_event_root(event: &nostr::Event) -> Result<EventId> {
894 Ok(EventId::parse( 991 Ok(EventId::parse(
895 event 992 event