diff --git a/crates/signed_git/src/lib.rs b/crates/signed_git/src/lib.rs index efeb1ba..f1eae1f 100644 --- a/crates/signed_git/src/lib.rs +++ b/crates/signed_git/src/lib.rs @@ -34,7 +34,7 @@ pub use repo::{ repo_tags, root_commit, worktree_branches, worktree_current_branch, worktree_ref_exists, worktree_ref_state, }; -pub use scan::find_git_repos; +pub use scan::{LocalRepo, find_git_repos}; pub use worktree::{ WorktreeSnapshot, find_readme, worktree_checkout_branch, worktree_checkout_tag, worktree_commits_ahead, worktree_dirty, worktree_entries, worktree_read, worktree_snapshot, diff --git a/crates/signed_git/src/scan.rs b/crates/signed_git/src/scan.rs index cd16606..6e64fc0 100644 --- a/crates/signed_git/src/scan.rs +++ b/crates/signed_git/src/scan.rs @@ -2,12 +2,21 @@ use std::path::{Path, PathBuf}; use ignore::WalkBuilder; +use crate::nip34::{Nip34Binding, detect_nip34}; + /// Caps nesting so pathological trees can't stall the scan. const SCAN_MAX_DEPTH: usize = 12; -/// Walk `root` recursively and collect the paths of git repositories below it. -/// `.gitignore` and `.ignore` files are honoured. -pub fn find_git_repos(root: &Path) -> Vec { +/// A git repository discovered under a scan root. +#[derive(Debug, Clone)] +pub struct LocalRepo { + pub path: PathBuf, + /// `None` for a plain repository. + pub nip34: Option, +} + +/// Walk `root` recursively and collect the git repositories below it. +pub fn find_git_repos(root: &Path) -> Vec { if !root.is_dir() { return Vec::new(); } @@ -38,4 +47,10 @@ pub fn find_git_repos(root: &Path) -> Vec { } roots + .into_iter() + .map(|path| { + let nip34 = detect_nip34(&path); + LocalRepo { path, nip34 } + }) + .collect() } diff --git a/crates/signed_git/src/tests.rs b/crates/signed_git/src/tests.rs index 890efb3..6f37456 100644 --- a/crates/signed_git/src/tests.rs +++ b/crates/signed_git/src/tests.rs @@ -42,7 +42,10 @@ fn find_git_repos_discovers_repositories_recursively() { std::fs::create_dir_all(outer.join(".git")).unwrap(); std::fs::create_dir_all(outer.join("sub/other/.git")).unwrap(); - let mut found = find_git_repos(root); + let mut found: Vec = find_git_repos(root) + .into_iter() + .map(|repo| repo.path) + .collect(); found.sort(); let mut expected = vec![ diff --git a/crates/signed_state/src/checkouts.rs b/crates/signed_state/src/checkouts.rs index 2b01d06..67e52ce 100644 --- a/crates/signed_state/src/checkouts.rs +++ b/crates/signed_state/src/checkouts.rs @@ -350,7 +350,9 @@ impl CheckoutsStore { // // The facts are the origin URL and the root commit, both CLI reads. let mut facts: Vec<(PathBuf, Option, Option)> = Vec::new(); - for path in scanned.iter() { + for scanned in scanned.iter() { + let path = &scanned.path; + // The browser's mirror clones share the announce URLs and EUCs. They are not user checkouts. if cache_root .as_ref() diff --git a/crates/signed_state/src/lib.rs b/crates/signed_state/src/lib.rs index a936dd9..297578f 100644 --- a/crates/signed_state/src/lib.rs +++ b/crates/signed_state/src/lib.rs @@ -16,12 +16,13 @@ use git_store::set_git_cache; pub use git_store::{ensure_repo_mirror, open_repo_mirror, repo_mirror_path}; use gpui::{App, AppContext}; pub use inbox::{Inbox, query_inbox}; -pub use local_repos::LocalReposStore; +pub use local_repos::{LocalReposStore, local_repo_addr}; pub use nostr_sdk::prelude::Timestamp; pub use profile::{Profile, ProfileStore}; pub use refresh::{RefreshGate, RefreshRequest}; pub use repo::RepoStore; pub use repos::{RepoActivityCounts, RepoListStore}; +pub use signed_git::{GraspSignals, LocalRepo, Nip34Binding, Nip34Kind}; use signed_nostr::new_backend; #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/signed_state/src/local_repos.rs b/crates/signed_state/src/local_repos.rs index 95b6a55..85891a3 100644 --- a/crates/signed_state/src/local_repos.rs +++ b/crates/signed_state/src/local_repos.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use anyhow::Error; use gpui::{App, AppContext, Context, Entity, Global, Task}; -use signed_git::find_git_repos; +use signed_core::{RepoAddr, repo_addr}; +use signed_git::{LocalRepo, find_git_repos}; struct GlobalLocalReposStore(Entity); @@ -13,7 +14,7 @@ impl Global for GlobalLocalReposStore {} pub struct LocalReposStore { pub roots: Arc>, /// Git repositories discovered under [`Self::roots`], sorted by path. - pub repos: Arc>, + pub repos: Arc>, pub scanning: bool, scan_dirty: bool, } @@ -48,7 +49,7 @@ impl LocalReposStore { self.repos = Arc::new( self.repos .iter() - .filter(|repo| repo.as_path() != path) + .filter(|repo| repo.path.as_path() != path) .cloned() .collect(), ); @@ -75,8 +76,8 @@ impl LocalReposStore { for root in roots.iter() { repos.extend(find_git_repos(root)); } - repos.sort(); - repos.dedup(); + repos.sort_by(|a, b| a.path.cmp(&b.path)); + repos.dedup_by(|a, b| a.path == b.path); repos }); @@ -103,3 +104,13 @@ impl LocalReposStore { task.detach(); } } + +/// The NIP-34 coordinate a repository's detection resolved, when both the owner +/// and the identifier were recovered. +pub fn local_repo_addr(repo: &LocalRepo) -> Option { + let binding = repo.nip34.as_ref()?; + let owner = binding.owner?; + let identifier = binding.identifier.as_deref()?; + + Some(repo_addr(owner, identifier)) +} diff --git a/crates/workspace/src/views/sidebar/mod.rs b/crates/workspace/src/views/sidebar/mod.rs index 2da0a3e..6d5c063 100644 --- a/crates/workspace/src/views/sidebar/mod.rs +++ b/crates/workspace/src/views/sidebar/mod.rs @@ -132,13 +132,13 @@ impl SidebarPanel { .read(cx) .repos .iter() - .filter(|path| { - let Some(name) = path.file_name() else { + .filter(|repo| { + let Some(name) = repo.path.file_name() else { return true; }; !ids.contains(&identifier_from_name(&name.to_string_lossy())) }) - .cloned() + .map(|repo| repo.path.clone()) .collect() }; diff --git a/docs/local-repo-nip34-detection.md b/docs/local-repo-nip34-detection.md index 125e658..7c7f71f 100644 --- a/docs/local-repo-nip34-detection.md +++ b/docs/local-repo-nip34-detection.md @@ -1,6 +1,6 @@ # Local repository NIP-34 detection -Status: plan, not yet implemented. +Status: Phases 1–2 implemented. Phases 3–5 pending. ## Motivation @@ -195,18 +195,22 @@ Implemented. ### Phase 2 — thread `LocalRepo` through the state layer -- [ ] `crates/signed_git/src/scan.rs`: change `find_git_repos` to `Vec`; in the walk, +Implemented. + +- [x] `crates/signed_git/src/scan.rs`: change `find_git_repos` to `Vec`; in the walk, `detect_nip34` each kept path. -- [ ] `crates/signed_git/src/tests.rs`: update `find_git_repos_*` expectations to the new type +- [x] `crates/signed_git/src/tests.rs`: update `find_git_repos_*` expectations to the new type (compare `.path`). -- [ ] `crates/signed_state/src/local_repos.rs`: `repos: Arc>`; `remove(&path)` +- [x] `crates/signed_state/src/local_repos.rs`: `repos: Arc>`; `remove(&path)` filters on `.path`; the background scan already returns the richer vector unchanged. -- [ ] `crates/signed_state/src/checkouts.rs` (~`run_refresh`): iterate `.path` instead of bare +- [x] `crates/signed_state/src/checkouts.rs` (`run_refresh`): iterate `.path` instead of bare paths when collecting `scanned`. -- [ ] `crates/signed_state/src/lib.rs`: re-export `LocalRepo`, `Nip34Binding`, `Nip34Kind` (and - `GraspSignals` if the UI needs the flags). -- [ ] `crates/signed_state/src/local_repos.rs`: add a helper that resolves a binding to a - `RepoAddr` (`signed_core::repo_addr(owner, identifier)`) when both are known. +- [x] `crates/signed_state/src/lib.rs`: re-export `LocalRepo`, `Nip34Binding`, `Nip34Kind` and + `GraspSignals`. +- [x] `crates/signed_state/src/local_repos.rs`: `local_repo_addr(&LocalRepo) -> Option` + resolves a binding when both owner and identifier are known. +- [x] `crates/workspace/src/views/sidebar/mod.rs`: minimal adaptation of the existing folder-name + dedupe to the new entry type so the tree compiles; the badge/dedupe rewrite is Phase 3. ### Phase 3 — sidebar: derive the local list and link to announcements