use std::{
fs,
io::{self, Read},
path::Path,
};
use sha1::{Digest, Sha1};
use crate::{
hash::{ObjectHash, get_hash_kind},
internal::object::types::ObjectType,
};
pub fn is_eof(reader: &mut dyn Read) -> bool {
let mut buf = [0; 1];
matches!(reader.read(&mut buf), Ok(0))
}
pub fn read_byte_and_check_continuation<R: Read>(stream: &mut R) -> io::Result<(u8, bool)> {
let mut bytes = [0; 1];
stream.read_exact(&mut bytes)?;
let byte = bytes[0];
let value = byte & 0b0111_1111;
let msb = byte >= 128;
Ok((value, msb))
}
pub fn read_type_and_varint_size<R: Read>(
stream: &mut R,
offset: &mut usize,
) -> io::Result<(u8, usize)> {
let (first_byte, continuation) = read_byte_and_check_continuation(stream)?;
*offset += 1;
let type_bits = (first_byte & 0b0111_0000) >> 4;
let mut size: u64 = (first_byte & 0b0000_1111) as u64;
let mut shift = 4;
let mut more_bytes = continuation;
while more_bytes {
let (next_byte, continuation) = read_byte_and_check_continuation(stream)?;
*offset += 1;
size |= (next_byte as u64) << shift;
shift += 7; more_bytes = continuation;
}
Ok((type_bits, size as usize))
}
pub fn read_varint_le<R: Read>(reader: &mut R) -> io::Result<(u64, usize)> {
let mut value: u64 = 0;
let mut shift = 0;
let mut offset = 0;
loop {
let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
let byte = buf[0];
if shift > 63 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"VarInt too long",
));
}
let byte_value = (byte & 0x7F) as u64;
value |= byte_value << shift;
offset += 1;
if byte & 0x80 == 0 {
break;
}
shift += 7;
}
Ok((value, offset))
}
pub fn read_offset_encoding<R: Read>(stream: &mut R) -> io::Result<(u64, usize)> {
let mut value = 0;
let mut offset = 0;
loop {
let (byte_value, more_bytes) = read_byte_and_check_continuation(stream)?;
offset += 1;
value = (value << 7) | byte_value as u64;
if !more_bytes {
return Ok((value, offset));
}
value += 1; }
}
#[inline]
pub fn read_bytes<R: Read, const N: usize>(stream: &mut R) -> io::Result<[u8; N]> {
let mut bytes = [0; N];
stream.read_exact(&mut bytes)?;
Ok(bytes)
}
pub fn read_partial_int<R: Read>(
stream: &mut R,
bytes: u8,
present_bytes: &mut u8,
) -> io::Result<usize> {
let mut value: usize = 0;
for byte_index in 0..bytes {
if *present_bytes & 1 != 0 {
let [byte] = read_bytes(stream)?;
value |= (byte as usize) << (byte_index * 8);
}
*present_bytes >>= 1;
}
Ok(value)
}
pub fn read_delta_object_size<R: Read>(stream: &mut R) -> io::Result<(usize, usize)> {
let base_size = read_varint_le(stream)?.0 as usize;
let result_size = read_varint_le(stream)?.0 as usize;
Ok((base_size, result_size))
}
fn decimal_usize_bytes(mut value: usize, buf: &mut [u8; 20]) -> &[u8] {
if value == 0 {
return b"0";
}
let mut cursor = buf.len();
while value > 0 {
cursor -= 1;
buf[cursor] = b'0' + (value % 10) as u8;
value /= 10;
}
&buf[cursor..]
}
pub fn calculate_object_hash(obj_type: ObjectType, data: &[u8]) -> ObjectHash {
let type_bytes = obj_type
.to_bytes()
.expect("calculate_object_hash called with a delta type that has no loose-object header");
let mut len_buf = [0; 20];
let len_bytes = decimal_usize_bytes(data.len(), &mut len_buf);
match get_hash_kind() {
crate::hash::HashKind::Sha1 => {
let mut hash = Sha1::new();
hash.update(type_bytes);
hash.update(b" ");
hash.update(len_bytes);
hash.update(b"\0");
hash.update(data);
let re: [u8; 20] = hash.finalize().into();
ObjectHash::Sha1(re)
}
crate::hash::HashKind::Sha256 => {
let mut hash = sha2::Sha256::new();
hash.update(type_bytes);
hash.update(b" ");
hash.update(len_bytes);
hash.update(b"\0");
hash.update(data);
let re: [u8; 32] = hash.finalize().into();
ObjectHash::Sha256(re)
}
}
}
pub fn create_empty_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
let dir = path.as_ref();
if dir.exists() {
fs::remove_dir_all(dir)?;
}
fs::create_dir_all(dir)?;
Ok(())
}
pub fn count_dir_files(path: &Path) -> io::Result<usize> {
let mut count = 0;
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
count += count_dir_files(&path)?;
} else {
count += 1;
}
}
Ok(count)
}
#[macro_export]
macro_rules! time_it {
($msg:expr, $block:block) => {{
let start = std::time::Instant::now();
let result = $block;
let elapsed = start.elapsed();
tracing::info!("{}: {:?}", $msg, elapsed);
result
}};
}
#[cfg(test)]
mod tests {
use std::{
io,
io::{Cursor, Read},
};
use crate::{
hash::{HashKind, set_hash_kind_for_test},
internal::{object::types::ObjectType, pack::utils::*},
};
#[test]
fn test_calc_obj_hash() {
let _guard = set_hash_kind_for_test(HashKind::Sha1);
let hash = calculate_object_hash(ObjectType::Blob, b"a".as_ref());
assert_eq!(hash.to_string(), "2e65efe2a145dda7ee51d1741299f848e5bf752e");
}
#[test]
fn test_calc_obj_hash_sha256() {
let _guard = set_hash_kind_for_test(HashKind::Sha256);
let hash = calculate_object_hash(ObjectType::Blob, b"a".as_ref());
assert_eq!(
hash.to_string(),
"eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7"
);
}
#[test]
fn test_calc_empty_obj_hash() {
let _guard = set_hash_kind_for_test(HashKind::Sha1);
let hash = calculate_object_hash(ObjectType::Blob, b"".as_ref());
assert_eq!(hash.to_string(), "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391");
}
#[test]
fn eof() {
let mut reader = Cursor::new(&b""[..]);
assert!(is_eof(&mut reader));
}
#[test]
fn not_eof() {
let mut reader = Cursor::new(&b"abc"[..]);
assert!(!is_eof(&mut reader));
}
#[test]
fn eof_midway() {
let mut reader = Cursor::new(&b"abc"[..]);
reader.read_exact(&mut [0; 2]).unwrap();
assert!(!is_eof(&mut reader));
}
#[test]
fn reader_error() {
struct BrokenReader;
impl Read for BrokenReader {
fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
Err(io::Error::other("error"))
}
}
let mut reader = BrokenReader;
assert!(!is_eof(&mut reader));
}
#[test]
fn test_read_byte_and_check_continuation_no_continuation() {
let data = [0b0101_0101]; let mut cursor = Cursor::new(data);
let (value, more_bytes) = read_byte_and_check_continuation(&mut cursor).unwrap();
assert_eq!(value, 85); assert!(!more_bytes); }
#[test]
fn test_read_byte_and_check_continuation_with_continuation() {
let data = [0b1010_1010]; let mut cursor = Cursor::new(data);
let (value, more_bytes) = read_byte_and_check_continuation(&mut cursor).unwrap();
assert_eq!(value, 42); assert!(more_bytes); }
#[test]
fn test_read_byte_and_check_continuation_edge_cases() {
let data = [0b0000_0000];
let mut cursor = Cursor::new(data);
let (value, more_bytes) = read_byte_and_check_continuation(&mut cursor).unwrap();
assert_eq!(value, 0); assert!(!more_bytes);
let data = [0b1111_1111];
let mut cursor = Cursor::new(data);
let (value, more_bytes) = read_byte_and_check_continuation(&mut cursor).unwrap();
assert_eq!(value, 127); assert!(more_bytes); }
#[test]
fn test_single_byte_no_continuation() {
let data = [0b0101_0101]; let mut offset: usize = 0;
let mut cursor = Cursor::new(data);
let (type_bits, size) = read_type_and_varint_size(&mut cursor, &mut offset).unwrap();
assert_eq!(offset, 1); assert_eq!(type_bits, 5); assert_eq!(size, 5); }
#[test]
fn test_multiple_bytes_with_continuation() {
let data = [0b1101_0101, 0b0000_0011]; let mut offset: usize = 0;
let mut cursor = Cursor::new(data);
let (type_bits, size) = read_type_and_varint_size(&mut cursor, &mut offset).unwrap();
assert_eq!(offset, 2); assert_eq!(type_bits, 5); assert_eq!(size, 53);
}
#[test]
fn test_edge_case_size_spread_across_bytes() {
let data = [0b0001_1111, 0b0000_0010]; let mut offset: usize = 0;
let mut cursor = Cursor::new(data);
let (type_bits, size) = read_type_and_varint_size(&mut cursor, &mut offset).unwrap();
assert_eq!(offset, 1); assert_eq!(type_bits, 1); assert_eq!(size, 15);
}
#[test]
fn test_read_varint_le_single_byte() {
let data = vec![0x05];
let mut cursor = Cursor::new(data);
let (value, offset) = read_varint_le(&mut cursor).unwrap();
assert_eq!(value, 5);
assert_eq!(offset, 1);
}
#[test]
fn test_read_varint_le_multiple_bytes() {
let data = vec![0x85, 0x01];
let mut cursor = Cursor::new(data);
let (value, offset) = read_varint_le(&mut cursor).unwrap();
assert_eq!(value, 133);
assert_eq!(offset, 2);
}
#[test]
fn test_read_varint_le_large_number() {
let data = vec![0xFF, 0xFF, 0xFF, 0xFF, 0xF];
let mut cursor = Cursor::new(data);
let (value, offset) = read_varint_le(&mut cursor).unwrap();
assert_eq!(value, 0xFFFFFFFF);
assert_eq!(offset, 5);
}
#[test]
fn test_read_varint_le_zero() {
let data = vec![0x00];
let mut cursor = Cursor::new(data);
let (value, offset) = read_varint_le(&mut cursor).unwrap();
assert_eq!(value, 0);
assert_eq!(offset, 1);
}
#[test]
fn test_read_varint_le_too_long() {
let data = vec![
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01,
];
let mut cursor = Cursor::new(data);
let result = read_varint_le(&mut cursor);
assert!(result.is_err());
}
#[test]
fn test_read_offset_encoding() {
let data: Vec<u8> = vec![0b_1101_0101, 0b_0000_0101];
let mut cursor = Cursor::new(data);
let result = read_offset_encoding(&mut cursor);
assert!(result.is_ok());
assert_eq!(result.unwrap(), (11013, 2));
}
}