use crate::{Commit, Error, File};
pub struct Branch<'a> {
pub(crate) repo: &'a git2::Repository,
pub(crate) branch: git2::Branch<'a>,
pub(crate) tree: git2::Tree<'a>,
}
impl<'a> Branch<'a> {
pub(crate) fn new(repo: &'a git2::Repository, branch: &str) -> Result<Self, Error> {
let branch = repo.find_branch(branch, git2::BranchType::Local)?;
let tree = branch.get().peel_to_tree()?;
Ok(Branch { repo, branch, tree })
}
pub(crate) fn new_by_reference(
repo: &'a git2::Repository,
reference: git2::Reference<'a>,
) -> Result<Self, Error> {
let branch = git2::Branch::wrap(reference);
let tree = branch.get().peel_to_tree()?;
Ok(Branch { repo, branch, tree })
}
pub fn name(&self) -> &str {
match self.branch.name() {
Ok(Some(name)) => name,
_ => "",
}
}
pub fn files(&'a self, mut cb: impl FnMut(File<'_>) -> Result<(), Error>) -> Result<(), Error> {
let mut result = Ok(());
self.tree
.walk(git2::TreeWalkMode::PreOrder, |path, entry| {
result = cb(File {
branch: self,
path,
entry: entry.into(),
});
if result.is_err() {
git2::TreeWalkResult::Abort
} else {
git2::TreeWalkResult::Ok
}
})?;
result
}
pub fn commits(&'a self) -> Result<impl Iterator<Item = Commit<'a>>, Error> {
let commit = self.branch.get().peel_to_commit()?;
Ok(BranchCommitIterator {
commit: Some(commit),
})
}
pub fn get_file_by_path(&'a self, path: &'a str) -> Result<File<'a>, Error> {
let ancestor = if let Some(idx) = path.rfind('/') {
&path[..idx + 1]
} else {
""
};
self.tree
.get_path(std::path::Path::new(path))
.map(|entry| File {
branch: self,
path: ancestor,
entry: entry.into(),
})
}
}
struct BranchCommitIterator<'a> {
commit: Option<git2::Commit<'a>>,
}
impl<'a> Iterator for BranchCommitIterator<'a> {
type Item = Commit<'a>;
fn next(&mut self) -> Option<Self::Item> {
let commit = self.commit.take()?;
if commit.parent_count() > 0 {
self.commit = commit.parent(0).ok();
}
Some(Commit { commit })
}
}
#[test]
fn test_files() {
let repo = crate::Repo::open(".").unwrap();
let branch = match repo.current_branch() {
Ok(Some(branch)) => branch,
_ => {
eprintln!("Warning: No branch found");
return;
}
};
println!("Found branch {:?}", branch.name());
let mut files = Vec::new();
branch
.files(|file| {
files.push(file.path());
Ok(())
})
.unwrap();
println!("Found {} files", files.len());
println!("{:#?}", files);
assert!(files.iter().any(|f| f.starts_with("src/")));
assert!(files.iter().any(|f| f == "Cargo.toml"));
let file = branch.get_file_by_path("src/lib.rs").unwrap();
assert_eq!("src/lib.rs", file.path());
}
#[test]
fn test_commits() {
let repo = crate::Repo::open(".").unwrap();
let branch = match repo.current_branch().unwrap() {
Some(branch) => branch,
None => {
eprintln!("Warning: No branch found");
return;
}
};
println!("Found branch {:?}", branch.name());
let commits: Vec<_> = branch.commits().unwrap().collect();
assert!(!commits.is_empty());
println!("Branch has {} commits", commits.len());
if commits.len() > 1 {
assert_eq!("Initial commit", commits.last().unwrap().message());
assert_eq!("4c247b6", commits.last().unwrap().id());
}
}