From 5b16ba50c2399a47d4922afb1a20b233ea887f51 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Tue, 13 Oct 2026 18:01:38 +0700 Subject: [PATCH] move state types from signed_core into signed_state --- crates/signed_core/src/lib.rs | 2 - crates/signed_state/src/backend.rs | 22 ++++-- crates/signed_state/src/lib.rs | 2 + crates/signed_state/src/push.rs | 7 +- crates/signed_state/src/repo.rs | 8 +-- .../src/state.rs | 70 ++++++++++++++----- crates/signed_state/src/sync_status.rs | 14 ++-- docs/push-refactor.md | 10 ++- 8 files changed, 99 insertions(+), 36 deletions(-) rename crates/{signed_core => signed_state}/src/state.rs (57%) diff --git a/crates/signed_core/src/lib.rs b/crates/signed_core/src/lib.rs index 66e6812..076deed 100644 --- a/crates/signed_core/src/lib.rs +++ b/crates/signed_core/src/lib.rs @@ -5,7 +5,6 @@ pub mod filters; pub mod git_event; pub mod inbox; pub mod pull_request; -pub mod state; pub mod status; pub mod upstream; @@ -16,6 +15,5 @@ pub use filters::Filters; pub use git_event::GitEvent; pub use inbox::{InboxItem, InboxReadState, ThreadResolver}; pub use pull_request::PullRequest; -pub use state::RepoState; pub use status::RepoStatus; pub use upstream::Upstream; diff --git a/crates/signed_state/src/backend.rs b/crates/signed_state/src/backend.rs index de1a045..ef7ae38 100644 --- a/crates/signed_state/src/backend.rs +++ b/crates/signed_state/src/backend.rs @@ -21,6 +21,7 @@ use crate::git_store::Mirrors; use crate::inbox::Inbox; use crate::push::{PushOutcome, grasp_base_url, grasp_clone_url, push_staged_to_grasps}; use crate::repos::RepoListStore; +use crate::state::GitRef; pub const USER_KEYRING: &str = "Signed Safe Storage"; pub const NOSTR_CONNECT_TIMEOUT: u64 = 60; @@ -581,7 +582,10 @@ impl Backend { &repo_id, &owner, &servers, - vec![("refs/heads/main".to_owned(), commit)], + vec![GitRef { + name: "refs/heads/main".to_owned(), + commit: commit_sha, + }], Some("main".to_owned()), &destination, cx, @@ -668,6 +672,12 @@ impl Backend { let euc = euc.and_then(|commit| Sha1Hash::from_str(&commit).ok()); let ann = build_announcement(&repo_id, &name, &description, &owner, &servers, euc); + let refs = state + .refs + .iter() + .map(|(name, commit)| GitRef::new(name, commit)) + .collect::, Error>>()?; + let event = announce_repository_and_push( &client, &signer, @@ -675,7 +685,7 @@ impl Backend { &repo_id, &owner, &servers, - state.refs.clone(), + refs, state.head.clone(), &path, cx, @@ -793,7 +803,11 @@ impl Backend { } // Grasp servers authorize a push by the state event they hold in purgatory. - let refs = state.refs.clone(); + let refs = state + .refs + .iter() + .map(|(name, commit)| GitRef::new(name, commit)) + .collect::, Error>>()?; let head = state.head.clone(); let outcome = if refs.is_empty() { @@ -1196,7 +1210,7 @@ async fn announce_repository_and_push( repo_id: &str, owner: &str, servers: &[RelayUrl], - refs: Vec<(String, String)>, + refs: Vec, head: Option, path: &Path, cx: &mut AsyncApp, diff --git a/crates/signed_state/src/lib.rs b/crates/signed_state/src/lib.rs index bcbd7e1..25e1ff0 100644 --- a/crates/signed_state/src/lib.rs +++ b/crates/signed_state/src/lib.rs @@ -10,6 +10,7 @@ mod push; mod refresh; mod repo; mod repos; +mod state; mod sync_status; use std::path::{Path, PathBuf}; @@ -29,6 +30,7 @@ pub use repo::RepoStore; pub use repos::RepoListStore; pub use signed_git::{Nip34Binding, Nip34Kind}; use signed_nostr::NostrBackend; +pub use state::{GitRef, SyncState}; pub use sync_status::{CheckoutSyncStatus, SyncStatusStore}; pub fn init( diff --git a/crates/signed_state/src/push.rs b/crates/signed_state/src/push.rs index 56f7171..6615ee8 100644 --- a/crates/signed_state/src/push.rs +++ b/crates/signed_state/src/push.rs @@ -3,7 +3,8 @@ use std::path::Path; use anyhow::Error; use nostr::prelude::Url; use nostr_sdk::prelude::*; -use signed_core::RepoState; + +use crate::state::{GitRef, SyncState}; /// `ws://` grasp servers, like ngit, use `http(s)://`. pub(crate) fn grasp_base_url(relay: &RelayUrl) -> Option { @@ -133,7 +134,7 @@ pub(crate) async fn push_staged_to_grasps( client: &Client, signer: &S, repo_id: &str, - refs: &[(String, String)], + refs: &[GitRef], head: Option<&str>, path: &Path, owner: &str, @@ -153,7 +154,7 @@ where continue; }; - let event = match RepoState::build(repo_id, refs, head) + let event = match SyncState::build(repo_id, refs, head) .finalize_async(signer) .await { diff --git a/crates/signed_state/src/repo.rs b/crates/signed_state/src/repo.rs index fba0a34..5edcaae 100644 --- a/crates/signed_state/src/repo.rs +++ b/crates/signed_state/src/repo.rs @@ -8,8 +8,7 @@ use nostr::event::IntoEventBuilder; use nostr_sdk::prelude::*; use settings::{EventFetchingStrategy, SettingsStore}; use signed_core::{ - Announcement, Deletions, Filters, GitEvent, PullRequest, RepoAddr, RepoState, RepoStatus, - filters, + Announcement, Deletions, Filters, GitEvent, PullRequest, RepoAddr, RepoStatus, filters, }; use signed_git::{Nip34Binding, Repo}; @@ -19,6 +18,7 @@ use crate::checkouts::CheckoutsStore; use crate::patch_series::{MAX_PATCH_EVENT_BYTES, PatchSeries}; use crate::push::{PushOutcome, grasp_base_url, grasp06_prs_url, pr_clone_urls}; use crate::repos::RepoListStore; +use crate::state::{GitRef, SyncState}; use crate::sync_status::SyncStatusStore; pub struct RepoStore { @@ -34,7 +34,7 @@ pub struct RepoStore { pub loaded: bool, pub head: Option, // Refs and timestamp of the latest state event, for sync detection. - pub state_refs: Vec<(String, String)>, + pub state_refs: Vec, pub state_at: Option, pub issues: Vec, pub patches: Vec, @@ -354,7 +354,7 @@ impl RepoStore { let all_states = states.into_iter().filter(|e| !deletions.is_deleted(e)); let state = - utils::latest(all_states).map(|event| (RepoState::parse(&event), event.created_at)); + utils::latest(all_states).map(|event| (SyncState::parse(&event), event.created_at)); let (mut issues, mut patches, mut pull_requests, mut statuses, mut comments) = (Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()); diff --git a/crates/signed_core/src/state.rs b/crates/signed_state/src/state.rs similarity index 57% rename from crates/signed_core/src/state.rs rename to crates/signed_state/src/state.rs index f7af5a2..c60556b 100644 --- a/crates/signed_core/src/state.rs +++ b/crates/signed_state/src/state.rs @@ -1,17 +1,41 @@ +use std::str::FromStr; + +use anyhow::{Error, anyhow}; +use bitcoin_hashes::sha1::Hash as Sha1Hash; use nostr::prelude::*; +/// A git ref with the commit it points at. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct RepoState { - pub refs: Vec<(String, String)>, +pub struct GitRef { + /// Full ref name, like `refs/heads/main` or `refs/tags/v1.0`. + pub name: String, + pub commit: Sha1Hash, +} + +impl GitRef { + pub fn new(name: impl Into, commit: &str) -> Result { + let commit = Sha1Hash::from_str(commit).map_err(|_| anyhow!("invalid commit: {commit}"))?; + + Ok(Self { + name: name.into(), + commit, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SyncState { + pub refs: Vec, pub head: Option, } -impl RepoState { - pub fn build(id: &str, refs: &[(String, String)], head: Option<&str>) -> EventBuilder { +impl SyncState { + pub fn build(id: &str, refs: &[GitRef], head: Option<&str>) -> EventBuilder { let mut tags: Vec = vec![Tag::identifier(id.to_owned())]; - for (name, commit) in refs { - tags.push(Tag::parse([name.as_str(), commit.as_str()]).expect("valid ref tag")); + for git_ref in refs { + let commit = git_ref.commit.to_string(); + tags.push(Tag::parse([git_ref.name.as_str(), commit.as_str()]).expect("valid ref tag")); } if let Some(head) = head { @@ -31,10 +55,16 @@ impl RepoState { match Nip34Tag::parse(tag.as_slice()) { Ok(Nip34Tag::Head(branch)) => head = Some(branch), Ok(Nip34Tag::RefHead { branch, commit }) => { - refs.push((format!("refs/heads/{branch}"), commit.to_string())); + refs.push(GitRef { + name: format!("refs/heads/{branch}"), + commit, + }); } Ok(Nip34Tag::RefTag { name, commit }) => { - refs.push((format!("refs/tags/{name}"), commit.to_string())); + refs.push(GitRef { + name: format!("refs/tags/{name}"), + commit, + }); } _ => {} } @@ -51,6 +81,10 @@ mod tests { const COMMIT_A: &str = "aa231c4c6a5777dc89b42207b499891a344add5c"; const COMMIT_B: &str = "59429cfc6cb35b0a1ddace73b5a5c5ed57b8f5ca"; + fn git_ref(name: &str, commit: &str) -> GitRef { + GitRef::new(name, commit).expect("valid ref") + } + fn keys() -> Keys { Keys::new( SecretKey::from_hex("0000000000000000000000000000000000000000000000000000000000000001") @@ -79,35 +113,35 @@ mod tests { &["refs/tags/v1.0", COMMIT_A], ]); - let state = RepoState::parse(&event); + let state = SyncState::parse(&event); assert_eq!(state.head.as_deref(), Some("main")); assert_eq!( state.refs, vec![ - ("refs/heads/main".to_owned(), COMMIT_A.to_owned()), - ("refs/heads/dev".to_owned(), COMMIT_B.to_owned()), - ("refs/tags/v1.0".to_owned(), COMMIT_A.to_owned()), + git_ref("refs/heads/main", COMMIT_A), + git_ref("refs/heads/dev", COMMIT_B), + git_ref("refs/tags/v1.0", COMMIT_A), ] ); } #[test] fn build_round_trips_through_parse() { - let refs = [ - ("refs/heads/main".to_owned(), COMMIT_A.to_owned()), - ("refs/heads/dev".to_owned(), COMMIT_B.to_owned()), - ("refs/tags/v1.0".to_owned(), COMMIT_A.to_owned()), + let refs = vec![ + git_ref("refs/heads/main", COMMIT_A), + git_ref("refs/heads/dev", COMMIT_B), + git_ref("refs/tags/v1.0", COMMIT_A), ]; - let event = RepoState::build("my-repo", &refs, Some("main")) + let event = SyncState::build("my-repo", &refs, Some("main")) .finalize(&keys()) .expect("signed event"); assert_eq!(event.kind, Kind::RepoState); assert_eq!(event.tags.identifier().as_deref(), Some("my-repo")); - let state = RepoState::parse(&event); + let state = SyncState::parse(&event); assert_eq!(state.refs, refs); assert_eq!(state.head.as_deref(), Some("main")); } diff --git a/crates/signed_state/src/sync_status.rs b/crates/signed_state/src/sync_status.rs index cfbe4a9..2c26fbb 100644 --- a/crates/signed_state/src/sync_status.rs +++ b/crates/signed_state/src/sync_status.rs @@ -5,13 +5,14 @@ use std::time::Duration; use anyhow::Error; use gpui::{App, AppContext, Context, Entity, Global, Subscription}; use nostr_sdk::prelude::*; -use signed_core::{Deletions, Filters, RepoAddr, RepoState}; +use signed_core::{Deletions, Filters, RepoAddr}; use signed_git::{Repo, RepoSyncStatus}; use crate::backend::{Backend, BackendEvent}; use crate::checkouts::CheckoutsStore; use crate::refresh::{RefreshGate, RefreshRequest}; use crate::repos::RepoListStore; +use crate::state::{GitRef, SyncState}; const SYNC_POLL: Duration = Duration::from_secs(180); const MAX_SYNC_CHECKOUTS: usize = 8; @@ -167,7 +168,7 @@ impl SyncStatusStore { let deletion_events = db.query(Filters::deletions()).await?; let deletions = Deletions::from_events(deletion_events); - let mut states: HashMap> = HashMap::new(); + let mut states: HashMap> = HashMap::new(); for (addr, _) in &targets { let events = db.query(addr.state_filter()).await?; let latest = utils::latest( @@ -178,7 +179,7 @@ impl SyncStatusStore { let Some(event) = latest else { continue; }; - states.insert(addr.clone(), RepoState::parse(&event).refs); + states.insert(addr.clone(), SyncState::parse(&event).refs); } let mut statuses: HashMap> = HashMap::new(); @@ -187,10 +188,15 @@ impl SyncStatusStore { continue; }; + let remote_refs: Vec<(String, String)> = nostr_refs + .iter() + .map(|git_ref| (git_ref.name.clone(), git_ref.commit.to_string())) + .collect(); + let mut list = Vec::new(); for path in paths.iter().take(MAX_SYNC_CHECKOUTS) { let synced = - Repo::try_open(path).and_then(|repo| repo.sync_status(nostr_refs).ok()); + Repo::try_open(path).and_then(|repo| repo.sync_status(&remote_refs).ok()); if let Some(status) = synced { list.push(CheckoutSyncStatus { diff --git a/docs/push-refactor.md b/docs/push-refactor.md index f1073fb..5aeedde 100644 --- a/docs/push-refactor.md +++ b/docs/push-refactor.md @@ -46,8 +46,16 @@ pub enum GitPushMode { Main, } +/// A git ref with the commit it points at. +pub struct GitRef { + /// Full ref name, like `refs/heads/main` or `refs/tags/v1.0`. + pub name: String, + /// Commit id, `bitcoin_hashes::sha1::Hash`, the type NIP-34 tags carry. + pub commit: Sha1Hash, +} + pub struct SyncState { - pub refs: Vec<(String, String)>, + pub refs: Vec, pub head: Option, /// Local repository to push from; `None` for states parsed from events. pub path: Option,