use std::cmp::Ordering;
use std::path::PathBuf;
use radicle_git_ext::Oid;
#[cfg(feature = "serde")]
use serde::{
ser::{SerializeStruct as _, Serializer},
Serialize,
};
use url::Url;
use crate::{fs, Commit, Error, Repository};
#[derive(Clone, Debug)]
pub struct Tree {
id: Oid,
entries: Vec<Entry>,
commit: Commit,
root: PathBuf,
}
#[derive(Debug, thiserror::Error)]
pub enum LastCommitError {
#[error(transparent)]
Repo(#[from] Error),
#[error("could not get the last commit for this entry")]
Missing,
}
impl Tree {
pub(crate) fn new(id: Oid, mut entries: Vec<Entry>, commit: Commit, root: PathBuf) -> Self {
entries.sort();
Self {
id,
entries,
commit,
root,
}
}
pub fn object_id(&self) -> Oid {
self.id
}
pub fn commit(&self) -> &Commit {
&self.commit
}
pub fn last_commit(&self, repo: &Repository) -> Result<Commit, LastCommitError> {
repo.last_commit(&self.root, self.commit().clone())?
.ok_or(LastCommitError::Missing)
}
pub fn entries(&self) -> &Vec<Entry> {
&self.entries
}
}
#[cfg(feature = "serde")]
impl Serialize for Tree {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
const FIELDS: usize = 4;
let mut state = serializer.serialize_struct("Tree", FIELDS)?;
state.serialize_field("oid", &self.id)?;
state.serialize_field("entries", &self.entries)?;
state.serialize_field("commit", &self.commit)?;
state.serialize_field("root", &self.root)?;
state.end()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EntryKind {
Tree(Oid),
Blob(Oid),
Submodule { id: Oid, url: Option<Url> },
}
impl PartialOrd for EntryKind {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for EntryKind {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(EntryKind::Submodule { .. }, EntryKind::Submodule { .. }) => Ordering::Equal,
(EntryKind::Submodule { .. }, EntryKind::Tree(_)) => Ordering::Equal,
(EntryKind::Tree(_), EntryKind::Submodule { .. }) => Ordering::Equal,
(EntryKind::Tree(_), EntryKind::Tree(_)) => Ordering::Equal,
(EntryKind::Tree(_), EntryKind::Blob(_)) => Ordering::Less,
(EntryKind::Blob(_), EntryKind::Tree(_)) => Ordering::Greater,
(EntryKind::Submodule { .. }, EntryKind::Blob(_)) => Ordering::Less,
(EntryKind::Blob(_), EntryKind::Submodule { .. }) => Ordering::Greater,
(EntryKind::Blob(_), EntryKind::Blob(_)) => Ordering::Equal,
}
}
}
#[derive(Clone, Debug)]
pub struct Entry {
name: String,
entry: EntryKind,
path: PathBuf,
commit: Commit,
}
impl Entry {
pub(crate) fn new(name: String, path: PathBuf, entry: EntryKind, commit: Commit) -> Self {
Self {
name,
entry,
path,
commit,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn path(&self) -> &PathBuf {
&self.path
}
pub fn entry(&self) -> &EntryKind {
&self.entry
}
pub fn is_tree(&self) -> bool {
matches!(self.entry, EntryKind::Tree(_))
}
pub fn commit(&self) -> &Commit {
&self.commit
}
pub fn object_id(&self) -> Oid {
match self.entry {
EntryKind::Blob(id) => id,
EntryKind::Tree(id) => id,
EntryKind::Submodule { id, .. } => id,
}
}
pub fn last_commit(&self, repo: &Repository) -> Result<Commit, LastCommitError> {
repo.last_commit(&self.path, self.commit.clone())?
.ok_or(LastCommitError::Missing)
}
}
impl Ord for Entry {
fn cmp(&self, other: &Self) -> Ordering {
self.entry
.cmp(&other.entry)
.then(self.name.cmp(&other.name))
}
}
impl PartialOrd for Entry {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Entry {
fn eq(&self, other: &Self) -> bool {
self.entry == other.entry && self.name == other.name
}
}
impl Eq for Entry {}
impl From<fs::Entry> for EntryKind {
fn from(entry: fs::Entry) -> Self {
match entry {
fs::Entry::File(f) => EntryKind::Blob(f.id()),
fs::Entry::Directory(d) => EntryKind::Tree(d.id()),
fs::Entry::Submodule(u) => EntryKind::Submodule {
id: u.id(),
url: u.url().clone(),
},
}
}
}
#[cfg(feature = "serde")]
impl Serialize for Entry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
const FIELDS: usize = 5;
let mut state = serializer.serialize_struct("TreeEntry", FIELDS)?;
state.serialize_field("name", &self.name)?;
state.serialize_field(
"kind",
match self.entry {
EntryKind::Blob(_) => "blob",
EntryKind::Tree(_) => "tree",
EntryKind::Submodule { .. } => "submodule",
},
)?;
if let EntryKind::Submodule { url: Some(url), .. } = &self.entry {
state.serialize_field("url", url)?;
};
state.serialize_field("oid", &self.object_id())?;
state.serialize_field("commit", &self.path)?;
state.end()
}
}