use std::fmt;
use std::time::SystemTime;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Oid([u8; 20]);
impl Oid {
pub fn from_bytes(bytes: [u8; 20]) -> Self {
Self(bytes)
}
pub fn as_bytes(&self) -> &[u8; 20] {
&self.0
}
pub fn is_zero(&self) -> bool {
self.0 == [0u8; 20]
}
}
impl fmt::Display for Oid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in &self.0 {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::Debug for Oid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Oid({self})")
}
}
pub type TreeHash = Oid;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Reference {
pub name: String,
pub target: Oid,
pub is_branch: bool,
pub is_tag: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature {
pub name: String,
pub email: String,
pub when: SystemTime,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Commit {
pub oid: Oid,
pub author: Signature,
pub committer: Signature,
pub message: String,
pub parents: Vec<Oid>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FileStatus {
Added,
Modified,
Deleted,
Renamed,
Copied,
Untracked,
Ignored,
TypeChanged,
Conflicted,
}
impl fmt::Display for FileStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Added => write!(f, "added"),
Self::Modified => write!(f, "modified"),
Self::Deleted => write!(f, "deleted"),
Self::Renamed => write!(f, "renamed"),
Self::Copied => write!(f, "copied"),
Self::Untracked => write!(f, "untracked"),
Self::Ignored => write!(f, "ignored"),
Self::TypeChanged => write!(f, "type_changed"),
Self::Conflicted => write!(f, "conflicted"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffEntry {
pub path: String,
pub old_path: Option<String>,
pub old_oid: Oid,
pub new_oid: Oid,
pub status: FileStatus,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DiffStats {
pub additions: usize,
pub deletions: usize,
pub files_changed: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum EntryState {
Staged,
Unstaged,
Untracked,
Conflicted,
}
impl fmt::Display for EntryState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Staged => write!(f, "staged"),
Self::Unstaged => write!(f, "unstaged"),
Self::Untracked => write!(f, "untracked"),
Self::Conflicted => write!(f, "conflicted"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StatusEntry {
pub path: String,
pub state: EntryState,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexEntry {
pub path: String,
pub oid: Oid,
pub kind: EntryKind,
pub filemode: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum EntryKind {
Blob,
Tree,
Submodule,
}
impl fmt::Display for EntryKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Blob => write!(f, "blob"),
Self::Tree => write!(f, "tree"),
Self::Submodule => write!(f, "submodule"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TreeEntry {
pub name: String,
pub oid: Oid,
pub kind: EntryKind,
pub filemode: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Branch {
pub name: String,
pub target: Oid,
pub upstream: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tag {
pub name: String,
pub target: Oid,
pub tagger: Option<Signature>,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Remote {
pub name: String,
pub url: String,
pub fetch_specs: Vec<String>,
pub push_specs: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlameLine {
pub line: usize,
pub commit_oid: Oid,
pub author: Signature,
pub content: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrepMatch {
pub path: String,
pub line_number: Option<usize>,
pub line: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StashEntry {
pub index: usize,
pub oid: Oid,
pub message: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MergeResult {
pub head: Option<Oid>,
pub fast_forward: bool,
pub conflicts: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RebaseResult {
pub head: Option<Oid>,
pub applied: usize,
pub conflicts: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum BranchFilter {
#[default]
Local,
Remote,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ResetMode {
#[default]
Mixed,
Soft,
Hard,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn oid_display_debug_and_zero_detection_are_stable() {
let zero = Oid::from_bytes([0; 20]);
let mut bytes = [0; 20];
bytes[0] = 0xab;
bytes[19] = 0x05;
let oid = Oid::from_bytes(bytes);
assert!(zero.is_zero());
assert!(!oid.is_zero());
assert_eq!(oid.as_bytes(), &bytes);
assert_eq!(oid.to_string(), "ab00000000000000000000000000000000000005");
assert_eq!(
format!("{oid:?}"),
"Oid(ab00000000000000000000000000000000000005)"
);
}
#[test]
fn enum_display_values_match_public_contract() {
assert_eq!(FileStatus::Added.to_string(), "added");
assert_eq!(FileStatus::Modified.to_string(), "modified");
assert_eq!(FileStatus::Deleted.to_string(), "deleted");
assert_eq!(FileStatus::Renamed.to_string(), "renamed");
assert_eq!(FileStatus::Copied.to_string(), "copied");
assert_eq!(FileStatus::Untracked.to_string(), "untracked");
assert_eq!(FileStatus::Ignored.to_string(), "ignored");
assert_eq!(FileStatus::TypeChanged.to_string(), "type_changed");
assert_eq!(FileStatus::Conflicted.to_string(), "conflicted");
assert_eq!(EntryState::Staged.to_string(), "staged");
assert_eq!(EntryState::Unstaged.to_string(), "unstaged");
assert_eq!(EntryState::Untracked.to_string(), "untracked");
assert_eq!(EntryState::Conflicted.to_string(), "conflicted");
assert_eq!(EntryKind::Blob.to_string(), "blob");
assert_eq!(EntryKind::Tree.to_string(), "tree");
assert_eq!(EntryKind::Submodule.to_string(), "submodule");
}
#[test]
fn default_result_types_are_empty_and_non_destructive() {
assert_eq!(DiffStats::default().files_changed, 0);
assert_eq!(BranchFilter::default(), BranchFilter::Local);
assert_eq!(ResetMode::default(), ResetMode::Mixed);
assert_eq!(MergeResult::default().conflicts, Vec::<String>::new());
assert_eq!(RebaseResult::default().applied, 0);
}
}