use gix_error::{ErrorExt, ExnMessageResult, ExnResult, Message, ResultExt};
use gix_object::bstr::ByteSlice;
use crate::{FullNameRef, file, store_impl::file::log};
pub fn forward(lines: &[u8]) -> Forward<'_> {
Forward {
inner: lines.as_bstr().lines().enumerate(),
}
}
pub struct Forward<'a> {
inner: std::iter::Enumerate<gix_object::bstr::Lines<'a>>,
}
impl<'a> Iterator for Forward<'a> {
type Item = ExnMessageResult<log::LineRef<'a>>;
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(|(ln, line)| log::LineRef::from_bytes(line).or_raise(|| invalid_reflog_entry(ln + 1, false)))
}
}
#[must_use = "Iterators should be obtained from this platform"]
pub struct Platform<'a, 's> {
pub store: &'s file::Store,
pub name: &'a FullNameRef,
pub buf: Vec<u8>,
}
impl Platform<'_, '_> {
pub fn rev(&mut self) -> std::io::Result<Option<log::iter::Reverse<'_, std::fs::File>>> {
self.buf.clear();
self.buf.resize(1024 * 4, 0);
self.store.reflog_iter_rev_inner(self.name, &mut self.buf)
}
pub fn all(&mut self) -> std::io::Result<Option<log::iter::Forward<'_>>> {
self.buf.clear();
self.store.reflog_iter_inner(self.name, &mut self.buf)
}
}
pub struct Reverse<'a, F> {
buf: &'a mut [u8],
count: usize,
read_and_pos: Option<(F, u64)>,
last_nl_pos: Option<usize>,
}
pub fn reverse<F>(mut log: F, buf: &mut [u8]) -> std::io::Result<Reverse<'_, F>>
where
F: std::io::Read + std::io::Seek,
{
let pos = log.seek(std::io::SeekFrom::End(0))?;
if buf.is_empty() {
return Err(std::io::Error::other(
"Zero sized buffers are not allowed, use 256 bytes or more for typical logs",
));
}
Ok(Reverse {
buf,
count: 0,
read_and_pos: Some((log, pos)),
last_nl_pos: None,
})
}
impl<F> Iterator for Reverse<'_, F>
where
F: std::io::Read + std::io::Seek,
{
type Item = ExnResult<crate::log::Line>;
fn next(&mut self) -> Option<Self::Item> {
match (self.last_nl_pos.take(), self.read_and_pos.take()) {
(None, Some((mut read, pos))) => {
let npos = pos.saturating_sub(self.buf.len() as u64);
if let Err(err) = read.seek(std::io::SeekFrom::Start(npos)) {
return Some(Err(err.raise_erased()));
}
let n = (pos - npos) as usize;
if n == 0 {
return None;
}
let buf = &mut self.buf[..n];
if let Err(err) = read.read_exact(buf) {
return Some(Err(err.raise_erased()));
}
let last_byte = *buf.last().expect("we have read non-zero bytes before");
self.last_nl_pos = Some(if last_byte != b'\n' { buf.len() } else { buf.len() - 1 });
self.read_and_pos = Some((read, npos));
self.next()
}
(Some(end), Some(read_and_pos)) => match self.buf[..end].rfind_byte(b'\n') {
Some(start) => {
self.read_and_pos = Some(read_and_pos);
self.last_nl_pos = Some(start);
let buf = &self.buf[start + 1..end];
let res = Some(
log::LineRef::from_bytes(buf)
.or_raise_erased(|| invalid_reflog_entry(self.count + 1, true))
.map(Into::into),
);
self.count += 1;
res
}
None => {
let (mut read, last_read_pos) = read_and_pos;
if last_read_pos == 0 {
let buf = &self.buf[..end];
Some(
log::LineRef::from_bytes(buf)
.or_raise_erased(|| invalid_reflog_entry(self.count + 1, true))
.map(Into::into),
)
} else {
let npos = last_read_pos.saturating_sub((self.buf.len() - end) as u64);
if npos == last_read_pos {
return Some(Err(std::io::Error::other(format!(
"buffer too small for line size, got until {:?}",
self.buf.as_bstr()
))
.raise_erased()));
}
let n = (last_read_pos - npos) as usize;
self.buf.copy_within(0..end, n);
if let Err(err) = read.seek(std::io::SeekFrom::Start(npos)) {
return Some(Err(err.raise_erased()));
}
if let Err(err) = read.read_exact(&mut self.buf[..n]) {
return Some(Err(err.raise_erased()));
}
self.read_and_pos = Some((read, npos));
self.last_nl_pos = Some(n + end);
self.next()
}
}
},
(None, None) => None,
(Some(_), None) => unreachable!("BUG: Invalid state: we never discard only our file, always both."),
}
}
}
fn invalid_reflog_entry(line: usize, from_end: bool) -> Message {
Message::new("Invalid reflog entry")
.with("line", line)
.with("from_end", from_end)
}