use crate::{errors::runtime_error, GitResult};
use git2::{
Branch, BranchType, Commit, Error, ErrorClass, ErrorCode, Index, IndexAddOption, IndexEntry, IndexTime, ObjectType, Oid,
Repository, RepositoryInitMode, RepositoryInitOptions, Signature, TreeEntry,
};
use globset::{Glob, GlobSet, GlobSetBuilder};
use std::{
ffi::OsStr,
fs::{File, OpenOptions},
io::{Read, Write},
path::Path,
str::FromStr,
};
pub struct GitCleaner {
repo: Repository,
purge_size: Option<usize>,
purge_path: GlobSet,
}
impl GitCleaner {
pub fn new(project: Repository) -> Self {
Self { repo: project, purge_size: None, purge_path: GlobSet::empty() }
}
pub fn set_max_size(&mut self, size: usize) {
if size == 0 {
self.purge_size = None;
}
else {
self.purge_size = Some(size);
}
}
pub fn set_pattern(&mut self, pattern: &str) -> GitResult<()> {
let mut set = GlobSetBuilder::new();
for line in pattern.lines() {
for item in line.split(',') {
match Glob::from_str(item.trim()) {
Ok(o) => {
set.add(o);
}
Err(e) => runtime_error(e.to_string())?,
}
}
}
match set.build() {
Ok(o) => self.purge_path = o,
Err(e) => runtime_error(e.to_string())?,
}
Ok(())
}
}
impl GitCleaner {
fn prune(&mut self, old: &str, start: Oid, new: &str) -> Result<Branch, Error> {
let old_branch = self.repo.find_branch(old, BranchType::Local)?;
let target = old_branch.get().peel_to_commit()?;
let new_branch = self.repo.branch(new, &target, true)?;
let old_tree = old_branch.get().peel_to_tree()?;
let mut tree_builder = self.repo.treebuilder(Some(&old_tree))?;
for entry in old_tree.iter() {
if let Ok(path) = self.should_remove(&entry) {
tree_builder.remove(path)?;
}
}
let new_tree_oid = tree_builder.write()?;
let new_tree = self.repo.find_tree(new_tree_oid)?;
let old_commit = old_branch.get().peel_to_commit()?;
let new_commit_oid = self.repo.commit(
Some("HEAD"),
&old_commit.author(),
&old_commit.committer(),
"Purge commit",
&new_tree,
&[&old_commit],
)?;
Ok(new_branch)
}
pub fn should_remove<'a>(&self, entry: &'a TreeEntry) -> GitResult<&'a [u8]> {
let object = entry.to_object(&self.repo)?;
let file_bytes = entry.name_bytes();
let file_utf8 = String::from_utf8_lossy(file_bytes);
let file_path = Path::new(file_utf8.as_ref());
let should_remove = match self.purge_path.is_match(file_path) {
true => true,
false => {
false
}
};
if should_remove {
return Ok(file_bytes);
}
Err(Error::from_str("File does not match purge conditions"))
}
}