use std::{
path::{Path, PathBuf},
slice,
};
use crate::{
ModifyGuardError,
vcs::{self, VcsRepository},
};
#[cfg(test)]
mod tests;
#[derive(Debug)]
pub struct Repository {
inner: Box<dyn VcsRepository>,
}
impl Repository {
#[inline]
pub fn discover<P>(path: P) -> Result<Option<Self>, ModifyGuardError>
where
P: AsRef<Path>,
{
let Some(inner) = vcs::discover(path.as_ref())? else {
return Ok(None);
};
Ok(Some(Self { inner }))
}
#[inline]
pub fn open<P>(path: P) -> Result<Self, ModifyGuardError>
where
P: AsRef<Path>,
{
let inner = vcs::open(path.as_ref())?;
Ok(Self { inner })
}
#[inline]
#[must_use]
pub fn worktree(&self) -> &Path {
self.inner.worktree()
}
#[inline]
pub fn resolve_path<P>(&self, path: P) -> Result<PathBuf, ModifyGuardError>
where
P: AsRef<Path>,
{
self.inner.resolve_path(path.as_ref())
}
#[inline]
pub fn repository_changes(&self) -> Result<Option<RepositoryChanges>, ModifyGuardError> {
self.inner.repository_changes()
}
#[inline]
pub fn path_changes<P>(&self, wt_path: P) -> Result<Option<RepositoryChanges>, ModifyGuardError>
where
P: AsRef<Path>,
{
self.inner.path_changes(wt_path.as_ref())
}
#[inline]
pub fn file_change<P>(&self, wt_path: P) -> Result<Option<FileChange>, ModifyGuardError>
where
P: AsRef<Path>,
{
self.inner.file_change(wt_path.as_ref())
}
}
#[derive(Debug, Clone)]
pub struct RepositoryChanges {
files: Vec<FileChange>,
num_dirty_files: usize,
num_staged_files: usize,
}
impl RepositoryChanges {
#[cfg(any(test, vcs_backend_enabled))]
pub(crate) fn new<I>(files: I) -> Option<Self>
where
I: IntoIterator<Item = FileChange>,
{
let mut files = files.into_iter().collect::<Vec<_>>();
files.sort_by(|a, b| a.wt_path().cmp(b.wt_path()));
assert!(
files
.array_windows()
.all(|[a, b]| a.wt_path() != b.wt_path()),
"repository change entries must be unique by worktree-relative path",
);
if files.is_empty() {
return None;
}
let mut num_dirty_files = 0;
let mut num_staged_files = 0;
for file in &files {
num_dirty_files += usize::from(file.is_dirty());
num_staged_files += usize::from(file.is_staged());
}
Some(Self {
files,
num_dirty_files,
num_staged_files,
})
}
#[inline]
#[must_use]
pub fn files(&self) -> Files<'_> {
Files {
iter: self.files.iter(),
}
}
#[inline]
#[must_use]
pub fn dirty_files(&self) -> DirtyFiles<'_> {
DirtyFiles {
iter: self.files.iter(),
len: self.num_dirty_files,
}
}
#[inline]
#[must_use]
pub fn staged_files(&self) -> StagedFiles<'_> {
StagedFiles {
iter: self.files.iter(),
len: self.num_staged_files,
}
}
#[inline]
#[must_use]
pub fn has_dirty_files(&self) -> bool {
self.num_dirty_files > 0
}
#[inline]
#[must_use]
pub fn has_staged_files(&self) -> bool {
self.num_staged_files > 0
}
}
#[derive(Debug, Clone)]
pub struct FileChange {
pub(crate) wt_path: PathBuf,
pub(crate) dirty: bool,
pub(crate) staged: bool,
}
impl FileChange {
#[inline]
#[must_use]
pub fn wt_path(&self) -> &Path {
&self.wt_path
}
#[inline]
#[must_use]
pub fn is_dirty(&self) -> bool {
self.dirty
}
#[inline]
#[must_use]
pub fn is_staged(&self) -> bool {
self.staged
}
}
#[derive(Debug, Clone)]
pub struct Files<'a> {
iter: slice::Iter<'a, FileChange>,
}
impl<'a> Iterator for Files<'a> {
type Item = &'a FileChange;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl DoubleEndedIterator for Files<'_> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back()
}
}
impl ExactSizeIterator for Files<'_> {
#[inline]
fn len(&self) -> usize {
self.iter.len()
}
}
#[derive(Debug, Clone)]
pub struct DirtyFiles<'a> {
iter: slice::Iter<'a, FileChange>,
len: usize,
}
impl<'a> Iterator for DirtyFiles<'a> {
type Item = &'a FileChange;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let file = self.iter.find(|file| file.is_dirty())?;
self.len -= 1;
Some(file)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl DoubleEndedIterator for DirtyFiles<'_> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
let file = self.iter.rfind(|file| file.is_dirty())?;
self.len -= 1;
Some(file)
}
}
impl ExactSizeIterator for DirtyFiles<'_> {
#[inline]
fn len(&self) -> usize {
self.len
}
}
#[derive(Debug, Clone)]
pub struct StagedFiles<'a> {
iter: slice::Iter<'a, FileChange>,
len: usize,
}
impl<'a> Iterator for StagedFiles<'a> {
type Item = &'a FileChange;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let file = self.iter.find(|file| file.is_staged())?;
self.len -= 1;
Some(file)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl DoubleEndedIterator for StagedFiles<'_> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
let file = self.iter.rfind(|file| file.is_staged())?;
self.len -= 1;
Some(file)
}
}
impl ExactSizeIterator for StagedFiles<'_> {
#[inline]
fn len(&self) -> usize {
self.len
}
}