use std::collections::HashMap;
use std::path::Path;
pub fn enumerate_mem_repo_branches(workspace_root: &Path) -> Option<Vec<String>> {
let gitdir = workspace_root.join("mem-repo").join(".git");
enumerate_branches_in_gitdir(&gitdir)
}
fn enumerate_branches_in_gitdir(gitdir: &Path) -> Option<Vec<String>> {
if !gitdir.is_dir() {
return None;
}
let repo = match gix::open(gitdir) {
Ok(repo) => repo,
Err(e) => {
tracing::warn!(
gitdir = %gitdir.display(),
error = %e,
"could not open mem-repo gitdir"
);
return None;
}
};
if repo.find_reference("refs/heads/__MEMSTEAD").is_err() {
return None;
}
let mut branch_names: Vec<String> = Vec::new();
let mut seen_leaves: HashMap<String, String> = HashMap::new();
let refs_platform = match repo.references() {
Ok(p) => p,
Err(e) => {
tracing::warn!(
gitdir = %gitdir.display(),
error = %e,
"could not access mem-repo references"
);
return None;
}
};
let iter = match refs_platform.local_branches() {
Ok(it) => it,
Err(e) => {
tracing::warn!(
gitdir = %gitdir.display(),
error = %e,
"could not enumerate mem-repo local branches"
);
return None;
}
};
for r in iter {
let reference = match r {
Ok(reference) => reference,
Err(e) => {
tracing::warn!(
error = %e,
"failed to read a mem-repo reference; skipping"
);
continue;
}
};
let short = reference.name().shorten();
let name = match std::str::from_utf8(short) {
Ok(name) => name,
Err(_) => continue,
};
if name == "main" {
continue;
}
if name
.split('/')
.next()
.map(|s| s.starts_with("__"))
.unwrap_or(false)
{
continue;
}
let leaf = name.rsplit('/').next().unwrap_or(name);
if let Some(prior) = seen_leaves.get(leaf) {
tracing::warn!(
leaf = leaf,
existing = prior.as_str(),
duplicate = name,
"mem-repo has two branches with the same leaf; dropping the second"
);
continue;
}
seen_leaves.insert(leaf.to_string(), name.to_string());
branch_names.push(leaf.to_string());
}
branch_names.sort();
Some(branch_names)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
fn init_mem_repo_with_branches(root: &Path, branches: &[&str]) {
let mem_repo = root.join("mem-repo").join(".git");
std::fs::create_dir_all(&mem_repo).unwrap();
let repo = gix::init_bare(&mem_repo).unwrap();
let actor = gix::actor::Signature {
name: "test".into(),
email: "test@example.com".into(),
time: gix::date::Time {
seconds: 0,
offset: 0,
},
};
let mut buf = gix::date::parse::TimeBuf::default();
let actor_ref = actor.to_ref(&mut buf);
let empty_tree = repo.empty_tree().id();
repo.commit_as(
actor_ref,
actor_ref,
"refs/heads/main",
"test main",
empty_tree,
Vec::<gix::ObjectId>::new(),
)
.unwrap();
let mut buf = gix::date::parse::TimeBuf::default();
let actor_ref = actor.to_ref(&mut buf);
repo.commit_as(
actor_ref,
actor_ref,
"refs/heads/__MEMSTEAD",
"test __MEMSTEAD",
empty_tree,
Vec::<gix::ObjectId>::new(),
)
.unwrap();
for branch in branches {
let mut buf = gix::date::parse::TimeBuf::default();
let actor_ref = actor.to_ref(&mut buf);
repo.commit_as(
actor_ref,
actor_ref,
format!("refs/heads/{branch}"),
"test seed",
empty_tree,
Vec::<gix::ObjectId>::new(),
)
.unwrap();
}
}
#[test]
fn enumerates_per_mem_branches_excluding_main_and_registry_refs() {
let tmp = TempDir::new().unwrap();
init_mem_repo_with_branches(tmp.path(), &["alpha", "beta", "gamma"]);
let names = enumerate_mem_repo_branches(tmp.path()).expect("real workspace");
assert_eq!(names, vec!["alpha", "beta", "gamma"]);
}
#[test]
fn returns_none_when_mem_repo_missing() {
let tmp = TempDir::new().unwrap();
assert!(enumerate_mem_repo_branches(tmp.path()).is_none());
}
#[test]
fn returns_none_when_memstead_ref_missing() {
let tmp = TempDir::new().unwrap();
let mem_repo = tmp.path().join("mem-repo").join(".git");
std::fs::create_dir_all(&mem_repo).unwrap();
gix::init_bare(&mem_repo).unwrap();
assert!(enumerate_mem_repo_branches(tmp.path()).is_none());
}
#[test]
fn surfaces_hierarchical_layout_as_leaf_names() {
let tmp = TempDir::new().unwrap();
init_mem_repo_with_branches(tmp.path(), &["demo/engine", "planning/exec-foo"]);
let names = enumerate_mem_repo_branches(tmp.path()).expect("real workspace");
assert_eq!(names, vec!["engine", "exec-foo"]);
}
}