diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2024-08-06 10:12:26 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2024-08-06 10:59:24 +0100 |
| commit | f1aa1be738af0dc80eb3b5827249bb537de1e0cd (patch) | |
| tree | 4974673e04f6b05484450bbad44c545ca73bd27b | |
| parent | 5ee6a65d7f095d5ba2d40e47bd97b2e65ed2443f (diff) | |
feat(remote): `list` includes open proposals
and filters out other branches in `prs/*` namespace
| -rw-r--r-- | src/git_remote_helper.rs | 100 | ||||
| -rw-r--r-- | test_utils/src/lib.rs | 16 | ||||
| -rw-r--r-- | tests/git_remote_helper.rs | 100 |
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::{ | |||
| 15 | use anyhow::{bail, Context, Result}; | 15 | use anyhow::{bail, Context, Result}; |
| 16 | use auth_git2::GitAuthenticator; | 16 | use auth_git2::GitAuthenticator; |
| 17 | use client::{ | 17 | use 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 | }; |
| 21 | use git::RepoActions; | 21 | use git::RepoActions; |
| 22 | use git2::{Oid, Repository}; | 22 | use git2::{Oid, Repository}; |
| 23 | use nostr::nips::nip01::Coordinate; | 23 | use nostr::nips::nip01::Coordinate; |
| 24 | use nostr_sdk::{hashes::sha1::Hash as Sha1Hash, EventBuilder, PublicKey, Tag, Url}; | 24 | use nostr_sdk::{ |
| 25 | hashes::sha1::Hash as Sha1Hash, Event, EventBuilder, EventId, Kind, PublicKey, Tag, Url, | ||
| 26 | }; | ||
| 25 | use nostr_signer::NostrSigner; | 27 | use nostr_signer::NostrSigner; |
| 26 | use repo_ref::RepoRef; | 28 | use repo_ref::RepoRef; |
| 27 | use repo_state::RepoState; | 29 | use repo_state::RepoState; |
| 28 | use sub_commands::send::send_events; | 30 | use 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))] |
| 31 | use crate::client::Client; | 39 | use 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 | ||
| 373 | async 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 | |||
| 344 | fn fetch(git_repo: &Repository, repo_ref: &RepoRef, stdin: &Stdin, oid: &str) -> Result<()> { | 434 | fn 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 | |||
| 1026 | pub 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 | ||
| 1085 | pub 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 | |||
| 1078 | pub fn create_and_populate_branch( | 1094 | pub 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 | ||