chore: refactor git module (#27)
Rust / build (macos-latest, stable) (push) Waiting to run
Rust / build (ubuntu-latest, stable) (push) Waiting to run
Rust / build (windows-latest, stable) (push) Waiting to run

Reviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
2026-10-07 02:40:12 +00:00
parent a706fadafa
commit 5887a14c50
13 changed files with 1760 additions and 1569 deletions
+52 -24
View File
@@ -1,11 +1,11 @@
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use ignore::WalkBuilder;
use crate::nip34::Nip34Binding;
use crate::repo::Repo;
/// Caps nesting so pathological trees can't stall the scan.
const SCAN_MAX_DEPTH: usize = 12;
#[derive(Debug, Clone)]
@@ -14,41 +14,69 @@ pub struct LocalRepo {
pub nip34: Option<Nip34Binding>,
}
pub fn find_git_repos(root: &Path) -> Vec<LocalRepo> {
if !root.is_dir() {
return Vec::new();
impl LocalRepo {
/// Creates a local repository entry from its path and optional NIP-34 binding.
pub fn new(path: PathBuf, nip34: Option<Nip34Binding>) -> Self {
Self { path, nip34 }
}
}
let walker = WalkBuilder::new(root)
/// Finds git repositories under `root` up to `SCAN_MAX_DEPTH` deep, without descending into them.
pub fn find_git_repos(root: &Path) -> Vec<LocalRepo> {
let Ok(root) = root.canonicalize() else {
return Vec::new();
};
let found = Arc::new(Mutex::new(Vec::<PathBuf>::new()));
let walker = WalkBuilder::new(&root)
.max_depth(Some(SCAN_MAX_DEPTH))
// Honour `.gitignore` even when the scan root is not itself a repository.
.require_git(false)
.filter_entry({
let found = found.clone();
move |entry| {
let Some(kind) = entry.file_type() else {
return true;
};
if !kind.is_dir() {
return true;
}
let dir = entry.path();
let Ok(repo) = gix::discover(dir) else {
return true;
};
let Some(workdir) = repo.workdir() else {
return false;
};
if workdir != dir {
return true;
}
match found.lock() {
Ok(mut repos) => repos.push(dir.to_path_buf()),
Err(poisoned) => poisoned.into_inner().push(dir.to_path_buf()),
}
false
}
})
.build();
let mut repos: Vec<PathBuf> = walker
.flatten()
.filter(|entry| entry.file_type().is_some_and(|kind| kind.is_dir()))
.map(ignore::DirEntry::into_path)
.filter(|dir| dir.join(".git").exists())
.filter_map(|dir| dir.canonicalize().ok())
.collect();
for _ in walker {}
let mut repos = match found.lock() {
Ok(mut repos) => std::mem::take(&mut *repos),
Err(poisoned) => std::mem::take(&mut *poisoned.into_inner()),
};
repos.sort();
repos.dedup();
// A repository nested inside another, like a submodule worktree, is not reported.
let mut roots: Vec<PathBuf> = Vec::with_capacity(repos.len());
for repo in repos {
if !roots.iter().any(|kept| repo.starts_with(kept)) {
roots.push(repo);
}
}
roots
repos
.into_iter()
.map(|path| {
let nip34 = Repo::open(&path).ok().and_then(|repo| repo.nip34_binding());
LocalRepo { path, nip34 }
LocalRepo::new(path, nip34)
})
.collect()
}