use std::{io::Read, sync::atomic::AtomicBool};
use bstr::BStr;
use gix_hash::ObjectId;
use gix_index as index;
use index::Entry;
use crate::index_as_worktree::Error;
pub trait CompareBlobs {
type Output;
fn compare_blobs<'a, 'b>(
&mut self,
entry: &gix_index::Entry,
worktree_blob_size: u64,
data: impl ReadData<'a>,
buf: &mut Vec<u8>,
) -> Result<Option<Self::Output>, Error>;
}
pub trait SubmoduleStatus {
type Output;
type Error: std::error::Error + Send + Sync + 'static;
fn status(&mut self, entry: &gix_index::Entry, rela_path: &BStr) -> Result<Option<Self::Output>, Self::Error>;
}
pub trait ReadData<'a> {
fn read_blob(self) -> Result<&'a [u8], Error>;
fn stream_worktree_file(self) -> Result<read_data::Stream<'a>, Error>;
}
pub mod read_data {
use std::sync::atomic::Ordering;
use gix_filter::pipeline::convert::ToGitOutcome;
use crate::AtomicU64;
pub struct Stream<'a> {
pub(crate) inner: ToGitOutcome<'a, std::fs::File>,
pub(crate) bytes: Option<&'a AtomicU64>,
pub(crate) len: Option<u64>,
}
impl<'a> Stream<'a> {
pub fn as_bytes(&self) -> Option<&'a [u8]> {
self.inner.as_bytes().inspect(|v| {
if let Some(bytes) = self.bytes {
bytes.fetch_add(v.len() as u64, Ordering::Relaxed);
}
})
}
pub fn size(&self) -> Option<u64> {
self.len
}
}
impl std::io::Read for Stream<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.inner.read(buf)?;
if let Some(bytes) = self.bytes {
bytes.fetch_add(n as u64, Ordering::Relaxed);
}
Ok(n)
}
}
}
#[derive(Clone)]
pub struct FastEq;
impl CompareBlobs for FastEq {
type Output = ();
fn compare_blobs<'a, 'b>(
&mut self,
entry: &Entry,
worktree_file_size: u64,
data: impl ReadData<'a>,
buf: &mut Vec<u8>,
) -> Result<Option<Self::Output>, Error> {
if u64::from(entry.stat.size) != worktree_file_size && (entry.id.is_empty_blob() || entry.stat.size != 0) {
return Ok(Some(()));
}
HashEq
.compare_blobs(entry, worktree_file_size, data, buf)
.map(|opt| opt.map(|_| ()))
}
}
#[derive(Clone)]
pub struct HashEq;
impl CompareBlobs for HashEq {
type Output = ObjectId;
fn compare_blobs<'a, 'b>(
&mut self,
entry: &Entry,
_worktree_blob_size: u64,
data: impl ReadData<'a>,
buf: &mut Vec<u8>,
) -> Result<Option<Self::Output>, Error> {
let mut stream = data.stream_worktree_file()?;
match stream.as_bytes() {
Some(buffer) => {
let file_hash = gix_object::compute_hash(entry.id.kind(), gix_object::Kind::Blob, buffer)
.map_err(gix_hash::io::Error::from)?;
Ok((entry.id != file_hash).then_some(file_hash))
}
None => {
let file_hash = match stream.size() {
None => {
stream.read_to_end(buf).map_err(gix_hash::io::Error::from)?;
gix_object::compute_hash(entry.id.kind(), gix_object::Kind::Blob, buf)
.map_err(gix_hash::io::Error::from)?
}
Some(len) => gix_object::compute_stream_hash(
entry.id.kind(),
gix_object::Kind::Blob,
&mut stream,
len,
&mut gix_features::progress::Discard,
&AtomicBool::default(),
)?,
};
Ok((entry.id != file_hash).then_some(file_hash))
}
}
}
}