use git2::{
Delta, DiffOptions, Error as GitError, ObjectType, Repository, TreeWalkMode, TreeWalkResult,
};
use log::{debug, info};
use std::path::{Path, PathBuf};
pub trait GitOpsTrait {
fn open_repository(&self, repo_path: &Path) -> Result<Repository, GitError>;
fn get_staged_files(&self, repo: &Repository) -> Result<Vec<PathBuf>, GitError>;
fn get_tracked_files(&self, repo: &Repository) -> Result<Vec<PathBuf>, GitError>;
fn get_deleted_files(&self, repo: &Repository) -> Result<Vec<PathBuf>, GitError>;
}
pub struct GitOps;
impl GitOpsTrait for GitOps {
fn open_repository(&self, repo_path: &Path) -> Result<Repository, GitError> {
debug!("Opening repository at path: {:?}", repo_path);
let repo = Repository::open(repo_path)?;
info!("Successfully opened repository at path: {:?}", repo_path);
Ok(repo)
}
fn get_staged_files(&self, repo: &Repository) -> Result<Vec<PathBuf>, GitError> {
debug!("Retrieving staged files with meaningful content changes");
let mut diff_opts = DiffOptions::new();
diff_opts
.ignore_whitespace(true)
.ignore_whitespace_change(true)
.ignore_whitespace_eol(true)
.include_untracked(false)
.force_text(true)
.skip_binary_check(true);
let head_tree = repo.head()?.peel_to_tree()?;
let diff = repo.diff_tree_to_index(Some(&head_tree), None, Some(&mut diff_opts))?;
let mut staged_files = Vec::new();
diff.foreach(
&mut |delta, _| {
if let Some(path) = delta.new_file().path() {
debug!("Staged file added/modified: {:?}", path);
staged_files.push(path.to_path_buf());
}
true
},
None,
None,
None,
)?;
info!("Found {} staged files", staged_files.len());
Ok(staged_files)
}
fn get_tracked_files(&self, repo: &Repository) -> Result<Vec<PathBuf>, GitError> {
debug!("Retrieving all tracked files");
let head_tree = repo.head()?.peel_to_tree()?;
let mut tracked_files = Vec::new();
head_tree.walk(TreeWalkMode::PreOrder, |root, entry| {
if entry.kind() == Some(ObjectType::Blob) {
let path = if root.is_empty() {
entry.name().unwrap_or("").into()
} else {
format!("{}/{}", root, entry.name().unwrap_or(""))
};
debug!("Tracked file: {:?}", path);
tracked_files.push(PathBuf::from(path));
}
TreeWalkResult::Ok
})?;
info!("Found {} tracked files", tracked_files.len());
Ok(tracked_files)
}
fn get_deleted_files(&self, repo: &Repository) -> Result<Vec<PathBuf>, GitError> {
debug!("Retrieving staged files that have been deleted");
let mut diff_opts = DiffOptions::new();
diff_opts.include_untracked(false).skip_binary_check(true);
let head_tree = repo.head()?.peel_to_tree()?;
let diff = repo.diff_tree_to_index(Some(&head_tree), None, Some(&mut diff_opts))?;
let mut deleted_files = Vec::new();
diff.foreach(
&mut |delta, _| {
if delta.status() == Delta::Deleted {
if let Some(path) = delta.old_file().path() {
debug!("Deleted file: {:?}", path);
deleted_files.push(path.to_path_buf());
}
}
true
},
None,
None,
None,
)?;
info!("Found {} deleted files", deleted_files.len());
Ok(deleted_files)
}
}