git_plumber/git/pack/
mod.rs

1use nom::{
2    IResult, Parser,
3    bytes::complete::tag,
4    combinator::map,
5    error::{Error, ErrorKind},
6    number::complete::be_u32,
7};
8
9pub mod delta;
10pub mod index;
11pub mod object;
12
13pub use delta::{DeltaInstruction, parse_delta_instructions};
14pub use index::PackIndex;
15pub use object::{Object, ObjectHeader, ObjectType};
16
17use thiserror::Error;
18
19#[derive(Debug)]
20pub struct Header {
21    pub version: u32,
22    pub object_count: u32,
23    pub raw_data: Vec<u8>,
24}
25
26impl Header {
27    /// 4-byte signature:
28    ///    The signature is: {'P', 'A', 'C', 'K'}
29    fn parse_signature(input: &[u8]) -> IResult<&[u8], ()> {
30        map(tag("PACK"), |_| ()).parse(input)
31    }
32
33    ///    4-byte version number (network byte order):
34    /// Git currently accepts version number 2 or 3 but
35    ///       generates version 2 only.
36    pub fn parse(input: &[u8]) -> IResult<&[u8], Self> {
37        let original_input = input;
38        let (input, ()) = Self::parse_signature(input)?;
39        let (input, version) = be_u32(input)?;
40
41        if version != 2 && version != 3 {
42            return Err(nom::Err::Error(Error::new(input, ErrorKind::Tag)));
43        }
44
45        let (input, object_count) = be_u32(input)?;
46
47        // Calculate the header size (12 bytes: 4 for signature + 4 for version + 4 for object count)
48        let header_size = original_input.len() - input.len();
49        let raw_data = original_input[..header_size].to_vec();
50
51        Ok((
52            input,
53            Header {
54                version,
55                object_count,
56                raw_data,
57            },
58        ))
59    }
60}
61
62#[derive(Debug, Error)]
63pub enum PackError {
64    #[error("Invalid pack file signature")]
65    InvalidSignature,
66
67    #[error("Unsupported pack version: {0}")]
68    UnsupportedVersion(u32),
69
70    #[error("Invalid object type: {0}")]
71    InvalidObjectType(u8),
72
73    #[error("Parse error: {0}")]
74    ParseError(String),
75
76    #[error("Decompression error: {0}")]
77    DecompressionError(#[from] std::io::Error),
78
79    // Index-specific errors
80    #[error("Invalid index file signature")]
81    InvalidIndexSignature,
82
83    #[error("Unsupported index version: {0}")]
84    UnsupportedIndexVersion(u32),
85
86    #[error("Corrupt fan-out table")]
87    CorruptFanOutTable,
88
89    #[error("Index checksum mismatch")]
90    IndexChecksumMismatch,
91
92    #[error("Pack checksum mismatch")]
93    PackChecksumMismatch,
94
95    #[error("Object not found in index: {0}")]
96    ObjectNotFound(String),
97
98    #[error("Invalid object index: {0}")]
99    InvalidObjectIndex(usize),
100}
101
102// Helper function to create a simple pack file header with the specified number of objects
103#[cfg(test)]
104mod tests {
105    use super::*;
106    fn create_pack_header(object_count: u32) -> Vec<u8> {
107        let mut header = Vec::new();
108        header.extend_from_slice(b"PACK"); // Signature
109        header.extend_from_slice(&[0, 0, 0, 2]); // Version 2
110        header.extend_from_slice(&object_count.to_be_bytes()); // Object count
111        header
112    }
113
114    #[test]
115    fn parse_pack_header() {
116        let data = create_pack_header(3); // TODO: use a real pack file
117        let (_, header) = Header::parse(&data).unwrap();
118        assert_eq!(header.version, 2);
119        assert_eq!(header.object_count, 3);
120
121        // Verify the raw data contains the expected header bytes
122        assert_eq!(header.raw_data.len(), 12); // 4 + 4 + 4 bytes
123        assert_eq!(&header.raw_data[0..4], b"PACK"); // Signature
124        assert_eq!(&header.raw_data[4..8], &[0, 0, 0, 2]); // Version 2
125        assert_eq!(&header.raw_data[8..12], &3u32.to_be_bytes()); // Object count 3
126    }
127}