just playing with tangled
at ig/vimdiffwarn 278 lines 9.1 kB view raw
1// Copyright 2020-2024 The Jujutsu Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15#![allow(missing_docs)] 16 17pub trait ObjectId { 18 fn object_type(&self) -> String; 19 fn as_bytes(&self) -> &[u8]; 20 fn to_bytes(&self) -> Vec<u8>; 21 fn hex(&self) -> String; 22} 23 24// Defines a new struct type with visibility `vis` and name `ident` containing 25// a single Vec<u8> used to store an identifier (typically the output of a hash 26// function) as bytes. Types defined using this macro automatically implement 27// the `ObjectId` and `ContentHash` traits. 28// Documentation comments written inside the macro definition and will be 29// captured and associated with the type defined by the macro. 30// 31// Example: 32// ```no_run 33// id_type!( 34// /// My favorite id type. 35// pub MyId { hex() } 36// ); 37// ``` 38macro_rules! id_type { 39 ( $(#[$attr:meta])* 40 $vis:vis $name:ident { $hex_method:ident() } 41 ) => { 42 $(#[$attr])* 43 #[derive(ContentHash, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] 44 $vis struct $name(Vec<u8>); 45 $crate::object_id::impl_id_type!($name, $hex_method); 46 }; 47} 48 49macro_rules! impl_id_type { 50 ($name:ident, $hex_method:ident) => { 51 impl $name { 52 pub fn new(value: Vec<u8>) -> Self { 53 Self(value) 54 } 55 56 pub fn from_bytes(bytes: &[u8]) -> Self { 57 Self(bytes.to_vec()) 58 } 59 60 /// Parses the given hex string into an ObjectId. 61 /// 62 /// The given string must be valid. A static str is required to 63 /// prevent API misuse. 64 pub fn from_hex(hex: &'static str) -> Self { 65 Self::try_from_hex(hex).unwrap() 66 } 67 68 /// Parses the given hex string into an ObjectId. 69 pub fn try_from_hex(hex: &str) -> Result<Self, hex::FromHexError> { 70 hex::decode(hex).map(Self) 71 } 72 } 73 74 impl std::fmt::Debug for $name { 75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 76 // TODO: should we use $hex_method here? 77 f.debug_tuple(stringify!($name)).field(&self.hex()).finish() 78 } 79 } 80 81 impl std::fmt::Display for $name { 82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 83 f.pad(&self.$hex_method()) 84 } 85 } 86 87 impl crate::object_id::ObjectId for $name { 88 fn object_type(&self) -> String { 89 stringify!($name) 90 .strip_suffix("Id") 91 .unwrap() 92 .to_ascii_lowercase() 93 .to_string() 94 } 95 96 fn as_bytes(&self) -> &[u8] { 97 &self.0 98 } 99 100 fn to_bytes(&self) -> Vec<u8> { 101 self.0.clone() 102 } 103 104 fn hex(&self) -> String { 105 hex::encode(&self.0) 106 } 107 } 108 }; 109} 110 111pub(crate) use id_type; 112pub(crate) use impl_id_type; 113 114/// An identifier prefix (typically from a type implementing the [`ObjectId`] 115/// trait) with facilities for converting between bytes and a hex string. 116#[derive(Debug, Clone, PartialEq, Eq)] 117pub struct HexPrefix { 118 // For odd-length prefixes, the lower 4 bits of the last byte are 119 // zero-filled (e.g. the prefix "abc" is stored in two bytes as "abc0"). 120 min_prefix_bytes: Vec<u8>, 121 has_odd_byte: bool, 122} 123 124impl HexPrefix { 125 /// Returns a new `HexPrefix` or `None` if `prefix` cannot be decoded from 126 /// hex to bytes. 127 pub fn new(prefix: &str) -> Option<HexPrefix> { 128 let has_odd_byte = prefix.len() & 1 != 0; 129 let min_prefix_bytes = if has_odd_byte { 130 hex::decode(prefix.to_owned() + "0").ok()? 131 } else { 132 hex::decode(prefix).ok()? 133 }; 134 Some(HexPrefix { 135 min_prefix_bytes, 136 has_odd_byte, 137 }) 138 } 139 140 pub fn from_bytes(bytes: &[u8]) -> Self { 141 HexPrefix { 142 min_prefix_bytes: bytes.to_owned(), 143 has_odd_byte: false, 144 } 145 } 146 147 pub fn hex(&self) -> String { 148 let mut hex_string = hex::encode(&self.min_prefix_bytes); 149 if self.has_odd_byte { 150 hex_string.pop().unwrap(); 151 } 152 hex_string 153 } 154 155 /// Minimum bytes that would match this prefix. (e.g. "abc0" for "abc") 156 /// 157 /// Use this to partition a sorted slice, and test `matches(id)` from there. 158 pub fn min_prefix_bytes(&self) -> &[u8] { 159 &self.min_prefix_bytes 160 } 161 162 /// Returns the bytes representation if this prefix can be a full id. 163 pub fn as_full_bytes(&self) -> Option<&[u8]> { 164 (!self.has_odd_byte).then_some(&self.min_prefix_bytes) 165 } 166 167 fn split_odd_byte(&self) -> (Option<u8>, &[u8]) { 168 if self.has_odd_byte { 169 let (&odd, prefix) = self.min_prefix_bytes.split_last().unwrap(); 170 (Some(odd), prefix) 171 } else { 172 (None, &self.min_prefix_bytes) 173 } 174 } 175 176 /// Returns whether the stored prefix matches the prefix of `id`. 177 pub fn matches<Q: ObjectId>(&self, id: &Q) -> bool { 178 let id_bytes = id.as_bytes(); 179 let (maybe_odd, prefix) = self.split_odd_byte(); 180 if id_bytes.starts_with(prefix) { 181 if let Some(odd) = maybe_odd { 182 matches!(id_bytes.get(prefix.len()), Some(v) if v & 0xf0 == odd) 183 } else { 184 true 185 } 186 } else { 187 false 188 } 189 } 190} 191 192/// The result of a prefix search. 193#[derive(Debug, Clone, PartialEq, Eq)] 194pub enum PrefixResolution<T> { 195 NoMatch, 196 SingleMatch(T), 197 AmbiguousMatch, 198} 199 200impl<T> PrefixResolution<T> { 201 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> PrefixResolution<U> { 202 match self { 203 PrefixResolution::NoMatch => PrefixResolution::NoMatch, 204 PrefixResolution::SingleMatch(x) => PrefixResolution::SingleMatch(f(x)), 205 PrefixResolution::AmbiguousMatch => PrefixResolution::AmbiguousMatch, 206 } 207 } 208} 209 210impl<T: Clone> PrefixResolution<T> { 211 pub fn plus(&self, other: &PrefixResolution<T>) -> PrefixResolution<T> { 212 match (self, other) { 213 (PrefixResolution::NoMatch, other) => other.clone(), 214 (local, PrefixResolution::NoMatch) => local.clone(), 215 (PrefixResolution::AmbiguousMatch, _) => PrefixResolution::AmbiguousMatch, 216 (_, PrefixResolution::AmbiguousMatch) => PrefixResolution::AmbiguousMatch, 217 (PrefixResolution::SingleMatch(_), PrefixResolution::SingleMatch(_)) => { 218 PrefixResolution::AmbiguousMatch 219 } 220 } 221 } 222} 223 224#[cfg(test)] 225mod tests { 226 use super::*; 227 use crate::backend::ChangeId; 228 use crate::backend::CommitId; 229 230 #[test] 231 fn test_display_object_id() { 232 let commit_id = CommitId::from_hex("deadbeef0123"); 233 assert_eq!(format!("{commit_id}"), "deadbeef0123"); 234 assert_eq!(format!("{commit_id:.6}"), "deadbe"); 235 236 let change_id = ChangeId::from_hex("deadbeef0123"); 237 assert_eq!(format!("{change_id}"), "mlpmollkzyxw"); 238 assert_eq!(format!("{change_id:.6}"), "mlpmol"); 239 } 240 241 #[test] 242 fn test_hex_prefix_prefixes() { 243 let prefix = HexPrefix::new("").unwrap(); 244 assert_eq!(prefix.min_prefix_bytes(), b""); 245 246 let prefix = HexPrefix::new("1").unwrap(); 247 assert_eq!(prefix.min_prefix_bytes(), b"\x10"); 248 249 let prefix = HexPrefix::new("12").unwrap(); 250 assert_eq!(prefix.min_prefix_bytes(), b"\x12"); 251 252 let prefix = HexPrefix::new("123").unwrap(); 253 assert_eq!(prefix.min_prefix_bytes(), b"\x12\x30"); 254 255 let bad_prefix = HexPrefix::new("0x123"); 256 assert_eq!(bad_prefix, None); 257 258 let bad_prefix = HexPrefix::new("foobar"); 259 assert_eq!(bad_prefix, None); 260 } 261 262 #[test] 263 fn test_hex_prefix_matches() { 264 let id = CommitId::from_hex("1234"); 265 266 assert!(HexPrefix::new("").unwrap().matches(&id)); 267 assert!(HexPrefix::new("1").unwrap().matches(&id)); 268 assert!(HexPrefix::new("12").unwrap().matches(&id)); 269 assert!(HexPrefix::new("123").unwrap().matches(&id)); 270 assert!(HexPrefix::new("1234").unwrap().matches(&id)); 271 assert!(!HexPrefix::new("12345").unwrap().matches(&id)); 272 273 assert!(!HexPrefix::new("a").unwrap().matches(&id)); 274 assert!(!HexPrefix::new("1a").unwrap().matches(&id)); 275 assert!(!HexPrefix::new("12a").unwrap().matches(&id)); 276 assert!(!HexPrefix::new("123a").unwrap().matches(&id)); 277 } 278}