diff --git a/radicle-surf/src/object/tree.rs b/radicle-surf/src/object/tree.rs index ee142a0..3378edd 100644 --- a/radicle-surf/src/object/tree.rs +++ b/radicle-surf/src/object/tree.rs @@ -36,14 +36,16 @@ use crate::{fs, object::commit}; pub struct Tree { /// The object id of this tree. id: Oid, + /// The first descendant entries for this tree. entries: Vec, /// The commit object that created this tree object. commit: commit::Header, } impl Tree { - /// Creates a new tree. - pub(crate) fn new(id: Oid, entries: Vec, commit: commit::Header) -> Self { + /// Creates a new tree, ensuring the `entries` are sorted. + pub(crate) fn new(id: Oid, mut entries: Vec, commit: commit::Header) -> Self { + entries.sort(); Self { id, entries, @@ -106,13 +108,40 @@ impl Serialize for Tree { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Entry { Tree(Oid), Blob(Oid), } -/// Entry in a Tree result. +impl PartialOrd for Entry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Entry { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (Entry::Tree(_), Entry::Tree(_)) => Ordering::Equal, + (Entry::Tree(_), Entry::Blob(_)) => Ordering::Less, + (Entry::Blob(_), Entry::Tree(_)) => Ordering::Greater, + (Entry::Blob(_), Entry::Blob(_)) => Ordering::Equal, + } + } +} + +/// An entry that can be found in a tree. +/// +/// Each entry consists of a [`TreeEntry::name`], its kind -- given as [`Entry`] +/// -- and the header of its associated [`TreeEntry::commit`]. +/// +/// # Ordering +/// +/// The ordering of a `TreeEntry` is first by its [`Entry`] kind where +/// [`Entry::Tree`]s come before [`Entry::Blob`]. If both kinds are +/// equal then they are next compared by the lexicographical ordering +/// of their `name`s. #[derive(Clone, Debug)] pub struct TreeEntry { name: String, @@ -158,7 +187,9 @@ impl TreeEntry { // To support `sort`. impl Ord for TreeEntry { fn cmp(&self, other: &Self) -> Ordering { - self.name.cmp(&other.name) + self.entry + .cmp(&other.entry) + .then(self.name.cmp(&other.name)) } } @@ -170,7 +201,7 @@ impl PartialOrd for TreeEntry { impl PartialEq for TreeEntry { fn eq(&self, other: &Self) -> bool { - self.name == other.name + self.entry == other.entry && self.name == other.name } } @@ -188,7 +219,7 @@ impl From for Entry { #[cfg(feature = "serde")] impl Serialize for TreeEntry { /// Sample output: - /// ``` + /// ```json /// { /// "kind": "blob", /// "lastCommit": { diff --git a/radicle-surf/t/src/source.rs b/radicle-surf/t/src/source.rs index f0cfd3c..7fb75e2 100644 --- a/radicle-surf/t/src/source.rs +++ b/radicle-surf/t/src/source.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use git_ref_format::refname; use radicle_surf::git::Repository; use serde_json::json; @@ -139,3 +141,27 @@ fn repo_blob() { // Verify as_bytes. assert_eq!(content.as_bytes().len(), content.size()); } + +#[test] +fn tree_ordering() { + let repo = Repository::open(GIT_PLATINUM).unwrap(); + let tree = repo + .tree(refname!("refs/heads/master"), &PathBuf::new()) + .unwrap(); + assert_eq!( + tree.entries() + .iter() + .map(|entry| entry.name().to_string()) + .collect::>(), + vec![ + "bin".to_string(), + "special".to_string(), + "src".to_string(), + "text".to_string(), + "this".to_string(), + ".i-am-well-hidden".to_string(), + ".i-too-am-hidden".to_string(), + "README.md".to_string(), + ] + ); +}