upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/bin/git_remote_nostr/list.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/git_remote_nostr/list.rs')
-rw-r--r--src/bin/git_remote_nostr/list.rs82
1 files changed, 64 insertions, 18 deletions
diff --git a/src/bin/git_remote_nostr/list.rs b/src/bin/git_remote_nostr/list.rs
index a32ed67..23b3b98 100644
--- a/src/bin/git_remote_nostr/list.rs
+++ b/src/bin/git_remote_nostr/list.rs
@@ -32,13 +32,14 @@ pub async fn run_list(
32 if is_verbose() { 32 if is_verbose() {
33 term.write_line("git servers: listing refs...")?; 33 term.write_line("git servers: listing refs...")?;
34 } 34 }
35 let nostr_git_url = repo_ref.to_nostr_git_url(&None);
35 // nostr_state is passed to list_from_remotes only for the sync-status 36 // nostr_state is passed to list_from_remotes only for the sync-status
36 // display; the actual ref state we advertise is determined below. 37 // display; the actual ref state we advertise is determined below.
37 let remote_states = list_from_remotes( 38 let remote_states = list_from_remotes(
38 &term, 39 &term,
39 git_repo, 40 git_repo,
40 &repo_ref.git_server, 41 &repo_ref.git_server,
41 &repo_ref.to_nostr_git_url(&None), 42 &nostr_git_url,
42 nostr_state.as_ref(), 43 nostr_state.as_ref(),
43 ) 44 )
44 .await; 45 .await;
@@ -134,6 +135,7 @@ pub async fn run_list(
134 135
135/// fetches branches and tags from git servers so patch parent commits can be 136/// fetches branches and tags from git servers so patch parent commits can be
136/// used to build patches with correct commit ids 137/// used to build patches with correct commit ids
138#[allow(clippy::too_many_lines)]
137async fn get_open_and_draft_proposals_state( 139async fn get_open_and_draft_proposals_state(
138 term: &console::Term, 140 term: &console::Term,
139 git_repo: &Repo, 141 git_repo: &Repo,
@@ -156,8 +158,6 @@ async fn get_open_and_draft_proposals_state(
156 .filter(|v| !v.starts_with("ref: ")) 158 .filter(|v| !v.starts_with("ref: "))
157 .cloned() 159 .cloned()
158 .collect::<Vec<String>>(), 160 .collect::<Vec<String>>(),
159 // TODO we could fetch the oids of Pull Requests and Pull Request Updates to prevent
160 // having repeat fetching during the git remote helper fetch phase
161 git_server_url, 161 git_server_url,
162 &repo_ref.to_nostr_git_url(&None), 162 &repo_ref.to_nostr_git_url(&None),
163 term, 163 term,
@@ -169,8 +169,62 @@ async fn get_open_and_draft_proposals_state(
169 } 169 }
170 } 170 }
171 171
172 let mut state = HashMap::new();
173 let open_and_draft_proposals = get_open_or_draft_proposals(git_repo, repo_ref).await?; 172 let open_and_draft_proposals = get_open_or_draft_proposals(git_repo, repo_ref).await?;
173
174 // Collect PR/PR-update tip OIDs that are still missing after the bulk prefetch.
175 // We borrow proposals here so we can move them in the state-building loop
176 // below.
177 let mut missing_pr_oids: Vec<String> = open_and_draft_proposals
178 .values()
179 .filter_map(|(_, events)| {
180 events
181 .iter()
182 .find(|e| e.kind.eq(&KIND_PULL_REQUEST) || e.kind.eq(&KIND_PULL_REQUEST_UPDATE))
183 .and_then(|e| tag_value(e, "c").ok())
184 })
185 .filter(|tip| !git_repo.does_commit_exist(tip).unwrap_or(false))
186 .collect();
187
188 // For each repo git server, batch-fetch the PR tip OIDs it carries that are
189 // still missing locally. Only OIDs the server has advertised are included in
190 // each batch (avoids all-or-nothing batch-poisoning). We mop up across servers
191 // until all missing OIDs are satisfied or all servers are exhausted.
192 //
193 // NOTE: we intentionally restrict mop-up to the repo's declared git servers
194 // (remote_states) and do NOT try the git-server URL carried in the PR event's
195 // `clone` tag. A PR submitter could include an arbitrary server URL there;
196 // fetching from it unconditionally would let a malicious or slow server
197 // delay every clone/fetch. If we later want to support PR-supplied servers,
198 // it should be opt-in (e.g. an explicit `--include-pr-servers` flag) so
199 // users consciously accept the trust/performance trade-off. PRs whose tip
200 // OID isn't carried by any repo git server will simply not be advertised as
201 // `refs/heads/pr/*` refs; they are still accessible via their patch events.
202 if !missing_pr_oids.is_empty() {
203 for (server_url, (server_state, is_grasp)) in remote_states {
204 let batch: Vec<String> = missing_pr_oids
205 .iter()
206 .filter(|oid| server_state.values().any(|v| v == *oid))
207 .cloned()
208 .collect();
209 if batch.is_empty() {
210 continue;
211 }
212 let _ = fetch_from_git_server(
213 git_repo,
214 &batch,
215 server_url,
216 &repo_ref.to_nostr_git_url(&None),
217 term,
218 *is_grasp,
219 );
220 missing_pr_oids.retain(|oid| !git_repo.does_commit_exist(oid).unwrap_or(false));
221 if missing_pr_oids.is_empty() {
222 break;
223 }
224 }
225 }
226
227 let mut state = HashMap::new();
174 let current_user = get_curent_user(git_repo)?; 228 let current_user = get_curent_user(git_repo)?;
175 for (_, (proposal, events_to_apply)) in open_and_draft_proposals { 229 for (_, (proposal, events_to_apply)) in open_and_draft_proposals {
176 if let Ok(cl) = event_to_cover_letter(&proposal) { 230 if let Ok(cl) = event_to_cover_letter(&proposal) {
@@ -192,17 +246,9 @@ async fn get_open_and_draft_proposals_state(
192 { 246 {
193 match tag_value(pr_or_pr_update, "c") { 247 match tag_value(pr_or_pr_update, "c") {
194 Ok(tip) => { 248 Ok(tip) => {
195 // only list Pull Requests as refs/heads/pr/* if data is commit is 249 // Only advertise once confirmed locally available — this
196 // advertised as tip of a ref on a repo git server or 250 // guarantees the subsequent fetch phase can serve the object.
197 // available locally. Otherwise the standard cmd: 251 if git_repo.does_commit_exist(&tip).is_ok_and(|r| r) {
198 // `git clone nostr://` will fail as it assumes all /refs/heads
199 // returned by list are accessable
200 let tip_oid_is_on_a_repo_git_server =
201 remote_states.iter().any(|(_url, (state, _is_grasp))| {
202 state.iter().any(|(_, oid)| tip == *oid)
203 }) || git_repo.does_commit_exist(&tip).is_ok_and(|r| r);
204
205 if tip_oid_is_on_a_repo_git_server {
206 state.insert(format!("refs/heads/{branch_name}"), tip); 252 state.insert(format!("refs/heads/{branch_name}"), tip);
207 } 253 }
208 } 254 }
@@ -254,8 +300,8 @@ async fn get_all_proposals_state(
254 let mut state = HashMap::new(); 300 let mut state = HashMap::new();
255 let all_proposals = get_all_proposals(git_repo, repo_ref).await?; 301 let all_proposals = get_all_proposals(git_repo, repo_ref).await?;
256 let current_user = get_curent_user(git_repo)?; 302 let current_user = get_curent_user(git_repo)?;
257 for (_, (proposal, events_to_apply)) in all_proposals { 303 for (proposal, events_to_apply) in all_proposals.values() {
258 if let Ok(cl) = event_to_cover_letter(&proposal) { 304 if let Ok(cl) = event_to_cover_letter(proposal) {
259 if let Ok(mut branch_name) = cl.get_branch_name_with_pr_prefix_and_shorthand_id() { 305 if let Ok(mut branch_name) = cl.get_branch_name_with_pr_prefix_and_shorthand_id() {
260 branch_name = if let Some(public_key) = current_user { 306 branch_name = if let Some(public_key) = current_user {
261 if proposal.pubkey.eq(&public_key) { 307 if proposal.pubkey.eq(&public_key) {
@@ -275,7 +321,7 @@ async fn get_all_proposals_state(
275 state.insert(format!("refs/pr/{}/head", proposal.id), tip); 321 state.insert(format!("refs/pr/{}/head", proposal.id), tip);
276 } 322 }
277 } else if let Ok(tip) = 323 } else if let Ok(tip) =
278 make_commits_for_proposal(git_repo, repo_ref, &events_to_apply) 324 make_commits_for_proposal(git_repo, repo_ref, events_to_apply)
279 { 325 {
280 state.insert(format!("refs/{branch_name}"), tip.clone()); 326 state.insert(format!("refs/{branch_name}"), tip.clone());
281 state.insert(format!("refs/pr/{}/head", proposal.id), tip); 327 state.insert(format!("refs/pr/{}/head", proposal.id), tip);