use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
pub fn repos_under(root: &Path) -> Result<Vec<PathBuf>> {
let mut out: Vec<PathBuf> = std::fs::read_dir(root)
.with_context(|| format!("read {}", root.display()))?
.filter_map(std::result::Result::ok)
.map(|e| e.path())
.filter(|p| p.is_dir() && is_repo(p))
.collect();
out.sort();
Ok(out)
}
#[must_use]
pub fn is_repo(dir: &Path) -> bool {
dir.join(".bare").is_dir() || dir.join(".git").exists()
}
#[must_use]
pub fn anchor_for(repo_dir: &Path) -> Option<PathBuf> {
let bare = repo_dir.join(".bare");
if bare.is_dir() {
Some(bare)
} else if repo_dir.join(".git").exists() {
Some(repo_dir.to_path_buf())
} else {
None
}
}
#[must_use]
pub fn repo_name(repo_dir: &Path) -> String {
repo_dir
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn tmp() -> TempDir {
tempfile::tempdir().expect("tempdir")
}
#[test]
fn detects_bare_clone() {
let d = tmp();
std::fs::create_dir_all(d.path().join(".bare")).unwrap();
assert!(is_repo(d.path()));
assert_eq!(anchor_for(d.path()), Some(d.path().join(".bare")));
}
#[test]
fn detects_plain_clone() {
let d = tmp();
std::fs::create_dir_all(d.path().join(".git")).unwrap();
assert!(is_repo(d.path()));
assert_eq!(anchor_for(d.path()), Some(d.path().to_path_buf()));
}
#[test]
fn detects_git_file_in_worktree() {
let d = tmp();
std::fs::write(d.path().join(".git"), "gitdir: /somewhere\n").unwrap();
assert!(is_repo(d.path()));
assert_eq!(anchor_for(d.path()), Some(d.path().to_path_buf()));
}
#[test]
fn not_repo_when_empty() {
let d = tmp();
assert!(!is_repo(d.path()));
assert!(anchor_for(d.path()).is_none());
}
#[test]
fn repos_under_finds_immediate_children() {
let root = tmp();
std::fs::create_dir_all(root.path().join("a/.bare")).unwrap();
std::fs::create_dir_all(root.path().join("b/.git")).unwrap();
std::fs::create_dir_all(root.path().join("not-a-repo")).unwrap();
let found = repos_under(root.path()).unwrap();
assert_eq!(found.len(), 2);
assert_eq!(found[0].file_name().unwrap(), "a");
assert_eq!(found[1].file_name().unwrap(), "b");
}
#[test]
fn repos_under_missing_root_errors() {
assert!(repos_under(Path::new("/nonexistent/path")).is_err());
}
#[test]
fn repo_name_returns_basename() {
assert_eq!(repo_name(Path::new("/a/b/myrepo")), "myrepo");
}
}