Skip to main content

gix_ref/store/packed/
buffer.rs

1use std::path::PathBuf;
2
3use gix_error::{ErrorExt, ExnResult, Message, ResultExt, message};
4
5use crate::store_impl::packed;
6
7impl AsRef<[u8]> for packed::Buffer {
8    fn as_ref(&self) -> &[u8] {
9        &self.data.as_ref()[self.offset..]
10    }
11}
12
13impl AsRef<[u8]> for packed::Backing {
14    fn as_ref(&self) -> &[u8] {
15        match self {
16            packed::Backing::InMemory(data) => data,
17            packed::Backing::Mapped(map) => map,
18        }
19    }
20}
21
22/// Initialization
23impl packed::Buffer {
24    fn open_with_backing(backing: packed::Backing, path: PathBuf, object_hash: gix_hash::Kind) -> ExnResult<Self> {
25        let (backing, offset) = {
26            let (offset, sorted) = {
27                let mut input = backing.as_ref();
28                if *input.first().unwrap_or(&b' ') == b'#' {
29                    let header = packed::decode::header(&mut input).map_err(|()| {
30                        gix_error::corruption("The header could not be parsed, even though first line started with '#'")
31                            .raise_erased()
32                    })?;
33                    let offset = backing.as_ref().len() - input.len();
34                    (offset, header.sorted)
35                } else {
36                    (0, false)
37                }
38            };
39
40            if !sorted {
41                // this implementation is likely slower than what git does, but it's less code, too.
42                let mut entries = packed::Iter::new(&backing.as_ref()[offset..], object_hash)
43                    .or_raise_erased(|| message("Could not iterate unsorted packed refs"))?
44                    .collect::<Result<Vec<_>, _>>()
45                    .or_erased()?;
46                entries.sort_by_key(|e| e.name.as_bstr());
47                let mut serialized = Vec::<u8>::new();
48                for entry in entries {
49                    serialized.extend_from_slice(entry.target);
50                    serialized.push(b' ');
51                    serialized.extend_from_slice(entry.name.as_bstr());
52                    serialized.push(b'\n');
53                    if let Some(object) = entry.object {
54                        serialized.push(b'^');
55                        serialized.extend_from_slice(object);
56                        serialized.push(b'\n');
57                    }
58                }
59                (packed::Backing::InMemory(serialized), 0)
60            } else {
61                (backing, offset)
62            }
63        };
64        Ok(packed::Buffer {
65            offset,
66            data: backing,
67            path,
68            object_hash,
69        })
70    }
71
72    /// Open the file at `path`, parsing object ids as `object_hash`, and map it into memory if the file size is larger
73    /// than `use_memory_map_if_larger_than_bytes`.
74    ///
75    /// In order to allow fast lookups and optimizations, the contents of the packed refs must be sorted.
76    /// If that's not the case, they will be sorted on the fly with the data being written into a memory buffer.
77    ///
78    /// I/O failures include [metadata](gix_error::Exn::metadata()) `path` (native path), the packed-refs file.
79    pub fn open(
80        path: PathBuf,
81        use_memory_map_if_larger_than_bytes: u64,
82        object_hash: gix_hash::Kind,
83    ) -> ExnResult<Self> {
84        let backing = (|| -> std::io::Result<packed::Backing> {
85            Ok(
86                if std::fs::metadata(&path)?.len() <= use_memory_map_if_larger_than_bytes {
87                    packed::Backing::InMemory(std::fs::read(&path)?)
88                } else {
89                    packed::Backing::Mapped(
90                        // SAFETY: Git replaces packed-refs rather than changing a mapped file in place.
91                        #[expect(unsafe_code)]
92                        unsafe {
93                            memmap2::MmapOptions::new().map_copy_read_only(&std::fs::File::open(&path)?)?
94                        },
95                    )
96                },
97            )
98        })()
99        .or_raise_erased(|| Message::new("Could not open packed refs").with("path", path.as_path()))?;
100        Self::open_with_backing(backing, path, object_hash)
101    }
102
103    /// Open a buffer from `bytes`, which is the content of a typical `packed-refs` file, parsing object ids as
104    /// `object_hash`.
105    ///
106    /// In order to allow fast lookups and optimizations, the contents of the packed refs must be sorted.
107    /// If that's not the case, they will be sorted on the fly.
108    pub fn from_bytes(bytes: &[u8], object_hash: gix_hash::Kind) -> ExnResult<Self> {
109        let backing = packed::Backing::InMemory(bytes.into());
110        Self::open_with_backing(backing, PathBuf::from("<memory>"), object_hash)
111    }
112}