upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-08-06 10:12:26 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-08-06 10:59:24 +0100
commitf1aa1be738af0dc80eb3b5827249bb537de1e0cd (patch)
tree4974673e04f6b05484450bbad44c545ca73bd27b
parent5ee6a65d7f095d5ba2d40e47bd97b2e65ed2443f (diff)
feat(remote): `list` includes open proposals
and filters out other branches in `prs/*` namespace
-rw-r--r--src/git_remote_helper.rs100
-rw-r--r--test_utils/src/lib.rs16
-rw-r--r--tests/git_remote_helper.rs100
3 files changed, 211 insertions, 5 deletions
diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs
index a930617..f851c90 100644
--- a/src/git_remote_helper.rs
+++ b/src/git_remote_helper.rs
@@ -15,17 +15,25 @@ use std::{
15use anyhow::{bail, Context, Result}; 15use anyhow::{bail, Context, Result};
16use auth_git2::GitAuthenticator; 16use auth_git2::GitAuthenticator;
17use client::{ 17use client::{
18 consolidate_fetch_reports, get_repo_ref_from_cache, get_state_from_cache, sign_event, Connect, 18 consolidate_fetch_reports, get_events_from_cache, get_repo_ref_from_cache,
19 STATE_KIND, 19 get_state_from_cache, sign_event, Connect, STATE_KIND,
20}; 20};
21use git::RepoActions; 21use git::RepoActions;
22use git2::{Oid, Repository}; 22use git2::{Oid, Repository};
23use nostr::nips::nip01::Coordinate; 23use nostr::nips::nip01::Coordinate;
24use nostr_sdk::{hashes::sha1::Hash as Sha1Hash, EventBuilder, PublicKey, Tag, Url}; 24use nostr_sdk::{
25 hashes::sha1::Hash as Sha1Hash, Event, EventBuilder, EventId, Kind, PublicKey, Tag, Url,
26};
25use nostr_signer::NostrSigner; 27use nostr_signer::NostrSigner;
26use repo_ref::RepoRef; 28use repo_ref::RepoRef;
27use repo_state::RepoState; 29use repo_state::RepoState;
28use sub_commands::send::send_events; 30use sub_commands::{
31 list::{
32 get_all_proposal_patch_events_from_cache, get_commit_id_from_patch,
33 get_most_recent_patch_with_ancestors, get_proposals_and_revisions_from_cache, status_kinds,
34 },
35 send::{event_is_revision_root, event_to_cover_letter, send_events},
36};
29 37
30#[cfg(not(test))] 38#[cfg(not(test))]
31use crate::client::Client; 39use crate::client::Client;
@@ -231,7 +239,7 @@ async fn list(
231 239
232 let remote_states = list_from_remotes(&term, git_repo, &repo_ref.git_server)?; 240 let remote_states = list_from_remotes(&term, git_repo, &repo_ref.git_server)?;
233 241
234 let state = if let Some(nostr_state) = nostr_state { 242 let mut state = if let Some(nostr_state) = nostr_state {
235 for (name, value) in &nostr_state.state { 243 for (name, value) in &nostr_state.state {
236 for (url, remote_state) in &remote_states { 244 for (url, remote_state) in &remote_states {
237 let remote_name = get_short_git_server_name(git_repo, url); 245 let remote_name = get_short_git_server_name(git_repo, url);
@@ -272,6 +280,27 @@ async fn list(
272 .clone() 280 .clone()
273 }; 281 };
274 282
283 state.retain(|k, _| !k.starts_with("refs/heads/prs/"));
284
285 let open_proposals = get_open_proposals(git_repo, repo_ref).await?;
286
287 for (_, (proposal, patches)) in open_proposals {
288 if let Ok(cl) = event_to_cover_letter(&proposal) {
289 if let Ok(branch_name) = cl.get_branch_name() {
290 if let Some(patch) = patches.first() {
291 // TODO this isn't resilient because the commit id stated may not be correct
292 // we will need to check whether the commit id exists in the repo or apply the
293 // proposal and each patch to check
294 if let Ok(commit_id) = get_commit_id_from_patch(patch) {
295 state.insert(branch_name, commit_id);
296 }
297 }
298 }
299 }
300 }
301
302 // TODO 'for push' should we check with the git servers to see if any of them
303 // allow push from the user?
275 for (name, value) in state { 304 for (name, value) in state {
276 if value.starts_with("ref: ") { 305 if value.starts_with("ref: ") {
277 if !for_push { 306 if !for_push {
@@ -341,6 +370,67 @@ fn get_ahead_behind(
341 git_repo.get_commits_ahead_behind(&base, &latest) 370 git_repo.get_commits_ahead_behind(&base, &latest)
342} 371}
343 372
373async fn get_open_proposals(
374 git_repo: &Repo,
375 repo_ref: &RepoRef,
376) -> Result<HashMap<EventId, (Event, Vec<Event>)>> {
377 let git_repo_path = git_repo.get_path()?;
378 let proposals: Vec<nostr::Event> =
379 get_proposals_and_revisions_from_cache(git_repo_path, repo_ref.coordinates())
380 .await?
381 .iter()
382 .filter(|e| !event_is_revision_root(e))
383 .cloned()
384 .collect();
385
386 let statuses: Vec<nostr::Event> = {
387 let mut statuses = get_events_from_cache(
388 git_repo_path,
389 vec![
390 nostr::Filter::default()
391 .kinds(status_kinds().clone())
392 .events(proposals.iter().map(nostr::Event::id)),
393 ],
394 )
395 .await?;
396 statuses.sort_by_key(|e| e.created_at);
397 statuses.reverse();
398 statuses
399 };
400 let mut open_proposals = HashMap::new();
401
402 for proposal in proposals {
403 let status = if let Some(e) = statuses
404 .iter()
405 .filter(|e| {
406 status_kinds().contains(&e.kind())
407 && e.iter_tags()
408 .any(|t| t.as_vec()[1].eq(&proposal.id.to_string()))
409 })
410 .collect::<Vec<&nostr::Event>>()
411 .first()
412 {
413 e.kind()
414 } else {
415 Kind::GitStatusOpen
416 };
417 if status.eq(&Kind::GitStatusOpen) {
418 if let Ok(commits_events) =
419 get_all_proposal_patch_events_from_cache(git_repo_path, repo_ref, &proposal.id)
420 .await
421 {
422 if let Ok(most_recent_proposal_patch_chain) =
423 get_most_recent_patch_with_ancestors(commits_events.clone())
424 {
425 open_proposals
426 .insert(proposal.id(), (proposal, most_recent_proposal_patch_chain));
427 }
428 }
429 }
430 }
431 Ok(open_proposals)
432}
433
344fn fetch(git_repo: &Repository, repo_ref: &RepoRef, stdin: &Stdin, oid: &str) -> Result<()> { 434fn fetch(git_repo: &Repository, repo_ref: &RepoRef, stdin: &Stdin, oid: &str) -> Result<()> {
345 let oids = get_oids_from_fetch_batch(stdin, oid)?; 435 let oids = get_oids_from_fetch_batch(stdin, oid)?;
346 436
diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs
index f463652..ff3833d 100644
--- a/test_utils/src/lib.rs
+++ b/test_utils/src/lib.rs
@@ -1020,6 +1020,13 @@ pub fn get_proposal_branch_name(
1020 .hashtag("root"), 1020 .hashtag("root"),
1021 ], 1021 ],
1022 ))?; 1022 ))?;
1023 get_proposal_branch_name_from_events(&events, branch_name_in_event)
1024}
1025
1026pub fn get_proposal_branch_name_from_events(
1027 events: &Vec<nostr::Event>,
1028 branch_name_in_event: &str,
1029) -> Result<String> {
1023 for event in events { 1030 for event in events {
1024 if event.iter_tags().any(|t| { 1031 if event.iter_tags().any(|t| {
1025 !t.as_vec()[1].eq("revision-root") 1032 !t.as_vec()[1].eq("revision-root")
@@ -1075,6 +1082,15 @@ pub fn cli_tester_create_proposals() -> Result<GitTestRepo> {
1075 Ok(git_repo) 1082 Ok(git_repo)
1076} 1083}
1077 1084
1085pub fn cli_tester_create_proposal_branches_ready_to_send() -> Result<GitTestRepo> {
1086 let git_repo = GitTestRepo::default();
1087 git_repo.populate()?;
1088 create_and_populate_branch(&git_repo, FEATURE_BRANCH_NAME_1, "a", false)?;
1089 create_and_populate_branch(&git_repo, FEATURE_BRANCH_NAME_2, "b", false)?;
1090 create_and_populate_branch(&git_repo, FEATURE_BRANCH_NAME_3, "c", false)?;
1091 Ok(git_repo)
1092}
1093
1078pub fn create_and_populate_branch( 1094pub fn create_and_populate_branch(
1079 test_repo: &GitTestRepo, 1095 test_repo: &GitTestRepo,
1080 branch_name: &str, 1096 branch_name: &str,
diff --git a/tests/git_remote_helper.rs b/tests/git_remote_helper.rs
index 4a60e63..77d7ecb 100644
--- a/tests/git_remote_helper.rs
+++ b/tests/git_remote_helper.rs
@@ -447,6 +447,106 @@ mod list {
447 Ok(()) 447 Ok(())
448 } 448 }
449 } 449 }
450
451 mod when_there_are_open_proposals {
452
453 use super::*;
454
455 #[tokio::test]
456 #[serial]
457 async fn open_proposal_listed_in_prs_namespace() -> Result<()> {
458 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
459 let source_path = source_git_repo.dir.to_str().unwrap().to_string();
460
461 let main_commit_id = source_git_repo.get_tip_of_local_branch("main")?;
462 let example_commit_id =
463 source_git_repo.get_tip_of_local_branch("example-branch")?;
464
465 let git_repo = prep_git_repo()?;
466
467 let events = vec![
468 generate_test_key_1_metadata_event("fred"),
469 generate_test_key_1_relay_list_event(),
470 generate_repo_ref_event_with_git_server(vec![
471 source_git_repo.dir.to_str().unwrap().to_string(),
472 ]),
473 state_event,
474 ];
475 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
476 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
477 Relay::new(8051, None, None),
478 Relay::new(8052, None, None),
479 Relay::new(8053, None, None),
480 Relay::new(8055, None, None),
481 Relay::new(8056, None, None),
482 Relay::new(8057, None, None),
483 );
484 r51.events = events.clone();
485 r55.events = events;
486
487 let cli_tester_handle = std::thread::spawn(move || -> Result<String> {
488 cli_tester_create_proposals()?;
489
490 let mut p = cli_tester_after_fetch(&git_repo)?;
491 p.send_line("list")?;
492 p.expect(format!("fetching refs list: {}...\r\n\r", source_path).as_str())?;
493 // println!("{}", p.expect_eventually("\r\n\r\n")?);
494 let res = p.expect_eventually("\r\n\r\n")?;
495
496 p.exit()?;
497 for p in [51, 52, 53, 55, 56, 57] {
498 relay::shutdown_relay(8000 + p)?;
499 }
500 Ok(res)
501 });
502 // launch relays
503 let _ = join!(
504 r51.listen_until_close(),
505 r52.listen_until_close(),
506 r53.listen_until_close(),
507 r55.listen_until_close(),
508 r56.listen_until_close(),
509 r57.listen_until_close(),
510 );
511
512 let res = cli_tester_handle.join().unwrap()?;
513
514 let proposal_creation_repo = cli_tester_create_proposal_branches_ready_to_send()?;
515
516 let mut pr_refs = vec![];
517 for name in [
518 FEATURE_BRANCH_NAME_1,
519 FEATURE_BRANCH_NAME_2,
520 FEATURE_BRANCH_NAME_3,
521 ] {
522 pr_refs.push(format!(
523 "{} {}",
524 proposal_creation_repo.get_tip_of_local_branch(name)?,
525 get_proposal_branch_name_from_events(&r55.events, name)?,
526 ));
527 }
528
529 assert_eq!(
530 res.split("\r\n")
531 .map(|e| e.to_string())
532 .collect::<HashSet<String>>(),
533 [
534 vec![
535 "@refs/heads/main HEAD".to_string(),
536 format!("{} refs/heads/main", main_commit_id),
537 format!("{} refs/heads/example-branch", example_commit_id),
538 ],
539 pr_refs,
540 ]
541 .concat()
542 .iter()
543 .cloned()
544 .collect::<HashSet<String>>()
545 );
546
547 Ok(())
548 }
549 }
450 } 550 }
451} 551}
452 552