1use std::collections::BTreeMap;
6use std::fmt;
7
8use serde::{Deserialize, Serialize};
9
10use crate::error::{Error, Result};
11
12#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
14#[serde(transparent)]
15pub struct GitId(pub String);
16
17const HEX_LEN: usize = 40;
19
20impl GitId {
21 pub fn parse(s: &str) -> Result<Self> {
23 if s.len() != HEX_LEN || !s.bytes().all(|b| b.is_ascii_hexdigit()) {
24 return Err(Error::Invalid(format!(
25 "git id {s:?}: not {HEX_LEN} hex digits"
26 )));
27 }
28 Ok(Self(s.to_ascii_lowercase()))
29 }
30
31 pub fn short(&self) -> &str {
32 &self.0[..self.0.len().min(10)]
33 }
34}
35
36impl fmt::Display for GitId {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 f.write_str(&self.0)
39 }
40}
41
42impl fmt::Debug for GitId {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(f, "GitId({})", self.short())
45 }
46}
47
48#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
49#[serde(rename_all = "lowercase")]
50pub enum FileMode {
51 Regular,
52 Executable,
53 Symlink,
54 Submodule,
56}
57
58#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
59pub struct FileEntry {
60 pub mode: FileMode,
61 pub blob: GitId,
62}
63
64pub type FileMap = BTreeMap<String, FileEntry>;
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn an_id_is_forty_hex_digits_kept_in_lowercase() {
73 let lower = "0123456789abcdef0123456789abcdef01234567";
74 assert_eq!(GitId::parse(lower).unwrap().0, lower);
75 let upper = lower.to_ascii_uppercase();
76 assert_eq!(GitId::parse(&upper).unwrap().0, lower);
77 for bad in [
78 "",
79 &lower[..39],
80 &format!("{lower}8"),
81 &lower.replace('a', "g"),
82 &"0".repeat(64),
83 ] {
84 assert!(GitId::parse(bad).is_err(), "{bad}");
85 }
86 let wide = format!("{}é", &lower[..38]);
88 assert_eq!(wide.len(), 40);
89 assert!(GitId::parse(&wide).is_err());
90 assert_eq!(GitId(lower.into()).short(), "0123456789");
91 assert_eq!(GitId("abc".into()).short(), "abc");
92 assert_eq!(format!("{:?}", GitId(lower.into())), "GitId(0123456789)");
93 assert_eq!(GitId(lower.into()).to_string(), lower);
94 }
95}