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-07-31 17:14:25 +0100
committerDanConwayDev <DanConwayDev@protonmail.com>2024-07-31 17:14:25 +0100
commit1180f0b3c3157b6fd8eb7bf08a255a114e961e5f (patch)
tree8bb6bd9a0382e1395b4ef3b124e98461cf9c8683
parent3acdeabfc3ab55d3e92d76d92d8ab6ad0383dd09 (diff)
fix(remote): updating `push` state event
ensure refs are included in state event use `HashMap` to improve `RepoState` struct
-rw-r--r--src/git_remote_helper.rs40
-rw-r--r--src/repo_state.rs10
-rw-r--r--tests/git_remote_helper.rs169
3 files changed, 186 insertions, 33 deletions
diff --git a/src/git_remote_helper.rs b/src/git_remote_helper.rs
index 68aa681..08a59c1 100644
--- a/src/git_remote_helper.rs
+++ b/src/git_remote_helper.rs
@@ -6,7 +6,7 @@
6 6
7use core::str; 7use core::str;
8use std::{ 8use std::{
9 collections::HashSet, 9 collections::{HashMap, HashSet},
10 env, 10 env,
11 io::{self, Stdin}, 11 io::{self, Stdin},
12 path::PathBuf, 12 path::PathBuf,
@@ -261,41 +261,25 @@ async fn generate_updated_state(
261 git_repo: &Repo, 261 git_repo: &Repo,
262 repo_ref: &RepoRef, 262 repo_ref: &RepoRef,
263 refspecs: &Vec<String>, 263 refspecs: &Vec<String>,
264) -> Result<Vec<(String, String)>> { 264) -> Result<HashMap<String, String>> {
265 let new_state = { 265 let new_state = {
266 if let Ok(mut repo_state) = get_state_from_cache(git_repo.get_path()?, repo_ref).await { 266 if let Ok(mut repo_state) = get_state_from_cache(git_repo.get_path()?, repo_ref).await {
267 for refspec in refspecs { 267 for refspec in refspecs {
268 let (from, to) = refspec_to_from_to(refspec)?; 268 let (from, to) = refspec_to_from_to(refspec)?;
269 if to.is_empty() { 269 if from.is_empty() {
270 // delete 270 // delete
271 repo_state.state.retain(|(name, _)| !name.eq(to)); 271 repo_state.state.remove(to);
272 } else if repo_state.state.iter().any(|(name, _)| name.eq(from)) {
273 // update
274 repo_state.state = repo_state
275 .state
276 .iter()
277 .map(|(name, value)| {
278 (
279 name.clone(),
280 if name.eq(to) {
281 reference_to_ref_value(&git_repo.git_repo, to).unwrap()
282 } else {
283 value.to_string()
284 },
285 )
286 })
287 .collect();
288 } else { 272 } else {
289 // add 273 // add or update
290 repo_state.state.push(( 274 repo_state.state.insert(
291 to.to_string(), 275 to.to_string(),
292 reference_to_ref_value(&git_repo.git_repo, to).unwrap(), 276 reference_to_ref_value(&git_repo.git_repo, to).unwrap(),
293 )); 277 );
294 } 278 }
295 } 279 }
296 repo_state.state 280 repo_state.state
297 } else { 281 } else {
298 let mut state = vec![]; 282 let mut state = HashMap::new();
299 let git_server_url = repo_ref 283 let git_server_url = repo_ref
300 .git_server 284 .git_server
301 .first() 285 .first()
@@ -303,14 +287,14 @@ async fn generate_updated_state(
303 let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_url)?; 287 let mut git_server_remote = git_repo.git_repo.remote_anonymous(git_server_url)?;
304 git_server_remote.connect(git2::Direction::Fetch)?; 288 git_server_remote.connect(git2::Direction::Fetch)?;
305 for head in git_server_remote.list()? { 289 for head in git_server_remote.list()? {
306 state.push(( 290 state.insert(
307 head.name().to_string(), 291 head.name().to_string(),
308 if let Some(symbolic_ref) = head.symref_target() { 292 if let Some(symbolic_ref) = head.symref_target() {
309 format!("ref: {}", symbolic_ref) 293 format!("ref: {symbolic_ref}")
310 } else { 294 } else {
311 head.oid().to_string() 295 head.oid().to_string()
312 }, 296 },
313 )); 297 );
314 } 298 }
315 git_server_remote.disconnect()?; 299 git_server_remote.disconnect()?;
316 state 300 state
@@ -460,7 +444,7 @@ fn get_refspecs_from_push_batch(stdin: &Stdin, initial_refspec: &str) -> Result<
460impl RepoState { 444impl RepoState {
461 pub async fn build( 445 pub async fn build(
462 identifier: String, 446 identifier: String,
463 state: Vec<(String, String)>, 447 state: HashMap<String, String>,
464 signer: &NostrSigner, 448 signer: &NostrSigner,
465 ) -> Result<RepoState> { 449 ) -> Result<RepoState> {
466 let mut tags = vec![Tag::identifier(identifier.clone())]; 450 let mut tags = vec![Tag::identifier(identifier.clone())];
diff --git a/src/repo_state.rs b/src/repo_state.rs
index 0c1aa30..a5cebab 100644
--- a/src/repo_state.rs
+++ b/src/repo_state.rs
@@ -1,9 +1,11 @@
1use std::collections::HashMap;
2
1use anyhow::{Context, Result}; 3use anyhow::{Context, Result};
2use git2::Oid; 4use git2::Oid;
3 5
4pub struct RepoState { 6pub struct RepoState {
5 pub identifier: String, 7 pub identifier: String,
6 pub state: Vec<(String, String)>, 8 pub state: HashMap<String, String>,
7 pub event: nostr::Event, 9 pub event: nostr::Event,
8} 10}
9 11
@@ -11,7 +13,7 @@ impl RepoState {
11 pub fn try_from(mut state_events: Vec<nostr::Event>) -> Result<Self> { 13 pub fn try_from(mut state_events: Vec<nostr::Event>) -> Result<Self> {
12 state_events.sort_by_key(|e| e.created_at); 14 state_events.sort_by_key(|e| e.created_at);
13 let event = state_events.first().context("no state events")?; 15 let event = state_events.first().context("no state events")?;
14 let mut state = vec![]; 16 let mut state = HashMap::new();
15 for tag in &event.tags { 17 for tag in &event.tags {
16 if let Some(name) = tag.as_vec().first() { 18 if let Some(name) = tag.as_vec().first() {
17 if ["refs/heads/", "refs/tags", "HEAD"] 19 if ["refs/heads/", "refs/tags", "HEAD"]
@@ -19,8 +21,8 @@ impl RepoState {
19 .any(|s| name.starts_with(*s)) 21 .any(|s| name.starts_with(*s))
20 { 22 {
21 if let Some(value) = tag.as_vec().get(1) { 23 if let Some(value) = tag.as_vec().get(1) {
22 if Oid::from_str(value).is_ok() { 24 if Oid::from_str(value).is_ok() || value.contains("ref: refs/") {
23 state.push((name.to_owned(), value.to_owned())); 25 state.insert(name.to_owned(), value.to_owned());
24 } 26 }
25 } 27 }
26 } 28 }
diff --git a/tests/git_remote_helper.rs b/tests/git_remote_helper.rs
index ea76445..fffcaa1 100644
--- a/tests/git_remote_helper.rs
+++ b/tests/git_remote_helper.rs
@@ -51,6 +51,77 @@ fn cli_tester_after_fetch(git_repo: &GitTestRepo) -> Result<CliTester> {
51 Ok(p) 51 Ok(p)
52} 52}
53 53
54async fn generate_repo_with_state_event() -> Result<(nostr::Event, GitTestRepo)> {
55 let git_repo = prep_git_repo()?;
56 let commit_id = git_repo.get_tip_of_local_branch("main")?.to_string();
57 let source_git_repo = GitTestRepo::recreate_as_bare(&git_repo)?;
58
59 let events = vec![
60 generate_test_key_1_metadata_event("fred"),
61 generate_test_key_1_relay_list_event(),
62 generate_repo_ref_event_with_git_server(source_git_repo.dir.to_str().unwrap()),
63 ];
64 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
65 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
66 Relay::new(8051, None, None),
67 Relay::new(8052, None, None),
68 Relay::new(8053, None, None),
69 Relay::new(8055, None, None),
70 Relay::new(8056, None, None),
71 Relay::new(8057, None, None),
72 );
73 r51.events = events.clone();
74 r55.events = events;
75
76 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
77 let mut p = cli_tester_after_fetch(&git_repo)?;
78 p.send_line("push refs/heads/main:refs/heads/main")?;
79 p.send_line("")?;
80 p.expect("ok refs/heads/main\r\n")?;
81 p.expect("\r\n")?;
82
83 p.exit()?;
84 for p in [51, 52, 53, 55, 56, 57] {
85 relay::shutdown_relay(8000 + p)?;
86 }
87 Ok(())
88 });
89 // launch relays
90 let _ = join!(
91 r51.listen_until_close(),
92 r52.listen_until_close(),
93 r53.listen_until_close(),
94 r55.listen_until_close(),
95 r56.listen_until_close(),
96 r57.listen_until_close(),
97 );
98 cli_tester_handle.join().unwrap()?;
99
100 let state_event = r56
101 .events
102 .iter()
103 .find(|e| e.kind().eq(&STATE_KIND))
104 .context("state event not created")?;
105
106 assert_eq!(
107 state_event
108 .tags
109 .iter()
110 .filter(|t| t.kind().to_string().as_str().ne("d"))
111 .map(|t| t.as_vec().to_vec())
112 .collect::<HashSet<Vec<String>>>(),
113 HashSet::from([
114 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()],
115 vec!["refs/heads/main".to_string(), commit_id,],
116 ]),
117 );
118
119 // wait for bigger timestamp
120 std::thread::sleep(std::time::Duration::from_millis(1000));
121
122 Ok((state_event.clone(), source_git_repo))
123}
124
54mod initially_runs_fetch { 125mod initially_runs_fetch {
55 126
56 use super::*; 127 use super::*;
@@ -523,7 +594,103 @@ mod push {
523 594
524 #[tokio::test] 595 #[tokio::test]
525 #[serial] 596 #[serial]
526 async fn sate_event_reflects_git_server_state() -> Result<()> { 597 async fn state_event_reflects_git_server_state() -> Result<()> {
598 async_run_test().await
599 }
600 }
601 }
602 mod existing_state_event {
603 use super::*;
604
605 mod state_on_git_server_published_in_nostr_state_event {
606
607 use super::*;
608
609 async fn async_run_test() -> Result<()> {
610 let (state_event, source_git_repo) = generate_repo_with_state_event().await?;
611
612 let git_repo = prep_git_repo()?;
613
614 std::fs::write(git_repo.dir.join("new.md"), "some content")?;
615 let main_commit_id = git_repo.stage_and_commit("new.md")?;
616 git_repo.create_branch("vnext")?;
617 git_repo.checkout("vnext")?;
618 std::fs::write(git_repo.dir.join("more.md"), "some content")?;
619 let vnext_commit_id = git_repo.stage_and_commit("more.md")?;
620
621 let events = vec![
622 generate_test_key_1_metadata_event("fred"),
623 generate_test_key_1_relay_list_event(),
624 generate_repo_ref_event_with_git_server(
625 source_git_repo.dir.to_str().unwrap(),
626 ),
627 state_event.clone(),
628 ];
629
630 // fallback (51,52) user write (53, 55) repo (55, 56) blaster (57)
631 let (mut r51, mut r52, mut r53, mut r55, mut r56, mut r57) = (
632 Relay::new(8051, None, None),
633 Relay::new(8052, None, None),
634 Relay::new(8053, None, None),
635 Relay::new(8055, None, None),
636 Relay::new(8056, None, None),
637 Relay::new(8057, None, None),
638 );
639 r51.events = events.clone();
640 r55.events = events;
641
642 let cli_tester_handle = std::thread::spawn(move || -> Result<()> {
643 let mut p = cli_tester_after_fetch(&git_repo)?;
644 p.send_line("push refs/heads/main:refs/heads/main")?;
645 p.send_line("push refs/heads/vnext:refs/heads/vnext")?;
646 p.send_line("")?;
647 p.expect("ok refs/heads/main\r\n")?;
648 p.expect("ok refs/heads/vnext\r\n")?;
649 p.expect("\r\n")?;
650 p.exit()?;
651 for p in [51, 52, 53, 55, 56, 57] {
652 relay::shutdown_relay(8000 + p)?;
653 }
654 Ok(())
655 });
656 // launch relays
657 let _ = join!(
658 r51.listen_until_close(),
659 r52.listen_until_close(),
660 r53.listen_until_close(),
661 r55.listen_until_close(),
662 r56.listen_until_close(),
663 r57.listen_until_close(),
664 );
665
666 cli_tester_handle.join().unwrap()?;
667
668 let state_event = r56
669 .events
670 .iter()
671 .find(|e| e.kind().eq(&STATE_KIND))
672 .context("state event not created")?;
673
674 // println!("{:#?}", state_event);
675 assert_eq!(
676 state_event
677 .tags
678 .iter()
679 .filter(|t| t.kind().to_string().as_str().ne("d"))
680 .map(|t| t.as_vec().to_vec())
681 .collect::<HashSet<Vec<String>>>(),
682 HashSet::from([
683 vec!["HEAD".to_string(), "ref: refs/heads/main".to_string()],
684 vec!["refs/heads/main".to_string(), main_commit_id.to_string()],
685 vec!["refs/heads/vnext".to_string(), vnext_commit_id.to_string()],
686 ]),
687 );
688 Ok(())
689 }
690
691 #[tokio::test]
692 #[serial]
693 async fn state_event_reflects_updated_with_pushed_changes() -> Result<()> {
527 async_run_test().await 694 async_run_test().await
528 } 695 }
529 } 696 }