use crate::git::GitReference;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RefNamespace {
Heads,
Tags,
Remotes,
}
impl RefNamespace {
pub fn from_component(component: &[u8]) -> Option<Self> {
match component {
b"heads" => Some(Self::Heads),
b"tags" => Some(Self::Tags),
b"remotes" => Some(Self::Remotes),
_ => None,
}
}
pub fn prefix(self) -> &'static [u8] {
match self {
Self::Heads => b"refs/heads/",
Self::Tags => b"refs/tags/",
Self::Remotes => b"refs/remotes/",
}
}
pub fn full_name(self, relative: &[u8]) -> Vec<u8> {
let mut full_name = self.prefix().to_vec();
full_name.extend_from_slice(relative);
full_name
}
pub fn prefix_with_relative(self, relative: &[u8]) -> Vec<u8> {
let mut prefix = self.full_name(relative);
prefix.push(b'/');
prefix
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConsumedPathSpec {
pub consumed: usize,
pub spec: Vec<u8>,
}
pub fn join_path_components(components: &[Vec<u8>]) -> Vec<u8> {
let mut joined = Vec::new();
for (index, component) in components.iter().enumerate() {
if index > 0 {
joined.push(b'/');
}
joined.extend_from_slice(component);
}
joined
}
pub fn revision_candidates(components: &[Vec<u8>]) -> Vec<ConsumedPathSpec> {
(1..=components.len())
.rev()
.map(|consumed| ConsumedPathSpec {
consumed,
spec: join_path_components(&components[..consumed]),
})
.collect()
}
pub fn reference_candidates(
namespace: RefNamespace,
components: &[Vec<u8>],
) -> Vec<ConsumedPathSpec> {
revision_candidates(components)
.into_iter()
.map(|candidate| ConsumedPathSpec {
consumed: candidate.consumed,
spec: namespace.full_name(&candidate.spec),
})
.collect()
}
pub fn reference_revision_name(reference: &GitReference) -> Option<&[u8]> {
reference
.full_name
.strip_prefix(b"refs/heads/")
.or_else(|| reference.full_name.strip_prefix(b"refs/tags/"))
.or_else(|| reference.full_name.strip_prefix(b"refs/remotes/"))
}
pub fn is_hex_revision_spec(spec: &[u8]) -> bool {
spec.len() >= 4 && spec.iter().all(u8::is_ascii_hexdigit)
}
pub fn is_valid_tree_entry_name(name: &[u8]) -> bool {
!(name.contains(&0) || name.contains(&b'/') || name == b"." || name == b"..")
}
pub fn split_path_bytes(path: &[u8]) -> Vec<Vec<u8>> {
path.split(|byte| *byte == b'/')
.map(<[u8]>::to_vec)
.collect()
}
#[cfg(test)]
mod tests {
use super::{
is_hex_revision_spec, is_valid_tree_entry_name, join_path_components, reference_candidates,
revision_candidates, split_path_bytes, RefNamespace,
};
#[test]
fn revision_candidates_are_greedy() {
let components = vec![b"feature".to_vec(), b"x".to_vec(), b"README.md".to_vec()];
assert_eq!(
revision_candidates(&components)
.into_iter()
.map(|candidate| candidate.spec)
.collect::<Vec<_>>(),
vec![
b"feature/x/README.md".to_vec(),
b"feature/x".to_vec(),
b"feature".to_vec(),
]
);
}
#[test]
fn reference_candidates_include_the_namespace_prefix() {
let components = vec![b"feature".to_vec(), b"x".to_vec()];
assert_eq!(
reference_candidates(RefNamespace::Heads, &components)
.into_iter()
.map(|candidate| candidate.spec)
.collect::<Vec<_>>(),
vec![
b"refs/heads/feature/x".to_vec(),
b"refs/heads/feature".to_vec()
]
);
}
#[test]
fn basic_path_helpers_match_expected_rules() {
assert_eq!(
join_path_components(&[b"refs".to_vec(), b"heads".to_vec(), b"main".to_vec()]),
b"refs/heads/main".to_vec()
);
assert_eq!(
split_path_bytes(b"refs/heads/main"),
vec![b"refs".to_vec(), b"heads".to_vec(), b"main".to_vec()]
);
assert!(is_hex_revision_spec(b"deadbeef"));
assert!(!is_hex_revision_spec(b"HEAD"));
assert!(is_valid_tree_entry_name(b"README.md"));
assert!(!is_valid_tree_entry_name(b"."));
assert!(!is_valid_tree_entry_name(b"dir/name"));
}
}