diff options
Diffstat (limited to 'src/bin')
| -rw-r--r-- | src/bin/git_remote_nostr/fetch.rs | 116 | ||||
| -rw-r--r-- | src/bin/git_remote_nostr/utils.rs | 93 |
2 files changed, 126 insertions, 83 deletions
diff --git a/src/bin/git_remote_nostr/fetch.rs b/src/bin/git_remote_nostr/fetch.rs index 6836456..f591fed 100644 --- a/src/bin/git_remote_nostr/fetch.rs +++ b/src/bin/git_remote_nostr/fetch.rs | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | use std::io::Stdin; | 1 | use std::io::Stdin; |
| 2 | 2 | ||
| 3 | use anyhow::{anyhow, bail, Context, Result}; | 3 | use anyhow::{bail, Result}; |
| 4 | use auth_git2::GitAuthenticator; | 4 | use auth_git2::GitAuthenticator; |
| 5 | use git2::Repository; | 5 | use git2::Repository; |
| 6 | use ngit::{ | 6 | use ngit::{ |
| @@ -16,6 +16,7 @@ use ngit::{ | |||
| 16 | 16 | ||
| 17 | use crate::utils::{ | 17 | use crate::utils::{ |
| 18 | find_proposal_and_patches_by_branch_name, get_oids_from_fetch_batch, get_open_proposals, | 18 | find_proposal_and_patches_by_branch_name, get_oids_from_fetch_batch, get_open_proposals, |
| 19 | get_read_protocols_to_try, join_with_and, | ||
| 19 | }; | 20 | }; |
| 20 | 21 | ||
| 21 | pub async fn run_fetch( | 22 | pub async fn run_fetch( |
| @@ -58,10 +59,10 @@ pub async fn run_fetch( | |||
| 58 | && !errors.is_empty() | 59 | && !errors.is_empty() |
| 59 | { | 60 | { |
| 60 | bail!( | 61 | bail!( |
| 61 | "failed to fetch objects in nostr state event from:\r\n{}", | 62 | "fetch: failed to fetch objects in nostr state event from:\r\n{}", |
| 62 | errors | 63 | errors |
| 63 | .iter() | 64 | .iter() |
| 64 | .map(std::string::ToString::to_string) | 65 | .map(|e| format!(" - {e}")) |
| 65 | .collect::<Vec<String>>() | 66 | .collect::<Vec<String>>() |
| 66 | .join("\r\n") | 67 | .join("\r\n") |
| 67 | ); | 68 | ); |
| @@ -114,97 +115,45 @@ fn fetch_from_git_server( | |||
| 114 | ) -> Result<()> { | 115 | ) -> Result<()> { |
| 115 | let server_url = git_server_url.parse::<CloneUrl>()?; | 116 | let server_url = git_server_url.parse::<CloneUrl>()?; |
| 116 | 117 | ||
| 117 | // if protocol is local - just try local | 118 | let protocols_to_attempt = get_read_protocols_to_try(&server_url, decoded_nostr_url); |
| 118 | if server_url.protocol() == ServerProtocol::Local { | ||
| 119 | let formatted_url = server_url.format_as(&ServerProtocol::Local, &None)?; | ||
| 120 | term.write_line(format!("fetching from {formatted_url}...").as_str())?; | ||
| 121 | if let Err(error) = fetch_from_git_server_url(git_repo, oids, &formatted_url) { | ||
| 122 | term.write_line( | ||
| 123 | format!("WARNING: failed to fetch from {formatted_url} error:{error}").as_str(), | ||
| 124 | )?; | ||
| 125 | return Err(error).context(format!("{formatted_url}: failed to fetch")); | ||
| 126 | } | ||
| 127 | return Ok(()); | ||
| 128 | } | ||
| 129 | 119 | ||
| 130 | term.write_line(format!("fetching from {}...", server_url.domain()).as_str())?; | 120 | let mut failed_protocols = vec![]; |
| 121 | let mut success = false; | ||
| 122 | for protocol in &protocols_to_attempt { | ||
| 123 | term.write_line( | ||
| 124 | format!("fetching from {} over {protocol}...", server_url.domain(),).as_str(), | ||
| 125 | )?; | ||
| 131 | 126 | ||
| 132 | // use overide protocol if specified | ||
| 133 | if let Some(protocol) = &decoded_nostr_url.protocol { | ||
| 134 | let formatted_url = server_url.format_as(protocol, &decoded_nostr_url.user)?; | 127 | let formatted_url = server_url.format_as(protocol, &decoded_nostr_url.user)?; |
| 135 | let res = fetch_from_git_server_url(git_repo, oids, &formatted_url); | 128 | let res = if [ServerProtocol::UnauthHttps, ServerProtocol::UnauthHttp].contains(protocol) { |
| 129 | fetch_from_git_server_url_unauthenticated(git_repo, oids, &formatted_url) | ||
| 130 | } else { | ||
| 131 | fetch_from_git_server_url(git_repo, oids, &formatted_url) | ||
| 132 | }; | ||
| 136 | term.clear_last_lines(1)?; | 133 | term.clear_last_lines(1)?; |
| 137 | if let Err(error) = res { | 134 | if let Err(error) = res { |
| 138 | term.write_line( | 135 | term.write_line( |
| 139 | format!( | 136 | format!("fetch: {formatted_url} failed over {protocol}: {error}").as_str(), |
| 140 | "WARNING: {formatted_url} failed to fetch over {protocol}{} as specified in nostr url. error:{error}", | ||
| 141 | if let Some(user) = &decoded_nostr_url.user { | ||
| 142 | format!(" with user '{user}'") | ||
| 143 | } else { | ||
| 144 | String::new() | ||
| 145 | } | ||
| 146 | ).as_str(), | ||
| 147 | )?; | 137 | )?; |
| 148 | return Err(error).context(format!("{formatted_url}: failed to fetch")); | 138 | failed_protocols.push(protocol); |
| 149 | } | 139 | } else { |
| 150 | return Ok(()); | 140 | success = true; |
| 151 | } | 141 | if !failed_protocols.is_empty() { |
| 152 | 142 | term.write_line(format!("fetch: succeeded over {protocol}").as_str())?; | |
| 153 | // Try https unauthenticated | ||
| 154 | let formatted_url = server_url.format_as(&ServerProtocol::Https, &None)?; | ||
| 155 | let res = fetch_from_git_server_url_unauthenticated(git_repo, oids, &formatted_url); | ||
| 156 | term.clear_last_lines(1)?; | ||
| 157 | if let Err(unauth_error) = res { | ||
| 158 | term.write_line( | ||
| 159 | format!( | ||
| 160 | "WARNING: {formatted_url} failed to fetch over unauthenticated https. {unauth_error}", | ||
| 161 | ).as_str(), | ||
| 162 | )?; | ||
| 163 | // TODO what about timeout errors? | ||
| 164 | // try over ssh | ||
| 165 | let mut ssh_error = None; | ||
| 166 | if check_ssh_keys() { | ||
| 167 | term.write_line(format!("fetching from {} over ssh...", server_url.domain()).as_str())?; | ||
| 168 | let formatted_url = server_url.format_as(&ServerProtocol::Ssh, &None)?; | ||
| 169 | let res = fetch_from_git_server_url(git_repo, oids, &formatted_url); | ||
| 170 | term.clear_last_lines(1)?; | ||
| 171 | if let Err(error) = res { | ||
| 172 | term.write_line( | ||
| 173 | format!("WARNING: {formatted_url} failed to fetch over ssh. error:{error}") | ||
| 174 | .as_str(), | ||
| 175 | )?; | ||
| 176 | term.write_line( | ||
| 177 | format!("fetching from {} over ssh...", server_url.domain()).as_str(), | ||
| 178 | )?; | ||
| 179 | ssh_error = Some(error); | ||
| 180 | } else { | ||
| 181 | return Ok(()); | ||
| 182 | } | 143 | } |
| 183 | } | 144 | } |
| 184 | // try over https authenticated | 145 | } |
| 185 | term.write_line( | 146 | if !success { |
| 186 | format!( | 147 | if decoded_nostr_url.protocol.is_some() { |
| 187 | "fetching from {} over authenticated https...", | ||
| 188 | server_url.domain() | ||
| 189 | ) | ||
| 190 | .as_str(), | ||
| 191 | )?; | ||
| 192 | let formatted_url = server_url.format_as(&ServerProtocol::Ssh, &None)?; | ||
| 193 | let res = fetch_from_git_server_url(git_repo, oids, &formatted_url); | ||
| 194 | term.clear_last_lines(1)?; | ||
| 195 | if let Err(auth_https_error) = res { | ||
| 196 | term.write_line( | 148 | term.write_line( |
| 197 | format!("WARNING: {formatted_url} failed to fetch over authenticated https. error:{auth_https_error}",) | 149 | "fetch: protocol override in nostr url so not attempting with any other protocols", |
| 198 | .as_str(), | ||
| 199 | )?; | 150 | )?; |
| 200 | let error_message = format!( | ||
| 201 | "{} failed to fetch over unauthenticated https ({unauth_error}), ssh ({}) and authenticated https ({auth_https_error})", | ||
| 202 | server_url.format_as(&ServerProtocol::Unspecified, &None)?, | ||
| 203 | ssh_error.unwrap_or(anyhow!("no keys found")) | ||
| 204 | ); | ||
| 205 | |||
| 206 | bail!(error_message) | ||
| 207 | } | 151 | } |
| 152 | bail!( | ||
| 153 | "{} failed over {}", | ||
| 154 | server_url.domain(), | ||
| 155 | join_with_and(&failed_protocols) | ||
| 156 | ); | ||
| 208 | } | 157 | } |
| 209 | Ok(()) | 158 | Ok(()) |
| 210 | } | 159 | } |
| @@ -214,6 +163,9 @@ fn fetch_from_git_server_url( | |||
| 214 | oids: &[String], | 163 | oids: &[String], |
| 215 | git_server_url: &str, | 164 | git_server_url: &str, |
| 216 | ) -> Result<()> { | 165 | ) -> Result<()> { |
| 166 | if git_server_url.parse::<CloneUrl>()?.protocol() == ServerProtocol::Ssh && !check_ssh_keys() { | ||
| 167 | bail!("no ssh keys found"); | ||
| 168 | } | ||
| 217 | let git_config = git_repo.config()?; | 169 | let git_config = git_repo.config()?; |
| 218 | let mut git_server_remote = git_repo.remote_anonymous(git_server_url)?; | 170 | let mut git_server_remote = git_repo.remote_anonymous(git_server_url)?; |
| 219 | let auth = GitAuthenticator::default(); | 171 | let auth = GitAuthenticator::default(); |
diff --git a/src/bin/git_remote_nostr/utils.rs b/src/bin/git_remote_nostr/utils.rs index c53c34f..a31dcbf 100644 --- a/src/bin/git_remote_nostr/utils.rs +++ b/src/bin/git_remote_nostr/utils.rs | |||
| @@ -10,7 +10,10 @@ use ngit::{ | |||
| 10 | get_all_proposal_patch_events_from_cache, get_events_from_cache, | 10 | get_all_proposal_patch_events_from_cache, get_events_from_cache, |
| 11 | get_proposals_and_revisions_from_cache, | 11 | get_proposals_and_revisions_from_cache, |
| 12 | }, | 12 | }, |
| 13 | git::{Repo, RepoActions}, | 13 | git::{ |
| 14 | nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, | ||
| 15 | Repo, RepoActions, | ||
| 16 | }, | ||
| 14 | git_events::{ | 17 | git_events::{ |
| 15 | event_is_revision_root, event_to_cover_letter, get_most_recent_patch_with_ancestors, | 18 | event_is_revision_root, event_to_cover_letter, get_most_recent_patch_with_ancestors, |
| 16 | status_kinds, | 19 | status_kinds, |
| @@ -246,3 +249,91 @@ pub fn find_proposal_and_patches_by_branch_name<'a>( | |||
| 246 | } | 249 | } |
| 247 | }) | 250 | }) |
| 248 | } | 251 | } |
| 252 | |||
| 253 | pub fn join_with_and<T: ToString>(items: &[T]) -> String { | ||
| 254 | match items.len() { | ||
| 255 | 0 => String::new(), | ||
| 256 | 1 => items[0].to_string(), | ||
| 257 | _ => { | ||
| 258 | let last_item = items.last().unwrap().to_string(); | ||
| 259 | let rest = &items[..items.len() - 1]; | ||
| 260 | format!( | ||
| 261 | "{} and {}", | ||
| 262 | rest.iter() | ||
| 263 | .map(std::string::ToString::to_string) | ||
| 264 | .collect::<Vec<_>>() | ||
| 265 | .join(", "), | ||
| 266 | last_item | ||
| 267 | ) | ||
| 268 | } | ||
| 269 | } | ||
| 270 | } | ||
| 271 | |||
| 272 | /// get an ordered vector of server protocols to attempt | ||
| 273 | pub fn get_read_protocols_to_try( | ||
| 274 | server_url: &CloneUrl, | ||
| 275 | decoded_nostr_url: &NostrUrlDecoded, | ||
| 276 | ) -> Vec<ServerProtocol> { | ||
| 277 | if server_url.protocol() == ServerProtocol::Filesystem { | ||
| 278 | vec![(ServerProtocol::Filesystem)] | ||
| 279 | } else if let Some(protocol) = &decoded_nostr_url.protocol { | ||
| 280 | vec![protocol.clone()] | ||
| 281 | } else if server_url.protocol() == ServerProtocol::Http { | ||
| 282 | vec![ | ||
| 283 | ServerProtocol::UnauthHttp, | ||
| 284 | ServerProtocol::Ssh, | ||
| 285 | ServerProtocol::Http, | ||
| 286 | ] | ||
| 287 | } else if server_url.protocol() == ServerProtocol::Ftp { | ||
| 288 | vec![ServerProtocol::Ftp, ServerProtocol::Ssh] | ||
| 289 | } else { | ||
| 290 | vec![ | ||
| 291 | ServerProtocol::UnauthHttps, | ||
| 292 | ServerProtocol::Ssh, | ||
| 293 | ServerProtocol::Https, | ||
| 294 | ] | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | #[cfg(test)] | ||
| 299 | mod tests { | ||
| 300 | use super::*; | ||
| 301 | mod join_with_and { | ||
| 302 | use super::*; | ||
| 303 | #[test] | ||
| 304 | fn test_empty() { | ||
| 305 | let items: Vec<&str> = vec![]; | ||
| 306 | assert_eq!(join_with_and(&items), ""); | ||
| 307 | } | ||
| 308 | |||
| 309 | #[test] | ||
| 310 | fn test_single_item() { | ||
| 311 | let items = vec!["apple"]; | ||
| 312 | assert_eq!(join_with_and(&items), "apple"); | ||
| 313 | } | ||
| 314 | |||
| 315 | #[test] | ||
| 316 | fn test_two_items() { | ||
| 317 | let items = vec!["apple", "banana"]; | ||
| 318 | assert_eq!(join_with_and(&items), "apple and banana"); | ||
| 319 | } | ||
| 320 | |||
| 321 | #[test] | ||
| 322 | fn test_three_items() { | ||
| 323 | let items = vec!["apple", "banana", "cherry"]; | ||
| 324 | assert_eq!(join_with_and(&items), "apple, banana and cherry"); | ||
| 325 | } | ||
| 326 | |||
| 327 | #[test] | ||
| 328 | fn test_four_items() { | ||
| 329 | let items = vec!["apple", "banana", "cherry", "date"]; | ||
| 330 | assert_eq!(join_with_and(&items), "apple, banana, cherry and date"); | ||
| 331 | } | ||
| 332 | |||
| 333 | #[test] | ||
| 334 | fn test_multiple_items() { | ||
| 335 | let items = vec!["one", "two", "three", "four", "five"]; | ||
| 336 | assert_eq!(join_with_and(&items), "one, two, three, four and five"); | ||
| 337 | } | ||
| 338 | } | ||
| 339 | } | ||