gix_index/file/
init.rs

1#![allow(unused)]
2
3use std::path::{Path, PathBuf};
4
5use crate::{decode, extension, File, State};
6
7mod error {
8
9    /// The error returned by [File::at()][super::File::at()].
10    #[derive(Debug, thiserror::Error)]
11    #[allow(missing_docs)]
12    pub enum Error {
13        #[error("An IO error occurred while opening the index")]
14        Io(#[from] std::io::Error),
15        #[error(transparent)]
16        Decode(#[from] crate::decode::Error),
17        #[error(transparent)]
18        LinkExtension(#[from] crate::extension::link::decode::Error),
19    }
20}
21
22pub use error::Error;
23
24/// Initialization
25impl File {
26    /// Try to open the index file at `path` with `options`, assuming `object_hash` is used throughout the file, or create a new
27    /// index that merely exists in memory and is empty. `skip_hash` will increase the performance by a factor of 2, at the cost of
28    /// possibly not detecting corruption.
29    ///
30    /// Note that the `path` will not be written if it doesn't exist.
31    pub fn at_or_default(
32        path: impl Into<PathBuf>,
33        object_hash: gix_hash::Kind,
34        skip_hash: bool,
35        options: decode::Options,
36    ) -> Result<Self, Error> {
37        let path = path.into();
38        Ok(match Self::at(&path, object_hash, skip_hash, options) {
39            Ok(f) => f,
40            Err(Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
41                File::from_state(State::new(object_hash), path)
42            }
43            Err(err) => return Err(err),
44        })
45    }
46
47    /// Open an index file at `path` with `options`, assuming `object_hash` is used throughout the file. If `skip_hash` is `true`,
48    /// we will not get or compare the checksum of the index at all, which generally increases performance of this method by a factor
49    /// of 2 or more.
50    ///
51    /// Note that the verification of the file hash depends on `options`, and even then it's performed after the file was read and not
52    /// before it is read. That way, invalid files would see a more descriptive error message as we try to parse them.
53    pub fn at(
54        path: impl Into<PathBuf>,
55        object_hash: gix_hash::Kind,
56        skip_hash: bool,
57        options: decode::Options,
58    ) -> Result<Self, Error> {
59        let _span = gix_features::trace::detail!("gix_index::File::at()");
60        let path = path.into();
61        let (data, mtime) = {
62            let mut file = std::fs::File::open(&path)?;
63            // SAFETY: we have to take the risk of somebody changing the file underneath. Git never writes into the same file.
64            #[allow(unsafe_code)]
65            let data = unsafe { memmap2::MmapOptions::new().map_copy_read_only(&file)? };
66
67            if !skip_hash {
68                // Note that even though it's trivial to offload this into a thread, which is worth it for all but the smallest
69                // index files, we choose more safety here just like git does and don't even try to decode the index if the hashes
70                // don't match.
71                // Thanks to `skip_hash`, we can get performance and it's under caller control, at the cost of some safety.
72                let expected =
73                    gix_hash::ObjectId::from_bytes_or_panic(&data[data.len() - object_hash.len_in_bytes()..]);
74                if !expected.is_null() {
75                    let _span = gix_features::trace::detail!("gix::open_index::hash_index", path = ?path);
76                    let meta = file.metadata()?;
77                    let num_bytes_to_hash = meta.len() - object_hash.len_in_bytes() as u64;
78                    let actual_hash = gix_features::hash::bytes(
79                        &mut file,
80                        num_bytes_to_hash,
81                        object_hash,
82                        &mut gix_features::progress::Discard,
83                        &Default::default(),
84                    )?;
85
86                    if actual_hash != expected {
87                        return Err(Error::Decode(decode::Error::ChecksumMismatch {
88                            actual_checksum: actual_hash,
89                            expected_checksum: expected,
90                        }));
91                    }
92                }
93            }
94
95            (data, filetime::FileTime::from_last_modification_time(&file.metadata()?))
96        };
97
98        let (state, checksum) = State::from_bytes(&data, mtime, object_hash, options)?;
99        let mut file = File { state, path, checksum };
100        if let Some(mut link) = file.link.take() {
101            link.dissolve_into(&mut file, object_hash, skip_hash, options)?;
102        }
103
104        Ok(file)
105    }
106
107    /// Consume `state` and pretend it was read from `path`, setting our checksum to `null`.
108    ///
109    /// `File` instances created like that should be written to disk to set the correct checksum via `[File::write()]`.
110    pub fn from_state(state: State, path: impl Into<PathBuf>) -> Self {
111        File {
112            state,
113            path: path.into(),
114            checksum: None,
115        }
116    }
117}