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-09-09 07:36:47 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-09-09 07:36:47 +0100
commitd2d0eeb72912809a00f09fafdae4e827a34d0e26 (patch)
tree5f87f87ce8f068d4fa22a9f7f12701ea467bf0ff /src
parentad28bd8db1a6e953dce999eea85becda1d90beae (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')
-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}