From 0e3a5ed865aa41047f456a5c7fc456066617ead6 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Tue, 13 Oct 2026 09:40:58 +0700 Subject: [PATCH] clean up --- crates/signed_git/src/repo.rs | 11 +- crates/signed_state/src/bootstrap.rs | 10 +- crates/signed_state/src/checkouts.rs | 267 ++++++------------------- crates/signed_state/src/sync_status.rs | 3 +- 4 files changed, 71 insertions(+), 220 deletions(-) diff --git a/crates/signed_git/src/repo.rs b/crates/signed_git/src/repo.rs index 2240653..de8a895 100644 --- a/crates/signed_git/src/repo.rs +++ b/crates/signed_git/src/repo.rs @@ -62,16 +62,13 @@ impl Repo { pub fn init(path: &Path, name: &str, description: &str) -> Result { use gix::refs::transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog}; - std::fs::create_dir_all(path) - .with_context(|| format!("failed to create {}", path.display()))?; - + std::fs::create_dir_all(path)?; let repo = gix::init(path)?; let (signature, mut time_buf) = Self::repository_signature(); - let signature = signature.to_ref(&mut time_buf); - let head = gix::refs::FullName::try_from("HEAD") - .map_err(|e| anyhow::anyhow!("invalid ref name: {e}"))?; + let signature = signature.to_ref(&mut time_buf); + let head = gix::refs::FullName::try_from("HEAD")?; repo.edit_references_as( [RefEdit { @@ -99,7 +96,7 @@ impl Repo { format!("# {name}\n\n{description}\n") }; - std::fs::write(path.join("README.md"), &readme).context("failed to write README.md")?; + std::fs::write(path.join("README.md"), &readme)?; let blob = repo.write_object(gix::objs::Blob { data: readme.into_bytes(), diff --git a/crates/signed_state/src/bootstrap.rs b/crates/signed_state/src/bootstrap.rs index dd0056f..0fe0fa7 100644 --- a/crates/signed_state/src/bootstrap.rs +++ b/crates/signed_state/src/bootstrap.rs @@ -78,16 +78,10 @@ pub async fn user_grasp_list_servers( ) -> Result, Error> { let events: Vec = client .database() - .query(Filters::grasp_list(user)) + .query(Filters::grasp_list(user).limit(1)) .await? .into_iter() .collect(); - let latest = events - .into_iter() - .max_by_key(|event| event.created_at) - .map(|event| grasp_list_servers(&event)) - .unwrap_or_default(); - - Ok(latest) + Ok(events.first().map(grasp_list_servers).unwrap_or_default()) } diff --git a/crates/signed_state/src/checkouts.rs b/crates/signed_state/src/checkouts.rs index 91db84e..b72892e 100644 --- a/crates/signed_state/src/checkouts.rs +++ b/crates/signed_state/src/checkouts.rs @@ -45,6 +45,53 @@ struct Remembered { last_used: u64, } +impl Remembered { + fn resolve_associations<'a, A>( + remembered: &[Remembered], + scanned: &[(PathBuf, Option, Option)], + announcements: A, + ) -> HashMap> + where + A: IntoIterator, + { + let announcements: Vec<&Announcement> = announcements.into_iter().collect(); + let mut out: HashMap> = HashMap::new(); + + let mut sorted: Vec<&Remembered> = remembered.iter().collect(); + sorted.sort_by_key(|record| std::cmp::Reverse(record.last_used)); + for record in sorted { + let paths = out.entry(record.addr.clone()).or_default(); + if !paths.contains(&record.path) { + paths.push(record.path.clone()); + } + } + + for (path, origin, root) in scanned { + for announcement in &announcements { + let url_match = origin.as_deref().is_some_and(|origin| { + announcement + .clone + .iter() + .any(|url| same_repo_url(origin, url.as_str())) + }); + + let euc_match = root + .as_deref() + .is_some_and(|root| announcement.euc.as_deref() == Some(root)); + + if url_match || euc_match { + let paths = out.entry(announcement.addr()).or_default(); + if !paths.contains(path) { + paths.push(path.clone()); + } + } + } + } + + out + } +} + pub struct CheckoutsStore { by_repo: HashMap>, statuses: HashMap>, @@ -117,9 +164,10 @@ impl CheckoutsStore { if !cfg!(target_arch = "wasm32") { let weak = cx.entity().downgrade(); cx.defer(move |cx| { - if let Err(error) = weak.update(cx, |this, cx| this.refresh(cx)) { - log::warn!("checkouts store dropped before initial refresh could run: {error}"); - } + weak.update(cx, |this, cx| { + this.refresh(cx); + }) + .ok(); }); } @@ -143,13 +191,15 @@ impl CheckoutsStore { if cfg!(target_arch = "wasm32") { return; } + + let settings = SettingsStore::global(cx); + let addr_str = addr.to_string(); + let last_used = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); - let addr_str = addr.to_string(); - let settings = SettingsStore::global(cx); settings.update(cx, |settings, cx| { settings.edit( |s| { @@ -306,7 +356,7 @@ impl CheckoutsStore { } let associations = - CheckoutsStore::resolve_associations(&remembered, &facts, announcements.iter()); + Remembered::resolve_associations(&remembered, &facts, announcements.iter()); let associations: HashMap> = associations .into_iter() @@ -435,7 +485,7 @@ impl CheckoutsStore { Ok::<_, Error>((statuses, push_statuses)) }); - let task: gpui::Task> = cx.spawn(async move |this, cx| { + cx.spawn(async move |this, cx| { let Ok((statuses, push_statuses)) = work.await else { return Ok(()); }; @@ -456,53 +506,9 @@ impl CheckoutsStore { } })?; - Ok(()) - }); - task.detach(); - } -} - -impl CheckoutsStore { - fn resolve_associations<'a>( - remembered: &[Remembered], - scanned: &[(PathBuf, Option, Option)], - announcements: impl IntoIterator, - ) -> HashMap> { - let announcements: Vec<&Announcement> = announcements.into_iter().collect(); - let mut out: HashMap> = HashMap::new(); - - let mut sorted: Vec<&Remembered> = remembered.iter().collect(); - sorted.sort_by_key(|record| std::cmp::Reverse(record.last_used)); - for record in sorted { - let paths = out.entry(record.addr.clone()).or_default(); - if !paths.contains(&record.path) { - paths.push(record.path.clone()); - } - } - - for (path, origin, root) in scanned { - for announcement in &announcements { - let url_match = origin.as_deref().is_some_and(|origin| { - announcement - .clone - .iter() - .any(|url| same_repo_url(origin, url.as_str())) - }); - - let euc_match = root - .as_deref() - .is_some_and(|root| announcement.euc.as_deref() == Some(root)); - - if url_match || euc_match { - let paths = out.entry(announcement.addr()).or_default(); - if !paths.contains(path) { - paths.push(path.clone()); - } - } - } - } - - out + Ok::<(), anyhow::Error>(()) + }) + .detach(); } fn checkout_status(path: &Path, announced_head: Option<&str>) -> Option { @@ -535,7 +541,7 @@ impl CheckoutsStore { }) } - // Never fetches the checked-out refs; reads the tracking refs as-is. + /// Returns checkouts that are ahead of their remote tracking branch. fn checkout_push_status(path: &Path, fetch: bool) -> Option { let repo = Repo::try_open(path)?; if repo.is_dirty() { @@ -546,15 +552,13 @@ impl CheckoutsStore { let head = repo.head()?; let origin = repo.origin_url().ok().flatten()?; - if fetch { - repo.fetch_refs(&[origin], "+refs/heads/*:refs/remotes/origin/*") - .ok(); + if fetch && let Err(e) = repo.fetch_refs(&[origin], "+refs/heads/*:refs/remotes/origin/*") { + log::warn!("failed to fetch refs for {path:?}: {e}"); } let remote = format!("refs/remotes/origin/{branch}"); - // A branch never fetched or pushed yet compares against the remote - // HEAD, the fork point in practice. + // If the branch is new and has no remote tracking branch, fall back to origin/HEAD. let base = if repo.ref_exists(&remote) { remote } else if repo.ref_exists("refs/remotes/origin/HEAD") { @@ -648,146 +652,3 @@ impl CheckoutsStore { branch_matches || tip_matches } } - -#[cfg(test)] -mod tests { - use std::process::Command; - - use super::*; - - #[test] - fn same_repo_url_ignores_the_transport_scheme() { - assert!(same_repo_url( - "grasp://relay.ngit.dev/npub1test/repo", - "https://relay.ngit.dev/npub1test/repo.git" - )); - assert!(same_repo_url( - "ws://localhost:8080/npub1test/repo", - "http://localhost:8080/npub1test/repo" - )); - assert!(!same_repo_url( - "wss://localhost:8081/npub1test/repo", - "wss://localhost:8080/npub1test/repo" - )); - assert!(!same_repo_url( - "wss://host/npub1test/repo", - "wss://host/npub1other/repo" - )); - assert!(same_repo_url("/local/path", "/local/path")); - assert!(!same_repo_url("/local/path", "/local/other")); - } - - #[test] - fn checkout_status_reports_ahead_branches_only() { - let dir = tempfile::tempdir().expect("tempdir"); - let path = dir.path().join("repo"); - let _initial = Repo::init(&path, "My Repo", "").expect("init"); - let run = |args: &[&str]| { - let status = Command::new("git") - .current_dir(&path) - .env("GIT_AUTHOR_NAME", "Test Author") - .env("GIT_AUTHOR_EMAIL", "test@example.com") - .env("GIT_COMMITTER_NAME", "Test Author") - .env("GIT_COMMITTER_EMAIL", "test@example.com") - .env("GIT_EDITOR", "true") - .args(args) - .status() - .expect("git"); - assert!(status.success(), "git {args:?} failed"); - }; - let commit = |message: &str| { - run(&["add", "-A"]); - run(&["commit", "-m", message]); - }; - - run(&["checkout", "-b", "feature"]); - std::fs::write(path.join("feature.txt"), "x\n").expect("write"); - commit("feature work"); - let status = CheckoutsStore::checkout_status(&path, Some("main")).expect("status"); - assert_eq!(status.branch, "feature"); - assert_eq!(status.base, "main"); - assert_eq!(status.ahead, 1); - assert_eq!(status.head.len(), 40); - - std::fs::write(path.join("uncommitted.txt"), "y\n").expect("write"); - assert!(CheckoutsStore::checkout_status(&path, Some("main")).is_none()); - run(&["checkout", "--", "."]); - - run(&["checkout", "main"]); - assert_eq!(CheckoutsStore::checkout_status(&path, Some("main")), None); - } - - #[test] - fn checkout_push_status_counts_unpushed_commits_only() { - let dir = tempfile::tempdir().expect("tempdir"); - let remote = dir.path().join("remote"); - Repo::init(&remote, "My Repo", "").expect("init"); - let config = Command::new("git") - .args(["config", "receive.denyCurrentBranch", "ignore"]) - .current_dir(&remote) - .status() - .expect("git config"); - assert!(config.success(), "git config failed"); - - let checkout = dir.path().join("checkout"); - let status = Command::new("git") - .args([ - "clone", - "-q", - remote.to_str().unwrap(), - checkout.to_str().unwrap(), - ]) - .status() - .expect("git clone"); - assert!(status.success(), "git clone failed"); - - let run = |args: &[&str]| { - let status = Command::new("git") - .current_dir(&checkout) - .env("GIT_AUTHOR_NAME", "Test Author") - .env("GIT_AUTHOR_EMAIL", "test@example.com") - .env("GIT_COMMITTER_NAME", "Test Author") - .env("GIT_COMMITTER_EMAIL", "test@example.com") - .env("GIT_EDITOR", "true") - .args(args) - .status() - .expect("git"); - assert!(status.success(), "git {args:?} failed"); - }; - - // A fresh clone has nothing to push. - assert_eq!(CheckoutsStore::checkout_push_status(&checkout, true), None); - - std::fs::write(checkout.join("work.txt"), "x\n").expect("write"); - run(&["add", "-A"]); - run(&["commit", "-m", "local work"]); - let status = CheckoutsStore::checkout_push_status(&checkout, true).expect("status"); - assert_eq!(status.branch, "main"); - assert_eq!(status.base, "refs/remotes/origin/main"); - assert_eq!(status.ahead, 1); - assert_eq!(status.head.len(), 40); - - let local = CheckoutsStore::checkout_push_status(&checkout, false).expect("local status"); - assert_eq!(local.ahead, 1); - - run(&["push", "origin", "main"]); - assert_eq!(CheckoutsStore::checkout_push_status(&checkout, true), None); - - let remote_run = |args: &[&str]| { - let status = Command::new("git") - .current_dir(&remote) - .env("GIT_AUTHOR_NAME", "Other Author") - .env("GIT_AUTHOR_EMAIL", "other@example.com") - .env("GIT_COMMITTER_NAME", "Other Author") - .env("GIT_COMMITTER_EMAIL", "other@example.com") - .args(args) - .status() - .expect("git"); - assert!(status.success(), "git {args:?} failed"); - }; - std::fs::write(remote.join("other.txt"), "y\n").expect("write"); - remote_run(&["add", "-A"]); - remote_run(&["commit", "-m", "remote work"]); - assert_eq!(CheckoutsStore::checkout_push_status(&checkout, true), None); - } -} diff --git a/crates/signed_state/src/sync_status.rs b/crates/signed_state/src/sync_status.rs index c6aba00..cfbe4a9 100644 --- a/crates/signed_state/src/sync_status.rs +++ b/crates/signed_state/src/sync_status.rs @@ -13,8 +13,7 @@ use crate::checkouts::CheckoutsStore; use crate::refresh::{RefreshGate, RefreshRequest}; use crate::repos::RepoListStore; -// Nostr state changes rarely, so drift detection runs on a slow poll. -const SYNC_POLL: Duration = Duration::from_secs(60); +const SYNC_POLL: Duration = Duration::from_secs(180); const MAX_SYNC_CHECKOUTS: usize = 8; struct GlobalSyncStatusStore(Entity);