From a3d4c8eaa263f4adb174ac81c4248fa200e1857e Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 18 Jul 2025 17:33:11 +0100 Subject: feat(pr): fetch pr and pr updates from clone urls we try and get them from clone urls of repo and fallback to those specified by contributor --- src/bin/git_remote_nostr/fetch.rs | 105 +++++++++++++++++++++++++++++++++----- src/bin/ngit/sub_commands/list.rs | 6 ++- src/lib/client.rs | 10 +++- src/lib/git_events.rs | 13 +++++ 4 files changed, 118 insertions(+), 16 deletions(-) diff --git a/src/bin/git_remote_nostr/fetch.rs b/src/bin/git_remote_nostr/fetch.rs index b191850..f3d4362 100644 --- a/src/bin/git_remote_nostr/fetch.rs +++ b/src/bin/git_remote_nostr/fetch.rs @@ -1,6 +1,6 @@ use core::str; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, io::Stdin, sync::{Arc, Mutex}, time::Instant, @@ -16,7 +16,7 @@ use ngit::{ nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, utils::check_ssh_keys, }, - git_events::tag_value, + git_events::{KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, tag_value}, login::get_curent_user, repo_ref::{RepoRef, is_grasp_server}, }; @@ -37,38 +37,78 @@ pub async fn run_fetch( ) -> Result<()> { let mut fetch_batch = get_oids_from_fetch_batch(stdin, oid, refstr)?; - let oids_from_git_servers = fetch_batch + let oids_from_state = fetch_batch .iter() .filter(|(refstr, _)| !refstr.contains("refs/heads/pr/")) .map(|(_, oid)| oid.clone()) .collect::>(); + let pr_oid_clone_url_map = identify_clone_urls_for_oids_from_pr_pr_update_events( + fetch_batch.values().collect::>(), + git_repo, + repo_ref, + ) + .await?; + + let oids_to_fetch_from_git_servers = [ + oids_from_state.clone(), + pr_oid_clone_url_map + .keys() + .cloned() + .collect::>(), + ] + .concat(); + + let git_servers = { + let mut seen: HashSet = HashSet::new(); + let mut out: Vec = vec![]; + for server in &repo_ref.git_server { + if seen.insert(server.clone()) { + out.push(server.clone()); + } + } + for url in pr_oid_clone_url_map.values().flatten() { + if seen.insert(url.clone()) { + out.push(url.clone()); + } + } + out + }; + let mut errors = vec![]; let term = console::Term::stderr(); - for git_server_url in &repo_ref.git_server { + for git_server_url in &git_servers { + let oids_to_fetch_from_server = oids_to_fetch_from_git_servers + .clone() + .into_iter() + .filter(|oid| !git_repo.does_commit_exist(oid).unwrap_or(false)) + .collect::>(); + + if oids_to_fetch_from_server.is_empty() { + continue; + } + let term = console::Term::stderr(); if let Err(error) = fetch_from_git_server( git_repo, - &oids_from_git_servers, + &oids_from_state, git_server_url, &repo_ref.to_nostr_git_url(&None), &term, is_grasp_server(git_server_url, &repo_ref.grasp_servers()), ) { errors.push(error); - } else { - break; } } - if oids_from_git_servers + if oids_from_state .iter() .any(|oid| !git_repo.does_commit_exist(oid).unwrap()) && !errors.is_empty() { bail!( - "fetch: failed to fetch objects in nostr state event from:\r\n{}", + "fetch: failed to fetch objects from:\r\n{}", errors .iter() .map(|e| format!(" - {e}")) @@ -79,12 +119,43 @@ pub async fn run_fetch( fetch_batch.retain(|refstr, _| refstr.contains("refs/heads/pr/")); - fetch_open_or_draft_proposals(git_repo, &term, repo_ref, &fetch_batch).await?; + fetch_open_or_draft_proposals_from_patches(git_repo, &term, repo_ref, &fetch_batch).await?; + // TODO fetch_open_or_draft_proposals just needs to do it for patches term.flush()?; println!(); Ok(()) } +async fn identify_clone_urls_for_oids_from_pr_pr_update_events( + oids: Vec<&String>, + git_repo: &Repo, + repo_ref: &RepoRef, +) -> Result>> { + let mut map: HashMap> = HashMap::new(); + + let open_and_draft_proposals = get_open_or_draft_proposals(git_repo, repo_ref).await?; + + for (_, (_, events)) in open_and_draft_proposals { + for event in events { + if [KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE].contains(&event.kind) { + if let Ok(c) = tag_value(&event, "c") { + if oids.contains(&&c) { + for tag in event.tags.as_slice() { + if tag.kind().eq(&nostr::event::TagKind::Clone) { + for clone_url in tag.as_slice().iter().skip(1) { + map.entry(c.clone()).or_default().push(clone_url.clone()); + } + } + } + } + } + } + } + } + + Ok(map) +} + pub fn make_commits_for_proposal( git_repo: &Repo, repo_ref: &RepoRef, @@ -128,7 +199,7 @@ pub fn make_commits_for_proposal( Ok(tip_commit_id) } -async fn fetch_open_or_draft_proposals( +async fn fetch_open_or_draft_proposals_from_patches( git_repo: &Repo, term: &console::Term, repo_ref: &RepoRef, @@ -140,12 +211,19 @@ async fn fetch_open_or_draft_proposals( let current_user = get_curent_user(git_repo)?; for refstr in proposal_refs.keys() { - if let Some((_, (_, patches))) = find_proposal_and_patches_by_branch_name( + if let Some((_, (_, events_to_apply))) = find_proposal_and_patches_by_branch_name( refstr, &open_and_draft_proposals, current_user.as_ref(), ) { - if let Err(error) = make_commits_for_proposal(git_repo, repo_ref, patches) { + if events_to_apply + .iter() + .any(|e| e.kind.eq(&KIND_PULL_REQUEST) || e.kind.eq(&KIND_PULL_REQUEST_UPDATE)) + { + // do nothing - we fetch these oids as part of run_fetch + } else if let Err(error) = + make_commits_for_proposal(git_repo, repo_ref, events_to_apply) + { term.write_line( format!("WARNING: failed to create branch for {refstr}, error: {error}",) .as_str(), @@ -429,6 +507,7 @@ fn fetch_from_git_server_url( remote_callbacks.credentials(auth.credentials(&git_config)); } fetch_options.remote_callbacks(remote_callbacks); + git_server_remote.download(oids, Some(&mut fetch_options))?; git_server_remote.disconnect()?; diff --git a/src/bin/ngit/sub_commands/list.rs b/src/bin/ngit/sub_commands/list.rs index a90b28e..9e35b33 100644 --- a/src/bin/ngit/sub_commands/list.rs +++ b/src/bin/ngit/sub_commands/list.rs @@ -219,7 +219,11 @@ pub async fn launch() -> Result<()> { PromptChoiceParms::default() .with_prompt("this is new PR event kind which ngit doesnt yet support") .with_default(0) - .with_choices(vec!["back to proposals".to_string()]), + .with_choices(vec![ + // TODO enable checkout by fetching oids, creating / updating branch and + // checking out + "back to proposals".to_string(), + ]), )? { 0 => continue, _ => { diff --git a/src/lib/client.rs b/src/lib/client.rs index 1f3b08c..091d68d 100644 --- a/src/lib/client.rs +++ b/src/lib/client.rs @@ -49,7 +49,8 @@ use crate::{ git::{Repo, RepoActions, get_git_config_item}, git_events::{ KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, event_is_cover_letter, - event_is_patch_set_root, event_is_revision_root, status_kinds, + event_is_patch_set_root, event_is_revision_root, event_is_valid_pr_or_pr_update, + status_kinds, }, login::{get_likely_logged_in_user, user::get_user_ref_from_cache}, repo_ref::RepoRef, @@ -1824,6 +1825,7 @@ pub async fn get_proposals_and_revisions_from_cache( .await? .iter() .filter(|e| event_is_patch_set_root(e) || e.kind.eq(&KIND_PULL_REQUEST)) + .filter(|e| e.kind.eq(&Kind::GitPatch) || event_is_valid_pr_or_pr_update(e)) .cloned() .collect::>(); proposals.sort_by_key(|e| e.created_at); @@ -1874,7 +1876,11 @@ pub async fn get_all_proposal_patch_pr_pr_update_events_from_cache( .iter() .copied() .collect(); - commit_events.retain(|e| permissioned_users.contains(&e.pubkey)); + + commit_events.retain(|e| { + permissioned_users.contains(&e.pubkey) + && (e.kind.eq(&Kind::GitPatch) || event_is_valid_pr_or_pr_update(e)) + }); let revision_roots: HashSet = commit_events .iter() diff --git a/src/lib/git_events.rs b/src/lib/git_events.rs index 7b25daf..09ec040 100644 --- a/src/lib/git_events.rs +++ b/src/lib/git_events.rs @@ -90,6 +90,19 @@ pub fn patch_supports_commit_ids(event: &Event) -> bool { .any(|t| !t.as_slice().is_empty() && t.as_slice()[0].eq("commit-pgp-sig")) } +pub fn event_is_valid_pr_or_pr_update(event: &Event) -> bool { + [KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE].contains(&event.kind) + && event.tags.iter().any(|t| { + t.as_slice().len().gt(&1) + && t.as_slice()[0].eq("c") + && git2::Oid::from_str(&t.as_slice()[1]).is_ok() + }) + && event + .tags + .iter() + .any(|t| t.as_slice().len().gt(&1) && t.as_slice()[0].eq("clone")) +} + #[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_lines)] pub async fn generate_patch_event( -- cgit v1.2.3