#![cfg_attr(
all(doc, feature = "document-features"),
doc = ::document_features::document_features!()
)]
#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg))]
#![deny(unsafe_code, missing_docs, rust_2018_idioms)]
use std::{ops::Range, path::PathBuf};
use bstr::{BStr, ByteSlice};
use filetime::FileTime;
pub use gix_hash as hash;
pub use gix_validate as validate;
pub mod file;
pub mod extension;
pub mod entry;
mod access;
pub mod init;
pub mod decode;
pub mod verify;
pub mod write;
pub mod fs;
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Version {
V2 = 2,
V3 = 3,
V4 = 4,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Entry {
pub stat: entry::Stat,
pub id: gix_hash::ObjectId,
pub flags: entry::Flags,
pub mode: entry::Mode,
path: Range<usize>,
}
#[derive(Clone)]
pub struct File {
pub(crate) state: State,
pub(crate) path: PathBuf,
pub(crate) checksum: Option<gix_hash::ObjectId>,
}
pub type PathStorage = Vec<u8>;
pub type PathStorageRef = [u8];
struct DirEntry<'a> {
entry: &'a Entry,
dir_end: usize,
}
impl DirEntry<'_> {
fn path<'a>(&self, state: &'a State) -> &'a BStr {
let range = self.entry.path.start..self.dir_end;
state.path_backing[range].as_bstr()
}
}
pub struct AccelerateLookup<'a> {
icase_entries: hashbrown::HashTable<&'a Entry>,
icase_dirs: hashbrown::HashTable<DirEntry<'a>>,
}
#[derive(Clone)]
pub struct State {
object_hash: gix_hash::Kind,
timestamp: FileTime,
version: Version,
entries: Vec<Entry>,
path_backing: PathStorage,
is_sparse: bool,
end_of_index_at_decode_time: bool,
offset_table_at_decode_time: bool,
tree: Option<extension::Tree>,
link: Option<extension::Link>,
resolve_undo: Option<extension::resolve_undo::Paths>,
untracked: Option<extension::UntrackedCache>,
fs_monitor: Option<extension::FsMonitor>,
}
mod impls {
use std::fmt::{Debug, Formatter};
use crate::{entry::Stage, 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() {
Stage::Unconflicted => " ",
Stage::Base => "BASE ",
Stage::Ours => "OURS ",
Stage::Theirs => "THEIRS ",
},
if entry.flags.is_empty() {
"".to_string()
} else {
format!("{:?} ", entry.flags)
},
entry.mode,
entry.id,
entry.path(self)
)?;
}
Ok(())
}
}
}
pub(crate) mod util {
#[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])> {
data.split_at_checked(4)
.map(|(num, data)| (u32::from_be_bytes(num.try_into().unwrap()), data))
}
#[inline]
pub fn read_u64(data: &[u8]) -> Option<(u64, &[u8])> {
data.split_at_checked(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..])
}
})
})
}
}