| 1 | |
|
| 2 | |
|
| 3 | | use rand::RngCore; |
| 4 | | use std::fmt; |
| 5 | |
|
| 6 | | macro_rules! id128 { |
| 7 | | ($name:ident, $doc:literal) => { |
| 8 | | #[doc = $doc] |
| 9 | | #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| 10 | | pub struct $name(pub [u8; 16]); |
| 11 | |
|
| 12 | | impl $name { |
| 13 | | pub const ZERO: $name = $name([0; 16]); |
| 14 | |
|
| 15 | | |
| 16 | 10702× | pub fn random() -> $name { |
| 17 | 10702× | let mut b = [0u8; 16]; |
| 18 | 10702× | rand::rngs::OsRng.fill_bytes(&mut b); |
| 19 | 10702× | $name(b) |
| 20 | 10702× | } |
| 21 | 51071× | pub fn to_hex(&self) -> String { |
| 22 | 51071× | hex::encode(self.0) |
| 23 | 51071× | } |
| 24 | 2050× | pub fn from_hex(s: &str) -> Option<$name> { |
| 25 | 2050× | let v = hex::decode(s).ok()?; |
| 26 | 2050× | Some($name(v.try_into().ok()?)) |
| 27 | 2050× | } |
| 28 | 220495× | pub fn from_slice(b: &[u8]) -> Option<$name> { |
| 29 | 220495× | Some($name(b.try_into().ok()?)) |
| 30 | 220495× | } |
| 31 | 20711× | pub fn short(&self, n: usize) -> String { |
| 32 | 20711× | let h = self.to_hex(); |
| 33 | 20711× | h[..n.min(h.len())].to_string() |
| 34 | 20711× | } |
| 35 | 26× | pub fn starts_with_hex(&self, prefix: &str) -> bool { |
| 36 | 26× | self.to_hex().starts_with(prefix) |
| 37 | 26× | } |
| 38 | | } |
| 39 | |
|
| 40 | | impl fmt::Display for $name { |
| 41 | 0× | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 42 | 0× | f.write_str(&self.to_hex()) |
| 43 | 0× | } |
| 44 | | } |
| 45 | | impl fmt::Debug for $name { |
| 46 | 19304× | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 47 | 19304× | write!(f, "{}({})", stringify!($name), self.short(8)) |
| 48 | 19304× | } |
| 49 | | } |
| 50 | | }; |
| 51 | | } |
| 52 | |
|
| 53 | | id128!( |
| 54 | | ChangeId, |
| 55 | | "Stable identity of a change across rewrites (spec §6.2)." |
| 56 | | ); |
| 57 | | id128!(NodeId, "Stable identity of a file, directory, or symlink across renames (spec §7.1). `NodeId::ZERO` is the root directory."); |
| 58 | | id128!( |
| 59 | | WorkspaceId, |
| 60 | | "Identity of a checkout directory (spec §12.2)." |
| 61 | | ); |
| 62 | |
|
| 63 | | impl NodeId { |
| 64 | | pub const ROOT: NodeId = NodeId::ZERO; |
| 65 | 6829228× | pub fn is_root(&self) -> bool { |
| 66 | 6829228× | self.is_zero() |
| 67 | 6829228× | } |
| 68 | | } |
| 69 | |
|
| 70 | | impl ChangeId { |
| 71 | 0× | pub fn is_zero(&self) -> bool { |
| 72 | 0× | *self == Self::ZERO |
| 73 | 0× | } |
| 74 | | } |
| 75 | | impl NodeId { |
| 76 | 6829228× | pub fn is_zero(&self) -> bool { |
| 77 | 6829228× | *self == Self::ZERO |
| 78 | 6829228× | } |
| 79 | | } |
| 80 | | impl WorkspaceId { |
| 81 | 0× | pub fn is_zero(&self) -> bool { |
| 82 | 0× | *self == Self::ZERO |
| 83 | 0× | } |
| 84 | | } |
| 85 | |
|
| 86 | | #[cfg(test)] |
| 87 | | mod tests { |
| 88 | | use super::*; |
| 89 | |
|
| 90 | | #[test] |
| 91 | 1× | fn random_ids_differ_and_round_trip() { |
| 92 | 1× | let a = ChangeId::random(); |
| 93 | 1× | let b = ChangeId::random(); |
| 94 | 1× | assert_ne!(a, b); |
| 95 | 1× | assert_eq!(ChangeId::from_hex(&a.to_hex()), Some(a)); |
| 96 | 1× | assert!(NodeId::ROOT.is_root()); |
| 97 | 1× | assert!(!NodeId::random().is_root()); |
| 98 | 1× | } |
| 99 | | } |