use std::fs::File;
use std::io::{self, Read};
use std::sync::atomic::{AtomicBool, Ordering};
use super::ImportedRecord;
use crate::refs::store::{check_path, io_error};
use crate::refs::{Backend, RefName, ReferenceError, References};
use crate::{ObjectFormat, ObjectId};
#[derive(Clone, Copy, Debug)]
pub struct ReflogLimits {
pub bytes: usize,
pub record_bytes: usize,
pub retained_bytes: usize,
pub records: usize,
}
impl Default for ReflogLimits {
fn default() -> Self {
Self {
bytes: 64 * 1024 * 1024,
record_bytes: 2 * 1024 * 1024,
retained_bytes: 64 * 1024 * 1024,
records: 100_000,
}
}
}
#[derive(Debug)]
pub enum ReflogReadEnd {
Eof,
Limit(&'static str),
Cancelled,
Io(io::Error),
}
#[derive(Debug)]
pub struct ImportedReflog {
records: Vec<ImportedRecord>,
end: ReflogReadEnd,
}
impl ImportedReflog {
pub fn read(
format: ObjectFormat,
mut reader: impl Read,
limits: ReflogLimits,
cancel: &AtomicBool,
) -> Self {
let mut result = Self {
records: Vec::new(),
end: ReflogReadEnd::Eof,
};
let mut line = Vec::new();
let mut retained = 0;
let mut consumed = 0;
let mut buffer = [0u8; 8192];
'read: loop {
if cancel.load(Ordering::Relaxed) {
result.end = ReflogReadEnd::Cancelled;
break;
}
let allowance = limits
.bytes
.saturating_sub(consumed)
.min(limits.retained_bytes.saturating_sub(retained));
if allowance == 0 {
result.end = ReflogReadEnd::Limit("bytes or retained bytes");
break;
}
let count = match reader.read(&mut buffer[..allowance.min(8192)]) {
Ok(0) => break,
Ok(count) => count,
Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
Err(error) => {
result.end = ReflogReadEnd::Io(error);
break;
}
};
consumed += count;
for &byte in &buffer[..count] {
if cancel.load(Ordering::Relaxed) {
result.end = ReflogReadEnd::Cancelled;
break 'read;
}
if result.records.len() == limits.records {
result.end = ReflogReadEnd::Limit("records");
break 'read;
}
if line.len() == limits.record_bytes {
result.end = ReflogReadEnd::Limit("record bytes");
break 'read;
}
line.push(byte);
retained += 1;
if byte == b'\n' {
result.records.push(ImportedRecord::File {
format,
bytes: std::mem::take(&mut line),
});
}
}
}
if !line.is_empty() {
result.records.push(ImportedRecord::File {
format,
bytes: line,
});
}
result
}
pub fn records(&self) -> &[ImportedRecord] {
&self.records
}
pub fn end(&self) -> &ReflogReadEnd {
&self.end
}
pub fn is_complete(&self) -> bool {
matches!(self.end, ReflogReadEnd::Eof) && self.records.iter().all(|r| r.fields().is_ok())
}
pub fn recoverable_roots(&self) -> impl Iterator<Item = ObjectId> + '_ {
self.records
.iter()
.flat_map(ImportedRecord::recoverable_roots)
}
pub(crate) fn binary(
records: impl Iterator<Item = crate::refs::reftable::LogRecord>,
limits: ReflogLimits,
cancel: &AtomicBool,
) -> Self {
let mut result = Self {
records: Vec::new(),
end: ReflogReadEnd::Eof,
};
let mut retained = 0usize;
for record in records {
if cancel.load(Ordering::Relaxed) {
result.end = ReflogReadEnd::Cancelled;
break;
}
let Some(value) = record.value else {
continue;
};
let size = value
.name
.len()
.saturating_add(value.email.len())
.saturating_add(value.message.len())
.saturating_add(2 * value.old.format().digest_len() + 18);
if result.records.len() == limits.records {
result.end = ReflogReadEnd::Limit("records");
break;
}
if size > limits.record_bytes {
result.end = ReflogReadEnd::Limit("record bytes");
break;
}
if size > limits.bytes.saturating_sub(retained)
|| size > limits.retained_bytes.saturating_sub(retained)
{
result.end = ReflogReadEnd::Limit("bytes or retained bytes");
break;
}
retained += size;
result.records.push(ImportedRecord::Reftable {
update_index: record.update_index,
value,
});
}
result
}
}
impl References<'_> {
pub fn imported_reflog(
&self,
name: &RefName,
limits: ReflogLimits,
cancel: &AtomicBool,
) -> Result<Option<ImportedReflog>, ReferenceError> {
if self.reference_backend == Backend::Reftable {
return crate::refs::reftable::backend::imported_reflog(self, name, limits, cancel);
}
let path = self.reflog_path(name)?;
check_path(&path)?;
let file = match File::open(&path) {
Ok(file) => file,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(error) => return Err(io_error(&path, error)),
};
Ok(Some(ImportedReflog::read(
self.object_format,
file,
limits,
cancel,
)))
}
}