mkit_git_bridge/
refname.rs1use crate::error::Refusal;
15
16pub fn check_git_legal(name: &str) -> Result<(), Refusal> {
20 for segment in name.split('/') {
21 if segment.starts_with('.') {
22 return Err(refusal(name, "segment begins with '.'"));
23 }
24 if segment.ends_with('.') {
25 return Err(refusal(name, "segment ends with '.'"));
26 }
27 if segment.contains("..") {
28 return Err(refusal(name, "segment contains '..'"));
29 }
30 }
31 Ok(())
32}
33
34#[allow(clippy::case_sensitive_file_extension_comparisons)]
40pub fn check_tag_name(name: &[u8]) -> Result<(), &'static str> {
41 if name.is_empty() {
42 return Err("empty");
43 }
44 if name.len() > usize::from(mkit_core::object::TAG_NAME_MAX_LEN) {
48 return Err("over the tag-name length cap");
49 }
50 let Ok(s) = std::str::from_utf8(name) else {
51 return Err("not UTF-8");
52 };
53 for b in s.bytes() {
56 if !(b.is_ascii_alphanumeric() || b == b'.' || b == b'_' || b == b'-') {
57 return Err("byte outside the mkit ref-segment grammar");
58 }
59 }
60 if s == "." || s == ".." {
61 return Err("'.' or '..' name");
62 }
63 if s == "HEAD" {
64 return Err("'HEAD' is reserved");
65 }
66 if s.ends_with(".lock") {
67 return Err("'.lock' suffix");
68 }
69 check_git_legal(s).map_err(|_| "git-illegal dot placement")
70}
71
72fn refusal(name: &str, reason: &'static str) -> Refusal {
73 Refusal::RefName {
74 name: name.to_owned(),
75 reason,
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn plain_names_pass() {
85 for n in ["refs/heads/main", "refs/tags/v1.0.0", "refs/heads/a-b_c.d"] {
86 assert!(check_git_legal(n).is_ok(), "{n}");
87 }
88 }
89
90 #[test]
91 fn git_illegal_dot_shapes_refused() {
92 for n in [
93 "refs/heads/.hidden",
94 "refs/heads/trailing.",
95 "refs/heads/a..b",
96 ] {
97 assert!(check_git_legal(n).is_err(), "{n}");
98 }
99 }
100
101 #[test]
102 fn tag_names_checked() {
103 assert!(check_tag_name(b"v1.0.0").is_ok());
104 assert!(check_tag_name(b"with space").is_err());
105 assert!(check_tag_name(b".dot").is_err());
106 assert!(
107 check_tag_name(b"no/slash").is_err(),
108 "SPEC-OBJECTS 6a forbids '/'"
109 );
110 assert!(check_tag_name(b"HEAD").is_err());
111 assert!(check_tag_name(b"v1.lock").is_err());
112 assert!(check_tag_name("naïve".as_bytes()).is_err());
113 }
114}