upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bin/git_remote_nostr/fetch.rs6
-rw-r--r--src/bin/git_remote_nostr/list.rs8
-rw-r--r--src/bin/git_remote_nostr/push.rs92
-rw-r--r--src/bin/git_remote_nostr/utils.rs76
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
17use crate::utils::{ 17use 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;
22use crate::{ 22use 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
7use anyhow::{bail, Context, Result}; 7use anyhow::{anyhow, bail, Context, Result};
8use auth_git2::GitAuthenticator; 8use auth_git2::GitAuthenticator;
9use client::{get_events_from_cache, get_state_from_cache, send_events, sign_event, STATE_KIND}; 9use client::{get_events_from_cache, get_state_from_cache, send_events, sign_event, STATE_KIND};
10use console::Term; 10use console::Term;
@@ -15,7 +15,10 @@ use git_events::{
15}; 15};
16use ngit::{ 16use 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(
345fn push_to_remote( 343fn 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
401fn 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
94pub 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
135pub async fn get_open_proposals( 94pub 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
300pub fn error_is_not_authentication_failure(error: &anyhow::Error) -> bool { 259/// get an ordered vector of server protocols to attempt
260pub 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
286pub 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
292pub 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}