//! ## Feature Flags #![cfg_attr( feature = "document-features", cfg_attr(doc, doc = ::document_features::document_features!()) )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![deny(unsafe_code, missing_docs, rust_2018_idioms)] use std::{ops::Range, path::PathBuf}; use filetime::FileTime; pub use gix_hash as hash; /// pub mod file; /// pub mod extension; /// pub mod entry; mod access; mod init; /// pub mod decode; /// pub mod verify; /// pub mod write; /// All known versions of a git index file. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Version { /// Supports entries and various extensions. V2 = 2, /// Adds support for additional flags for each entry, called extended entries. V3 = 3, /// Supports deltified entry paths. V4 = 4, } /// An entry in the index, identifying a non-tree item on disk. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Entry { /// The filesystem stat information for the file on disk. pub stat: entry::Stat, /// The object id for this entry's ODB representation (assuming it's up-to-date with it). pub id: gix_hash::ObjectId, /// Additional flags for use in algorithms and for efficiently storing stage information. pub flags: entry::Flags, /// The kind of item this entry represents - it's not all blobs in the index anymore. pub mode: entry::Mode, /// The range to lookup in the path backing to obtain the entry path relative to the repository. /// This costs additional memory but is probably worth it given that paths can stay in one big allocation. path: Range, } /// An index file whose state was read from a file on disk. #[derive(Clone)] pub struct File { /// The state containing the actual index data. pub(crate) state: State, /// The path from which the index was read or to which it is supposed to be written. pub(crate) path: PathBuf, /// The checksum of all bytes prior to the checksum itself. pub(crate) checksum: Option, } /// The type to use and store paths to all entries. pub type PathStorage = Vec; /// The type to use and store paths to all entries, as reference pub type PathStorageRef = [u8]; /// An in-memory cache of a fully parsed git index file. /// /// As opposed to a snapshot, it's meant to be altered and eventually be written back to disk or converted into a tree. /// We treat index and its state synonymous. #[derive(Clone)] pub struct State { /// The kind of object hash used when storing the underlying file. /// /// Empty states for example won't have a single object id, so deduction of the hash used isn't always possible. object_hash: gix_hash::Kind, /// The time at which the state was created, indicating its freshness compared to other files on disk. /// /// Note that on platforms that only have a precisions of a second for this time, we will treat all entries with the /// same timestamp as this as potentially changed, checking more thoroughly if a change actually happened. timestamp: FileTime, version: Version, entries: Vec, /// A memory area keeping all index paths, in full length, independently of the index version. /// /// Ranges into this storage are referred to by parts of `entries`. path_backing: PathStorage, /// True if one entry in the index has a special marker mode is_sparse: bool, // Extensions tree: Option, link: Option, resolve_undo: Option, untracked: Option, fs_monitor: Option, } mod impls { use std::fmt::{Debug, Formatter}; use crate::State; impl Debug for State { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { for entry in &self.entries { writeln!( f, "{} {}{:?} {} {}", match entry.flags.stage() { 0 => " ", 1 => "BASE ", 2 => "OURS ", 3 => "THEIRS ", _ => "UNKNOWN", }, if entry.flags.is_empty() { "".to_string() } else { format!("{:?} ", entry.flags) }, entry.mode, entry.id, entry.path(self) )?; } Ok(()) } } } pub(crate) mod util { use std::convert::TryInto; #[inline] pub fn var_int(data: &[u8]) -> Option<(u64, &[u8])> { let (num, consumed) = gix_features::decode::leb64_from_read(data).ok()?; let data = &data[consumed..]; (num, data).into() } #[inline] pub fn read_u32(data: &[u8]) -> Option<(u32, &[u8])> { split_at_pos(data, 4).map(|(num, data)| (u32::from_be_bytes(num.try_into().unwrap()), data)) } #[inline] pub fn read_u64(data: &[u8]) -> Option<(u64, &[u8])> { split_at_pos(data, 8).map(|(num, data)| (u64::from_be_bytes(num.try_into().unwrap()), data)) } #[inline] pub fn from_be_u32(b: &[u8]) -> u32 { u32::from_be_bytes(b.try_into().unwrap()) } #[inline] pub fn split_at_byte_exclusive(data: &[u8], byte: u8) -> Option<(&[u8], &[u8])> { if data.len() < 2 { return None; } data.iter().enumerate().find_map(|(idx, b)| { (*b == byte).then(|| { if idx == 0 { (&[] as &[u8], &data[1..]) } else { let (a, b) = data.split_at(idx); (a, &b[1..]) } }) }) } #[inline] pub fn split_at_pos(data: &[u8], pos: usize) -> Option<(&[u8], &[u8])> { if data.len() < pos { return None; } data.split_at(pos).into() } } #[test] fn size_of_entry() { assert_eq!(std::mem::size_of::(), 80); // the reason we have our own time is half the size. assert_eq!(std::mem::size_of::(), 8); assert_eq!(std::mem::size_of::(), 16); }