gix_ref/store/packed/
buffer.rs

1use crate::store_impl::packed;
2
3impl AsRef<[u8]> for packed::Buffer {
4    fn as_ref(&self) -> &[u8] {
5        &self.data.as_ref()[self.offset..]
6    }
7}
8
9impl AsRef<[u8]> for packed::Backing {
10    fn as_ref(&self) -> &[u8] {
11        match self {
12            packed::Backing::InMemory(data) => data,
13            packed::Backing::Mapped(map) => map,
14        }
15    }
16}
17
18///
19pub mod open {
20    use std::path::PathBuf;
21
22    use winnow::{prelude::*, stream::Offset};
23
24    use crate::store_impl::packed;
25
26    /// Initialization
27    impl packed::Buffer {
28        fn open_with_backing(backing: packed::Backing, path: PathBuf) -> Result<Self, Error> {
29            let (backing, offset) = {
30                let (offset, sorted) = {
31                    let mut input = backing.as_ref();
32                    if *input.first().unwrap_or(&b' ') == b'#' {
33                        let header = packed::decode::header::<()>
34                            .parse_next(&mut input)
35                            .map_err(|_| Error::HeaderParsing)?;
36                        let offset = input.offset_from(&backing.as_ref());
37                        (offset, header.sorted)
38                    } else {
39                        (0, false)
40                    }
41                };
42
43                if !sorted {
44                    // this implementation is likely slower than what git does, but it's less code, too.
45                    let mut entries = packed::Iter::new(&backing.as_ref()[offset..])?.collect::<Result<Vec<_>, _>>()?;
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                    (Backing::InMemory(serialized), 0)
60                } else {
61                    (backing, offset)
62                }
63            };
64            Ok(packed::Buffer {
65                offset,
66                data: backing,
67                path,
68            })
69        }
70
71        /// Open the file at `path` and map it into memory if the file size is larger than `use_memory_map_if_larger_than_bytes`.
72        ///
73        /// In order to allow fast lookups and optimizations, the contents of the packed refs must be sorted.
74        /// If that's not the case, they will be sorted on the fly with the data being written into a memory buffer.
75        pub fn open(path: PathBuf, use_memory_map_if_larger_than_bytes: u64) -> Result<Self, Error> {
76            let backing = if std::fs::metadata(&path)?.len() <= use_memory_map_if_larger_than_bytes {
77                packed::Backing::InMemory(std::fs::read(&path)?)
78            } else {
79                packed::Backing::Mapped(
80                    // SAFETY: we have to take the risk of somebody changing the file underneath. Git never writes into the same file.
81                    #[allow(unsafe_code)]
82                    unsafe {
83                        memmap2::MmapOptions::new().map_copy_read_only(&std::fs::File::open(&path)?)?
84                    },
85                )
86            };
87            Self::open_with_backing(backing, path)
88        }
89
90        /// Open a buffer from `bytes`, which is the content of a typical `packed-refs` file.
91        ///
92        /// In order to allow fast lookups and optimizations, the contents of the packed refs must be sorted.
93        /// If that's not the case, they will be sorted on the fly.
94        pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
95            let backing = packed::Backing::InMemory(bytes.into());
96            Self::open_with_backing(backing, PathBuf::from("<memory>"))
97        }
98    }
99
100    mod error {
101        use crate::packed;
102
103        /// The error returned by [`open()`][super::packed::Buffer::open()].
104        #[derive(Debug, thiserror::Error)]
105        #[allow(missing_docs)]
106        pub enum Error {
107            #[error("The packed-refs file did not have a header or wasn't sorted and could not be iterated")]
108            Iter(#[from] packed::iter::Error),
109            #[error("The header could not be parsed, even though first line started with '#'")]
110            HeaderParsing,
111            #[error("The buffer could not be opened or read")]
112            Io(#[from] std::io::Error),
113        }
114    }
115    pub use error::Error;
116
117    use crate::packed::Backing;
118}