diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-09-23 10:46:26 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-09-23 13:36:53 +0100 |
| commit | 2cdbb8c7ac6c98af6e36c4458c08bef2299794e1 (patch) | |
| tree | fcf7c56d2f5eda0776643f8a52090965f1de06a8 /src/bin/git_remote_nostr/fetch.rs | |
| parent | 51358320c50afece31fc25945a09e3d7aac8f39c (diff) | |
fix(remote): add resilience to `prs`
make poorly formatted patches fail silently. we stop trusting that the
`commit` tag in the latest patch can be produced by apply the patches.
to achieve this we must recreate the commit during the list command,
which require fetching the parent oids.
support patches without optional `commit` and `parent-commit` tags.
Diffstat (limited to 'src/bin/git_remote_nostr/fetch.rs')
| -rw-r--r-- | src/bin/git_remote_nostr/fetch.rs | 117 |
1 files changed, 75 insertions, 42 deletions
diff --git a/src/bin/git_remote_nostr/fetch.rs b/src/bin/git_remote_nostr/fetch.rs index 2e16297..46e7ad3 100644 --- a/src/bin/git_remote_nostr/fetch.rs +++ b/src/bin/git_remote_nostr/fetch.rs | |||
| @@ -1,11 +1,12 @@ | |||
| 1 | use core::str; | 1 | use core::str; |
| 2 | use std::{ | 2 | use std::{ |
| 3 | collections::HashMap, | ||
| 3 | io::Stdin, | 4 | io::Stdin, |
| 4 | sync::{Arc, Mutex}, | 5 | sync::{Arc, Mutex}, |
| 5 | time::Instant, | 6 | time::Instant, |
| 6 | }; | 7 | }; |
| 7 | 8 | ||
| 8 | use anyhow::{anyhow, bail, Result}; | 9 | use anyhow::{anyhow, bail, Context, Result}; |
| 9 | use auth_git2::GitAuthenticator; | 10 | use auth_git2::GitAuthenticator; |
| 10 | use git2::{Progress, Repository}; | 11 | use git2::{Progress, Repository}; |
| 11 | use ngit::{ | 12 | use ngit::{ |
| @@ -19,7 +20,7 @@ use ngit::{ | |||
| 19 | repo_ref::RepoRef, | 20 | repo_ref::RepoRef, |
| 20 | }; | 21 | }; |
| 21 | use nostr::nips::nip19; | 22 | use nostr::nips::nip19; |
| 22 | use nostr_sdk::ToBech32; | 23 | use nostr_sdk::{Event, ToBech32}; |
| 23 | 24 | ||
| 24 | use crate::utils::{ | 25 | use crate::utils::{ |
| 25 | count_lines_per_msg_vec, fetch_or_list_error_is_not_authentication_failure, | 26 | count_lines_per_msg_vec, fetch_or_list_error_is_not_authentication_failure, |
| @@ -78,65 +79,97 @@ pub async fn run_fetch( | |||
| 78 | 79 | ||
| 79 | fetch_batch.retain(|refstr, _| refstr.contains("refs/heads/pr/")); | 80 | fetch_batch.retain(|refstr, _| refstr.contains("refs/heads/pr/")); |
| 80 | 81 | ||
| 81 | if !fetch_batch.is_empty() { | 82 | fetch_proposals(git_repo, &term, repo_ref, &fetch_batch).await?; |
| 83 | term.flush()?; | ||
| 84 | println!(); | ||
| 85 | Ok(()) | ||
| 86 | } | ||
| 87 | |||
| 88 | pub fn make_commits_for_proposal( | ||
| 89 | git_repo: &Repo, | ||
| 90 | repo_ref: &RepoRef, | ||
| 91 | patches_ancestor_last: &[Event], | ||
| 92 | ) -> Result<String> { | ||
| 93 | let patches_ancestor_first: Vec<&Event> = patches_ancestor_last.iter().rev().collect(); | ||
| 94 | let mut tip_commit_id = if let Ok(parent_commit) = tag_value( | ||
| 95 | patches_ancestor_first | ||
| 96 | .first() | ||
| 97 | .context("proposal should have at least one patch")?, | ||
| 98 | "parent-commit", | ||
| 99 | ) { | ||
| 100 | parent_commit | ||
| 101 | } else { | ||
| 102 | // TODO choose most recent commit on master before patch timestamp so it doesnt | ||
| 103 | // constantly get rebased | ||
| 104 | let (_, hash) = git_repo.get_main_or_master_branch()?; | ||
| 105 | hash.to_string() | ||
| 106 | }; | ||
| 107 | |||
| 108 | for patch in &patches_ancestor_first { | ||
| 109 | let commit_id = git_repo | ||
| 110 | .create_commit_from_patch(patch, Some(tip_commit_id.clone())) | ||
| 111 | .context(format!( | ||
| 112 | "cannot create commit for patch {}", | ||
| 113 | nip19::Nip19Event { | ||
| 114 | event_id: patch.id(), | ||
| 115 | author: Some(patch.author()), | ||
| 116 | kind: Some(patch.kind()), | ||
| 117 | relays: if let Some(relay) = repo_ref.relays.first() { | ||
| 118 | vec![relay.to_string()] | ||
| 119 | } else { | ||
| 120 | vec![] | ||
| 121 | }, | ||
| 122 | } | ||
| 123 | .to_bech32() | ||
| 124 | .unwrap_or_default() | ||
| 125 | ))?; | ||
| 126 | tip_commit_id = commit_id.to_string(); | ||
| 127 | } | ||
| 128 | Ok(tip_commit_id) | ||
| 129 | } | ||
| 130 | |||
| 131 | async fn fetch_proposals( | ||
| 132 | git_repo: &Repo, | ||
| 133 | term: &console::Term, | ||
| 134 | repo_ref: &RepoRef, | ||
| 135 | proposal_refs: &HashMap<String, String>, | ||
| 136 | ) -> Result<()> { | ||
| 137 | if !proposal_refs.is_empty() { | ||
| 82 | let open_proposals = get_open_proposals(git_repo, repo_ref).await?; | 138 | let open_proposals = get_open_proposals(git_repo, repo_ref).await?; |
| 83 | 139 | ||
| 84 | let current_user = get_curent_user(git_repo)?; | 140 | let current_user = get_curent_user(git_repo)?; |
| 85 | 141 | ||
| 86 | for (refstr, oid) in fetch_batch { | 142 | for refstr in proposal_refs.keys() { |
| 87 | if let Some((_, (_, patches))) = | 143 | if let Some((_, (_, patches))) = |
| 88 | find_proposal_and_patches_by_branch_name(&refstr, &open_proposals, ¤t_user) | 144 | find_proposal_and_patches_by_branch_name(refstr, &open_proposals, ¤t_user) |
| 89 | { | 145 | { |
| 90 | if !git_repo.does_commit_exist(&oid)? { | 146 | if let Err(error) = make_commits_for_proposal(git_repo, repo_ref, patches) { |
| 91 | let mut patches_ancestor_first = patches.clone(); | 147 | term.write_line( |
| 92 | patches_ancestor_first.reverse(); | 148 | format!("WARNING: cannot create branch for {refstr}, error: {error}",) |
| 93 | if git_repo.does_commit_exist(&tag_value( | 149 | .as_str(), |
| 94 | patches_ancestor_first.first().unwrap(), | 150 | )?; |
| 95 | "parent-commit", | 151 | break; |
| 96 | )?)? { | ||
| 97 | for patch in &patches_ancestor_first { | ||
| 98 | if let Err(error) = git_repo.create_commit_from_patch(patch) { | ||
| 99 | term.write_line( | ||
| 100 | format!( | ||
| 101 | "WARNING: cannot create branch for {refstr}, error: {error} for patch {}", | ||
| 102 | nip19::Nip19Event { | ||
| 103 | event_id: patch.id(), | ||
| 104 | author: Some(patch.author()), | ||
| 105 | kind: Some(patch.kind()), | ||
| 106 | relays: if let Some(relay) = repo_ref.relays.first() { | ||
| 107 | vec![relay.to_string()] | ||
| 108 | } else { vec![]}, | ||
| 109 | }.to_bech32().unwrap_or_default() | ||
| 110 | ) | ||
| 111 | .as_str(), | ||
| 112 | )?; | ||
| 113 | break; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } else { | ||
| 117 | term.write_line( | ||
| 118 | format!("WARNING: cannot find parent commit for {refstr}").as_str(), | ||
| 119 | )?; | ||
| 120 | } | ||
| 121 | } | 152 | } |
| 122 | } else { | ||
| 123 | term.write_line(format!("WARNING: cannot find proposal for {refstr}").as_str())?; | ||
| 124 | } | 153 | } |
| 125 | } | 154 | } |
| 126 | } | 155 | } |
| 127 | |||
| 128 | term.flush()?; | ||
| 129 | println!(); | ||
| 130 | Ok(()) | 156 | Ok(()) |
| 131 | } | 157 | } |
| 132 | 158 | ||
| 133 | fn fetch_from_git_server( | 159 | pub fn fetch_from_git_server( |
| 134 | git_repo: &Repo, | 160 | git_repo: &Repo, |
| 135 | oids: &[String], | 161 | oids: &[String], |
| 136 | git_server_url: &str, | 162 | git_server_url: &str, |
| 137 | decoded_nostr_url: &NostrUrlDecoded, | 163 | decoded_nostr_url: &NostrUrlDecoded, |
| 138 | term: &console::Term, | 164 | term: &console::Term, |
| 139 | ) -> Result<()> { | 165 | ) -> Result<()> { |
| 166 | let already_have_oids = oids | ||
| 167 | .iter() | ||
| 168 | .all(|oid| git_repo.does_commit_exist(oid).is_ok_and(|outcome| outcome)); | ||
| 169 | if already_have_oids { | ||
| 170 | return Ok(()); | ||
| 171 | } | ||
| 172 | |||
| 140 | let server_url = git_server_url.parse::<CloneUrl>()?; | 173 | let server_url = git_server_url.parse::<CloneUrl>()?; |
| 141 | 174 | ||
| 142 | let protocols_to_attempt = get_read_protocols_to_try(git_repo, &server_url, decoded_nostr_url); | 175 | let protocols_to_attempt = get_read_protocols_to_try(git_repo, &server_url, decoded_nostr_url); |