| |
&git::fmt::qualified!("refs/heads/quarterly/hardened/15-stable/main/2026q2")
|
| |
));
|
| |
}
|
| + |
|
| + |
#[test]
|
| + |
fn test_matches_properties() {
|
| + |
/*
|
| + |
* See: https://git-scm.com/docs/git-check-ref-format
|
| + |
* They can include slash / for hierarchical (directory) grouping, but no slash-separated component can begin with a dot . or end with the sequence .lock.
|
| + |
* They must contain at least one /. This enforces the presence of a category like heads/, tags/ etc. but the actual names are not restricted. If the --allow-onelevel option is used, this rule is waived.
|
| + |
* They cannot have two consecutive dots .. anywhere.
|
| + |
* They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
|
| + |
* They cannot have question-mark ?, asterisk *, or open bracket [ anywhere. See the --refspec-pattern option below for an exception to this rule.
|
| + |
* They cannot begin or end with a slash / or contain multiple consecutive slashes (see the --normalize option below for an exception to this rule).
|
| + |
* They cannot end with a dot ..
|
| + |
* They cannot contain a sequence @{.
|
| + |
* They cannot be the single character @.
|
| + |
* They cannot contain a \.
|
| + |
*/
|
| + |
fn make_valid_comp(s: String) -> String {
|
| + |
let mut comp: String = s
|
| + |
.chars()
|
| + |
.filter(|&c| {
|
| + |
let is_control_or_space = c <= ' ' || c == '\x7F';
|
| + |
let is_forbidden_sym = ['~', '^', ':', '?', '*', '[', '\\', '/'].contains(&c);
|
| + |
!is_control_or_space && !is_forbidden_sym
|
| + |
})
|
| + |
.collect();
|
| + |
|
| + |
while comp.contains("..") {
|
| + |
comp = comp.replace("..", ".");
|
| + |
}
|
| + |
while comp.contains("@{") {
|
| + |
comp = comp.replace("@{", "@");
|
| + |
}
|
| + |
|
| + |
if comp.starts_with('.') {
|
| + |
comp.insert(0, 'a');
|
| + |
}
|
| + |
if comp.ends_with(".lock") {
|
| + |
comp.push('a');
|
| + |
}
|
| + |
if comp.ends_with('.') {
|
| + |
comp.push('a');
|
| + |
}
|
| + |
if comp == "@" {
|
| + |
comp.push('a');
|
| + |
}
|
| + |
|
| + |
if comp.is_empty() {
|
| + |
comp.push('a');
|
| + |
}
|
| + |
|
| + |
comp
|
| + |
}
|
| + |
|
| + |
fn to_refname(name: &str) -> git::fmt::Qualified<'_> {
|
| + |
let refstr = git::fmt::RefStr::try_from_str(name).unwrap();
|
| + |
git::fmt::Qualified::from_refstr(refstr).unwrap()
|
| + |
}
|
| + |
fn prop_identity(c1: String, c2: String, c3: String) -> bool {
|
| + |
let c1 = make_valid_comp(c1);
|
| + |
let c2 = make_valid_comp(c2);
|
| + |
let c3 = make_valid_comp(c3);
|
| + |
|
| + |
let name = format!("refs/{c1}/{c2}/{c3}");
|
| + |
|
| + |
let pattern: RawPattern =
|
| + |
serde_json::from_value(serde_json::Value::String(name.clone())).unwrap();
|
| + |
|
| + |
println!("refs/{c1}/{c2}/{c3}");
|
| + |
matches(&pattern, &to_refname(&name))
|
| + |
}
|
| + |
|
| + |
fn prop_prefix(c1: String, c2: String, c3: String) -> bool {
|
| + |
let c1 = make_valid_comp(c1);
|
| + |
let c2 = make_valid_comp(c2);
|
| + |
let c3 = make_valid_comp(c3);
|
| + |
|
| + |
let pat_str = format!("refs/{c1}/*");
|
| + |
let pattern: RawPattern =
|
| + |
serde_json::from_value(serde_json::Value::String(pat_str)).unwrap();
|
| + |
|
| + |
matches(&pattern, &to_refname(&format!("refs/{c1}/{c2}/{c3}")))
|
| + |
&& matches(&pattern, &to_refname(&format!("refs/{c1}/{c2}")))
|
| + |
&& matches(&pattern, &to_refname(&format!("refs/{c1}/{c3}")))
|
| + |
&& matches(&pattern, &to_refname(&format!("refs/{c1}/{c3}/{c2}")))
|
| + |
}
|
| + |
|
| + |
fn prop_suffix(c1: String, c2: String, c3: String) -> bool {
|
| + |
let c1 = make_valid_comp(c1);
|
| + |
let c2 = make_valid_comp(c2);
|
| + |
let c3 = make_valid_comp(c3);
|
| + |
|
| + |
let pat_str = format!("refs/*/{c3}");
|
| + |
let pattern: RawPattern =
|
| + |
serde_json::from_value(serde_json::Value::String(pat_str)).unwrap();
|
| + |
|
| + |
matches(&pattern, &to_refname(&format!("refs/{c1}/{c2}/{c3}")))
|
| + |
&& matches(&pattern, &to_refname(&format!("refs/{c2}/{c3}")))
|
| + |
&& matches(&pattern, &to_refname(&format!("refs/{c1}/{c3}")))
|
| + |
&& matches(&pattern, &to_refname(&format!("refs/{c2}/{c1}/{c3}")))
|
| + |
}
|
| + |
|
| + |
fn prop_trailing_asterisk_partial_component(c1: String, c2: String, c3: String) -> bool {
|
| + |
let c1 = make_valid_comp(c1);
|
| + |
let c2 = make_valid_comp(c2);
|
| + |
let c3 = make_valid_comp(c3);
|
| + |
|
| + |
let pat_str = format!("refs/{c1}/{c2}*");
|
| + |
let pattern: RawPattern =
|
| + |
serde_json::from_value(serde_json::Value::String(pat_str)).unwrap();
|
| + |
|
| + |
matches(&pattern, &to_refname(&format!("refs/{c1}/{c2}{c3}")))
|
| + |
&& matches(&pattern, &to_refname(&format!("refs/{c1}/{c2}-{c3}")))
|
| + |
&& matches(&pattern, &to_refname(&format!("refs/{c1}/{c2}/{c3}")))
|
| + |
}
|
| + |
fn prop_prefix_negative(c1: String, c2: String, c3: String) -> bool {
|
| + |
let (c1, c2, c3) = (
|
| + |
make_valid_comp(c1),
|
| + |
make_valid_comp(c2),
|
| + |
make_valid_comp(c3),
|
| + |
);
|
| + |
if c1 == c2 {
|
| + |
return true;
|
| + |
}
|
| + |
|
| + |
let pat_str = format!("refs/{c1}/*");
|
| + |
let pattern: RawPattern =
|
| + |
serde_json::from_value(serde_json::Value::String(pat_str)).unwrap();
|
| + |
|
| + |
!matches(&pattern, &to_refname(&format!("refs/{c2}/{c3}")))
|
| + |
&& !matches(&pattern, &to_refname(&format!("refs/{c3}/{c2}")))
|
| + |
}
|
| + |
|
| + |
fn prop_suffix_negative(c1: String, c2: String, c3: String) -> bool {
|
| + |
let (c1, c2, c3) = (
|
| + |
make_valid_comp(c1),
|
| + |
make_valid_comp(c2),
|
| + |
make_valid_comp(c3),
|
| + |
);
|
| + |
if c2 == c3 {
|
| + |
return true;
|
| + |
}
|
| + |
|
| + |
let pat_str = format!("refs/*/{c3}");
|
| + |
let pattern: RawPattern =
|
| + |
serde_json::from_value(serde_json::Value::String(pat_str)).unwrap();
|
| + |
|
| + |
!matches(&pattern, &to_refname(&format!("refs/{c1}/{c2}")))
|
| + |
&& !matches(&pattern, &to_refname(&format!("refs/{c2}/{c1}")))
|
| + |
}
|
| + |
|
| + |
qcheck::QuickCheck::new().quickcheck(prop_identity as fn(String, String, String) -> bool);
|
| + |
qcheck::QuickCheck::new().quickcheck(prop_prefix as fn(String, String, String) -> bool);
|
| + |
qcheck::QuickCheck::new().quickcheck(prop_suffix as fn(String, String, String) -> bool);
|
| + |
qcheck::QuickCheck::new().quickcheck(
|
| + |
prop_trailing_asterisk_partial_component as fn(String, String, String) -> bool,
|
| + |
);
|
| + |
qcheck::QuickCheck::new()
|
| + |
.quickcheck(prop_prefix_negative as fn(String, String, String) -> bool);
|
| + |
qcheck::QuickCheck::new()
|
| + |
.quickcheck(prop_suffix_negative as fn(String, String, String) -> bool);
|
| + |
}
|
| |
}
|