1use std::cmp::Ordering;
5use std::path::PathBuf;
6
7use radicle_git_ext::Oid;
8#[cfg(feature = "serde")]
9use serde::{
10 ser::{SerializeStruct as _, Serializer},
11 Serialize,
12};
13use url::Url;
14
15use crate::{fs, Commit, Error, Repository};
16
17#[derive(Clone, Debug)]
21pub struct Tree {
22 id: Oid,
24 entries: Vec<Entry>,
26 commit: Commit,
28 root: PathBuf,
30}
31
32#[derive(Debug, thiserror::Error)]
33pub enum LastCommitError {
34 #[error(transparent)]
35 Repo(#[from] Error),
36 #[error("could not get the last commit for this entry")]
37 Missing,
38}
39
40impl Tree {
41 pub(crate) fn new(id: Oid, mut entries: Vec<Entry>, commit: Commit, root: PathBuf) -> Self {
43 entries.sort();
44 Self {
45 id,
46 entries,
47 commit,
48 root,
49 }
50 }
51
52 pub fn object_id(&self) -> Oid {
53 self.id
54 }
55
56 pub fn commit(&self) -> &Commit {
58 &self.commit
59 }
60
61 pub fn last_commit(&self, repo: &Repository) -> Result<Commit, LastCommitError> {
63 repo.last_commit(&self.root, self.commit().clone())?
64 .ok_or(LastCommitError::Missing)
65 }
66
67 pub fn entries(&self) -> &Vec<Entry> {
69 &self.entries
70 }
71}
72
73#[cfg(feature = "serde")]
74impl Serialize for Tree {
75 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102 where
103 S: Serializer,
104 {
105 const FIELDS: usize = 4;
106 let mut state = serializer.serialize_struct("Tree", FIELDS)?;
107 state.serialize_field("oid", &self.id)?;
108 state.serialize_field("entries", &self.entries)?;
109 state.serialize_field("commit", &self.commit)?;
110 state.serialize_field("root", &self.root)?;
111 state.end()
112 }
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub enum EntryKind {
117 Tree(Oid),
118 Blob(Oid),
119 Submodule { id: Oid, url: Option<Url> },
120}
121
122impl PartialOrd for EntryKind {
123 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
124 Some(self.cmp(other))
125 }
126}
127
128impl Ord for EntryKind {
129 fn cmp(&self, other: &Self) -> Ordering {
130 match (self, other) {
131 (EntryKind::Submodule { .. }, EntryKind::Submodule { .. }) => Ordering::Equal,
132 (EntryKind::Submodule { .. }, EntryKind::Tree(_)) => Ordering::Equal,
133 (EntryKind::Tree(_), EntryKind::Submodule { .. }) => Ordering::Equal,
134 (EntryKind::Tree(_), EntryKind::Tree(_)) => Ordering::Equal,
135 (EntryKind::Tree(_), EntryKind::Blob(_)) => Ordering::Less,
136 (EntryKind::Blob(_), EntryKind::Tree(_)) => Ordering::Greater,
137 (EntryKind::Submodule { .. }, EntryKind::Blob(_)) => Ordering::Less,
138 (EntryKind::Blob(_), EntryKind::Submodule { .. }) => Ordering::Greater,
139 (EntryKind::Blob(_), EntryKind::Blob(_)) => Ordering::Equal,
140 }
141 }
142}
143
144#[derive(Clone, Debug)]
153pub struct Entry {
154 name: String,
155 entry: EntryKind,
156 path: PathBuf,
157 commit: Commit,
159}
160
161impl Entry {
162 pub(crate) fn new(name: String, path: PathBuf, entry: EntryKind, commit: Commit) -> Self {
163 Self {
164 name,
165 entry,
166 path,
167 commit,
168 }
169 }
170
171 pub fn name(&self) -> &str {
172 &self.name
173 }
174
175 pub fn path(&self) -> &PathBuf {
177 &self.path
178 }
179
180 pub fn entry(&self) -> &EntryKind {
181 &self.entry
182 }
183
184 pub fn is_tree(&self) -> bool {
185 matches!(self.entry, EntryKind::Tree(_))
186 }
187
188 pub fn commit(&self) -> &Commit {
189 &self.commit
190 }
191
192 pub fn object_id(&self) -> Oid {
193 match self.entry {
194 EntryKind::Blob(id) => id,
195 EntryKind::Tree(id) => id,
196 EntryKind::Submodule { id, .. } => id,
197 }
198 }
199
200 pub fn last_commit(&self, repo: &Repository) -> Result<Commit, LastCommitError> {
202 repo.last_commit(&self.path, self.commit.clone())?
203 .ok_or(LastCommitError::Missing)
204 }
205}
206
207impl Ord for Entry {
209 fn cmp(&self, other: &Self) -> Ordering {
210 self.entry
211 .cmp(&other.entry)
212 .then(self.name.cmp(&other.name))
213 }
214}
215
216impl PartialOrd for Entry {
217 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
218 Some(self.cmp(other))
219 }
220}
221
222impl PartialEq for Entry {
223 fn eq(&self, other: &Self) -> bool {
224 self.entry == other.entry && self.name == other.name
225 }
226}
227
228impl Eq for Entry {}
229
230impl From<fs::Entry> for EntryKind {
231 fn from(entry: fs::Entry) -> Self {
232 match entry {
233 fs::Entry::File(f) => EntryKind::Blob(f.id()),
234 fs::Entry::Directory(d) => EntryKind::Tree(d.id()),
235 fs::Entry::Submodule(u) => EntryKind::Submodule {
236 id: u.id(),
237 url: u.url().clone(),
238 },
239 }
240 }
241}
242
243#[cfg(feature = "serde")]
244impl Serialize for Entry {
245 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
269 where
270 S: Serializer,
271 {
272 const FIELDS: usize = 5;
273 let mut state = serializer.serialize_struct("TreeEntry", FIELDS)?;
274 state.serialize_field("name", &self.name)?;
275 state.serialize_field(
276 "kind",
277 match self.entry {
278 EntryKind::Blob(_) => "blob",
279 EntryKind::Tree(_) => "tree",
280 EntryKind::Submodule { .. } => "submodule",
281 },
282 )?;
283 if let EntryKind::Submodule { url: Some(url), .. } = &self.entry {
284 state.serialize_field("url", url)?;
285 };
286 state.serialize_field("oid", &self.object_id())?;
287 state.serialize_field("commit", &self.path)?;
288 state.end()
289 }
290}