diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-07-18 17:33:11 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-07-18 17:33:11 +0100 |
| commit | a3d4c8eaa263f4adb174ac81c4248fa200e1857e (patch) | |
| tree | 0b786013c3b3da67123e2ff02f95b7c41eb8dfc0 /src | |
| parent | 3eb2354edb8e76428625d5645e110c30aa1ccc2a (diff) | |
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
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/git_remote_nostr/fetch.rs | 105 | ||||
| -rw-r--r-- | src/bin/ngit/sub_commands/list.rs | 6 | ||||
| -rw-r--r-- | src/lib/client.rs | 10 | ||||
| -rw-r--r-- | 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 @@ | |||
| 1 | use core::str; | 1 | use core::str; |
| 2 | use std::{ | 2 | use std::{ |
| 3 | collections::HashMap, | 3 | collections::{HashMap, HashSet}, |
| 4 | io::Stdin, | 4 | io::Stdin, |
| 5 | sync::{Arc, Mutex}, | 5 | sync::{Arc, Mutex}, |
| 6 | time::Instant, | 6 | time::Instant, |
| @@ -16,7 +16,7 @@ use ngit::{ | |||
| 16 | nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, | 16 | nostr_url::{CloneUrl, NostrUrlDecoded, ServerProtocol}, |
| 17 | utils::check_ssh_keys, | 17 | utils::check_ssh_keys, |
| 18 | }, | 18 | }, |
| 19 | git_events::tag_value, | 19 | git_events::{KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, tag_value}, |
| 20 | login::get_curent_user, | 20 | login::get_curent_user, |
| 21 | repo_ref::{RepoRef, is_grasp_server}, | 21 | repo_ref::{RepoRef, is_grasp_server}, |
| 22 | }; | 22 | }; |
| @@ -37,38 +37,78 @@ pub async fn run_fetch( | |||
| 37 | ) -> Result<()> { | 37 | ) -> Result<()> { |
| 38 | let mut fetch_batch = get_oids_from_fetch_batch(stdin, oid, refstr)?; | 38 | let mut fetch_batch = get_oids_from_fetch_batch(stdin, oid, refstr)?; |
| 39 | 39 | ||
| 40 | let oids_from_git_servers = fetch_batch | 40 | let oids_from_state = fetch_batch |
| 41 | .iter() | 41 | .iter() |
| 42 | .filter(|(refstr, _)| !refstr.contains("refs/heads/pr/")) | 42 | .filter(|(refstr, _)| !refstr.contains("refs/heads/pr/")) |
| 43 | .map(|(_, oid)| oid.clone()) | 43 | .map(|(_, oid)| oid.clone()) |
| 44 | .collect::<Vec<String>>(); | 44 | .collect::<Vec<String>>(); |
| 45 | 45 | ||
| 46 | let pr_oid_clone_url_map = identify_clone_urls_for_oids_from_pr_pr_update_events( | ||
| 47 | fetch_batch.values().collect::<Vec<&String>>(), | ||
| 48 | git_repo, | ||
| 49 | repo_ref, | ||
| 50 | ) | ||
| 51 | .await?; | ||
| 52 | |||
| 53 | let oids_to_fetch_from_git_servers = [ | ||
| 54 | oids_from_state.clone(), | ||
| 55 | pr_oid_clone_url_map | ||
| 56 | .keys() | ||
| 57 | .cloned() | ||
| 58 | .collect::<Vec<String>>(), | ||
| 59 | ] | ||
| 60 | .concat(); | ||
| 61 | |||
| 62 | let git_servers = { | ||
| 63 | let mut seen: HashSet<String> = HashSet::new(); | ||
| 64 | let mut out: Vec<String> = vec![]; | ||
| 65 | for server in &repo_ref.git_server { | ||
| 66 | if seen.insert(server.clone()) { | ||
| 67 | out.push(server.clone()); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | for url in pr_oid_clone_url_map.values().flatten() { | ||
| 71 | if seen.insert(url.clone()) { | ||
| 72 | out.push(url.clone()); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | out | ||
| 76 | }; | ||
| 77 | |||
| 46 | let mut errors = vec![]; | 78 | let mut errors = vec![]; |
| 47 | let term = console::Term::stderr(); | 79 | let term = console::Term::stderr(); |
| 48 | 80 | ||
| 49 | for git_server_url in &repo_ref.git_server { | 81 | for git_server_url in &git_servers { |
| 82 | let oids_to_fetch_from_server = oids_to_fetch_from_git_servers | ||
| 83 | .clone() | ||
| 84 | .into_iter() | ||
| 85 | .filter(|oid| !git_repo.does_commit_exist(oid).unwrap_or(false)) | ||
| 86 | .collect::<Vec<String>>(); | ||
| 87 | |||
| 88 | if oids_to_fetch_from_server.is_empty() { | ||
| 89 | continue; | ||
| 90 | } | ||
| 91 | |||
| 50 | let term = console::Term::stderr(); | 92 | let term = console::Term::stderr(); |
| 51 | if let Err(error) = fetch_from_git_server( | 93 | if let Err(error) = fetch_from_git_server( |
| 52 | git_repo, | 94 | git_repo, |
| 53 | &oids_from_git_servers, | 95 | &oids_from_state, |
| 54 | git_server_url, | 96 | git_server_url, |
| 55 | &repo_ref.to_nostr_git_url(&None), | 97 | &repo_ref.to_nostr_git_url(&None), |
| 56 | &term, | 98 | &term, |
| 57 | is_grasp_server(git_server_url, &repo_ref.grasp_servers()), | 99 | is_grasp_server(git_server_url, &repo_ref.grasp_servers()), |
| 58 | ) { | 100 | ) { |
| 59 | errors.push(error); | 101 | errors.push(error); |
| 60 | } else { | ||
| 61 | break; | ||
| 62 | } | 102 | } |
| 63 | } | 103 | } |
| 64 | 104 | ||
| 65 | if oids_from_git_servers | 105 | if oids_from_state |
| 66 | .iter() | 106 | .iter() |
| 67 | .any(|oid| !git_repo.does_commit_exist(oid).unwrap()) | 107 | .any(|oid| !git_repo.does_commit_exist(oid).unwrap()) |
| 68 | && !errors.is_empty() | 108 | && !errors.is_empty() |
| 69 | { | 109 | { |
| 70 | bail!( | 110 | bail!( |
| 71 | "fetch: failed to fetch objects in nostr state event from:\r\n{}", | 111 | "fetch: failed to fetch objects from:\r\n{}", |
| 72 | errors | 112 | errors |
| 73 | .iter() | 113 | .iter() |
| 74 | .map(|e| format!(" - {e}")) | 114 | .map(|e| format!(" - {e}")) |
| @@ -79,12 +119,43 @@ pub async fn run_fetch( | |||
| 79 | 119 | ||
| 80 | fetch_batch.retain(|refstr, _| refstr.contains("refs/heads/pr/")); | 120 | fetch_batch.retain(|refstr, _| refstr.contains("refs/heads/pr/")); |
| 81 | 121 | ||
| 82 | fetch_open_or_draft_proposals(git_repo, &term, repo_ref, &fetch_batch).await?; | 122 | fetch_open_or_draft_proposals_from_patches(git_repo, &term, repo_ref, &fetch_batch).await?; |
| 123 | // TODO fetch_open_or_draft_proposals just needs to do it for patches | ||
| 83 | term.flush()?; | 124 | term.flush()?; |
| 84 | println!(); | 125 | println!(); |
| 85 | Ok(()) | 126 | Ok(()) |
| 86 | } | 127 | } |
| 87 | 128 | ||
| 129 | async fn identify_clone_urls_for_oids_from_pr_pr_update_events( | ||
| 130 | oids: Vec<&String>, | ||
| 131 | git_repo: &Repo, | ||
| 132 | repo_ref: &RepoRef, | ||
| 133 | ) -> Result<HashMap<String, Vec<String>>> { | ||
| 134 | let mut map: HashMap<String, Vec<String>> = HashMap::new(); | ||
| 135 | |||
| 136 | let open_and_draft_proposals = get_open_or_draft_proposals(git_repo, repo_ref).await?; | ||
| 137 | |||
| 138 | for (_, (_, events)) in open_and_draft_proposals { | ||
| 139 | for event in events { | ||
| 140 | if [KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE].contains(&event.kind) { | ||
| 141 | if let Ok(c) = tag_value(&event, "c") { | ||
| 142 | if oids.contains(&&c) { | ||
| 143 | for tag in event.tags.as_slice() { | ||
| 144 | if tag.kind().eq(&nostr::event::TagKind::Clone) { | ||
| 145 | for clone_url in tag.as_slice().iter().skip(1) { | ||
| 146 | map.entry(c.clone()).or_default().push(clone_url.clone()); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | Ok(map) | ||
| 157 | } | ||
| 158 | |||
| 88 | pub fn make_commits_for_proposal( | 159 | pub fn make_commits_for_proposal( |
| 89 | git_repo: &Repo, | 160 | git_repo: &Repo, |
| 90 | repo_ref: &RepoRef, | 161 | repo_ref: &RepoRef, |
| @@ -128,7 +199,7 @@ pub fn make_commits_for_proposal( | |||
| 128 | Ok(tip_commit_id) | 199 | Ok(tip_commit_id) |
| 129 | } | 200 | } |
| 130 | 201 | ||
| 131 | async fn fetch_open_or_draft_proposals( | 202 | async fn fetch_open_or_draft_proposals_from_patches( |
| 132 | git_repo: &Repo, | 203 | git_repo: &Repo, |
| 133 | term: &console::Term, | 204 | term: &console::Term, |
| 134 | repo_ref: &RepoRef, | 205 | repo_ref: &RepoRef, |
| @@ -140,12 +211,19 @@ async fn fetch_open_or_draft_proposals( | |||
| 140 | let current_user = get_curent_user(git_repo)?; | 211 | let current_user = get_curent_user(git_repo)?; |
| 141 | 212 | ||
| 142 | for refstr in proposal_refs.keys() { | 213 | for refstr in proposal_refs.keys() { |
| 143 | if let Some((_, (_, patches))) = find_proposal_and_patches_by_branch_name( | 214 | if let Some((_, (_, events_to_apply))) = find_proposal_and_patches_by_branch_name( |
| 144 | refstr, | 215 | refstr, |
| 145 | &open_and_draft_proposals, | 216 | &open_and_draft_proposals, |
| 146 | current_user.as_ref(), | 217 | current_user.as_ref(), |
| 147 | ) { | 218 | ) { |
| 148 | if let Err(error) = make_commits_for_proposal(git_repo, repo_ref, patches) { | 219 | if events_to_apply |
| 220 | .iter() | ||
| 221 | .any(|e| e.kind.eq(&KIND_PULL_REQUEST) || e.kind.eq(&KIND_PULL_REQUEST_UPDATE)) | ||
| 222 | { | ||
| 223 | // do nothing - we fetch these oids as part of run_fetch | ||
| 224 | } else if let Err(error) = | ||
| 225 | make_commits_for_proposal(git_repo, repo_ref, events_to_apply) | ||
| 226 | { | ||
| 149 | term.write_line( | 227 | term.write_line( |
| 150 | format!("WARNING: failed to create branch for {refstr}, error: {error}",) | 228 | format!("WARNING: failed to create branch for {refstr}, error: {error}",) |
| 151 | .as_str(), | 229 | .as_str(), |
| @@ -429,6 +507,7 @@ fn fetch_from_git_server_url( | |||
| 429 | remote_callbacks.credentials(auth.credentials(&git_config)); | 507 | remote_callbacks.credentials(auth.credentials(&git_config)); |
| 430 | } | 508 | } |
| 431 | fetch_options.remote_callbacks(remote_callbacks); | 509 | fetch_options.remote_callbacks(remote_callbacks); |
| 510 | |||
| 432 | git_server_remote.download(oids, Some(&mut fetch_options))?; | 511 | git_server_remote.download(oids, Some(&mut fetch_options))?; |
| 433 | 512 | ||
| 434 | git_server_remote.disconnect()?; | 513 | 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<()> { | |||
| 219 | PromptChoiceParms::default() | 219 | PromptChoiceParms::default() |
| 220 | .with_prompt("this is new PR event kind which ngit doesnt yet support") | 220 | .with_prompt("this is new PR event kind which ngit doesnt yet support") |
| 221 | .with_default(0) | 221 | .with_default(0) |
| 222 | .with_choices(vec!["back to proposals".to_string()]), | 222 | .with_choices(vec![ |
| 223 | // TODO enable checkout by fetching oids, creating / updating branch and | ||
| 224 | // checking out | ||
| 225 | "back to proposals".to_string(), | ||
| 226 | ]), | ||
| 223 | )? { | 227 | )? { |
| 224 | 0 => continue, | 228 | 0 => continue, |
| 225 | _ => { | 229 | _ => { |
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::{ | |||
| 49 | git::{Repo, RepoActions, get_git_config_item}, | 49 | git::{Repo, RepoActions, get_git_config_item}, |
| 50 | git_events::{ | 50 | git_events::{ |
| 51 | KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, event_is_cover_letter, | 51 | KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE, event_is_cover_letter, |
| 52 | event_is_patch_set_root, event_is_revision_root, status_kinds, | 52 | event_is_patch_set_root, event_is_revision_root, event_is_valid_pr_or_pr_update, |
| 53 | status_kinds, | ||
| 53 | }, | 54 | }, |
| 54 | login::{get_likely_logged_in_user, user::get_user_ref_from_cache}, | 55 | login::{get_likely_logged_in_user, user::get_user_ref_from_cache}, |
| 55 | repo_ref::RepoRef, | 56 | repo_ref::RepoRef, |
| @@ -1824,6 +1825,7 @@ pub async fn get_proposals_and_revisions_from_cache( | |||
| 1824 | .await? | 1825 | .await? |
| 1825 | .iter() | 1826 | .iter() |
| 1826 | .filter(|e| event_is_patch_set_root(e) || e.kind.eq(&KIND_PULL_REQUEST)) | 1827 | .filter(|e| event_is_patch_set_root(e) || e.kind.eq(&KIND_PULL_REQUEST)) |
| 1828 | .filter(|e| e.kind.eq(&Kind::GitPatch) || event_is_valid_pr_or_pr_update(e)) | ||
| 1827 | .cloned() | 1829 | .cloned() |
| 1828 | .collect::<Vec<nostr::Event>>(); | 1830 | .collect::<Vec<nostr::Event>>(); |
| 1829 | proposals.sort_by_key(|e| e.created_at); | 1831 | 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( | |||
| 1874 | .iter() | 1876 | .iter() |
| 1875 | .copied() | 1877 | .copied() |
| 1876 | .collect(); | 1878 | .collect(); |
| 1877 | commit_events.retain(|e| permissioned_users.contains(&e.pubkey)); | 1879 | |
| 1880 | commit_events.retain(|e| { | ||
| 1881 | permissioned_users.contains(&e.pubkey) | ||
| 1882 | && (e.kind.eq(&Kind::GitPatch) || event_is_valid_pr_or_pr_update(e)) | ||
| 1883 | }); | ||
| 1878 | 1884 | ||
| 1879 | let revision_roots: HashSet<nostr::EventId> = commit_events | 1885 | let revision_roots: HashSet<nostr::EventId> = commit_events |
| 1880 | .iter() | 1886 | .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 { | |||
| 90 | .any(|t| !t.as_slice().is_empty() && t.as_slice()[0].eq("commit-pgp-sig")) | 90 | .any(|t| !t.as_slice().is_empty() && t.as_slice()[0].eq("commit-pgp-sig")) |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | pub fn event_is_valid_pr_or_pr_update(event: &Event) -> bool { | ||
| 94 | [KIND_PULL_REQUEST, KIND_PULL_REQUEST_UPDATE].contains(&event.kind) | ||
| 95 | && event.tags.iter().any(|t| { | ||
| 96 | t.as_slice().len().gt(&1) | ||
| 97 | && t.as_slice()[0].eq("c") | ||
| 98 | && git2::Oid::from_str(&t.as_slice()[1]).is_ok() | ||
| 99 | }) | ||
| 100 | && event | ||
| 101 | .tags | ||
| 102 | .iter() | ||
| 103 | .any(|t| t.as_slice().len().gt(&1) && t.as_slice()[0].eq("clone")) | ||
| 104 | } | ||
| 105 | |||
| 93 | #[allow(clippy::too_many_arguments)] | 106 | #[allow(clippy::too_many_arguments)] |
| 94 | #[allow(clippy::too_many_lines)] | 107 | #[allow(clippy::too_many_lines)] |
| 95 | pub async fn generate_patch_event( | 108 | pub async fn generate_patch_event( |