1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
|
use crate::utils;
use gix_object::bstr::{BStr, BString};
use std::io::{self, ErrorKind, Read, Write};
// Format: [usize-LE][usize-LE][byte][byte][hash][relative_path_bytes][object_stream]
// Note that stream_len can be usize::MAX to indicate the stream size is unknown
pub(crate) fn read_entry_info(
read: &mut utils::Read,
path_buf: &mut BString,
) -> std::io::Result<(Option<usize>, gix_object::tree::EntryMode, gix_hash::ObjectId)> {
let mut buf = [0; std::mem::size_of::<usize>() * 2 + 2];
read.read_exact(&mut buf)?;
let (path_len, rest) = buf.split_at(std::mem::size_of::<usize>());
let (stream_len, bytes) = rest.split_at(std::mem::size_of::<usize>());
let path_len = usize::from_le_bytes(path_len.try_into().expect("valid"));
let stream_size = usize::from_le_bytes(stream_len.try_into().expect("valid"));
let mode = byte_to_mode(bytes[0]);
let hash_kind = byte_to_hash(bytes[1]);
let mut hash = hash_kind.null();
read.read_exact(hash.as_mut_slice())?;
clear_and_set_len(path_buf, path_len)?;
read.read_exact(path_buf)?;
Ok(((stream_size != usize::MAX).then_some(stream_size), mode, hash))
}
/// This function must match the read-count of `read_entry_info` for max efficiency.
pub(crate) fn write_entry_header_and_path(
path: &BStr,
oid: &gix_hash::oid,
mode: gix_object::tree::EntryMode,
stream_len: Option<usize>,
out: &mut gix_features::io::pipe::Writer,
) -> std::io::Result<()> {
const HEADER_LEN: usize = std::mem::size_of::<usize>() * 2 + 2;
let mut buf = [0u8; HEADER_LEN + gix_hash::Kind::longest().len_in_bytes()];
let (path_len_buf, rest) = buf.split_at_mut(std::mem::size_of::<usize>());
let (stream_len_buf, bytes) = rest.split_at_mut(std::mem::size_of::<usize>());
path_len_buf.copy_from_slice(&path.len().to_le_bytes());
stream_len_buf.copy_from_slice(&stream_len.unwrap_or(usize::MAX).to_le_bytes());
bytes[0] = mode_to_byte(mode);
bytes[1] = hash_to_byte(oid.kind());
bytes[2..][..oid.kind().len_in_bytes()].copy_from_slice(oid.as_bytes());
// We know how `out` works in a pipe writer, it's always writing everything.
#[allow(clippy::unused_io_amount)]
{
out.write(&buf[..HEADER_LEN + oid.kind().len_in_bytes()])?;
out.write(path)?;
}
Ok(())
}
/// This writes everything in `input` in such way that the receiver knows exactly how much to read.
/// The format is similar to the packetline format, but in binary.
pub(crate) fn write_stream(
buf: &mut Vec<u8>,
mut input: impl std::io::Read,
out: &mut gix_features::io::pipe::Writer,
) -> std::io::Result<()> {
const BUF_LEN: usize = u16::MAX as usize;
clear_and_set_len(buf, BUF_LEN)?;
// We know how `out` works in a pipe writer, it's always writing everything.
#[allow(clippy::unused_io_amount)]
loop {
match input.read(buf) {
Ok(0) => {
// terminator
out.write(&0_u16.to_le_bytes())?;
break;
}
Ok(n) => {
out.write(&(n as u16).to_le_bytes())?;
out.write(&buf[..n])?;
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
fn byte_to_hash(b: u8) -> gix_hash::Kind {
match b {
0 => gix_hash::Kind::Sha1,
_ => unreachable!("BUG: we control the protocol"),
}
}
fn byte_to_mode(b: u8) -> gix_object::tree::EntryMode {
use gix_object::tree::EntryKind::*;
match b {
0 => Tree,
1 => Blob,
2 => BlobExecutable,
3 => Link,
4 => Commit,
_ => unreachable!("BUG: we control the protocol"),
}
.into()
}
fn hash_to_byte(h: gix_hash::Kind) -> u8 {
match h {
gix_hash::Kind::Sha1 => 0,
}
}
fn mode_to_byte(m: gix_object::tree::EntryMode) -> u8 {
use gix_object::tree::EntryKind::*;
match m.kind() {
Tree => 0,
Blob => 1,
BlobExecutable => 2,
Link => 3,
Commit => 4,
}
}
fn clear_and_set_len(buf: &mut Vec<u8>, len: usize) -> io::Result<()> {
buf.clear();
buf.try_reserve(len).map_err(|_| ErrorKind::OutOfMemory)?;
buf.resize(len, 0);
Ok(())
}
|