From 513fce723c7e37aa353844303f36022517f2db43 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 25 Jul 2025 21:02:26 +0100 Subject: refactor: move `utils` and `list` helpers to lib to enable forthcoming `ngit sync` cmd --- src/lib/list.rs | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/lib/list.rs (limited to 'src/lib/list.rs') diff --git a/src/lib/list.rs b/src/lib/list.rs new file mode 100644 index 0000000..b940546 --- /dev/null +++ b/src/lib/list.rs @@ -0,0 +1,159 @@ +use std::collections::HashMap; + +use anyhow::{Result, anyhow}; +use auth_git2::GitAuthenticator; +use nostr::hashes::sha1::Hash as Sha1Hash; + +use crate::{ + git::{ + Repo, RepoActions, + nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, + }, + repo_ref::is_grasp_server, + utils::{Direction, get_read_protocols_to_try, join_with_and, set_protocol_preference}, +}; + +pub fn list_from_remotes( + term: &console::Term, + git_repo: &Repo, + git_servers: &Vec, + decoded_nostr_url: &NostrUrlDecoded, + grasp_servers: &[String], +) -> HashMap, bool)> { + let mut remote_states = HashMap::new(); + let mut errors = HashMap::new(); + for url in git_servers { + let is_grasp_server = is_grasp_server(url, grasp_servers); + match list_from_remote(term, git_repo, url, decoded_nostr_url, is_grasp_server) { + Err(error) => { + errors.insert(url, error); + } + Ok(state) => { + remote_states.insert(url.to_string(), (state, is_grasp_server)); + } + } + } + remote_states +} + +pub fn list_from_remote( + term: &console::Term, + git_repo: &Repo, + git_server_url: &str, + decoded_nostr_url: &NostrUrlDecoded, + is_grasp_server: bool, +) -> Result> { + let server_url = git_server_url.parse::()?; + let protocols_to_attempt = + get_read_protocols_to_try(git_repo, &server_url, decoded_nostr_url, is_grasp_server); + + let mut failed_protocols = vec![]; + let mut remote_state: Option> = None; + + for protocol in &protocols_to_attempt { + term.write_line( + format!( + "fetching {} ref list over {protocol}...", + server_url.short_name(), + ) + .as_str(), + )?; + + let formatted_url = server_url.format_as(protocol, &decoded_nostr_url.user)?; + let res = list_from_remote_url( + git_repo, + &formatted_url, + [ServerProtocol::UnauthHttps, ServerProtocol::UnauthHttp].contains(protocol), + term, + ); + + match res { + Ok(state) => { + remote_state = Some(state); + term.clear_last_lines(1)?; + if !failed_protocols.is_empty() { + term.write_line( + format!( + "list: succeeded over {protocol} from {}", + server_url.short_name(), + ) + .as_str(), + )?; + let _ = + set_protocol_preference(git_repo, protocol, &server_url, &Direction::Fetch); + } + break; + } + Err(error) => { + term.clear_last_lines(1)?; + term.write_line( + format!("list: {formatted_url} failed over {protocol}: {error}").as_str(), + )?; + failed_protocols.push(protocol); + } + } + } + if let Some(remote_state) = remote_state { + if failed_protocols.is_empty() { + term.clear_last_lines(1)?; + } + Ok(remote_state) + } else { + let error = anyhow!( + "{} failed over {}{}", + server_url.short_name(), + join_with_and(&failed_protocols), + if decoded_nostr_url.protocol.is_some() { + " and nostr url contains protocol override so no other protocols were attempted" + } else { + "" + }, + ); + term.write_line(format!("list: {error}").as_str())?; + Err(error) + } +} + +fn list_from_remote_url( + git_repo: &Repo, + git_server_remote_url: &str, + dont_authenticate: bool, + term: &console::Term, +) -> Result> { + let git_config = git_repo.git_repo.config()?; + + let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_remote_url)?; + // authentication may be required + let auth = GitAuthenticator::default(); + let mut remote_callbacks = git2::RemoteCallbacks::new(); + if !dont_authenticate { + remote_callbacks.credentials(auth.credentials(&git_config)); + } + term.write_line("list: connecting...")?; + git_server_remote.connect_auth(git2::Direction::Fetch, Some(remote_callbacks), None)?; + term.clear_last_lines(1)?; + let mut state = HashMap::new(); + for head in git_server_remote.list()? { + if let Some(symbolic_reference) = head.symref_target() { + state.insert( + head.name().to_string(), + format!("ref: {symbolic_reference}"), + ); + // ignore dereferenced tags + } else if !head.name().to_string().ends_with("^{}") { + state.insert(head.name().to_string(), head.oid().to_string()); + } + } + git_server_remote.disconnect()?; + Ok(state) +} + +pub fn get_ahead_behind( + git_repo: &Repo, + base_ref_or_oid: &str, + latest_ref_or_oid: &str, +) -> Result<(Vec, Vec)> { + let base = git_repo.get_commit_or_tip_of_reference(base_ref_or_oid)?; + let latest = git_repo.get_commit_or_tip_of_reference(latest_ref_or_oid)?; + git_repo.get_commits_ahead_behind(&base, &latest) +} -- cgit v1.2.3