From f41e51e5840f1cc66fdec1eb4ac2ecf208c67911 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 11 Sep 2025 15:39:24 +0100 Subject: fix(sync): fetch refs missing locally before sync and fail more gracefully if refs cant be fetched, by continuing to sync other refs --- src/bin/ngit/sub_commands/sync.rs | 115 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/bin/ngit/sub_commands/sync.rs b/src/bin/ngit/sub_commands/sync.rs index 5578a0f..1a2d4ee 100644 --- a/src/bin/ngit/sub_commands/sync.rs +++ b/src/bin/ngit/sub_commands/sync.rs @@ -1,14 +1,19 @@ +use std::collections::HashMap; + use anyhow::{Context, Result, bail}; +use console::Term; use ngit::{ client::{ Client, Connect, Params, fetching_with_report, get_repo_ref_from_cache, get_state_from_cache, }, - git::{Repo, RepoActions}, + fetch::fetch_from_git_server, + git::{Repo, RepoActions, nostr_url::NostrUrlDecoded}, list::{get_ahead_behind, list_from_remotes}, push::push_to_remote, - repo_ref::get_repo_coordinates_when_remote_unknown, - utils::get_short_git_server_name, + repo_ref::{get_repo_coordinates_when_remote_unknown, is_grasp_server_clone_url}, + repo_state::RepoState, + utils::{get_short_git_server_name, join_with_and}, }; #[derive(Debug, clap::Args)] @@ -75,10 +80,13 @@ pub async fn launch(args: &SubCommandArgs) -> Result<()> { &term, &git_repo, &repo_ref.git_server, - &repo_ref.to_nostr_git_url(&None), + &decoded_nostr_url, &repo_ref.grasp_servers(), ); + let missing_refs = + fetch_missing_refs(&git_repo, &nostr_state, &remote_states, &decoded_nostr_url); + for (url, (remote_state, is_grasp_server)) in &remote_states { let remote_name = get_short_git_server_name(&git_repo, url); let mut refspecs = vec![]; @@ -116,6 +124,10 @@ pub async fn launch(args: &SubCommandArgs) -> Result<()> { continue; } } + // skip refs missing locally + if missing_refs.contains(nostr_ref_name) { + continue; + } if invalid_nostr_state_ref(nostr_ref_name) { // ensure nostr_state only supports refs/heads and refs/tags/ // and not refs/heads/prs/* @@ -209,6 +221,12 @@ pub async fn launch(args: &SubCommandArgs) -> Result<()> { } } + if !missing_refs.is_empty() { + println!( + "skipped the following refs as could not find them locally or on any git servers: {}", + join_with_and(&missing_refs) + ); + } Ok(()) } @@ -216,3 +234,92 @@ fn invalid_nostr_state_ref(ref_name: &str) -> bool { ref_name.starts_with("refs/heads/pr/") && !(ref_name.starts_with("refs/heads/") || ref_name.starts_with("refs/tags/")) } + +fn identify_missing_refs(git_repo: &Repo, state: &HashMap) -> Vec { + let mut missing_oids = vec![]; + for tip in state.values() { + if let Ok(exist) = git_repo.does_commit_exist(tip) { + if !exist { + missing_oids.push(tip.to_string()); + } + } + } + missing_oids +} + +/// returns refs that are still missing +fn fetch_missing_refs( + git_repo: &Repo, + nostr_state: &RepoState, + remote_states: &HashMap, bool)>, + nostr_url_decoded: &NostrUrlDecoded, +) -> Vec { + let mut tried_remotes: Vec = vec![]; + let required_oids = identify_missing_refs(git_repo, &nostr_state.state); + if !required_oids.is_empty() { + println!("fetching git data missing locally"); + } + loop { + let required_oids = identify_missing_refs(git_repo, &nostr_state.state); + let mut oids_on_remote: HashMap> = HashMap::new(); + if !required_oids.is_empty() { + for (url, (state, _)) in remote_states { + if tried_remotes.contains(url) { + continue; + } + for oid in &required_oids { + if state.values().any(|v| v.eq(oid)) { + oids_on_remote + .entry(url.to_string()) + .or_default() + .push(oid.clone()); + } + } + } + } + if let Some((url, oids)) = oids_on_remote.iter().max_by_key(|(url, vec)| { + if tried_remotes.contains(url) { + 0 + } else { + vec.len() + } + }) { + if oids.is_empty() || tried_remotes.contains(url) { + break; + } + tried_remotes.push(url.clone()); + let _ = fetch_from_git_server( + git_repo, + oids, + url, + nostr_url_decoded, + &Term::stdout(), + is_grasp_server_clone_url(url), + ); + } else { + break; + } + } + + let still_missing_oids = identify_missing_refs(git_repo, &nostr_state.state); + if still_missing_oids.is_empty() { + vec![] + } else { + let missing_refs: Vec = nostr_state + .state + .iter() + .filter_map(|(key, value)| { + if still_missing_oids.contains(value) { + Some(key.clone()) + } else { + None + } + }) + .collect(); + println!( + "could not find refs on repo git servers: {}", + join_with_and(&missing_refs) + ); + missing_refs + } +} -- cgit v1.2.3