use std::fs::{metadata, read_dir}; use std::io; use std::path::{Path, PathBuf}; use eyre::Result; use git2::Repository; pub struct State { pub root: PathBuf, pub data: Vec, } // TODO figure out how to make this return impl Stream without living in actual hell // Heartbreaking: E0733 fn find_repos_in(dir: impl AsRef) -> Result> { let dir = dir.as_ref(); let dir = read_dir(dir); let dir = match dir { Ok(x) => x, Err(err) if err.kind() == io::ErrorKind::PermissionDenied => return Ok(vec![]), Err(err) => Err(err)?, }; let mut result = vec![]; for subdir in dir { if let Ok(subdir) = subdir { match Repository::open_bare(subdir.path()) { Ok(repo) => result.push(repo), Err(err) if err.class() == git2::ErrorClass::Repository && err.code() == git2::ErrorCode::NotFound => { let metadata = metadata(subdir.path())?; if metadata.is_dir() { result.extend(find_repos_in(subdir.path())?) } } // TODO handle in a non-god-awful way Err(err) => panic!("{}", err), } } } Ok(result) } impl State { pub async fn discover(root: impl AsRef) -> Result { let root = root.as_ref(); let mut data = find_repos_in(root)?; data.sort_by_key(|repo| { let mut last_update = git2::Time::new(0, 0); repo.odb().expect("no object database").foreach(|oid| { if let Ok(commit) = repo.find_commit(oid.clone()) { last_update = last_update.max(commit.time()); } true }).expect("foreach failed"); last_update }); data.reverse(); Ok(Self { root: root.to_owned(), data }) } pub fn relative_path<'a>(&'a self, subdir: &'a Path) -> &'a Path { subdir.strip_prefix(&self.root).unwrap_or(subdir) } pub fn open(&self, path: impl AsRef) -> Result { let path = self.root.join(path); Ok(Repository::open_bare(path)?) } }