use std::collections::HashSet;
use std::fmt::{Debug, Display};
use anyhow::{bail, Context, Result};
use git2::Reference;
pub struct Branch<'a>(git2::Branch<'a>);
impl Branch<'_> {
pub fn delete(&mut self) -> Result<()> {
self.0.delete().context("Failed to delete branch")
}
pub fn name(&self) -> Result<String> {
match self.0.name() {
Ok(Some(name)) => Ok(name.to_string()),
Ok(None) => bail!("Branch has no name"),
Err(e) => bail!("Failed to get branch name: {}", e),
}
}
pub fn name_without_prefix(&self, prefixes: &HashSet<String>) -> Result<String> {
let name = self.name().context("Branch has no name")?;
for prefix in prefixes {
if name.starts_with(prefix) {
return Ok(name[prefix.len()..].to_string());
}
}
Ok(name)
}
}
impl<'a> From<git2::Branch<'a>> for Branch<'a> {
fn from(branch: git2::Branch<'a>) -> Self {
Branch(branch)
}
}
impl<'a> From<Reference<'a>> for Branch<'a> {
fn from(reference: Reference<'a>) -> Self {
Branch(git2::Branch::wrap(reference))
}
}
impl PartialEq for Branch<'_> {
fn eq(&self, other: &Self) -> bool {
self.name().ok() == other.name().ok()
}
}
impl Eq for Branch<'_> {}
impl Debug for Branch<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Branch")
.field("name", &self.name())
.finish()
}
}
impl Display for Branch<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.name() {
Ok(name) => write!(f, "{name}"),
Err(_) => write!(f, "<invalid branch>"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utilities;
#[test]
fn test_name() {
let (_testdir, repo) = test_utilities::create_mock_repo();
let branch_name = "test-branch";
let target_commit = test_utilities::get_latest_commit(&repo);
let branch = repo.branch(branch_name, &target_commit, false).unwrap();
let actual = Branch::from(branch).name().unwrap();
assert_eq!(actual, branch_name);
}
#[test]
fn test_name_without_prefix() {
let (_testdir, repo) = test_utilities::create_mock_repo();
let branch_name = "origin/test-branch";
let target_commit = test_utilities::get_latest_commit(&repo);
let branch = repo.branch(branch_name, &target_commit, false).unwrap();
let remote_prefixes = HashSet::from_iter(vec!["origin/".to_string()]);
let actual = Branch::from(branch)
.name_without_prefix(&remote_prefixes)
.unwrap();
assert_eq!(actual, "test-branch");
}
#[test]
fn test_name_without_prefix_no_prefix() {
let (_testdir, repo) = test_utilities::create_mock_repo();
let branch_name = "test-branch";
let target_commit = test_utilities::get_latest_commit(&repo);
let branch = repo.branch(branch_name, &target_commit, false).unwrap();
let remote_prefixes = HashSet::from_iter(vec!["origin/".to_string()]);
let actual = Branch::from(branch)
.name_without_prefix(&remote_prefixes)
.unwrap();
assert_eq!(actual, "test-branch");
}
#[test]
fn test_delete() {
let (_testdir, repo) = test_utilities::create_mock_repo();
let branch_name = "test-branch";
let target_commit = test_utilities::get_latest_commit(&repo);
let branch = repo.branch(branch_name, &target_commit, false);
let mut branch = Branch::from(branch.unwrap());
let _ = branch.delete();
assert!(repo
.find_branch(branch_name, git2::BranchType::Local)
.is_err());
}
#[test]
fn test_branch_equality() {
let (_testdir, repo) = test_utilities::create_mock_repo();
let branch_name = "test-branch";
let target_commit = test_utilities::get_latest_commit(&repo);
let branch1 = repo.branch(branch_name, &target_commit, false).unwrap();
let branch2 = repo
.find_branch(branch_name, git2::BranchType::Local)
.unwrap();
assert_eq!(Branch::from(branch1), Branch::from(branch2));
}
}