use std::cmp::Ordering;
use crate::{
bstr::{BStr, BString},
tree,
};
mod ref_iter;
#[allow(clippy::empty_docs)]
pub mod write;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EntryMode(pub u16);
#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)]
#[repr(u16)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EntryKind {
Tree = 0o040000u16,
Blob = 0o100644,
BlobExecutable = 0o100755,
Link = 0o120000,
Commit = 0o160000,
}
impl From<EntryKind> for EntryMode {
fn from(value: EntryKind) -> Self {
EntryMode(value as u16)
}
}
impl From<EntryMode> for EntryKind {
fn from(value: EntryMode) -> Self {
value.kind()
}
}
impl EntryKind {
pub fn as_octal_str(&self) -> &'static BStr {
use EntryKind::*;
let bytes: &[u8] = match self {
Tree => b"40000",
Blob => b"100644",
BlobExecutable => b"100755",
Link => b"120000",
Commit => b"160000",
};
bytes.into()
}
}
impl std::ops::Deref for EntryMode {
type Target = u16;
fn deref(&self) -> &Self::Target {
&self.0
}
}
const IFMT: u16 = 0o170000;
impl EntryMode {
pub const fn kind(&self) -> EntryKind {
let etype = self.0 & IFMT;
if etype == 0o100000 {
if self.0 & 0o000100 == 0o000100 {
EntryKind::BlobExecutable
} else {
EntryKind::Blob
}
} else if etype == EntryKind::Link as u16 {
EntryKind::Link
} else if etype == EntryKind::Tree as u16 {
EntryKind::Tree
} else {
EntryKind::Commit
}
}
pub const fn is_tree(&self) -> bool {
self.0 & IFMT == EntryKind::Tree as u16
}
pub const fn is_commit(&self) -> bool {
self.0 & IFMT == EntryKind::Commit as u16
}
pub const fn is_link(&self) -> bool {
self.0 & IFMT == EntryKind::Link as u16
}
pub const fn is_no_tree(&self) -> bool {
self.0 & IFMT != EntryKind::Tree as u16
}
pub const fn is_blob(&self) -> bool {
self.0 & IFMT == 0o100000
}
pub const fn is_executable(&self) -> bool {
matches!(self.kind(), EntryKind::BlobExecutable)
}
pub const fn is_blob_or_symlink(&self) -> bool {
matches!(
self.kind(),
EntryKind::Blob | EntryKind::BlobExecutable | EntryKind::Link
)
}
pub const fn as_str(&self) -> &'static str {
use EntryKind::*;
match self.kind() {
Tree => "tree",
Blob => "blob",
BlobExecutable => "exe",
Link => "link",
Commit => "commit",
}
}
pub fn as_bytes<'a>(&self, backing: &'a mut [u8; 6]) -> &'a BStr {
if self.0 == 0 {
std::slice::from_ref(&b'0')
} else {
let mut nb = 0;
let mut n = self.0;
while n > 0 {
let remainder = (n % 8) as u8;
backing[nb] = b'0' + remainder;
n /= 8;
nb += 1;
}
let res = &mut backing[..nb];
res.reverse();
res
}
.into()
}
}
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EntryRef<'a> {
pub mode: tree::EntryMode,
pub filename: &'a BStr,
#[cfg_attr(feature = "serde", serde(borrow))]
pub oid: &'a gix_hash::oid,
}
impl<'a> PartialOrd for EntryRef<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for EntryRef<'a> {
fn cmp(&self, b: &Self) -> Ordering {
let a = self;
let common = a.filename.len().min(b.filename.len());
a.filename[..common].cmp(&b.filename[..common]).then_with(|| {
let a = a.filename.get(common).or_else(|| a.mode.is_tree().then_some(&b'/'));
let b = b.filename.get(common).or_else(|| b.mode.is_tree().then_some(&b'/'));
a.cmp(&b)
})
}
}
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Entry {
pub mode: EntryMode,
pub filename: BString,
pub oid: gix_hash::ObjectId,
}
impl PartialOrd for Entry {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Entry {
fn cmp(&self, b: &Self) -> Ordering {
let a = self;
let common = a.filename.len().min(b.filename.len());
a.filename[..common].cmp(&b.filename[..common]).then_with(|| {
let a = a.filename.get(common).or_else(|| a.mode.is_tree().then_some(&b'/'));
let b = b.filename.get(common).or_else(|| b.mode.is_tree().then_some(&b'/'));
a.cmp(&b)
})
}
}