cleanup: remove useless Git struct, using free functions instead

pull/9875/merge
Alexis (Poliorcetics) Bourget 7 months ago committed by Michael Davis
parent 918dd3fa37
commit f1461b49fa

@ -17,15 +17,64 @@ use gix::status::{
}; };
use gix::{Commit, ObjectId, Repository, ThreadSafeRepository}; use gix::{Commit, ObjectId, Repository, ThreadSafeRepository};
use crate::{DiffProvider, FileChange}; use crate::FileChange;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
#[derive(Clone, Copy)] pub fn get_diff_base(file: &Path) -> Result<Vec<u8>> {
pub struct Git; debug_assert!(!file.exists() || file.is_file());
debug_assert!(file.is_absolute());
// TODO cache repository lookup
let repo_dir = file.parent().context("file has no parent directory")?;
let repo = open_repo(repo_dir, None)
.context("failed to open git repo")?
.to_thread_local();
let head = repo.head_commit()?;
let file_oid = find_file_in_commit(&repo, &head, file)?;
let file_object = repo.find_object(file_oid)?;
let data = file_object.detach().data;
// Get the actual data that git would make out of the git object.
// This will apply the user's git config or attributes like crlf conversions.
if let Some(work_dir) = repo.work_dir() {
let rela_path = file.strip_prefix(work_dir)?;
let rela_path = gix::path::try_into_bstr(rela_path)?;
let (mut pipeline, _) = repo.filter_pipeline(None)?;
let mut worktree_outcome =
pipeline.convert_to_worktree(&data, rela_path.as_ref(), Delay::Forbid)?;
let mut buf = Vec::with_capacity(data.len());
worktree_outcome.read_to_end(&mut buf)?;
Ok(buf)
} else {
Ok(data)
}
}
pub fn get_current_head_name(file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
debug_assert!(!file.exists() || file.is_file());
debug_assert!(file.is_absolute());
let repo_dir = file.parent().context("file has no parent directory")?;
let repo = open_repo(repo_dir, None)
.context("failed to open git repo")?
.to_thread_local();
let head_ref = repo.head_ref()?;
let head_commit = repo.head_commit()?;
let name = match head_ref {
Some(reference) => reference.name().shorten().to_string(),
None => head_commit.id.to_hex_with_len(8).to_string(),
};
Ok(Arc::new(ArcSwap::from_pointee(name.into_boxed_str())))
}
pub fn for_each_changed_file(cwd: &Path, f: impl Fn(Result<FileChange>) -> bool) -> Result<()> {
status(&open_repo(cwd, None)?.to_thread_local(), f)
}
impl Git {
fn open_repo(path: &Path, ceiling_dir: Option<&Path>) -> Result<ThreadSafeRepository> { fn open_repo(path: &Path, ceiling_dir: Option<&Path>) -> Result<ThreadSafeRepository> {
// custom open options // custom open options
let mut git_open_opts_map = gix::sec::trust::Mapping::<gix::open::Options>::default(); let mut git_open_opts_map = gix::sec::trust::Mapping::<gix::open::Options>::default();
@ -137,72 +186,6 @@ impl Git {
Ok(()) Ok(())
} }
}
impl Git {
pub fn get_diff_base(&self, file: &Path) -> Result<Vec<u8>> {
debug_assert!(!file.exists() || file.is_file());
debug_assert!(file.is_absolute());
// TODO cache repository lookup
let repo_dir = file.parent().context("file has no parent directory")?;
let repo = Git::open_repo(repo_dir, None)
.context("failed to open git repo")?
.to_thread_local();
let head = repo.head_commit()?;
let file_oid = find_file_in_commit(&repo, &head, file)?;
let file_object = repo.find_object(file_oid)?;
let data = file_object.detach().data;
// Get the actual data that git would make out of the git object.
// This will apply the user's git config or attributes like crlf conversions.
if let Some(work_dir) = repo.work_dir() {
let rela_path = file.strip_prefix(work_dir)?;
let rela_path = gix::path::try_into_bstr(rela_path)?;
let (mut pipeline, _) = repo.filter_pipeline(None)?;
let mut worktree_outcome =
pipeline.convert_to_worktree(&data, rela_path.as_ref(), Delay::Forbid)?;
let mut buf = Vec::with_capacity(data.len());
worktree_outcome.read_to_end(&mut buf)?;
Ok(buf)
} else {
Ok(data)
}
}
pub fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
debug_assert!(!file.exists() || file.is_file());
debug_assert!(file.is_absolute());
let repo_dir = file.parent().context("file has no parent directory")?;
let repo = Git::open_repo(repo_dir, None)
.context("failed to open git repo")?
.to_thread_local();
let head_ref = repo.head_ref()?;
let head_commit = repo.head_commit()?;
let name = match head_ref {
Some(reference) => reference.name().shorten().to_string(),
None => head_commit.id.to_hex_with_len(8).to_string(),
};
Ok(Arc::new(ArcSwap::from_pointee(name.into_boxed_str())))
}
pub fn for_each_changed_file(
&self,
cwd: &Path,
f: impl Fn(Result<FileChange>) -> bool,
) -> Result<()> {
Self::status(&Self::open_repo(cwd, None)?.to_thread_local(), f)
}
}
impl From<Git> for DiffProvider {
fn from(value: Git) -> Self {
DiffProvider::Git(value)
}
}
/// Finds the object that contains the contents of a file at a specific commit. /// Finds the object that contains the contents of a file at a specific commit.
fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Result<ObjectId> { fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Result<ObjectId> {

@ -2,7 +2,7 @@ use std::{fs::File, io::Write, path::Path, process::Command};
use tempfile::TempDir; use tempfile::TempDir;
use crate::git::Git; use crate::git;
fn exec_git_cmd(args: &str, git_dir: &Path) { fn exec_git_cmd(args: &str, git_dir: &Path) {
let res = Command::new("git") let res = Command::new("git")
@ -54,7 +54,7 @@ fn missing_file() {
let file = temp_git.path().join("file.txt"); let file = temp_git.path().join("file.txt");
File::create(&file).unwrap().write_all(b"foo").unwrap(); File::create(&file).unwrap().write_all(b"foo").unwrap();
assert!(Git.get_diff_base(&file).is_err()); assert!(git::get_diff_base(&file).is_err());
} }
#[test] #[test]
@ -64,7 +64,7 @@ fn unmodified_file() {
let contents = b"foo".as_slice(); let contents = b"foo".as_slice();
File::create(&file).unwrap().write_all(contents).unwrap(); File::create(&file).unwrap().write_all(contents).unwrap();
create_commit(temp_git.path(), true); create_commit(temp_git.path(), true);
assert_eq!(Git.get_diff_base(&file).unwrap(), Vec::from(contents)); assert_eq!(git::get_diff_base(&file).unwrap(), Vec::from(contents));
} }
#[test] #[test]
@ -76,7 +76,7 @@ fn modified_file() {
create_commit(temp_git.path(), true); create_commit(temp_git.path(), true);
File::create(&file).unwrap().write_all(b"bar").unwrap(); File::create(&file).unwrap().write_all(b"bar").unwrap();
assert_eq!(Git.get_diff_base(&file).unwrap(), Vec::from(contents)); assert_eq!(git::get_diff_base(&file).unwrap(), Vec::from(contents));
} }
/// Test that `get_file_head` does not return content for a directory. /// Test that `get_file_head` does not return content for a directory.
@ -95,7 +95,7 @@ fn directory() {
std::fs::remove_dir_all(&dir).unwrap(); std::fs::remove_dir_all(&dir).unwrap();
File::create(&dir).unwrap().write_all(b"bar").unwrap(); File::create(&dir).unwrap().write_all(b"bar").unwrap();
assert!(Git.get_diff_base(&dir).is_err()); assert!(git::get_diff_base(&dir).is_err());
} }
/// Test that `get_file_head` does not return content for a symlink. /// Test that `get_file_head` does not return content for a symlink.
@ -116,6 +116,6 @@ fn symlink() {
symlink("file.txt", &file_link).unwrap(); symlink("file.txt", &file_link).unwrap();
create_commit(temp_git.path(), true); create_commit(temp_git.path(), true);
assert!(Git.get_diff_base(&file_link).is_err()); assert!(git::get_diff_base(&file_link).is_err());
assert_eq!(Git.get_diff_base(&file).unwrap(), Vec::from(contents)); assert_eq!(git::get_diff_base(&file).unwrap(), Vec::from(contents));
} }

@ -1,4 +1,4 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, bail, Result};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -74,7 +74,7 @@ impl Default for DiffProviderRegistry {
// TODO make this configurable when more providers are added // TODO make this configurable when more providers are added
let providers = vec![ let providers = vec![
#[cfg(feature = "git")] #[cfg(feature = "git")]
git::Git.into(), DiffProvider::Git,
]; ];
DiffProviderRegistry { providers } DiffProviderRegistry { providers }
} }
@ -82,24 +82,29 @@ impl Default for DiffProviderRegistry {
/// A union type that includes all types that implement [DiffProvider]. We need this type to allow /// A union type that includes all types that implement [DiffProvider]. We need this type to allow
/// cloning [DiffProviderRegistry] as `Clone` cannot be used in trait objects. /// cloning [DiffProviderRegistry] as `Clone` cannot be used in trait objects.
#[derive(Clone)] ///
/// `Copy` is simply to ensure the `clone()` call is the simplest it can be.
#[derive(Copy, Clone)]
pub enum DiffProvider { pub enum DiffProvider {
#[cfg(feature = "git")] #[cfg(feature = "git")]
Git(git::Git), Git,
None,
} }
impl DiffProvider { impl DiffProvider {
fn get_diff_base(&self, file: &Path) -> Result<Vec<u8>> { fn get_diff_base(&self, file: &Path) -> Result<Vec<u8>> {
match self { match self {
#[cfg(feature = "git")] #[cfg(feature = "git")]
Self::Git(inner) => inner.get_diff_base(file), Self::Git => git::get_diff_base(file),
Self::None => bail!("No diff support compiled in"),
} }
} }
fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> { fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
match self { match self {
#[cfg(feature = "git")] #[cfg(feature = "git")]
Self::Git(inner) => inner.get_current_head_name(file), Self::Git => git::get_current_head_name(file),
Self::None => bail!("No diff support compiled in"),
} }
} }
@ -110,7 +115,8 @@ impl DiffProvider {
) -> Result<()> { ) -> Result<()> {
match self { match self {
#[cfg(feature = "git")] #[cfg(feature = "git")]
Self::Git(inner) => inner.for_each_changed_file(cwd, f), Self::Git => git::for_each_changed_file(cwd, f),
Self::None => bail!("No diff support compiled in"),
} }
} }
} }

Loading…
Cancel
Save