use std::collections::HashMap;
use std::ops::ControlFlow;
use std::path::{Path, PathBuf};
use super::diff_err;
use crate::vcs::error::Error;
use crate::vcs::options::FileTypeScope;
pub(crate) struct OpenRepo {
pub repo: gix::Repository,
pub workdir: Option<PathBuf>,
pub shallow: bool,
}
pub(crate) fn open(root: &Path) -> Result<OpenRepo, Error> {
let repo = gix::discover(root).map_err(|e| map_discover_error(root, &e))?;
let shallow = repo.is_shallow();
let workdir = repo
.workdir()
.map(|w| w.canonicalize().unwrap_or_else(|_| w.to_path_buf()));
Ok(OpenRepo {
repo,
workdir,
shallow,
})
}
pub(crate) fn workdir_root(path: &Path) -> Option<PathBuf> {
let directory = if path.is_dir() {
path
} else {
match path.parent() {
Some(parent) if !parent.as_os_str().is_empty() => parent,
_ => Path::new("."),
}
};
open(directory).ok().and_then(|open| open.workdir)
}
pub(crate) fn resolve_commit<'repo>(
repo: &'repo gix::Repository,
reference: &str,
) -> Result<gix::Commit<'repo>, Error> {
let resolve_err = |e: &dyn std::fmt::Display| Error::ResolveRef {
reference: reference.to_owned(),
reason: e.to_string(),
};
let tip = repo
.rev_parse_single(reference.as_bytes())
.map_err(|e| resolve_err(&e))?;
tip.object()
.map_err(|e| resolve_err(&e))?
.peel_to_commit()
.map_err(|e| resolve_err(&e))
}
fn map_discover_error(root: &Path, error: &gix::discover::Error) -> Error {
use gix::discover::upwards::Error as Upwards;
if let gix::discover::Error::Discover(
Upwards::NoGitRepository { .. }
| Upwards::NoGitRepositoryWithinCeiling { .. }
| Upwards::NoGitRepositoryWithinFs { .. }
| Upwards::NoTrustedGitRepository { .. },
) = error
{
return Error::NotARepository(root.to_path_buf());
}
Error::OpenRepository(error.to_string())
}
pub(crate) fn enumerate_target_files(
repo: &gix::Repository,
target_tree: &gix::Tree<'_>,
scope: &FileTypeScope,
) -> Result<HashMap<PathBuf, u64>, Error> {
let empty = repo.empty_tree();
let mut cache = repo.diff_resource_cache_for_tree_diff().map_err(diff_err)?;
let mut files = HashMap::new();
empty
.changes()
.map_err(diff_err)?
.options(|opts| {
opts.track_path();
opts.track_rewrites(None);
})
.for_each_to_obtain_tree(target_tree, |change| -> Result<ControlFlow<()>, Error> {
let entry_mode = change.entry_mode();
if entry_mode.is_blob() && !entry_mode.is_link() {
let path = bstr_to_path(change.location())?;
if !scope.includes(&path) {
return Ok(ControlFlow::Continue(()));
}
if let Some(stats) = change
.diff(&mut cache)
.map_err(diff_err)?
.line_counts()
.map_err(diff_err)?
{
files.insert(path, stats.after as u64);
}
}
Ok(ControlFlow::Continue(()))
})
.map_err(diff_err)?;
Ok(files)
}
pub(crate) fn bstr_to_path(location: &bstr::BStr) -> Result<PathBuf, Error> {
gix::path::try_from_bstr(location)
.map(std::borrow::Cow::into_owned)
.map_err(|_| Error::Diff(format!("path {location:?} is not valid UTF-8")))
}