use objects::{error::HeddleError, object::is_reserved_heddle_namespace};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("invalid ref name: {name}")]
pub struct RefNameError {
pub name: String,
}
pub fn validate_ref_name(name: &str) -> Result<(), RefNameError> {
if name.is_empty() {
return Err(invalid(name));
}
if name.bytes().any(|b| b < 0x20 || b == 0x7f) {
return Err(invalid(name));
}
if name.contains("..") || name.contains("//") {
return Err(invalid(name));
}
if name.contains('\\') || name.starts_with('/') || name.ends_with('/') {
return Err(invalid(name));
}
if name.starts_with('.') {
return Err(invalid(name));
}
if name
.split('/')
.any(|component| component.ends_with(".lock"))
{
return Err(invalid(name));
}
if is_reserved_heddle_namespace(name) {
return Err(invalid(name));
}
Ok(())
}
pub fn require_user_ref_name(name: impl AsRef<str>) -> objects::error::Result<()> {
validate_ref_name(name.as_ref()).map_err(|error| HeddleError::InvalidRefName(error.name))
}
fn invalid(name: &str) -> RefNameError {
RefNameError {
name: name.to_string(),
}
}
#[cfg(test)]
mod tests {
use objects::error::HeddleError;
use super::{require_user_ref_name, validate_ref_name};
#[test]
fn rejects_ref_ending_in_lock() {
assert!(validate_ref_name("refs/heads/foo.lock").is_err());
}
#[test]
fn rejects_any_component_ending_in_lock() {
assert!(validate_ref_name("refs/heads/foo.lock/bar").is_err());
assert!(validate_ref_name("refs/foo.lock/heads/bar").is_err());
}
#[test]
fn allows_lock_as_non_suffix() {
assert!(validate_ref_name("refs/heads/foo.locker").is_ok());
assert!(validate_ref_name("refs/heads/lock").is_ok());
assert!(validate_ref_name("refs/heads/foo.lock.bak").is_ok());
}
#[test]
fn allows_plain_ref() {
assert!(validate_ref_name("refs/heads/main").is_ok());
}
#[test]
fn reserves_heddle_rooted_names() {
assert!(validate_ref_name("heddle/frontier/main/hc-abc").is_err());
assert!(validate_ref_name("Heddle/x").is_err());
assert!(validate_ref_name("heddle").is_ok());
assert!(validate_ref_name("heddlefoo").is_ok());
assert!(validate_ref_name("my/heddle").is_ok());
assert!(validate_ref_name("main@review").is_ok());
assert!(validate_ref_name("main@hd-abc").is_ok());
}
#[test]
fn require_user_ref_name_rejects_reserved_namespace() {
let error = require_user_ref_name("heddle/frontier/main/hc-abc").unwrap_err();
assert!(
matches!(error, HeddleError::InvalidRefName(name) if name == "heddle/frontier/main/hc-abc")
);
assert!(require_user_ref_name("main").is_ok());
}
}