Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 28 additions & 28 deletions libvctrl_plumbing/src/cat_file.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
use alloc::sync::Arc;
use core::fmt::Write as _;

use libvctrl::{Decoder, EntryKind, Hash, ObjectStore, VctrlError};
use std::fmt::Write;
use std::io::{BufRead, Write as IoWrite};

#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub enum CatFileMode {
PrettyPrint,

ObjectType,

ObjectSize,

Exists,

Raw(ObjectType),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ObjectType {
Blob,

Tree,

Commit,

Tag,
}

Expand All @@ -36,31 +31,30 @@ pub fn cat_file<D: Decoder>(
let hash = parse_hash(object_name)?;

let mut encoded = Vec::new();
store
let _ = store
.get(&hash)?
.read_to_end(&mut encoded)
.map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?;
.map_err(|e| VctrlError::IoError(Arc::new(e)))?;

match mode {
CatFileMode::Exists => Ok(()),
CatFileMode::ObjectType => {
let obj_type = decode_type(decoder, &encoded)?;
let type_str = obj_type_to_str(obj_type);
writeln!(writer, "{type_str}")
.map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?;
writeln!(writer, "{type_str}").map_err(|e| VctrlError::IoError(Arc::new(e)))?;
Ok(())
}
CatFileMode::ObjectSize => {
let _obj_type = decode_type(decoder, &encoded)?;
let size = encoded.len();
writeln!(writer, "{size}").map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?;
writeln!(writer, "{size}").map_err(|e| VctrlError::IoError(Arc::new(e)))?;
Ok(())
}
CatFileMode::PrettyPrint => {
let content = pretty_print(decoder, &encoded)?;
writer
.write_all(content.as_bytes())
.map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?;
.map_err(|e| VctrlError::IoError(Arc::new(e)))?;
Ok(())
}
CatFileMode::Raw(expected_type) => {
Expand All @@ -74,23 +68,19 @@ pub fn cat_file<D: Decoder>(
}
writer
.write_all(&encoded)
.map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?;
.map_err(|e| VctrlError::IoError(Arc::new(e)))?;
Ok(())
}
}
}

#[allow(clippy::struct_excessive_bools)]
#[derive(Default)]
#[derive(Debug, Default)]
pub struct BatchOptions {
pub format: Option<String>,

pub nul_terminated: bool,

pub follow_symlinks: bool,

pub buffer: bool,

pub print_contents: bool,
}

Expand All @@ -110,7 +100,7 @@ pub fn cat_file_batch<D: Decoder>(
line.clear();
if input
.read_line(&mut line)
.map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?
.map_err(|e| VctrlError::IoError(Arc::new(e)))?
== 0
{
break;
Expand All @@ -137,7 +127,7 @@ pub fn cat_file_batch<D: Decoder>(
if !options.buffer {
output
.write_all(&out_buf)
.map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?;
.map_err(|e| VctrlError::IoError(Arc::new(e)))?;
out_buf.clear();
}
} else {
Expand All @@ -147,7 +137,7 @@ pub fn cat_file_batch<D: Decoder>(
if !options.buffer {
output
.write_all(&out_buf)
.map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?;
.map_err(|e| VctrlError::IoError(Arc::new(e)))?;
out_buf.clear();
}
}
Expand All @@ -156,7 +146,7 @@ pub fn cat_file_batch<D: Decoder>(
if !out_buf.is_empty() {
output
.write_all(&out_buf)
.map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?;
.map_err(|e| VctrlError::IoError(Arc::new(e)))?;
}
Ok(())
}
Expand All @@ -170,10 +160,10 @@ fn handle_one_object<D: Decoder>(
let hash = parse_hash(object_name)?;

let mut encoded = Vec::new();
store
let _ = store
.get(&hash)?
.read_to_end(&mut encoded)
.map_err(|e| VctrlError::IoError(std::sync::Arc::new(e)))?;
.map_err(|e| VctrlError::IoError(Arc::new(e)))?;

let obj_type = decode_type(decoder, &encoded)?;
let obj_size = encoded.len() as u64;
Expand Down Expand Up @@ -202,12 +192,22 @@ fn parse_hash(s: &str) -> Result<Hash, VctrlError> {
"invalid hash length: {actual_len} (expected 128)"
)));
}

let mut bytes = [0u8; 64];
for (i, byte) in bytes.iter_mut().enumerate() {
let hex_byte = &s[i * 2..i * 2 + 2];
let start = i
.checked_mul(2)
.ok_or_else(|| VctrlError::Other("hash index overflow".into()))?;
let end = start
.checked_add(2)
.ok_or_else(|| VctrlError::Other("hash index overflow".into()))?;
let hex_byte = s
.get(start..end)
.ok_or_else(|| VctrlError::Other("hash slice out of bounds".into()))?;
*byte = u8::from_str_radix(hex_byte, 16)
.map_err(|e| VctrlError::Other(format!("invalid hex character in hash: {e}")))?;
}

Hash::from_bytes(&bytes)
}

Expand Down
2 changes: 2 additions & 0 deletions libvctrl_plumbing/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
extern crate alloc;

#[cfg(test)]
use libvctrl_core as _;

Expand Down
11 changes: 5 additions & 6 deletions libvctrl_plumbing/tests/cat_file_tests.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
//! Integration tests for the cat-file plumbing command.

use libvctrl::{BinaryDecoder, BinaryEncoder};
use std::io::Cursor;

use libvctrl::{
Blob, Commit, Encoder, EntryKind, Hash, Hasher, ObjectStore, Tag, Tree, TreeEntry, UserID,
VctrlError,
BinaryDecoder, BinaryEncoder, Blob, Commit, Encoder, EntryKind, Hash, Hasher, MemoryStore,
ObjectStore, Sha512Hasher, Tag, Tree, TreeEntry, UserID, VctrlError,
};
use libvctrl::{MemoryStore, Sha512Hasher};
use libvctrl_core as _;
use libvctrl_plumbing::cat_file::{
BatchOptions, CatFileMode, ObjectType, cat_file, cat_file_batch,
};
use std::io::Cursor;

// Helper: build a minimal repository with one object of each type
struct TestRepo {
Expand Down Expand Up @@ -185,7 +184,7 @@ fn object_size() -> Result<(), VctrlError> {
let size: usize = utf8_string(out)?
.trim()
.parse::<usize>()
.map_err(|e: std::num::ParseIntError| VctrlError::Other(e.to_string()))?;
.map_err(|e: core::num::ParseIntError| VctrlError::Other(e.to_string()))?;
assert!(size > 0);
Ok(())
}
Expand Down