1use thiserror::Error;
9
10use crate::NodeType;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct UntypedEdgeLabel(pub(crate) u64);
15
16impl From<u64> for UntypedEdgeLabel {
17 #[inline(always)]
18 fn from(n: u64) -> UntypedEdgeLabel {
19 UntypedEdgeLabel(n)
20 }
21}
22
23#[derive(Error, Debug, Clone, PartialEq, Eq, Hash)]
24pub enum EdgeTypingError {
25 #[error("{src} -> {dst} arcs cannot have labels")]
26 NodeTypes { src: NodeType, dst: NodeType },
27}
28
29impl UntypedEdgeLabel {
30 pub fn for_edge_type(
31 &self,
32 src: NodeType,
33 dst: NodeType,
34 transpose_graph: bool,
35 ) -> Result<EdgeLabel, EdgeTypingError> {
36 use crate::NodeType::*;
37
38 let (src, dst) = if transpose_graph {
39 (dst, src)
40 } else {
41 (src, dst)
42 };
43
44 match (src, dst) {
45 (Snapshot, _) => Ok(EdgeLabel::Branch(self.0.into())),
46 (Directory, _) => Ok(EdgeLabel::DirEntry(self.0.into())),
47 (Origin, Snapshot) => Ok(EdgeLabel::Visit(self.0.into())),
48 _ => Err(EdgeTypingError::NodeTypes { src, dst }),
49 }
50 }
51}
52
53impl From<EdgeLabel> for UntypedEdgeLabel {
54 #[inline(always)]
55 fn from(label: EdgeLabel) -> Self {
56 UntypedEdgeLabel(match label {
57 EdgeLabel::Branch(branch) => branch.0,
58 EdgeLabel::DirEntry(dir_entry) => dir_entry.0,
59 EdgeLabel::Visit(visit) => visit.0,
60 })
61 }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
65pub enum EdgeLabel {
66 Branch(Branch),
68 DirEntry(DirEntry),
70 Visit(Visit),
72}
73
74macro_rules! impl_edgelabel_convert {
75 ( $variant:ident ( $inner:ty ) ) => {
76 impl From<$inner> for EdgeLabel {
77 fn from(v: $inner) -> EdgeLabel {
78 EdgeLabel::$variant(v)
79 }
80 }
81
82 impl TryFrom<EdgeLabel> for $inner {
83 type Error = ();
84
85 fn try_from(label: EdgeLabel) -> Result<$inner, Self::Error> {
86 match label {
87 EdgeLabel::$variant(v) => Ok(v),
88 _ => Err(()),
89 }
90 }
91 }
92
93 impl From<UntypedEdgeLabel> for $inner {
94 fn from(label: UntypedEdgeLabel) -> $inner {
95 <$inner>::from(label.0)
96 }
97 }
98 };
99}
100
101impl_edgelabel_convert!(Branch(Branch));
102impl_edgelabel_convert!(DirEntry(DirEntry));
103impl_edgelabel_convert!(Visit(Visit));
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
106pub enum VisitStatus {
107 Full,
108 Partial,
109}
110
111#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
112pub struct Visit(pub(crate) u64);
113
114impl From<u64> for Visit {
115 #[inline(always)]
116 fn from(n: u64) -> Visit {
117 Visit(n)
118 }
119}
120
121impl Visit {
122 pub fn new(status: VisitStatus, timestamp: u64) -> Option<Visit> {
126 let is_full = match status {
127 VisitStatus::Full => 1u64,
128 VisitStatus::Partial => 0,
129 };
130 let reserved_bits = 0b1000u64;
131 timestamp
132 .checked_shl(5)
133 .map(|shifted_timestamp| Visit(shifted_timestamp | (is_full << 4) | reserved_bits))
134 }
135
136 #[inline(always)]
137 pub fn timestamp(&self) -> u64 {
138 self.0 >> 5
139 }
140
141 #[inline(always)]
142 pub fn status(&self) -> VisitStatus {
143 if self.0 & 0b10000 != 0 {
144 VisitStatus::Full
145 } else {
146 VisitStatus::Partial
147 }
148 }
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
152pub struct Branch(pub(crate) u64);
153
154impl From<u64> for Branch {
155 #[inline(always)]
156 fn from(n: u64) -> Branch {
157 Branch(n)
158 }
159}
160
161impl Branch {
162 pub fn new(label_name_id: LabelNameId) -> Option<Branch> {
166 label_name_id.0.checked_shl(3).map(Branch)
167 }
168
169 #[deprecated(since = "7.0.0", note = "filename_id was renamed label_name_id")]
170 #[inline(always)]
172 pub fn filename_id(self) -> LabelNameId {
173 self.label_name_id()
174 }
175
176 #[inline(always)]
180 pub fn label_name_id(self) -> LabelNameId {
181 LabelNameId(self.0 >> 3)
182 }
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
186pub struct DirEntry(pub(crate) u64);
187
188impl From<u64> for DirEntry {
189 #[inline(always)]
190 fn from(n: u64) -> DirEntry {
191 DirEntry(n)
192 }
193}
194
195impl DirEntry {
196 pub fn new(permission: Permission, label_name_id: LabelNameId) -> Option<DirEntry> {
200 label_name_id
201 .0
202 .checked_shl(3)
203 .map(|shifted_label_name_id| DirEntry(shifted_label_name_id | (permission as u64)))
204 }
205
206 #[deprecated(since = "7.0.0", note = "filename_id was renamed label_name_id")]
207 #[inline(always)]
209 pub fn filename_id(self) -> LabelNameId {
210 self.label_name_id()
211 }
212
213 #[inline(always)]
217 pub fn label_name_id(self) -> LabelNameId {
218 LabelNameId(self.0 >> 3)
219 }
220
221 pub fn permission(self) -> Option<Permission> {
226 use Permission::*;
227 match self.0 & 0b111 {
228 0 => Some(None),
229 1 => Some(Content),
230 2 => Some(ExecutableContent),
231 3 => Some(Symlink),
232 4 => Some(Directory),
233 5 => Some(Revision),
234 _ => Option::None,
235 }
236 }
237
238 pub unsafe fn permission_unchecked(self) -> Permission {
245 use Permission::*;
246 match self.0 & 0b111 {
247 0 => None,
248 1 => Content,
249 2 => ExecutableContent,
250 3 => Symlink,
251 4 => Directory,
252 5 => Revision,
253 n => unreachable!("{} mode", n),
254 }
255 }
256}
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
259pub struct LabelNameId(pub u64);
260
261#[deprecated(since = "7.0.0", note = "FilenameId was renamed to LabelNameId")]
262pub type FilenameId = LabelNameId;
263
264#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
265#[repr(u8)]
266pub enum Permission {
267 None = 0,
268 Content = 1,
269 ExecutableContent = 2,
270 Symlink = 3,
271 Directory = 4,
272 Revision = 5,
273}
274
275impl Permission {
276 #[inline(always)]
282 pub fn to_git(self) -> u16 {
283 use Permission::*;
284 match self {
285 None => 0,
286 Content => 0o100644,
287 ExecutableContent => 0o100755,
288 Symlink => 0o120000,
289 Directory => 0o040000,
290 Revision => 0o160000,
291 }
292 }
293
294 pub fn from_git(mode: u16) -> Option<Permission> {
298 use Permission::*;
299 match mode {
300 0 => Some(None),
301 0o100644 => Some(Content),
302 0o100755 => Some(ExecutableContent),
303 0o120000 => Some(Symlink),
304 0o040000 => Some(Directory),
305 0o160000 => Some(Revision),
306 _ => Option::None,
307 }
308 }
309
310 pub unsafe fn from_git_unchecked(mode: u16) -> Permission {
318 use Permission::*;
319 match mode {
320 0 => None,
321 0o100644 => Content,
322 0o100755 => ExecutableContent,
323 0o120000 => Symlink,
324 0o040000 => Directory,
325 0o160000 => Revision,
326 _ => unreachable!("{} mode", mode),
327 }
328 }
329}