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_len} staged files",
staged_files_len = 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_len} tracked files",
tracked_files_len = 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_len} deleted files",
deleted_files_len = deleted_files.len()
);
Ok(deleted_files)
}
}