1use std::borrow::Borrow;
35use std::fmt;
36use std::fmt::Display;
37use std::ops::Deref;
38
39use ref_cast::RefCastCustom;
40use ref_cast::ref_cast_custom;
41
42use crate::content_hash::ContentHash;
43use crate::symbol_util::format_string;
44
45#[derive(Clone, ContentHash, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
51pub struct GitRefNameBuf(String);
52
53#[derive(ContentHash, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, RefCastCustom)]
58#[repr(transparent)]
59pub struct GitRefName(str);
60
61#[derive(Clone, ContentHash, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
67pub struct RefNameBuf(String);
68
69#[derive(ContentHash, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, RefCastCustom)]
74#[repr(transparent)]
75pub struct RefName(str);
76
77#[derive(Clone, ContentHash, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
83pub struct RemoteNameBuf(String);
84
85#[derive(ContentHash, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, RefCastCustom)]
90#[repr(transparent)]
91pub struct RemoteName(str);
92
93#[derive(Clone, ContentHash, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Serialize)]
99#[serde(transparent)]
100pub struct WorkspaceNameBuf(String);
101
102#[derive(
107 ContentHash, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, RefCastCustom, serde::Serialize,
108)]
109#[serde(transparent)]
110#[repr(transparent)]
111pub struct WorkspaceName(str);
112
113macro_rules! impl_partial_eq {
114 ($borrowed_ty:ty, $lhs:ty, $rhs:ty) => {
115 impl PartialEq<$rhs> for $lhs {
116 fn eq(&self, other: &$rhs) -> bool {
117 <$borrowed_ty as PartialEq>::eq(self, other)
118 }
119 }
120
121 impl PartialEq<$lhs> for $rhs {
122 fn eq(&self, other: &$lhs) -> bool {
123 <$borrowed_ty as PartialEq>::eq(self, other)
124 }
125 }
126 };
127}
128
129macro_rules! impl_partial_eq_str {
130 ($borrowed_ty:ty, $lhs:ty, $rhs:ty) => {
131 impl PartialEq<$rhs> for $lhs {
132 fn eq(&self, other: &$rhs) -> bool {
133 <$borrowed_ty as PartialEq>::eq(self, other.as_ref())
134 }
135 }
136
137 impl PartialEq<$lhs> for $rhs {
138 fn eq(&self, other: &$lhs) -> bool {
139 <$borrowed_ty as PartialEq>::eq(self.as_ref(), other)
140 }
141 }
142 };
143}
144
145macro_rules! impl_name_type {
146 ($owned_ty:ident, $borrowed_ty:ident) => {
147 impl $owned_ty {
148 pub fn into_string(self) -> String {
150 self.0
151 }
152 }
153
154 impl $borrowed_ty {
155 #[ref_cast_custom]
157 pub const fn new(name: &str) -> &Self;
158
159 pub const fn as_str(&self) -> &str {
161 &self.0
162 }
163
164 pub fn as_symbol(&self) -> &RefSymbol {
166 RefSymbol::new(&self.0)
167 }
168 }
169
170 impl From<String> for $owned_ty {
173 fn from(value: String) -> Self {
174 $owned_ty(value)
175 }
176 }
177
178 impl From<&String> for $owned_ty {
179 fn from(value: &String) -> Self {
180 $owned_ty(value.clone())
181 }
182 }
183
184 impl From<&str> for $owned_ty {
185 fn from(value: &str) -> Self {
186 $owned_ty(value.to_owned())
187 }
188 }
189
190 impl From<&$owned_ty> for $owned_ty {
193 fn from(value: &$owned_ty) -> Self {
194 value.clone()
195 }
196 }
197
198 impl From<&$borrowed_ty> for $owned_ty {
199 fn from(value: &$borrowed_ty) -> Self {
200 value.to_owned()
201 }
202 }
203
204 impl AsRef<$borrowed_ty> for String {
207 fn as_ref(&self) -> &$borrowed_ty {
208 $borrowed_ty::new(self)
209 }
210 }
211
212 impl AsRef<$borrowed_ty> for str {
213 fn as_ref(&self) -> &$borrowed_ty {
214 $borrowed_ty::new(self)
215 }
216 }
217
218 impl From<$owned_ty> for String {
221 fn from(value: $owned_ty) -> Self {
222 value.0
223 }
224 }
225
226 impl From<&$owned_ty> for String {
227 fn from(value: &$owned_ty) -> Self {
228 value.0.clone()
229 }
230 }
231
232 impl From<&$borrowed_ty> for String {
233 fn from(value: &$borrowed_ty) -> Self {
234 value.0.to_owned()
235 }
236 }
237
238 impl AsRef<str> for $owned_ty {
239 fn as_ref(&self) -> &str {
240 self.as_str()
241 }
242 }
243
244 impl AsRef<str> for $borrowed_ty {
245 fn as_ref(&self) -> &str {
246 self.as_str()
247 }
248 }
249
250 impl AsRef<$borrowed_ty> for $owned_ty {
253 fn as_ref(&self) -> &$borrowed_ty {
254 self
255 }
256 }
257
258 impl AsRef<$borrowed_ty> for $borrowed_ty {
259 fn as_ref(&self) -> &$borrowed_ty {
260 self
261 }
262 }
263
264 impl Borrow<$borrowed_ty> for $owned_ty {
265 fn borrow(&self) -> &$borrowed_ty {
266 self
267 }
268 }
269
270 impl Deref for $owned_ty {
271 type Target = $borrowed_ty;
272
273 fn deref(&self) -> &Self::Target {
274 $borrowed_ty::new(&self.0)
275 }
276 }
277
278 impl ToOwned for $borrowed_ty {
279 type Owned = $owned_ty;
280
281 fn to_owned(&self) -> Self::Owned {
282 $owned_ty(self.0.to_owned())
283 }
284 }
285
286 impl_partial_eq!($borrowed_ty, $owned_ty, $borrowed_ty);
288 impl_partial_eq!($borrowed_ty, $owned_ty, &$borrowed_ty);
289
290 impl_partial_eq_str!($borrowed_ty, $owned_ty, str);
292 impl_partial_eq_str!($borrowed_ty, $owned_ty, &str);
293 impl_partial_eq_str!($borrowed_ty, $owned_ty, String);
294 impl_partial_eq_str!($borrowed_ty, $borrowed_ty, str);
295 impl_partial_eq_str!($borrowed_ty, $borrowed_ty, &str);
296 impl_partial_eq_str!($borrowed_ty, $borrowed_ty, String);
297 impl_partial_eq_str!($borrowed_ty, &$borrowed_ty, str);
298 impl_partial_eq_str!($borrowed_ty, &$borrowed_ty, String);
299 };
300}
301
302impl_name_type!(GitRefNameBuf, GitRefName);
303impl_name_type!(RefNameBuf, RefName);
306impl_name_type!(RemoteNameBuf, RemoteName);
307impl_name_type!(WorkspaceNameBuf, WorkspaceName);
308
309impl RefName {
310 pub fn to_remote_symbol<'a>(&'a self, remote: &'a RemoteName) -> RemoteRefSymbol<'a> {
312 RemoteRefSymbol { name: self, remote }
313 }
314}
315
316impl WorkspaceName {
317 pub const DEFAULT: &Self = Self::new("default");
319}
320
321#[derive(Debug, RefCastCustom)]
325#[repr(transparent)]
326pub struct RefSymbol(str);
327
328impl RefSymbol {
329 #[ref_cast_custom]
331 const fn new(name: &str) -> &Self;
332}
333
334impl Display for RefSymbol {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 if is_safe_identifier(&self.0) {
337 f.pad(&self.0)
338 } else {
339 f.pad(&format_string(&self.0))
340 }
341 }
342}
343
344#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
349pub struct RemoteRefSymbolBuf {
350 pub name: RefNameBuf,
352 pub remote: RemoteNameBuf,
354}
355
356impl RemoteRefSymbolBuf {
357 pub fn as_ref(&self) -> RemoteRefSymbol<'_> {
359 RemoteRefSymbol {
360 name: &self.name,
361 remote: &self.remote,
362 }
363 }
364}
365
366#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
371pub struct RemoteRefSymbol<'a> {
372 pub name: &'a RefName,
374 pub remote: &'a RemoteName,
376}
377
378impl RemoteRefSymbol<'_> {
379 pub fn to_owned(self) -> RemoteRefSymbolBuf {
381 RemoteRefSymbolBuf {
382 name: self.name.to_owned(),
383 remote: self.remote.to_owned(),
384 }
385 }
386}
387
388impl From<RemoteRefSymbol<'_>> for RemoteRefSymbolBuf {
389 fn from(value: RemoteRefSymbol<'_>) -> Self {
390 value.to_owned()
391 }
392}
393
394impl PartialEq<RemoteRefSymbol<'_>> for RemoteRefSymbolBuf {
395 fn eq(&self, other: &RemoteRefSymbol) -> bool {
396 self.as_ref() == *other
397 }
398}
399
400impl PartialEq<RemoteRefSymbol<'_>> for &RemoteRefSymbolBuf {
401 fn eq(&self, other: &RemoteRefSymbol) -> bool {
402 self.as_ref() == *other
403 }
404}
405
406impl PartialEq<RemoteRefSymbolBuf> for RemoteRefSymbol<'_> {
407 fn eq(&self, other: &RemoteRefSymbolBuf) -> bool {
408 *self == other.as_ref()
409 }
410}
411
412impl PartialEq<&RemoteRefSymbolBuf> for RemoteRefSymbol<'_> {
413 fn eq(&self, other: &&RemoteRefSymbolBuf) -> bool {
414 *self == other.as_ref()
415 }
416}
417
418impl Display for RemoteRefSymbolBuf {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 Display::fmt(&self.as_ref(), f)
421 }
422}
423
424impl Display for RemoteRefSymbol<'_> {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 let RemoteRefSymbol { name, remote } = self;
427 f.pad(&format!("{}@{}", name.as_symbol(), remote.as_symbol()))
428 }
429}
430
431fn is_safe_identifier(symbol: &str) -> bool {
436 let is_safe_byte = |b: u8| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'/');
438 symbol
439 .split(&['.', '-', '+'])
440 .all(|part| !part.is_empty() && part.as_bytes().iter().copied().all(is_safe_byte))
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn test_symbol_formatting() {
449 assert_eq!(RefSymbol::new("").to_string(), r#""""#);
450 assert_eq!(RefSymbol::new("foo").to_string(), "foo");
451 assert_eq!(RefSymbol::new("..").to_string(), r#""..""#);
452 assert_eq!(RefSymbol::new("柔術").to_string(), r#""柔術""#);
453 assert_eq!(
454 RemoteRefSymbol {
455 name: "foo".as_ref(),
456 remote: "bar".as_ref()
457 }
458 .to_string(),
459 "foo@bar"
460 );
461 assert_eq!(
462 RemoteRefSymbol {
463 name: ".".as_ref(),
464 remote: "-".as_ref()
465 }
466 .to_string(),
467 r#""."@"-""#
468 );
469 }
470
471 #[test]
472 fn test_is_safe_identifier() {
473 assert!(!is_safe_identifier(""));
475 assert!(is_safe_identifier("0"));
477 assert!(is_safe_identifier("foo_bar/baz"));
479 assert!(!is_safe_identifier("*/foo/**"));
481
482 assert!(is_safe_identifier("foo.bar-v1+7"));
484 assert!(!is_safe_identifier(".foo"));
486 assert!(!is_safe_identifier("foo."));
487 assert!(!is_safe_identifier("-foo"));
488 assert!(!is_safe_identifier("foo-"));
489 assert!(!is_safe_identifier("+foo"));
490 assert!(!is_safe_identifier("foo+"));
491 assert!(!is_safe_identifier("foo--bar"));
493 assert!(!is_safe_identifier("foo.+bar"));
494 assert!(!is_safe_identifier("foo++bar"));
495 assert!(!is_safe_identifier("foo+-bar"));
496
497 assert!(!is_safe_identifier("柔術"));
499 }
500}