use crate::errors::MapDagError;
use dag::ops::DagAlgorithm;
use dag::ops::DagPersistent;
use dag::Dag;
use dag::Set;
use dag::Vertex;
use std::collections::BTreeMap;
use std::ops::Deref;
use std::path::Path;
pub struct GitDag {
dag: Dag,
heads: Set,
references: BTreeMap<String, Vertex>,
}
impl GitDag {
pub fn open(git_dir: &Path, dag_dir: &Path, main_branch: &str) -> dag::Result<Self> {
let git_repo = git2::Repository::open(git_dir)
.with_context(|| format!("opening git repo at {}", git_dir.display()))?;
Self::open_git_repo(&git_repo, dag_dir, main_branch)
}
pub fn open_git_repo(
git_repo: &git2::Repository,
dag_dir: &Path,
main_branch: &str,
) -> dag::Result<Self> {
let dag = Dag::open(dag_dir)?;
Ok(sync_from_git(dag, git_repo, main_branch)?)
}
pub fn git_references(&self) -> &BTreeMap<String, Vertex> {
&self.references
}
pub fn git_heads(&self) -> Set {
self.heads.clone()
}
}
impl Deref for GitDag {
type Target = Dag;
fn deref(&self) -> &Self::Target {
&self.dag
}
}
fn sync_from_git(
mut dag: Dag,
git_repo: &git2::Repository,
main_branch: &str,
) -> dag::Result<GitDag> {
let mut master_heads = Vec::new();
let mut non_master_heads = Vec::new();
let mut references = BTreeMap::new();
let git_refs = git_repo.references().context("listing git references")?;
for git_ref in git_refs {
let git_ref = git_ref.context("resolving git reference")?;
let commit = match git_ref.peel_to_commit() {
Err(e) => {
tracing::warn!(
"git ref {} cannot resolve to commit: {}",
String::from_utf8_lossy(git_ref.name_bytes()),
e
);
continue;
}
Ok(c) => c,
};
let oid = commit.id();
let vertex = Vertex::copy_from(oid.as_bytes());
if let Some(name) = git_ref.name() {
references.insert(name.to_string(), vertex.clone());
}
if git_ref.name() == Some(main_branch) {
master_heads.push(vertex);
} else {
non_master_heads.push(vertex);
}
}
let parent_func = |v: Vertex| -> dag::Result<Vec<Vertex>> {
tracing::trace!("visiting git commit {:?}", &v);
let oid = git2::Oid::from_bytes(v.as_ref())
.with_context(|| format!("converting to git oid for {:?}", &v))?;
let commit = git_repo
.find_commit(oid)
.with_context(|| format!("resolving {:?} to git commit", &v))?;
Ok(commit
.parent_ids()
.map(|id| Vertex::copy_from(id.as_bytes()))
.collect())
};
dag.add_heads_and_flush(parent_func, &master_heads, &non_master_heads)?;
let possible_heads =
Set::from_static_names(master_heads.into_iter().chain(non_master_heads.into_iter()));
let heads = dag.heads_ancestors(possible_heads)?;
Ok(GitDag {
dag,
heads,
references,
})
}