gix-object 0.42.3

Immutable and mutable git objects with decoding and encoding support
Documentation
use std::cmp::Ordering;

use crate::{
    bstr::{BStr, BString},
    tree,
};

mod ref_iter;
///
#[allow(clippy::empty_docs)]
pub mod write;

/// The mode of items storable in a tree, similar to the file mode on a unix file system.
///
/// Used in [`mutable::Entry`][crate::tree::Entry] and [`EntryRef`].
///
/// Note that even though it can be created from any `u16`, it should be preferable to
/// create it by converting [`EntryKind`] into `EntryMode`.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EntryMode(pub u16);

/// A discretized version of ideal and valid values for entry modes.
///
/// Note that even though it can represent every valid [mode](EntryMode), it might
/// loose information due to that as well.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)]
#[repr(u16)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EntryKind {
    /// A tree, or directory
    Tree = 0o040000u16,
    /// A file that is not executable
    Blob = 0o100644,
    /// A file that is executable
    BlobExecutable = 0o100755,
    /// A symbolic link
    Link = 0o120000,
    /// A commit of a git submodule
    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()
    }
}

/// Serialization
impl EntryKind {
    /// Return the representation as used in the git internal format.
    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 {
    /// Discretize the raw mode into an enum with well-known state while dropping unnecessary details.
    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
        }
    }

    /// Return true if this entry mode represents a Tree/directory
    pub const fn is_tree(&self) -> bool {
        self.0 & IFMT == EntryKind::Tree as u16
    }

    /// Return true if this entry mode represents the commit of a submodule.
    pub const fn is_commit(&self) -> bool {
        self.0 & IFMT == EntryKind::Commit as u16
    }

    /// Return true if this entry mode represents a symbolic link
    pub const fn is_link(&self) -> bool {
        self.0 & IFMT == EntryKind::Link as u16
    }

    /// Return true if this entry mode represents anything BUT Tree/directory
    pub const fn is_no_tree(&self) -> bool {
        self.0 & IFMT != EntryKind::Tree as u16
    }

    /// Return true if the entry is any kind of blob.
    pub const fn is_blob(&self) -> bool {
        self.0 & IFMT == 0o100000
    }

    /// Return true if the entry is an executable blob.
    pub const fn is_executable(&self) -> bool {
        matches!(self.kind(), EntryKind::BlobExecutable)
    }

    /// Return true if the entry is any kind of blob or symlink.
    pub const fn is_blob_or_symlink(&self) -> bool {
        matches!(
            self.kind(),
            EntryKind::Blob | EntryKind::BlobExecutable | EntryKind::Link
        )
    }

    /// Represent the mode as descriptive string.
    pub const fn as_str(&self) -> &'static str {
        use EntryKind::*;
        match self.kind() {
            Tree => "tree",
            Blob => "blob",
            BlobExecutable => "exe",
            Link => "link",
            Commit => "commit",
        }
    }

    /// Return the representation as used in the git internal format, which is octal and written
    /// to the `backing` buffer. The respective sub-slice that was written to is returned.
    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()
    }
}

/// An element of a [`TreeRef`][crate::TreeRef::entries].
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EntryRef<'a> {
    /// The kind of object to which `oid` is pointing.
    pub mode: tree::EntryMode,
    /// The name of the file in the parent tree.
    pub filename: &'a BStr,
    /// The id of the object representing the entry.
    // TODO: figure out how these should be called. id or oid? It's inconsistent around the codebase.
    //       Answer: make it 'id', as in `git2`
    #[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)
        })
    }
}

/// An entry in a [`Tree`][crate::Tree], similar to an entry in a directory.
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Entry {
    /// The kind of object to which `oid` is pointing to.
    pub mode: EntryMode,
    /// The name of the file in the parent tree.
    pub filename: BString,
    /// The id of the object representing the entry.
    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)
        })
    }
}