chore: refactor git module (#27)
Reviewed-on: #27
This commit was merged in pull request #27.
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user