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